diff options
Diffstat (limited to 'libs/assimp/code')
434 files changed, 200451 insertions, 0 deletions
diff --git a/libs/assimp/code/.editorconfig b/libs/assimp/code/.editorconfig new file mode 100644 index 0000000..4a194a3 --- /dev/null +++ b/libs/assimp/code/.editorconfig @@ -0,0 +1,8 @@ +# See <http://EditorConfig.org> for details + +[*.{h,hpp,c,cpp}] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_size = 4 +indent_style = space diff --git a/libs/assimp/code/AssetLib/3DS/3DSConverter.cpp b/libs/assimp/code/AssetLib/3DS/3DSConverter.cpp new file mode 100644 index 0000000..5a01429 --- /dev/null +++ b/libs/assimp/code/AssetLib/3DS/3DSConverter.cpp @@ -0,0 +1,807 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the 3ds importer class */ + +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +// internal headers +#include "3DSLoader.h" +#include "Common/TargetAnimation.h" +#include <assimp/StringComparison.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <cctype> +#include <memory> + +using namespace Assimp; + +static const unsigned int NotSet = 0xcdcdcdcd; + +// ------------------------------------------------------------------------------------------------ +// Setup final material indices, generae a default material if necessary +void Discreet3DSImporter::ReplaceDefaultMaterial() { + // Try to find an existing material that matches the + // typical default material setting: + // - no textures + // - diffuse color (in grey!) + // NOTE: This is here to workaround the fact that some + // exporters are writing a default material, too. + unsigned int idx(NotSet); + for (unsigned int i = 0; i < mScene->mMaterials.size(); ++i) { + std::string s = mScene->mMaterials[i].mName; + for (char & it : s) { + it = static_cast<char>(::tolower(static_cast<unsigned char>(it))); + } + + if (std::string::npos == s.find("default")) continue; + + if (mScene->mMaterials[i].mDiffuse.r != + mScene->mMaterials[i].mDiffuse.g || + mScene->mMaterials[i].mDiffuse.r != + mScene->mMaterials[i].mDiffuse.b) continue; + + if (ContainsTextures(i)) { + continue; + } + idx = i; + } + if (NotSet == idx) { + idx = (unsigned int)mScene->mMaterials.size(); + } + + // now iterate through all meshes and through all faces and + // find all faces that are using the default material + unsigned int cnt = 0; + for (std::vector<D3DS::Mesh>::iterator + i = mScene->mMeshes.begin(); + i != mScene->mMeshes.end(); ++i) { + for (std::vector<unsigned int>::iterator + a = (*i).mFaceMaterials.begin(); + a != (*i).mFaceMaterials.end(); ++a) { + // NOTE: The additional check seems to be necessary, + // some exporters seem to generate invalid data here + if (0xcdcdcdcd == (*a)) { + (*a) = idx; + ++cnt; + } else if ((*a) >= mScene->mMaterials.size()) { + (*a) = idx; + ASSIMP_LOG_WARN("Material index overflow in 3DS file. Using default material"); + ++cnt; + } + } + } + if (cnt && idx == mScene->mMaterials.size()) { + // We need to create our own default material + D3DS::Material sMat("%%%DEFAULT"); + sMat.mDiffuse = aiColor3D(0.3f, 0.3f, 0.3f); + mScene->mMaterials.push_back(sMat); + + ASSIMP_LOG_INFO("3DS: Generating default material"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Check whether all indices are valid. Otherwise we'd crash before the validation step is reached +void Discreet3DSImporter::CheckIndices(D3DS::Mesh &sMesh) { + for (std::vector<D3DS::Face>::iterator i = sMesh.mFaces.begin(); i != sMesh.mFaces.end(); ++i) { + // check whether all indices are in range + for (unsigned int a = 0; a < 3; ++a) { + if ((*i).mIndices[a] >= sMesh.mPositions.size()) { + ASSIMP_LOG_WARN("3DS: Vertex index overflow)"); + (*i).mIndices[a] = (uint32_t)sMesh.mPositions.size() - 1; + } + if (!sMesh.mTexCoords.empty() && (*i).mIndices[a] >= sMesh.mTexCoords.size()) { + ASSIMP_LOG_WARN("3DS: Texture coordinate index overflow)"); + (*i).mIndices[a] = (uint32_t)sMesh.mTexCoords.size() - 1; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Generate out unique verbose format representation +void Discreet3DSImporter::MakeUnique(D3DS::Mesh &sMesh) { + // TODO: really necessary? I don't think. Just a waste of memory and time + // to do it now in a separate buffer. + + // Allocate output storage + std::vector<aiVector3D> vNew(sMesh.mFaces.size() * 3); + std::vector<aiVector3D> vNew2; + if (sMesh.mTexCoords.size()) + vNew2.resize(sMesh.mFaces.size() * 3); + + for (unsigned int i = 0, base = 0; i < sMesh.mFaces.size(); ++i) { + D3DS::Face &face = sMesh.mFaces[i]; + + // Positions + for (unsigned int a = 0; a < 3; ++a, ++base) { + vNew[base] = sMesh.mPositions[face.mIndices[a]]; + if (sMesh.mTexCoords.size()) + vNew2[base] = sMesh.mTexCoords[face.mIndices[a]]; + + face.mIndices[a] = base; + } + } + sMesh.mPositions = vNew; + sMesh.mTexCoords = vNew2; +} + +// ------------------------------------------------------------------------------------------------ +// Convert a 3DS texture to texture keys in an aiMaterial +void CopyTexture(aiMaterial &mat, D3DS::Texture &texture, aiTextureType type) { + // Setup the texture name + aiString tex; + tex.Set(texture.mMapName); + mat.AddProperty(&tex, AI_MATKEY_TEXTURE(type, 0)); + + // Setup the texture blend factor + if (is_not_qnan(texture.mTextureBlend)) + mat.AddProperty<ai_real>(&texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type, 0)); + + // Setup the texture mapping mode + int mapMode = static_cast<int>(texture.mMapMode); + mat.AddProperty<int>(&mapMode, 1, AI_MATKEY_MAPPINGMODE_U(type, 0)); + mat.AddProperty<int>(&mapMode, 1, AI_MATKEY_MAPPINGMODE_V(type, 0)); + + // Mirroring - double the scaling values + // FIXME: this is not really correct ... + if (texture.mMapMode == aiTextureMapMode_Mirror) { + texture.mScaleU *= 2.0; + texture.mScaleV *= 2.0; + texture.mOffsetU /= 2.0; + texture.mOffsetV /= 2.0; + } + + // Setup texture UV transformations + mat.AddProperty<ai_real>(&texture.mOffsetU, 5, AI_MATKEY_UVTRANSFORM(type, 0)); +} + +// ------------------------------------------------------------------------------------------------ +// Convert a 3DS material to an aiMaterial +void Discreet3DSImporter::ConvertMaterial(D3DS::Material &oldMat, + aiMaterial &mat) { + // NOTE: Pass the background image to the viewer by bypassing the + // material system. This is an evil hack, never do it again! + if (0 != mBackgroundImage.length() && bHasBG) { + aiString tex; + tex.Set(mBackgroundImage); + mat.AddProperty(&tex, AI_MATKEY_GLOBAL_BACKGROUND_IMAGE); + + // Be sure this is only done for the first material + mBackgroundImage = std::string(); + } + + // At first add the base ambient color of the scene to the material + oldMat.mAmbient.r += mClrAmbient.r; + oldMat.mAmbient.g += mClrAmbient.g; + oldMat.mAmbient.b += mClrAmbient.b; + + aiString name; + name.Set(oldMat.mName); + mat.AddProperty(&name, AI_MATKEY_NAME); + + // Material colors + mat.AddProperty(&oldMat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT); + mat.AddProperty(&oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + mat.AddProperty(&oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); + mat.AddProperty(&oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); + + // Phong shininess and shininess strength + if (D3DS::Discreet3DS::Phong == oldMat.mShading || + D3DS::Discreet3DS::Metal == oldMat.mShading) { + if (!oldMat.mSpecularExponent || !oldMat.mShininessStrength) { + oldMat.mShading = D3DS::Discreet3DS::Gouraud; + } else { + mat.AddProperty(&oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS); + mat.AddProperty(&oldMat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH); + } + } + + // Opacity + mat.AddProperty<ai_real>(&oldMat.mTransparency, 1, AI_MATKEY_OPACITY); + + // Bump height scaling + mat.AddProperty<ai_real>(&oldMat.mBumpHeight, 1, AI_MATKEY_BUMPSCALING); + + // Two sided rendering? + if (oldMat.mTwoSided) { + int i = 1; + mat.AddProperty<int>(&i, 1, AI_MATKEY_TWOSIDED); + } + + // Shading mode + aiShadingMode eShading = aiShadingMode_NoShading; + switch (oldMat.mShading) { + case D3DS::Discreet3DS::Flat: + eShading = aiShadingMode_Flat; + break; + + // I don't know what "Wire" shading should be, + // assume it is simple lambertian diffuse shading + case D3DS::Discreet3DS::Wire: { + // Set the wireframe flag + unsigned int iWire = 1; + mat.AddProperty<int>((int *)&iWire, 1, AI_MATKEY_ENABLE_WIREFRAME); + } + + case D3DS::Discreet3DS::Gouraud: + eShading = aiShadingMode_Gouraud; + break; + + // assume cook-torrance shading for metals. + case D3DS::Discreet3DS::Phong: + eShading = aiShadingMode_Phong; + break; + + case D3DS::Discreet3DS::Metal: + eShading = aiShadingMode_CookTorrance; + break; + + // FIX to workaround a warning with GCC 4 who complained + // about a missing case Blinn: here - Blinn isn't a valid + // value in the 3DS Loader, it is just needed for ASE + case D3DS::Discreet3DS::Blinn: + eShading = aiShadingMode_Blinn; + break; + } + int eShading_ = static_cast<int>(eShading); + mat.AddProperty<int>(&eShading_, 1, AI_MATKEY_SHADING_MODEL); + + // DIFFUSE texture + if (oldMat.sTexDiffuse.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexDiffuse, aiTextureType_DIFFUSE); + + // SPECULAR texture + if (oldMat.sTexSpecular.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexSpecular, aiTextureType_SPECULAR); + + // OPACITY texture + if (oldMat.sTexOpacity.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexOpacity, aiTextureType_OPACITY); + + // EMISSIVE texture + if (oldMat.sTexEmissive.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexEmissive, aiTextureType_EMISSIVE); + + // BUMP texture + if (oldMat.sTexBump.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexBump, aiTextureType_HEIGHT); + + // SHININESS texture + if (oldMat.sTexShininess.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexShininess, aiTextureType_SHININESS); + + // REFLECTION texture + if (oldMat.sTexReflective.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexReflective, aiTextureType_REFLECTION); + + // Store the name of the material itself, too + if (oldMat.mName.length()) { + aiString tex; + tex.Set(oldMat.mName); + mat.AddProperty(&tex, AI_MATKEY_NAME); + } +} + +// ------------------------------------------------------------------------------------------------ +// Split meshes by their materials and generate output aiMesh'es +void Discreet3DSImporter::ConvertMeshes(aiScene *pcOut) { + std::vector<aiMesh *> avOutMeshes; + avOutMeshes.reserve(mScene->mMeshes.size() * 2); + + unsigned int iFaceCnt = 0, num = 0; + aiString name; + + // we need to split all meshes by their materials + for (std::vector<D3DS::Mesh>::iterator i = mScene->mMeshes.begin(); i != mScene->mMeshes.end(); ++i) { + std::unique_ptr<std::vector<unsigned int>[]> aiSplit(new std::vector<unsigned int>[mScene->mMaterials.size()]); + + name.length = ASSIMP_itoa10(name.data, num++); + + unsigned int iNum = 0; + for (std::vector<unsigned int>::const_iterator a = (*i).mFaceMaterials.begin(); + a != (*i).mFaceMaterials.end(); ++a, ++iNum) { + aiSplit[*a].push_back(iNum); + } + // now generate submeshes + for (unsigned int p = 0; p < mScene->mMaterials.size(); ++p) { + if (aiSplit[p].empty()) { + continue; + } + aiMesh *meshOut = new aiMesh(); + meshOut->mName = name; + meshOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + // be sure to setup the correct material index + meshOut->mMaterialIndex = p; + + // use the color data as temporary storage + meshOut->mColors[0] = (aiColor4D *)(&*i); + avOutMeshes.push_back(meshOut); + + // convert vertices + meshOut->mNumFaces = (unsigned int)aiSplit[p].size(); + meshOut->mNumVertices = meshOut->mNumFaces * 3; + + // allocate enough storage for faces + meshOut->mFaces = new aiFace[meshOut->mNumFaces]; + iFaceCnt += meshOut->mNumFaces; + + meshOut->mVertices = new aiVector3D[meshOut->mNumVertices]; + meshOut->mNormals = new aiVector3D[meshOut->mNumVertices]; + if ((*i).mTexCoords.size()) { + meshOut->mTextureCoords[0] = new aiVector3D[meshOut->mNumVertices]; + } + for (unsigned int q = 0, base = 0; q < aiSplit[p].size(); ++q) { + unsigned int index = aiSplit[p][q]; + aiFace &face = meshOut->mFaces[q]; + + face.mIndices = new unsigned int[3]; + face.mNumIndices = 3; + + for (unsigned int a = 0; a < 3; ++a, ++base) { + unsigned int idx = (*i).mFaces[index].mIndices[a]; + meshOut->mVertices[base] = (*i).mPositions[idx]; + meshOut->mNormals[base] = (*i).mNormals[idx]; + + if ((*i).mTexCoords.size()) + meshOut->mTextureCoords[0][base] = (*i).mTexCoords[idx]; + + face.mIndices[a] = base; + } + } + } + } + + // Copy them to the output array + pcOut->mNumMeshes = (unsigned int)avOutMeshes.size(); + pcOut->mMeshes = new aiMesh *[pcOut->mNumMeshes](); + for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) { + pcOut->mMeshes[a] = avOutMeshes[a]; + } + + // We should have at least one face here + if (!iFaceCnt) { + throw DeadlyImportError("No faces loaded. The mesh is empty"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Add a node to the scenegraph and setup its final transformation +void Discreet3DSImporter::AddNodeToGraph(aiScene *pcSOut, aiNode *pcOut, + D3DS::Node *pcIn, aiMatrix4x4 & /*absTrafo*/) { + std::vector<unsigned int> iArray; + iArray.reserve(3); + + aiMatrix4x4 abs; + + // Find all meshes with the same name as the node + for (unsigned int a = 0; a < pcSOut->mNumMeshes; ++a) { + const D3DS::Mesh *pcMesh = (const D3DS::Mesh *)pcSOut->mMeshes[a]->mColors[0]; + ai_assert(nullptr != pcMesh); + + if (pcIn->mName == pcMesh->mName) + iArray.push_back(a); + } + if (!iArray.empty()) { + // The matrix should be identical for all meshes with the + // same name. It HAS to be identical for all meshes ..... + D3DS::Mesh *imesh = ((D3DS::Mesh *)pcSOut->mMeshes[iArray[0]]->mColors[0]); + + // Compute the inverse of the transformation matrix to move the + // vertices back to their relative and local space + aiMatrix4x4 mInv = imesh->mMat, mInvTransposed = imesh->mMat; + mInv.Inverse(); + mInvTransposed.Transpose(); + aiVector3D pivot = pcIn->vPivot; + + pcOut->mNumMeshes = (unsigned int)iArray.size(); + pcOut->mMeshes = new unsigned int[iArray.size()]; + for (unsigned int i = 0; i < iArray.size(); ++i) { + const unsigned int iIndex = iArray[i]; + aiMesh *const mesh = pcSOut->mMeshes[iIndex]; + + if (mesh->mColors[1] == nullptr) { + // Transform the vertices back into their local space + // fixme: consider computing normals after this, so we don't need to transform them + const aiVector3D *const pvEnd = mesh->mVertices + mesh->mNumVertices; + aiVector3D *pvCurrent = mesh->mVertices, *t2 = mesh->mNormals; + + for (; pvCurrent != pvEnd; ++pvCurrent, ++t2) { + *pvCurrent = mInv * (*pvCurrent); + *t2 = mInvTransposed * (*t2); + } + + // Handle negative transformation matrix determinant -> invert vertex x + if (imesh->mMat.Determinant() < 0.0f) { + /* we *must* have normals */ + for (pvCurrent = mesh->mVertices, t2 = mesh->mNormals; pvCurrent != pvEnd; ++pvCurrent, ++t2) { + pvCurrent->x *= -1.f; + t2->x *= -1.f; + } + ASSIMP_LOG_INFO("3DS: Flipping mesh X-Axis"); + } + + // Handle pivot point + if (pivot.x || pivot.y || pivot.z) { + for (pvCurrent = mesh->mVertices; pvCurrent != pvEnd; ++pvCurrent) { + *pvCurrent -= pivot; + } + } + + mesh->mColors[1] = (aiColor4D *)1; + } else + mesh->mColors[1] = (aiColor4D *)1; + + // Setup the mesh index + pcOut->mMeshes[i] = iIndex; + } + } + + // Setup the name of the node + // First instance keeps its name otherwise something might break, all others will be postfixed with their instance number + if (pcIn->mInstanceNumber > 1) { + char tmp[12]; + ASSIMP_itoa10(tmp, pcIn->mInstanceNumber); + std::string tempStr = pcIn->mName + "_inst_"; + tempStr += tmp; + pcOut->mName.Set(tempStr); + } else + pcOut->mName.Set(pcIn->mName); + + // Now build the transformation matrix of the node + // ROTATION + if (pcIn->aRotationKeys.size()) { + + // FIX to get to Assimp's quaternion conventions + for (std::vector<aiQuatKey>::iterator it = pcIn->aRotationKeys.begin(); it != pcIn->aRotationKeys.end(); ++it) { + (*it).mValue.w *= -1.f; + } + + pcOut->mTransformation = aiMatrix4x4(pcIn->aRotationKeys[0].mValue.GetMatrix()); + } else if (pcIn->aCameraRollKeys.size()) { + aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(-pcIn->aCameraRollKeys[0].mValue), + pcOut->mTransformation); + } + + // SCALING + aiMatrix4x4 &m = pcOut->mTransformation; + if (pcIn->aScalingKeys.size()) { + const aiVector3D &v = pcIn->aScalingKeys[0].mValue; + m.a1 *= v.x; + m.b1 *= v.x; + m.c1 *= v.x; + m.a2 *= v.y; + m.b2 *= v.y; + m.c2 *= v.y; + m.a3 *= v.z; + m.b3 *= v.z; + m.c3 *= v.z; + } + + // TRANSLATION + if (pcIn->aPositionKeys.size()) { + const aiVector3D &v = pcIn->aPositionKeys[0].mValue; + m.a4 += v.x; + m.b4 += v.y; + m.c4 += v.z; + } + + // Generate animation channels for the node + if (pcIn->aPositionKeys.size() > 1 || pcIn->aRotationKeys.size() > 1 || + pcIn->aScalingKeys.size() > 1 || pcIn->aCameraRollKeys.size() > 1 || + pcIn->aTargetPositionKeys.size() > 1) { + aiAnimation *anim = pcSOut->mAnimations[0]; + ai_assert(nullptr != anim); + + if (pcIn->aCameraRollKeys.size() > 1) { + ASSIMP_LOG_VERBOSE_DEBUG("3DS: Converting camera roll track ..."); + + // Camera roll keys - in fact they're just rotations + // around the camera's z axis. The angles are given + // in degrees (and they're clockwise). + pcIn->aRotationKeys.resize(pcIn->aCameraRollKeys.size()); + for (unsigned int i = 0; i < pcIn->aCameraRollKeys.size(); ++i) { + aiQuatKey &q = pcIn->aRotationKeys[i]; + aiFloatKey &f = pcIn->aCameraRollKeys[i]; + + q.mTime = f.mTime; + + // FIX to get to Assimp quaternion conventions + q.mValue = aiQuaternion(0.f, 0.f, AI_DEG_TO_RAD(/*-*/ f.mValue)); + } + } +#if 0 + if (pcIn->aTargetPositionKeys.size() > 1) + { + ASSIMP_LOG_VERBOSE_DEBUG("3DS: Converting target track ..."); + + // Camera or spot light - need to convert the separate + // target position channel to our representation + TargetAnimationHelper helper; + + if (pcIn->aPositionKeys.empty()) + { + // We can just pass zero here ... + helper.SetFixedMainAnimationChannel(aiVector3D()); + } + else helper.SetMainAnimationChannel(&pcIn->aPositionKeys); + helper.SetTargetAnimationChannel(&pcIn->aTargetPositionKeys); + + // Do the conversion + std::vector<aiVectorKey> distanceTrack; + helper.Process(&distanceTrack); + + // Now add a new node as child, name it <ourName>.Target + // and assign the distance track to it. This is that the + // information where the target is and how it moves is + // not lost + D3DS::Node* nd = new D3DS::Node(); + pcIn->push_back(nd); + + nd->mName = pcIn->mName + ".Target"; + + aiNodeAnim* nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim(); + nda->mNodeName.Set(nd->mName); + + nda->mNumPositionKeys = (unsigned int)distanceTrack.size(); + nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys]; + ::memcpy(nda->mPositionKeys,&distanceTrack[0], + sizeof(aiVectorKey)*nda->mNumPositionKeys); + } +#endif + + // Cameras or lights define their transformation in their parent node and in the + // corresponding light or camera chunks. However, we read and process the latter + // to to be able to return valid cameras/lights even if no scenegraph is given. + for (unsigned int n = 0; n < pcSOut->mNumCameras; ++n) { + if (pcSOut->mCameras[n]->mName == pcOut->mName) { + pcSOut->mCameras[n]->mLookAt = aiVector3D(0.f, 0.f, 1.f); + } + } + for (unsigned int n = 0; n < pcSOut->mNumLights; ++n) { + if (pcSOut->mLights[n]->mName == pcOut->mName) { + pcSOut->mLights[n]->mDirection = aiVector3D(0.f, 0.f, 1.f); + } + } + + // Allocate a new node anim and setup its name + aiNodeAnim *nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim(); + nda->mNodeName.Set(pcIn->mName); + + // POSITION keys + if (pcIn->aPositionKeys.size() > 0) { + nda->mNumPositionKeys = (unsigned int)pcIn->aPositionKeys.size(); + nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys]; + ::memcpy(nda->mPositionKeys, &pcIn->aPositionKeys[0], + sizeof(aiVectorKey) * nda->mNumPositionKeys); + } + + // ROTATION keys + if (pcIn->aRotationKeys.size() > 0) { + nda->mNumRotationKeys = (unsigned int)pcIn->aRotationKeys.size(); + nda->mRotationKeys = new aiQuatKey[nda->mNumRotationKeys]; + + // Rotations are quaternion offsets + aiQuaternion abs1; + for (unsigned int n = 0; n < nda->mNumRotationKeys; ++n) { + const aiQuatKey &q = pcIn->aRotationKeys[n]; + + abs1 = (n ? abs1 * q.mValue : q.mValue); + nda->mRotationKeys[n].mTime = q.mTime; + nda->mRotationKeys[n].mValue = abs1.Normalize(); + } + } + + // SCALING keys + if (pcIn->aScalingKeys.size() > 0) { + nda->mNumScalingKeys = (unsigned int)pcIn->aScalingKeys.size(); + nda->mScalingKeys = new aiVectorKey[nda->mNumScalingKeys]; + ::memcpy(nda->mScalingKeys, &pcIn->aScalingKeys[0], + sizeof(aiVectorKey) * nda->mNumScalingKeys); + } + } + + // Allocate storage for children + pcOut->mNumChildren = (unsigned int)pcIn->mChildren.size(); + pcOut->mChildren = new aiNode *[pcIn->mChildren.size()]; + + // Recursively process all children + const unsigned int size = static_cast<unsigned int>(pcIn->mChildren.size()); + for (unsigned int i = 0; i < size; ++i) { + pcOut->mChildren[i] = new aiNode(); + pcOut->mChildren[i]->mParent = pcOut; + AddNodeToGraph(pcSOut, pcOut->mChildren[i], pcIn->mChildren[i], abs); + } +} + +// ------------------------------------------------------------------------------------------------ +// Find out how many node animation channels we'll have finally +void CountTracks(D3DS::Node *node, unsigned int &cnt) { + ////////////////////////////////////////////////////////////////////////////// + // We will never generate more than one channel for a node, so + // this is rather easy here. + + if (node->aPositionKeys.size() > 1 || node->aRotationKeys.size() > 1 || + node->aScalingKeys.size() > 1 || node->aCameraRollKeys.size() > 1 || + node->aTargetPositionKeys.size() > 1) { + ++cnt; + + // account for the additional channel for the camera/spotlight target position + if (node->aTargetPositionKeys.size() > 1) ++cnt; + } + + // Recursively process all children + for (unsigned int i = 0; i < node->mChildren.size(); ++i) + CountTracks(node->mChildren[i], cnt); +} + +// ------------------------------------------------------------------------------------------------ +// Generate the output node graph +void Discreet3DSImporter::GenerateNodeGraph(aiScene *pcOut) { + pcOut->mRootNode = new aiNode(); + if (0 == mRootNode->mChildren.size()) { + ////////////////////////////////////////////////////////////////////////////// + // It seems the file is so messed up that it has not even a hierarchy. + // generate a flat hiearachy which looks like this: + // + // ROOT_NODE + // | + // ---------------------------------------- + // | | | | | + // MESH_0 MESH_1 MESH_2 ... MESH_N CAMERA_0 .... + // + ASSIMP_LOG_WARN("No hierarchy information has been found in the file. "); + + pcOut->mRootNode->mNumChildren = pcOut->mNumMeshes + + static_cast<unsigned int>(mScene->mCameras.size() + mScene->mLights.size()); + + pcOut->mRootNode->mChildren = new aiNode *[pcOut->mRootNode->mNumChildren]; + pcOut->mRootNode->mName.Set("<3DSDummyRoot>"); + + // Build dummy nodes for all meshes + unsigned int a = 0; + for (unsigned int i = 0; i < pcOut->mNumMeshes; ++i, ++a) { + aiNode *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode(); + pcNode->mParent = pcOut->mRootNode; + pcNode->mMeshes = new unsigned int[1]; + pcNode->mMeshes[0] = i; + pcNode->mNumMeshes = 1; + + // Build a name for the node + pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "3DSMesh_%u", i); + } + + // Build dummy nodes for all cameras + for (unsigned int i = 0; i < (unsigned int)mScene->mCameras.size(); ++i, ++a) { + aiNode *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode(); + pcNode->mParent = pcOut->mRootNode; + + // Build a name for the node + pcNode->mName = mScene->mCameras[i]->mName; + } + + // Build dummy nodes for all lights + for (unsigned int i = 0; i < (unsigned int)mScene->mLights.size(); ++i, ++a) { + aiNode *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode(); + pcNode->mParent = pcOut->mRootNode; + + // Build a name for the node + pcNode->mName = mScene->mLights[i]->mName; + } + } else { + // First of all: find out how many scaling, rotation and translation + // animation tracks we'll have afterwards + unsigned int numChannel = 0; + CountTracks(mRootNode, numChannel); + + if (numChannel) { + // Allocate a primary animation channel + pcOut->mNumAnimations = 1; + pcOut->mAnimations = new aiAnimation *[1]; + aiAnimation *anim = pcOut->mAnimations[0] = new aiAnimation(); + + anim->mName.Set("3DSMasterAnim"); + + // Allocate enough storage for all node animation channels, + // but don't set the mNumChannels member - we'll use it to + // index into the array + anim->mChannels = new aiNodeAnim *[numChannel]; + } + + aiMatrix4x4 m; + AddNodeToGraph(pcOut, pcOut->mRootNode, mRootNode, m); + } + + // We used the first and second vertex color set to store some temporary values so we need to cleanup here + for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) { + pcOut->mMeshes[a]->mColors[0] = nullptr; + pcOut->mMeshes[a]->mColors[1] = nullptr; + } + + pcOut->mRootNode->mTransformation = aiMatrix4x4( + 1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f) * + pcOut->mRootNode->mTransformation; + + // If the root node is unnamed name it "<3DSRoot>" + if (::strstr(pcOut->mRootNode->mName.data, "UNNAMED") || + (pcOut->mRootNode->mName.data[0] == '$' && pcOut->mRootNode->mName.data[1] == '$')) { + pcOut->mRootNode->mName.Set("<3DSRoot>"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Convert all meshes in the scene and generate the final output scene. +void Discreet3DSImporter::ConvertScene(aiScene *pcOut) { + // Allocate enough storage for all output materials + pcOut->mNumMaterials = (unsigned int)mScene->mMaterials.size(); + pcOut->mMaterials = new aiMaterial *[pcOut->mNumMaterials]; + + // ... and convert the 3DS materials to aiMaterial's + for (unsigned int i = 0; i < pcOut->mNumMaterials; ++i) { + aiMaterial *pcNew = new aiMaterial(); + ConvertMaterial(mScene->mMaterials[i], *pcNew); + pcOut->mMaterials[i] = pcNew; + } + + // Generate the output mesh list + ConvertMeshes(pcOut); + + // Now copy all light sources to the output scene + pcOut->mNumLights = (unsigned int)mScene->mLights.size(); + if (pcOut->mNumLights) { + pcOut->mLights = new aiLight *[pcOut->mNumLights]; + ::memcpy(pcOut->mLights, &mScene->mLights[0], sizeof(void *) * pcOut->mNumLights); + } + + // Now copy all cameras to the output scene + pcOut->mNumCameras = (unsigned int)mScene->mCameras.size(); + if (pcOut->mNumCameras) { + pcOut->mCameras = new aiCamera *[pcOut->mNumCameras]; + ::memcpy(pcOut->mCameras, &mScene->mCameras[0], sizeof(void *) * pcOut->mNumCameras); + } +} + +#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER diff --git a/libs/assimp/code/AssetLib/3DS/3DSExporter.cpp b/libs/assimp/code/AssetLib/3DS/3DSExporter.cpp new file mode 100644 index 0000000..71588f9 --- /dev/null +++ b/libs/assimp/code/AssetLib/3DS/3DSExporter.cpp @@ -0,0 +1,584 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_3DS_EXPORTER + +#include "AssetLib/3DS/3DSExporter.h" +#include "AssetLib/3DS/3DSHelper.h" +#include "AssetLib/3DS/3DSLoader.h" +#include "PostProcessing/SplitLargeMeshes.h" + +#include <assimp/SceneCombiner.h> +#include <assimp/StringComparison.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Exporter.hpp> +#include <assimp/IOSystem.hpp> + +#include <memory> + +namespace Assimp { + +using namespace D3DS; + +namespace { + +////////////////////////////////////////////////////////////////////////////////////// +// Scope utility to write a 3DS file chunk. +// +// Upon construction, the chunk header is written with the chunk type (flags) +// filled out, but the chunk size left empty. Upon destruction, the correct chunk +// size based on the then-position of the output stream cursor is filled in. +class ChunkWriter { + enum { + CHUNK_SIZE_NOT_SET = 0xdeadbeef, + SIZE_OFFSET = 2 + }; + +public: + ChunkWriter(StreamWriterLE &writer, uint16_t chunk_type) : + writer(writer) { + chunk_start_pos = writer.GetCurrentPos(); + writer.PutU2(chunk_type); + writer.PutU4((uint32_t)CHUNK_SIZE_NOT_SET); + } + + ~ChunkWriter() { + std::size_t head_pos = writer.GetCurrentPos(); + + ai_assert(head_pos > chunk_start_pos); + const std::size_t chunk_size = head_pos - chunk_start_pos; + + writer.SetCurrentPos(chunk_start_pos + SIZE_OFFSET); + writer.PutU4(static_cast<uint32_t>(chunk_size)); + writer.SetCurrentPos(head_pos); + } + +private: + StreamWriterLE &writer; + std::size_t chunk_start_pos; +}; + +// Return an unique name for a given |mesh| attached to |node| that +// preserves the mesh's given name if it has one. |index| is the index +// of the mesh in |aiScene::mMeshes|. +std::string GetMeshName(const aiMesh &mesh, unsigned int index, const aiNode &node) { + static const char underscore = '_'; + char postfix[10] = { 0 }; + ASSIMP_itoa10(postfix, index); + + std::string result = node.mName.C_Str(); + if (mesh.mName.length > 0) { + result += underscore; + result += mesh.mName.C_Str(); + } + return result + underscore + postfix; +} + +// Return an unique name for a given |mat| with original position |index| +// in |aiScene::mMaterials|. The name preserves the original material +// name if possible. +std::string GetMaterialName(const aiMaterial &mat, unsigned int index) { + static const std::string underscore = "_"; + char postfix[10] = { 0 }; + ASSIMP_itoa10(postfix, index); + + aiString mat_name; + if (AI_SUCCESS == mat.Get(AI_MATKEY_NAME, mat_name)) { + return mat_name.C_Str() + underscore + postfix; + } + + return "Material" + underscore + postfix; +} + +// Collect world transformations for each node +void CollectTrafos(const aiNode *node, std::map<const aiNode *, aiMatrix4x4> &trafos) { + const aiMatrix4x4 &parent = node->mParent ? trafos[node->mParent] : aiMatrix4x4(); + trafos[node] = parent * node->mTransformation; + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + CollectTrafos(node->mChildren[i], trafos); + } +} + +// Generate a flat list of the meshes (by index) assigned to each node +void CollectMeshes(const aiNode *node, std::multimap<const aiNode *, unsigned int> &meshes) { + for (unsigned int i = 0; i < node->mNumMeshes; ++i) { + meshes.insert(std::make_pair(node, node->mMeshes[i])); + } + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + CollectMeshes(node->mChildren[i], meshes); + } +} +} // namespace + +// ------------------------------------------------------------------------------------------------ +// Worker function for exporting a scene to 3DS. Prototyped and registered in Exporter.cpp +void ExportScene3DS(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) { + std::shared_ptr<IOStream> outfile(pIOSystem->Open(pFile, "wb")); + if (!outfile) { + throw DeadlyExportError("Could not open output .3ds file: " + std::string(pFile)); + } + + // TODO: This extra copy should be avoided and all of this made a preprocess + // requirement of the 3DS exporter. + // + // 3DS meshes can be max 0xffff (16 Bit) vertices and faces, respectively. + // SplitLargeMeshes can do this, but it requires the correct limit to be set + // which is not possible with the current way of specifying preprocess steps + // in |Exporter::ExportFormatEntry|. + aiScene *scenecopy_tmp; + SceneCombiner::CopyScene(&scenecopy_tmp, pScene); + std::unique_ptr<aiScene> scenecopy(scenecopy_tmp); + + SplitLargeMeshesProcess_Triangle tri_splitter; + tri_splitter.SetLimit(0xffff); + tri_splitter.Execute(scenecopy.get()); + + SplitLargeMeshesProcess_Vertex vert_splitter; + vert_splitter.SetLimit(0xffff); + vert_splitter.Execute(scenecopy.get()); + + // Invoke the actual exporter + Discreet3DSExporter exporter(outfile, scenecopy.get()); +} + +} // end of namespace Assimp + +// ------------------------------------------------------------------------------------------------ +Discreet3DSExporter::Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, const aiScene *scene) : + scene(scene), writer(outfile) { + CollectTrafos(scene->mRootNode, trafos); + CollectMeshes(scene->mRootNode, meshes); + + ChunkWriter curRootChunk(writer, Discreet3DS::CHUNK_MAIN); + + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_OBJMESH); + WriteMaterials(); + WriteMeshes(); + + { + ChunkWriter curChunk1(writer, Discreet3DS::CHUNK_MASTER_SCALE); + writer.PutF4(1.0f); + } + } + + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_KEYFRAMER); + WriteHierarchy(*scene->mRootNode, -1, -1); + } +} + +// ------------------------------------------------------------------------------------------------ +Discreet3DSExporter::~Discreet3DSExporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +int Discreet3DSExporter::WriteHierarchy(const aiNode &node, int seq, int sibling_level) { + // 3DS scene hierarchy is serialized as in http://www.martinreddy.net/gfx/3d/3DS.spec + { + ChunkWriter curRootChunk(writer, Discreet3DS::CHUNK_TRACKINFO); + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME); + + // Assimp node names are unique and distinct from all mesh-node + // names we generate; thus we can use them as-is + WriteString(node.mName); + + // Two unknown int16 values - it is even unclear if 0 is a safe value + // but luckily importers do not know better either. + writer.PutI4(0); + + int16_t hierarchy_pos = static_cast<int16_t>(seq); + if (sibling_level != -1) { + hierarchy_pos = (uint16_t)sibling_level; + } + + // Write the hierarchy position + writer.PutI2(hierarchy_pos); + } + } + + // TODO: write transformation chunks + + ++seq; + sibling_level = seq; + + // Write all children + for (unsigned int i = 0; i < node.mNumChildren; ++i) { + seq = WriteHierarchy(*node.mChildren[i], seq, i == 0 ? -1 : sibling_level); + } + + // Write all meshes as separate nodes to be able to reference the meshes by name + for (unsigned int i = 0; i < node.mNumMeshes; ++i) { + const bool first_child = node.mNumChildren == 0 && i == 0; + + const unsigned int mesh_idx = node.mMeshes[i]; + const aiMesh &mesh = *scene->mMeshes[mesh_idx]; + + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_TRACKINFO); + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME); + WriteString(GetMeshName(mesh, mesh_idx, node)); + + writer.PutI4(0); + writer.PutI2(static_cast<int16_t>(first_child ? seq : sibling_level)); + ++seq; + } + } + return seq; +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteMaterials() { + for (unsigned int i = 0; i < scene->mNumMaterials; ++i) { + ChunkWriter curRootChunk(writer, Discreet3DS::CHUNK_MAT_MATERIAL); + const aiMaterial &mat = *scene->mMaterials[i]; + + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MATNAME); + const std::string &name = GetMaterialName(mat, i); + WriteString(name); + } + + aiColor3D color; + if (mat.Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_DIFFUSE); + WriteColor(color); + } + + if (mat.Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_SPECULAR); + WriteColor(color); + } + + if (mat.Get(AI_MATKEY_COLOR_AMBIENT, color) == AI_SUCCESS) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_AMBIENT); + WriteColor(color); + } + + float f; + if (mat.Get(AI_MATKEY_OPACITY, f) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_TRANSPARENCY); + WritePercentChunk(1.0f - f); + } + + if (mat.Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_SELF_ILLUM); + WriteColor(color); + } + + aiShadingMode shading_mode = aiShadingMode_Flat; + if (mat.Get(AI_MATKEY_SHADING_MODEL, shading_mode) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHADING); + + Discreet3DS::shadetype3ds shading_mode_out; + switch (shading_mode) { + case aiShadingMode_Flat: + case aiShadingMode_NoShading: + shading_mode_out = Discreet3DS::Flat; + break; + + case aiShadingMode_Gouraud: + case aiShadingMode_Toon: + case aiShadingMode_OrenNayar: + case aiShadingMode_Minnaert: + shading_mode_out = Discreet3DS::Gouraud; + break; + + case aiShadingMode_Phong: + case aiShadingMode_Blinn: + case aiShadingMode_CookTorrance: + case aiShadingMode_Fresnel: + case aiShadingMode_PBR_BRDF: // Possibly should be Discreet3DS::Metal in some cases but this is undocumented + shading_mode_out = Discreet3DS::Phong; + break; + + default: + shading_mode_out = Discreet3DS::Flat; + ai_assert(false); + }; + writer.PutU2(static_cast<uint16_t>(shading_mode_out)); + } + + if (mat.Get(AI_MATKEY_SHININESS, f) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS); + WritePercentChunk(f); + } + + if (mat.Get(AI_MATKEY_SHININESS_STRENGTH, f) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS_PERCENT); + WritePercentChunk(f); + } + + int twosided; + if (mat.Get(AI_MATKEY_TWOSIDED, twosided) == AI_SUCCESS && twosided != 0) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_TWO_SIDE); + writer.PutI2(1); + } + + // Fallback to BASE_COLOR if no DIFFUSE + if (!WriteTexture(mat, aiTextureType_DIFFUSE, Discreet3DS::CHUNK_MAT_TEXTURE)) + WriteTexture(mat, aiTextureType_BASE_COLOR, Discreet3DS::CHUNK_MAT_TEXTURE); + + WriteTexture(mat, aiTextureType_HEIGHT, Discreet3DS::CHUNK_MAT_BUMPMAP); + WriteTexture(mat, aiTextureType_OPACITY, Discreet3DS::CHUNK_MAT_OPACMAP); + WriteTexture(mat, aiTextureType_SHININESS, Discreet3DS::CHUNK_MAT_MAT_SHINMAP); + WriteTexture(mat, aiTextureType_SPECULAR, Discreet3DS::CHUNK_MAT_SPECMAP); + WriteTexture(mat, aiTextureType_EMISSIVE, Discreet3DS::CHUNK_MAT_SELFIMAP); + WriteTexture(mat, aiTextureType_REFLECTION, Discreet3DS::CHUNK_MAT_REFLMAP); + } +} + +// ------------------------------------------------------------------------------------------------ +// returns true if the texture existed +bool Discreet3DSExporter::WriteTexture(const aiMaterial &mat, aiTextureType type, uint16_t chunk_flags) { + aiString path; + aiTextureMapMode map_mode[2] = { + aiTextureMapMode_Wrap, aiTextureMapMode_Wrap + }; + ai_real blend = 1.0; + if (mat.GetTexture(type, 0, &path, nullptr, nullptr, &blend, nullptr, map_mode) != AI_SUCCESS || !path.length) { + return false; + } + + // TODO: handle embedded textures properly + if (path.data[0] == '*') { + ASSIMP_LOG_ERROR("Ignoring embedded texture for export: ", path.C_Str()); + return false; + } + + ChunkWriter chunk(writer, chunk_flags); + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAPFILE); + WriteString(path); + } + + WritePercentChunk(blend); + + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_MAP_TILING); + uint16_t val = 0; // WRAP + if (map_mode[0] == aiTextureMapMode_Mirror) { + val = 0x2; + } else if (map_mode[0] == aiTextureMapMode_Decal) { + val = 0x10; + } + writer.PutU2(val); + } + // TODO: export texture transformation (i.e. UV offset, scale, rotation) + return true; +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteMeshes() { + // NOTE: 3DS allows for instances. However: + // i) not all importers support reading them + // ii) instances are not as flexible as they are in assimp, in particular, + // nodes can carry (and instance) only one mesh. + // + // This exporter currently deep clones all instanced meshes, i.e. for each mesh + // attached to a node a full TRIMESH chunk is written to the file. + // + // Furthermore, the TRIMESH is transformed into world space so that it will + // appear correctly if importers don't read the scene hierarchy at all. + for (MeshesByNodeMap::const_iterator it = meshes.begin(); it != meshes.end(); ++it) { + const aiNode &node = *(*it).first; + const unsigned int mesh_idx = (*it).second; + + const aiMesh &mesh = *scene->mMeshes[mesh_idx]; + + // This should not happen if the SLM step is correctly executed + // before the scene is handed to the exporter + ai_assert(mesh.mNumVertices <= 0xffff); + ai_assert(mesh.mNumFaces <= 0xffff); + + const aiMatrix4x4 &trafo = trafos[&node]; + + ChunkWriter chunk(writer, Discreet3DS::CHUNK_OBJBLOCK); + + // Mesh name is tied to the node it is attached to so it can later be referenced + const std::string &name = GetMeshName(mesh, mesh_idx, node); + WriteString(name); + + // TRIMESH chunk + ChunkWriter chunk2(writer, Discreet3DS::CHUNK_TRIMESH); + + // Vertices in world space + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_VERTLIST); + + const uint16_t count = static_cast<uint16_t>(mesh.mNumVertices); + writer.PutU2(count); + for (unsigned int i = 0; i < mesh.mNumVertices; ++i) { + const aiVector3D &v = mesh.mVertices[i]; + writer.PutF4(v.x); + writer.PutF4(v.y); + writer.PutF4(v.z); + } + } + + // UV coordinates + if (mesh.HasTextureCoords(0)) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAPLIST); + const uint16_t count = static_cast<uint16_t>(mesh.mNumVertices); + writer.PutU2(count); + + for (unsigned int i = 0; i < mesh.mNumVertices; ++i) { + const aiVector3D &v = mesh.mTextureCoords[0][i]; + writer.PutF4(v.x); + writer.PutF4(v.y); + } + } + + // Faces (indices) + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_FACELIST); + + ai_assert(mesh.mNumFaces <= 0xffff); + + // Count triangles, discard lines and points + uint16_t count = 0; + for (unsigned int i = 0; i < mesh.mNumFaces; ++i) { + const aiFace &f = mesh.mFaces[i]; + if (f.mNumIndices < 3) { + continue; + } + // TRIANGULATE step is a pre-requisite so we should not see polys here + ai_assert(f.mNumIndices == 3); + ++count; + } + + writer.PutU2(count); + for (unsigned int i = 0; i < mesh.mNumFaces; ++i) { + const aiFace &f = mesh.mFaces[i]; + if (f.mNumIndices < 3) { + continue; + } + + for (unsigned int j = 0; j < 3; ++j) { + ai_assert(f.mIndices[j] <= 0xffff); + writer.PutI2(static_cast<uint16_t>(f.mIndices[j])); + } + + // Edge visibility flag + writer.PutI2(0x0); + } + + // TODO: write smoothing groups (CHUNK_SMOOLIST) + + WriteFaceMaterialChunk(mesh); + } + + // Transformation matrix by which the mesh vertices have been pre-transformed with. + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_TRMATRIX); + // Store rotation 3x3 matrix row wise + for (unsigned int r = 0; r < 3; ++r) { + for (unsigned int c = 0; c < 3; ++c) { + writer.PutF4(trafo[r][c]); + } + } + // Store translation sub vector column wise + for (unsigned int r = 0; r < 3; ++r) { + writer.PutF4(trafo[r][3]); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteFaceMaterialChunk(const aiMesh &mesh) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_FACEMAT); + const std::string &name = GetMaterialName(*scene->mMaterials[mesh.mMaterialIndex], mesh.mMaterialIndex); + WriteString(name); + + // Because assimp splits meshes by material, only a single + // FACEMAT chunk needs to be written + ai_assert(mesh.mNumFaces <= 0xffff); + const uint16_t count = static_cast<uint16_t>(mesh.mNumFaces); + writer.PutU2(count); + + for (unsigned int i = 0; i < mesh.mNumFaces; ++i) { + writer.PutU2(static_cast<uint16_t>(i)); + } +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteString(const std::string &s) { + for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) { + writer.PutI1(*it); + } + writer.PutI1('\0'); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteString(const aiString &s) { + for (std::size_t i = 0; i < s.length; ++i) { + writer.PutI1(s.data[i]); + } + writer.PutI1('\0'); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteColor(const aiColor3D &color) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_RGBF); + writer.PutF4(color.r); + writer.PutF4(color.g); + writer.PutF4(color.b); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WritePercentChunk(float f) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_PERCENTF); + writer.PutF4(f); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WritePercentChunk(double f) { + ChunkWriter ccurChunkhunk(writer, Discreet3DS::CHUNK_PERCENTD); + writer.PutF8(f); +} + +#endif // ASSIMP_BUILD_NO_3DS_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/3DS/3DSExporter.h b/libs/assimp/code/AssetLib/3DS/3DSExporter.h new file mode 100644 index 0000000..82ec351 --- /dev/null +++ b/libs/assimp/code/AssetLib/3DS/3DSExporter.h @@ -0,0 +1,98 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 3DSExporter.h + * 3DS Exporter Main Header + */ +#ifndef AI_3DSEXPORTER_H_INC +#define AI_3DSEXPORTER_H_INC + +#include <map> +#include <memory> + +#include <assimp/StreamWriter.h> +#include <assimp/material.h> + +struct aiScene; +struct aiNode; +struct aiMaterial; +struct aiMesh; + +namespace Assimp +{ + +// ------------------------------------------------------------------------------------------------ +/** + * @brief Helper class to export a given scene to a 3DS file. + */ +// ------------------------------------------------------------------------------------------------ +class Discreet3DSExporter { +public: + Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, const aiScene* pScene); + ~Discreet3DSExporter(); + +private: + void WriteMeshes(); + void WriteMaterials(); + bool WriteTexture(const aiMaterial& mat, aiTextureType type, uint16_t chunk_flags); + void WriteFaceMaterialChunk(const aiMesh& mesh); + int WriteHierarchy(const aiNode& node, int level, int sibling_level); + void WriteString(const std::string& s); + void WriteString(const aiString& s); + void WriteColor(const aiColor3D& color); + void WritePercentChunk(float f); + void WritePercentChunk(double f); + +private: + const aiScene* const scene; + StreamWriterLE writer; + + std::map<const aiNode*, aiMatrix4x4> trafos; + + typedef std::multimap<const aiNode*, unsigned int> MeshesByNodeMap; + MeshesByNodeMap meshes; + +}; + +} // Namespace Assimp + +#endif // AI_3DSEXPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/3DS/3DSHelper.h b/libs/assimp/code/AssetLib/3DS/3DSHelper.h new file mode 100644 index 0000000..dc10980 --- /dev/null +++ b/libs/assimp/code/AssetLib/3DS/3DSHelper.h @@ -0,0 +1,702 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines helper data structures for the import of 3DS files */ + +#ifndef AI_3DSFILEHELPER_H_INC +#define AI_3DSFILEHELPER_H_INC + +#include <assimp/SmoothingGroups.h> +#include <assimp/SpatialSort.h> +#include <assimp/StringUtils.h> +#include <assimp/anim.h> +#include <assimp/camera.h> +#include <assimp/light.h> +#include <assimp/material.h> +#include <assimp/qnan.h> +#include <cstdio> //sprintf + +namespace Assimp { +namespace D3DS { + +#include <assimp/Compiler/pushpack1.h> + +// --------------------------------------------------------------------------- +/** Defines chunks and data structures. +*/ +namespace Discreet3DS { + + //! data structure for a single chunk in a .3ds file + struct Chunk { + uint16_t Flag; + uint32_t Size; + } PACK_STRUCT; + + //! Used for shading field in material3ds structure + //! From AutoDesk 3ds SDK + typedef enum { + // translated to gouraud shading with wireframe active + Wire = 0x0, + + // if this material is set, no vertex normals will + // be calculated for the model. Face normals + gouraud + Flat = 0x1, + + // standard gouraud shading + Gouraud = 0x2, + + // phong shading + Phong = 0x3, + + // cooktorrance or anistropic phong shading ... + // the exact meaning is unknown, if you know it + // feel free to tell me ;-) + Metal = 0x4, + + // required by the ASE loader + Blinn = 0x5 + } shadetype3ds; + + // Flags for animated keys + enum { + KEY_USE_TENS = 0x1, + KEY_USE_CONT = 0x2, + KEY_USE_BIAS = 0x4, + KEY_USE_EASE_TO = 0x8, + KEY_USE_EASE_FROM = 0x10 + }; + + enum { + + // ******************************************************************** + // Basic chunks which can be found everywhere in the file + CHUNK_VERSION = 0x0002, + CHUNK_RGBF = 0x0010, // float4 R; float4 G; float4 B + CHUNK_RGBB = 0x0011, // int1 R; int1 G; int B + + // Linear color values (gamma = 2.2?) + CHUNK_LINRGBF = 0x0013, // float4 R; float4 G; float4 B + CHUNK_LINRGBB = 0x0012, // int1 R; int1 G; int B + + CHUNK_PERCENTW = 0x0030, // int2 percentage + CHUNK_PERCENTF = 0x0031, // float4 percentage + CHUNK_PERCENTD = 0x0032, // float8 percentage + // ******************************************************************** + + // Prj master chunk + CHUNK_PRJ = 0xC23D, + + // MDLI master chunk + CHUNK_MLI = 0x3DAA, + + // Primary main chunk of the .3ds file + CHUNK_MAIN = 0x4D4D, + + // Mesh main chunk + CHUNK_OBJMESH = 0x3D3D, + + // Specifies the background color of the .3ds file + // This is passed through the material system for + // viewing purposes. + CHUNK_BKGCOLOR = 0x1200, + + // Specifies the ambient base color of the scene. + // This is added to all materials in the file + CHUNK_AMBCOLOR = 0x2100, + + // Specifies the background image for the whole scene + // This value is passed through the material system + // to the viewer + CHUNK_BIT_MAP = 0x1100, + CHUNK_BIT_MAP_EXISTS = 0x1101, + + // ******************************************************************** + // Viewport related stuff. Ignored + CHUNK_DEFAULT_VIEW = 0x3000, + CHUNK_VIEW_TOP = 0x3010, + CHUNK_VIEW_BOTTOM = 0x3020, + CHUNK_VIEW_LEFT = 0x3030, + CHUNK_VIEW_RIGHT = 0x3040, + CHUNK_VIEW_FRONT = 0x3050, + CHUNK_VIEW_BACK = 0x3060, + CHUNK_VIEW_USER = 0x3070, + CHUNK_VIEW_CAMERA = 0x3080, + // ******************************************************************** + + // Mesh chunks + CHUNK_OBJBLOCK = 0x4000, + CHUNK_TRIMESH = 0x4100, + CHUNK_VERTLIST = 0x4110, + CHUNK_VERTFLAGS = 0x4111, + CHUNK_FACELIST = 0x4120, + CHUNK_FACEMAT = 0x4130, + CHUNK_MAPLIST = 0x4140, + CHUNK_SMOOLIST = 0x4150, + CHUNK_TRMATRIX = 0x4160, + CHUNK_MESHCOLOR = 0x4165, + CHUNK_TXTINFO = 0x4170, + CHUNK_LIGHT = 0x4600, + CHUNK_CAMERA = 0x4700, + CHUNK_HIERARCHY = 0x4F00, + + // Specifies the global scaling factor. This is applied + // to the root node's transformation matrix + CHUNK_MASTER_SCALE = 0x0100, + + // ******************************************************************** + // Material chunks + CHUNK_MAT_MATERIAL = 0xAFFF, + + // asciiz containing the name of the material + CHUNK_MAT_MATNAME = 0xA000, + CHUNK_MAT_AMBIENT = 0xA010, // followed by color chunk + CHUNK_MAT_DIFFUSE = 0xA020, // followed by color chunk + CHUNK_MAT_SPECULAR = 0xA030, // followed by color chunk + + // Specifies the shininess of the material + // followed by percentage chunk + CHUNK_MAT_SHININESS = 0xA040, + CHUNK_MAT_SHININESS_PERCENT = 0xA041, + + // Specifies the shading mode to be used + // followed by a short + CHUNK_MAT_SHADING = 0xA100, + + // NOTE: Emissive color (self illumination) seems not + // to be a color but a single value, type is unknown. + // Make the parser accept both of them. + // followed by percentage chunk (?) + CHUNK_MAT_SELF_ILLUM = 0xA080, + + // Always followed by percentage chunk (?) + CHUNK_MAT_SELF_ILPCT = 0xA084, + + // Always followed by percentage chunk + CHUNK_MAT_TRANSPARENCY = 0xA050, + + // Diffuse texture channel 0 + CHUNK_MAT_TEXTURE = 0xA200, + + // Contains opacity information for each texel + CHUNK_MAT_OPACMAP = 0xA210, + + // Contains a reflection map to be used to reflect + // the environment. This is partially supported. + CHUNK_MAT_REFLMAP = 0xA220, + + // Self Illumination map (emissive colors) + CHUNK_MAT_SELFIMAP = 0xA33d, + + // Bumpmap. Not specified whether it is a heightmap + // or a normal map. Assme it is a heightmap since + // artist normally prefer this format. + CHUNK_MAT_BUMPMAP = 0xA230, + + // Specular map. Seems to influence the specular color + CHUNK_MAT_SPECMAP = 0xA204, + + // Holds shininess data. + CHUNK_MAT_MAT_SHINMAP = 0xA33C, + + // Scaling in U/V direction. + // (need to gen separate UV coordinate set + // and do this by hand) + CHUNK_MAT_MAP_USCALE = 0xA354, + CHUNK_MAT_MAP_VSCALE = 0xA356, + + // Translation in U/V direction. + // (need to gen separate UV coordinate set + // and do this by hand) + CHUNK_MAT_MAP_UOFFSET = 0xA358, + CHUNK_MAT_MAP_VOFFSET = 0xA35a, + + // UV-coordinates rotation around the z-axis + // Assumed to be in radians. + CHUNK_MAT_MAP_ANG = 0xA35C, + + // Tiling flags for 3DS files + CHUNK_MAT_MAP_TILING = 0xa351, + + // Specifies the file name of a texture + CHUNK_MAPFILE = 0xA300, + + // Specifies whether a material requires two-sided rendering + CHUNK_MAT_TWO_SIDE = 0xA081, + // ******************************************************************** + + // Main keyframer chunk. Contains translation/rotation/scaling data + CHUNK_KEYFRAMER = 0xB000, + + // Supported sub chunks + CHUNK_TRACKINFO = 0xB002, + CHUNK_TRACKOBJNAME = 0xB010, + CHUNK_TRACKDUMMYOBJNAME = 0xB011, + CHUNK_TRACKPIVOT = 0xB013, + CHUNK_TRACKPOS = 0xB020, + CHUNK_TRACKROTATE = 0xB021, + CHUNK_TRACKSCALE = 0xB022, + + // ******************************************************************** + // Keyframes for various other stuff in the file + // Partially ignored + CHUNK_AMBIENTKEY = 0xB001, + CHUNK_TRACKMORPH = 0xB026, + CHUNK_TRACKHIDE = 0xB029, + CHUNK_OBJNUMBER = 0xB030, + CHUNK_TRACKCAMERA = 0xB003, + CHUNK_TRACKFOV = 0xB023, + CHUNK_TRACKROLL = 0xB024, + CHUNK_TRACKCAMTGT = 0xB004, + CHUNK_TRACKLIGHT = 0xB005, + CHUNK_TRACKLIGTGT = 0xB006, + CHUNK_TRACKSPOTL = 0xB007, + CHUNK_FRAMES = 0xB008, + // ******************************************************************** + + // light sub-chunks + CHUNK_DL_OFF = 0x4620, + CHUNK_DL_OUTER_RANGE = 0x465A, + CHUNK_DL_INNER_RANGE = 0x4659, + CHUNK_DL_MULTIPLIER = 0x465B, + CHUNK_DL_EXCLUDE = 0x4654, + CHUNK_DL_ATTENUATE = 0x4625, + CHUNK_DL_SPOTLIGHT = 0x4610, + + // camera sub-chunks + CHUNK_CAM_RANGES = 0x4720 + }; +} + +// --------------------------------------------------------------------------- +/** Helper structure representing a 3ds mesh face */ +struct Face : public FaceWithSmoothingGroup { +}; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4315) +#endif // _MSC_VER + +// --------------------------------------------------------------------------- +/** Helper structure representing a texture */ +struct Texture { + //! Default constructor + Texture() AI_NO_EXCEPT + : mTextureBlend(0.0f), + mMapName(), + mOffsetU(0.0), + mOffsetV(0.0), + mScaleU(1.0), + mScaleV(1.0), + mRotation(0.0), + mMapMode(aiTextureMapMode_Wrap), + bPrivate(), + iUVSrc(0) { + mTextureBlend = get_qnan(); + } + + Texture(const Texture &other) : + mTextureBlend(other.mTextureBlend), + mMapName(other.mMapName), + mOffsetU(other.mOffsetU), + mOffsetV(other.mOffsetV), + mScaleU(other.mScaleU), + mScaleV(other.mScaleV), + mRotation(other.mRotation), + mMapMode(other.mMapMode), + bPrivate(other.bPrivate), + iUVSrc(other.iUVSrc) { + // empty + } + + Texture(Texture &&other) AI_NO_EXCEPT : mTextureBlend(other.mTextureBlend), + mMapName(std::move(other.mMapName)), + mOffsetU(other.mOffsetU), + mOffsetV(other.mOffsetV), + mScaleU(other.mScaleU), + mScaleV(other.mScaleV), + mRotation(other.mRotation), + mMapMode(other.mMapMode), + bPrivate(other.bPrivate), + iUVSrc(other.iUVSrc) { + // empty + } + + Texture &operator=(Texture &&other) AI_NO_EXCEPT { + if (this == &other) { + return *this; + } + + mTextureBlend = other.mTextureBlend; + mMapName = std::move(other.mMapName); + mOffsetU = other.mOffsetU; + mOffsetV = other.mOffsetV; + mScaleU = other.mScaleU; + mScaleV = other.mScaleV; + mRotation = other.mRotation; + mMapMode = other.mMapMode; + bPrivate = other.bPrivate; + iUVSrc = other.iUVSrc; + + return *this; + } + + //! Specifies the blend factor for the texture + ai_real mTextureBlend; + + //! Specifies the filename of the texture + std::string mMapName; + + //! Specifies texture coordinate offsets/scaling/rotations + ai_real mOffsetU; + ai_real mOffsetV; + ai_real mScaleU; + ai_real mScaleV; + ai_real mRotation; + + //! Specifies the mapping mode to be used for the texture + aiTextureMapMode mMapMode; + + //! Used internally + bool bPrivate; + int iUVSrc; +}; + +#include <assimp/Compiler/poppack1.h> + +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + +// --------------------------------------------------------------------------- +/** Helper structure representing a 3ds material */ +struct Material { + //! Default constructor has been deleted + Material() : + mName(), + mDiffuse(ai_real(0.6), ai_real(0.6), ai_real(0.6)), + mSpecularExponent(ai_real(0.0)), + mShininessStrength(ai_real(1.0)), + mShading(Discreet3DS::Gouraud), + mTransparency(ai_real(1.0)), + mBumpHeight(ai_real(1.0)), + mTwoSided(false) { + // empty + } + + //! Constructor with explicit name + explicit Material(const std::string &name) : + mName(name), + mDiffuse(ai_real(0.6), ai_real(0.6), ai_real(0.6)), + mSpecularExponent(ai_real(0.0)), + mShininessStrength(ai_real(1.0)), + mShading(Discreet3DS::Gouraud), + mTransparency(ai_real(1.0)), + mBumpHeight(ai_real(1.0)), + mTwoSided(false) { + // empty + } + + Material(const Material &other) : + mName(other.mName), + mDiffuse(other.mDiffuse), + mSpecularExponent(other.mSpecularExponent), + mShininessStrength(other.mShininessStrength), + mSpecular(other.mSpecular), + mAmbient(other.mAmbient), + mShading(other.mShading), + mTransparency(other.mTransparency), + sTexDiffuse(other.sTexDiffuse), + sTexOpacity(other.sTexOpacity), + sTexSpecular(other.sTexSpecular), + sTexReflective(other.sTexReflective), + sTexBump(other.sTexBump), + sTexEmissive(other.sTexEmissive), + sTexShininess(other.sTexShininess), + mBumpHeight(other.mBumpHeight), + mEmissive(other.mEmissive), + sTexAmbient(other.sTexAmbient), + mTwoSided(other.mTwoSided) { + // empty + } + + //! Move constructor. This is explicitly written because MSVC doesn't support defaulting it + Material(Material &&other) AI_NO_EXCEPT : mName(std::move(other.mName)), + mDiffuse(other.mDiffuse), + mSpecularExponent(other.mSpecularExponent), + mShininessStrength(other.mShininessStrength), + mSpecular(other.mSpecular), + mAmbient(other.mAmbient), + mShading(other.mShading), + mTransparency(other.mTransparency), + sTexDiffuse(std::move(other.sTexDiffuse)), + sTexOpacity(std::move(other.sTexOpacity)), + sTexSpecular(std::move(other.sTexSpecular)), + sTexReflective(std::move(other.sTexReflective)), + sTexBump(std::move(other.sTexBump)), + sTexEmissive(std::move(other.sTexEmissive)), + sTexShininess(std::move(other.sTexShininess)), + mBumpHeight(other.mBumpHeight), + mEmissive(other.mEmissive), + sTexAmbient(std::move(other.sTexAmbient)), + mTwoSided(other.mTwoSided) { + // empty + } + + Material &operator=(Material &&other) AI_NO_EXCEPT { + if (this == &other) { + return *this; + } + + mName = std::move(other.mName); + mDiffuse = other.mDiffuse; + mSpecularExponent = other.mSpecularExponent; + mShininessStrength = other.mShininessStrength, + mSpecular = other.mSpecular; + mAmbient = other.mAmbient; + mShading = other.mShading; + mTransparency = other.mTransparency; + sTexDiffuse = std::move(other.sTexDiffuse); + sTexOpacity = std::move(other.sTexOpacity); + sTexSpecular = std::move(other.sTexSpecular); + sTexReflective = std::move(other.sTexReflective); + sTexBump = std::move(other.sTexBump); + sTexEmissive = std::move(other.sTexEmissive); + sTexShininess = std::move(other.sTexShininess); + mBumpHeight = other.mBumpHeight; + mEmissive = other.mEmissive; + sTexAmbient = std::move(other.sTexAmbient); + mTwoSided = other.mTwoSided; + + return *this; + } + + virtual ~Material() { + // empty + } + + //! Name of the material + std::string mName; + //! Diffuse color of the material + aiColor3D mDiffuse; + //! Specular exponent + ai_real mSpecularExponent; + //! Shininess strength, in percent + ai_real mShininessStrength; + //! Specular color of the material + aiColor3D mSpecular; + //! Ambient color of the material + aiColor3D mAmbient; + //! Shading type to be used + Discreet3DS::shadetype3ds mShading; + //! Opacity of the material + ai_real mTransparency; + //! Diffuse texture channel + Texture sTexDiffuse; + //! Opacity texture channel + Texture sTexOpacity; + //! Specular texture channel + Texture sTexSpecular; + //! Reflective texture channel + Texture sTexReflective; + //! Bump texture channel + Texture sTexBump; + //! Emissive texture channel + Texture sTexEmissive; + //! Shininess texture channel + Texture sTexShininess; + //! Scaling factor for the bump values + ai_real mBumpHeight; + //! Emissive color + aiColor3D mEmissive; + //! Ambient texture channel + //! (used by the ASE format) + Texture sTexAmbient; + //! True if the material must be rendered from two sides + bool mTwoSided; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent a 3ds file mesh */ +struct Mesh : public MeshWithSmoothingGroups<D3DS::Face> { + //! Default constructor has been deleted + Mesh() = delete; + + //! Constructor with explicit name + explicit Mesh(const std::string &name) : + mName(name) { + } + + //! Name of the mesh + std::string mName; + + //! Texture coordinates + std::vector<aiVector3D> mTexCoords; + + //! Face materials + std::vector<unsigned int> mFaceMaterials; + + //! Local transformation matrix + aiMatrix4x4 mMat; +}; + +// --------------------------------------------------------------------------- +/** Float key - quite similar to aiVectorKey and aiQuatKey. Both are in the + C-API, so it would be difficult to make them a template. */ +struct aiFloatKey { + double mTime; ///< The time of this key + ai_real mValue; ///< The value of this key + +#ifdef __cplusplus + + // time is not compared + bool operator==(const aiFloatKey &o) const { return o.mValue == this->mValue; } + + bool operator!=(const aiFloatKey &o) const { return o.mValue != this->mValue; } + + // Only time is compared. This operator is defined + // for use with std::sort + bool operator<(const aiFloatKey &o) const { return mTime < o.mTime; } + + bool operator>(const aiFloatKey &o) const { return mTime > o.mTime; } + +#endif +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent a 3ds file node */ +struct Node { + Node() = delete; + + explicit Node(const std::string &name) : + mParent(nullptr), + mName(name), + mInstanceNumber(0), + mHierarchyPos(0), + mHierarchyIndex(0), + mInstanceCount(1) { + aRotationKeys.reserve(20); + aPositionKeys.reserve(20); + aScalingKeys.reserve(20); + } + + ~Node() { + for (unsigned int i = 0; i < mChildren.size(); ++i) + delete mChildren[i]; + } + + //! Pointer to the parent node + Node *mParent; + + //! Holds all child nodes + std::vector<Node *> mChildren; + + //! Name of the node + std::string mName; + + //! InstanceNumber of the node + int32_t mInstanceNumber; + + //! Dummy nodes: real name to be combined with the $$$DUMMY + std::string mDummyName; + + //! Position of the node in the hierarchy (tree depth) + int16_t mHierarchyPos; + + //! Index of the node + int16_t mHierarchyIndex; + + //! Rotation keys loaded from the file + std::vector<aiQuatKey> aRotationKeys; + + //! Position keys loaded from the file + std::vector<aiVectorKey> aPositionKeys; + + //! Scaling keys loaded from the file + std::vector<aiVectorKey> aScalingKeys; + + // For target lights (spot lights and directional lights): + // The position of the target + std::vector<aiVectorKey> aTargetPositionKeys; + + // For cameras: the camera roll angle + std::vector<aiFloatKey> aCameraRollKeys; + + //! Pivot position loaded from the file + aiVector3D vPivot; + + //instance count, will be kept only for the first node + int32_t mInstanceCount; + + //! Add a child node, setup the right parent node for it + //! \param pc Node to be 'adopted' + inline Node &push_back(Node *pc) { + mChildren.push_back(pc); + pc->mParent = this; + return *this; + } +}; +// --------------------------------------------------------------------------- +/** Helper structure analogue to aiScene */ +struct Scene { + //! List of all materials loaded + //! NOTE: 3ds references materials globally + std::vector<Material> mMaterials; + + //! List of all meshes loaded + std::vector<Mesh> mMeshes; + + //! List of all cameras loaded + std::vector<aiCamera *> mCameras; + + //! List of all lights loaded + std::vector<aiLight *> mLights; + + //! Pointer to the root node of the scene + // --- moved to main class + // Node* pcRootNode; +}; + +} // end of namespace D3DS +} // end of namespace Assimp + +#endif // AI_XFILEHELPER_H_INC diff --git a/libs/assimp/code/AssetLib/3DS/3DSLoader.cpp b/libs/assimp/code/AssetLib/3DS/3DSLoader.cpp new file mode 100644 index 0000000..0ec8b87 --- /dev/null +++ b/libs/assimp/code/AssetLib/3DS/3DSLoader.cpp @@ -0,0 +1,1336 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 3DSLoader.cpp + * @brief Implementation of the 3ds importer class + * + * http://www.the-labs.com/Blender/3DS-details.html + */ + +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +#include "3DSLoader.h" +#include <assimp/StringComparison.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Discreet 3DS Importer", + "", + "", + "Limited animation support", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "3ds prj" +}; + +// ------------------------------------------------------------------------------------------------ +// Begins a new parsing block +// - Reads the current chunk and validates it +// - computes its length +#define ASSIMP_3DS_BEGIN_CHUNK() \ + while (true) { \ + if (stream->GetRemainingSizeToLimit() < sizeof(Discreet3DS::Chunk)) { \ + return; \ + } \ + Discreet3DS::Chunk chunk; \ + ReadChunk(&chunk); \ + int chunkSize = chunk.Size - sizeof(Discreet3DS::Chunk); \ + if (chunkSize <= 0) \ + continue; \ + const unsigned int oldReadLimit = stream->SetReadLimit( \ + stream->GetCurrentPos() + chunkSize); + +// ------------------------------------------------------------------------------------------------ +// End a parsing block +// Must follow at the end of each parsing block, reset chunk end marker to previous value +#define ASSIMP_3DS_END_CHUNK() \ + stream->SkipToReadLimit(); \ + stream->SetReadLimit(oldReadLimit); \ + if (stream->GetRemainingSizeToLimit() == 0) \ + return; \ + } + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +Discreet3DSImporter::Discreet3DSImporter() : + stream(), mLastNodeIndex(), mCurrentNode(), mRootNode(), mScene(), mMasterScale(), bHasBG(), bIsPrj() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +Discreet3DSImporter::~Discreet3DSImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool Discreet3DSImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const uint16_t token[] = { 0x4d4d, 0x3dc2 /*, 0x3daa */ }; + return CheckMagicToken(pIOHandler, pFile, token, AI_COUNT_OF(token), 0, sizeof token[0]); +} + +// ------------------------------------------------------------------------------------------------ +// Loader registry entry +const aiImporterDesc *Discreet3DSImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties +void Discreet3DSImporter::SetupProperties(const Importer * /*pImp*/) { + // nothing to be done for the moment +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void Discreet3DSImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, IOSystem *pIOHandler) { + + auto theFile = pIOHandler->Open(pFile, "rb"); + if (!theFile) { + throw DeadlyImportError("3DS: Could not open ", pFile); + } + + StreamReaderLE theStream(theFile); + + // We should have at least one chunk + if (theStream.GetRemainingSize() < 16) { + throw DeadlyImportError("3DS file is either empty or corrupt: ", pFile); + } + this->stream = &theStream; + + // Allocate our temporary 3DS representation + D3DS::Scene _scene; + mScene = &_scene; + + // Initialize members + D3DS::Node _rootNode("UNNAMED"); + mLastNodeIndex = -1; + mCurrentNode = &_rootNode; + mRootNode = mCurrentNode; + mRootNode->mHierarchyPos = -1; + mRootNode->mHierarchyIndex = -1; + mRootNode->mParent = nullptr; + mMasterScale = 1.0f; + mBackgroundImage = std::string(); + bHasBG = false; + bIsPrj = false; + + // Parse the file + ParseMainChunk(); + + // Process all meshes in the file. First check whether all + // face indices have valid values. The generate our + // internal verbose representation. Finally compute normal + // vectors from the smoothing groups we read from the + // file. + for (auto &mesh : mScene->mMeshes) { + if (mesh.mFaces.size() > 0 && mesh.mPositions.size() == 0) { + throw DeadlyImportError("3DS file contains faces but no vertices: ", pFile); + } + CheckIndices(mesh); + MakeUnique(mesh); + ComputeNormalsWithSmoothingsGroups<D3DS::Face>(mesh); + } + + // Replace all occurrences of the default material with a + // valid material. Generate it if no material containing + // DEFAULT in its name has been found in the file + ReplaceDefaultMaterial(); + + // Convert the scene from our internal representation to an + // aiScene object. This involves copying all meshes, lights + // and cameras to the scene + ConvertScene(pScene); + + // Generate the node graph for the scene. This is a little bit + // tricky since we'll need to split some meshes into sub-meshes + GenerateNodeGraph(pScene); + + // Now apply the master scaling factor to the scene + ApplyMasterScale(pScene); + + // Our internal scene representation and the root + // node will be automatically deleted, so the whole hierarchy will follow + + AI_DEBUG_INVALIDATE_PTR(mRootNode); + AI_DEBUG_INVALIDATE_PTR(mScene); + AI_DEBUG_INVALIDATE_PTR(this->stream); +} + +// ------------------------------------------------------------------------------------------------ +// Applies a master-scaling factor to the imported scene +void Discreet3DSImporter::ApplyMasterScale(aiScene *pScene) { + // There are some 3DS files with a zero scaling factor + if (!mMasterScale) + mMasterScale = 1.0f; + else + mMasterScale = 1.0f / mMasterScale; + + // Construct an uniform scaling matrix and multiply with it + pScene->mRootNode->mTransformation *= aiMatrix4x4( + mMasterScale, 0.0f, 0.0f, 0.0f, + 0.0f, mMasterScale, 0.0f, 0.0f, + 0.0f, 0.0f, mMasterScale, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + + // Check whether a scaling track is assigned to the root node. +} + +// ------------------------------------------------------------------------------------------------ +// Reads a new chunk from the file +void Discreet3DSImporter::ReadChunk(Discreet3DS::Chunk *pcOut) { + ai_assert(pcOut != nullptr); + + pcOut->Flag = stream->GetI2(); + pcOut->Size = stream->GetI4(); + + if (pcOut->Size - sizeof(Discreet3DS::Chunk) > stream->GetRemainingSize()) { + throw DeadlyImportError("Chunk is too large"); + } + + if (pcOut->Size - sizeof(Discreet3DS::Chunk) > stream->GetRemainingSizeToLimit()) { + ASSIMP_LOG_ERROR("3DS: Chunk overflow"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Skip a chunk +void Discreet3DSImporter::SkipChunk() { + Discreet3DS::Chunk psChunk; + ReadChunk(&psChunk); + + stream->IncPtr(psChunk.Size - sizeof(Discreet3DS::Chunk)); + return; +} + +// ------------------------------------------------------------------------------------------------ +// Process the primary chunk of the file +void Discreet3DSImporter::ParseMainChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) { + + case Discreet3DS::CHUNK_PRJ: + bIsPrj = true; + break; + case Discreet3DS::CHUNK_MAIN: + ParseEditorChunk(); + break; + }; + + ASSIMP_3DS_END_CHUNK(); + // recursively continue processing this hierarchy level + return ParseMainChunk(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseEditorChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_OBJMESH: + + ParseObjectChunk(); + break; + + // NOTE: In several documentations in the internet this + // chunk appears at different locations + case Discreet3DS::CHUNK_KEYFRAMER: + + ParseKeyframeChunk(); + break; + + case Discreet3DS::CHUNK_VERSION: { + // print the version number + char buff[10]; + ASSIMP_itoa10(buff, stream->GetI2()); + ASSIMP_LOG_INFO("3DS file format version: ", buff); + } break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseObjectChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_OBJBLOCK: { + unsigned int cnt = 0; + const char *sz = (const char *)stream->GetPtr(); + + // Get the name of the geometry object + while (stream->GetI1()) + ++cnt; + ParseChunk(sz, cnt); + } break; + + case Discreet3DS::CHUNK_MAT_MATERIAL: + + // Add a new material to the list + mScene->mMaterials.push_back(D3DS::Material(std::string("UNNAMED_" + ai_to_string(mScene->mMaterials.size())))); + ParseMaterialChunk(); + break; + + case Discreet3DS::CHUNK_AMBCOLOR: + + // This is the ambient base color of the scene. + // We add it to the ambient color of all materials + ParseColorChunk(&mClrAmbient, true); + if (is_qnan(mClrAmbient.r)) { + // We failed to read the ambient base color. + ASSIMP_LOG_ERROR("3DS: Failed to read ambient base color"); + mClrAmbient.r = mClrAmbient.g = mClrAmbient.b = 0.0f; + } + break; + + case Discreet3DS::CHUNK_BIT_MAP: { + // Specifies the background image. The string should already be + // properly 0 terminated but we need to be sure + unsigned int cnt = 0; + const char *sz = (const char *)stream->GetPtr(); + while (stream->GetI1()) + ++cnt; + mBackgroundImage = std::string(sz, cnt); + } break; + + case Discreet3DS::CHUNK_BIT_MAP_EXISTS: + bHasBG = true; + break; + + case Discreet3DS::CHUNK_MASTER_SCALE: + // Scene master scaling factor + mMasterScale = stream->GetF4(); + break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseChunk(const char *name, unsigned int num) { + ASSIMP_3DS_BEGIN_CHUNK(); + + // IMPLEMENTATION NOTE; + // Cameras or lights define their transformation in their parent node and in the + // corresponding light or camera chunks. However, we read and process the latter + // to to be able to return valid cameras/lights even if no scenegraph is given. + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_TRIMESH: { + // this starts a new triangle mesh + mScene->mMeshes.push_back(D3DS::Mesh(std::string(name, num))); + + // Read mesh chunks + ParseMeshChunk(); + } break; + + case Discreet3DS::CHUNK_LIGHT: { + // This starts a new light + aiLight *light = new aiLight(); + mScene->mLights.push_back(light); + + light->mName.Set(std::string(name, num)); + + // First read the position of the light + light->mPosition.x = stream->GetF4(); + light->mPosition.y = stream->GetF4(); + light->mPosition.z = stream->GetF4(); + + light->mColorDiffuse = aiColor3D(1.f, 1.f, 1.f); + + // Now check for further subchunks + if (!bIsPrj) /* fixme */ + ParseLightChunk(); + + // The specular light color is identical the the diffuse light color. The ambient light color + // is equal to the ambient base color of the whole scene. + light->mColorSpecular = light->mColorDiffuse; + light->mColorAmbient = mClrAmbient; + + if (light->mType == aiLightSource_UNDEFINED) { + // It must be a point light + light->mType = aiLightSource_POINT; + } + } break; + + case Discreet3DS::CHUNK_CAMERA: { + // This starts a new camera + aiCamera *camera = new aiCamera(); + mScene->mCameras.push_back(camera); + camera->mName.Set(std::string(name, num)); + + // First read the position of the camera + camera->mPosition.x = stream->GetF4(); + camera->mPosition.y = stream->GetF4(); + camera->mPosition.z = stream->GetF4(); + + // Then the camera target + camera->mLookAt.x = stream->GetF4() - camera->mPosition.x; + camera->mLookAt.y = stream->GetF4() - camera->mPosition.y; + camera->mLookAt.z = stream->GetF4() - camera->mPosition.z; + ai_real len = camera->mLookAt.Length(); + if (len < 1e-5) { + + // There are some files with lookat == position. Don't know why or whether it's ok or not. + ASSIMP_LOG_ERROR("3DS: Unable to read proper camera look-at vector"); + camera->mLookAt = aiVector3D(0.0, 1.0, 0.0); + + } else + camera->mLookAt /= len; + + // And finally - the camera rotation angle, in counter clockwise direction + const ai_real angle = AI_DEG_TO_RAD(stream->GetF4()); + aiQuaternion quat(camera->mLookAt, angle); + camera->mUp = quat.GetMatrix() * aiVector3D(0.0, 1.0, 0.0); + + // Read the lense angle + camera->mHorizontalFOV = AI_DEG_TO_RAD(stream->GetF4()); + if (camera->mHorizontalFOV < 0.001f) { + camera->mHorizontalFOV = float(AI_DEG_TO_RAD(45.f)); + } + + // Now check for further subchunks + if (!bIsPrj) /* fixme */ { + ParseCameraChunk(); + } + } break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseLightChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + aiLight *light = mScene->mLights.back(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_DL_SPOTLIGHT: + // Now we can be sure that the light is a spot light + light->mType = aiLightSource_SPOT; + + // We wouldn't need to normalize here, but we do it + light->mDirection.x = stream->GetF4() - light->mPosition.x; + light->mDirection.y = stream->GetF4() - light->mPosition.y; + light->mDirection.z = stream->GetF4() - light->mPosition.z; + light->mDirection.Normalize(); + + // Now the hotspot and falloff angles - in degrees + light->mAngleInnerCone = AI_DEG_TO_RAD(stream->GetF4()); + + // FIX: the falloff angle is just an offset + light->mAngleOuterCone = light->mAngleInnerCone + AI_DEG_TO_RAD(stream->GetF4()); + break; + + // intensity multiplier + case Discreet3DS::CHUNK_DL_MULTIPLIER: + light->mColorDiffuse = light->mColorDiffuse * stream->GetF4(); + break; + + // light color + case Discreet3DS::CHUNK_RGBF: + case Discreet3DS::CHUNK_LINRGBF: + light->mColorDiffuse.r *= stream->GetF4(); + light->mColorDiffuse.g *= stream->GetF4(); + light->mColorDiffuse.b *= stream->GetF4(); + break; + + // light attenuation + case Discreet3DS::CHUNK_DL_ATTENUATE: + light->mAttenuationLinear = stream->GetF4(); + break; + }; + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseCameraChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + aiCamera *camera = mScene->mCameras.back(); + + // get chunk type + switch (chunk.Flag) { + // near and far clip plane + case Discreet3DS::CHUNK_CAM_RANGES: + camera->mClipPlaneNear = stream->GetF4(); + camera->mClipPlaneFar = stream->GetF4(); + break; + } + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseKeyframeChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_TRACKCAMTGT: + case Discreet3DS::CHUNK_TRACKSPOTL: + case Discreet3DS::CHUNK_TRACKCAMERA: + case Discreet3DS::CHUNK_TRACKINFO: + case Discreet3DS::CHUNK_TRACKLIGHT: + case Discreet3DS::CHUNK_TRACKLIGTGT: + + // this starts a new mesh hierarchy chunk + ParseHierarchyChunk(chunk.Flag); + break; + }; + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Little helper function for ParseHierarchyChunk +void Discreet3DSImporter::InverseNodeSearch(D3DS::Node *pcNode, D3DS::Node *pcCurrent) { + if (!pcCurrent) { + mRootNode->push_back(pcNode); + return; + } + + if (pcCurrent->mHierarchyPos == pcNode->mHierarchyPos) { + if (pcCurrent->mParent) { + pcCurrent->mParent->push_back(pcNode); + } else + pcCurrent->push_back(pcNode); + return; + } + return InverseNodeSearch(pcNode, pcCurrent->mParent); +} + +// ------------------------------------------------------------------------------------------------ +// Find a node with a specific name in the import hierarchy +D3DS::Node *FindNode(D3DS::Node *root, const std::string &name) { + if (root->mName == name) { + return root; + } + + for (std::vector<D3DS::Node *>::iterator it = root->mChildren.begin(); it != root->mChildren.end(); ++it) { + D3DS::Node *nd = FindNode(*it, name); + if (nullptr != nd) { + return nd; + } + } + + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// Binary predicate for std::unique() +template <class T> +bool KeyUniqueCompare(const T &first, const T &second) { + return first.mTime == second.mTime; +} + +// ------------------------------------------------------------------------------------------------ +// Skip some additional import data. +void Discreet3DSImporter::SkipTCBInfo() { + unsigned int flags = stream->GetI2(); + + if (!flags) { + // Currently we can't do anything with these values. They occur + // quite rare, so it wouldn't be worth the effort implementing + // them. 3DS is not really suitable for complex animations, + // so full support is not required. + ASSIMP_LOG_WARN("3DS: Skipping TCB animation info"); + } + + if (flags & Discreet3DS::KEY_USE_TENS) { + stream->IncPtr(4); + } + if (flags & Discreet3DS::KEY_USE_BIAS) { + stream->IncPtr(4); + } + if (flags & Discreet3DS::KEY_USE_CONT) { + stream->IncPtr(4); + } + if (flags & Discreet3DS::KEY_USE_EASE_FROM) { + stream->IncPtr(4); + } + if (flags & Discreet3DS::KEY_USE_EASE_TO) { + stream->IncPtr(4); + } +} + +// ------------------------------------------------------------------------------------------------ +// Read hierarchy and keyframe info +void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent) { + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_TRACKOBJNAME: + + // This is the name of the object to which the track applies. The chunk also + // defines the position of this object in the hierarchy. + { + + // First of all: get the name of the object + unsigned int cnt = 0; + const char *sz = (const char *)stream->GetPtr(); + + while (stream->GetI1()) + ++cnt; + std::string name = std::string(sz, cnt); + + // Now find out whether we have this node already (target animation channels + // are stored with a separate object ID) + D3DS::Node *pcNode = FindNode(mRootNode, name); + int instanceNumber = 1; + + if (pcNode) { + // if the source is not a CHUNK_TRACKINFO block it won't be an object instance + if (parent != Discreet3DS::CHUNK_TRACKINFO) { + mCurrentNode = pcNode; + break; + } + pcNode->mInstanceCount++; + instanceNumber = pcNode->mInstanceCount; + } + pcNode = new D3DS::Node(name); + pcNode->mInstanceNumber = instanceNumber; + + // There are two unknown values which we can safely ignore + stream->IncPtr(4); + + // Now read the hierarchy position of the object + uint16_t hierarchy = stream->GetI2() + 1; + pcNode->mHierarchyPos = hierarchy; + pcNode->mHierarchyIndex = mLastNodeIndex; + + // And find a proper position in the graph for it + if (mCurrentNode && mCurrentNode->mHierarchyPos == hierarchy) { + + // add to the parent of the last touched node + mCurrentNode->mParent->push_back(pcNode); + mLastNodeIndex++; + } else if (hierarchy >= mLastNodeIndex) { + + // place it at the current position in the hierarchy + mCurrentNode->push_back(pcNode); + mLastNodeIndex = hierarchy; + } else { + // need to go back to the specified position in the hierarchy. + InverseNodeSearch(pcNode, mCurrentNode); + mLastNodeIndex++; + } + // Make this node the current node + mCurrentNode = pcNode; + } + break; + + case Discreet3DS::CHUNK_TRACKDUMMYOBJNAME: + + // This is the "real" name of a $$$DUMMY object + { + const char *sz = (const char *)stream->GetPtr(); + while (stream->GetI1()) + ; + + // If object name is DUMMY, take this one instead + if (mCurrentNode->mName == "$$$DUMMY") { + mCurrentNode->mName = std::string(sz); + break; + } + } + break; + + case Discreet3DS::CHUNK_TRACKPIVOT: + + if (Discreet3DS::CHUNK_TRACKINFO != parent) { + ASSIMP_LOG_WARN("3DS: Skipping pivot subchunk for non usual object"); + break; + } + + // Pivot = origin of rotation and scaling + mCurrentNode->vPivot.x = stream->GetF4(); + mCurrentNode->vPivot.y = stream->GetF4(); + mCurrentNode->vPivot.z = stream->GetF4(); + break; + + // //////////////////////////////////////////////////////////////////// + // POSITION KEYFRAME + case Discreet3DS::CHUNK_TRACKPOS: { + stream->IncPtr(10); + const unsigned int numFrames = stream->GetI4(); + bool sortKeys = false; + + // This could also be meant as the target position for + // (targeted) lights and cameras + std::vector<aiVectorKey> *l; + if (Discreet3DS::CHUNK_TRACKCAMTGT == parent || Discreet3DS::CHUNK_TRACKLIGTGT == parent) { + l = &mCurrentNode->aTargetPositionKeys; + } else + l = &mCurrentNode->aPositionKeys; + + l->reserve(numFrames); + for (unsigned int i = 0; i < numFrames; ++i) { + const unsigned int fidx = stream->GetI4(); + + // Setup a new position key + aiVectorKey v; + v.mTime = (double)fidx; + + SkipTCBInfo(); + v.mValue.x = stream->GetF4(); + v.mValue.y = stream->GetF4(); + v.mValue.z = stream->GetF4(); + + // check whether we'll need to sort the keys + if (!l->empty() && v.mTime <= l->back().mTime) + sortKeys = true; + + // Add the new keyframe to the list + l->push_back(v); + } + + // Sort all keys with ascending time values and remove duplicates? + if (sortKeys) { + std::stable_sort(l->begin(), l->end()); + l->erase(std::unique(l->begin(), l->end(), &KeyUniqueCompare<aiVectorKey>), l->end()); + } + } + + break; + + // //////////////////////////////////////////////////////////////////// + // CAMERA ROLL KEYFRAME + case Discreet3DS::CHUNK_TRACKROLL: { + // roll keys are accepted for cameras only + if (parent != Discreet3DS::CHUNK_TRACKCAMERA) { + ASSIMP_LOG_WARN("3DS: Ignoring roll track for non-camera object"); + break; + } + bool sortKeys = false; + std::vector<aiFloatKey> *l = &mCurrentNode->aCameraRollKeys; + + stream->IncPtr(10); + const unsigned int numFrames = stream->GetI4(); + l->reserve(numFrames); + for (unsigned int i = 0; i < numFrames; ++i) { + const unsigned int fidx = stream->GetI4(); + + // Setup a new position key + aiFloatKey v; + v.mTime = (double)fidx; + + // This is just a single float + SkipTCBInfo(); + v.mValue = stream->GetF4(); + + // Check whether we'll need to sort the keys + if (!l->empty() && v.mTime <= l->back().mTime) + sortKeys = true; + + // Add the new keyframe to the list + l->push_back(v); + } + + // Sort all keys with ascending time values and remove duplicates? + if (sortKeys) { + std::stable_sort(l->begin(), l->end()); + l->erase(std::unique(l->begin(), l->end(), &KeyUniqueCompare<aiFloatKey>), l->end()); + } + } break; + + // //////////////////////////////////////////////////////////////////// + // CAMERA FOV KEYFRAME + case Discreet3DS::CHUNK_TRACKFOV: { + ASSIMP_LOG_ERROR("3DS: Skipping FOV animation track. " + "This is not supported"); + } break; + + // //////////////////////////////////////////////////////////////////// + // ROTATION KEYFRAME + case Discreet3DS::CHUNK_TRACKROTATE: { + stream->IncPtr(10); + const unsigned int numFrames = stream->GetI4(); + + bool sortKeys = false; + std::vector<aiQuatKey> *l = &mCurrentNode->aRotationKeys; + l->reserve(numFrames); + + for (unsigned int i = 0; i < numFrames; ++i) { + const unsigned int fidx = stream->GetI4(); + SkipTCBInfo(); + + aiQuatKey v; + v.mTime = (double)fidx; + + // The rotation keyframe is given as an axis-angle pair + const float rad = stream->GetF4(); + aiVector3D axis; + axis.x = stream->GetF4(); + axis.y = stream->GetF4(); + axis.z = stream->GetF4(); + + if (!axis.x && !axis.y && !axis.z) + axis.y = 1.f; + + // Construct a rotation quaternion from the axis-angle pair + v.mValue = aiQuaternion(axis, rad); + + // Check whether we'll need to sort the keys + if (!l->empty() && v.mTime <= l->back().mTime) + sortKeys = true; + + // add the new keyframe to the list + l->push_back(v); + } + // Sort all keys with ascending time values and remove duplicates? + if (sortKeys) { + std::stable_sort(l->begin(), l->end()); + l->erase(std::unique(l->begin(), l->end(), &KeyUniqueCompare<aiQuatKey>), l->end()); + } + } break; + + // //////////////////////////////////////////////////////////////////// + // SCALING KEYFRAME + case Discreet3DS::CHUNK_TRACKSCALE: { + stream->IncPtr(10); + const unsigned int numFrames = stream->GetI2(); + stream->IncPtr(2); + + bool sortKeys = false; + std::vector<aiVectorKey> *l = &mCurrentNode->aScalingKeys; + l->reserve(numFrames); + + for (unsigned int i = 0; i < numFrames; ++i) { + const unsigned int fidx = stream->GetI4(); + SkipTCBInfo(); + + // Setup a new key + aiVectorKey v; + v.mTime = (double)fidx; + + // ... and read its value + v.mValue.x = stream->GetF4(); + v.mValue.y = stream->GetF4(); + v.mValue.z = stream->GetF4(); + + // check whether we'll need to sort the keys + if (!l->empty() && v.mTime <= l->back().mTime) + sortKeys = true; + + // Remove zero-scalings on singular axes - they've been reported to be there erroneously in some strange files + if (!v.mValue.x) v.mValue.x = 1.f; + if (!v.mValue.y) v.mValue.y = 1.f; + if (!v.mValue.z) v.mValue.z = 1.f; + + l->push_back(v); + } + // Sort all keys with ascending time values and remove duplicates? + if (sortKeys) { + std::stable_sort(l->begin(), l->end()); + l->erase(std::unique(l->begin(), l->end(), &KeyUniqueCompare<aiVectorKey>), l->end()); + } + } break; + }; + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a face chunk - it contains smoothing groups and material assignments +void Discreet3DSImporter::ParseFaceChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + + // Get the mesh we're currently working on + D3DS::Mesh &mMesh = mScene->mMeshes.back(); + + // Get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_SMOOLIST: { + // This is the list of smoothing groups - a bitfield for every face. + // Up to 32 smoothing groups assigned to a single face. + unsigned int num = chunkSize / 4, m = 0; + if (num > mMesh.mFaces.size()) { + throw DeadlyImportError("3DS: More smoothing groups than faces"); + } + for (std::vector<D3DS::Face>::iterator i = mMesh.mFaces.begin(); m != num; ++i, ++m) { + // nth bit is set for nth smoothing group + (*i).iSmoothGroup = stream->GetI4(); + } + } break; + + case Discreet3DS::CHUNK_FACEMAT: { + // at fist an asciiz with the material name + const char *sz = (const char *)stream->GetPtr(); + while (stream->GetI1()) + ; + + // find the index of the material + unsigned int idx = 0xcdcdcdcd, cnt = 0; + for (std::vector<D3DS::Material>::const_iterator i = mScene->mMaterials.begin(); i != mScene->mMaterials.end(); ++i, ++cnt) { + // use case independent comparisons. hopefully it will work. + if ((*i).mName.length() && !ASSIMP_stricmp(sz, (*i).mName.c_str())) { + idx = cnt; + break; + } + } + if (0xcdcdcdcd == idx) { + ASSIMP_LOG_ERROR("3DS: Unknown material: ", sz); + } + + // Now continue and read all material indices + cnt = (uint16_t)stream->GetI2(); + for (unsigned int i = 0; i < cnt; ++i) { + unsigned int fidx = (uint16_t)stream->GetI2(); + + // check range + if (fidx >= mMesh.mFaceMaterials.size()) { + ASSIMP_LOG_ERROR("3DS: Invalid face index in face material list"); + } else + mMesh.mFaceMaterials[fidx] = idx; + } + } break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a mesh chunk. Here's the actual mesh data +void Discreet3DSImporter::ParseMeshChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + + // Get the mesh we're currently working on + D3DS::Mesh &mMesh = mScene->mMeshes.back(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_VERTLIST: { + // This is the list of all vertices in the current mesh + int num = (int)(uint16_t)stream->GetI2(); + mMesh.mPositions.reserve(num); + while (num-- > 0) { + aiVector3D v; + v.x = stream->GetF4(); + v.y = stream->GetF4(); + v.z = stream->GetF4(); + mMesh.mPositions.push_back(v); + } + } break; + case Discreet3DS::CHUNK_TRMATRIX: { + // This is the RLEATIVE transformation matrix of the current mesh. Vertices are + // pretransformed by this matrix wonder. + mMesh.mMat.a1 = stream->GetF4(); + mMesh.mMat.b1 = stream->GetF4(); + mMesh.mMat.c1 = stream->GetF4(); + mMesh.mMat.a2 = stream->GetF4(); + mMesh.mMat.b2 = stream->GetF4(); + mMesh.mMat.c2 = stream->GetF4(); + mMesh.mMat.a3 = stream->GetF4(); + mMesh.mMat.b3 = stream->GetF4(); + mMesh.mMat.c3 = stream->GetF4(); + mMesh.mMat.a4 = stream->GetF4(); + mMesh.mMat.b4 = stream->GetF4(); + mMesh.mMat.c4 = stream->GetF4(); + } break; + + case Discreet3DS::CHUNK_MAPLIST: { + // This is the list of all UV coords in the current mesh + int num = (int)(uint16_t)stream->GetI2(); + mMesh.mTexCoords.reserve(num); + while (num-- > 0) { + aiVector3D v; + v.x = stream->GetF4(); + v.y = stream->GetF4(); + mMesh.mTexCoords.push_back(v); + } + } break; + + case Discreet3DS::CHUNK_FACELIST: { + // This is the list of all faces in the current mesh + int num = (int)(uint16_t)stream->GetI2(); + mMesh.mFaces.reserve(num); + while (num-- > 0) { + // 3DS faces are ALWAYS triangles + mMesh.mFaces.push_back(D3DS::Face()); + D3DS::Face &sFace = mMesh.mFaces.back(); + + sFace.mIndices[0] = (uint16_t)stream->GetI2(); + sFace.mIndices[1] = (uint16_t)stream->GetI2(); + sFace.mIndices[2] = (uint16_t)stream->GetI2(); + + stream->IncPtr(2); // skip edge visibility flag + } + + // Resize the material array (0xcdcdcdcd marks the default material; so if a face is + // not referenced by a material, $$DEFAULT will be assigned to it) + mMesh.mFaceMaterials.resize(mMesh.mFaces.size(), 0xcdcdcdcd); + + // Larger 3DS files could have multiple FACE chunks here + chunkSize = (int)stream->GetRemainingSizeToLimit(); + if (chunkSize > (int)sizeof(Discreet3DS::Chunk)) + ParseFaceChunk(); + } break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a 3DS material chunk +void Discreet3DSImporter::ParseMaterialChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + switch (chunk.Flag) { + case Discreet3DS::CHUNK_MAT_MATNAME: + + { + // The material name string is already zero-terminated, but we need to be sure ... + const char *sz = (const char *)stream->GetPtr(); + unsigned int cnt = 0; + while (stream->GetI1()) + ++cnt; + + if (!cnt) { + // This may not be, we use the default name instead + ASSIMP_LOG_ERROR("3DS: Empty material name"); + } else + mScene->mMaterials.back().mName = std::string(sz, cnt); + } break; + + case Discreet3DS::CHUNK_MAT_DIFFUSE: { + // This is the diffuse material color + aiColor3D *pc = &mScene->mMaterials.back().mDiffuse; + ParseColorChunk(pc); + if (is_qnan(pc->r)) { + // color chunk is invalid. Simply ignore it + ASSIMP_LOG_ERROR("3DS: Unable to read DIFFUSE chunk"); + pc->r = pc->g = pc->b = 1.0f; + } + } break; + + case Discreet3DS::CHUNK_MAT_SPECULAR: { + // This is the specular material color + aiColor3D *pc = &mScene->mMaterials.back().mSpecular; + ParseColorChunk(pc); + if (is_qnan(pc->r)) { + // color chunk is invalid. Simply ignore it + ASSIMP_LOG_ERROR("3DS: Unable to read SPECULAR chunk"); + pc->r = pc->g = pc->b = 1.0f; + } + } break; + + case Discreet3DS::CHUNK_MAT_AMBIENT: { + // This is the ambient material color + aiColor3D *pc = &mScene->mMaterials.back().mAmbient; + ParseColorChunk(pc); + if (is_qnan(pc->r)) { + // color chunk is invalid. Simply ignore it + ASSIMP_LOG_ERROR("3DS: Unable to read AMBIENT chunk"); + pc->r = pc->g = pc->b = 0.0f; + } + } break; + + case Discreet3DS::CHUNK_MAT_SELF_ILLUM: { + // This is the emissive material color + aiColor3D *pc = &mScene->mMaterials.back().mEmissive; + ParseColorChunk(pc); + if (is_qnan(pc->r)) { + // color chunk is invalid. Simply ignore it + ASSIMP_LOG_ERROR("3DS: Unable to read EMISSIVE chunk"); + pc->r = pc->g = pc->b = 0.0f; + } + } break; + + case Discreet3DS::CHUNK_MAT_TRANSPARENCY: { + // This is the material's transparency + ai_real *pcf = &mScene->mMaterials.back().mTransparency; + *pcf = ParsePercentageChunk(); + + // NOTE: transparency, not opacity + if (is_qnan(*pcf)) + *pcf = ai_real(1.0); + else + *pcf = ai_real(1.0) - *pcf * (ai_real)0xFFFF / ai_real(100.0); + } break; + + case Discreet3DS::CHUNK_MAT_SHADING: + // This is the material shading mode + mScene->mMaterials.back().mShading = (D3DS::Discreet3DS::shadetype3ds)stream->GetI2(); + break; + + case Discreet3DS::CHUNK_MAT_TWO_SIDE: + // This is the two-sided flag + mScene->mMaterials.back().mTwoSided = true; + break; + + case Discreet3DS::CHUNK_MAT_SHININESS: { // This is the shininess of the material + ai_real *pcf = &mScene->mMaterials.back().mSpecularExponent; + *pcf = ParsePercentageChunk(); + if (is_qnan(*pcf)) + *pcf = 0.0; + else + *pcf *= (ai_real)0xFFFF; + } break; + + case Discreet3DS::CHUNK_MAT_SHININESS_PERCENT: { // This is the shininess strength of the material + ai_real *pcf = &mScene->mMaterials.back().mShininessStrength; + *pcf = ParsePercentageChunk(); + if (is_qnan(*pcf)) + *pcf = ai_real(0.0); + else + *pcf *= (ai_real)0xffff / ai_real(100.0); + } break; + + case Discreet3DS::CHUNK_MAT_SELF_ILPCT: { // This is the self illumination strength of the material + ai_real f = ParsePercentageChunk(); + if (is_qnan(f)) + f = ai_real(0.0); + else + f *= (ai_real)0xFFFF / ai_real(100.0); + mScene->mMaterials.back().mEmissive = aiColor3D(f, f, f); + } break; + + // Parse texture chunks + case Discreet3DS::CHUNK_MAT_TEXTURE: + // Diffuse texture + ParseTextureChunk(&mScene->mMaterials.back().sTexDiffuse); + break; + case Discreet3DS::CHUNK_MAT_BUMPMAP: + // Height map + ParseTextureChunk(&mScene->mMaterials.back().sTexBump); + break; + case Discreet3DS::CHUNK_MAT_OPACMAP: + // Opacity texture + ParseTextureChunk(&mScene->mMaterials.back().sTexOpacity); + break; + case Discreet3DS::CHUNK_MAT_MAT_SHINMAP: + // Shininess map + ParseTextureChunk(&mScene->mMaterials.back().sTexShininess); + break; + case Discreet3DS::CHUNK_MAT_SPECMAP: + // Specular map + ParseTextureChunk(&mScene->mMaterials.back().sTexSpecular); + break; + case Discreet3DS::CHUNK_MAT_SELFIMAP: + // Self-illumination (emissive) map + ParseTextureChunk(&mScene->mMaterials.back().sTexEmissive); + break; + case Discreet3DS::CHUNK_MAT_REFLMAP: + // Reflection map + ParseTextureChunk(&mScene->mMaterials.back().sTexReflective); + break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseTextureChunk(D3DS::Texture *pcOut) { + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_MAPFILE: { + // The material name string is already zero-terminated, but we need to be sure ... + const char *sz = (const char *)stream->GetPtr(); + unsigned int cnt = 0; + while (stream->GetI1()) + ++cnt; + pcOut->mMapName = std::string(sz, cnt); + } break; + + case Discreet3DS::CHUNK_PERCENTD: + // Manually parse the blend factor + pcOut->mTextureBlend = ai_real(stream->GetF8()); + break; + + case Discreet3DS::CHUNK_PERCENTF: + // Manually parse the blend factor + pcOut->mTextureBlend = stream->GetF4(); + break; + + case Discreet3DS::CHUNK_PERCENTW: + // Manually parse the blend factor + pcOut->mTextureBlend = (ai_real)((uint16_t)stream->GetI2()) / ai_real(100.0); + break; + + case Discreet3DS::CHUNK_MAT_MAP_USCALE: + // Texture coordinate scaling in the U direction + pcOut->mScaleU = stream->GetF4(); + if (0.0f == pcOut->mScaleU) { + ASSIMP_LOG_WARN("Texture coordinate scaling in the x direction is zero. Assuming 1."); + pcOut->mScaleU = 1.0f; + } + break; + case Discreet3DS::CHUNK_MAT_MAP_VSCALE: + // Texture coordinate scaling in the V direction + pcOut->mScaleV = stream->GetF4(); + if (0.0f == pcOut->mScaleV) { + ASSIMP_LOG_WARN("Texture coordinate scaling in the y direction is zero. Assuming 1."); + pcOut->mScaleV = 1.0f; + } + break; + + case Discreet3DS::CHUNK_MAT_MAP_UOFFSET: + // Texture coordinate offset in the U direction + pcOut->mOffsetU = -stream->GetF4(); + break; + + case Discreet3DS::CHUNK_MAT_MAP_VOFFSET: + // Texture coordinate offset in the V direction + pcOut->mOffsetV = stream->GetF4(); + break; + + case Discreet3DS::CHUNK_MAT_MAP_ANG: + // Texture coordinate rotation, CCW in DEGREES + pcOut->mRotation = -AI_DEG_TO_RAD(stream->GetF4()); + break; + + case Discreet3DS::CHUNK_MAT_MAP_TILING: { + const uint16_t iFlags = stream->GetI2(); + + // Get the mapping mode (for both axes) + if (iFlags & 0x2u) + pcOut->mMapMode = aiTextureMapMode_Mirror; + + else if (iFlags & 0x10u) + pcOut->mMapMode = aiTextureMapMode_Decal; + + // wrapping in all remaining cases + else + pcOut->mMapMode = aiTextureMapMode_Wrap; + } break; + }; + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a percentage chunk +ai_real Discreet3DSImporter::ParsePercentageChunk() { + Discreet3DS::Chunk chunk; + ReadChunk(&chunk); + + if (Discreet3DS::CHUNK_PERCENTF == chunk.Flag) { + return stream->GetF4() * ai_real(100) / ai_real(0xFFFF); + } else if (Discreet3DS::CHUNK_PERCENTW == chunk.Flag) { + return (ai_real)((uint16_t)stream->GetI2()) / (ai_real)0xFFFF; + } + + return get_qnan(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a color chunk. If a percentage chunk is found instead it is read as a grayscale color +void Discreet3DSImporter::ParseColorChunk(aiColor3D *out, bool acceptPercent) { + ai_assert(out != nullptr); + + // error return value + const ai_real qnan = get_qnan(); + static const aiColor3D clrError = aiColor3D(qnan, qnan, qnan); + + Discreet3DS::Chunk chunk; + ReadChunk(&chunk); + const unsigned int diff = chunk.Size - sizeof(Discreet3DS::Chunk); + + bool bGamma = false; + + // Get the type of the chunk + switch (chunk.Flag) { + case Discreet3DS::CHUNK_LINRGBF: + bGamma = true; + + case Discreet3DS::CHUNK_RGBF: + if (sizeof(float) * 3 > diff) { + *out = clrError; + return; + } + out->r = stream->GetF4(); + out->g = stream->GetF4(); + out->b = stream->GetF4(); + break; + + case Discreet3DS::CHUNK_LINRGBB: + bGamma = true; + case Discreet3DS::CHUNK_RGBB: { + if (sizeof(char) * 3 > diff) { + *out = clrError; + return; + } + const ai_real invVal = ai_real(1.0) / ai_real(255.0); + out->r = (ai_real)(uint8_t)stream->GetI1() * invVal; + out->g = (ai_real)(uint8_t)stream->GetI1() * invVal; + out->b = (ai_real)(uint8_t)stream->GetI1() * invVal; + } break; + + // Percentage chunks are accepted, too. + case Discreet3DS::CHUNK_PERCENTF: + if (acceptPercent && 4 <= diff) { + out->g = out->b = out->r = stream->GetF4(); + break; + } + *out = clrError; + return; + + case Discreet3DS::CHUNK_PERCENTW: + if (acceptPercent && 1 <= diff) { + out->g = out->b = out->r = (ai_real)(uint8_t)stream->GetI1() / ai_real(255.0); + break; + } + *out = clrError; + return; + + default: + stream->IncPtr(diff); + // Skip unknown chunks, hope this won't cause any problems. + return ParseColorChunk(out, acceptPercent); + }; + (void)bGamma; +} + +#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER diff --git a/libs/assimp/code/AssetLib/3DS/3DSLoader.h b/libs/assimp/code/AssetLib/3DS/3DSLoader.h new file mode 100644 index 0000000..f47fcfe --- /dev/null +++ b/libs/assimp/code/AssetLib/3DS/3DSLoader.h @@ -0,0 +1,289 @@ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 3DSLoader.h + * @brief 3DS File format loader + */ +#ifndef AI_3DSIMPORTER_H_INC +#define AI_3DSIMPORTER_H_INC +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +#include <assimp/BaseImporter.h> +#include <assimp/types.h> + + +#include "3DSHelper.h" +#include <assimp/StreamReader.h> + +struct aiNode; + +namespace Assimp { + + +using namespace D3DS; + +// --------------------------------------------------------------------------------- +/** Importer class for 3D Studio r3 and r4 3DS files + */ +class Discreet3DSImporter : public BaseImporter { +public: + Discreet3DSImporter(); + ~Discreet3DSImporter(); + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool checkSig) const override; + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer* pImp) override; + +protected: + + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc* GetInfo () const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler) override; + + // ------------------------------------------------------------------- + /** Converts a temporary material to the outer representation + */ + void ConvertMaterial(D3DS::Material& p_cMat, + aiMaterial& p_pcOut); + + // ------------------------------------------------------------------- + /** Read a chunk + * + * @param pcOut Receives the current chunk + */ + void ReadChunk(Discreet3DS::Chunk* pcOut); + + // ------------------------------------------------------------------- + /** Parse a percentage chunk. mCurrent will point to the next + * chunk behind afterwards. If no percentage chunk is found + * QNAN is returned. + */ + ai_real ParsePercentageChunk(); + + // ------------------------------------------------------------------- + /** Parse a color chunk. mCurrent will point to the next + * chunk behind afterwards. If no color chunk is found + * QNAN is returned in all members. + */ + void ParseColorChunk(aiColor3D* p_pcOut, + bool p_bAcceptPercent = true); + + + // ------------------------------------------------------------------- + /** Skip a chunk in the file + */ + void SkipChunk(); + + // ------------------------------------------------------------------- + /** Generate the nodegraph + */ + void GenerateNodeGraph(aiScene* pcOut); + + // ------------------------------------------------------------------- + /** Parse a main top-level chunk in the file + */ + void ParseMainChunk(); + + // ------------------------------------------------------------------- + /** Parse a top-level chunk in the file + */ + void ParseChunk(const char* name, unsigned int num); + + // ------------------------------------------------------------------- + /** Parse a top-level editor chunk in the file + */ + void ParseEditorChunk(); + + // ------------------------------------------------------------------- + /** Parse a top-level object chunk in the file + */ + void ParseObjectChunk(); + + // ------------------------------------------------------------------- + /** Parse a material chunk in the file + */ + void ParseMaterialChunk(); + + // ------------------------------------------------------------------- + /** Parse a mesh chunk in the file + */ + void ParseMeshChunk(); + + // ------------------------------------------------------------------- + /** Parse a light chunk in the file + */ + void ParseLightChunk(); + + // ------------------------------------------------------------------- + /** Parse a camera chunk in the file + */ + void ParseCameraChunk(); + + // ------------------------------------------------------------------- + /** Parse a face list chunk in the file + */ + void ParseFaceChunk(); + + // ------------------------------------------------------------------- + /** Parse a keyframe chunk in the file + */ + void ParseKeyframeChunk(); + + // ------------------------------------------------------------------- + /** Parse a hierarchy chunk in the file + */ + void ParseHierarchyChunk(uint16_t parent); + + // ------------------------------------------------------------------- + /** Parse a texture chunk in the file + */ + void ParseTextureChunk(D3DS::Texture* pcOut); + + // ------------------------------------------------------------------- + /** Convert the meshes in the file + */ + void ConvertMeshes(aiScene* pcOut); + + // ------------------------------------------------------------------- + /** Replace the default material in the scene + */ + void ReplaceDefaultMaterial(); + + bool ContainsTextures(unsigned int i) const { + return !mScene->mMaterials[i].sTexDiffuse.mMapName.empty() || + !mScene->mMaterials[i].sTexBump.mMapName.empty() || + !mScene->mMaterials[i].sTexOpacity.mMapName.empty() || + !mScene->mMaterials[i].sTexEmissive.mMapName.empty() || + !mScene->mMaterials[i].sTexSpecular.mMapName.empty() || + !mScene->mMaterials[i].sTexShininess.mMapName.empty() ; + } + + // ------------------------------------------------------------------- + /** Convert the whole scene + */ + void ConvertScene(aiScene* pcOut); + + // ------------------------------------------------------------------- + /** generate unique vertices for a mesh + */ + void MakeUnique(D3DS::Mesh& sMesh); + + // ------------------------------------------------------------------- + /** Add a node to the node graph + */ + void AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,D3DS::Node* pcIn, + aiMatrix4x4& absTrafo); + + // ------------------------------------------------------------------- + /** Search for a node in the graph. + * Called recursively + */ + void InverseNodeSearch(D3DS::Node* pcNode,D3DS::Node* pcCurrent); + + // ------------------------------------------------------------------- + /** Apply the master scaling factor to the mesh + */ + void ApplyMasterScale(aiScene* pScene); + + // ------------------------------------------------------------------- + /** Clamp all indices in the file to a valid range + */ + void CheckIndices(D3DS::Mesh& sMesh); + + // ------------------------------------------------------------------- + /** Skip the TCB info in a track key + */ + void SkipTCBInfo(); + +protected: + + /** Stream to read from */ + StreamReaderLE* stream; + + /** Last touched node index */ + short mLastNodeIndex; + + /** Current node, root node */ + D3DS::Node* mCurrentNode, *mRootNode; + + /** Scene under construction */ + D3DS::Scene* mScene; + + /** Ambient base color of the scene */ + aiColor3D mClrAmbient; + + /** Master scaling factor of the scene */ + ai_real mMasterScale; + + /** Path to the background image of the scene */ + std::string mBackgroundImage; + bool bHasBG; + + /** true if PRJ file */ + bool bIsPrj; +}; + +} // end of namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER + +#endif // AI_3DSIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/3MF/3MFTypes.h b/libs/assimp/code/AssetLib/3MF/3MFTypes.h new file mode 100644 index 0000000..987cdf6 --- /dev/null +++ b/libs/assimp/code/AssetLib/3MF/3MFTypes.h @@ -0,0 +1,165 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#pragma once + +#include <assimp/vector3.h> +#include <assimp/matrix4x4.h> +#include <assimp/ParsingUtils.h> +#include <vector> +#include <string> + +struct aiMaterial; +struct aiMesh; + +namespace Assimp { +namespace D3MF { + +enum class ResourceType { + RT_Object, + RT_BaseMaterials, + RT_EmbeddedTexture2D, + RT_Texture2DGroup, + RT_Unknown +}; // To be extended with other resource types (eg. material extension resources like Texture2d, Texture2dGroup...) + +class Resource { +public: + int mId; + + Resource(int id) : + mId(id) { + // empty + } + + virtual ~Resource() { + // empty + } + + virtual ResourceType getType() const { + return ResourceType::RT_Unknown; + } +}; + +class EmbeddedTexture : public Resource { +public: + std::string mPath; + std::string mContentType; + std::string mTilestyleU; + std::string mTilestyleV; + std::vector<char> mBuffer; + + EmbeddedTexture(int id) : + Resource(id), + mPath(), + mContentType(), + mTilestyleU(), + mTilestyleV() { + // empty + } + + ~EmbeddedTexture() = default; + + ResourceType getType() const override { + return ResourceType::RT_EmbeddedTexture2D; + } +}; + +class Texture2DGroup : public Resource { +public: + std::vector<aiVector2D> mTex2dCoords; + int mTexId; + Texture2DGroup(int id) : + Resource(id), + mTexId(-1) { + // empty + } + + ~Texture2DGroup() = default; + + ResourceType getType() const override { + return ResourceType::RT_Texture2DGroup; + } +}; + +class BaseMaterials : public Resource { +public: + std::vector<unsigned int> mMaterialIndex; + + BaseMaterials(int id) : + Resource(id), + mMaterialIndex() { + // empty + } + + ~BaseMaterials() = default; + + ResourceType getType() const override { + return ResourceType::RT_BaseMaterials; + } +}; + +struct Component { + int mObjectId; + aiMatrix4x4 mTransformation; +}; + +class Object : public Resource { +public: + std::vector<aiMesh *> mMeshes; + std::vector<unsigned int> mMeshIndex; + std::vector<Component> mComponents; + std::string mName; + + Object(int id) : + Resource(id), + mName(std::string("Object_") + ai_to_string(id)) { + // empty + } + + ~Object() = default; + + ResourceType getType() const override { + return ResourceType::RT_Object; + } +}; + +} // namespace D3MF +} // namespace Assimp diff --git a/libs/assimp/code/AssetLib/3MF/3MFXmlTags.h b/libs/assimp/code/AssetLib/3MF/3MFXmlTags.h new file mode 100644 index 0000000..63f18b4 --- /dev/null +++ b/libs/assimp/code/AssetLib/3MF/3MFXmlTags.h @@ -0,0 +1,117 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#pragma once + +namespace Assimp { +namespace D3MF { + +namespace XmlTag { + // Root tag + const char* const RootTag = "3MF"; + + // Meta-data + const char* const meta = "metadata"; + const char* const meta_name = "name"; + + // Model-data specific tags + const char* const model = "model"; + const char* const model_unit = "unit"; + const char* const metadata = "metadata"; + const char* const resources = "resources"; + const char* const object = "object"; + const char* const mesh = "mesh"; + const char* const components = "components"; + const char* const component = "component"; + const char* const vertices = "vertices"; + const char* const vertex = "vertex"; + const char* const triangles = "triangles"; + const char* const triangle = "triangle"; + const char* const x = "x"; + const char* const y = "y"; + const char* const z = "z"; + const char* const v1 = "v1"; + const char* const v2 = "v2"; + const char* const v3 = "v3"; + const char* const id = "id"; + const char* const pid = "pid"; + const char* const pindex = "pindex"; + const char* const p1 = "p1"; + const char* const name = "name"; + const char* const type = "type"; + const char* const build = "build"; + const char* const item = "item"; + const char* const objectid = "objectid"; + const char* const transform = "transform"; + const char *const path = "path"; + + // Material definitions + const char* const basematerials = "basematerials"; + const char* const basematerials_base = "base"; + const char* const basematerials_name = "name"; + const char* const basematerials_displaycolor = "displaycolor"; + const char* const texture_2d = "m:texture2d"; + const char *const texture_group = "m:texture2dgroup"; + const char *const texture_content_type = "contenttype"; + const char *const texture_tilestyleu = "tilestyleu"; + const char *const texture_tilestylev = "tilestylev"; + const char *const texture_2d_coord = "m:tex2coord"; + const char *const texture_cuurd_u = "u"; + const char *const texture_cuurd_v = "v"; + + // Meta info tags + const char* const CONTENT_TYPES_ARCHIVE = "[Content_Types].xml"; + const char* const ROOT_RELATIONSHIPS_ARCHIVE = "_rels/.rels"; + const char* const SCHEMA_CONTENTTYPES = "http://schemas.openxmlformats.org/package/2006/content-types"; + const char* const SCHEMA_RELATIONSHIPS = "http://schemas.openxmlformats.org/package/2006/relationships"; + const char* const RELS_RELATIONSHIP_CONTAINER = "Relationships"; + const char* const RELS_RELATIONSHIP_NODE = "Relationship"; + const char* const RELS_ATTRIB_TARGET = "Target"; + const char* const RELS_ATTRIB_TYPE = "Type"; + const char* const RELS_ATTRIB_ID = "Id"; + const char* const PACKAGE_START_PART_RELATIONSHIP_TYPE = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"; + const char* const PACKAGE_PRINT_TICKET_RELATIONSHIP_TYPE = "http://schemas.microsoft.com/3dmanufacturing/2013/01/printticket"; + const char* const PACKAGE_TEXTURE_RELATIONSHIP_TYPE = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dtexture"; + const char* const PACKAGE_CORE_PROPERTIES_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"; + const char* const PACKAGE_THUMBNAIL_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail"; +} + +} // Namespace D3MF +} // Namespace Assimp diff --git a/libs/assimp/code/AssetLib/3MF/D3MFExporter.cpp b/libs/assimp/code/AssetLib/3MF/D3MFExporter.cpp new file mode 100644 index 0000000..42cd991 --- /dev/null +++ b/libs/assimp/code/AssetLib/3MF/D3MFExporter.cpp @@ -0,0 +1,403 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_3MF_EXPORTER + +#include "D3MFExporter.h" + +#include <assimp/Exceptional.h> +#include <assimp/StringUtils.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Exporter.hpp> +#include <assimp/IOStream.hpp> +#include <assimp/IOSystem.hpp> + +#include "3MFXmlTags.h" +#include "D3MFOpcPackage.h" + +#ifdef ASSIMP_USE_HUNTER +#include <zip/zip.h> +#else +#include <contrib/zip/src/zip.h> +#endif + +namespace Assimp { + +void ExportScene3MF(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) { + if (nullptr == pIOSystem) { + throw DeadlyExportError("Could not export 3MP archive: " + std::string(pFile)); + } + D3MF::D3MFExporter myExporter(pFile, pScene); + if (myExporter.validate()) { + if (pIOSystem->Exists(pFile)) { + if (!pIOSystem->DeleteFile(pFile)) { + throw DeadlyExportError("File exists, cannot override : " + std::string(pFile)); + } + } + bool ok = myExporter.exportArchive(pFile); + if (!ok) { + throw DeadlyExportError("Could not export 3MP archive: " + std::string(pFile)); + } + } +} + +namespace D3MF { + +D3MFExporter::D3MFExporter(const char *pFile, const aiScene *pScene) : + mArchiveName(pFile), m_zipArchive(nullptr), mScene(pScene), mModelOutput(), mRelOutput(), mContentOutput(), mBuildItems(), mRelations() { + // empty +} + +D3MFExporter::~D3MFExporter() { + for (size_t i = 0; i < mRelations.size(); ++i) { + delete mRelations[i]; + } + mRelations.clear(); +} + +bool D3MFExporter::validate() { + if (mArchiveName.empty()) { + return false; + } + + if (nullptr == mScene) { + return false; + } + + return true; +} + +bool D3MFExporter::exportArchive(const char *file) { + bool ok(true); + + m_zipArchive = zip_open(file, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w'); + if (nullptr == m_zipArchive) { + return false; + } + + ok |= exportContentTypes(); + ok |= export3DModel(); + ok |= exportRelations(); + + zip_close(m_zipArchive); + m_zipArchive = nullptr; + + return ok; +} + +bool D3MFExporter::exportContentTypes() { + mContentOutput.clear(); + + mContentOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; + mContentOutput << std::endl; + mContentOutput << "<Types xmlns = \"http://schemas.openxmlformats.org/package/2006/content-types\">"; + mContentOutput << std::endl; + mContentOutput << "<Default Extension = \"rels\" ContentType = \"application/vnd.openxmlformats-package.relationships+xml\" />"; + mContentOutput << std::endl; + mContentOutput << "<Default Extension = \"model\" ContentType = \"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\" />"; + mContentOutput << std::endl; + mContentOutput << "</Types>"; + mContentOutput << std::endl; + zipContentType(XmlTag::CONTENT_TYPES_ARCHIVE); + + return true; +} + +bool D3MFExporter::exportRelations() { + mRelOutput.clear(); + + mRelOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; + mRelOutput << std::endl; + mRelOutput << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">"; + + for (size_t i = 0; i < mRelations.size(); ++i) { + if (mRelations[i]->target[0] == '/') { + mRelOutput << "<Relationship Target=\"" << mRelations[i]->target << "\" "; + } else { + mRelOutput << "<Relationship Target=\"/" << mRelations[i]->target << "\" "; + } + mRelOutput << "Id=\"" << mRelations[i]->id << "\" "; + mRelOutput << "Type=\"" << mRelations[i]->type << "\" />"; + mRelOutput << std::endl; + } + mRelOutput << "</Relationships>"; + mRelOutput << std::endl; + + zipRelInfo("_rels", ".rels"); + mRelOutput.flush(); + + return true; +} + +bool D3MFExporter::export3DModel() { + mModelOutput.clear(); + + writeHeader(); + mModelOutput << "<" << XmlTag::model << " " << XmlTag::model_unit << "=\"millimeter\"" + << " xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\">" + << std::endl; + mModelOutput << "<" << XmlTag::resources << ">"; + mModelOutput << std::endl; + + writeMetaData(); + + writeBaseMaterials(); + + writeObjects(); + + mModelOutput << "</" << XmlTag::resources << ">"; + mModelOutput << std::endl; + writeBuild(); + + mModelOutput << "</" << XmlTag::model << ">\n"; + + OpcPackageRelationship *info = new OpcPackageRelationship; + info->id = "rel0"; + info->target = "/3D/3DModel.model"; + info->type = XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE; + mRelations.push_back(info); + + zipModel("3D", "3DModel.model"); + mModelOutput.flush(); + + return true; +} + +void D3MFExporter::writeHeader() { + mModelOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; + mModelOutput << std::endl; +} + +void D3MFExporter::writeMetaData() { + if (nullptr == mScene->mMetaData) { + return; + } + + const unsigned int numMetaEntries(mScene->mMetaData->mNumProperties); + if (0 == numMetaEntries) { + return; + } + + const aiString *key = nullptr; + const aiMetadataEntry *entry(nullptr); + for (size_t i = 0; i < numMetaEntries; ++i) { + mScene->mMetaData->Get(i, key, entry); + std::string k(key->C_Str()); + aiString value; + mScene->mMetaData->Get(k, value); + mModelOutput << "<" << XmlTag::meta << " " << XmlTag::meta_name << "=\"" << key->C_Str() << "\">"; + mModelOutput << value.C_Str(); + mModelOutput << "</" << XmlTag::meta << ">" << std::endl; + } +} + +void D3MFExporter::writeBaseMaterials() { + mModelOutput << "<basematerials id=\"1\">\n"; + std::string strName, hexDiffuseColor, tmp; + for (size_t i = 0; i < mScene->mNumMaterials; ++i) { + aiMaterial *mat = mScene->mMaterials[i]; + aiString name; + if (mat->Get(AI_MATKEY_NAME, name) != aiReturn_SUCCESS) { + strName = "basemat_" + ai_to_string(i); + } else { + strName = name.C_Str(); + } + aiColor4D color; + if (mat->Get(AI_MATKEY_COLOR_DIFFUSE, color) == aiReturn_SUCCESS) { + hexDiffuseColor.clear(); + tmp.clear(); + // rgbs % + if (color.r <= 1 && color.g <= 1 && color.b <= 1 && color.a <= 1) { + + hexDiffuseColor = ai_rgba2hex( + (int)((ai_real)color.r) * 255, + (int)((ai_real)color.g) * 255, + (int)((ai_real)color.b) * 255, + (int)((ai_real)color.a) * 255, + true); + + } else { + hexDiffuseColor = "#"; + tmp = ai_decimal_to_hexa((ai_real)color.r); + hexDiffuseColor += tmp; + tmp = ai_decimal_to_hexa((ai_real)color.g); + hexDiffuseColor += tmp; + tmp = ai_decimal_to_hexa((ai_real)color.b); + hexDiffuseColor += tmp; + tmp = ai_decimal_to_hexa((ai_real)color.a); + hexDiffuseColor += tmp; + } + } else { + hexDiffuseColor = "#FFFFFFFF"; + } + + mModelOutput << "<base name=\"" + strName + "\" " + " displaycolor=\"" + hexDiffuseColor + "\" />\n"; + } + mModelOutput << "</basematerials>\n"; +} + +void D3MFExporter::writeObjects() { + if (nullptr == mScene->mRootNode) { + return; + } + + aiNode *root = mScene->mRootNode; + for (unsigned int i = 0; i < root->mNumChildren; ++i) { + aiNode *currentNode(root->mChildren[i]); + if (nullptr == currentNode) { + continue; + } + mModelOutput << "<" << XmlTag::object << " id=\"" << i + 2 << "\" type=\"model\">"; + mModelOutput << std::endl; + for (unsigned int j = 0; j < currentNode->mNumMeshes; ++j) { + aiMesh *currentMesh = mScene->mMeshes[currentNode->mMeshes[j]]; + if (nullptr == currentMesh) { + continue; + } + writeMesh(currentMesh); + } + mBuildItems.push_back(i); + + mModelOutput << "</" << XmlTag::object << ">"; + mModelOutput << std::endl; + } +} + +void D3MFExporter::writeMesh(aiMesh *mesh) { + if (nullptr == mesh) { + return; + } + + mModelOutput << "<" + << XmlTag::mesh + << ">" << "\n"; + mModelOutput << "<" + << XmlTag::vertices + << ">" << "\n"; + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + writeVertex(mesh->mVertices[i]); + } + mModelOutput << "</" + << XmlTag::vertices << ">" + << "\n"; + + const unsigned int matIdx(mesh->mMaterialIndex); + + writeFaces(mesh, matIdx); + + mModelOutput << "</" + << XmlTag::mesh << ">" + << "\n"; +} + +void D3MFExporter::writeVertex(const aiVector3D &pos) { + mModelOutput << "<" << XmlTag::vertex << " x=\"" << pos.x << "\" y=\"" << pos.y << "\" z=\"" << pos.z << "\" />"; + mModelOutput << std::endl; +} + +void D3MFExporter::writeFaces(aiMesh *mesh, unsigned int matIdx) { + if (nullptr == mesh) { + return; + } + + if (!mesh->HasFaces()) { + return; + } + mModelOutput << "<" + << XmlTag::triangles << ">" + << "\n"; + for (unsigned int i = 0; i < mesh->mNumFaces; ++i) { + aiFace ¤tFace = mesh->mFaces[i]; + mModelOutput << "<" << XmlTag::triangle << " v1=\"" << currentFace.mIndices[0] << "\" v2=\"" + << currentFace.mIndices[1] << "\" v3=\"" << currentFace.mIndices[2] + << "\" pid=\"1\" p1=\"" + ai_to_string(matIdx) + "\" />"; + mModelOutput << "\n"; + } + mModelOutput << "</" + << XmlTag::triangles + << ">"; + mModelOutput << "\n"; +} + +void D3MFExporter::writeBuild() { + mModelOutput << "<" + << XmlTag::build + << ">" + << "\n"; + + for (size_t i = 0; i < mBuildItems.size(); ++i) { + mModelOutput << "<" << XmlTag::item << " objectid=\"" << i + 2 << "\"/>"; + mModelOutput << "\n"; + } + mModelOutput << "</" << XmlTag::build << ">"; + mModelOutput << "\n"; +} + +void D3MFExporter::zipContentType(const std::string &filename) { + addFileInZip(filename, mContentOutput.str()); +} + +void D3MFExporter::zipModel(const std::string &folder, const std::string &modelName) { + const std::string entry = folder + "/" + modelName; + addFileInZip(entry, mModelOutput.str()); +} + +void D3MFExporter::zipRelInfo(const std::string &folder, const std::string &relName) { + const std::string entry = folder + "/" + relName; + addFileInZip(entry, mRelOutput.str()); +} + +void D3MFExporter::addFileInZip(const std::string& entry, const std::string& content) { + if (nullptr == m_zipArchive) { + throw DeadlyExportError("3MF-Export: Zip archive not valid, nullptr."); + } + + zip_entry_open(m_zipArchive, entry.c_str()); + zip_entry_write(m_zipArchive, content.c_str(), content.size()); + zip_entry_close(m_zipArchive); +} + +} // Namespace D3MF +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_3MF_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/3MF/D3MFExporter.h b/libs/assimp/code/AssetLib/3MF/D3MFExporter.h new file mode 100644 index 0000000..680d54f --- /dev/null +++ b/libs/assimp/code/AssetLib/3MF/D3MFExporter.h @@ -0,0 +1,111 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#pragma once + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_3MF_EXPORTER + +#include <memory> +#include <sstream> +#include <vector> +#include <assimp/vector3.h> + +struct aiScene; +struct aiNode; +struct aiMaterial; +struct aiMesh; + +struct zip_t; + +namespace Assimp { + +class IOStream; + +namespace D3MF { + + +struct OpcPackageRelationship; + +class D3MFExporter { +public: + D3MFExporter( const char* pFile, const aiScene* pScene ); + ~D3MFExporter(); + bool validate(); + bool exportArchive( const char *file ); + bool exportContentTypes(); + bool exportRelations(); + bool export3DModel(); + +protected: + void writeHeader(); + void writeMetaData(); + void writeBaseMaterials(); + void writeObjects(); + void writeMesh( aiMesh *mesh ); + void writeVertex( const aiVector3D &pos ); + void writeFaces( aiMesh *mesh, unsigned int matIdx ); + void writeBuild(); + + // Zip the data + void zipContentType( const std::string &filename ); + void zipModel( const std::string &folder, const std::string &modelName ); + void zipRelInfo( const std::string &folder, const std::string &relName ); + void addFileInZip( const std::string &entry, const std::string &content ); + +private: + std::string mArchiveName; + zip_t *m_zipArchive; + const aiScene *mScene; + std::ostringstream mModelOutput; + std::ostringstream mRelOutput; + std::ostringstream mContentOutput; + std::vector<unsigned int> mBuildItems; + std::vector<OpcPackageRelationship*> mRelations; +}; + + +} // Namespace D3MF +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_3MF_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT + + diff --git a/libs/assimp/code/AssetLib/3MF/D3MFImporter.cpp b/libs/assimp/code/AssetLib/3MF/D3MFImporter.cpp new file mode 100644 index 0000000..5b0f34c --- /dev/null +++ b/libs/assimp/code/AssetLib/3MF/D3MFImporter.cpp @@ -0,0 +1,130 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_3MF_IMPORTER + +#include "D3MFImporter.h" +#include "3MFXmlTags.h" +#include "D3MFOpcPackage.h" +#include "XmlSerializer.h" + +#include <assimp/StringComparison.h> +#include <assimp/StringUtils.h> +#include <assimp/XmlParser.h> +#include <assimp/ZipArchiveIOSystem.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/fast_atof.h> + +#include <cassert> +#include <map> +#include <memory> +#include <string> +#include <vector> +#include <iomanip> +#include <cstring> + +namespace Assimp { + +using namespace D3MF; + +static const aiImporterDesc desc = { + "3mf Importer", + "", + "", + "http://3mf.io/", + aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour, + 0, + 0, + 0, + 0, + "3mf" +}; + +D3MFImporter::D3MFImporter() : + BaseImporter() { + // empty +} + +D3MFImporter::~D3MFImporter() { + // empty +} + +bool D3MFImporter::CanRead(const std::string &filename, IOSystem *pIOHandler, bool /*checkSig*/) const { + if (!ZipArchiveIOSystem::isZipArchive(pIOHandler, filename)) { + return false; + } + D3MF::D3MFOpcPackage opcPackage(pIOHandler, filename); + return opcPackage.validate(); +} + +void D3MFImporter::SetupProperties(const Importer*) { + // empty +} + +const aiImporterDesc *D3MFImporter::GetInfo() const { + return &desc; +} + +void D3MFImporter::InternReadFile(const std::string &filename, aiScene *pScene, IOSystem *pIOHandler) { + D3MFOpcPackage opcPackage(pIOHandler, filename); + + XmlParser xmlParser; + if (xmlParser.parse(opcPackage.RootStream())) { + XmlSerializer xmlSerializer(&xmlParser); + xmlSerializer.ImportXml(pScene); + + const std::vector<aiTexture*> &tex = opcPackage.GetEmbeddedTextures(); + if (!tex.empty()) { + pScene->mNumTextures = static_cast<unsigned int>(tex.size()); + pScene->mTextures = new aiTexture *[pScene->mNumTextures]; + for (unsigned int i = 0; i < pScene->mNumTextures; ++i) { + pScene->mTextures[i] = tex[i]; + } + } + } +} + +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_3MF_IMPORTER diff --git a/libs/assimp/code/AssetLib/3MF/D3MFImporter.h b/libs/assimp/code/AssetLib/3MF/D3MFImporter.h new file mode 100644 index 0000000..a39ae79 --- /dev/null +++ b/libs/assimp/code/AssetLib/3MF/D3MFImporter.h @@ -0,0 +1,91 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef AI_D3MFLOADER_H_INCLUDED +#define AI_D3MFLOADER_H_INCLUDED + +#ifndef ASSIMP_BUILD_NO_3MF_IMPORTER + +#include <assimp/BaseImporter.h> + +namespace Assimp { + +// --------------------------------------------------------------------------- +/// @brief The 3MF-importer class. +/// +/// Implements the basic topology import and embedded textures. +// --------------------------------------------------------------------------- +class D3MFImporter : public BaseImporter { +public: + /// @brief The default class constructor. + D3MFImporter(); + + /// @brief The class destructor. + ~D3MFImporter() override; + + /// @brief Performs the data format detection. + /// @param pFile The filename to check. + /// @param pIOHandler The used IO-System. + /// @param checkSig true for signature checking. + /// @return true for can be loaded, false for not. + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + + /// @brief Not used + /// @param pImp Not used + void SetupProperties(const Importer *pImp) override; + + /// @brief The importer description getter. + /// @return The info + const aiImporterDesc *GetInfo() const override; + +protected: + /// @brief Internal read function, performs the file parsing. + /// @param pFile The filename + /// @param pScene The scene to load in. + /// @param pIOHandler The io-system + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; +}; + +} // Namespace Assimp + +#endif // #ifndef ASSIMP_BUILD_NO_3MF_IMPORTER + +#endif // AI_D3MFLOADER_H_INCLUDED diff --git a/libs/assimp/code/AssetLib/3MF/D3MFOpcPackage.cpp b/libs/assimp/code/AssetLib/3MF/D3MFOpcPackage.cpp new file mode 100644 index 0000000..f88039a --- /dev/null +++ b/libs/assimp/code/AssetLib/3MF/D3MFOpcPackage.cpp @@ -0,0 +1,256 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_3MF_IMPORTER + +#include "D3MFOpcPackage.h" +#include <assimp/Exceptional.h> +#include <assimp/XmlParser.h> +#include <assimp/ZipArchiveIOSystem.h> +#include <assimp/ai_assert.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOStream.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/texture.h> +#include "3MFXmlTags.h" + +#include <algorithm> +#include <cassert> +#include <cstdlib> +#include <map> +#include <vector> + +namespace Assimp { + +namespace D3MF { +// ------------------------------------------------------------------------------------------------ + +using OpcPackageRelationshipPtr = std::shared_ptr<OpcPackageRelationship>; + +class OpcPackageRelationshipReader { +public: + OpcPackageRelationshipReader(XmlParser &parser) : + m_relationShips() { + XmlNode root = parser.getRootNode(); + ParseRootNode(root); + } + + void ParseRootNode(XmlNode &node) { + ParseAttributes(node); + for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) { + std::string name = currentNode.name(); + if (name == "Relationships") { + ParseRelationsNode(currentNode); + } + } + } + + void ParseAttributes(XmlNode & /*node*/) { + // empty + } + + bool validateRels(OpcPackageRelationshipPtr &relPtr) { + if (relPtr->id.empty() || relPtr->type.empty() || relPtr->target.empty()) { + return false; + } + + return true; + } + + void ParseRelationsNode(XmlNode &node) { + if (node.empty()) { + return; + } + + for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) { + const std::string name = currentNode.name(); + if (name == "Relationship") { + OpcPackageRelationshipPtr relPtr(new OpcPackageRelationship()); + relPtr->id = currentNode.attribute(XmlTag::RELS_ATTRIB_ID).as_string(); + relPtr->type = currentNode.attribute(XmlTag::RELS_ATTRIB_TYPE).as_string(); + relPtr->target = currentNode.attribute(XmlTag::RELS_ATTRIB_TARGET).as_string(); + if (validateRels(relPtr)) { + m_relationShips.push_back(relPtr); + } + } + } + } + + std::vector<OpcPackageRelationshipPtr> m_relationShips; +}; + +static bool IsEmbeddedTexture( const std::string &filename ) { + const std::string extension = BaseImporter::GetExtension(filename); + if (extension == "jpg" || extension == "png") { + std::string::size_type pos = filename.find("thumbnail"); + if (pos == std::string::npos) { + return false; + } + return true; + } + + return false; +} +// ------------------------------------------------------------------------------------------------ +D3MFOpcPackage::D3MFOpcPackage(IOSystem *pIOHandler, const std::string &rFile) : + mRootStream(nullptr), + mZipArchive() { + mZipArchive = new ZipArchiveIOSystem(pIOHandler, rFile); + if (!mZipArchive->isOpen()) { + throw DeadlyImportError("Failed to open file ", rFile, "."); + } + + std::vector<std::string> fileList; + mZipArchive->getFileList(fileList); + + for (auto &file : fileList) { + if (file == D3MF::XmlTag::ROOT_RELATIONSHIPS_ARCHIVE) { + if (!mZipArchive->Exists(file.c_str())) { + continue; + } + + IOStream *fileStream = mZipArchive->Open(file.c_str()); + if (nullptr == fileStream) { + ASSIMP_LOG_ERROR("Filestream is nullptr."); + continue; + } + + std::string rootFile = ReadPackageRootRelationship(fileStream); + if (!rootFile.empty() && rootFile[0] == '/') { + rootFile = rootFile.substr(1); + if (rootFile[0] == '/') { + // deal with zip-bug + rootFile = rootFile.substr(1); + } + } + + ASSIMP_LOG_VERBOSE_DEBUG(rootFile); + + mZipArchive->Close(fileStream); + + mRootStream = mZipArchive->Open(rootFile.c_str()); + ai_assert(mRootStream != nullptr); + if (nullptr == mRootStream) { + throw DeadlyImportError("Cannot open root-file in archive : " + rootFile); + } + } else if (file == D3MF::XmlTag::CONTENT_TYPES_ARCHIVE) { + ASSIMP_LOG_WARN("Ignored file of unsupported type CONTENT_TYPES_ARCHIVES", file); + } else if (IsEmbeddedTexture(file)) { + IOStream *fileStream = mZipArchive->Open(file.c_str()); + LoadEmbeddedTextures(fileStream, file); + mZipArchive->Close(fileStream); + } else { + ASSIMP_LOG_WARN("Ignored file of unknown type: ", file); + } + } +} + +D3MFOpcPackage::~D3MFOpcPackage() { + mZipArchive->Close(mRootStream); + delete mZipArchive; +} + +IOStream *D3MFOpcPackage::RootStream() const { + return mRootStream; +} + +const std::vector<aiTexture *> &D3MFOpcPackage::GetEmbeddedTextures() const { + return mEmbeddedTextures; +} + +static const char *const ModelRef = "3D/3dmodel.model"; + +bool D3MFOpcPackage::validate() { + if (nullptr == mRootStream || nullptr == mZipArchive) { + return false; + } + + return mZipArchive->Exists(ModelRef); +} + +std::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream *stream) { + XmlParser xmlParser; + if (!xmlParser.parse(stream)) { + return std::string(); + } + + OpcPackageRelationshipReader reader(xmlParser); + + auto itr = std::find_if(reader.m_relationShips.begin(), reader.m_relationShips.end(), [](const OpcPackageRelationshipPtr &rel) { + return rel->type == XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE; + }); + + if (itr == reader.m_relationShips.end()) { + throw DeadlyImportError("Cannot find ", XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE); + } + + return (*itr)->target; +} + +void D3MFOpcPackage::LoadEmbeddedTextures(IOStream *fileStream, const std::string &filename) { + if (nullptr == fileStream) { + return; + } + + const size_t size = fileStream->FileSize(); + if (0 == size) { + return; + } + + unsigned char *data = new unsigned char[size]; + fileStream->Read(data, 1, size); + aiTexture *texture = new aiTexture; + std::string embName = "*" + filename; + texture->mFilename.Set(embName.c_str()); + texture->mWidth = static_cast<unsigned int>(size); + texture->mHeight = 0; + texture->achFormatHint[0] = 'p'; + texture->achFormatHint[1] = 'n'; + texture->achFormatHint[2] = 'g'; + texture->achFormatHint[3] = '\0'; + texture->pcData = (aiTexel*) data; + mEmbeddedTextures.emplace_back(texture); +} + +} // Namespace D3MF +} // Namespace Assimp + +#endif //ASSIMP_BUILD_NO_3MF_IMPORTER diff --git a/libs/assimp/code/AssetLib/3MF/D3MFOpcPackage.h b/libs/assimp/code/AssetLib/3MF/D3MFOpcPackage.h new file mode 100644 index 0000000..f6803a0 --- /dev/null +++ b/libs/assimp/code/AssetLib/3MF/D3MFOpcPackage.h @@ -0,0 +1,84 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef D3MFOPCPACKAGE_H +#define D3MFOPCPACKAGE_H + +#include <assimp/IOSystem.hpp> +#include <memory> +#include <string> + +struct aiTexture; + +namespace Assimp { + +class ZipArchiveIOSystem; + +namespace D3MF { + +struct OpcPackageRelationship { + std::string id; + std::string type; + std::string target; +}; + +class D3MFOpcPackage { +public: + D3MFOpcPackage( IOSystem* pIOHandler, const std::string& file ); + ~D3MFOpcPackage(); + IOStream* RootStream() const; + bool validate(); + const std::vector<aiTexture*> &GetEmbeddedTextures() const; + +protected: + std::string ReadPackageRootRelationship(IOStream* stream); + void LoadEmbeddedTextures(IOStream *fileStream, const std::string &filename); + +private: + IOStream* mRootStream; + ZipArchiveIOSystem *mZipArchive; + std::vector<aiTexture *> mEmbeddedTextures; +}; + +} // namespace D3MF +} // namespace Assimp + +#endif // D3MFOPCPACKAGE_H diff --git a/libs/assimp/code/AssetLib/3MF/XmlSerializer.cpp b/libs/assimp/code/AssetLib/3MF/XmlSerializer.cpp new file mode 100644 index 0000000..9bd1c5b --- /dev/null +++ b/libs/assimp/code/AssetLib/3MF/XmlSerializer.cpp @@ -0,0 +1,593 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#include "XmlSerializer.h" +#include "D3MFOpcPackage.h" +#include "3MFXmlTags.h" +#include "3MFTypes.h" +#include <assimp/scene.h> + +namespace Assimp { +namespace D3MF { + +static const int IdNotSet = -1; + +namespace { + +static const size_t ColRGBA_Len = 9; +static const size_t ColRGB_Len = 7; + +// format of the color string: #RRGGBBAA or #RRGGBB (3MF Core chapter 5.1.1) +bool validateColorString(const char *color) { + const size_t len = strlen(color); + if (ColRGBA_Len != len && ColRGB_Len != len) { + return false; + } + + return true; +} + +aiFace ReadTriangle(XmlNode &node) { + aiFace face; + + face.mNumIndices = 3; + face.mIndices = new unsigned int[face.mNumIndices]; + face.mIndices[0] = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::v1).as_string())); + face.mIndices[1] = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::v2).as_string())); + face.mIndices[2] = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::v3).as_string())); + + return face; +} + +aiVector3D ReadVertex(XmlNode &node) { + aiVector3D vertex; + vertex.x = ai_strtof(node.attribute(XmlTag::x).as_string(), nullptr); + vertex.y = ai_strtof(node.attribute(XmlTag::y).as_string(), nullptr); + vertex.z = ai_strtof(node.attribute(XmlTag::z).as_string(), nullptr); + + return vertex; +} + +bool getNodeAttribute(const XmlNode &node, const std::string &attribute, std::string &value) { + pugi::xml_attribute objectAttribute = node.attribute(attribute.c_str()); + if (!objectAttribute.empty()) { + value = objectAttribute.as_string(); + return true; + } + + return false; +} + +bool getNodeAttribute(const XmlNode &node, const std::string &attribute, int &value) { + std::string strValue; + const bool ret = getNodeAttribute(node, attribute, strValue); + if (ret) { + value = std::atoi(strValue.c_str()); + return true; + } + + return false; +} + +aiMatrix4x4 parseTransformMatrix(std::string matrixStr) { + // split the string + std::vector<float> numbers; + std::string currentNumber; + for (char c : matrixStr) { + if (c == ' ') { + if (!currentNumber.empty()) { + float f = std::stof(currentNumber); + numbers.push_back(f); + currentNumber.clear(); + } + } else { + currentNumber.push_back(c); + } + } + if (!currentNumber.empty()) { + const float f = std::stof(currentNumber); + numbers.push_back(f); + } + + aiMatrix4x4 transformMatrix; + transformMatrix.a1 = numbers[0]; + transformMatrix.b1 = numbers[1]; + transformMatrix.c1 = numbers[2]; + transformMatrix.d1 = 0; + + transformMatrix.a2 = numbers[3]; + transformMatrix.b2 = numbers[4]; + transformMatrix.c2 = numbers[5]; + transformMatrix.d2 = 0; + + transformMatrix.a3 = numbers[6]; + transformMatrix.b3 = numbers[7]; + transformMatrix.c3 = numbers[8]; + transformMatrix.d3 = 0; + + transformMatrix.a4 = numbers[9]; + transformMatrix.b4 = numbers[10]; + transformMatrix.c4 = numbers[11]; + transformMatrix.d4 = 1; + + return transformMatrix; +} + +bool parseColor(const char *color, aiColor4D &diffuse) { + if (nullptr == color) { + return false; + } + + if (!validateColorString(color)) { + return false; + } + + if ('#' != color[0]) { + return false; + } + + char r[3] = { color[1], color[2], '\0' }; + diffuse.r = static_cast<ai_real>(strtol(r, nullptr, 16)) / ai_real(255.0); + + char g[3] = { color[3], color[4], '\0' }; + diffuse.g = static_cast<ai_real>(strtol(g, nullptr, 16)) / ai_real(255.0); + + char b[3] = { color[5], color[6], '\0' }; + diffuse.b = static_cast<ai_real>(strtol(b, nullptr, 16)) / ai_real(255.0); + const size_t len = strlen(color); + if (ColRGB_Len == len) { + return true; + } + + char a[3] = { color[7], color[8], '\0' }; + diffuse.a = static_cast<ai_real>(strtol(a, nullptr, 16)) / ai_real(255.0); + + return true; +} + +void assignDiffuseColor(XmlNode &node, aiMaterial *mat) { + const char *color = node.attribute(XmlTag::basematerials_displaycolor).as_string(); + aiColor4D diffuse; + if (parseColor(color, diffuse)) { + mat->AddProperty<aiColor4D>(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + } +} + +} // namespace + +XmlSerializer::XmlSerializer(XmlParser *xmlParser) : + mResourcesDictionnary(), + mMeshCount(0), + mXmlParser(xmlParser) { + ai_assert(nullptr != xmlParser); +} + +XmlSerializer::~XmlSerializer() { + for (auto &it : mResourcesDictionnary) { + delete it.second; + } +} + +void XmlSerializer::ImportXml(aiScene *scene) { + if (nullptr == scene) { + return; + } + + scene->mRootNode = new aiNode(XmlTag::RootTag); + XmlNode node = mXmlParser->getRootNode().child(XmlTag::model); + if (node.empty()) { + return; + } + + XmlNode resNode = node.child(XmlTag::resources); + for (auto ¤tNode : resNode.children()) { + const std::string currentNodeName = currentNode.name(); + if (currentNodeName == XmlTag::texture_2d) { + ReadEmbeddecTexture(currentNode); + } else if (currentNodeName == XmlTag::texture_group) { + ReadTextureGroup(currentNode); + } else if (currentNodeName == XmlTag::object) { + ReadObject(currentNode); + } else if (currentNodeName == XmlTag::basematerials) { + ReadBaseMaterials(currentNode); + } else if (currentNodeName == XmlTag::meta) { + ReadMetadata(currentNode); + } + } + StoreMaterialsInScene(scene); + XmlNode buildNode = node.child(XmlTag::build); + if (buildNode.empty()) { + return; + } + + for (auto ¤tNode : buildNode.children()) { + const std::string currentNodeName = currentNode.name(); + if (currentNodeName == XmlTag::item) { + int objectId = IdNotSet; + std::string transformationMatrixStr; + aiMatrix4x4 transformationMatrix; + getNodeAttribute(currentNode, D3MF::XmlTag::objectid, objectId); + bool hasTransform = getNodeAttribute(currentNode, D3MF::XmlTag::transform, transformationMatrixStr); + + auto it = mResourcesDictionnary.find(objectId); + if (it != mResourcesDictionnary.end() && it->second->getType() == ResourceType::RT_Object) { + Object *obj = static_cast<Object *>(it->second); + if (hasTransform) { + transformationMatrix = parseTransformMatrix(transformationMatrixStr); + } + + addObjectToNode(scene->mRootNode, obj, transformationMatrix); + } + } + } + + // import the metadata + if (!mMetaData.empty()) { + const size_t numMeta = mMetaData.size(); + scene->mMetaData = aiMetadata::Alloc(static_cast<unsigned int>(numMeta)); + for (size_t i = 0; i < numMeta; ++i) { + aiString val(mMetaData[i].value); + scene->mMetaData->Set(static_cast<unsigned int>(i), mMetaData[i].name, val); + } + } + + // import the meshes, materials are already stored + scene->mNumMeshes = static_cast<unsigned int>(mMeshCount); + if (scene->mNumMeshes != 0) { + scene->mMeshes = new aiMesh *[scene->mNumMeshes](); + for (auto &it : mResourcesDictionnary) { + if (it.second->getType() == ResourceType::RT_Object) { + Object *obj = static_cast<Object *>(it.second); + ai_assert(nullptr != obj); + for (unsigned int i = 0; i < obj->mMeshes.size(); ++i) { + scene->mMeshes[obj->mMeshIndex[i]] = obj->mMeshes[i]; + } + } + } + } +} + +void XmlSerializer::addObjectToNode(aiNode *parent, Object *obj, aiMatrix4x4 nodeTransform) { + ai_assert(nullptr != obj); + + aiNode *sceneNode = new aiNode(obj->mName); + sceneNode->mNumMeshes = static_cast<unsigned int>(obj->mMeshes.size()); + sceneNode->mMeshes = new unsigned int[sceneNode->mNumMeshes]; + std::copy(obj->mMeshIndex.begin(), obj->mMeshIndex.end(), sceneNode->mMeshes); + + sceneNode->mTransformation = nodeTransform; + if (nullptr != parent) { + parent->addChildren(1, &sceneNode); + } + + for (Assimp::D3MF::Component c : obj->mComponents) { + auto it = mResourcesDictionnary.find(c.mObjectId); + if (it != mResourcesDictionnary.end() && it->second->getType() == ResourceType::RT_Object) { + addObjectToNode(sceneNode, static_cast<Object *>(it->second), c.mTransformation); + } + } +} + +void XmlSerializer::ReadObject(XmlNode &node) { + int id = IdNotSet, pid = IdNotSet, pindex = IdNotSet; + bool hasId = getNodeAttribute(node, XmlTag::id, id); + if (!hasId) { + return; + } + + bool hasPid = getNodeAttribute(node, XmlTag::pid, pid); + bool hasPindex = getNodeAttribute(node, XmlTag::pindex, pindex); + + Object *obj = new Object(id); + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == D3MF::XmlTag::mesh) { + auto mesh = ReadMesh(currentNode); + mesh->mName.Set(ai_to_string(id)); + + if (hasPid) { + auto it = mResourcesDictionnary.find(pid); + if (hasPindex && it != mResourcesDictionnary.end() && it->second->getType() == ResourceType::RT_BaseMaterials) { + BaseMaterials *materials = static_cast<BaseMaterials *>(it->second); + mesh->mMaterialIndex = materials->mMaterialIndex[pindex]; + } + } + + obj->mMeshes.push_back(mesh); + obj->mMeshIndex.push_back(mMeshCount); + mMeshCount++; + } else if (currentName == D3MF::XmlTag::components) { + for (XmlNode ¤tSubNode : currentNode.children()) { + const std::string subNodeName = currentSubNode.name(); + if (subNodeName == D3MF::XmlTag::component) { + int objectId = IdNotSet; + std::string componentTransformStr; + aiMatrix4x4 componentTransform; + if (getNodeAttribute(currentSubNode, D3MF::XmlTag::transform, componentTransformStr)) { + componentTransform = parseTransformMatrix(componentTransformStr); + } + + if (getNodeAttribute(currentSubNode, D3MF::XmlTag::objectid, objectId)) { + obj->mComponents.push_back({ objectId, componentTransform }); + } + } + } + } + } + + mResourcesDictionnary.insert(std::make_pair(id, obj)); +} + +aiMesh *XmlSerializer::ReadMesh(XmlNode &node) { + if (node.empty()) { + return nullptr; + } + + aiMesh *mesh = new aiMesh(); + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == XmlTag::vertices) { + ImportVertices(currentNode, mesh); + } else if (currentName == XmlTag::triangles) { + ImportTriangles(currentNode, mesh); + } + } + + return mesh; +} + +void XmlSerializer::ReadMetadata(XmlNode &node) { + pugi::xml_attribute attribute = node.attribute(D3MF::XmlTag::meta_name); + const std::string name = attribute.as_string(); + const std::string value = node.value(); + if (name.empty()) { + return; + } + + MetaEntry entry; + entry.name = name; + entry.value = value; + mMetaData.push_back(entry); +} + +void XmlSerializer::ImportVertices(XmlNode &node, aiMesh *mesh) { + ai_assert(nullptr != mesh); + + std::vector<aiVector3D> vertices; + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == XmlTag::vertex) { + vertices.push_back(ReadVertex(currentNode)); + } + } + + mesh->mNumVertices = static_cast<unsigned int>(vertices.size()); + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + std::copy(vertices.begin(), vertices.end(), mesh->mVertices); +} + +void XmlSerializer::ImportTriangles(XmlNode &node, aiMesh *mesh) { + std::vector<aiFace> faces; + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == XmlTag::triangle) { + int pid = IdNotSet, p1 = IdNotSet; + bool hasPid = getNodeAttribute(currentNode, D3MF::XmlTag::pid, pid); + bool hasP1 = getNodeAttribute(currentNode, D3MF::XmlTag::p1, p1); + + if (hasPid && hasP1) { + auto it = mResourcesDictionnary.find(pid); + if (it != mResourcesDictionnary.end()) { + if (it->second->getType() == ResourceType::RT_BaseMaterials) { + BaseMaterials *baseMaterials = static_cast<BaseMaterials *>(it->second); + mesh->mMaterialIndex = baseMaterials->mMaterialIndex[p1]; + } else if (it->second->getType() == ResourceType::RT_Texture2DGroup) { + if (mesh->mTextureCoords[0] == nullptr) { + Texture2DGroup *group = static_cast<Texture2DGroup *>(it->second); + const std::string name = ai_to_string(group->mTexId); + for (size_t i = 0; i < mMaterials.size(); ++i) { + if (name == mMaterials[i]->GetName().C_Str()) { + mesh->mMaterialIndex = static_cast<unsigned int>(i); + } + } + mesh->mTextureCoords[0] = new aiVector3D[group->mTex2dCoords.size()]; + for (unsigned int i = 0; i < group->mTex2dCoords.size(); ++i) { + mesh->mTextureCoords[0][i] = aiVector3D(group->mTex2dCoords[i].x, group->mTex2dCoords[i].y, 0); + } + } + } + } + } + + aiFace face = ReadTriangle(currentNode); + faces.push_back(face); + } + } + + mesh->mNumFaces = static_cast<unsigned int>(faces.size()); + mesh->mFaces = new aiFace[mesh->mNumFaces]; + mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + std::copy(faces.begin(), faces.end(), mesh->mFaces); +} + +void XmlSerializer::ReadBaseMaterials(XmlNode &node) { + int id = IdNotSet; + if (getNodeAttribute(node, D3MF::XmlTag::id, id)) { + BaseMaterials *baseMaterials = new BaseMaterials(id); + + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == XmlTag::basematerials_base) { + baseMaterials->mMaterialIndex.push_back(static_cast<unsigned int>(mMaterials.size())); + mMaterials.push_back(readMaterialDef(currentNode, id)); + } + } + + mResourcesDictionnary.insert(std::make_pair(id, baseMaterials)); + } +} + +void XmlSerializer::ReadEmbeddecTexture(XmlNode &node) { + if (node.empty()) { + return; + } + + std::string value; + EmbeddedTexture *tex2D = nullptr; + if (XmlParser::getStdStrAttribute(node, XmlTag::id, value)) { + tex2D = new EmbeddedTexture(atoi(value.c_str())); + } + if (nullptr == tex2D) { + return; + } + + if (XmlParser::getStdStrAttribute(node, XmlTag::path, value)) { + tex2D->mPath = value; + } + if (XmlParser::getStdStrAttribute(node, XmlTag::texture_content_type, value)) { + tex2D->mContentType = value; + } + if (XmlParser::getStdStrAttribute(node, XmlTag::texture_tilestyleu, value)) { + tex2D->mTilestyleU = value; + } + if (XmlParser::getStdStrAttribute(node, XmlTag::texture_tilestylev, value)) { + tex2D->mTilestyleV = value; + } + mEmbeddedTextures.emplace_back(tex2D); + StoreEmbeddedTexture(tex2D); +} + +void XmlSerializer::StoreEmbeddedTexture(EmbeddedTexture *tex) { + aiMaterial *mat = new aiMaterial; + aiString s; + s.Set(ai_to_string(tex->mId).c_str()); + mat->AddProperty(&s, AI_MATKEY_NAME); + const std::string name = "*" + tex->mPath; + s.Set(name); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0)); + + aiColor3D col; + mat->AddProperty<aiColor3D>(&col, 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty<aiColor3D>(&col, 1, AI_MATKEY_COLOR_AMBIENT); + mat->AddProperty<aiColor3D>(&col, 1, AI_MATKEY_COLOR_EMISSIVE); + mat->AddProperty<aiColor3D>(&col, 1, AI_MATKEY_COLOR_SPECULAR); + mMaterials.emplace_back(mat); +} + +void XmlSerializer::ReadTextureCoords2D(XmlNode &node, Texture2DGroup *tex2DGroup) { + if (node.empty() || nullptr == tex2DGroup) { + return; + } + + int id = IdNotSet; + if (XmlParser::getIntAttribute(node, "texid", id)) { + tex2DGroup->mTexId = id; + } + + double value = 0.0; + for (XmlNode currentNode : node.children()) { + const std::string currentName = currentNode.name(); + aiVector2D texCoord; + if (currentName == XmlTag::texture_2d_coord) { + XmlParser::getDoubleAttribute(currentNode, XmlTag::texture_cuurd_u, value); + texCoord.x = (ai_real)value; + XmlParser::getDoubleAttribute(currentNode, XmlTag::texture_cuurd_v, value); + texCoord.y = (ai_real)value; + tex2DGroup->mTex2dCoords.push_back(texCoord); + } + } +} + +void XmlSerializer::ReadTextureGroup(XmlNode &node) { + if (node.empty()) { + return; + } + + int id = IdNotSet; + if (!XmlParser::getIntAttribute(node, XmlTag::id, id)) { + return; + } + + Texture2DGroup *group = new Texture2DGroup(id); + ReadTextureCoords2D(node, group); + mResourcesDictionnary.insert(std::make_pair(id, group)); +} + +aiMaterial *XmlSerializer::readMaterialDef(XmlNode &node, unsigned int basematerialsId) { + aiMaterial *material = new aiMaterial(); + material->mNumProperties = 0; + std::string name; + bool hasName = getNodeAttribute(node, D3MF::XmlTag::basematerials_name, name); + + std::string stdMaterialName; + const std::string strId(ai_to_string(basematerialsId)); + stdMaterialName += "id"; + stdMaterialName += strId; + stdMaterialName += "_"; + if (hasName) { + stdMaterialName += std::string(name); + } else { + stdMaterialName += "basemat_"; + stdMaterialName += ai_to_string(mMaterials.size()); + } + + aiString assimpMaterialName(stdMaterialName); + material->AddProperty(&assimpMaterialName, AI_MATKEY_NAME); + + assignDiffuseColor(node, material); + + return material; +} + +void XmlSerializer::StoreMaterialsInScene(aiScene *scene) { + if (nullptr == scene || mMaterials.empty()) { + return; + } + + scene->mNumMaterials = static_cast<unsigned int>(mMaterials.size()); + scene->mMaterials = new aiMaterial *[scene->mNumMaterials]; + for (size_t i = 0; i < mMaterials.size(); ++i) { + scene->mMaterials[i] = mMaterials[i]; + } +} + +} // namespace D3MF +} // namespace Assimp diff --git a/libs/assimp/code/AssetLib/3MF/XmlSerializer.h b/libs/assimp/code/AssetLib/3MF/XmlSerializer.h new file mode 100644 index 0000000..6cf6a70 --- /dev/null +++ b/libs/assimp/code/AssetLib/3MF/XmlSerializer.h @@ -0,0 +1,96 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#pragma once + +#include <assimp/XmlParser.h> +#include <assimp/mesh.h> +#include <vector> +#include <map> + +struct aiNode; +struct aiMesh; +struct aiMaterial; + +namespace Assimp { +namespace D3MF { + +class Resource; +class D3MFOpcPackage; +class Object; +class Texture2DGroup; +class EmbeddedTexture; + +class XmlSerializer { +public: + XmlSerializer(XmlParser *xmlParser); + ~XmlSerializer(); + void ImportXml(aiScene *scene); + +private: + void addObjectToNode(aiNode *parent, Object *obj, aiMatrix4x4 nodeTransform); + void ReadObject(XmlNode &node); + aiMesh *ReadMesh(XmlNode &node); + void ReadMetadata(XmlNode &node); + void ImportVertices(XmlNode &node, aiMesh *mesh); + void ImportTriangles(XmlNode &node, aiMesh *mesh); + void ReadBaseMaterials(XmlNode &node); + void ReadEmbeddecTexture(XmlNode &node); + void StoreEmbeddedTexture(EmbeddedTexture *tex); + void ReadTextureCoords2D(XmlNode &node, Texture2DGroup *tex2DGroup); + void ReadTextureGroup(XmlNode &node); + aiMaterial *readMaterialDef(XmlNode &node, unsigned int basematerialsId); + void StoreMaterialsInScene(aiScene *scene); + +private: + struct MetaEntry { + std::string name; + std::string value; + }; + std::vector<MetaEntry> mMetaData; + std::vector<EmbeddedTexture *> mEmbeddedTextures; + std::vector<aiMaterial *> mMaterials; + std::map<unsigned int, Resource *> mResourcesDictionnary; + unsigned int mMeshCount; + XmlParser *mXmlParser; +}; + +} // namespace D3MF +} // namespace Assimp diff --git a/libs/assimp/code/AssetLib/AC/ACLoader.cpp b/libs/assimp/code/AssetLib/AC/ACLoader.cpp new file mode 100644 index 0000000..e93624b --- /dev/null +++ b/libs/assimp/code/AssetLib/AC/ACLoader.cpp @@ -0,0 +1,865 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the AC3D importer class */ + +#ifndef ASSIMP_BUILD_NO_AC_IMPORTER + +// internal headers +#include "ACLoader.h" +#include "Common/Importer.h" +#include <assimp/BaseImporter.h> +#include <assimp/ParsingUtils.h> +#include <assimp/Subdivision.h> +#include <assimp/config.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/light.h> +#include <assimp/material.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/Importer.hpp> +#include <memory> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "AC3D Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "ac acc ac3d" +}; + +// ------------------------------------------------------------------------------------------------ +// skip to the next token +inline const char *AcSkipToNextToken(const char *buffer) { + if (!SkipSpaces(&buffer)) { + ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL"); + } + return buffer; +} + +// ------------------------------------------------------------------------------------------------ +// read a string (may be enclosed in double quotation marks). buffer must point to " +inline const char *AcGetString(const char *buffer, std::string &out) { + if (*buffer == '\0') { + throw DeadlyImportError("AC3D: Unexpected EOF in string"); + } + ++buffer; + const char *sz = buffer; + while ('\"' != *buffer) { + if (IsLineEnd(*buffer)) { + ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL in string"); + out = "ERROR"; + break; + } + ++buffer; + } + if (IsLineEnd(*buffer)) { + return buffer; + } + out = std::string(sz, (unsigned int)(buffer - sz)); + ++buffer; + + return buffer; +} + +// ------------------------------------------------------------------------------------------------ +// read 1 to n floats prefixed with an optional predefined identifier +template <class T> +inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *name, size_t name_length, size_t num, T *out) { + buffer = AcSkipToNextToken(buffer); + if (0 != name_length) { + if (0 != strncmp(buffer, name, name_length) || !IsSpace(buffer[name_length])) { + ASSIMP_LOG_ERROR("AC3D: Unexpected token. ", name, " was expected."); + return buffer; + } + buffer += name_length + 1; + } + for (unsigned int _i = 0; _i < num; ++_i) { + buffer = AcSkipToNextToken(buffer); + buffer = fast_atoreal_move<float>(buffer, ((float *)out)[_i]); + } + + return buffer; +} + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +AC3DImporter::AC3DImporter() : + buffer(), + configSplitBFCull(), + configEvalSubdivision(), + mNumMeshes(), + mLights(), + mLightsCounter(0), + mGroupsCounter(0), + mPolysCounter(0), + mWorldsCounter(0) { + // nothing to be done here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +AC3DImporter::~AC3DImporter() { + // nothing to be done here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool AC3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const uint32_t tokens[] = { AI_MAKE_MAGIC("AC3D") }; + return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Loader meta information +const aiImporterDesc *AC3DImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Get a pointer to the next line from the file +bool AC3DImporter::GetNextLine() { + SkipLine(&buffer); + return SkipSpaces(&buffer); +} + +// ------------------------------------------------------------------------------------------------ +// Parse an object section in an AC file +void AC3DImporter::LoadObjectSection(std::vector<Object> &objects) { + if (!TokenMatch(buffer, "OBJECT", 6)) + return; + + SkipSpaces(&buffer); + + ++mNumMeshes; + + objects.push_back(Object()); + Object &obj = objects.back(); + + aiLight *light = nullptr; + if (!ASSIMP_strincmp(buffer, "light", 5)) { + // This is a light source. Add it to the list + mLights->push_back(light = new aiLight()); + + // Return a point light with no attenuation + light->mType = aiLightSource_POINT; + light->mColorDiffuse = light->mColorSpecular = aiColor3D(1.f, 1.f, 1.f); + light->mAttenuationConstant = 1.f; + + // Generate a default name for both the light source and the node + // FIXME - what's the right way to print a size_t? Is 'zu' universally available? stick with the safe version. + light->mName.length = ::ai_snprintf(light->mName.data, MAXLEN, "ACLight_%i", static_cast<unsigned int>(mLights->size()) - 1); + obj.name = std::string(light->mName.data); + + ASSIMP_LOG_VERBOSE_DEBUG("AC3D: Light source encountered"); + obj.type = Object::Light; + } else if (!ASSIMP_strincmp(buffer, "group", 5)) { + obj.type = Object::Group; + } else if (!ASSIMP_strincmp(buffer, "world", 5)) { + obj.type = Object::World; + } else + obj.type = Object::Poly; + while (GetNextLine()) { + if (TokenMatch(buffer, "kids", 4)) { + SkipSpaces(&buffer); + unsigned int num = strtoul10(buffer, &buffer); + GetNextLine(); + if (num) { + // load the children of this object recursively + obj.children.reserve(num); + for (unsigned int i = 0; i < num; ++i) + LoadObjectSection(obj.children); + } + return; + } else if (TokenMatch(buffer, "name", 4)) { + SkipSpaces(&buffer); + buffer = AcGetString(buffer, obj.name); + + // If this is a light source, we'll also need to store + // the name of the node in it. + if (light) { + light->mName.Set(obj.name); + } + } else if (TokenMatch(buffer, "texture", 7)) { + SkipSpaces(&buffer); + buffer = AcGetString(buffer, obj.texture); + } else if (TokenMatch(buffer, "texrep", 6)) { + SkipSpaces(&buffer); + buffer = TAcCheckedLoadFloatArray(buffer, "", 0, 2, &obj.texRepeat); + if (!obj.texRepeat.x || !obj.texRepeat.y) + obj.texRepeat = aiVector2D(1.f, 1.f); + } else if (TokenMatch(buffer, "texoff", 6)) { + SkipSpaces(&buffer); + buffer = TAcCheckedLoadFloatArray(buffer, "", 0, 2, &obj.texOffset); + } else if (TokenMatch(buffer, "rot", 3)) { + SkipSpaces(&buffer); + buffer = TAcCheckedLoadFloatArray(buffer, "", 0, 9, &obj.rotation); + } else if (TokenMatch(buffer, "loc", 3)) { + SkipSpaces(&buffer); + buffer = TAcCheckedLoadFloatArray(buffer, "", 0, 3, &obj.translation); + } else if (TokenMatch(buffer, "subdiv", 6)) { + SkipSpaces(&buffer); + obj.subDiv = strtoul10(buffer, &buffer); + } else if (TokenMatch(buffer, "crease", 6)) { + SkipSpaces(&buffer); + obj.crease = fast_atof(buffer); + } else if (TokenMatch(buffer, "numvert", 7)) { + SkipSpaces(&buffer); + + unsigned int t = strtoul10(buffer, &buffer); + if (t >= AI_MAX_ALLOC(aiVector3D)) { + throw DeadlyImportError("AC3D: Too many vertices, would run out of memory"); + } + obj.vertices.reserve(t); + for (unsigned int i = 0; i < t; ++i) { + if (!GetNextLine()) { + ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: not all vertices have been parsed yet"); + break; + } else if (!IsNumeric(*buffer)) { + ASSIMP_LOG_ERROR("AC3D: Unexpected token: not all vertices have been parsed yet"); + --buffer; // make sure the line is processed a second time + break; + } + obj.vertices.push_back(aiVector3D()); + aiVector3D &v = obj.vertices.back(); + buffer = TAcCheckedLoadFloatArray(buffer, "", 0, 3, &v.x); + } + } else if (TokenMatch(buffer, "numsurf", 7)) { + SkipSpaces(&buffer); + + bool Q3DWorkAround = false; + + const unsigned int t = strtoul10(buffer, &buffer); + obj.surfaces.reserve(t); + for (unsigned int i = 0; i < t; ++i) { + GetNextLine(); + if (!TokenMatch(buffer, "SURF", 4)) { + // FIX: this can occur for some files - Quick 3D for + // example writes no surf chunks + if (!Q3DWorkAround) { + ASSIMP_LOG_WARN("AC3D: SURF token was expected"); + ASSIMP_LOG_VERBOSE_DEBUG("Continuing with Quick3D Workaround enabled"); + } + --buffer; // make sure the line is processed a second time + // break; --- see fix notes above + + Q3DWorkAround = true; + } + SkipSpaces(&buffer); + obj.surfaces.push_back(Surface()); + Surface &surf = obj.surfaces.back(); + surf.flags = strtoul_cppstyle(buffer); + + while (1) { + if (!GetNextLine()) { + throw DeadlyImportError("AC3D: Unexpected EOF: surface is incomplete"); + } + if (TokenMatch(buffer, "mat", 3)) { + SkipSpaces(&buffer); + surf.mat = strtoul10(buffer); + } else if (TokenMatch(buffer, "refs", 4)) { + // --- see fix notes above + if (Q3DWorkAround) { + if (!surf.entries.empty()) { + buffer -= 6; + break; + } + } + + SkipSpaces(&buffer); + const unsigned int m = strtoul10(buffer); + surf.entries.reserve(m); + + obj.numRefs += m; + + for (unsigned int k = 0; k < m; ++k) { + if (!GetNextLine()) { + ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: surface references are incomplete"); + break; + } + surf.entries.push_back(Surface::SurfaceEntry()); + Surface::SurfaceEntry &entry = surf.entries.back(); + + entry.first = strtoul10(buffer, &buffer); + SkipSpaces(&buffer); + buffer = TAcCheckedLoadFloatArray(buffer, "", 0, 2, &entry.second); + } + } else { + --buffer; // make sure the line is processed a second time + break; + } + } + } + } + } + ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: \'kids\' line was expected"); +} + +// ------------------------------------------------------------------------------------------------ +// Convert a material from AC3DImporter::Material to aiMaterial +void AC3DImporter::ConvertMaterial(const Object &object, + const Material &matSrc, + aiMaterial &matDest) { + aiString s; + + if (matSrc.name.length()) { + s.Set(matSrc.name); + matDest.AddProperty(&s, AI_MATKEY_NAME); + } + if (object.texture.length()) { + s.Set(object.texture); + matDest.AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0)); + + // UV transformation + if (1.f != object.texRepeat.x || 1.f != object.texRepeat.y || + object.texOffset.x || object.texOffset.y) { + aiUVTransform transform; + transform.mScaling = object.texRepeat; + transform.mTranslation = object.texOffset; + matDest.AddProperty(&transform, 1, AI_MATKEY_UVTRANSFORM_DIFFUSE(0)); + } + } + + matDest.AddProperty<aiColor3D>(&matSrc.rgb, 1, AI_MATKEY_COLOR_DIFFUSE); + matDest.AddProperty<aiColor3D>(&matSrc.amb, 1, AI_MATKEY_COLOR_AMBIENT); + matDest.AddProperty<aiColor3D>(&matSrc.emis, 1, AI_MATKEY_COLOR_EMISSIVE); + matDest.AddProperty<aiColor3D>(&matSrc.spec, 1, AI_MATKEY_COLOR_SPECULAR); + + int n = -1; + if (matSrc.shin) { + n = aiShadingMode_Phong; + matDest.AddProperty<float>(&matSrc.shin, 1, AI_MATKEY_SHININESS); + } else { + n = aiShadingMode_Gouraud; + } + matDest.AddProperty<int>(&n, 1, AI_MATKEY_SHADING_MODEL); + + float f = 1.f - matSrc.trans; + matDest.AddProperty<float>(&f, 1, AI_MATKEY_OPACITY); +} + +// ------------------------------------------------------------------------------------------------ +// Converts the loaded data to the internal verbose representation +aiNode *AC3DImporter::ConvertObjectSection(Object &object, + std::vector<aiMesh *> &meshes, + std::vector<aiMaterial *> &outMaterials, + const std::vector<Material> &materials, + aiNode *parent) { + aiNode *node = new aiNode(); + node->mParent = parent; + if (object.vertices.size()) { + if (!object.surfaces.size() || !object.numRefs) { + /* " An object with 7 vertices (no surfaces, no materials defined). + This is a good way of getting point data into AC3D. + The Vertex->create convex-surface/object can be used on these + vertices to 'wrap' a 3d shape around them " + (http://www.opencity.info/html/ac3dfileformat.html) + + therefore: if no surfaces are defined return point data only + */ + + ASSIMP_LOG_INFO("AC3D: No surfaces defined in object definition, " + "a point list is returned"); + + meshes.push_back(new aiMesh()); + aiMesh *mesh = meshes.back(); + + mesh->mNumFaces = mesh->mNumVertices = (unsigned int)object.vertices.size(); + aiFace *faces = mesh->mFaces = new aiFace[mesh->mNumFaces]; + aiVector3D *verts = mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + + for (unsigned int i = 0; i < mesh->mNumVertices; ++i, ++faces, ++verts) { + *verts = object.vertices[i]; + faces->mNumIndices = 1; + faces->mIndices = new unsigned int[1]; + faces->mIndices[0] = i; + } + + // use the primary material in this case. this should be the + // default material if all objects of the file contain points + // and no faces. + mesh->mMaterialIndex = 0; + outMaterials.push_back(new aiMaterial()); + ConvertMaterial(object, materials[0], *outMaterials.back()); + } else { + // need to generate one or more meshes for this object. + // find out how many different materials we have + typedef std::pair<unsigned int, unsigned int> IntPair; + typedef std::vector<IntPair> MatTable; + MatTable needMat(materials.size(), IntPair(0, 0)); + + std::vector<Surface>::iterator it, end = object.surfaces.end(); + std::vector<Surface::SurfaceEntry>::iterator it2, end2; + + for (it = object.surfaces.begin(); it != end; ++it) { + unsigned int idx = (*it).mat; + if (idx >= needMat.size()) { + ASSIMP_LOG_ERROR("AC3D: material index is out of range"); + idx = 0; + } + if ((*it).entries.empty()) { + ASSIMP_LOG_WARN("AC3D: surface her zero vertex references"); + } + + // validate all vertex indices to make sure we won't crash here + for (it2 = (*it).entries.begin(), + end2 = (*it).entries.end(); + it2 != end2; ++it2) { + if ((*it2).first >= object.vertices.size()) { + ASSIMP_LOG_WARN("AC3D: Invalid vertex reference"); + (*it2).first = 0; + } + } + + if (!needMat[idx].first) { + ++node->mNumMeshes; + } + + switch ((*it).GetType()) { + // closed line + case Surface::ClosedLine: + needMat[idx].first += (unsigned int)(*it).entries.size(); + needMat[idx].second += (unsigned int)(*it).entries.size() << 1u; + break; + + // unclosed line + case Surface::OpenLine: + needMat[idx].first += (unsigned int)(*it).entries.size() - 1; + needMat[idx].second += ((unsigned int)(*it).entries.size() - 1) << 1u; + break; + + // triangle strip + case Surface::TriangleStrip: + needMat[idx].first += (unsigned int)(*it).entries.size() - 2; + needMat[idx].second += ((unsigned int)(*it).entries.size() - 2) * 3; + break; + + default: + // Coerce unknowns to a polygon and warn + ASSIMP_LOG_WARN("AC3D: The type flag of a surface is unknown: ", (*it).flags); + (*it).flags &= ~(Surface::Mask); + // fallthrough + + // polygon + case Surface::Polygon: + // the number of faces increments by one, the number + // of vertices by surface.numref. + needMat[idx].first++; + needMat[idx].second += (unsigned int)(*it).entries.size(); + }; + } + unsigned int *pip = node->mMeshes = new unsigned int[node->mNumMeshes]; + unsigned int mat = 0; + const size_t oldm = meshes.size(); + for (MatTable::const_iterator cit = needMat.begin(), cend = needMat.end(); + cit != cend; ++cit, ++mat) { + if (!(*cit).first) { + continue; + } + + // allocate a new aiMesh object + *pip++ = (unsigned int)meshes.size(); + aiMesh *mesh = new aiMesh(); + meshes.push_back(mesh); + + mesh->mMaterialIndex = static_cast<unsigned int>(outMaterials.size()); + outMaterials.push_back(new aiMaterial()); + ConvertMaterial(object, materials[mat], *outMaterials.back()); + + // allocate storage for vertices and normals + mesh->mNumFaces = (*cit).first; + if (mesh->mNumFaces == 0) { + throw DeadlyImportError("AC3D: No faces"); + } else if (mesh->mNumFaces > AI_MAX_ALLOC(aiFace)) { + throw DeadlyImportError("AC3D: Too many faces, would run out of memory"); + } + aiFace *faces = mesh->mFaces = new aiFace[mesh->mNumFaces]; + + mesh->mNumVertices = (*cit).second; + if (mesh->mNumVertices == 0) { + throw DeadlyImportError("AC3D: No vertices"); + } else if (mesh->mNumVertices > AI_MAX_ALLOC(aiVector3D)) { + throw DeadlyImportError("AC3D: Too many vertices, would run out of memory"); + } + aiVector3D *vertices = mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + unsigned int cur = 0; + + // allocate UV coordinates, but only if the texture name for the + // surface is not empty + aiVector3D *uv = nullptr; + if (object.texture.length()) { + uv = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; + mesh->mNumUVComponents[0] = 2; + } + + for (it = object.surfaces.begin(); it != end; ++it) { + if (mat == (*it).mat) { + const Surface &src = *it; + + // closed polygon + uint8_t type = (*it).GetType(); + if (type == Surface::Polygon) { + aiFace &face = *faces++; + face.mNumIndices = (unsigned int)src.entries.size(); + if (0 != face.mNumIndices) { + face.mIndices = new unsigned int[face.mNumIndices]; + for (unsigned int i = 0; i < face.mNumIndices; ++i, ++vertices) { + const Surface::SurfaceEntry &entry = src.entries[i]; + face.mIndices[i] = cur++; + + // copy vertex positions + if (static_cast<unsigned>(vertices - mesh->mVertices) >= mesh->mNumVertices) { + throw DeadlyImportError("AC3D: Invalid number of vertices"); + } + *vertices = object.vertices[entry.first] + object.translation; + + // copy texture coordinates + if (uv) { + uv->x = entry.second.x; + uv->y = entry.second.y; + ++uv; + } + } + } + } else if (type == Surface::TriangleStrip) { + for (unsigned int i = 0; i < (unsigned int)src.entries.size() - 2; ++i) { + const Surface::SurfaceEntry &entry1 = src.entries[i]; + const Surface::SurfaceEntry &entry2 = src.entries[i + 1]; + const Surface::SurfaceEntry &entry3 = src.entries[i + 2]; + + // skip degenerate triangles + if (object.vertices[entry1.first] == object.vertices[entry2.first] || + object.vertices[entry1.first] == object.vertices[entry3.first] || + object.vertices[entry2.first] == object.vertices[entry3.first]) { + mesh->mNumFaces--; + mesh->mNumVertices -= 3; + continue; + } + + aiFace &face = *faces++; + face.mNumIndices = 3; + face.mIndices = new unsigned int[face.mNumIndices]; + face.mIndices[0] = cur++; + face.mIndices[1] = cur++; + face.mIndices[2] = cur++; + if (!(i & 1)) { + *vertices++ = object.vertices[entry1.first] + object.translation; + if (uv) { + uv->x = entry1.second.x; + uv->y = entry1.second.y; + ++uv; + } + *vertices++ = object.vertices[entry2.first] + object.translation; + if (uv) { + uv->x = entry2.second.x; + uv->y = entry2.second.y; + ++uv; + } + } else { + *vertices++ = object.vertices[entry2.first] + object.translation; + if (uv) { + uv->x = entry2.second.x; + uv->y = entry2.second.y; + ++uv; + } + *vertices++ = object.vertices[entry1.first] + object.translation; + if (uv) { + uv->x = entry1.second.x; + uv->y = entry1.second.y; + ++uv; + } + } + if (static_cast<unsigned>(vertices - mesh->mVertices) >= mesh->mNumVertices) { + throw DeadlyImportError("AC3D: Invalid number of vertices"); + } + *vertices++ = object.vertices[entry3.first] + object.translation; + if (uv) { + uv->x = entry3.second.x; + uv->y = entry3.second.y; + ++uv; + } + } + } else { + + it2 = (*it).entries.begin(); + + // either a closed or an unclosed line + unsigned int tmp = (unsigned int)(*it).entries.size(); + if (Surface::OpenLine == type) --tmp; + for (unsigned int m = 0; m < tmp; ++m) { + aiFace &face = *faces++; + + face.mNumIndices = 2; + face.mIndices = new unsigned int[2]; + face.mIndices[0] = cur++; + face.mIndices[1] = cur++; + + // copy vertex positions + if (it2 == (*it).entries.end()) { + throw DeadlyImportError("AC3D: Bad line"); + } + ai_assert((*it2).first < object.vertices.size()); + *vertices++ = object.vertices[(*it2).first]; + + // copy texture coordinates + if (uv) { + uv->x = (*it2).second.x; + uv->y = (*it2).second.y; + ++uv; + } + + if (Surface::ClosedLine == type && tmp - 1 == m) { + // if this is a closed line repeat its beginning now + it2 = (*it).entries.begin(); + } else + ++it2; + + // second point + *vertices++ = object.vertices[(*it2).first]; + + if (uv) { + uv->x = (*it2).second.x; + uv->y = (*it2).second.y; + ++uv; + } + } + } + } + } + } + + // Now apply catmull clark subdivision if necessary. We split meshes into + // materials which is not done by AC3D during smoothing, so we need to + // collect all meshes using the same material group. + if (object.subDiv) { + if (configEvalSubdivision) { + std::unique_ptr<Subdivider> div(Subdivider::Create(Subdivider::CATMULL_CLARKE)); + ASSIMP_LOG_INFO("AC3D: Evaluating subdivision surface: ", object.name); + + std::vector<aiMesh *> cpy(meshes.size() - oldm, nullptr); + div->Subdivide(&meshes[oldm], cpy.size(), &cpy.front(), object.subDiv, true); + std::copy(cpy.begin(), cpy.end(), meshes.begin() + oldm); + + // previous meshes are deleted vy Subdivide(). + } else { + ASSIMP_LOG_INFO("AC3D: Letting the subdivision surface untouched due to my configuration: ", object.name); + } + } + } + } + + if (object.name.length()) + node->mName.Set(object.name); + else { + // generate a name depending on the type of the node + switch (object.type) { + case Object::Group: + node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACGroup_%i", mGroupsCounter++); + break; + case Object::Poly: + node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACPoly_%i", mPolysCounter++); + break; + case Object::Light: + node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACLight_%i", mLightsCounter++); + break; + + // there shouldn't be more than one world, but we don't care + case Object::World: + node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACWorld_%i", mWorldsCounter++); + break; + } + } + + // setup the local transformation matrix of the object + // compute the transformation offset to the parent node + node->mTransformation = aiMatrix4x4(object.rotation); + + if (object.type == Object::Group || !object.numRefs) { + node->mTransformation.a4 = object.translation.x; + node->mTransformation.b4 = object.translation.y; + node->mTransformation.c4 = object.translation.z; + } + + // add children to the object + if (object.children.size()) { + node->mNumChildren = (unsigned int)object.children.size(); + node->mChildren = new aiNode *[node->mNumChildren]; + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + node->mChildren[i] = ConvertObjectSection(object.children[i], meshes, outMaterials, materials, node); + } + } + + return node; +} + +// ------------------------------------------------------------------------------------------------ +void AC3DImporter::SetupProperties(const Importer *pImp) { + configSplitBFCull = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_SEPARATE_BFCULL, 1) ? true : false; + configEvalSubdivision = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_EVAL_SUBDIVISION, 1) ? true : false; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void AC3DImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb")); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open AC3D file ", pFile, "."); + } + + // allocate storage and copy the contents of the file to a memory buffer + std::vector<char> mBuffer2; + TextFileToBuffer(file.get(), mBuffer2); + + buffer = &mBuffer2[0]; + mNumMeshes = 0; + + mLightsCounter = mPolysCounter = mWorldsCounter = mGroupsCounter = 0; + + if (::strncmp(buffer, "AC3D", 4)) { + throw DeadlyImportError("AC3D: No valid AC3D file, magic sequence not found"); + } + + // print the file format version to the console + unsigned int version = HexDigitToDecimal(buffer[4]); + char msg[3]; + ASSIMP_itoa10(msg, 3, version); + ASSIMP_LOG_INFO("AC3D file format version: ", msg); + + std::vector<Material> materials; + materials.reserve(5); + + std::vector<Object> rootObjects; + rootObjects.reserve(5); + + std::vector<aiLight *> lights; + mLights = &lights; + + while (GetNextLine()) { + if (TokenMatch(buffer, "MATERIAL", 8)) { + materials.push_back(Material()); + Material &mat = materials.back(); + + // manually parse the material ... sscanf would use the buldin atof ... + // Format: (name) rgb %f %f %f amb %f %f %f emis %f %f %f spec %f %f %f shi %d trans %f + + buffer = AcSkipToNextToken(buffer); + if ('\"' == *buffer) { + buffer = AcGetString(buffer, mat.name); + buffer = AcSkipToNextToken(buffer); + } + + buffer = TAcCheckedLoadFloatArray(buffer, "rgb", 3, 3, &mat.rgb); + buffer = TAcCheckedLoadFloatArray(buffer, "amb", 3, 3, &mat.amb); + buffer = TAcCheckedLoadFloatArray(buffer, "emis", 4, 3, &mat.emis); + buffer = TAcCheckedLoadFloatArray(buffer, "spec", 4, 3, &mat.spec); + buffer = TAcCheckedLoadFloatArray(buffer, "shi", 3, 1, &mat.shin); + buffer = TAcCheckedLoadFloatArray(buffer, "trans", 5, 1, &mat.trans); + } + LoadObjectSection(rootObjects); + } + + if (rootObjects.empty() || !mNumMeshes) { + throw DeadlyImportError("AC3D: No meshes have been loaded"); + } + if (materials.empty()) { + ASSIMP_LOG_WARN("AC3D: No material has been found"); + materials.push_back(Material()); + } + + mNumMeshes += (mNumMeshes >> 2u) + 1; + std::vector<aiMesh *> meshes; + meshes.reserve(mNumMeshes); + + std::vector<aiMaterial *> omaterials; + materials.reserve(mNumMeshes); + + // generate a dummy root if there are multiple objects on the top layer + Object *root; + if (1 == rootObjects.size()) + root = &rootObjects[0]; + else { + root = new Object(); + } + + // now convert the imported stuff to our output data structure + pScene->mRootNode = ConvertObjectSection(*root, meshes, omaterials, materials); + if (1 != rootObjects.size()) { + delete root; + } + + if (!::strncmp(pScene->mRootNode->mName.data, "Node", 4)) { + pScene->mRootNode->mName.Set("<AC3DWorld>"); + } + + // copy meshes + if (meshes.empty()) { + throw DeadlyImportError("An unknown error occurred during converting"); + } + pScene->mNumMeshes = (unsigned int)meshes.size(); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + ::memcpy(pScene->mMeshes, &meshes[0], pScene->mNumMeshes * sizeof(void *)); + + // copy materials + pScene->mNumMaterials = (unsigned int)omaterials.size(); + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + ::memcpy(pScene->mMaterials, &omaterials[0], pScene->mNumMaterials * sizeof(void *)); + + // copy lights + pScene->mNumLights = (unsigned int)lights.size(); + if (lights.size()) { + pScene->mLights = new aiLight *[lights.size()]; + ::memcpy(pScene->mLights, &lights[0], lights.size() * sizeof(void *)); + } +} + +#endif //!defined ASSIMP_BUILD_NO_AC_IMPORTER diff --git a/libs/assimp/code/AssetLib/AC/ACLoader.h b/libs/assimp/code/AssetLib/AC/ACLoader.h new file mode 100644 index 0000000..aabc114 --- /dev/null +++ b/libs/assimp/code/AssetLib/AC/ACLoader.h @@ -0,0 +1,270 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 ACLoader.h + * @brief Declaration of the .ac importer class. + */ +#ifndef AI_AC3DLOADER_H_INCLUDED +#define AI_AC3DLOADER_H_INCLUDED + +#include <vector> + +#include <assimp/BaseImporter.h> +#include <assimp/types.h> + +struct aiNode; +struct aiMesh; +struct aiMaterial; +struct aiLight; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** AC3D (*.ac) importer class +*/ +class AC3DImporter : public BaseImporter { +public: + AC3DImporter(); + ~AC3DImporter() override; + + // Represents an AC3D material + struct Material { + Material() : + rgb(0.6f, 0.6f, 0.6f), + spec(1.f, 1.f, 1.f), + shin(0.f), + trans(0.f) {} + + // base color of the material + aiColor3D rgb; + + // ambient color of the material + aiColor3D amb; + + // emissive color of the material + aiColor3D emis; + + // specular color of the material + aiColor3D spec; + + // shininess exponent + float shin; + + // transparency. 0 == opaque + float trans; + + // name of the material. optional. + std::string name; + }; + + // Represents an AC3D surface + struct Surface { + Surface() : + mat(0), + flags(0) {} + + unsigned int mat, flags; + + typedef std::pair<unsigned int, aiVector2D> SurfaceEntry; + std::vector<SurfaceEntry> entries; + + // Type is low nibble of flags + enum Type : uint8_t { + Polygon = 0x0, + ClosedLine = 0x1, + OpenLine = 0x2, + TriangleStrip = 0x4, // ACC extension (TORCS and Speed Dreams) + + Mask = 0xf, + }; + + inline uint8_t GetType() const { return (flags & Mask); } + }; + + // Represents an AC3D object + struct Object { + Object() : + type(World), + name(), + children(), + texture(), + texRepeat(1.f, 1.f), + texOffset(0.0f, 0.0f), + rotation(), + translation(), + vertices(), + surfaces(), + numRefs(0), + subDiv(0), + crease() {} + + // Type description + enum Type { + World = 0x0, + Poly = 0x1, + Group = 0x2, + Light = 0x4 + } type; + + // name of the object + std::string name; + + // object children + std::vector<Object> children; + + // texture to be assigned to all surfaces of the object + std::string texture; + + // texture repat factors (scaling for all coordinates) + aiVector2D texRepeat, texOffset; + + // rotation matrix + aiMatrix3x3 rotation; + + // translation vector + aiVector3D translation; + + // vertices + std::vector<aiVector3D> vertices; + + // surfaces + std::vector<Surface> surfaces; + + // number of indices (= num verts in verbose format) + unsigned int numRefs; + + // number of subdivisions to be performed on the + // imported data + unsigned int subDiv; + + // max angle limit for smoothing + float crease; + }; + +public: + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details */ + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details*/ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list.*/ + void SetupProperties(const Importer *pImp) override; + +private: + // ------------------------------------------------------------------- + /** Get the next line from the file. + * @return false if the end of the file was reached*/ + bool GetNextLine(); + + // ------------------------------------------------------------------- + /** Load the object section. This method is called recursively to + * load subobjects, the method returns after a 'kids 0' was + * encountered. + * @objects List of output objects*/ + void LoadObjectSection(std::vector<Object> &objects); + + // ------------------------------------------------------------------- + /** Convert all objects into meshes and nodes. + * @param object Current object to work on + * @param meshes Pointer to the list of output meshes + * @param outMaterials List of output materials + * @param materials Material list + * @param Scenegraph node for the object */ + aiNode *ConvertObjectSection(Object &object, + std::vector<aiMesh *> &meshes, + std::vector<aiMaterial *> &outMaterials, + const std::vector<Material> &materials, + aiNode *parent = nullptr); + + // ------------------------------------------------------------------- + /** Convert a material + * @param object Current object + * @param matSrc Source material description + * @param matDest Destination material to be filled */ + void ConvertMaterial(const Object &object, + const Material &matSrc, + aiMaterial &matDest); + +private: + // points to the next data line + const char *buffer; + + // Configuration option: if enabled, up to two meshes + // are generated per material: those faces who have + // their bf cull flags set are separated. + bool configSplitBFCull; + + // Configuration switch: subdivision surfaces are only + // evaluated if the value is true. + bool configEvalSubdivision; + + // counts how many objects we have in the tree. + // basing on this information we can find a + // good estimate how many meshes we'll have in the final scene. + unsigned int mNumMeshes; + + // current list of light sources + std::vector<aiLight *> *mLights; + + // name counters + unsigned int mLightsCounter, mGroupsCounter, mPolysCounter, mWorldsCounter; +}; + +} // end of namespace Assimp + +#endif // AI_AC3DIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/AMF/AMFImporter.cpp b/libs/assimp/code/AssetLib/AMF/AMFImporter.cpp new file mode 100644 index 0000000..4103dcd --- /dev/null +++ b/libs/assimp/code/AssetLib/AMF/AMFImporter.cpp @@ -0,0 +1,524 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. +--------------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER + +// Header files, Assimp. +#include "AMFImporter.hpp" + +#include <assimp/DefaultIOSystem.h> +#include <assimp/fast_atof.h> +#include <assimp/StringUtils.h> + +// Header files, stdlib. +#include <memory> + +namespace Assimp { + +const aiImporterDesc AMFImporter::Description = { + "Additive manufacturing file format(AMF) Importer", + "smalcom", + "", + "See documentation in source code. Chapter: Limitations.", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, + 0, + 0, + 0, + 0, + "amf" +}; + +void AMFImporter::Clear() { + mNodeElement_Cur = nullptr; + mUnit.clear(); + mMaterial_Converted.clear(); + mTexture_Converted.clear(); + // Delete all elements + if (!mNodeElement_List.empty()) { + for (AMFNodeElementBase *ne : mNodeElement_List) { + delete ne; + } + + mNodeElement_List.clear(); + } +} + +AMFImporter::AMFImporter() AI_NO_EXCEPT : + mNodeElement_Cur(nullptr), + mXmlParser(nullptr), + mUnit(), + mVersion(), + mMaterial_Converted(), + mTexture_Converted() { + // empty +} + +AMFImporter::~AMFImporter() { + delete mXmlParser; + // Clear() is accounting if data already is deleted. So, just check again if all data is deleted. + Clear(); +} + +/*********************************************************************************************************************************************/ +/************************************************************ Functions: find set ************************************************************/ +/*********************************************************************************************************************************************/ + +bool AMFImporter::Find_NodeElement(const std::string &pID, const AMFNodeElementBase::EType pType, AMFNodeElementBase **pNodeElement) const { + for (AMFNodeElementBase *ne : mNodeElement_List) { + if ((ne->ID == pID) && (ne->Type == pType)) { + if (pNodeElement != nullptr) { + *pNodeElement = ne; + } + + return true; + } + } // for(CAMFImporter_NodeElement* ne: mNodeElement_List) + + return false; +} + +bool AMFImporter::Find_ConvertedNode(const std::string &pID, NodeArray &nodeArray, aiNode **pNode) const { + aiString node_name(pID.c_str()); + for (aiNode *node : nodeArray) { + if (node->mName == node_name) { + if (pNode != nullptr) { + *pNode = node; + } + + return true; + } + } // for(aiNode* node: pNodeList) + + return false; +} + +bool AMFImporter::Find_ConvertedMaterial(const std::string &pID, const SPP_Material **pConvertedMaterial) const { + for (const SPP_Material &mat : mMaterial_Converted) { + if (mat.ID == pID) { + if (pConvertedMaterial != nullptr) { + *pConvertedMaterial = &mat; + } + + return true; + } + } // for(const SPP_Material& mat: mMaterial_Converted) + + return false; +} + +/*********************************************************************************************************************************************/ +/************************************************************ Functions: throw set ***********************************************************/ +/*********************************************************************************************************************************************/ + +void AMFImporter::Throw_CloseNotFound(const std::string &nodeName) { + throw DeadlyImportError("Close tag for node <" + nodeName + "> not found. Seems file is corrupt."); +} + +void AMFImporter::Throw_IncorrectAttr(const std::string &nodeName, const std::string &attrName) { + throw DeadlyImportError("Node <" + nodeName + "> has incorrect attribute \"" + attrName + "\"."); +} + +void AMFImporter::Throw_IncorrectAttrValue(const std::string &nodeName, const std::string &attrName) { + throw DeadlyImportError("Attribute \"" + attrName + "\" in node <" + nodeName + "> has incorrect value."); +} + +void AMFImporter::Throw_MoreThanOnceDefined(const std::string &nodeName, const std::string &pNodeType, const std::string &pDescription) { + throw DeadlyImportError("\"" + pNodeType + "\" node can be used only once in " + nodeName + ". Description: " + pDescription); +} + +void AMFImporter::Throw_ID_NotFound(const std::string &pID) const { + throw DeadlyImportError("Not found node with name \"", pID, "\"."); +} + +/*********************************************************************************************************************************************/ +/************************************************************* Functions: XML set ************************************************************/ +/*********************************************************************************************************************************************/ + +void AMFImporter::XML_CheckNode_MustHaveChildren(pugi::xml_node &node) { + if (node.children().begin() == node.children().end()) { + throw DeadlyImportError(std::string("Node <") + node.name() + "> must have children."); + } +} + +bool AMFImporter::XML_SearchNode(const std::string &nodeName) { + return nullptr != mXmlParser->findNode(nodeName); +} + +void AMFImporter::ParseHelper_FixTruncatedFloatString(const char *pInStr, std::string &pOutString) { + size_t instr_len; + + pOutString.clear(); + instr_len = strlen(pInStr); + if (!instr_len) return; + + pOutString.reserve(instr_len * 3 / 2); + // check and correct floats in format ".x". Must be "x.y". + if (pInStr[0] == '.') pOutString.push_back('0'); + + pOutString.push_back(pInStr[0]); + for (size_t ci = 1; ci < instr_len; ci++) { + if ((pInStr[ci] == '.') && ((pInStr[ci - 1] == ' ') || (pInStr[ci - 1] == '-') || (pInStr[ci - 1] == '+') || (pInStr[ci - 1] == '\t'))) { + pOutString.push_back('0'); + pOutString.push_back('.'); + } else { + pOutString.push_back(pInStr[ci]); + } + } +} + +static bool ParseHelper_Decode_Base64_IsBase64(const char pChar) { + return (isalnum((unsigned char)pChar) || (pChar == '+') || (pChar == '/')); +} + +void AMFImporter::ParseHelper_Decode_Base64(const std::string &pInputBase64, std::vector<uint8_t> &pOutputData) const { + // With help from + // René Nyffenegger http://www.adp-gmbh.ch/cpp/common/base64.html + const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + uint8_t tidx = 0; + uint8_t arr4[4], arr3[3]; + + // check input data + if (pInputBase64.size() % 4) throw DeadlyImportError("Base64-encoded data must have size multiply of four."); + // prepare output place + pOutputData.clear(); + pOutputData.reserve(pInputBase64.size() / 4 * 3); + + for (size_t in_len = pInputBase64.size(), in_idx = 0; (in_len > 0) && (pInputBase64[in_idx] != '='); in_len--) { + if (ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx])) { + arr4[tidx++] = pInputBase64[in_idx++]; + if (tidx == 4) { + for (tidx = 0; tidx < 4; tidx++) + arr4[tidx] = (uint8_t)base64_chars.find(arr4[tidx]); + + arr3[0] = (arr4[0] << 2) + ((arr4[1] & 0x30) >> 4); + arr3[1] = ((arr4[1] & 0x0F) << 4) + ((arr4[2] & 0x3C) >> 2); + arr3[2] = ((arr4[2] & 0x03) << 6) + arr4[3]; + for (tidx = 0; tidx < 3; tidx++) + pOutputData.push_back(arr3[tidx]); + + tidx = 0; + } // if(tidx == 4) + } // if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx])) + else { + in_idx++; + } // if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx])) else + } + + if (tidx) { + for (uint8_t i = tidx; i < 4; i++) + arr4[i] = 0; + for (uint8_t i = 0; i < 4; i++) + arr4[i] = (uint8_t)(base64_chars.find(arr4[i])); + + arr3[0] = (arr4[0] << 2) + ((arr4[1] & 0x30) >> 4); + arr3[1] = ((arr4[1] & 0x0F) << 4) + ((arr4[2] & 0x3C) >> 2); + arr3[2] = ((arr4[2] & 0x03) << 6) + arr4[3]; + for (uint8_t i = 0; i < (tidx - 1); i++) + pOutputData.push_back(arr3[i]); + } +} + +void AMFImporter::ParseFile(const std::string &pFile, IOSystem *pIOHandler) { + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb")); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open AMF file ", pFile, "."); + } + + mXmlParser = new XmlParser(); + if (!mXmlParser->parse(file.get())) { + delete mXmlParser; + mXmlParser = nullptr; + throw DeadlyImportError("Failed to create XML reader for file ", pFile, "."); + } + + // Start reading, search for root tag <amf> + if (!mXmlParser->hasNode("amf")) { + throw DeadlyImportError("Root node \"amf\" not found."); + } + ParseNode_Root(); +} // namespace Assimp + +void AMFImporter::ParseHelper_Node_Enter(AMFNodeElementBase *node) { + mNodeElement_Cur->Child.push_back(node); // add new element to current element child list. + mNodeElement_Cur = node; +} + +void AMFImporter::ParseHelper_Node_Exit() { + if (mNodeElement_Cur != nullptr) mNodeElement_Cur = mNodeElement_Cur->Parent; +} + +// <amf +// unit="" - The units to be used. May be "inch", "millimeter", "meter", "feet", or "micron". +// version="" - Version of file format. +// > +// </amf> +// Root XML element. +// Multi elements - No. +void AMFImporter::ParseNode_Root() { + AMFNodeElementBase *ne = nullptr; + XmlNode *root = mXmlParser->findNode("amf"); + if (nullptr == root) { + throw DeadlyImportError("Root node \"amf\" not found."); + } + XmlNode node = *root; + mUnit = ai_tolower(std::string(node.attribute("unit").as_string())); + + mVersion = node.attribute("version").as_string(); + + // Read attributes for node <amf>. + // Check attributes + if (!mUnit.empty()) { + if ((mUnit != "inch") && (mUnit != "millimeters") && (mUnit != "millimeter") && (mUnit != "meter") && (mUnit != "feet") && (mUnit != "micron")) { + Throw_IncorrectAttrValue("unit", mUnit); + } + } + + // create root node element. + ne = new AMFRoot(nullptr); + + mNodeElement_Cur = ne; // set first "current" element + // and assign attribute's values + ((AMFRoot *)ne)->Unit = mUnit; + ((AMFRoot *)ne)->Version = mVersion; + + // Check for child nodes + for (XmlNode ¤tNode : node.children() ) { + const std::string currentName = currentNode.name(); + if (currentName == "object") { + ParseNode_Object(currentNode); + } else if (currentName == "material") { + ParseNode_Material(currentNode); + } else if (currentName == "texture") { + ParseNode_Texture(currentNode); + } else if (currentName == "constellation") { + ParseNode_Constellation(currentNode); + } else if (currentName == "metadata") { + ParseNode_Metadata(currentNode); + } + mNodeElement_Cur = ne; + } + mNodeElement_Cur = ne; // force restore "current" element + mNodeElement_List.push_back(ne); // add to node element list because its a new object in graph. +} + +// <constellation +// id="" - The Object ID of the new constellation being defined. +// > +// </constellation> +// A collection of objects or constellations with specific relative locations. +// Multi elements - Yes. +// Parent element - <amf>. +void AMFImporter::ParseNode_Constellation(XmlNode &node) { + std::string id; + id = node.attribute("id").as_string(); + + // create and if needed - define new grouping object. + AMFNodeElementBase *ne = new AMFConstellation(mNodeElement_Cur); + + AMFConstellation &als = *((AMFConstellation *)ne); // alias for convenience + + if (!id.empty()) { + als.ID = id; + } + + // Check for child nodes + if (!node.empty()) { + ParseHelper_Node_Enter(ne); + for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) { + std::string name = currentNode.name(); + if (name == "instance") { + ParseNode_Instance(currentNode); + } else if (name == "metadata") { + ParseNode_Metadata(currentNode); + } + } + ParseHelper_Node_Exit(); + } else { + mNodeElement_Cur->Child.push_back(ne); + } + mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph. +} + +// <instance +// objectid="" - The Object ID of the new constellation being defined. +// > +// </instance> +// A collection of objects or constellations with specific relative locations. +// Multi elements - Yes. +// Parent element - <amf>. +void AMFImporter::ParseNode_Instance(XmlNode &node) { + AMFNodeElementBase *ne(nullptr); + + // Read attributes for node <constellation>. + std::string objectid = node.attribute("objectid").as_string(); + + // used object id must be defined, check that. + if (objectid.empty()) { + throw DeadlyImportError("\"objectid\" in <instance> must be defined."); + } + // create and define new grouping object. + ne = new AMFInstance(mNodeElement_Cur); + AMFInstance &als = *((AMFInstance *)ne); + als.ObjectID = objectid; + + if (!node.empty()) { + ParseHelper_Node_Enter(ne); + for (auto ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "deltax") { + XmlParser::getValueAsFloat(currentNode, als.Delta.x); + } else if (currentName == "deltay") { + XmlParser::getValueAsFloat(currentNode, als.Delta.y); + } else if (currentName == "deltaz") { + XmlParser::getValueAsFloat(currentNode, als.Delta.z); + } else if (currentName == "rx") { + XmlParser::getValueAsFloat(currentNode, als.Delta.x); + } else if (currentName == "ry") { + XmlParser::getValueAsFloat(currentNode, als.Delta.y); + } else if (currentName == "rz") { + XmlParser::getValueAsFloat(currentNode, als.Delta.z); + } + } + ParseHelper_Node_Exit(); + } else { + mNodeElement_Cur->Child.push_back(ne); + } + + mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph. +} + +// <object +// id="" - A unique ObjectID for the new object being defined. +// > +// </object> +// An object definition. +// Multi elements - Yes. +// Parent element - <amf>. +void AMFImporter::ParseNode_Object(XmlNode &node) { + AMFNodeElementBase *ne = nullptr; + + // Read attributes for node <object>. + std::string id = node.attribute("id").as_string(); + + // create and if needed - define new geometry object. + ne = new AMFObject(mNodeElement_Cur); + + AMFObject &als = *((AMFObject *)ne); // alias for convenience + + if (!id.empty()) { + als.ID = id; + } + + // Check for child nodes + if (!node.empty()) { + ParseHelper_Node_Enter(ne); + for (auto ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "color") { + ParseNode_Color(currentNode); + } else if (currentName == "mesh") { + ParseNode_Mesh(currentNode); + } else if (currentName == "metadata") { + ParseNode_Metadata(currentNode); + } + } + ParseHelper_Node_Exit(); + } else { + mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element + } + + mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph. +} + +// <metadata +// type="" - The type of the attribute. +// > +// </metadata> +// Specify additional information about an entity. +// Multi elements - Yes. +// Parent element - <amf>, <object>, <volume>, <material>, <vertex>. +// +// Reserved types are: +// "Name" - The alphanumeric label of the entity, to be used by the interpreter if interacting with the user. +// "Description" - A description of the content of the entity +// "URL" - A link to an external resource relating to the entity +// "Author" - Specifies the name(s) of the author(s) of the entity +// "Company" - Specifying the company generating the entity +// "CAD" - specifies the name of the originating CAD software and version +// "Revision" - specifies the revision of the entity +// "Tolerance" - specifies the desired manufacturing tolerance of the entity in entity's unit system +// "Volume" - specifies the total volume of the entity, in the entity's unit system, to be used for verification (object and volume only) +void AMFImporter::ParseNode_Metadata(XmlNode &node) { + AMFNodeElementBase *ne = nullptr; + + std::string type = node.attribute("type").as_string(), value; + XmlParser::getValueAsString(node, value); + + // read attribute + ne = new AMFMetadata(mNodeElement_Cur); + ((AMFMetadata *)ne)->Type = type; + ((AMFMetadata *)ne)->Value = value; + mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element + mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph. +} + +bool AMFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*pCheckSig*/) const { + static const char *tokens[] = { "<amf" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +const aiImporterDesc *AMFImporter::GetInfo() const { + return &Description; +} + +void AMFImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + Clear(); // delete old graph. + ParseFile(pFile, pIOHandler); + Postprocess_BuildScene(pScene); + // scene graph is ready, exit. +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER diff --git a/libs/assimp/code/AssetLib/AMF/AMFImporter.hpp b/libs/assimp/code/AssetLib/AMF/AMFImporter.hpp new file mode 100644 index 0000000..601eae4 --- /dev/null +++ b/libs/assimp/code/AssetLib/AMF/AMFImporter.hpp @@ -0,0 +1,310 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AMFImporter.hpp +/// \brief AMF-format files importer for Assimp. +/// \date 2016 +/// \author smal.root@gmail.com +// Thanks to acorn89 for support. + +#pragma once +#ifndef INCLUDED_AI_AMF_IMPORTER_H +#define INCLUDED_AI_AMF_IMPORTER_H + +#include "AMFImporter_Node.hpp" + +// Header files, Assimp. +#include "assimp/types.h" +#include <assimp/BaseImporter.h> +#include <assimp/XmlParser.h> +#include <assimp/importerdesc.h> +#include <assimp/DefaultLogger.hpp> + +// Header files, stdlib. +#include <set> + +namespace Assimp { + +/// \class AMFImporter +/// Class that holding scene graph which include: geometry, metadata, materials etc. +/// +/// Implementing features. +/// +/// Limitations. +/// +/// 1. When for texture mapping used set of source textures (r, g, b, a) not only one then attribute "tiled" for all set will be true if it true in any of +/// source textures. +/// Example. Triangle use for texture mapping three textures. Two of them has "tiled" set to false and one - set to true. In scene all three textures +/// will be tiled. +/// +/// Unsupported features: +/// 1. Node <composite>, formulas in <composite> and <color>. For implementing this feature can be used expression parser "muParser" like in project +/// "amf_tools". +/// 2. Attribute "profile" in node <color>. +/// 3. Curved geometry: <edge>, <normal> and children nodes of them. +/// 4. Attributes: "unit" and "version" in <amf> read but do nothing. +/// 5. <metadata> stored only for root node <amf>. +/// 6. Color averaging of vertices for which <triangle>'s set different colors. +/// +/// Supported nodes: +/// General: +/// <amf>; <constellation>; <instance> and children <deltax>, <deltay>, <deltaz>, <rx>, <ry>, <rz>; <metadata>; +/// +/// Geometry: +/// <object>; <mesh>; <vertices>; <vertex>; <coordinates> and children <x>, <y>, <z>; <volume>; <triangle> and children <v1>, <v2>, <v3>; +/// +/// Material: +/// <color> and children <r>, <g>, <b>, <a>; <texture>; <material>; +/// two variants of texture coordinates: +/// new - <texmap> and children <utex1>, <utex2>, <utex3>, <vtex1>, <vtex2>, <vtex3> +/// old - <map> and children <u1>, <u2>, <u3>, <v1>, <v2>, <v3> +/// +class AMFImporter : public BaseImporter { +private: + struct SPP_Material; // forward declaration + + /// Data type for post-processing step. More suitable container for part of material's composition. + struct SPP_Composite { + SPP_Material *Material; ///< Pointer to material - part of composition. + std::string Formula; ///< Formula for calculating ratio of \ref Material. + }; + + /// \struct SPP_Material + /// Data type for post-processing step. More suitable container for material. + struct SPP_Material { + std::string ID; ///< Material ID. + std::list<AMFMetadata *> Metadata; ///< Metadata of material. + AMFColor *Color; ///< Color of material. + std::list<SPP_Composite> Composition; ///< List of child materials if current material is composition of few another. + + /// Return color calculated for specified coordinate. + /// \param [in] pX - "x" coordinate. + /// \param [in] pY - "y" coordinate. + /// \param [in] pZ - "z" coordinate. + /// \return calculated color. + aiColor4D GetColor(const float pX, const float pY, const float pZ) const; + }; + + /// Data type for post-processing step. More suitable container for texture. + struct SPP_Texture { + std::string ID; + size_t Width, Height, Depth; + bool Tiled; + char FormatHint[9]; // 8 for string + 1 for terminator. + uint8_t *Data; + }; + + /// Data type for post-processing step. Contain face data. + struct SComplexFace { + aiFace Face; ///< Face vertices. + const AMFColor *Color; ///< Face color. Equal to nullptr if color is not set for the face. + const AMFTexMap *TexMap; ///< Face texture mapping data. Equal to nullptr if texture mapping is not set for the face. + }; + + using AMFMetaDataArray = std::vector<AMFMetadata*>; + using MeshArray = std::vector<aiMesh*>; + using NodeArray = std::vector<aiNode*>; + + /// Clear all temporary data. + void Clear(); + + /// Get data stored in <vertices> and place it to arrays. + /// \param [in] pNodeElement - reference to node element which kept <object> data. + /// \param [in] pVertexCoordinateArray - reference to vertices coordinates kept in <vertices>. + /// \param [in] pVertexColorArray - reference to vertices colors for all <vertex's. If color for vertex is not set then corresponding member of array + /// contain nullptr. + void PostprocessHelper_CreateMeshDataArray(const AMFMesh &pNodeElement, std::vector<aiVector3D> &pVertexCoordinateArray, + std::vector<AMFColor *> &pVertexColorArray) const; + + /// Return converted texture ID which related to specified source textures ID's. If converted texture does not exist then it will be created and ID on new + /// converted texture will be returned. Conversion: set of textures from \ref CAMFImporter_NodeElement_Texture to one \ref SPP_Texture and place it + /// to converted textures list. + /// Any of source ID's can be absent(empty string) or even one ID only specified. But at least one ID must be specified. + /// \param [in] pID_R - ID of source "red" texture. + /// \param [in] pID_G - ID of source "green" texture. + /// \param [in] pID_B - ID of source "blue" texture. + /// \param [in] pID_A - ID of source "alpha" texture. + /// \return index of the texture in array of the converted textures. + size_t PostprocessHelper_GetTextureID_Or_Create(const std::string &pID_R, const std::string &pID_G, const std::string &pID_B, const std::string &pID_A); + + /// Separate input list by texture IDs. This step is needed because aiMesh can contain mesh which is use only one texture (or set: diffuse, bump etc). + /// \param [in] pInputList - input list with faces. Some of them can contain color or texture mapping, or both of them, or nothing. Will be cleared after + /// processing. + /// \param [out] pOutputList_Separated - output list of the faces lists. Separated faces list by used texture IDs. Will be cleared before processing. + void PostprocessHelper_SplitFacesByTextureID(std::list<SComplexFace> &pInputList, std::list<std::list<SComplexFace>> &pOutputList_Separated); + + /// Check if child elements of node element is metadata and add it to scene node. + /// \param [in] pMetadataList - reference to list with collected metadata. + /// \param [out] pSceneNode - scene node in which metadata will be added. + void Postprocess_AddMetadata(const AMFMetaDataArray &pMetadataList, aiNode &pSceneNode) const; + + /// To create aiMesh and aiNode for it from <object>. + /// \param [in] pNodeElement - reference to node element which kept <object> data. + /// \param [out] meshList - reference to a list with all aiMesh of the scene. + /// \param [out] pSceneNode - pointer to place where new aiNode will be created. + void Postprocess_BuildNodeAndObject(const AMFObject &pNodeElement, MeshArray &meshList, aiNode **pSceneNode); + + /// Create mesh for every <volume> in <mesh>. + /// \param [in] pNodeElement - reference to node element which kept <mesh> data. + /// \param [in] pVertexCoordinateArray - reference to vertices coordinates for all <volume>'s. + /// \param [in] pVertexColorArray - reference to vertices colors for all <volume>'s. If color for vertex is not set then corresponding member of array + /// contain nullptr. + /// \param [in] pObjectColor - pointer to colors for <object>. If color is not set then argument contain nullptr. + /// \param [in] pMaterialList - reference to a list with defined materials. + /// \param [out] pMeshList - reference to a list with all aiMesh of the scene. + /// \param [out] pSceneNode - reference to aiNode which will own new aiMesh's. + void Postprocess_BuildMeshSet(const AMFMesh &pNodeElement, const std::vector<aiVector3D> &pVertexCoordinateArray, + const std::vector<AMFColor *> &pVertexColorArray, const AMFColor *pObjectColor, + MeshArray &pMeshList, aiNode &pSceneNode); + + /// Convert material from \ref CAMFImporter_NodeElement_Material to \ref SPP_Material. + /// \param [in] pMaterial - source CAMFImporter_NodeElement_Material. + void Postprocess_BuildMaterial(const AMFMaterial &pMaterial); + + /// Create and add to aiNode's list new part of scene graph defined by <constellation>. + /// \param [in] pConstellation - reference to <constellation> node. + /// \param [out] nodeArray - reference to aiNode's list. + void Postprocess_BuildConstellation(AMFConstellation &pConstellation, NodeArray &nodeArray) const; + + /// Build Assimp scene graph in aiScene from collected data. + /// \param [out] pScene - pointer to aiScene where tree will be built. + void Postprocess_BuildScene(aiScene *pScene); + + /// Decode Base64-encoded data. + /// \param [in] pInputBase64 - reference to input Base64-encoded string. + /// \param [out] pOutputData - reference to output array for decoded data. + void ParseHelper_Decode_Base64(const std::string &pInputBase64, std::vector<uint8_t> &pOutputData) const; + + /// Parse <AMF> node of the file. + void ParseNode_Root(); + + /// Parse <constellation> node of the file. + void ParseNode_Constellation(XmlNode &node); + + /// Parse <instance> node of the file. + void ParseNode_Instance(XmlNode &node); + + /// Parse <material> node of the file. + void ParseNode_Material(XmlNode &node); + + /// Parse <metadata> node. + void ParseNode_Metadata(XmlNode &node); + + /// Parse <object> node of the file. + void ParseNode_Object(XmlNode &node); + + /// Parse <texture> node of the file. + void ParseNode_Texture(XmlNode &node); + + /// Parse <coordinates> node of the file. + void ParseNode_Coordinates(XmlNode &node); + + /// Parse <edge> node of the file. + void ParseNode_Edge(XmlNode &node); + + /// Parse <mesh> node of the file. + void ParseNode_Mesh(XmlNode &node); + + /// Parse <triangle> node of the file. + void ParseNode_Triangle(XmlNode &node); + + /// Parse <vertex> node of the file. + void ParseNode_Vertex(XmlNode &node); + + /// Parse <vertices> node of the file. + void ParseNode_Vertices(XmlNode &node); + + /// Parse <volume> node of the file. + void ParseNode_Volume(XmlNode &node); + + /// Parse <color> node of the file. + void ParseNode_Color(XmlNode &node); + + /// Parse <texmap> of <map> node of the file. + /// \param [in] pUseOldName - if true then use old name of node(and children) - <map>, instead of new name - <texmap>. + void ParseNode_TexMap(XmlNode &node, const bool pUseOldName = false); + +public: + /// Default constructor. + AMFImporter() AI_NO_EXCEPT; + + /// Default destructor. + ~AMFImporter() override; + + /// Parse AMF file and fill scene graph. The function has no return value. Result can be found by analyzing the generated graph. + /// Also exception can be thrown if trouble will found. + /// \param [in] pFile - name of file to be parsed. + /// \param [in] pIOHandler - pointer to IO helper object. + void ParseFile(const std::string &pFile, IOSystem *pIOHandler); + void ParseHelper_Node_Enter(AMFNodeElementBase *child); + void ParseHelper_Node_Exit(); + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool pCheckSig) const override; + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + const aiImporterDesc *GetInfo() const override; + bool Find_NodeElement(const std::string &pID, const AMFNodeElementBase::EType pType, AMFNodeElementBase **pNodeElement) const; + bool Find_ConvertedNode(const std::string &pID, NodeArray &nodeArray, aiNode **pNode) const; + bool Find_ConvertedMaterial(const std::string &pID, const SPP_Material **pConvertedMaterial) const; + void Throw_CloseNotFound(const std::string &nodeName); + void Throw_IncorrectAttr(const std::string &nodeName, const std::string &pAttrName); + void Throw_IncorrectAttrValue(const std::string &nodeName, const std::string &pAttrName); + void Throw_MoreThanOnceDefined(const std::string &nodeName, const std::string &pNodeType, const std::string &pDescription); + void Throw_ID_NotFound(const std::string &pID) const; + void XML_CheckNode_MustHaveChildren(pugi::xml_node &node); + bool XML_SearchNode(const std::string &nodeName); + void ParseHelper_FixTruncatedFloatString(const char *pInStr, std::string &pOutString); + AMFImporter(const AMFImporter &pScene) = delete; + AMFImporter &operator=(const AMFImporter &pScene) = delete; + +private: + static const aiImporterDesc Description; + + AMFNodeElementBase *mNodeElement_Cur; ///< Current element. + std::list<AMFNodeElementBase *> mNodeElement_List; ///< All elements of scene graph. + XmlParser *mXmlParser; + std::string mUnit; + std::string mVersion; + std::list<SPP_Material> mMaterial_Converted; ///< List of converted materials for postprocessing step. + std::list<SPP_Texture> mTexture_Converted; ///< List of converted textures for postprocessing step. +}; + +} // namespace Assimp + +#endif // INCLUDED_AI_AMF_IMPORTER_H diff --git a/libs/assimp/code/AssetLib/AMF/AMFImporter_Geometry.cpp b/libs/assimp/code/AssetLib/AMF/AMFImporter_Geometry.cpp new file mode 100644 index 0000000..341999f --- /dev/null +++ b/libs/assimp/code/AssetLib/AMF/AMFImporter_Geometry.cpp @@ -0,0 +1,284 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. +--------------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER + +#include "AMFImporter.hpp" +#include <assimp/ParsingUtils.h> + +namespace Assimp { + +// <mesh> +// </mesh> +// A 3D mesh hull. +// Multi elements - Yes. +// Parent element - <object>. +void AMFImporter::ParseNode_Mesh(XmlNode &node) { + AMFNodeElementBase *ne = nullptr; + + // Check for child nodes + if (0 != ASSIMP_stricmp(node.name(), "mesh")) { + return; + } + // create new mesh object. + ne = new AMFMesh(mNodeElement_Cur); + bool found_verts = false, found_volumes = false; + if (!node.empty()) { + ParseHelper_Node_Enter(ne); + pugi::xml_node vertNode = node.child("vertices"); + if (!vertNode.empty()) { + ParseNode_Vertices(vertNode); + found_verts = true; + } + + pugi::xml_node volumeNode = node.child("volume"); + if (!volumeNode.empty()) { + ParseNode_Volume(volumeNode); + found_volumes = true; + } + ParseHelper_Node_Exit(); + } + + if (!found_verts && !found_volumes) { + mNodeElement_Cur->Child.push_back(ne); + } // if(!mReader->isEmptyElement()) else + + // and to node element list because its a new object in graph. + mNodeElement_List.push_back(ne); +} + +// <vertices> +// </vertices> +// The list of vertices to be used in defining triangles. +// Multi elements - No. +// Parent element - <mesh>. +void AMFImporter::ParseNode_Vertices(XmlNode &node) { + AMFNodeElementBase *ne = nullptr; + + // create new mesh object. + ne = new AMFVertices(mNodeElement_Cur); + // Check for child nodes + if (node.empty()) { + mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element + return; + } + ParseHelper_Node_Enter(ne); + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "vertex") { + ParseNode_Vertex(currentNode); + } + } + ParseHelper_Node_Exit(); + mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph. +} + +// <vertex> +// </vertex> +// A vertex to be referenced in triangles. +// Multi elements - Yes. +// Parent element - <vertices>. +void AMFImporter::ParseNode_Vertex(XmlNode &node) { + AMFNodeElementBase *ne = nullptr; + + // create new mesh object. + ne = new AMFVertex(mNodeElement_Cur); + + // Check for child nodes + pugi::xml_node colorNode = node.child("color"); + bool col_read = false; + bool coord_read = false; + if (!node.empty()) { + ParseHelper_Node_Enter(ne); + if (!colorNode.empty()) { + ParseNode_Color(colorNode); + col_read = true; + } + pugi::xml_node coordNode = node.child("coordinates"); + if (!coordNode.empty()) { + ParseNode_Coordinates(coordNode); + coord_read = true; + } + ParseHelper_Node_Exit(); + } + + if (!coord_read && !col_read) { + mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element + } + + mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph. +} + +// <coordinates> +// </coordinates> +// Specifies the 3D location of this vertex. +// Multi elements - No. +// Parent element - <vertex>. +// +// Children elements: +// <x>, <y>, <z> +// Multi elements - No. +// X, Y, or Z coordinate, respectively, of a vertex position in space. +void AMFImporter::ParseNode_Coordinates(XmlNode &node) { + AMFNodeElementBase *ne = nullptr; + if (!node.empty()) { + ne = new AMFCoordinates(mNodeElement_Cur); + ParseHelper_Node_Enter(ne); + for (XmlNode ¤tNode : node.children()) { + // create new color object. + AMFCoordinates &als = *((AMFCoordinates *)ne); // alias for convenience + const std::string ¤tName = ai_tolower(currentNode.name()); + if (currentName == "x") { + XmlParser::getValueAsFloat(currentNode, als.Coordinate.x); + } else if (currentName == "y") { + XmlParser::getValueAsFloat(currentNode, als.Coordinate.y); + } else if (currentName == "z") { + XmlParser::getValueAsFloat(currentNode, als.Coordinate.z); + } + } + ParseHelper_Node_Exit(); + + } else { + mNodeElement_Cur->Child.push_back(new AMFCoordinates(mNodeElement_Cur)); + } + + mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph. +} + +// <volume +// materialid="" - Which material to use. +// type="" - What this volume describes can be "region" or "support". If none specified, "object" is assumed. If support, then the geometric +// requirements 1-8 listed in section 5 do not need to be maintained. +// > +// </volume> +// Defines a volume from the established vertex list. +// Multi elements - Yes. +// Parent element - <mesh>. +void AMFImporter::ParseNode_Volume(XmlNode &node) { + std::string materialid; + std::string type; + AMFNodeElementBase *ne = new AMFVolume(mNodeElement_Cur); + + // Read attributes for node <color>. + // and assign read data + + ((AMFVolume *)ne)->MaterialID = node.attribute("materialid").as_string(); + + ((AMFVolume *)ne)->Type = type; + // Check for child nodes + bool col_read = false; + if (!node.empty()) { + ParseHelper_Node_Enter(ne); + for (auto ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == "color") { + if (col_read) Throw_MoreThanOnceDefined(currentName, "color", "Only one color can be defined for <volume>."); + ParseNode_Color(currentNode); + col_read = true; + } else if (currentName == "triangle") { + ParseNode_Triangle(currentNode); + } else if (currentName == "metadata") { + ParseNode_Metadata(currentNode); + } else if (currentName == "volume") { + ParseNode_Metadata(currentNode); + } + } + ParseHelper_Node_Exit(); + } else { + mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element + } + + mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph. +} + +// <triangle> +// </triangle> +// Defines a 3D triangle from three vertices, according to the right-hand rule (counter-clockwise when looking from the outside). +// Multi elements - Yes. +// Parent element - <volume>. +// +// Children elements: +// <v1>, <v2>, <v3> +// Multi elements - No. +// Index of the desired vertices in a triangle or edge. +void AMFImporter::ParseNode_Triangle(XmlNode &node) { + AMFNodeElementBase *ne = new AMFTriangle(mNodeElement_Cur); + + // create new triangle object. + + AMFTriangle &als = *((AMFTriangle *)ne); // alias for convenience + + bool col_read = false; + if (!node.empty()) { + ParseHelper_Node_Enter(ne); + std::string v; + for (auto ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == "color") { + if (col_read) Throw_MoreThanOnceDefined(currentName, "color", "Only one color can be defined for <triangle>."); + ParseNode_Color(currentNode); + col_read = true; + } else if (currentName == "texmap") { + ParseNode_TexMap(currentNode); + } else if (currentName == "map") { + ParseNode_TexMap(currentNode, true); + } else if (currentName == "v1") { + XmlParser::getValueAsString(currentNode, v); + als.V[0] = std::atoi(v.c_str()); + } else if (currentName == "v2") { + XmlParser::getValueAsString(currentNode, v); + als.V[1] = std::atoi(v.c_str()); + } else if (currentName == "v3") { + XmlParser::getValueAsString(currentNode, v); + als.V[2] = std::atoi(v.c_str()); + } + } + ParseHelper_Node_Exit(); + } else { + mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element + } + + mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph. +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER diff --git a/libs/assimp/code/AssetLib/AMF/AMFImporter_Material.cpp b/libs/assimp/code/AssetLib/AMF/AMFImporter_Material.cpp new file mode 100644 index 0000000..676e748 --- /dev/null +++ b/libs/assimp/code/AssetLib/AMF/AMFImporter_Material.cpp @@ -0,0 +1,327 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AMFImporter_Material.cpp +/// \brief Parsing data from material nodes. +/// \date 2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER + +#include "AMFImporter.hpp" + +namespace Assimp { + +// <color +// profile="" - The ICC color space used to interpret the three color channels <r>, <g> and <b>. +// > +// </color> +// A color definition. +// Multi elements - No. +// Parent element - <material>, <object>, <volume>, <vertex>, <triangle>. +// +// "profile" can be one of "sRGB", "AdobeRGB", "Wide-Gamut-RGB", "CIERGB", "CIELAB", or "CIEXYZ". +// Children elements: +// <r>, <g>, <b>, <a> +// Multi elements - No. +// Red, Greed, Blue and Alpha (transparency) component of a color in sRGB space, values ranging from 0 to 1. The +// values can be specified as constants, or as a formula depending on the coordinates. +void AMFImporter::ParseNode_Color(XmlNode &node) { + if (node.empty()) { + return; + } + + const std::string &profile = node.attribute("profile").as_string(); + bool read_flag[4] = { false, false, false, false }; + AMFNodeElementBase *ne = new AMFColor(mNodeElement_Cur); + AMFColor &als = *((AMFColor *)ne); // alias for convenience + ParseHelper_Node_Enter(ne); + for (pugi::xml_node &child : node.children()) { + // create new color object. + als.Profile = profile; + + const std::string &name = child.name(); + if ( name == "r") { + read_flag[0] = true; + XmlParser::getValueAsFloat(child, als.Color.r); + } else if (name == "g") { + read_flag[1] = true; + XmlParser::getValueAsFloat(child, als.Color.g); + } else if (name == "b") { + read_flag[2] = true; + XmlParser::getValueAsFloat(child, als.Color.b); + } else if (name == "a") { + read_flag[3] = true; + XmlParser::getValueAsFloat(child, als.Color.a); + } + // check if <a> is absent. Then manually add "a == 1". + if (!read_flag[3]) { + als.Color.a = 1; + } + } + als.Composed = false; + mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph. + ParseHelper_Node_Exit(); + // check that all components was defined + if (!(read_flag[0] && read_flag[1] && read_flag[2])) { + throw DeadlyImportError("Not all color components are defined."); + } +} + +// <material +// id="" - A unique material id. material ID "0" is reserved to denote no material (void) or sacrificial material. +// > +// </material> +// An available material. +// Multi elements - Yes. +// Parent element - <amf>. +void AMFImporter::ParseNode_Material(XmlNode &node) { + // create new object and assign read data + std::string id = node.attribute("id").as_string(); + AMFNodeElementBase *ne = new AMFMaterial(mNodeElement_Cur); + ((AMFMaterial*)ne)->ID = id; + + // Check for child nodes + if (!node.empty()) { + ParseHelper_Node_Enter(ne); + for (pugi::xml_node &child : node.children()) { + const std::string name = child.name(); + if (name == "color") { + ParseNode_Color(child); + } else if (name == "metadata") { + ParseNode_Metadata(child); + } + } + ParseHelper_Node_Exit(); + } else { + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + } + + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +// <texture +// id="" - Assigns a unique texture id for the new texture. +// width="" - Width (horizontal size, x) of the texture, in pixels. +// height="" - Height (lateral size, y) of the texture, in pixels. +// depth="" - Depth (vertical size, z) of the texture, in pixels. +// type="" - Encoding of the data in the texture. Currently allowed values are "grayscale" only. In grayscale mode, each pixel is represented by one byte +// in the range of 0-255. When the texture is referenced using the tex function, these values are converted into a single floating point number in the +// range of 0-1 (see Annex 2). A full color graphics will typically require three textures, one for each of the color channels. A graphic involving +// transparency may require a fourth channel. +// tiled="" - If true then texture repeated when UV-coordinates is greater than 1. +// > +// </triangle> +// Specifies an texture data to be used as a map. Lists a sequence of Base64 values specifying values for pixels from left to right then top to bottom, +// then layer by layer. +// Multi elements - Yes. +// Parent element - <amf>. +void AMFImporter::ParseNode_Texture(XmlNode &node) { + const std::string id = node.attribute("id").as_string(); + const uint32_t width = node.attribute("width").as_uint(); + const uint32_t height = node.attribute("height").as_uint(); + uint32_t depth = node.attribute("depth").as_uint(); + const std::string type = node.attribute("type").as_string(); + bool tiled = node.attribute("tiled").as_bool(); + + if (node.empty()) { + return; + } + + // create new texture object. + AMFNodeElementBase *ne = new AMFTexture(mNodeElement_Cur); + + AMFTexture& als = *((AMFTexture*)ne);// alias for convenience + + std::string enc64_data; + XmlParser::getValueAsString(node, enc64_data); + // Check for child nodes + + // check that all components was defined + if (id.empty()) { + throw DeadlyImportError("ID for texture must be defined."); + } + if (width < 1) { + throw DeadlyImportError("Invalid width for texture."); + } + if (height < 1) { + throw DeadlyImportError("Invalid height for texture."); + } + if (type != "grayscale") { + throw DeadlyImportError("Invalid type for texture."); + } + if (enc64_data.empty()) { + throw DeadlyImportError("Texture data not defined."); + } + // copy data + als.ID = id; + als.Width = width; + als.Height = height; + als.Depth = depth; + als.Tiled = tiled; + ParseHelper_Decode_Base64(enc64_data, als.Data); + if (depth == 0) { + depth = (uint32_t)(als.Data.size() / (width * height)); + } + // check data size + if ((width * height * depth) != als.Data.size()) { + throw DeadlyImportError("Texture has incorrect data size."); + } + + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +// <texmap +// rtexid="" - Texture ID for red color component. +// gtexid="" - Texture ID for green color component. +// btexid="" - Texture ID for blue color component. +// atexid="" - Texture ID for alpha color component. Optional. +// > +// </texmap>, old name: <map> +// Specifies texture coordinates for triangle. +// Multi elements - No. +// Parent element - <triangle>. +// Children elements: +// <utex1>, <utex2>, <utex3>, <vtex1>, <vtex2>, <vtex3>. Old name: <u1>, <u2>, <u3>, <v1>, <v2>, <v3>. +// Multi elements - No. +// Texture coordinates for every vertex of triangle. +void AMFImporter::ParseNode_TexMap(XmlNode &node, const bool pUseOldName) { + // Read attributes for node <color>. + AMFNodeElementBase *ne = new AMFTexMap(mNodeElement_Cur); + AMFTexMap &als = *((AMFTexMap *)ne); // + std::string rtexid, gtexid, btexid, atexid; + if (!node.empty()) { + for (pugi::xml_attribute &attr : node.attributes()) { + const std::string ¤tAttr = attr.name(); + if (currentAttr == "rtexid") { + rtexid = attr.as_string(); + } else if (currentAttr == "gtexid") { + gtexid = attr.as_string(); + } else if (currentAttr == "btexid") { + btexid = attr.as_string(); + } else if (currentAttr == "atexid") { + atexid = attr.as_string(); + } + } + } + + // create new texture coordinates object, alias for convenience + // check data + if (rtexid.empty() && gtexid.empty() && btexid.empty()) { + throw DeadlyImportError("ParseNode_TexMap. At least one texture ID must be defined."); + } + + // Check for children nodes + if (node.children().begin() == node.children().end()) { + throw DeadlyImportError("Invalid children definition."); + } + // read children nodes + bool read_flag[6] = { false, false, false, false, false, false }; + + if (!pUseOldName) { + ParseHelper_Node_Enter(ne); + for ( XmlNode ¤tNode : node.children()) { + const std::string &name = currentNode.name(); + if (name == "utex1") { + read_flag[0] = true; + XmlParser::getValueAsFloat(node, als.TextureCoordinate[0].x); + } else if (name == "utex2") { + read_flag[1] = true; + XmlParser::getValueAsFloat(node, als.TextureCoordinate[1].x); + } else if (name == "utex3") { + read_flag[2] = true; + XmlParser::getValueAsFloat(node, als.TextureCoordinate[2].x); + } else if (name == "vtex1") { + read_flag[3] = true; + XmlParser::getValueAsFloat(node, als.TextureCoordinate[0].y); + } else if (name == "vtex2") { + read_flag[4] = true; + XmlParser::getValueAsFloat(node, als.TextureCoordinate[1].y); + } else if (name == "vtex3") { + read_flag[5] = true; + XmlParser::getValueAsFloat(node, als.TextureCoordinate[2].y); + } + } + ParseHelper_Node_Exit(); + + } else { + for (pugi::xml_attribute &attr : node.attributes()) { + const std::string name = attr.name(); + if (name == "u") { + read_flag[0] = true; + als.TextureCoordinate[0].x = attr.as_float(); + } else if (name == "u2") { + read_flag[1] = true; + als.TextureCoordinate[1].x = attr.as_float(); + } else if (name == "u3") { + read_flag[2] = true; + als.TextureCoordinate[2].x = attr.as_float(); + } else if (name == "v1") { + read_flag[3] = true; + als.TextureCoordinate[0].y = attr.as_float(); + } else if (name == "v2") { + read_flag[4] = true; + als.TextureCoordinate[1].y = attr.as_float(); + } else if (name == "v3") { + read_flag[5] = true; + als.TextureCoordinate[0].y = attr.as_float(); + } + } + } + + // check that all components was defined + if (!(read_flag[0] && read_flag[1] && read_flag[2] && read_flag[3] && read_flag[4] && read_flag[5])) { + throw DeadlyImportError("Not all texture coordinates are defined."); + } + + // copy attributes data + als.TextureID_R = rtexid; + als.TextureID_G = gtexid; + als.TextureID_B = btexid; + als.TextureID_A = atexid; + + mNodeElement_List.push_back(ne); +} + +}// namespace Assimp + +#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER diff --git a/libs/assimp/code/AssetLib/AMF/AMFImporter_Node.hpp b/libs/assimp/code/AssetLib/AMF/AMFImporter_Node.hpp new file mode 100644 index 0000000..c827533 --- /dev/null +++ b/libs/assimp/code/AssetLib/AMF/AMFImporter_Node.hpp @@ -0,0 +1,305 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AMFImporter_Node.hpp +/// \brief Elements of scene graph. +/// \date 2016 +/// \author smal.root@gmail.com + +#pragma once +#ifndef INCLUDED_AI_AMF_IMPORTER_NODE_H +#define INCLUDED_AI_AMF_IMPORTER_NODE_H + +// Header files, Assimp. +#include <assimp/scene.h> +#include <assimp/types.h> + +#include <list> +#include <string> +#include <vector> + +/// \class CAMFImporter_NodeElement +/// Base class for elements of nodes. +class AMFNodeElementBase { +public: + /// Define what data type contain node element. + enum EType { + ENET_Color, ///< Color element: <color>. + ENET_Constellation, ///< Grouping element: <constellation>. + ENET_Coordinates, ///< Coordinates element: <coordinates>. + ENET_Edge, ///< Edge element: <edge>. + ENET_Instance, ///< Grouping element: <constellation>. + ENET_Material, ///< Material element: <material>. + ENET_Metadata, ///< Metadata element: <metadata>. + ENET_Mesh, ///< Metadata element: <mesh>. + ENET_Object, ///< Element which hold object: <object>. + ENET_Root, ///< Root element: <amf>. + ENET_Triangle, ///< Triangle element: <triangle>. + ENET_TexMap, ///< Texture coordinates element: <texmap> or <map>. + ENET_Texture, ///< Texture element: <texture>. + ENET_Vertex, ///< Vertex element: <vertex>. + ENET_Vertices, ///< Vertex element: <vertices>. + ENET_Volume, ///< Volume element: <volume>. + + ENET_Invalid ///< Element has invalid type and possible contain invalid data. + }; + + const EType Type; ///< Type of element. + std::string ID; ///< ID of element. + AMFNodeElementBase *Parent; ///< Parent element. If nullptr then this node is root. + std::list<AMFNodeElementBase *> Child; ///< Child elements. + +public: /// Destructor, virtual.. + virtual ~AMFNodeElementBase() { + // empty + } + + /// Disabled copy constructor and co. + AMFNodeElementBase(const AMFNodeElementBase &pNodeElement) = delete; + AMFNodeElementBase(AMFNodeElementBase &&) = delete; + AMFNodeElementBase &operator=(const AMFNodeElementBase &pNodeElement) = delete; + AMFNodeElementBase() = delete; + +protected: + /// In constructor inheritor must set element type. + /// \param [in] pType - element type. + /// \param [in] pParent - parent element. + AMFNodeElementBase(const EType pType, AMFNodeElementBase *pParent) : + Type(pType), ID(), Parent(pParent), Child() { + // empty + } +}; // class IAMFImporter_NodeElement + +/// \struct CAMFImporter_NodeElement_Constellation +/// A collection of objects or constellations with specific relative locations. +struct AMFConstellation : public AMFNodeElementBase { + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFConstellation(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Constellation, pParent) {} + +}; // struct CAMFImporter_NodeElement_Constellation + +/// \struct CAMFImporter_NodeElement_Instance +/// Part of constellation. +struct AMFInstance : public AMFNodeElementBase { + + std::string ObjectID; ///< ID of object for instantiation. + /// \var Delta - The distance of translation in the x, y, or z direction, respectively, in the referenced object's coordinate system, to + /// create an instance of the object in the current constellation. + aiVector3D Delta; + + /// \var Rotation - The rotation, in degrees, to rotate the referenced object about its x, y, and z axes, respectively, to create an + /// instance of the object in the current constellation. Rotations shall be executed in order of x first, then y, then z. + aiVector3D Rotation; + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFInstance(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Instance, pParent) {} +}; + +/// \struct CAMFImporter_NodeElement_Metadata +/// Structure that define metadata node. +struct AMFMetadata : public AMFNodeElementBase { + + std::string Type; ///< Type of "Value". + std::string Value; ///< Value. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFMetadata(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Metadata, pParent) {} +}; + +/// \struct CAMFImporter_NodeElement_Root +/// Structure that define root node. +struct AMFRoot : public AMFNodeElementBase { + + std::string Unit; ///< The units to be used. May be "inch", "millimeter", "meter", "feet", or "micron". + std::string Version; ///< Version of format. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFRoot(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Root, pParent) {} +}; + +/// \struct CAMFImporter_NodeElement_Color +/// Structure that define object node. +struct AMFColor : public AMFNodeElementBase { + bool Composed; ///< Type of color stored: if true then look for formula in \ref Color_Composed[4], else - in \ref Color. + std::string Color_Composed[4]; ///< By components formulas of composed color. [0..3] - RGBA. + aiColor4D Color; ///< Constant color. + std::string Profile; ///< The ICC color space used to interpret the three color channels r, g and b.. + + /// @brief Constructor. + /// @param [in] pParent - pointer to parent node. + AMFColor(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Color, pParent), Composed(false), Color(), Profile() { + // empty + } +}; + +/// \struct CAMFImporter_NodeElement_Material +/// Structure that define material node. +struct AMFMaterial : public AMFNodeElementBase { + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFMaterial(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Material, pParent) {} +}; + +/// \struct CAMFImporter_NodeElement_Object +/// Structure that define object node. +struct AMFObject : public AMFNodeElementBase { + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFObject(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Object, pParent) {} +}; + +/// \struct CAMFImporter_NodeElement_Mesh +/// Structure that define mesh node. +struct AMFMesh : public AMFNodeElementBase { + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFMesh(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Mesh, pParent) {} +}; + +/// \struct CAMFImporter_NodeElement_Vertex +/// Structure that define vertex node. +struct AMFVertex : public AMFNodeElementBase { + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFVertex(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Vertex, pParent) {} +}; + +/// \struct CAMFImporter_NodeElement_Edge +/// Structure that define edge node. +struct AMFEdge : public AMFNodeElementBase { + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFEdge(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Edge, pParent) {} +}; + +/// \struct CAMFImporter_NodeElement_Vertices +/// Structure that define vertices node. +struct AMFVertices : public AMFNodeElementBase { + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFVertices(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Vertices, pParent) {} +}; + +/// \struct CAMFImporter_NodeElement_Volume +/// Structure that define volume node. +struct AMFVolume : public AMFNodeElementBase { + std::string MaterialID; ///< Which material to use. + std::string Type; ///< What this volume describes can be "region" or "support". If none specified, "object" is assumed. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFVolume(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Volume, pParent) {} +}; + +/// \struct CAMFImporter_NodeElement_Coordinates +/// Structure that define coordinates node. +struct AMFCoordinates : public AMFNodeElementBase { + aiVector3D Coordinate; ///< Coordinate. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFCoordinates(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Coordinates, pParent) {} +}; + +/// \struct CAMFImporter_NodeElement_TexMap +/// Structure that define texture coordinates node. +struct AMFTexMap : public AMFNodeElementBase { + aiVector3D TextureCoordinate[3]; ///< Texture coordinates. + std::string TextureID_R; ///< Texture ID for red color component. + std::string TextureID_G; ///< Texture ID for green color component. + std::string TextureID_B; ///< Texture ID for blue color component. + std::string TextureID_A; ///< Texture ID for alpha color component. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFTexMap(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_TexMap, pParent), TextureCoordinate{}, TextureID_R(), TextureID_G(), TextureID_B(), TextureID_A() { + // empty + } +}; + +/// \struct CAMFImporter_NodeElement_Triangle +/// Structure that define triangle node. +struct AMFTriangle : public AMFNodeElementBase { + size_t V[3]; ///< Triangle vertices. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFTriangle(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Triangle, pParent) { + // empty + } +}; + +/// Structure that define texture node. +struct AMFTexture : public AMFNodeElementBase { + size_t Width, Height, Depth; ///< Size of the texture. + std::vector<uint8_t> Data; ///< Data of the texture. + bool Tiled; + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + AMFTexture(AMFNodeElementBase *pParent) : + AMFNodeElementBase(ENET_Texture, pParent), Width(0), Height(0), Depth(0), Data(), Tiled(false) { + // empty + } +}; + +#endif // INCLUDED_AI_AMF_IMPORTER_NODE_H diff --git a/libs/assimp/code/AssetLib/AMF/AMFImporter_Postprocess.cpp b/libs/assimp/code/AssetLib/AMF/AMFImporter_Postprocess.cpp new file mode 100644 index 0000000..a65f926 --- /dev/null +++ b/libs/assimp/code/AssetLib/AMF/AMFImporter_Postprocess.cpp @@ -0,0 +1,894 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AMFImporter_Postprocess.cpp +/// \brief Convert built scenegraph and objects to Assimp scenegraph. +/// \date 2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER + +#include "AMFImporter.hpp" + +#include <assimp/SceneCombiner.h> +#include <assimp/StandardShapes.h> +#include <assimp/StringUtils.h> + +#include <iterator> + +namespace Assimp { + +aiColor4D AMFImporter::SPP_Material::GetColor(const float /*pX*/, const float /*pY*/, const float /*pZ*/) const { + aiColor4D tcol; + + // Check if stored data are supported. + if (!Composition.empty()) { + throw DeadlyImportError("IME. GetColor for composition"); + } + + if (Color->Composed) { + throw DeadlyImportError("IME. GetColor, composed color"); + } + + tcol = Color->Color; + + // Check if default color must be used + if ((tcol.r == 0) && (tcol.g == 0) && (tcol.b == 0) && (tcol.a == 0)) { + tcol.r = 0.5f; + tcol.g = 0.5f; + tcol.b = 0.5f; + tcol.a = 1; + } + + return tcol; +} + +void AMFImporter::PostprocessHelper_CreateMeshDataArray(const AMFMesh &nodeElement, std::vector<aiVector3D> &vertexCoordinateArray, + std::vector<AMFColor *> &pVertexColorArray) const { + AMFVertices *vn = nullptr; + size_t col_idx; + + // All data stored in "vertices", search for it. + for (AMFNodeElementBase *ne_child : nodeElement.Child) { + if (ne_child->Type == AMFNodeElementBase::ENET_Vertices) { + vn = (AMFVertices*)ne_child; + } + } + + // If "vertices" not found then no work for us. + if (vn == nullptr) { + return; + } + + // all coordinates stored as child and we need to reserve space for future push_back's. + vertexCoordinateArray.reserve(vn->Child.size()); + + // colors count equal vertices count. + pVertexColorArray.resize(vn->Child.size()); + col_idx = 0; + + // Inside vertices collect all data and place to arrays + for (AMFNodeElementBase *vn_child : vn->Child) { + // vertices, colors + if (vn_child->Type == AMFNodeElementBase::ENET_Vertex) { + // by default clear color for current vertex + pVertexColorArray[col_idx] = nullptr; + + for (AMFNodeElementBase *vtx : vn_child->Child) { + if (vtx->Type == AMFNodeElementBase::ENET_Coordinates) { + vertexCoordinateArray.push_back(((AMFCoordinates *)vtx)->Coordinate); + continue; + } + + if (vtx->Type == AMFNodeElementBase::ENET_Color) { + pVertexColorArray[col_idx] = (AMFColor *)vtx; + continue; + } + } + + ++col_idx; + } + } +} + +size_t AMFImporter::PostprocessHelper_GetTextureID_Or_Create(const std::string &r, const std::string &g, const std::string &b, const std::string &a) { + if (r.empty() && g.empty() && b.empty() && a.empty()) { + throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. At least one texture ID must be defined."); + } + + std::string TextureConverted_ID = r + "_" + g + "_" + b + "_" + a; + size_t TextureConverted_Index = 0; + for (const SPP_Texture &tex_convd : mTexture_Converted) { + if (tex_convd.ID == TextureConverted_ID) { + return TextureConverted_Index; + } else { + ++TextureConverted_Index; + } + } + + // Converted texture not found, create it. + AMFTexture *src_texture[4] { + nullptr + }; + std::vector<AMFTexture *> src_texture_4check; + SPP_Texture converted_texture; + + { // find all specified source textures + AMFNodeElementBase *t_tex = nullptr; + + // R + if (!r.empty()) { + if (!Find_NodeElement(r, AMFNodeElementBase::EType::ENET_Texture, &t_tex)) { + Throw_ID_NotFound(r); + } + + src_texture[0] = (AMFTexture *)t_tex; + src_texture_4check.push_back((AMFTexture *)t_tex); + } else { + src_texture[0] = nullptr; + } + + // G + if (!g.empty()) { + if (!Find_NodeElement(g, AMFNodeElementBase::ENET_Texture, &t_tex)) { + Throw_ID_NotFound(g); + } + + src_texture[1] = (AMFTexture *)t_tex; + src_texture_4check.push_back((AMFTexture *)t_tex); + } else { + src_texture[1] = nullptr; + } + + // B + if (!b.empty()) { + if (!Find_NodeElement(b, AMFNodeElementBase::ENET_Texture, &t_tex)) { + Throw_ID_NotFound(b); + } + + src_texture[2] = (AMFTexture *)t_tex; + src_texture_4check.push_back((AMFTexture *)t_tex); + } else { + src_texture[2] = nullptr; + } + + // A + if (!a.empty()) { + if (!Find_NodeElement(a, AMFNodeElementBase::ENET_Texture, &t_tex)) { + Throw_ID_NotFound(a); + } + + src_texture[3] = (AMFTexture *)t_tex; + src_texture_4check.push_back((AMFTexture *)t_tex); + } else { + src_texture[3] = nullptr; + } + } // END: find all specified source textures + + // check that all textures has same size + if (src_texture_4check.size() > 1) { + for (size_t i = 0, i_e = (src_texture_4check.size() - 1); i < i_e; i++) { + if ((src_texture_4check[i]->Width != src_texture_4check[i + 1]->Width) || (src_texture_4check[i]->Height != src_texture_4check[i + 1]->Height) || + (src_texture_4check[i]->Depth != src_texture_4check[i + 1]->Depth)) { + throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. Source texture must has the same size."); + } + } + } // if(src_texture_4check.size() > 1) + + // set texture attributes + converted_texture.Width = src_texture_4check[0]->Width; + converted_texture.Height = src_texture_4check[0]->Height; + converted_texture.Depth = src_texture_4check[0]->Depth; + // if one of source texture is tiled then converted texture is tiled too. + converted_texture.Tiled = false; + for (uint8_t i = 0; i < src_texture_4check.size(); ++i) { + converted_texture.Tiled |= src_texture_4check[i]->Tiled; + } + + // Create format hint. + strcpy(converted_texture.FormatHint, "rgba0000"); // copy initial string. + if (!r.empty()) converted_texture.FormatHint[4] = '8'; + if (!g.empty()) converted_texture.FormatHint[5] = '8'; + if (!b.empty()) converted_texture.FormatHint[6] = '8'; + if (!a.empty()) converted_texture.FormatHint[7] = '8'; + + // Сopy data of textures. + size_t tex_size = 0; + size_t step = 0; + size_t off_g = 0; + size_t off_b = 0; + + // Calculate size of the target array and rule how data will be copied. + if (!r.empty() && nullptr != src_texture[0]) { + tex_size += src_texture[0]->Data.size(); + step++, off_g++, off_b++; + } + if (!g.empty() && nullptr != src_texture[1]) { + tex_size += src_texture[1]->Data.size(); + step++, off_b++; + } + if (!b.empty() && nullptr != src_texture[2]) { + tex_size += src_texture[2]->Data.size(); + step++; + } + if (!a.empty() && nullptr != src_texture[3]) { + tex_size += src_texture[3]->Data.size(); + step++; + } + + // Create target array. + converted_texture.Data = new uint8_t[tex_size]; + // And copy data + auto CopyTextureData = [&](const std::string &pID, const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void { + if (!pID.empty()) { + for (size_t idx_target = pOffset, idx_src = 0; idx_target < tex_size; idx_target += pStep, idx_src++) { + AMFTexture *tex = src_texture[pSrcTexNum]; + ai_assert(tex); + converted_texture.Data[idx_target] = tex->Data.at(idx_src); + } + } + }; // auto CopyTextureData = [&](const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void + + CopyTextureData(r, 0, step, 0); + CopyTextureData(g, off_g, step, 1); + CopyTextureData(b, off_b, step, 2); + CopyTextureData(a, step - 1, step, 3); + + // Store new converted texture ID + converted_texture.ID = TextureConverted_ID; + // Store new converted texture + mTexture_Converted.push_back(converted_texture); + + return TextureConverted_Index; +} + +void AMFImporter::PostprocessHelper_SplitFacesByTextureID(std::list<SComplexFace> &pInputList, std::list<std::list<SComplexFace>> &pOutputList_Separated) { + auto texmap_is_equal = [](const AMFTexMap *pTexMap1, const AMFTexMap *pTexMap2) -> bool { + if ((pTexMap1 == nullptr) && (pTexMap2 == nullptr)) return true; + if (pTexMap1 == nullptr) return false; + if (pTexMap2 == nullptr) return false; + + if (pTexMap1->TextureID_R != pTexMap2->TextureID_R) return false; + if (pTexMap1->TextureID_G != pTexMap2->TextureID_G) return false; + if (pTexMap1->TextureID_B != pTexMap2->TextureID_B) return false; + if (pTexMap1->TextureID_A != pTexMap2->TextureID_A) return false; + + return true; + }; + + pOutputList_Separated.clear(); + if (pInputList.empty()) return; + + do { + SComplexFace face_start = pInputList.front(); + std::list<SComplexFace> face_list_cur; + + for (std::list<SComplexFace>::iterator it = pInputList.begin(), it_end = pInputList.end(); it != it_end;) { + if (texmap_is_equal(face_start.TexMap, it->TexMap)) { + auto it_old = it; + + ++it; + face_list_cur.push_back(*it_old); + pInputList.erase(it_old); + } else { + ++it; + } + } + + if (!face_list_cur.empty()) pOutputList_Separated.push_back(face_list_cur); + + } while (!pInputList.empty()); +} + +void AMFImporter::Postprocess_AddMetadata(const AMFMetaDataArray &metadataList, aiNode &sceneNode) const { + if (metadataList.empty()) { + return; + } + + if (sceneNode.mMetaData != nullptr) { + throw DeadlyImportError("Postprocess. MetaData member in node are not nullptr. Something went wrong."); + } + + // copy collected metadata to output node. + sceneNode.mMetaData = aiMetadata::Alloc(static_cast<unsigned int>(metadataList.size())); + size_t meta_idx(0); + + for (const AMFMetadata *metadata : metadataList) { + sceneNode.mMetaData->Set(static_cast<unsigned int>(meta_idx++), metadata->Type, aiString(metadata->Value)); + } +} + +void AMFImporter::Postprocess_BuildNodeAndObject(const AMFObject &pNodeElement, MeshArray &meshList, aiNode **pSceneNode) { + AMFColor *object_color = nullptr; + + // create new aiNode and set name as <object> has. + *pSceneNode = new aiNode; + (*pSceneNode)->mName = pNodeElement.ID; + // read mesh and color + for (const AMFNodeElementBase *ne_child : pNodeElement.Child) { + std::vector<aiVector3D> vertex_arr; + std::vector<AMFColor *> color_arr; + + // color for object + if (ne_child->Type == AMFNodeElementBase::ENET_Color) { + object_color = (AMFColor *) ne_child; + } + + if (ne_child->Type == AMFNodeElementBase::ENET_Mesh) { + // Create arrays from children of mesh: vertices. + PostprocessHelper_CreateMeshDataArray(*((AMFMesh *)ne_child), vertex_arr, color_arr); + // Use this arrays as a source when creating every aiMesh + Postprocess_BuildMeshSet(*((AMFMesh *)ne_child), vertex_arr, color_arr, object_color, meshList, **pSceneNode); + } + } // for(const CAMFImporter_NodeElement* ne_child: pNodeElement) +} + +void AMFImporter::Postprocess_BuildMeshSet(const AMFMesh &pNodeElement, const std::vector<aiVector3D> &pVertexCoordinateArray, + const std::vector<AMFColor *> &pVertexColorArray, const AMFColor *pObjectColor, MeshArray &pMeshList, aiNode &pSceneNode) { + std::list<unsigned int> mesh_idx; + + // all data stored in "volume", search for it. + for (const AMFNodeElementBase *ne_child : pNodeElement.Child) { + const AMFColor *ne_volume_color = nullptr; + const SPP_Material *cur_mat = nullptr; + + if (ne_child->Type == AMFNodeElementBase::ENET_Volume) { + /******************* Get faces *******************/ + const AMFVolume *ne_volume = reinterpret_cast<const AMFVolume *>(ne_child); + + std::list<SComplexFace> complex_faces_list; // List of the faces of the volume. + std::list<std::list<SComplexFace>> complex_faces_toplist; // List of the face list for every mesh. + + // check if volume use material + if (!ne_volume->MaterialID.empty()) { + if (!Find_ConvertedMaterial(ne_volume->MaterialID, &cur_mat)) { + Throw_ID_NotFound(ne_volume->MaterialID); + } + } + + // inside "volume" collect all data and place to arrays or create new objects + for (const AMFNodeElementBase *ne_volume_child : ne_volume->Child) { + // color for volume + if (ne_volume_child->Type == AMFNodeElementBase::ENET_Color) { + ne_volume_color = reinterpret_cast<const AMFColor *>(ne_volume_child); + } else if (ne_volume_child->Type == AMFNodeElementBase::ENET_Triangle) // triangles, triangles colors + { + const AMFTriangle &tri_al = *reinterpret_cast<const AMFTriangle *>(ne_volume_child); + + SComplexFace complex_face; + + // initialize pointers + complex_face.Color = nullptr; + complex_face.TexMap = nullptr; + // get data from triangle children: color, texture coordinates. + if (tri_al.Child.size()) { + for (const AMFNodeElementBase *ne_triangle_child : tri_al.Child) { + if (ne_triangle_child->Type == AMFNodeElementBase::ENET_Color) + complex_face.Color = reinterpret_cast<const AMFColor *>(ne_triangle_child); + else if (ne_triangle_child->Type == AMFNodeElementBase::ENET_TexMap) + complex_face.TexMap = reinterpret_cast<const AMFTexMap *>(ne_triangle_child); + } + } // if(tri_al.Child.size()) + + // create new face and store it. + complex_face.Face.mNumIndices = 3; + complex_face.Face.mIndices = new unsigned int[3]; + complex_face.Face.mIndices[0] = static_cast<unsigned int>(tri_al.V[0]); + complex_face.Face.mIndices[1] = static_cast<unsigned int>(tri_al.V[1]); + complex_face.Face.mIndices[2] = static_cast<unsigned int>(tri_al.V[2]); + complex_faces_list.push_back(complex_face); + } + } // for(const CAMFImporter_NodeElement* ne_volume_child: ne_volume->Child) + + /**** Split faces list: one list per mesh ****/ + PostprocessHelper_SplitFacesByTextureID(complex_faces_list, complex_faces_toplist); + + /***** Create mesh for every faces list ******/ + for (std::list<SComplexFace> &face_list_cur : complex_faces_toplist) { + auto VertexIndex_GetMinimal = [](const std::list<SComplexFace> &pFaceList, const size_t *pBiggerThan) -> size_t { + size_t rv = 0; + + if (pBiggerThan != nullptr) { + bool found = false; + const size_t biggerThan = *pBiggerThan; + for (const SComplexFace &face : pFaceList) { + for (size_t idx_vert = 0; idx_vert < face.Face.mNumIndices; idx_vert++) { + if (face.Face.mIndices[idx_vert] > biggerThan) { + rv = face.Face.mIndices[idx_vert]; + found = true; + break; + } + } + + if (found) { + break; + } + } + + if (!found) { + return *pBiggerThan; + } + } else { + rv = pFaceList.front().Face.mIndices[0]; + } // if(pBiggerThan != nullptr) else + + for (const SComplexFace &face : pFaceList) { + for (size_t vi = 0; vi < face.Face.mNumIndices; vi++) { + if (face.Face.mIndices[vi] < rv) { + if (pBiggerThan != nullptr) { + if (face.Face.mIndices[vi] > *pBiggerThan) rv = face.Face.mIndices[vi]; + } else { + rv = face.Face.mIndices[vi]; + } + } + } + } // for(const SComplexFace& face: pFaceList) + + return rv; + }; // auto VertexIndex_GetMinimal = [](const std::list<SComplexFace>& pFaceList, const size_t* pBiggerThan) -> size_t + + auto VertexIndex_Replace = [](std::list<SComplexFace> &pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void { + for (const SComplexFace &face : pFaceList) { + for (size_t vi = 0; vi < face.Face.mNumIndices; vi++) { + if (face.Face.mIndices[vi] == pIdx_From) face.Face.mIndices[vi] = static_cast<unsigned int>(pIdx_To); + } + } + }; // auto VertexIndex_Replace = [](std::list<SComplexFace>& pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void + + auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D { + // Color priorities(In descending order): + // 1. triangle color; + // 2. vertex color; + // 3. volume color; + // 4. object color; + // 5. material; + // 6. default - invisible coat. + // + // Fill vertices colors in color priority list above that's points from 1 to 6. + if ((pIdx < pVertexColorArray.size()) && (pVertexColorArray[pIdx] != nullptr)) // check for vertex color + { + if (pVertexColorArray[pIdx]->Composed) + throw DeadlyImportError("IME: vertex color composed"); + else + return pVertexColorArray[pIdx]->Color; + } else if (ne_volume_color != nullptr) // check for volume color + { + if (ne_volume_color->Composed) + throw DeadlyImportError("IME: volume color composed"); + else + return ne_volume_color->Color; + } else if (pObjectColor != nullptr) // check for object color + { + if (pObjectColor->Composed) + throw DeadlyImportError("IME: object color composed"); + else + return pObjectColor->Color; + } else if (cur_mat != nullptr) // check for material + { + return cur_mat->GetColor(pVertexCoordinateArray.at(pIdx).x, pVertexCoordinateArray.at(pIdx).y, pVertexCoordinateArray.at(pIdx).z); + } else // set default color. + { + return { 0, 0, 0, 0 }; + } // if((vi < pVertexColorArray.size()) && (pVertexColorArray[vi] != nullptr)) else + }; // auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D + + aiMesh *tmesh = new aiMesh; + + tmesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; // Only triangles is supported by AMF. + // + // set geometry and colors (vertices) + // + // copy faces/triangles + tmesh->mNumFaces = static_cast<unsigned int>(face_list_cur.size()); + tmesh->mFaces = new aiFace[tmesh->mNumFaces]; + + // Create vertices list and optimize indices. Optimization mean following.In AMF all volumes use one big list of vertices. And one volume + // can use only part of vertices list, for example: vertices list contain few thousands of vertices and volume use vertices 1, 3, 10. + // Do you need all this thousands of garbage? Of course no. So, optimization step transform sparse indices set to continuous. + size_t VertexCount_Max = tmesh->mNumFaces * 3; // 3 - triangles. + std::vector<aiVector3D> vert_arr, texcoord_arr; + std::vector<aiColor4D> col_arr; + + vert_arr.reserve(VertexCount_Max * 2); // "* 2" - see below TODO. + col_arr.reserve(VertexCount_Max * 2); + + { // fill arrays + size_t vert_idx_from, vert_idx_to; + + // first iteration. + vert_idx_to = 0; + vert_idx_from = VertexIndex_GetMinimal(face_list_cur, nullptr); + vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from)); + col_arr.push_back(Vertex_CalculateColor(vert_idx_from)); + if (vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to); + + // rest iterations + do { + vert_idx_from = VertexIndex_GetMinimal(face_list_cur, &vert_idx_to); + if (vert_idx_from == vert_idx_to) break; // all indices are transferred, + + vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from)); + col_arr.push_back(Vertex_CalculateColor(vert_idx_from)); + vert_idx_to++; + if (vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to); + + } while (true); + } // fill arrays. END. + + // + // check if triangle colors are used and create additional faces if needed. + // + for (const SComplexFace &face_cur : face_list_cur) { + if (face_cur.Color != nullptr) { + aiColor4D face_color; + size_t vert_idx_new = vert_arr.size(); + + if (face_cur.Color->Composed) + throw DeadlyImportError("IME: face color composed"); + else + face_color = face_cur.Color->Color; + + for (size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++) { + vert_arr.push_back(vert_arr.at(face_cur.Face.mIndices[idx_ind])); + col_arr.push_back(face_color); + face_cur.Face.mIndices[idx_ind] = static_cast<unsigned int>(vert_idx_new++); + } + } // if(face_cur.Color != nullptr) + } // for(const SComplexFace& face_cur: face_list_cur) + + // + // if texture is used then copy texture coordinates too. + // + if (face_list_cur.front().TexMap != nullptr) { + size_t idx_vert_new = vert_arr.size(); + ///TODO: clean unused vertices. "* 2": in certain cases - mesh full of triangle colors - vert_arr will contain duplicated vertices for + /// colored triangles and initial vertices (for colored vertices) which in real became unused. This part need more thinking about + /// optimization. + bool *idx_vert_used; + + idx_vert_used = new bool[VertexCount_Max * 2]; + for (size_t i = 0, i_e = VertexCount_Max * 2; i < i_e; i++) + idx_vert_used[i] = false; + + // This ID's will be used when set materials ID in scene. + tmesh->mMaterialIndex = static_cast<unsigned int>(PostprocessHelper_GetTextureID_Or_Create(face_list_cur.front().TexMap->TextureID_R, + face_list_cur.front().TexMap->TextureID_G, + face_list_cur.front().TexMap->TextureID_B, + face_list_cur.front().TexMap->TextureID_A)); + texcoord_arr.resize(VertexCount_Max * 2); + for (const SComplexFace &face_cur : face_list_cur) { + for (size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++) { + const size_t idx_vert = face_cur.Face.mIndices[idx_ind]; + + if (!idx_vert_used[idx_vert]) { + texcoord_arr.at(idx_vert) = face_cur.TexMap->TextureCoordinate[idx_ind]; + idx_vert_used[idx_vert] = true; + } else if (texcoord_arr.at(idx_vert) != face_cur.TexMap->TextureCoordinate[idx_ind]) { + // in that case one vertex is shared with many texture coordinates. We need to duplicate vertex with another texture + // coordinates. + vert_arr.push_back(vert_arr.at(idx_vert)); + col_arr.push_back(col_arr.at(idx_vert)); + texcoord_arr.at(idx_vert_new) = face_cur.TexMap->TextureCoordinate[idx_ind]; + face_cur.Face.mIndices[idx_ind] = static_cast<unsigned int>(idx_vert_new++); + } + } // for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++) + } // for(const SComplexFace& face_cur: face_list_cur) + + delete[] idx_vert_used; + // shrink array + texcoord_arr.resize(idx_vert_new); + } // if(face_list_cur.front().TexMap != nullptr) + + // + // copy collected data to mesh + // + tmesh->mNumVertices = static_cast<unsigned int>(vert_arr.size()); + tmesh->mVertices = new aiVector3D[tmesh->mNumVertices]; + tmesh->mColors[0] = new aiColor4D[tmesh->mNumVertices]; + + memcpy(tmesh->mVertices, vert_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D)); + memcpy(tmesh->mColors[0], col_arr.data(), tmesh->mNumVertices * sizeof(aiColor4D)); + if (texcoord_arr.size() > 0) { + tmesh->mTextureCoords[0] = new aiVector3D[tmesh->mNumVertices]; + memcpy(tmesh->mTextureCoords[0], texcoord_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D)); + tmesh->mNumUVComponents[0] = 2; // U and V stored in "x", "y" of aiVector3D. + } + + size_t idx_face = 0; + for (const SComplexFace &face_cur : face_list_cur) + tmesh->mFaces[idx_face++] = face_cur.Face; + + // store new aiMesh + mesh_idx.push_back(static_cast<unsigned int>(pMeshList.size())); + pMeshList.push_back(tmesh); + } // for(const std::list<SComplexFace>& face_list_cur: complex_faces_toplist) + } // if(ne_child->Type == CAMFImporter_NodeElement::ENET_Volume) + } // for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child) + + // if meshes was created then assign new indices with current aiNode + if (!mesh_idx.empty()) { + std::list<unsigned int>::const_iterator mit = mesh_idx.begin(); + + pSceneNode.mNumMeshes = static_cast<unsigned int>(mesh_idx.size()); + pSceneNode.mMeshes = new unsigned int[pSceneNode.mNumMeshes]; + for (size_t i = 0; i < pSceneNode.mNumMeshes; i++) + pSceneNode.mMeshes[i] = *mit++; + } // if(mesh_idx.size() > 0) +} + +void AMFImporter::Postprocess_BuildMaterial(const AMFMaterial &pMaterial) { + SPP_Material new_mat; + + new_mat.ID = pMaterial.ID; + for (const AMFNodeElementBase *mat_child : pMaterial.Child) { + if (mat_child->Type == AMFNodeElementBase::ENET_Color) { + new_mat.Color = (AMFColor*)mat_child; + } else if (mat_child->Type == AMFNodeElementBase::ENET_Metadata) { + new_mat.Metadata.push_back((AMFMetadata *)mat_child); + } + } // for(const CAMFImporter_NodeElement* mat_child; pMaterial.Child) + + // place converted material to special list + mMaterial_Converted.push_back(new_mat); +} + +void AMFImporter::Postprocess_BuildConstellation(AMFConstellation &pConstellation, NodeArray &nodeArray) const { + aiNode *con_node; + std::list<aiNode *> ch_node; + + // We will build next hierarchy: + // aiNode as parent (<constellation>) for set of nodes as a children + // |- aiNode for transformation (<instance> -> <delta...>, <r...>) - aiNode for pointing to object ("objectid") + // ... + // \_ aiNode for transformation (<instance> -> <delta...>, <r...>) - aiNode for pointing to object ("objectid") + con_node = new aiNode; + con_node->mName = pConstellation.ID; + // Walk through children and search for instances of another objects, constellations. + for (const AMFNodeElementBase *ne : pConstellation.Child) { + aiMatrix4x4 tmat; + aiNode *t_node; + aiNode *found_node; + + if (ne->Type == AMFNodeElementBase::ENET_Metadata) continue; + if (ne->Type != AMFNodeElementBase::ENET_Instance) throw DeadlyImportError("Only <instance> nodes can be in <constellation>."); + + // create alias for convenience + AMFInstance &als = *((AMFInstance *)ne); + // find referenced object + if (!Find_ConvertedNode(als.ObjectID, nodeArray, &found_node)) Throw_ID_NotFound(als.ObjectID); + + // create node for applying transformation + t_node = new aiNode; + t_node->mParent = con_node; + // apply transformation + aiMatrix4x4::Translation(als.Delta, tmat), t_node->mTransformation *= tmat; + aiMatrix4x4::RotationX(als.Rotation.x, tmat), t_node->mTransformation *= tmat; + aiMatrix4x4::RotationY(als.Rotation.y, tmat), t_node->mTransformation *= tmat; + aiMatrix4x4::RotationZ(als.Rotation.z, tmat), t_node->mTransformation *= tmat; + // create array for one child node + t_node->mNumChildren = 1; + t_node->mChildren = new aiNode *[t_node->mNumChildren]; + SceneCombiner::Copy(&t_node->mChildren[0], found_node); + t_node->mChildren[0]->mParent = t_node; + ch_node.push_back(t_node); + } // for(const CAMFImporter_NodeElement* ne: pConstellation.Child) + + // copy found aiNode's as children + if (ch_node.empty()) throw DeadlyImportError("<constellation> must have at least one <instance>."); + + size_t ch_idx = 0; + + con_node->mNumChildren = static_cast<unsigned int>(ch_node.size()); + con_node->mChildren = new aiNode *[con_node->mNumChildren]; + for (aiNode *node : ch_node) + con_node->mChildren[ch_idx++] = node; + + // and place "root" of <constellation> node to node list + nodeArray.push_back(con_node); +} + +void AMFImporter::Postprocess_BuildScene(aiScene *pScene) { + NodeArray nodeArray; + MeshArray mesh_list; + AMFMetaDataArray meta_list; + + // + // Because for AMF "material" is just complex colors mixing so aiMaterial will not be used. + // For building aiScene we are must to do few steps: + // at first creating root node for aiScene. + pScene->mRootNode = new aiNode; + pScene->mRootNode->mParent = nullptr; + pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED; + // search for root(<amf>) element + AMFNodeElementBase *root_el = nullptr; + + for (AMFNodeElementBase *ne : mNodeElement_List) { + if (ne->Type != AMFNodeElementBase::ENET_Root) { + continue; + } + + root_el = ne; + break; + } // for(const CAMFImporter_NodeElement* ne: mNodeElement_List) + + // Check if root element are found. + if (root_el == nullptr) { + throw DeadlyImportError("Root(<amf>) element not found."); + } + + // after that walk through children of root and collect data. Five types of nodes can be placed at top level - in <amf>: <object>, <material>, <texture>, + // <constellation> and <metadata>. But at first we must read <material> and <texture> because they will be used in <object>. <metadata> can be read + // at any moment. + // + // 1. <material> + // 2. <texture> will be converted later when processing triangles list. \sa Postprocess_BuildMeshSet + for (const AMFNodeElementBase *root_child : root_el->Child) { + if (root_child->Type == AMFNodeElementBase::ENET_Material) { + Postprocess_BuildMaterial(*((AMFMaterial *)root_child)); + } + } + + // After "appearance" nodes we must read <object> because it will be used in <constellation> -> <instance>. + // + // 3. <object> + for (const AMFNodeElementBase *root_child : root_el->Child) { + if (root_child->Type == AMFNodeElementBase::ENET_Object) { + aiNode *tnode = nullptr; + + // for <object> mesh and node must be built: object ID assigned to aiNode name and will be used in future for <instance> + Postprocess_BuildNodeAndObject(*((AMFObject *)root_child), mesh_list, &tnode); + if (tnode != nullptr) { + nodeArray.push_back(tnode); + } + } + } // for(const CAMFImporter_NodeElement* root_child: root_el->Child) + + // And finally read rest of nodes. + // + for (const AMFNodeElementBase *root_child : root_el->Child) { + // 4. <constellation> + if (root_child->Type == AMFNodeElementBase::ENET_Constellation) { + // <object> and <constellation> at top of self abstraction use aiNode. So we can use only aiNode list for creating new aiNode's. + Postprocess_BuildConstellation(*((AMFConstellation *)root_child), nodeArray); + } + + // 5, <metadata> + if (root_child->Type == AMFNodeElementBase::ENET_Metadata) meta_list.push_back((AMFMetadata *)root_child); + } // for(const CAMFImporter_NodeElement* root_child: root_el->Child) + + // at now we can add collected metadata to root node + Postprocess_AddMetadata(meta_list, *pScene->mRootNode); + // + // Check constellation children + // + // As said in specification: + // "When multiple objects and constellations are defined in a single file, only the top level objects and constellations are available for printing." + // What that means? For example: if some object is used in constellation then you must show only constellation but not original object. + // And at this step we are checking that relations. +nl_clean_loop: + + if (nodeArray.size() > 1) { + // walk through all nodes + for (NodeArray::iterator nl_it = nodeArray.begin(); nl_it != nodeArray.end(); ++nl_it) { + // and try to find them in another top nodes. + NodeArray::const_iterator next_it = nl_it; + + ++next_it; + for (; next_it != nodeArray.end(); ++next_it) { + if ((*next_it)->FindNode((*nl_it)->mName) != nullptr) { + // if current top node(nl_it) found in another top node then erase it from node_list and restart search loop. + nodeArray.erase(nl_it); + + goto nl_clean_loop; + } + } // for(; next_it != node_list.end(); next_it++) + } // for(std::list<aiNode*>::const_iterator nl_it = node_list.begin(); nl_it != node_list.end(); nl_it++) + } + + // + // move created objects to aiScene + // + // + // Nodes + if (!nodeArray.empty()) { + NodeArray::const_iterator nl_it = nodeArray.begin(); + + pScene->mRootNode->mNumChildren = static_cast<unsigned int>(nodeArray.size()); + pScene->mRootNode->mChildren = new aiNode *[pScene->mRootNode->mNumChildren]; + for (size_t i = 0; i < pScene->mRootNode->mNumChildren; i++) { + // Objects and constellation that must be showed placed at top of hierarchy in <amf> node. So all aiNode's in node_list must have + // mRootNode only as parent. + (*nl_it)->mParent = pScene->mRootNode; + pScene->mRootNode->mChildren[i] = *nl_it++; + } + } // if(node_list.size() > 0) + + // + // Meshes + if (!mesh_list.empty()) { + MeshArray::const_iterator ml_it = mesh_list.begin(); + + pScene->mNumMeshes = static_cast<unsigned int>(mesh_list.size()); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + for (size_t i = 0; i < pScene->mNumMeshes; i++) + pScene->mMeshes[i] = *ml_it++; + } // if(mesh_list.size() > 0) + + // + // Textures + pScene->mNumTextures = static_cast<unsigned int>(mTexture_Converted.size()); + if (pScene->mNumTextures > 0) { + size_t idx; + + idx = 0; + pScene->mTextures = new aiTexture *[pScene->mNumTextures]; + for (const SPP_Texture &tex_convd : mTexture_Converted) { + pScene->mTextures[idx] = new aiTexture; + pScene->mTextures[idx]->mWidth = static_cast<unsigned int>(tex_convd.Width); + pScene->mTextures[idx]->mHeight = static_cast<unsigned int>(tex_convd.Height); + pScene->mTextures[idx]->pcData = (aiTexel *)tex_convd.Data; + // texture format description. + strcpy(pScene->mTextures[idx]->achFormatHint, tex_convd.FormatHint); + idx++; + } // for(const SPP_Texture& tex_convd: mTexture_Converted) + + // Create materials for embedded textures. + idx = 0; + pScene->mNumMaterials = static_cast<unsigned int>(mTexture_Converted.size()); + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + for (const SPP_Texture &tex_convd : mTexture_Converted) { + const aiString texture_id(AI_EMBEDDED_TEXNAME_PREFIX + ai_to_string(idx)); + const int mode = aiTextureOp_Multiply; + const int repeat = tex_convd.Tiled ? 1 : 0; + + pScene->mMaterials[idx] = new aiMaterial; + pScene->mMaterials[idx]->AddProperty(&texture_id, AI_MATKEY_TEXTURE_DIFFUSE(0)); + pScene->mMaterials[idx]->AddProperty(&mode, 1, AI_MATKEY_TEXOP_DIFFUSE(0)); + pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0)); + pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0)); + idx++; + } + } // if(pScene->mNumTextures > 0) +} // END: after that walk through children of root and collect data + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER diff --git a/libs/assimp/code/AssetLib/ASE/ASELoader.cpp b/libs/assimp/code/AssetLib/ASE/ASELoader.cpp new file mode 100644 index 0000000..caa7089 --- /dev/null +++ b/libs/assimp/code/AssetLib/ASE/ASELoader.cpp @@ -0,0 +1,1288 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 ASELoader.cpp + * @brief Implementation of the ASE importer class + */ + +#ifndef ASSIMP_BUILD_NO_ASE_IMPORTER + +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +// internal headers +#include "ASELoader.h" +#include "Common/TargetAnimation.h" +#include <assimp/SkeletonMeshBuilder.h> +#include <assimp/StringComparison.h> + +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/Importer.hpp> + +#include <memory> + +// utilities +#include <assimp/fast_atof.h> + +using namespace Assimp; +using namespace Assimp::ASE; + +static const aiImporterDesc desc = { + "ASE Importer", + "", + "", + "Similar to 3DS but text-encoded", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "ase ask" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +ASEImporter::ASEImporter() : + mParser(), mBuffer(), pcScene(), configRecomputeNormals(), noSkeletonMesh() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +ASEImporter::~ASEImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool ASEImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "*3dsmax_asciiexport" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Loader meta information +const aiImporterDesc *ASEImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration options +void ASEImporter::SetupProperties(const Importer *pImp) { + configRecomputeNormals = (pImp->GetPropertyInteger( + AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS, 1) ? + true : + false); + + noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, 0) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void ASEImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb")); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open ASE file ", pFile, "."); + } + + // Allocate storage and copy the contents of the file to a memory buffer + std::vector<char> mBuffer2; + TextFileToBuffer(file.get(), mBuffer2); + + this->mBuffer = &mBuffer2[0]; + this->pcScene = pScene; + + // ------------------------------------------------------------------ + // Guess the file format by looking at the extension + // ASC is considered to be the older format 110, + // ASE is the actual version 200 (that is currently written by max) + // ------------------------------------------------------------------ + unsigned int defaultFormat; + std::string::size_type s = pFile.length() - 1; + switch (pFile.c_str()[s]) { + + case 'C': + case 'c': + defaultFormat = AI_ASE_OLD_FILE_FORMAT; + break; + default: + defaultFormat = AI_ASE_NEW_FILE_FORMAT; + }; + + // Construct an ASE parser and parse the file + ASE::Parser parser(mBuffer, defaultFormat); + mParser = &parser; + mParser->Parse(); + + //------------------------------------------------------------------ + // Check whether we god at least one mesh. If we did - generate + // materials and copy meshes. + // ------------------------------------------------------------------ + if (!mParser->m_vMeshes.empty()) { + + // If absolutely no material has been loaded from the file + // we need to generate a default material + GenerateDefaultMaterial(); + + // process all meshes + bool tookNormals = false; + std::vector<aiMesh *> avOutMeshes; + avOutMeshes.reserve(mParser->m_vMeshes.size() * 2); + for (std::vector<ASE::Mesh>::iterator i = mParser->m_vMeshes.begin(); i != mParser->m_vMeshes.end(); ++i) { + if ((*i).bSkip) { + continue; + } + BuildUniqueRepresentation(*i); + + // Need to generate proper vertex normals if necessary + if (GenerateNormals(*i)) { + tookNormals = true; + } + + // Convert all meshes to aiMesh objects + ConvertMeshes(*i, avOutMeshes); + } + if (tookNormals) { + ASSIMP_LOG_DEBUG("ASE: Taking normals from the file. Use " + "the AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS setting if you " + "experience problems"); + } + + // Now build the output mesh list. Remove dummies + pScene->mNumMeshes = (unsigned int)avOutMeshes.size(); + aiMesh **pp = pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + for (std::vector<aiMesh *>::const_iterator i = avOutMeshes.begin(); i != avOutMeshes.end(); ++i) { + if (!(*i)->mNumFaces) { + continue; + } + *pp++ = *i; + } + pScene->mNumMeshes = (unsigned int)(pp - pScene->mMeshes); + + // Build final material indices (remove submaterials and setup + // the final list) + BuildMaterialIndices(); + } + + // ------------------------------------------------------------------ + // Copy all scene graph nodes - lights, cameras, dummies and meshes + // into one huge list. + //------------------------------------------------------------------ + std::vector<BaseNode *> nodes; + nodes.reserve(mParser->m_vMeshes.size() + mParser->m_vLights.size() + mParser->m_vCameras.size() + mParser->m_vDummies.size()); + + // Lights + for (auto &light : mParser->m_vLights) + nodes.push_back(&light); + // Cameras + for (auto &camera : mParser->m_vCameras) + nodes.push_back(&camera); + // Meshes + for (auto &mesh : mParser->m_vMeshes) + nodes.push_back(&mesh); + // Dummies + for (auto &dummy : mParser->m_vDummies) + nodes.push_back(&dummy); + + // build the final node graph + BuildNodes(nodes); + + // build output animations + BuildAnimations(nodes); + + // build output cameras + BuildCameras(); + + // build output lights + BuildLights(); + + // ------------------------------------------------------------------ + // If we have no meshes use the SkeletonMeshBuilder helper class + // to build a mesh for the animation skeleton + // FIXME: very strange results + // ------------------------------------------------------------------ + if (!pScene->mNumMeshes) { + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + if (!noSkeletonMesh) { + SkeletonMeshBuilder skeleton(pScene); + } + } +} +// ------------------------------------------------------------------------------------------------ +void ASEImporter::GenerateDefaultMaterial() { + ai_assert(nullptr != mParser); + + bool bHas = false; + for (std::vector<ASE::Mesh>::iterator i = mParser->m_vMeshes.begin(); i != mParser->m_vMeshes.end(); ++i) { + if ((*i).bSkip) continue; + if (ASE::Face::DEFAULT_MATINDEX == (*i).iMaterialIndex) { + (*i).iMaterialIndex = (unsigned int)mParser->m_vMaterials.size(); + bHas = true; + } + } + if (bHas || mParser->m_vMaterials.empty()) { + // add a simple material without submaterials to the parser's list + mParser->m_vMaterials.push_back(ASE::Material(AI_DEFAULT_MATERIAL_NAME)); + ASE::Material &mat = mParser->m_vMaterials.back(); + + mat.mDiffuse = aiColor3D(0.6f, 0.6f, 0.6f); + mat.mSpecular = aiColor3D(1.0f, 1.0f, 1.0f); + mat.mAmbient = aiColor3D(0.05f, 0.05f, 0.05f); + mat.mShading = Discreet3DS::Gouraud; + } +} + +// ------------------------------------------------------------------------------------------------ +void ASEImporter::BuildAnimations(const std::vector<BaseNode *> &nodes) { + // check whether we have at least one mesh which has animations + std::vector<ASE::BaseNode *>::const_iterator i = nodes.begin(); + unsigned int iNum = 0; + for (; i != nodes.end(); ++i) { + + // TODO: Implement Bezier & TCB support + if ((*i)->mAnim.mPositionType != ASE::Animation::TRACK) { + ASSIMP_LOG_WARN("ASE: Position controller uses Bezier/TCB keys. " + "This is not supported."); + } + if ((*i)->mAnim.mRotationType != ASE::Animation::TRACK) { + ASSIMP_LOG_WARN("ASE: Rotation controller uses Bezier/TCB keys. " + "This is not supported."); + } + if ((*i)->mAnim.mScalingType != ASE::Animation::TRACK) { + ASSIMP_LOG_WARN("ASE: Position controller uses Bezier/TCB keys. " + "This is not supported."); + } + + // We compare against 1 here - firstly one key is not + // really an animation and secondly MAX writes dummies + // that represent the node transformation. + if ((*i)->mAnim.akeyPositions.size() > 1 || (*i)->mAnim.akeyRotations.size() > 1 || (*i)->mAnim.akeyScaling.size() > 1) { + ++iNum; + } + if ((*i)->mTargetAnim.akeyPositions.size() > 1 && is_not_qnan((*i)->mTargetPosition.x)) { + ++iNum; + } + } + if (iNum) { + // Generate a new animation channel and setup everything for it + pcScene->mNumAnimations = 1; + pcScene->mAnimations = new aiAnimation *[1]; + aiAnimation *pcAnim = pcScene->mAnimations[0] = new aiAnimation(); + pcAnim->mNumChannels = iNum; + pcAnim->mChannels = new aiNodeAnim *[iNum]; + pcAnim->mTicksPerSecond = mParser->iFrameSpeed * mParser->iTicksPerFrame; + + iNum = 0; + + // Now iterate through all meshes and collect all data we can find + for (i = nodes.begin(); i != nodes.end(); ++i) { + + ASE::BaseNode *me = *i; + if (me->mTargetAnim.akeyPositions.size() > 1 && is_not_qnan(me->mTargetPosition.x)) { + // Generate an extra channel for the camera/light target. + // BuildNodes() does also generate an extra node, named + // <baseName>.Target. + aiNodeAnim *nd = pcAnim->mChannels[iNum++] = new aiNodeAnim(); + nd->mNodeName.Set(me->mName + ".Target"); + + // If there is no input position channel we will need + // to supply the default position from the node's + // local transformation matrix. + /*TargetAnimationHelper helper; + if (me->mAnim.akeyPositions.empty()) + { + aiMatrix4x4& mat = (*i)->mTransform; + helper.SetFixedMainAnimationChannel(aiVector3D( + mat.a4, mat.b4, mat.c4)); + } + else helper.SetMainAnimationChannel (&me->mAnim.akeyPositions); + helper.SetTargetAnimationChannel (&me->mTargetAnim.akeyPositions); + + helper.Process(&me->mTargetAnim.akeyPositions);*/ + + // Allocate the key array and fill it + nd->mNumPositionKeys = (unsigned int)me->mTargetAnim.akeyPositions.size(); + nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys]; + + ::memcpy(nd->mPositionKeys, &me->mTargetAnim.akeyPositions[0], + nd->mNumPositionKeys * sizeof(aiVectorKey)); + } + + if (me->mAnim.akeyPositions.size() > 1 || me->mAnim.akeyRotations.size() > 1 || me->mAnim.akeyScaling.size() > 1) { + // Begin a new node animation channel for this node + aiNodeAnim *nd = pcAnim->mChannels[iNum++] = new aiNodeAnim(); + nd->mNodeName.Set(me->mName); + + // copy position keys + if (me->mAnim.akeyPositions.size() > 1) { + // Allocate the key array and fill it + nd->mNumPositionKeys = (unsigned int)me->mAnim.akeyPositions.size(); + nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys]; + + ::memcpy(nd->mPositionKeys, &me->mAnim.akeyPositions[0], + nd->mNumPositionKeys * sizeof(aiVectorKey)); + } + // copy rotation keys + if (me->mAnim.akeyRotations.size() > 1) { + // Allocate the key array and fill it + nd->mNumRotationKeys = (unsigned int)me->mAnim.akeyRotations.size(); + nd->mRotationKeys = new aiQuatKey[nd->mNumRotationKeys]; + + // -------------------------------------------------------------------- + // Rotation keys are offsets to the previous keys. + // We have the quaternion representations of all + // of them, so we just need to concatenate all + // (unit-length) quaternions to get the absolute + // rotations. + // Rotation keys are ABSOLUTE for older files + // -------------------------------------------------------------------- + + aiQuaternion cur; + for (unsigned int a = 0; a < nd->mNumRotationKeys; ++a) { + aiQuatKey q = me->mAnim.akeyRotations[a]; + + if (mParser->iFileFormat > 110) { + cur = (a ? cur * q.mValue : q.mValue); + q.mValue = cur.Normalize(); + } + nd->mRotationKeys[a] = q; + + // need this to get to Assimp quaternion conventions + nd->mRotationKeys[a].mValue.w *= -1.f; + } + } + // copy scaling keys + if (me->mAnim.akeyScaling.size() > 1) { + // Allocate the key array and fill it + nd->mNumScalingKeys = (unsigned int)me->mAnim.akeyScaling.size(); + nd->mScalingKeys = new aiVectorKey[nd->mNumScalingKeys]; + + ::memcpy(nd->mScalingKeys, &me->mAnim.akeyScaling[0], + nd->mNumScalingKeys * sizeof(aiVectorKey)); + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Build output cameras +void ASEImporter::BuildCameras() { + if (!mParser->m_vCameras.empty()) { + pcScene->mNumCameras = (unsigned int)mParser->m_vCameras.size(); + pcScene->mCameras = new aiCamera *[pcScene->mNumCameras]; + + for (unsigned int i = 0; i < pcScene->mNumCameras; ++i) { + aiCamera *out = pcScene->mCameras[i] = new aiCamera(); + ASE::Camera &in = mParser->m_vCameras[i]; + + // copy members + out->mClipPlaneFar = in.mFar; + out->mClipPlaneNear = (in.mNear ? in.mNear : 0.1f); + out->mHorizontalFOV = in.mFOV; + + out->mName.Set(in.mName); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Build output lights +void ASEImporter::BuildLights() { + if (!mParser->m_vLights.empty()) { + pcScene->mNumLights = (unsigned int)mParser->m_vLights.size(); + pcScene->mLights = new aiLight *[pcScene->mNumLights]; + + for (unsigned int i = 0; i < pcScene->mNumLights; ++i) { + aiLight *out = pcScene->mLights[i] = new aiLight(); + ASE::Light &in = mParser->m_vLights[i]; + + // The direction is encoded in the transformation matrix of the node. + // In 3DS MAX the light source points into negative Z direction if + // the node transformation is the identity. + out->mDirection = aiVector3D(0.f, 0.f, -1.f); + + out->mName.Set(in.mName); + switch (in.mLightType) { + case ASE::Light::TARGET: + out->mType = aiLightSource_SPOT; + out->mAngleInnerCone = AI_DEG_TO_RAD(in.mAngle); + out->mAngleOuterCone = (in.mFalloff ? AI_DEG_TO_RAD(in.mFalloff) : out->mAngleInnerCone); + break; + + case ASE::Light::DIRECTIONAL: + out->mType = aiLightSource_DIRECTIONAL; + break; + + default: + //case ASE::Light::OMNI: + out->mType = aiLightSource_POINT; + break; + }; + out->mColorDiffuse = out->mColorSpecular = in.mColor * in.mIntensity; + } + } +} + +// ------------------------------------------------------------------------------------------------ +void ASEImporter::AddNodes(const std::vector<BaseNode *> &nodes, + aiNode *pcParent, const char *szName) { + aiMatrix4x4 m; + AddNodes(nodes, pcParent, szName, m); +} + +// ------------------------------------------------------------------------------------------------ +// Add meshes to a given node +void ASEImporter::AddMeshes(const ASE::BaseNode *snode, aiNode *node) { + for (unsigned int i = 0; i < pcScene->mNumMeshes; ++i) { + // Get the name of the mesh (the mesh instance has been temporarily stored in the third vertex color) + const aiMesh *pcMesh = pcScene->mMeshes[i]; + const ASE::Mesh *mesh = (const ASE::Mesh *)pcMesh->mColors[2]; + + if (mesh == snode) { + ++node->mNumMeshes; + } + } + + if (node->mNumMeshes) { + node->mMeshes = new unsigned int[node->mNumMeshes]; + for (unsigned int i = 0, p = 0; i < pcScene->mNumMeshes; ++i) { + + const aiMesh *pcMesh = pcScene->mMeshes[i]; + const ASE::Mesh *mesh = (const ASE::Mesh *)pcMesh->mColors[2]; + if (mesh == snode) { + node->mMeshes[p++] = i; + + // Transform all vertices of the mesh back into their local space -> + // at the moment they are pretransformed + aiMatrix4x4 m = mesh->mTransform; + m.Inverse(); + + aiVector3D *pvCurPtr = pcMesh->mVertices; + const aiVector3D *pvEndPtr = pvCurPtr + pcMesh->mNumVertices; + while (pvCurPtr != pvEndPtr) { + *pvCurPtr = m * (*pvCurPtr); + pvCurPtr++; + } + + // Do the same for the normal vectors, if we have them. + // As always, inverse transpose. + if (pcMesh->mNormals) { + aiMatrix3x3 m3 = aiMatrix3x3(mesh->mTransform); + m3.Transpose(); + + pvCurPtr = pcMesh->mNormals; + pvEndPtr = pvCurPtr + pcMesh->mNumVertices; + while (pvCurPtr != pvEndPtr) { + *pvCurPtr = m3 * (*pvCurPtr); + pvCurPtr++; + } + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Add child nodes to a given parent node +void ASEImporter::AddNodes(const std::vector<BaseNode *> &nodes, + aiNode *pcParent, const char *szName, + const aiMatrix4x4 &mat) { + const size_t len = szName ? ::strlen(szName) : 0; + ai_assert(4 <= AI_MAX_NUMBER_OF_COLOR_SETS); + + // Receives child nodes for the pcParent node + std::vector<aiNode *> apcNodes; + + // Now iterate through all nodes in the scene and search for one + // which has *us* as parent. + for (std::vector<BaseNode *>::const_iterator it = nodes.begin(), end = nodes.end(); it != end; ++it) { + const BaseNode *snode = *it; + if (szName) { + if (len != snode->mParent.length() || ::strcmp(szName, snode->mParent.c_str())) + continue; + } else if (snode->mParent.length()) + continue; + + (*it)->mProcessed = true; + + // Allocate a new node and add it to the output data structure + apcNodes.push_back(new aiNode()); + aiNode *node = apcNodes.back(); + + node->mName.Set((snode->mName.length() ? snode->mName.c_str() : "Unnamed_Node")); + node->mParent = pcParent; + + // Setup the transformation matrix of the node + aiMatrix4x4 mParentAdjust = mat; + mParentAdjust.Inverse(); + node->mTransformation = mParentAdjust * snode->mTransform; + + // Add sub nodes - prevent stack overflow due to recursive parenting + if (node->mName != node->mParent->mName && node->mName != node->mParent->mParent->mName) { + AddNodes(nodes, node, node->mName.data, snode->mTransform); + } + + // Further processing depends on the type of the node + if (snode->mType == ASE::BaseNode::Mesh) { + // If the type of this node is "Mesh" we need to search + // the list of output meshes in the data structure for + // all those that belonged to this node once. This is + // slightly inconvinient here and a better solution should + // be used when this code is refactored next. + AddMeshes(snode, node); + } else if (is_not_qnan(snode->mTargetPosition.x)) { + // If this is a target camera or light we generate a small + // child node which marks the position of the camera + // target (the direction information is contained in *this* + // node's animation track but the exact target position + // would be lost otherwise) + if (!node->mNumChildren) { + node->mChildren = new aiNode *[1]; + } + + aiNode *nd = new aiNode(); + + nd->mName.Set(snode->mName + ".Target"); + + nd->mTransformation.a4 = snode->mTargetPosition.x - snode->mTransform.a4; + nd->mTransformation.b4 = snode->mTargetPosition.y - snode->mTransform.b4; + nd->mTransformation.c4 = snode->mTargetPosition.z - snode->mTransform.c4; + + nd->mParent = node; + + // The .Target node is always the first child node + for (unsigned int m = 0; m < node->mNumChildren; ++m) + node->mChildren[m + 1] = node->mChildren[m]; + + node->mChildren[0] = nd; + node->mNumChildren++; + + // What we did is so great, it is at least worth a debug message + ASSIMP_LOG_VERBOSE_DEBUG("ASE: Generating separate target node (", snode->mName, ")"); + } + } + + // Allocate enough space for the child nodes + // We allocate one slot more in case this is a target camera/light + pcParent->mNumChildren = (unsigned int)apcNodes.size(); + if (pcParent->mNumChildren) { + pcParent->mChildren = new aiNode *[apcNodes.size() + 1 /* PLUS ONE !!! */]; + + // now build all nodes for our nice new children + for (unsigned int p = 0; p < apcNodes.size(); ++p) + pcParent->mChildren[p] = apcNodes[p]; + } + return; +} + +// ------------------------------------------------------------------------------------------------ +// Build the output node graph +void ASEImporter::BuildNodes(std::vector<BaseNode *> &nodes) { + ai_assert(nullptr != pcScene); + + // allocate the one and only root node + aiNode *root = pcScene->mRootNode = new aiNode(); + root->mName.Set("<ASERoot>"); + + // Setup the coordinate system transformation + pcScene->mRootNode->mNumChildren = 1; + pcScene->mRootNode->mChildren = new aiNode *[1]; + aiNode *ch = pcScene->mRootNode->mChildren[0] = new aiNode(); + ch->mParent = root; + + // Change the transformation matrix of all nodes + for (BaseNode *node : nodes) { + aiMatrix4x4 &m = node->mTransform; + m.Transpose(); // row-order vs column-order + } + + // add all nodes + AddNodes(nodes, ch, nullptr); + + // now iterate through al nodes and find those that have not yet + // been added to the nodegraph (= their parent could not be recognized) + std::vector<const BaseNode *> aiList; + for (std::vector<BaseNode *>::iterator it = nodes.begin(), end = nodes.end(); it != end; ++it) { + if ((*it)->mProcessed) { + continue; + } + + // check whether our parent is known + bool bKnowParent = false; + + // search the list another time, starting *here* and try to find out whether + // there is a node that references *us* as a parent + for (std::vector<BaseNode *>::const_iterator it2 = nodes.begin(); it2 != end; ++it2) { + if (it2 == it) { + continue; + } + + if ((*it2)->mName == (*it)->mParent) { + bKnowParent = true; + break; + } + } + if (!bKnowParent) { + aiList.push_back(*it); + } + } + + // Are there any orphaned nodes? + if (!aiList.empty()) { + std::vector<aiNode *> apcNodes; + apcNodes.reserve(aiList.size() + pcScene->mRootNode->mNumChildren); + + for (unsigned int i = 0; i < pcScene->mRootNode->mNumChildren; ++i) + apcNodes.push_back(pcScene->mRootNode->mChildren[i]); + + delete[] pcScene->mRootNode->mChildren; + for (std::vector<const BaseNode *>::/*const_*/ iterator i = aiList.begin(); i != aiList.end(); ++i) { + const ASE::BaseNode *src = *i; + + // The parent is not known, so we can assume that we must add + // this node to the root node of the whole scene + aiNode *pcNode = new aiNode(); + pcNode->mParent = pcScene->mRootNode; + pcNode->mName.Set(src->mName); + AddMeshes(src, pcNode); + AddNodes(nodes, pcNode, pcNode->mName.data); + apcNodes.push_back(pcNode); + } + + // Regenerate our output array + pcScene->mRootNode->mChildren = new aiNode *[apcNodes.size()]; + for (unsigned int i = 0; i < apcNodes.size(); ++i) + pcScene->mRootNode->mChildren[i] = apcNodes[i]; + + pcScene->mRootNode->mNumChildren = (unsigned int)apcNodes.size(); + } + + // Reset the third color set to nullptr - we used this field to store a temporary pointer + for (unsigned int i = 0; i < pcScene->mNumMeshes; ++i) + pcScene->mMeshes[i]->mColors[2] = nullptr; + + // The root node should not have at least one child or the file is valid + if (!pcScene->mRootNode->mNumChildren) { + throw DeadlyImportError("ASE: No nodes loaded. The file is either empty or corrupt"); + } + + // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system + pcScene->mRootNode->mTransformation = aiMatrix4x4(1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, 0.f, -1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f); +} + +// ------------------------------------------------------------------------------------------------ +// Convert the imported data to the internal verbose representation +void ASEImporter::BuildUniqueRepresentation(ASE::Mesh &mesh) { + // allocate output storage + std::vector<aiVector3D> mPositions; + std::vector<aiVector3D> amTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + std::vector<aiColor4D> mVertexColors; + std::vector<aiVector3D> mNormals; + std::vector<BoneVertex> mBoneVertices; + + unsigned int iSize = (unsigned int)mesh.mFaces.size() * 3; + mPositions.resize(iSize); + + // optional texture coordinates + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (!mesh.amTexCoords[i].empty()) { + amTexCoords[i].resize(iSize); + } + } + // optional vertex colors + if (!mesh.mVertexColors.empty()) { + mVertexColors.resize(iSize); + } + + // optional vertex normals (vertex normals can simply be copied) + if (!mesh.mNormals.empty()) { + mNormals.resize(iSize); + } + // bone vertices. There is no need to change the bone list + if (!mesh.mBoneVertices.empty()) { + mBoneVertices.resize(iSize); + } + + // iterate through all faces in the mesh + unsigned int iCurrent = 0, fi = 0; + for (std::vector<ASE::Face>::iterator i = mesh.mFaces.begin(); i != mesh.mFaces.end(); ++i, ++fi) { + for (unsigned int n = 0; n < 3; ++n, ++iCurrent) { + mPositions[iCurrent] = mesh.mPositions[(*i).mIndices[n]]; + + // add texture coordinates + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c) { + if (mesh.amTexCoords[c].empty()) break; + amTexCoords[c][iCurrent] = mesh.amTexCoords[c][(*i).amUVIndices[c][n]]; + } + // add vertex colors + if (!mesh.mVertexColors.empty()) { + mVertexColors[iCurrent] = mesh.mVertexColors[(*i).mColorIndices[n]]; + } + // add normal vectors + if (!mesh.mNormals.empty()) { + mNormals[iCurrent] = mesh.mNormals[fi * 3 + n]; + mNormals[iCurrent].Normalize(); + } + + // handle bone vertices + if ((*i).mIndices[n] < mesh.mBoneVertices.size()) { + // (sometimes this will cause bone verts to be duplicated + // however, I' quite sure Schrompf' JoinVerticesStep + // will fix that again ...) + mBoneVertices[iCurrent] = mesh.mBoneVertices[(*i).mIndices[n]]; + } + (*i).mIndices[n] = iCurrent; + } + } + + // replace the old arrays + mesh.mNormals = mNormals; + mesh.mPositions = mPositions; + mesh.mVertexColors = mVertexColors; + + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c) + mesh.amTexCoords[c] = amTexCoords[c]; +} + +// ------------------------------------------------------------------------------------------------ +// Copy a texture from the ASE structs to the output material +void CopyASETexture(aiMaterial &mat, ASE::Texture &texture, aiTextureType type) { + // Setup the texture name + aiString tex; + tex.Set(texture.mMapName); + mat.AddProperty(&tex, AI_MATKEY_TEXTURE(type, 0)); + + // Setup the texture blend factor + if (is_not_qnan(texture.mTextureBlend)) + mat.AddProperty<ai_real>(&texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type, 0)); + + // Setup texture UV transformations + mat.AddProperty<ai_real>(&texture.mOffsetU, 5, AI_MATKEY_UVTRANSFORM(type, 0)); +} + +// ------------------------------------------------------------------------------------------------ +// Convert from ASE material to output material +void ASEImporter::ConvertMaterial(ASE::Material &mat) { + // LARGE TODO: Much code her is copied from 3DS ... join them maybe? + + // Allocate the output material + mat.pcInstance = new aiMaterial(); + + // At first add the base ambient color of the + // scene to the material + mat.mAmbient.r += mParser->m_clrAmbient.r; + mat.mAmbient.g += mParser->m_clrAmbient.g; + mat.mAmbient.b += mParser->m_clrAmbient.b; + + aiString name; + name.Set(mat.mName); + mat.pcInstance->AddProperty(&name, AI_MATKEY_NAME); + + // material colors + mat.pcInstance->AddProperty(&mat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT); + mat.pcInstance->AddProperty(&mat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + mat.pcInstance->AddProperty(&mat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); + mat.pcInstance->AddProperty(&mat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); + + // shininess + if (0.0f != mat.mSpecularExponent && 0.0f != mat.mShininessStrength) { + mat.pcInstance->AddProperty(&mat.mSpecularExponent, 1, AI_MATKEY_SHININESS); + mat.pcInstance->AddProperty(&mat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH); + } + // If there is no shininess, we can disable phong lighting + else if (D3DS::Discreet3DS::Metal == mat.mShading || + D3DS::Discreet3DS::Phong == mat.mShading || + D3DS::Discreet3DS::Blinn == mat.mShading) { + mat.mShading = D3DS::Discreet3DS::Gouraud; + } + + // opacity + mat.pcInstance->AddProperty<ai_real>(&mat.mTransparency, 1, AI_MATKEY_OPACITY); + + // Two sided rendering? + if (mat.mTwoSided) { + int i = 1; + mat.pcInstance->AddProperty<int>(&i, 1, AI_MATKEY_TWOSIDED); + } + + // shading mode + aiShadingMode eShading = aiShadingMode_NoShading; + switch (mat.mShading) { + case D3DS::Discreet3DS::Flat: + eShading = aiShadingMode_Flat; + break; + case D3DS::Discreet3DS::Phong: + eShading = aiShadingMode_Phong; + break; + case D3DS::Discreet3DS::Blinn: + eShading = aiShadingMode_Blinn; + break; + + // I don't know what "Wire" shading should be, + // assume it is simple lambertian diffuse (L dot N) shading + case D3DS::Discreet3DS::Wire: { + // set the wireframe flag + unsigned int iWire = 1; + mat.pcInstance->AddProperty<int>((int *)&iWire, 1, AI_MATKEY_ENABLE_WIREFRAME); + } + case D3DS::Discreet3DS::Gouraud: + eShading = aiShadingMode_Gouraud; + break; + case D3DS::Discreet3DS::Metal: + eShading = aiShadingMode_CookTorrance; + break; + } + mat.pcInstance->AddProperty<int>((int *)&eShading, 1, AI_MATKEY_SHADING_MODEL); + + // DIFFUSE texture + if (mat.sTexDiffuse.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance, mat.sTexDiffuse, aiTextureType_DIFFUSE); + + // SPECULAR texture + if (mat.sTexSpecular.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance, mat.sTexSpecular, aiTextureType_SPECULAR); + + // AMBIENT texture + if (mat.sTexAmbient.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance, mat.sTexAmbient, aiTextureType_AMBIENT); + + // OPACITY texture + if (mat.sTexOpacity.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance, mat.sTexOpacity, aiTextureType_OPACITY); + + // EMISSIVE texture + if (mat.sTexEmissive.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance, mat.sTexEmissive, aiTextureType_EMISSIVE); + + // BUMP texture + if (mat.sTexBump.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance, mat.sTexBump, aiTextureType_HEIGHT); + + // SHININESS texture + if (mat.sTexShininess.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance, mat.sTexShininess, aiTextureType_SHININESS); + + // store the name of the material itself, too + if (mat.mName.length() > 0) { + aiString tex; + tex.Set(mat.mName); + mat.pcInstance->AddProperty(&tex, AI_MATKEY_NAME); + } + return; +} + +// ------------------------------------------------------------------------------------------------ +// Build output meshes +void ASEImporter::ConvertMeshes(ASE::Mesh &mesh, std::vector<aiMesh *> &avOutMeshes) { + // validate the material index of the mesh + if (mesh.iMaterialIndex >= mParser->m_vMaterials.size()) { + mesh.iMaterialIndex = (unsigned int)mParser->m_vMaterials.size() - 1; + ASSIMP_LOG_WARN("Material index is out of range"); + } + + // If the material the mesh is assigned to is consisting of submeshes, split it + if (!mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials.empty()) { + std::vector<ASE::Material> vSubMaterials = mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials; + + std::vector<unsigned int> *aiSplit = new std::vector<unsigned int>[vSubMaterials.size()]; + + // build a list of all faces per sub-material + for (unsigned int i = 0; i < mesh.mFaces.size(); ++i) { + // check range + if (mesh.mFaces[i].iMaterial >= vSubMaterials.size()) { + ASSIMP_LOG_WARN("Submaterial index is out of range"); + + // use the last material instead + aiSplit[vSubMaterials.size() - 1].push_back(i); + } else + aiSplit[mesh.mFaces[i].iMaterial].push_back(i); + } + + // now generate submeshes + for (unsigned int p = 0; p < vSubMaterials.size(); ++p) { + if (!aiSplit[p].empty()) { + + aiMesh *p_pcOut = new aiMesh(); + p_pcOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + // let the sub material index + p_pcOut->mMaterialIndex = p; + + // we will need this material + mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials[p].bNeed = true; + + // store the real index here ... color channel 3 + p_pcOut->mColors[3] = (aiColor4D *)(uintptr_t)mesh.iMaterialIndex; + + // store a pointer to the mesh in color channel 2 + p_pcOut->mColors[2] = (aiColor4D *)&mesh; + avOutMeshes.push_back(p_pcOut); + + // convert vertices + p_pcOut->mNumVertices = (unsigned int)aiSplit[p].size() * 3; + p_pcOut->mNumFaces = (unsigned int)aiSplit[p].size(); + + // receive output vertex weights + std::vector<std::pair<unsigned int, float>> *avOutputBones = nullptr; + if (!mesh.mBones.empty()) { + avOutputBones = new std::vector<std::pair<unsigned int, float>>[mesh.mBones.size()]; + } + + // allocate enough storage for faces + p_pcOut->mFaces = new aiFace[p_pcOut->mNumFaces]; + + unsigned int iBase = 0, iIndex; + if (p_pcOut->mNumVertices) { + p_pcOut->mVertices = new aiVector3D[p_pcOut->mNumVertices]; + p_pcOut->mNormals = new aiVector3D[p_pcOut->mNumVertices]; + for (unsigned int q = 0; q < aiSplit[p].size(); ++q) { + + iIndex = aiSplit[p][q]; + + p_pcOut->mFaces[q].mIndices = new unsigned int[3]; + p_pcOut->mFaces[q].mNumIndices = 3; + + for (unsigned int t = 0; t < 3; ++t, ++iBase) { + const uint32_t iIndex2 = mesh.mFaces[iIndex].mIndices[t]; + + p_pcOut->mVertices[iBase] = mesh.mPositions[iIndex2]; + p_pcOut->mNormals[iBase] = mesh.mNormals[iIndex2]; + + // convert bones, if existing + if (!mesh.mBones.empty()) { + ai_assert(avOutputBones); + // check whether there is a vertex weight for this vertex index + if (iIndex2 < mesh.mBoneVertices.size()) { + + for (std::vector<std::pair<int, float>>::const_iterator + blubb = mesh.mBoneVertices[iIndex2].mBoneWeights.begin(); + blubb != mesh.mBoneVertices[iIndex2].mBoneWeights.end(); ++blubb) { + + // NOTE: illegal cases have already been filtered out + avOutputBones[(*blubb).first].push_back(std::pair<unsigned int, float>( + iBase, (*blubb).second)); + } + } + } + p_pcOut->mFaces[q].mIndices[t] = iBase; + } + } + } + // convert texture coordinates (up to AI_MAX_NUMBER_OF_TEXTURECOORDS sets supported) + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c) { + if (!mesh.amTexCoords[c].empty()) { + p_pcOut->mTextureCoords[c] = new aiVector3D[p_pcOut->mNumVertices]; + iBase = 0; + for (unsigned int q = 0; q < aiSplit[p].size(); ++q) { + iIndex = aiSplit[p][q]; + for (unsigned int t = 0; t < 3; ++t) { + p_pcOut->mTextureCoords[c][iBase++] = mesh.amTexCoords[c][mesh.mFaces[iIndex].mIndices[t]]; + } + } + // Setup the number of valid vertex components + p_pcOut->mNumUVComponents[c] = mesh.mNumUVComponents[c]; + } + } + + // Convert vertex colors (only one set supported) + if (!mesh.mVertexColors.empty()) { + p_pcOut->mColors[0] = new aiColor4D[p_pcOut->mNumVertices]; + iBase = 0; + for (unsigned int q = 0; q < aiSplit[p].size(); ++q) { + iIndex = aiSplit[p][q]; + for (unsigned int t = 0; t < 3; ++t) { + p_pcOut->mColors[0][iBase++] = mesh.mVertexColors[mesh.mFaces[iIndex].mIndices[t]]; + } + } + } + // Copy bones + if (!mesh.mBones.empty()) { + p_pcOut->mNumBones = 0; + for (unsigned int mrspock = 0; mrspock < mesh.mBones.size(); ++mrspock) + if (!avOutputBones[mrspock].empty()) p_pcOut->mNumBones++; + + p_pcOut->mBones = new aiBone *[p_pcOut->mNumBones]; + aiBone **pcBone = p_pcOut->mBones; + for (unsigned int mrspock = 0; mrspock < mesh.mBones.size(); ++mrspock) { + if (!avOutputBones[mrspock].empty()) { + // we will need this bone. add it to the output mesh and + // add all per-vertex weights + aiBone *pc = *pcBone = new aiBone(); + pc->mName.Set(mesh.mBones[mrspock].mName); + + pc->mNumWeights = (unsigned int)avOutputBones[mrspock].size(); + pc->mWeights = new aiVertexWeight[pc->mNumWeights]; + + for (unsigned int captainkirk = 0; captainkirk < pc->mNumWeights; ++captainkirk) { + const std::pair<unsigned int, float> &ref = avOutputBones[mrspock][captainkirk]; + pc->mWeights[captainkirk].mVertexId = ref.first; + pc->mWeights[captainkirk].mWeight = ref.second; + } + ++pcBone; + } + } + // delete allocated storage + delete[] avOutputBones; + } + } + } + // delete storage + delete[] aiSplit; + } else { + // Otherwise we can simply copy the data to one output mesh + // This codepath needs less memory and uses fast memcpy()s + // to do the actual copying. So I think it is worth the + // effort here. + + aiMesh *p_pcOut = new aiMesh(); + p_pcOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + // set an empty sub material index + p_pcOut->mMaterialIndex = ASE::Face::DEFAULT_MATINDEX; + mParser->m_vMaterials[mesh.iMaterialIndex].bNeed = true; + + // store the real index here ... in color channel 3 + p_pcOut->mColors[3] = (aiColor4D *)(uintptr_t)mesh.iMaterialIndex; + + // store a pointer to the mesh in color channel 2 + p_pcOut->mColors[2] = (aiColor4D *)&mesh; + avOutMeshes.push_back(p_pcOut); + + // If the mesh hasn't faces or vertices, there are two cases + // possible: 1. the model is invalid. 2. This is a dummy + // helper object which we are going to remove later ... + if (mesh.mFaces.empty() || mesh.mPositions.empty()) { + return; + } + + // convert vertices + p_pcOut->mNumVertices = (unsigned int)mesh.mPositions.size(); + p_pcOut->mNumFaces = (unsigned int)mesh.mFaces.size(); + + // allocate enough storage for faces + p_pcOut->mFaces = new aiFace[p_pcOut->mNumFaces]; + + // copy vertices + p_pcOut->mVertices = new aiVector3D[mesh.mPositions.size()]; + memcpy(p_pcOut->mVertices, &mesh.mPositions[0], + mesh.mPositions.size() * sizeof(aiVector3D)); + + // copy normals + p_pcOut->mNormals = new aiVector3D[mesh.mNormals.size()]; + memcpy(p_pcOut->mNormals, &mesh.mNormals[0], + mesh.mNormals.size() * sizeof(aiVector3D)); + + // copy texture coordinates + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c) { + if (!mesh.amTexCoords[c].empty()) { + p_pcOut->mTextureCoords[c] = new aiVector3D[mesh.amTexCoords[c].size()]; + memcpy(p_pcOut->mTextureCoords[c], &mesh.amTexCoords[c][0], + mesh.amTexCoords[c].size() * sizeof(aiVector3D)); + + // setup the number of valid vertex components + p_pcOut->mNumUVComponents[c] = mesh.mNumUVComponents[c]; + } + } + + // copy vertex colors + if (!mesh.mVertexColors.empty()) { + p_pcOut->mColors[0] = new aiColor4D[mesh.mVertexColors.size()]; + memcpy(p_pcOut->mColors[0], &mesh.mVertexColors[0], + mesh.mVertexColors.size() * sizeof(aiColor4D)); + } + + // copy faces + for (unsigned int iFace = 0; iFace < p_pcOut->mNumFaces; ++iFace) { + p_pcOut->mFaces[iFace].mNumIndices = 3; + p_pcOut->mFaces[iFace].mIndices = new unsigned int[3]; + + // copy indices + p_pcOut->mFaces[iFace].mIndices[0] = mesh.mFaces[iFace].mIndices[0]; + p_pcOut->mFaces[iFace].mIndices[1] = mesh.mFaces[iFace].mIndices[1]; + p_pcOut->mFaces[iFace].mIndices[2] = mesh.mFaces[iFace].mIndices[2]; + } + + // copy vertex bones + if (!mesh.mBones.empty() && !mesh.mBoneVertices.empty()) { + std::vector<std::vector<aiVertexWeight>> avBonesOut(mesh.mBones.size()); + + // find all vertex weights for this bone + unsigned int quak = 0; + for (std::vector<BoneVertex>::const_iterator harrypotter = mesh.mBoneVertices.begin(); + harrypotter != mesh.mBoneVertices.end(); ++harrypotter, ++quak) { + + for (std::vector<std::pair<int, float>>::const_iterator + ronaldweasley = (*harrypotter).mBoneWeights.begin(); + ronaldweasley != (*harrypotter).mBoneWeights.end(); ++ronaldweasley) { + aiVertexWeight weight; + weight.mVertexId = quak; + weight.mWeight = (*ronaldweasley).second; + avBonesOut[(*ronaldweasley).first].push_back(weight); + } + } + + // now build a final bone list + p_pcOut->mNumBones = 0; + for (unsigned int jfkennedy = 0; jfkennedy < mesh.mBones.size(); ++jfkennedy) + if (!avBonesOut[jfkennedy].empty()) p_pcOut->mNumBones++; + + p_pcOut->mBones = new aiBone *[p_pcOut->mNumBones]; + aiBone **pcBone = p_pcOut->mBones; + for (unsigned int jfkennedy = 0; jfkennedy < mesh.mBones.size(); ++jfkennedy) { + if (!avBonesOut[jfkennedy].empty()) { + aiBone *pc = *pcBone = new aiBone(); + pc->mName.Set(mesh.mBones[jfkennedy].mName); + pc->mNumWeights = (unsigned int)avBonesOut[jfkennedy].size(); + pc->mWeights = new aiVertexWeight[pc->mNumWeights]; + ::memcpy(pc->mWeights, &avBonesOut[jfkennedy][0], + sizeof(aiVertexWeight) * pc->mNumWeights); + ++pcBone; + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Setup proper material indices and build output materials +void ASEImporter::BuildMaterialIndices() { + ai_assert(nullptr != pcScene); + + // iterate through all materials and check whether we need them + for (unsigned int iMat = 0; iMat < mParser->m_vMaterials.size(); ++iMat) { + ASE::Material &mat = mParser->m_vMaterials[iMat]; + if (mat.bNeed) { + // Convert it to the aiMaterial layout + ConvertMaterial(mat); + ++pcScene->mNumMaterials; + } + for (unsigned int iSubMat = 0; iSubMat < mat.avSubMaterials.size(); ++iSubMat) { + ASE::Material &submat = mat.avSubMaterials[iSubMat]; + if (submat.bNeed) { + // Convert it to the aiMaterial layout + ConvertMaterial(submat); + ++pcScene->mNumMaterials; + } + } + } + + // allocate the output material array + pcScene->mMaterials = new aiMaterial *[pcScene->mNumMaterials]; + D3DS::Material **pcIntMaterials = new D3DS::Material *[pcScene->mNumMaterials]; + + unsigned int iNum = 0; + for (unsigned int iMat = 0; iMat < mParser->m_vMaterials.size(); ++iMat) { + ASE::Material &mat = mParser->m_vMaterials[iMat]; + if (mat.bNeed) { + ai_assert(nullptr != mat.pcInstance); + pcScene->mMaterials[iNum] = mat.pcInstance; + + // Store the internal material, too + pcIntMaterials[iNum] = &mat; + + // Iterate through all meshes and search for one which is using + // this top-level material index + for (unsigned int iMesh = 0; iMesh < pcScene->mNumMeshes; ++iMesh) { + aiMesh *mesh = pcScene->mMeshes[iMesh]; + if (ASE::Face::DEFAULT_MATINDEX == mesh->mMaterialIndex && + iMat == (uintptr_t)mesh->mColors[3]) { + mesh->mMaterialIndex = iNum; + mesh->mColors[3] = nullptr; + } + } + iNum++; + } + for (unsigned int iSubMat = 0; iSubMat < mat.avSubMaterials.size(); ++iSubMat) { + ASE::Material &submat = mat.avSubMaterials[iSubMat]; + if (submat.bNeed) { + ai_assert(nullptr != submat.pcInstance); + pcScene->mMaterials[iNum] = submat.pcInstance; + + // Store the internal material, too + pcIntMaterials[iNum] = &submat; + + // Iterate through all meshes and search for one which is using + // this sub-level material index + for (unsigned int iMesh = 0; iMesh < pcScene->mNumMeshes; ++iMesh) { + aiMesh *mesh = pcScene->mMeshes[iMesh]; + + if (iSubMat == mesh->mMaterialIndex && iMat == (uintptr_t)mesh->mColors[3]) { + mesh->mMaterialIndex = iNum; + mesh->mColors[3] = nullptr; + } + } + iNum++; + } + } + } + + // Delete our temporary array + delete[] pcIntMaterials; +} + +// ------------------------------------------------------------------------------------------------ +// Generate normal vectors basing on smoothing groups +bool ASEImporter::GenerateNormals(ASE::Mesh &mesh) { + + if (!mesh.mNormals.empty() && !configRecomputeNormals) { + // Check whether there are only uninitialized normals. If there are + // some, skip all normals from the file and compute them on our own + for (std::vector<aiVector3D>::const_iterator qq = mesh.mNormals.begin(); qq != mesh.mNormals.end(); ++qq) { + if ((*qq).x || (*qq).y || (*qq).z) { + return true; + } + } + } + // The array is reused. + ComputeNormalsWithSmoothingsGroups<ASE::Face>(mesh); + return false; +} + +#endif // ASSIMP_BUILD_NO_3DS_IMPORTER + +#endif // !! ASSIMP_BUILD_NO_BASE_IMPORTER diff --git a/libs/assimp/code/AssetLib/ASE/ASELoader.h b/libs/assimp/code/AssetLib/ASE/ASELoader.h new file mode 100644 index 0000000..cd91235 --- /dev/null +++ b/libs/assimp/code/AssetLib/ASE/ASELoader.h @@ -0,0 +1,192 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 ASELoader.h + * @brief Definition of the .ASE importer class. + */ +#ifndef AI_ASELOADER_H_INCLUDED +#define AI_ASELOADER_H_INCLUDED + +#include <assimp/BaseImporter.h> +#include <assimp/types.h> +#include "ASEParser.h" + +struct aiNode; + +namespace Assimp { + +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +// -------------------------------------------------------------------------------- +/** Importer class for the 3DS ASE ASCII format. + * + */ +class ASEImporter : public BaseImporter { +public: + ASEImporter(); + ~ASEImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc* GetInfo () const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler) override; + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer* pImp) override; + +private: + // ------------------------------------------------------------------- + /** Generate normal vectors basing on smoothing groups + * (in some cases the normal are already contained in the file) + * \param mesh Mesh to work on + * \return false if the normals have been recomputed + */ + bool GenerateNormals(ASE::Mesh& mesh); + + // ------------------------------------------------------------------- + /** Create valid vertex/normal/UV/color/face lists. + * All elements are unique, faces have only one set of indices + * after this step occurs. + * \param mesh Mesh to work on + */ + void BuildUniqueRepresentation(ASE::Mesh& mesh); + + /** Create one-material-per-mesh meshes ;-) + * \param mesh Mesh to work with + * \param Receives the list of all created meshes + */ + void ConvertMeshes(ASE::Mesh& mesh, std::vector<aiMesh*>& avOut); + + // ------------------------------------------------------------------- + /** Convert a material to a aiMaterial object + * \param mat Input material + */ + void ConvertMaterial(ASE::Material& mat); + + // ------------------------------------------------------------------- + /** Setup the final material indices for each mesh + */ + void BuildMaterialIndices(); + + // ------------------------------------------------------------------- + /** Build the node graph + */ + void BuildNodes(std::vector<ASE::BaseNode*>& nodes); + + // ------------------------------------------------------------------- + /** Build output cameras + */ + void BuildCameras(); + + // ------------------------------------------------------------------- + /** Build output lights + */ + void BuildLights(); + + // ------------------------------------------------------------------- + /** Build output animations + */ + void BuildAnimations(const std::vector<ASE::BaseNode*>& nodes); + + // ------------------------------------------------------------------- + /** Add sub nodes to a node + * \param pcParent parent node to be filled + * \param szName Name of the parent node + * \param matrix Current transform + */ + void AddNodes(const std::vector<ASE::BaseNode*>& nodes, + aiNode* pcParent,const char* szName); + + void AddNodes(const std::vector<ASE::BaseNode*>& nodes, + aiNode* pcParent,const char* szName, + const aiMatrix4x4& matrix); + + void AddMeshes(const ASE::BaseNode* snode,aiNode* node); + + // ------------------------------------------------------------------- + /** Generate a default material and add it to the parser's list + * Called if no material has been found in the file (rare for ASE, + * but not impossible) + */ + void GenerateDefaultMaterial(); + +protected: + /** Parser instance */ + ASE::Parser* mParser; + + /** Buffer to hold the loaded file */ + char* mBuffer; + + /** Scene to be filled */ + aiScene* pcScene; + + /** Config options: Recompute the normals in every case - WA + for 3DS Max broken ASE normal export */ + bool configRecomputeNormals; + bool noSkeletonMesh; +}; + +#endif // ASSIMP_BUILD_NO_3DS_IMPORTER + +} // end of namespace Assimp + + +#endif // AI_3DSIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/ASE/ASEParser.cpp b/libs/assimp/code/AssetLib/ASE/ASEParser.cpp new file mode 100644 index 0000000..14eb720 --- /dev/null +++ b/libs/assimp/code/AssetLib/ASE/ASEParser.cpp @@ -0,0 +1,1869 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 ASEParser.cpp + * @brief Implementation of the ASE parser class + */ + +#ifndef ASSIMP_BUILD_NO_ASE_IMPORTER +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +// internal headers +#include "ASELoader.h" +#include "PostProcessing/TextureTransform.h" + +#include <assimp/fast_atof.h> +#include <assimp/DefaultLogger.hpp> + +using namespace Assimp; +using namespace Assimp::ASE; + +// ------------------------------------------------------------------------------------------------ +// Begin an ASE parsing function + +#define AI_ASE_PARSER_INIT() \ + int iDepth = 0; + +// ------------------------------------------------------------------------------------------------ +// Handle a "top-level" section in the file. EOF is no error in this case. + +#define AI_ASE_HANDLE_TOP_LEVEL_SECTION() \ + else if ('{' == *filePtr) iDepth++; \ + else if ('}' == *filePtr) { \ + if (0 == --iDepth) { \ + ++filePtr; \ + SkipToNextToken(); \ + return; \ + } \ + } \ + else if ('\0' == *filePtr) { \ + return; \ + } \ + if (IsLineEnd(*filePtr) && !bLastWasEndLine) { \ + ++iLineNumber; \ + bLastWasEndLine = true; \ + } else \ + bLastWasEndLine = false; \ + ++filePtr; + +// ------------------------------------------------------------------------------------------------ +// Handle a nested section in the file. EOF is an error in this case +// @param level "Depth" of the section +// @param msg Full name of the section (including the asterisk) + +#define AI_ASE_HANDLE_SECTION(level, msg) \ + if ('{' == *filePtr) \ + iDepth++; \ + else if ('}' == *filePtr) { \ + if (0 == --iDepth) { \ + ++filePtr; \ + SkipToNextToken(); \ + return; \ + } \ + } else if ('\0' == *filePtr) { \ + LogError("Encountered unexpected EOL while parsing a " msg \ + " chunk (Level " level ")"); \ + } \ + if (IsLineEnd(*filePtr) && !bLastWasEndLine) { \ + ++iLineNumber; \ + bLastWasEndLine = true; \ + } else \ + bLastWasEndLine = false; \ + ++filePtr; + +// ------------------------------------------------------------------------------------------------ +Parser::Parser(const char *szFile, unsigned int fileFormatDefault) { + ai_assert(nullptr != szFile); + + filePtr = szFile; + iFileFormat = fileFormatDefault; + + // make sure that the color values are invalid + m_clrBackground.r = get_qnan(); + m_clrAmbient.r = get_qnan(); + + // setup some default values + iLineNumber = 0; + iFirstFrame = 0; + iLastFrame = 0; + iFrameSpeed = 30; // use 30 as default value for this property + iTicksPerFrame = 1; // use 1 as default value for this property + bLastWasEndLine = false; // need to handle \r\n seqs due to binary file mapping +} + +// ------------------------------------------------------------------------------------------------ +void Parser::LogWarning(const char *szWarn) { + ai_assert(nullptr != szWarn); + + char szTemp[2048]; +#if _MSC_VER >= 1400 + sprintf_s(szTemp, "Line %u: %s", iLineNumber, szWarn); +#else + ai_snprintf(szTemp, sizeof(szTemp), "Line %u: %s", iLineNumber, szWarn); +#endif + + // output the warning to the logger ... + ASSIMP_LOG_WARN(szTemp); +} + +// ------------------------------------------------------------------------------------------------ +void Parser::LogInfo(const char *szWarn) { + ai_assert(nullptr != szWarn); + + char szTemp[1024]; +#if _MSC_VER >= 1400 + sprintf_s(szTemp, "Line %u: %s", iLineNumber, szWarn); +#else + ai_snprintf(szTemp, 1024, "Line %u: %s", iLineNumber, szWarn); +#endif + + // output the information to the logger ... + ASSIMP_LOG_INFO(szTemp); +} + +// ------------------------------------------------------------------------------------------------ +AI_WONT_RETURN void Parser::LogError(const char *szWarn) { + ai_assert(nullptr != szWarn); + + char szTemp[1024]; +#if _MSC_VER >= 1400 + sprintf_s(szTemp, "Line %u: %s", iLineNumber, szWarn); +#else + ai_snprintf(szTemp, 1024, "Line %u: %s", iLineNumber, szWarn); +#endif + + // throw an exception + throw DeadlyImportError(szTemp); +} + +// ------------------------------------------------------------------------------------------------ +bool Parser::SkipToNextToken() { + while (true) { + char me = *filePtr; + + // increase the line number counter if necessary + if (IsLineEnd(me) && !bLastWasEndLine) { + ++iLineNumber; + bLastWasEndLine = true; + } else + bLastWasEndLine = false; + if ('*' == me || '}' == me || '{' == me) return true; + if ('\0' == me) return false; + + ++filePtr; + } +} + +// ------------------------------------------------------------------------------------------------ +bool Parser::SkipSection() { + // must handle subsections ... + int iCnt = 0; + while (true) { + if ('}' == *filePtr) { + --iCnt; + if (0 == iCnt) { + // go to the next valid token ... + ++filePtr; + SkipToNextToken(); + return true; + } + } else if ('{' == *filePtr) { + ++iCnt; + } else if ('\0' == *filePtr) { + LogWarning("Unable to parse block: Unexpected EOF, closing bracket \'}\' was expected [#1]"); + return false; + } else if (IsLineEnd(*filePtr)) + ++iLineNumber; + ++filePtr; + } +} + +// ------------------------------------------------------------------------------------------------ +void Parser::Parse() { + AI_ASE_PARSER_INIT(); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + // Version should be 200. Validate this ... + if (TokenMatch(filePtr, "3DSMAX_ASCIIEXPORT", 18)) { + unsigned int fmt; + ParseLV4MeshLong(fmt); + + if (fmt > 200) { + LogWarning("Unknown file format version: *3DSMAX_ASCIIEXPORT should \ + be <= 200"); + } + // ************************************************************* + // - fmt will be 0 if we're unable to read the version number + // there are some faulty files without a version number ... + // in this case we'll guess the exact file format by looking + // at the file extension (ASE, ASK, ASC) + // ************************************************************* + + if (fmt) { + iFileFormat = fmt; + } + continue; + } + // main scene information + if (TokenMatch(filePtr, "SCENE", 5)) { + ParseLV1SceneBlock(); + continue; + } + // "group" - no implementation yet, in facte + // we're just ignoring them for the moment + if (TokenMatch(filePtr, "GROUP", 5)) { + Parse(); + continue; + } + // material list + if (TokenMatch(filePtr, "MATERIAL_LIST", 13)) { + ParseLV1MaterialListBlock(); + continue; + } + // geometric object (mesh) + if (TokenMatch(filePtr, "GEOMOBJECT", 10)) + + { + m_vMeshes.push_back(Mesh("UNNAMED")); + ParseLV1ObjectBlock(m_vMeshes.back()); + continue; + } + // helper object = dummy in the hierarchy + if (TokenMatch(filePtr, "HELPEROBJECT", 12)) + + { + m_vDummies.push_back(Dummy()); + ParseLV1ObjectBlock(m_vDummies.back()); + continue; + } + // light object + if (TokenMatch(filePtr, "LIGHTOBJECT", 11)) + + { + m_vLights.push_back(Light("UNNAMED")); + ParseLV1ObjectBlock(m_vLights.back()); + continue; + } + // camera object + if (TokenMatch(filePtr, "CAMERAOBJECT", 12)) { + m_vCameras.push_back(Camera("UNNAMED")); + ParseLV1ObjectBlock(m_vCameras.back()); + continue; + } + // comment - print it on the console + if (TokenMatch(filePtr, "COMMENT", 7)) { + std::string out = "<unknown>"; + ParseString(out, "*COMMENT"); + LogInfo(("Comment: " + out).c_str()); + continue; + } + // ASC bone weights + if (AI_ASE_IS_OLD_FILE_FORMAT() && TokenMatch(filePtr, "MESH_SOFTSKINVERTS", 18)) { + ParseLV1SoftSkinBlock(); + } + } + AI_ASE_HANDLE_TOP_LEVEL_SECTION(); + } + return; +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV1SoftSkinBlock() { + // TODO: fix line counting here + + // ************************************************************** + // The soft skin block is formatted differently. There are no + // nested sections supported and the single elements aren't + // marked by keywords starting with an asterisk. + + /** + FORMAT BEGIN + + *MESH_SOFTSKINVERTS { + <nodename> + <number of vertices> + + [for <number of vertices> times:] + <number of weights> [for <number of weights> times:] <bone name> <weight> + } + + FORMAT END + */ + // ************************************************************** + while (true) { + if (*filePtr == '}') { + ++filePtr; + return; + } else if (*filePtr == '\0') + return; + else if (*filePtr == '{') + ++filePtr; + + else // if (!IsSpace(*filePtr) && !IsLineEnd(*filePtr)) + { + ASE::Mesh *curMesh = nullptr; + unsigned int numVerts = 0; + + const char *sz = filePtr; + while (!IsSpaceOrNewLine(*filePtr)) + ++filePtr; + + const unsigned int diff = (unsigned int)(filePtr - sz); + if (diff) { + std::string name = std::string(sz, diff); + for (std::vector<ASE::Mesh>::iterator it = m_vMeshes.begin(); + it != m_vMeshes.end(); ++it) { + if ((*it).mName == name) { + curMesh = &(*it); + break; + } + } + if (!curMesh) { + LogWarning("Encountered unknown mesh in *MESH_SOFTSKINVERTS section"); + + // Skip the mesh data - until we find a new mesh + // or the end of the *MESH_SOFTSKINVERTS section + while (true) { + SkipSpacesAndLineEnd(&filePtr); + if (*filePtr == '}') { + ++filePtr; + return; + } else if (!IsNumeric(*filePtr)) + break; + + SkipLine(&filePtr); + } + } else { + SkipSpacesAndLineEnd(&filePtr); + ParseLV4MeshLong(numVerts); + + // Reserve enough storage + curMesh->mBoneVertices.reserve(numVerts); + + for (unsigned int i = 0; i < numVerts; ++i) { + SkipSpacesAndLineEnd(&filePtr); + unsigned int numWeights; + ParseLV4MeshLong(numWeights); + + curMesh->mBoneVertices.push_back(ASE::BoneVertex()); + ASE::BoneVertex &vert = curMesh->mBoneVertices.back(); + + // Reserve enough storage + vert.mBoneWeights.reserve(numWeights); + + std::string bone; + for (unsigned int w = 0; w < numWeights; ++w) { + bone.clear(); + ParseString(bone, "*MESH_SOFTSKINVERTS.Bone"); + + // Find the bone in the mesh's list + std::pair<int, ai_real> me; + me.first = -1; + + for (unsigned int n = 0; n < curMesh->mBones.size(); ++n) { + if (curMesh->mBones[n].mName == bone) { + me.first = n; + break; + } + } + if (-1 == me.first) { + // We don't have this bone yet, so add it to the list + me.first = static_cast<int>(curMesh->mBones.size()); + curMesh->mBones.push_back(ASE::Bone(bone)); + } + ParseLV4MeshFloat(me.second); + + // Add the new bone weight to list + vert.mBoneWeights.push_back(me); + } + } + } + } + } + ++filePtr; + SkipSpacesAndLineEnd(&filePtr); + } +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV1SceneBlock() { + AI_ASE_PARSER_INIT(); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + if (TokenMatch(filePtr, "SCENE_BACKGROUND_STATIC", 23)) + + { + // parse a color triple and assume it is really the bg color + ParseLV4MeshFloatTriple(&m_clrBackground.r); + continue; + } + if (TokenMatch(filePtr, "SCENE_AMBIENT_STATIC", 20)) + + { + // parse a color triple and assume it is really the bg color + ParseLV4MeshFloatTriple(&m_clrAmbient.r); + continue; + } + if (TokenMatch(filePtr, "SCENE_FIRSTFRAME", 16)) { + ParseLV4MeshLong(iFirstFrame); + continue; + } + if (TokenMatch(filePtr, "SCENE_LASTFRAME", 15)) { + ParseLV4MeshLong(iLastFrame); + continue; + } + if (TokenMatch(filePtr, "SCENE_FRAMESPEED", 16)) { + ParseLV4MeshLong(iFrameSpeed); + continue; + } + if (TokenMatch(filePtr, "SCENE_TICKSPERFRAME", 19)) { + ParseLV4MeshLong(iTicksPerFrame); + continue; + } + } + AI_ASE_HANDLE_TOP_LEVEL_SECTION(); + } +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV1MaterialListBlock() { + AI_ASE_PARSER_INIT(); + + unsigned int iMaterialCount = 0; + unsigned int iOldMaterialCount = (unsigned int)m_vMaterials.size(); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + if (TokenMatch(filePtr, "MATERIAL_COUNT", 14)) { + ParseLV4MeshLong(iMaterialCount); + + // now allocate enough storage to hold all materials + m_vMaterials.resize(iOldMaterialCount + iMaterialCount, Material("INVALID")); + continue; + } + if (TokenMatch(filePtr, "MATERIAL", 8)) { + unsigned int iIndex = 0; + ParseLV4MeshLong(iIndex); + + if (iIndex >= iMaterialCount) { + LogWarning("Out of range: material index is too large"); + iIndex = iMaterialCount - 1; + return; + } + + // get a reference to the material + Material &sMat = m_vMaterials[iIndex + iOldMaterialCount]; + // parse the material block + ParseLV2MaterialBlock(sMat); + continue; + } + if( iDepth == 1 ){ + // CRUDE HACK: support missing brace after "Ascii Scene Exporter v2.51" + LogWarning("Missing closing brace in material list"); + --filePtr; + return; + } + } + AI_ASE_HANDLE_TOP_LEVEL_SECTION(); + } +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV2MaterialBlock(ASE::Material &mat) { + AI_ASE_PARSER_INIT(); + + unsigned int iNumSubMaterials = 0; + while (true) { + if ('*' == *filePtr) { + ++filePtr; + if (TokenMatch(filePtr, "MATERIAL_NAME", 13)) { + if (!ParseString(mat.mName, "*MATERIAL_NAME")) + SkipToNextToken(); + continue; + } + // ambient material color + if (TokenMatch(filePtr, "MATERIAL_AMBIENT", 16)) { + ParseLV4MeshFloatTriple(&mat.mAmbient.r); + continue; + } + // diffuse material color + if (TokenMatch(filePtr, "MATERIAL_DIFFUSE", 16)) { + ParseLV4MeshFloatTriple(&mat.mDiffuse.r); + continue; + } + // specular material color + if (TokenMatch(filePtr, "MATERIAL_SPECULAR", 17)) { + ParseLV4MeshFloatTriple(&mat.mSpecular.r); + continue; + } + // material shading type + if (TokenMatch(filePtr, "MATERIAL_SHADING", 16)) { + if (TokenMatch(filePtr, "Blinn", 5)) { + mat.mShading = Discreet3DS::Blinn; + } else if (TokenMatch(filePtr, "Phong", 5)) { + mat.mShading = Discreet3DS::Phong; + } else if (TokenMatch(filePtr, "Flat", 4)) { + mat.mShading = Discreet3DS::Flat; + } else if (TokenMatch(filePtr, "Wire", 4)) { + mat.mShading = Discreet3DS::Wire; + } else { + // assume gouraud shading + mat.mShading = Discreet3DS::Gouraud; + SkipToNextToken(); + } + continue; + } + // material transparency + if (TokenMatch(filePtr, "MATERIAL_TRANSPARENCY", 21)) { + ParseLV4MeshFloat(mat.mTransparency); + mat.mTransparency = ai_real(1.0) - mat.mTransparency; + continue; + } + // material self illumination + if (TokenMatch(filePtr, "MATERIAL_SELFILLUM", 18)) { + ai_real f = 0.0; + ParseLV4MeshFloat(f); + + mat.mEmissive.r = f; + mat.mEmissive.g = f; + mat.mEmissive.b = f; + continue; + } + // material shininess + if (TokenMatch(filePtr, "MATERIAL_SHINE", 14)) { + ParseLV4MeshFloat(mat.mSpecularExponent); + mat.mSpecularExponent *= 15; + continue; + } + // two-sided material + if (TokenMatch(filePtr, "MATERIAL_TWOSIDED", 17)) { + mat.mTwoSided = true; + continue; + } + // material shininess strength + if (TokenMatch(filePtr, "MATERIAL_SHINESTRENGTH", 22)) { + ParseLV4MeshFloat(mat.mShininessStrength); + continue; + } + // diffuse color map + if (TokenMatch(filePtr, "MAP_DIFFUSE", 11)) { + // parse the texture block + ParseLV3MapBlock(mat.sTexDiffuse); + continue; + } + // ambient color map + if (TokenMatch(filePtr, "MAP_AMBIENT", 11)) { + // parse the texture block + ParseLV3MapBlock(mat.sTexAmbient); + continue; + } + // specular color map + if (TokenMatch(filePtr, "MAP_SPECULAR", 12)) { + // parse the texture block + ParseLV3MapBlock(mat.sTexSpecular); + continue; + } + // opacity map + if (TokenMatch(filePtr, "MAP_OPACITY", 11)) { + // parse the texture block + ParseLV3MapBlock(mat.sTexOpacity); + continue; + } + // emissive map + if (TokenMatch(filePtr, "MAP_SELFILLUM", 13)) { + // parse the texture block + ParseLV3MapBlock(mat.sTexEmissive); + continue; + } + // bump map + if (TokenMatch(filePtr, "MAP_BUMP", 8)) { + // parse the texture block + ParseLV3MapBlock(mat.sTexBump); + } + // specular/shininess map + if (TokenMatch(filePtr, "MAP_SHINESTRENGTH", 17)) { + // parse the texture block + ParseLV3MapBlock(mat.sTexShininess); + continue; + } + // number of submaterials + if (TokenMatch(filePtr, "NUMSUBMTLS", 10)) { + ParseLV4MeshLong(iNumSubMaterials); + + // allocate enough storage + mat.avSubMaterials.resize(iNumSubMaterials, Material("INVALID SUBMATERIAL")); + } + // submaterial chunks + if (TokenMatch(filePtr, "SUBMATERIAL", 11)) { + + unsigned int iIndex = 0; + ParseLV4MeshLong(iIndex); + + if (iIndex >= iNumSubMaterials) { + LogWarning("Out of range: submaterial index is too large"); + iIndex = iNumSubMaterials - 1; + } + + // get a reference to the material + Material &sMat = mat.avSubMaterials[iIndex]; + + // parse the material block + ParseLV2MaterialBlock(sMat); + continue; + } + } + AI_ASE_HANDLE_SECTION("2", "*MATERIAL"); + } +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MapBlock(Texture &map) { + AI_ASE_PARSER_INIT(); + + // *********************************************************** + // *BITMAP should not be there if *MAP_CLASS is not BITMAP, + // but we need to expect that case ... if the path is + // empty the texture won't be used later. + // *********************************************************** + bool parsePath = true; + std::string temp; + while (true) { + if ('*' == *filePtr) { + ++filePtr; + // type of map + if (TokenMatch(filePtr, "MAP_CLASS", 9)) { + temp.clear(); + if (!ParseString(temp, "*MAP_CLASS")) + SkipToNextToken(); + if (temp != "Bitmap" && temp != "Normal Bump") { + ASSIMP_LOG_WARN("ASE: Skipping unknown map type: ", temp); + parsePath = false; + } + continue; + } + // path to the texture + if (parsePath && TokenMatch(filePtr, "BITMAP", 6)) { + if (!ParseString(map.mMapName, "*BITMAP")) + SkipToNextToken(); + + if (map.mMapName == "None") { + // Files with 'None' as map name are produced by + // an Maja to ASE exporter which name I forgot .. + ASSIMP_LOG_WARN("ASE: Skipping invalid map entry"); + map.mMapName = std::string(); + } + + continue; + } + // offset on the u axis + if (TokenMatch(filePtr, "UVW_U_OFFSET", 12)) { + ParseLV4MeshFloat(map.mOffsetU); + continue; + } + // offset on the v axis + if (TokenMatch(filePtr, "UVW_V_OFFSET", 12)) { + ParseLV4MeshFloat(map.mOffsetV); + continue; + } + // tiling on the u axis + if (TokenMatch(filePtr, "UVW_U_TILING", 12)) { + ParseLV4MeshFloat(map.mScaleU); + continue; + } + // tiling on the v axis + if (TokenMatch(filePtr, "UVW_V_TILING", 12)) { + ParseLV4MeshFloat(map.mScaleV); + continue; + } + // rotation around the z-axis + if (TokenMatch(filePtr, "UVW_ANGLE", 9)) { + ParseLV4MeshFloat(map.mRotation); + continue; + } + // map blending factor + if (TokenMatch(filePtr, "MAP_AMOUNT", 10)) { + ParseLV4MeshFloat(map.mTextureBlend); + continue; + } + } + AI_ASE_HANDLE_SECTION("3", "*MAP_XXXXXX"); + } + return; +} + +// ------------------------------------------------------------------------------------------------ +bool Parser::ParseString(std::string &out, const char *szName) { + char szBuffer[1024]; + if (!SkipSpaces(&filePtr)) { + + ai_snprintf(szBuffer, 1024, "Unable to parse %s block: Unexpected EOL", szName); + LogWarning(szBuffer); + return false; + } + // there must be '"' + if ('\"' != *filePtr) { + + ai_snprintf(szBuffer, 1024, "Unable to parse %s block: Strings are expected " + "to be enclosed in double quotation marks", + szName); + LogWarning(szBuffer); + return false; + } + ++filePtr; + const char *sz = filePtr; + while (true) { + if ('\"' == *sz) + break; + else if ('\0' == *sz) { + ai_snprintf(szBuffer, 1024, "Unable to parse %s block: Strings are expected to " + "be enclosed in double quotation marks but EOF was reached before " + "a closing quotation mark was encountered", + szName); + LogWarning(szBuffer); + return false; + } + sz++; + } + out = std::string(filePtr, (uintptr_t)sz - (uintptr_t)filePtr); + filePtr = sz + 1; + return true; +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV1ObjectBlock(ASE::BaseNode &node) { + AI_ASE_PARSER_INIT(); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + // first process common tokens such as node name and transform + // name of the mesh/node + if (TokenMatch(filePtr, "NODE_NAME", 9)) { + if (!ParseString(node.mName, "*NODE_NAME")) + SkipToNextToken(); + continue; + } + // name of the parent of the node + if (TokenMatch(filePtr, "NODE_PARENT", 11)) { + if (!ParseString(node.mParent, "*NODE_PARENT")) + SkipToNextToken(); + continue; + } + // transformation matrix of the node + if (TokenMatch(filePtr, "NODE_TM", 7)) { + ParseLV2NodeTransformBlock(node); + continue; + } + // animation data of the node + if (TokenMatch(filePtr, "TM_ANIMATION", 12)) { + ParseLV2AnimationBlock(node); + continue; + } + + if (node.mType == BaseNode::Light) { + // light settings + if (TokenMatch(filePtr, "LIGHT_SETTINGS", 14)) { + ParseLV2LightSettingsBlock((ASE::Light &)node); + continue; + } + // type of the light source + if (TokenMatch(filePtr, "LIGHT_TYPE", 10)) { + if (!ASSIMP_strincmp("omni", filePtr, 4)) { + ((ASE::Light &)node).mLightType = ASE::Light::OMNI; + } else if (!ASSIMP_strincmp("target", filePtr, 6)) { + ((ASE::Light &)node).mLightType = ASE::Light::TARGET; + } else if (!ASSIMP_strincmp("free", filePtr, 4)) { + ((ASE::Light &)node).mLightType = ASE::Light::FREE; + } else if (!ASSIMP_strincmp("directional", filePtr, 11)) { + ((ASE::Light &)node).mLightType = ASE::Light::DIRECTIONAL; + } else { + LogWarning("Unknown kind of light source"); + } + continue; + } + } else if (node.mType == BaseNode::Camera) { + // Camera settings + if (TokenMatch(filePtr, "CAMERA_SETTINGS", 15)) { + ParseLV2CameraSettingsBlock((ASE::Camera &)node); + continue; + } else if (TokenMatch(filePtr, "CAMERA_TYPE", 11)) { + if (!ASSIMP_strincmp("target", filePtr, 6)) { + ((ASE::Camera &)node).mCameraType = ASE::Camera::TARGET; + } else if (!ASSIMP_strincmp("free", filePtr, 4)) { + ((ASE::Camera &)node).mCameraType = ASE::Camera::FREE; + } else { + LogWarning("Unknown kind of camera"); + } + continue; + } + } else if (node.mType == BaseNode::Mesh) { + // mesh data + // FIX: Older files use MESH_SOFTSKIN + if (TokenMatch(filePtr, "MESH", 4) || + TokenMatch(filePtr, "MESH_SOFTSKIN", 13)) { + ParseLV2MeshBlock((ASE::Mesh &)node); + continue; + } + // mesh material index + if (TokenMatch(filePtr, "MATERIAL_REF", 12)) { + ParseLV4MeshLong(((ASE::Mesh &)node).iMaterialIndex); + continue; + } + } + } + AI_ASE_HANDLE_TOP_LEVEL_SECTION(); + } + return; +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV2CameraSettingsBlock(ASE::Camera &camera) { + AI_ASE_PARSER_INIT(); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + if (TokenMatch(filePtr, "CAMERA_NEAR", 11)) { + ParseLV4MeshFloat(camera.mNear); + continue; + } + if (TokenMatch(filePtr, "CAMERA_FAR", 10)) { + ParseLV4MeshFloat(camera.mFar); + continue; + } + if (TokenMatch(filePtr, "CAMERA_FOV", 10)) { + ParseLV4MeshFloat(camera.mFOV); + continue; + } + } + AI_ASE_HANDLE_SECTION("2", "CAMERA_SETTINGS"); + } + return; +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV2LightSettingsBlock(ASE::Light &light) { + AI_ASE_PARSER_INIT(); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + if (TokenMatch(filePtr, "LIGHT_COLOR", 11)) { + ParseLV4MeshFloatTriple(&light.mColor.r); + continue; + } + if (TokenMatch(filePtr, "LIGHT_INTENS", 12)) { + ParseLV4MeshFloat(light.mIntensity); + continue; + } + if (TokenMatch(filePtr, "LIGHT_HOTSPOT", 13)) { + ParseLV4MeshFloat(light.mAngle); + continue; + } + if (TokenMatch(filePtr, "LIGHT_FALLOFF", 13)) { + ParseLV4MeshFloat(light.mFalloff); + continue; + } + } + AI_ASE_HANDLE_SECTION("2", "LIGHT_SETTINGS"); + } +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV2AnimationBlock(ASE::BaseNode &mesh) { + AI_ASE_PARSER_INIT(); + + ASE::Animation *anim = &mesh.mAnim; + while (true) { + if ('*' == *filePtr) { + ++filePtr; + if (TokenMatch(filePtr, "NODE_NAME", 9)) { + std::string temp; + if (!ParseString(temp, "*NODE_NAME")) + SkipToNextToken(); + + // If the name of the node contains .target it + // represents an animated camera or spot light + // target. + if (std::string::npos != temp.find(".Target")) { + if ((mesh.mType != BaseNode::Camera || ((ASE::Camera &)mesh).mCameraType != ASE::Camera::TARGET) && + (mesh.mType != BaseNode::Light || ((ASE::Light &)mesh).mLightType != ASE::Light::TARGET)) { + + ASSIMP_LOG_ERROR("ASE: Found target animation channel " + "but the node is neither a camera nor a spot light"); + anim = nullptr; + } else + anim = &mesh.mTargetAnim; + } + continue; + } + + // position keyframes + if (TokenMatch(filePtr, "CONTROL_POS_TRACK", 17) || + TokenMatch(filePtr, "CONTROL_POS_BEZIER", 18) || + TokenMatch(filePtr, "CONTROL_POS_TCB", 15)) { + if (!anim) + SkipSection(); + else + ParseLV3PosAnimationBlock(*anim); + continue; + } + // scaling keyframes + if (TokenMatch(filePtr, "CONTROL_SCALE_TRACK", 19) || + TokenMatch(filePtr, "CONTROL_SCALE_BEZIER", 20) || + TokenMatch(filePtr, "CONTROL_SCALE_TCB", 17)) { + if (!anim || anim == &mesh.mTargetAnim) { + // Target animation channels may have no rotation channels + ASSIMP_LOG_ERROR("ASE: Ignoring scaling channel in target animation"); + SkipSection(); + } else + ParseLV3ScaleAnimationBlock(*anim); + continue; + } + // rotation keyframes + if (TokenMatch(filePtr, "CONTROL_ROT_TRACK", 17) || + TokenMatch(filePtr, "CONTROL_ROT_BEZIER", 18) || + TokenMatch(filePtr, "CONTROL_ROT_TCB", 15)) { + if (!anim || anim == &mesh.mTargetAnim) { + // Target animation channels may have no rotation channels + ASSIMP_LOG_ERROR("ASE: Ignoring rotation channel in target animation"); + SkipSection(); + } else + ParseLV3RotAnimationBlock(*anim); + continue; + } + } + AI_ASE_HANDLE_SECTION("2", "TM_ANIMATION"); + } +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3ScaleAnimationBlock(ASE::Animation &anim) { + AI_ASE_PARSER_INIT(); + unsigned int iIndex; + + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + bool b = false; + + // For the moment we're just reading the three floats - + // we ignore the additional information for bezier's and TCBs + + // simple scaling keyframe + if (TokenMatch(filePtr, "CONTROL_SCALE_SAMPLE", 20)) { + b = true; + anim.mScalingType = ASE::Animation::TRACK; + } + + // Bezier scaling keyframe + if (TokenMatch(filePtr, "CONTROL_BEZIER_SCALE_KEY", 24)) { + b = true; + anim.mScalingType = ASE::Animation::BEZIER; + } + // TCB scaling keyframe + if (TokenMatch(filePtr, "CONTROL_TCB_SCALE_KEY", 21)) { + b = true; + anim.mScalingType = ASE::Animation::TCB; + } + if (b) { + anim.akeyScaling.push_back(aiVectorKey()); + aiVectorKey &key = anim.akeyScaling.back(); + ParseLV4MeshFloatTriple(&key.mValue.x, iIndex); + key.mTime = (double)iIndex; + } + } + AI_ASE_HANDLE_SECTION("3", "*CONTROL_POS_TRACK"); + } +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3PosAnimationBlock(ASE::Animation &anim) { + AI_ASE_PARSER_INIT(); + unsigned int iIndex; + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + bool b = false; + + // For the moment we're just reading the three floats - + // we ignore the additional information for bezier's and TCBs + + // simple scaling keyframe + if (TokenMatch(filePtr, "CONTROL_POS_SAMPLE", 18)) { + b = true; + anim.mPositionType = ASE::Animation::TRACK; + } + + // Bezier scaling keyframe + if (TokenMatch(filePtr, "CONTROL_BEZIER_POS_KEY", 22)) { + b = true; + anim.mPositionType = ASE::Animation::BEZIER; + } + // TCB scaling keyframe + if (TokenMatch(filePtr, "CONTROL_TCB_POS_KEY", 19)) { + b = true; + anim.mPositionType = ASE::Animation::TCB; + } + if (b) { + anim.akeyPositions.push_back(aiVectorKey()); + aiVectorKey &key = anim.akeyPositions.back(); + ParseLV4MeshFloatTriple(&key.mValue.x, iIndex); + key.mTime = (double)iIndex; + } + } + AI_ASE_HANDLE_SECTION("3", "*CONTROL_POS_TRACK"); + } +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3RotAnimationBlock(ASE::Animation &anim) { + AI_ASE_PARSER_INIT(); + unsigned int iIndex; + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + bool b = false; + + // For the moment we're just reading the floats - + // we ignore the additional information for bezier's and TCBs + + // simple scaling keyframe + if (TokenMatch(filePtr, "CONTROL_ROT_SAMPLE", 18)) { + b = true; + anim.mRotationType = ASE::Animation::TRACK; + } + + // Bezier scaling keyframe + if (TokenMatch(filePtr, "CONTROL_BEZIER_ROT_KEY", 22)) { + b = true; + anim.mRotationType = ASE::Animation::BEZIER; + } + // TCB scaling keyframe + if (TokenMatch(filePtr, "CONTROL_TCB_ROT_KEY", 19)) { + b = true; + anim.mRotationType = ASE::Animation::TCB; + } + if (b) { + anim.akeyRotations.push_back(aiQuatKey()); + aiQuatKey &key = anim.akeyRotations.back(); + aiVector3D v; + ai_real f; + ParseLV4MeshFloatTriple(&v.x, iIndex); + ParseLV4MeshFloat(f); + key.mTime = (double)iIndex; + key.mValue = aiQuaternion(v, f); + } + } + AI_ASE_HANDLE_SECTION("3", "*CONTROL_ROT_TRACK"); + } +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode &mesh) { + AI_ASE_PARSER_INIT(); + int mode = 0; + while (true) { + if ('*' == *filePtr) { + ++filePtr; + // name of the node + if (TokenMatch(filePtr, "NODE_NAME", 9)) { + std::string temp; + if (!ParseString(temp, "*NODE_NAME")) + SkipToNextToken(); + + std::string::size_type s; + if (temp == mesh.mName) { + mode = 1; + } else if (std::string::npos != (s = temp.find(".Target")) && + mesh.mName == temp.substr(0, s)) { + // This should be either a target light or a target camera + if ((mesh.mType == BaseNode::Light && ((ASE::Light &)mesh).mLightType == ASE::Light::TARGET) || + (mesh.mType == BaseNode::Camera && ((ASE::Camera &)mesh).mCameraType == ASE::Camera::TARGET)) { + mode = 2; + } else { + ASSIMP_LOG_ERROR("ASE: Ignoring target transform, " + "this is no spot light or target camera"); + } + } else { + ASSIMP_LOG_ERROR("ASE: Unknown node transformation: ", temp); + // mode = 0 + } + continue; + } + if (mode) { + // fourth row of the transformation matrix - and also the + // only information here that is interesting for targets + if (TokenMatch(filePtr, "TM_ROW3", 7)) { + ParseLV4MeshFloatTriple((mode == 1 ? mesh.mTransform[3] : &mesh.mTargetPosition.x)); + continue; + } + if (mode == 1) { + // first row of the transformation matrix + if (TokenMatch(filePtr, "TM_ROW0", 7)) { + ParseLV4MeshFloatTriple(mesh.mTransform[0]); + continue; + } + // second row of the transformation matrix + if (TokenMatch(filePtr, "TM_ROW1", 7)) { + ParseLV4MeshFloatTriple(mesh.mTransform[1]); + continue; + } + // third row of the transformation matrix + if (TokenMatch(filePtr, "TM_ROW2", 7)) { + ParseLV4MeshFloatTriple(mesh.mTransform[2]); + continue; + } + // inherited position axes + if (TokenMatch(filePtr, "INHERIT_POS", 11)) { + unsigned int aiVal[3]; + ParseLV4MeshLongTriple(aiVal); + + for (unsigned int i = 0; i < 3; ++i) + mesh.inherit.abInheritPosition[i] = aiVal[i] != 0; + continue; + } + // inherited rotation axes + if (TokenMatch(filePtr, "INHERIT_ROT", 11)) { + unsigned int aiVal[3]; + ParseLV4MeshLongTriple(aiVal); + + for (unsigned int i = 0; i < 3; ++i) + mesh.inherit.abInheritRotation[i] = aiVal[i] != 0; + continue; + } + // inherited scaling axes + if (TokenMatch(filePtr, "INHERIT_SCL", 11)) { + unsigned int aiVal[3]; + ParseLV4MeshLongTriple(aiVal); + + for (unsigned int i = 0; i < 3; ++i) + mesh.inherit.abInheritScaling[i] = aiVal[i] != 0; + continue; + } + } + } + } + AI_ASE_HANDLE_SECTION("2", "*NODE_TM"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV2MeshBlock(ASE::Mesh &mesh) { + AI_ASE_PARSER_INIT(); + + unsigned int iNumVertices = 0; + unsigned int iNumFaces = 0; + unsigned int iNumTVertices = 0; + unsigned int iNumTFaces = 0; + unsigned int iNumCVertices = 0; + unsigned int iNumCFaces = 0; + while (true) { + if ('*' == *filePtr) { + ++filePtr; + // Number of vertices in the mesh + if (TokenMatch(filePtr, "MESH_NUMVERTEX", 14)) { + ParseLV4MeshLong(iNumVertices); + continue; + } + // Number of texture coordinates in the mesh + if (TokenMatch(filePtr, "MESH_NUMTVERTEX", 15)) { + ParseLV4MeshLong(iNumTVertices); + continue; + } + // Number of vertex colors in the mesh + if (TokenMatch(filePtr, "MESH_NUMCVERTEX", 15)) { + ParseLV4MeshLong(iNumCVertices); + continue; + } + // Number of regular faces in the mesh + if (TokenMatch(filePtr, "MESH_NUMFACES", 13)) { + ParseLV4MeshLong(iNumFaces); + continue; + } + // Number of UVWed faces in the mesh + if (TokenMatch(filePtr, "MESH_NUMTVFACES", 15)) { + ParseLV4MeshLong(iNumTFaces); + continue; + } + // Number of colored faces in the mesh + if (TokenMatch(filePtr, "MESH_NUMCVFACES", 15)) { + ParseLV4MeshLong(iNumCFaces); + continue; + } + // mesh vertex list block + if (TokenMatch(filePtr, "MESH_VERTEX_LIST", 16)) { + ParseLV3MeshVertexListBlock(iNumVertices, mesh); + continue; + } + // mesh face list block + if (TokenMatch(filePtr, "MESH_FACE_LIST", 14)) { + ParseLV3MeshFaceListBlock(iNumFaces, mesh); + continue; + } + // mesh texture vertex list block + if (TokenMatch(filePtr, "MESH_TVERTLIST", 14)) { + ParseLV3MeshTListBlock(iNumTVertices, mesh); + continue; + } + // mesh texture face block + if (TokenMatch(filePtr, "MESH_TFACELIST", 14)) { + ParseLV3MeshTFaceListBlock(iNumTFaces, mesh); + continue; + } + // mesh color vertex list block + if (TokenMatch(filePtr, "MESH_CVERTLIST", 14)) { + ParseLV3MeshCListBlock(iNumCVertices, mesh); + continue; + } + // mesh color face block + if (TokenMatch(filePtr, "MESH_CFACELIST", 14)) { + ParseLV3MeshCFaceListBlock(iNumCFaces, mesh); + continue; + } + // mesh normals + if (TokenMatch(filePtr, "MESH_NORMALS", 12)) { + ParseLV3MeshNormalListBlock(mesh); + continue; + } + // another mesh UV channel ... + if (TokenMatch(filePtr, "MESH_MAPPINGCHANNEL", 19)) { + unsigned int iIndex(0); + ParseLV4MeshLong(iIndex); + if (0 == iIndex) { + LogWarning("Mapping channel has an invalid index. Skipping UV channel"); + // skip it ... + SkipSection(); + } else { + if (iIndex < 2) { + LogWarning("Mapping channel has an invalid index. Skipping UV channel"); + // skip it ... + SkipSection(); + } + if (iIndex > AI_MAX_NUMBER_OF_TEXTURECOORDS) { + LogWarning("Too many UV channels specified. Skipping channel .."); + // skip it ... + SkipSection(); + } else { + // parse the mapping channel + ParseLV3MappingChannel(iIndex - 1, mesh); + } + continue; + } + } + // mesh animation keyframe. Not supported + if (TokenMatch(filePtr, "MESH_ANIMATION", 14)) { + + LogWarning("Found *MESH_ANIMATION element in ASE/ASK file. " + "Keyframe animation is not supported by Assimp, this element " + "will be ignored"); + //SkipSection(); + continue; + } + if (TokenMatch(filePtr, "MESH_WEIGHTS", 12)) { + ParseLV3MeshWeightsBlock(mesh); + continue; + } + } + AI_ASE_HANDLE_SECTION("2", "*MESH"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshWeightsBlock(ASE::Mesh &mesh) { + AI_ASE_PARSER_INIT(); + + unsigned int iNumVertices = 0, iNumBones = 0; + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + // Number of bone vertices ... + if (TokenMatch(filePtr, "MESH_NUMVERTEX", 14)) { + ParseLV4MeshLong(iNumVertices); + continue; + } + // Number of bones + if (TokenMatch(filePtr, "MESH_NUMBONE", 12)) { + ParseLV4MeshLong(iNumBones); + continue; + } + // parse the list of bones + if (TokenMatch(filePtr, "MESH_BONE_LIST", 14)) { + ParseLV4MeshBones(iNumBones, mesh); + continue; + } + // parse the list of bones vertices + if (TokenMatch(filePtr, "MESH_BONE_VERTEX_LIST", 21)) { + ParseLV4MeshBonesVertices(iNumVertices, mesh); + continue; + } + } + AI_ASE_HANDLE_SECTION("3", "*MESH_WEIGHTS"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshBones(unsigned int iNumBones, ASE::Mesh &mesh) { + AI_ASE_PARSER_INIT(); + mesh.mBones.resize(iNumBones, Bone("UNNAMED")); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + // Mesh bone with name ... + if (TokenMatch(filePtr, "MESH_BONE_NAME", 14)) { + // parse an index ... + if (SkipSpaces(&filePtr)) { + unsigned int iIndex = strtoul10(filePtr, &filePtr); + if (iIndex >= iNumBones) { + LogWarning("Bone index is out of bounds"); + continue; + } + if (!ParseString(mesh.mBones[iIndex].mName, "*MESH_BONE_NAME")) + SkipToNextToken(); + continue; + } + } + } + AI_ASE_HANDLE_SECTION("3", "*MESH_BONE_LIST"); + } +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshBonesVertices(unsigned int iNumVertices, ASE::Mesh &mesh) { + AI_ASE_PARSER_INIT(); + mesh.mBoneVertices.resize(iNumVertices); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + // Mesh bone vertex + if (TokenMatch(filePtr, "MESH_BONE_VERTEX", 16)) { + // read the vertex index + unsigned int iIndex = strtoul10(filePtr, &filePtr); + if (iIndex >= mesh.mPositions.size()) { + iIndex = (unsigned int)mesh.mPositions.size() - 1; + LogWarning("Bone vertex index is out of bounds. Using the largest valid " + "bone vertex index instead"); + } + + // --- ignored + ai_real afVert[3]; + ParseLV4MeshFloatTriple(afVert); + + std::pair<int, float> pairOut; + while (true) { + // first parse the bone index ... + if (!SkipSpaces(&filePtr)) break; + pairOut.first = strtoul10(filePtr, &filePtr); + + // then parse the vertex weight + if (!SkipSpaces(&filePtr)) break; + filePtr = fast_atoreal_move<float>(filePtr, pairOut.second); + + // -1 marks unused entries + if (-1 != pairOut.first) { + mesh.mBoneVertices[iIndex].mBoneWeights.push_back(pairOut); + } + } + continue; + } + } + AI_ASE_HANDLE_SECTION("4", "*MESH_BONE_VERTEX"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshVertexListBlock( + unsigned int iNumVertices, ASE::Mesh &mesh) { + AI_ASE_PARSER_INIT(); + + // allocate enough storage in the array + mesh.mPositions.resize(iNumVertices); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + // Vertex entry + if (TokenMatch(filePtr, "MESH_VERTEX", 11)) { + + aiVector3D vTemp; + unsigned int iIndex; + ParseLV4MeshFloatTriple(&vTemp.x, iIndex); + + if (iIndex >= iNumVertices) { + LogWarning("Invalid vertex index. It will be ignored"); + } else + mesh.mPositions[iIndex] = vTemp; + continue; + } + } + AI_ASE_HANDLE_SECTION("3", "*MESH_VERTEX_LIST"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh) { + AI_ASE_PARSER_INIT(); + + // allocate enough storage in the face array + mesh.mFaces.resize(iNumFaces); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + // Face entry + if (TokenMatch(filePtr, "MESH_FACE", 9)) { + + ASE::Face mFace; + ParseLV4MeshFace(mFace); + + if (mFace.iFace >= iNumFaces) { + LogWarning("Face has an invalid index. It will be ignored"); + } else + mesh.mFaces[mFace.iFace] = mFace; + continue; + } + } + AI_ASE_HANDLE_SECTION("3", "*MESH_FACE_LIST"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshTListBlock(unsigned int iNumVertices, + ASE::Mesh &mesh, unsigned int iChannel) { + AI_ASE_PARSER_INIT(); + + // allocate enough storage in the array + mesh.amTexCoords[iChannel].resize(iNumVertices); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + // Vertex entry + if (TokenMatch(filePtr, "MESH_TVERT", 10)) { + aiVector3D vTemp; + unsigned int iIndex; + ParseLV4MeshFloatTriple(&vTemp.x, iIndex); + + if (iIndex >= iNumVertices) { + LogWarning("Tvertex has an invalid index. It will be ignored"); + } else + mesh.amTexCoords[iChannel][iIndex] = vTemp; + + if (0.0f != vTemp.z) { + // we need 3 coordinate channels + mesh.mNumUVComponents[iChannel] = 3; + } + continue; + } + } + AI_ASE_HANDLE_SECTION("3", "*MESH_TVERT_LIST"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshTFaceListBlock(unsigned int iNumFaces, + ASE::Mesh &mesh, unsigned int iChannel) { + AI_ASE_PARSER_INIT(); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + // Face entry + if (TokenMatch(filePtr, "MESH_TFACE", 10)) { + unsigned int aiValues[3]; + unsigned int iIndex = 0; + + ParseLV4MeshLongTriple(aiValues, iIndex); + if (iIndex >= iNumFaces || iIndex >= mesh.mFaces.size()) { + LogWarning("UV-Face has an invalid index. It will be ignored"); + } else { + // copy UV indices + mesh.mFaces[iIndex].amUVIndices[iChannel][0] = aiValues[0]; + mesh.mFaces[iIndex].amUVIndices[iChannel][1] = aiValues[1]; + mesh.mFaces[iIndex].amUVIndices[iChannel][2] = aiValues[2]; + } + continue; + } + } + AI_ASE_HANDLE_SECTION("3", "*MESH_TFACE_LIST"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MappingChannel(unsigned int iChannel, ASE::Mesh &mesh) { + AI_ASE_PARSER_INIT(); + + unsigned int iNumTVertices = 0; + unsigned int iNumTFaces = 0; + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + // Number of texture coordinates in the mesh + if (TokenMatch(filePtr, "MESH_NUMTVERTEX", 15)) { + ParseLV4MeshLong(iNumTVertices); + continue; + } + // Number of UVWed faces in the mesh + if (TokenMatch(filePtr, "MESH_NUMTVFACES", 15)) { + ParseLV4MeshLong(iNumTFaces); + continue; + } + // mesh texture vertex list block + if (TokenMatch(filePtr, "MESH_TVERTLIST", 14)) { + ParseLV3MeshTListBlock(iNumTVertices, mesh, iChannel); + continue; + } + // mesh texture face block + if (TokenMatch(filePtr, "MESH_TFACELIST", 14)) { + ParseLV3MeshTFaceListBlock(iNumTFaces, mesh, iChannel); + continue; + } + } + AI_ASE_HANDLE_SECTION("3", "*MESH_MAPPING_CHANNEL"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshCListBlock(unsigned int iNumVertices, ASE::Mesh &mesh) { + AI_ASE_PARSER_INIT(); + + // allocate enough storage in the array + mesh.mVertexColors.resize(iNumVertices); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + // Vertex entry + if (TokenMatch(filePtr, "MESH_VERTCOL", 12)) { + aiColor4D vTemp; + vTemp.a = 1.0f; + unsigned int iIndex; + ParseLV4MeshFloatTriple(&vTemp.r, iIndex); + + if (iIndex >= iNumVertices) { + LogWarning("Vertex color has an invalid index. It will be ignored"); + } else + mesh.mVertexColors[iIndex] = vTemp; + continue; + } + } + AI_ASE_HANDLE_SECTION("3", "*MESH_CVERTEX_LIST"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshCFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh) { + AI_ASE_PARSER_INIT(); + while (true) { + if ('*' == *filePtr) { + ++filePtr; + + // Face entry + if (TokenMatch(filePtr, "MESH_CFACE", 10)) { + unsigned int aiValues[3]; + unsigned int iIndex = 0; + + ParseLV4MeshLongTriple(aiValues, iIndex); + if (iIndex >= iNumFaces || iIndex >= mesh.mFaces.size()) { + LogWarning("UV-Face has an invalid index. It will be ignored"); + } else { + // copy color indices + mesh.mFaces[iIndex].mColorIndices[0] = aiValues[0]; + mesh.mFaces[iIndex].mColorIndices[1] = aiValues[1]; + mesh.mFaces[iIndex].mColorIndices[2] = aiValues[2]; + } + continue; + } + } + AI_ASE_HANDLE_SECTION("3", "*MESH_CFACE_LIST"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) { + AI_ASE_PARSER_INIT(); + + // Allocate enough storage for the normals + sMesh.mNormals.resize(sMesh.mFaces.size() * 3, aiVector3D(0.f, 0.f, 0.f)); + unsigned int index, faceIdx = UINT_MAX; + + // FIXME: rewrite this and find out how to interpret the normals + // correctly. This is crap. + + // Smooth the vertex and face normals together. The result + // will be edgy then, but otherwise everything would be soft ... + while (true) { + if ('*' == *filePtr) { + ++filePtr; + if (faceIdx != UINT_MAX && TokenMatch(filePtr, "MESH_VERTEXNORMAL", 17)) { + aiVector3D vNormal; + ParseLV4MeshFloatTriple(&vNormal.x, index); + if (faceIdx >= sMesh.mFaces.size()) + continue; + + // Make sure we assign it to the correct face + const ASE::Face &face = sMesh.mFaces[faceIdx]; + if (index == face.mIndices[0]) + index = 0; + else if (index == face.mIndices[1]) + index = 1; + else if (index == face.mIndices[2]) + index = 2; + else { + ASSIMP_LOG_ERROR("ASE: Invalid vertex index in MESH_VERTEXNORMAL section"); + continue; + } + // We'll renormalize later + sMesh.mNormals[faceIdx * 3 + index] += vNormal; + continue; + } + if (TokenMatch(filePtr, "MESH_FACENORMAL", 15)) { + aiVector3D vNormal; + ParseLV4MeshFloatTriple(&vNormal.x, faceIdx); + + if (faceIdx >= sMesh.mFaces.size()) { + ASSIMP_LOG_ERROR("ASE: Invalid vertex index in MESH_FACENORMAL section"); + continue; + } + + // We'll renormalize later + sMesh.mNormals[faceIdx * 3] += vNormal; + sMesh.mNormals[faceIdx * 3 + 1] += vNormal; + sMesh.mNormals[faceIdx * 3 + 2] += vNormal; + continue; + } + } + AI_ASE_HANDLE_SECTION("3", "*MESH_NORMALS"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFace(ASE::Face &out) { + // skip spaces and tabs + if (!SkipSpaces(&filePtr)) { + LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL [#1]"); + SkipToNextToken(); + return; + } + + // parse the face index + out.iFace = strtoul10(filePtr, &filePtr); + + // next character should be ':' + if (!SkipSpaces(&filePtr)) { + // FIX: there are some ASE files which haven't got : here .... + LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL. \':\' expected [#2]"); + SkipToNextToken(); + return; + } + // FIX: There are some ASE files which haven't got ':' here + if (':' == *filePtr) ++filePtr; + + // Parse all mesh indices + for (unsigned int i = 0; i < 3; ++i) { + unsigned int iIndex = 0; + if (!SkipSpaces(&filePtr)) { + LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL"); + SkipToNextToken(); + return; + } + switch (*filePtr) { + case 'A': + case 'a': + break; + case 'B': + case 'b': + iIndex = 1; + break; + case 'C': + case 'c': + iIndex = 2; + break; + default: + LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL. " + "A,B or C expected [#3]"); + SkipToNextToken(); + return; + }; + ++filePtr; + + // next character should be ':' + if (!SkipSpaces(&filePtr) || ':' != *filePtr) { + LogWarning("Unable to parse *MESH_FACE Element: " + "Unexpected EOL. \':\' expected [#2]"); + SkipToNextToken(); + return; + } + + ++filePtr; + if (!SkipSpaces(&filePtr)) { + LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL. " + "Vertex index ecpected [#4]"); + SkipToNextToken(); + return; + } + out.mIndices[iIndex] = strtoul10(filePtr, &filePtr); + } + + // now we need to skip the AB, BC, CA blocks. + while (true) { + if ('*' == *filePtr) break; + if (IsLineEnd(*filePtr)) { + //iLineNumber++; + return; + } + filePtr++; + } + + // parse the smoothing group of the face + if (TokenMatch(filePtr, "*MESH_SMOOTHING", 15)) { + if (!SkipSpaces(&filePtr)) { + LogWarning("Unable to parse *MESH_SMOOTHING Element: " + "Unexpected EOL. Smoothing group(s) expected [#5]"); + SkipToNextToken(); + return; + } + + // Parse smoothing groups until we don't anymore see commas + // FIX: There needn't always be a value, sad but true + while (true) { + if (*filePtr < '9' && *filePtr >= '0') { + out.iSmoothGroup |= (1 << strtoul10(filePtr, &filePtr)); + } + SkipSpaces(&filePtr); + if (',' != *filePtr) { + break; + } + ++filePtr; + SkipSpaces(&filePtr); + } + } + + // *MESH_MTLID is optional, too + while (true) { + if ('*' == *filePtr) { + break; + } + if (IsLineEnd(*filePtr)) { + return; + } + filePtr++; + } + + if (TokenMatch(filePtr, "*MESH_MTLID", 11)) { + if (!SkipSpaces(&filePtr)) { + LogWarning("Unable to parse *MESH_MTLID Element: Unexpected EOL. " + "Material index expected [#6]"); + SkipToNextToken(); + return; + } + out.iMaterial = strtoul10(filePtr, &filePtr); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshLongTriple(unsigned int *apOut) { + ai_assert(nullptr != apOut); + + for (unsigned int i = 0; i < 3; ++i) + ParseLV4MeshLong(apOut[i]); +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshLongTriple(unsigned int *apOut, unsigned int &rIndexOut) { + ai_assert(nullptr != apOut); + + // parse the index + ParseLV4MeshLong(rIndexOut); + + // parse the three others + ParseLV4MeshLongTriple(apOut); +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFloatTriple(ai_real *apOut, unsigned int &rIndexOut) { + ai_assert(nullptr != apOut); + + // parse the index + ParseLV4MeshLong(rIndexOut); + + // parse the three others + ParseLV4MeshFloatTriple(apOut); +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFloatTriple(ai_real *apOut) { + ai_assert(nullptr != apOut); + + for (unsigned int i = 0; i < 3; ++i) { + ParseLV4MeshFloat(apOut[i]); + } +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFloat(ai_real &fOut) { + // skip spaces and tabs + if (!SkipSpaces(&filePtr)) { + // LOG + LogWarning("Unable to parse float: unexpected EOL [#1]"); + fOut = 0.0; + ++iLineNumber; + return; + } + // parse the first float + filePtr = fast_atoreal_move<ai_real>(filePtr, fOut); +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshLong(unsigned int &iOut) { + // Skip spaces and tabs + if (!SkipSpaces(&filePtr)) { + // LOG + LogWarning("Unable to parse long: unexpected EOL [#1]"); + iOut = 0; + ++iLineNumber; + return; + } + // parse the value + iOut = strtoul10(filePtr, &filePtr); +} + +#endif // ASSIMP_BUILD_NO_3DS_IMPORTER + +#endif // !! ASSIMP_BUILD_NO_BASE_IMPORTER diff --git a/libs/assimp/code/AssetLib/ASE/ASEParser.h b/libs/assimp/code/AssetLib/ASE/ASEParser.h new file mode 100644 index 0000000..5c24fff --- /dev/null +++ b/libs/assimp/code/AssetLib/ASE/ASEParser.h @@ -0,0 +1,676 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines the helper data structures for importing ASE files */ +#ifndef AI_ASEFILEHELPER_H_INC +#define AI_ASEFILEHELPER_H_INC + +// public ASSIMP headers +#include <assimp/anim.h> +#include <assimp/mesh.h> +#include <assimp/types.h> + +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +// for some helper routines like IsSpace() +#include <assimp/ParsingUtils.h> +#include <assimp/qnan.h> + +// ASE is quite similar to 3ds. We can reuse some structures +#include "AssetLib/3DS/3DSLoader.h" + +namespace Assimp { +namespace ASE { + +using namespace D3DS; + +// --------------------------------------------------------------------------- +/** Helper structure representing an ASE material */ +struct Material : public D3DS::Material { + //! Default constructor has been deleted + Material() = delete; + + //! Constructor with explicit name + explicit Material(const std::string &name) : + D3DS::Material(name), + pcInstance(nullptr), + bNeed(false) { + // empty + } + + Material(const Material &other) = default; + + Material &operator=(const Material &other) { + if (this == &other) { + return *this; + } + + avSubMaterials = other.avSubMaterials; + pcInstance = other.pcInstance; + bNeed = other.bNeed; + + return *this; + } + + //! Move constructor. This is explicitly written because MSVC doesn't support defaulting it + Material(Material &&other) AI_NO_EXCEPT + : D3DS::Material(std::move(other)), + avSubMaterials(std::move(other.avSubMaterials)), + pcInstance(other.pcInstance), + bNeed(other.bNeed) { + other.pcInstance = nullptr; + } + + Material &operator=(Material &&other) AI_NO_EXCEPT { + if (this == &other) { + return *this; + } + + //D3DS::Material::operator=(std::move(other)); + + avSubMaterials = std::move(other.avSubMaterials); + pcInstance = other.pcInstance; + bNeed = other.bNeed; + + other.pcInstance = nullptr; + + return *this; + } + + ~Material() {} + + //! Contains all sub materials of this material + std::vector<Material> avSubMaterials; + + //! aiMaterial object + aiMaterial *pcInstance; + + //! Can we remove this material? + bool bNeed; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE file face */ +struct Face : public FaceWithSmoothingGroup { + //! Default constructor. Initializes everything with 0 + Face() AI_NO_EXCEPT + : iMaterial(DEFAULT_MATINDEX), + iFace(0) { + // empty + } + + //! special value to indicate that no material index has + //! been assigned to a face. The default material index + //! will replace this value later. + static const unsigned int DEFAULT_MATINDEX = 0xFFFFFFFF; + + //! Indices into each list of texture coordinates + unsigned int amUVIndices[AI_MAX_NUMBER_OF_TEXTURECOORDS][3]; + + //! Index into the list of vertex colors + unsigned int mColorIndices[3]; + + //! (Sub)Material index to be assigned to this face + unsigned int iMaterial; + + //! Index of the face. It is not specified whether it is + //! a requirement of the file format that all faces are + //! written in sequential order, so we have to expect this case + unsigned int iFace; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE file bone */ +struct Bone { + //! Constructor + Bone() = delete; + + //! Construction from an existing name + explicit Bone(const std::string &name) : + mName(name) { + // empty + } + + //! Name of the bone + std::string mName; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE file bone vertex */ +struct BoneVertex { + //! Bone and corresponding vertex weight. + //! -1 for unrequired bones .... + std::vector<std::pair<int, float>> mBoneWeights; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE file animation */ +struct Animation { + enum Type { + TRACK = 0x0, + BEZIER = 0x1, + TCB = 0x2 + } mRotationType, + mScalingType, mPositionType; + + Animation() AI_NO_EXCEPT + : mRotationType(TRACK), + mScalingType(TRACK), + mPositionType(TRACK) { + // empty + } + + //! List of track rotation keyframes + std::vector<aiQuatKey> akeyRotations; + + //! List of track position keyframes + std::vector<aiVectorKey> akeyPositions; + + //! List of track scaling keyframes + std::vector<aiVectorKey> akeyScaling; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent the inheritance information of an ASE node */ +struct InheritanceInfo { + //! Default constructor + InheritanceInfo() AI_NO_EXCEPT { + for (size_t i = 0; i < 3; ++i) { + abInheritPosition[i] = abInheritRotation[i] = abInheritScaling[i] = true; + } + } + + //! Inherit the parent's position?, axis order is x,y,z + bool abInheritPosition[3]; + + //! Inherit the parent's rotation?, axis order is x,y,z + bool abInheritRotation[3]; + + //! Inherit the parent's scaling?, axis order is x,y,z + bool abInheritScaling[3]; +}; + +// --------------------------------------------------------------------------- +/** Represents an ASE file node. Base class for mesh, light and cameras */ +struct BaseNode { + enum Type { + Light, + Camera, + Mesh, + Dummy + } mType; + + //! Construction from an existing name + BaseNode(Type _mType, const std::string &name) : + mType(_mType), mName(name), mProcessed(false) { + // Set mTargetPosition to qnan + const ai_real qnan = get_qnan(); + mTargetPosition.x = qnan; + } + + //! Name of the mesh + std::string mName; + + //! Name of the parent of the node + //! "" if there is no parent ... + std::string mParent; + + //! Transformation matrix of the node + aiMatrix4x4 mTransform; + + //! Target position (target lights and cameras) + aiVector3D mTargetPosition; + + //! Specifies which axes transformations a node inherits + //! from its parent ... + InheritanceInfo inherit; + + //! Animation channels for the node + Animation mAnim; + + //! Needed for lights and cameras: target animation channel + //! Should contain position keys only. + Animation mTargetAnim; + + bool mProcessed; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE file mesh */ +struct Mesh : public MeshWithSmoothingGroups<ASE::Face>, public BaseNode { + //! Default constructor has been deleted + Mesh() = delete; + + //! Construction from an existing name + explicit Mesh(const std::string &name) : + BaseNode(BaseNode::Mesh, name), mVertexColors(), mBoneVertices(), mBones(), iMaterialIndex(Face::DEFAULT_MATINDEX), bSkip(false) { + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c) { + this->mNumUVComponents[c] = 2; + } + } + + //! List of all texture coordinate sets + std::vector<aiVector3D> amTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + + //! List of all vertex color sets. + std::vector<aiColor4D> mVertexColors; + + //! List of all bone vertices + std::vector<BoneVertex> mBoneVertices; + + //! List of all bones + std::vector<Bone> mBones; + + //! Material index of the mesh + unsigned int iMaterialIndex; + + //! Number of vertex components for each UVW set + unsigned int mNumUVComponents[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + + //! used internally + bool bSkip; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE light source */ +struct Light : public BaseNode { + enum LightType { + OMNI, + TARGET, + FREE, + DIRECTIONAL + }; + + //! Default constructor has been deleted + Light() = delete; + + //! Construction from an existing name + explicit Light(const std::string &name) : + BaseNode(BaseNode::Light, name), mLightType(OMNI), mColor(1.f, 1.f, 1.f), mIntensity(1.f) // light is white by default + , + mAngle(45.f), + mFalloff(0.f) { + } + + LightType mLightType; + aiColor3D mColor; + ai_real mIntensity; + ai_real mAngle; // in degrees + ai_real mFalloff; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE camera */ +struct Camera : public BaseNode { + enum CameraType { + FREE, + TARGET + }; + + //! Default constructor has been deleted + Camera() = delete; + + //! Construction from an existing name + explicit Camera(const std::string &name) : + BaseNode(BaseNode::Camera, name), mFOV(0.75f) // in radians + , + mNear(0.1f), + mFar(1000.f) // could be zero + , + mCameraType(FREE) { + } + + ai_real mFOV, mNear, mFar; + CameraType mCameraType; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE helper object (dummy) */ +struct Dummy : public BaseNode { + //! Constructor + Dummy() AI_NO_EXCEPT + : BaseNode(BaseNode::Dummy, "DUMMY") { + // empty + } +}; + +// Parameters to Parser::Parse() +#define AI_ASE_NEW_FILE_FORMAT 200 +#define AI_ASE_OLD_FILE_FORMAT 110 + +// Internally we're a little bit more tolerant +#define AI_ASE_IS_NEW_FILE_FORMAT() (iFileFormat >= 200) +#define AI_ASE_IS_OLD_FILE_FORMAT() (iFileFormat < 200) + +// ------------------------------------------------------------------------------- +/** \brief Class to parse ASE files + */ +class Parser { +private: + Parser() AI_NO_EXCEPT { + // empty + } + +public: + // ------------------------------------------------------------------- + //! Construct a parser from a given input file which is + //! guaranteed to be terminated with zero. + //! @param szFile Input file + //! @param fileFormatDefault Assumed file format version. If the + //! file format is specified in the file the new value replaces + //! the default value. + Parser(const char *szFile, unsigned int fileFormatDefault); + + // ------------------------------------------------------------------- + //! Parses the file into the parsers internal representation + void Parse(); + +private: + // ------------------------------------------------------------------- + //! Parse the *SCENE block in a file + void ParseLV1SceneBlock(); + + // ------------------------------------------------------------------- + //! Parse the *MESH_SOFTSKINVERTS block in a file + void ParseLV1SoftSkinBlock(); + + // ------------------------------------------------------------------- + //! Parse the *MATERIAL_LIST block in a file + void ParseLV1MaterialListBlock(); + + // ------------------------------------------------------------------- + //! Parse a *<xxx>OBJECT block in a file + //! \param mesh Node to be filled + void ParseLV1ObjectBlock(BaseNode &mesh); + + // ------------------------------------------------------------------- + //! Parse a *MATERIAL blocks in a material list + //! \param mat Material structure to be filled + void ParseLV2MaterialBlock(Material &mat); + + // ------------------------------------------------------------------- + //! Parse a *NODE_TM block in a file + //! \param mesh Node (!) object to be filled + void ParseLV2NodeTransformBlock(BaseNode &mesh); + + // ------------------------------------------------------------------- + //! Parse a *TM_ANIMATION block in a file + //! \param mesh Mesh object to be filled + void ParseLV2AnimationBlock(BaseNode &mesh); + void ParseLV3PosAnimationBlock(ASE::Animation &anim); + void ParseLV3ScaleAnimationBlock(ASE::Animation &anim); + void ParseLV3RotAnimationBlock(ASE::Animation &anim); + + // ------------------------------------------------------------------- + //! Parse a *MESH block in a file + //! \param mesh Mesh object to be filled + void ParseLV2MeshBlock(Mesh &mesh); + + // ------------------------------------------------------------------- + //! Parse a *LIGHT_SETTINGS block in a file + //! \param light Light object to be filled + void ParseLV2LightSettingsBlock(Light &light); + + // ------------------------------------------------------------------- + //! Parse a *CAMERA_SETTINGS block in a file + //! \param cam Camera object to be filled + void ParseLV2CameraSettingsBlock(Camera &cam); + + // ------------------------------------------------------------------- + //! Parse the *MAP_XXXXXX blocks in a material + //! \param map Texture structure to be filled + void ParseLV3MapBlock(Texture &map); + + // ------------------------------------------------------------------- + //! Parse a *MESH_VERTEX_LIST block in a file + //! \param iNumVertices Value of *MESH_NUMVERTEX, if present. + //! Otherwise zero. This is used to check the consistency of the file. + //! A warning is sent to the logger if the validations fails. + //! \param mesh Mesh object to be filled + void ParseLV3MeshVertexListBlock( + unsigned int iNumVertices, Mesh &mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_FACE_LIST block in a file + //! \param iNumFaces Value of *MESH_NUMFACES, if present. + //! Otherwise zero. This is used to check the consistency of the file. + //! A warning is sent to the logger if the validations fails. + //! \param mesh Mesh object to be filled + void ParseLV3MeshFaceListBlock( + unsigned int iNumFaces, Mesh &mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_TVERT_LIST block in a file + //! \param iNumVertices Value of *MESH_NUMTVERTEX, if present. + //! Otherwise zero. This is used to check the consistency of the file. + //! A warning is sent to the logger if the validations fails. + //! \param mesh Mesh object to be filled + //! \param iChannel Output UVW channel + void ParseLV3MeshTListBlock( + unsigned int iNumVertices, Mesh &mesh, unsigned int iChannel = 0); + + // ------------------------------------------------------------------- + //! Parse a *MESH_TFACELIST block in a file + //! \param iNumFaces Value of *MESH_NUMTVFACES, if present. + //! Otherwise zero. This is used to check the consistency of the file. + //! A warning is sent to the logger if the validations fails. + //! \param mesh Mesh object to be filled + //! \param iChannel Output UVW channel + void ParseLV3MeshTFaceListBlock( + unsigned int iNumFaces, Mesh &mesh, unsigned int iChannel = 0); + + // ------------------------------------------------------------------- + //! Parse an additional mapping channel + //! (specified via *MESH_MAPPINGCHANNEL) + //! \param iChannel Channel index to be filled + //! \param mesh Mesh object to be filled + void ParseLV3MappingChannel( + unsigned int iChannel, Mesh &mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_CVERTLIST block in a file + //! \param iNumVertices Value of *MESH_NUMCVERTEX, if present. + //! Otherwise zero. This is used to check the consistency of the file. + //! A warning is sent to the logger if the validations fails. + //! \param mesh Mesh object to be filled + void ParseLV3MeshCListBlock( + unsigned int iNumVertices, Mesh &mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_CFACELIST block in a file + //! \param iNumFaces Value of *MESH_NUMCVFACES, if present. + //! Otherwise zero. This is used to check the consistency of the file. + //! A warning is sent to the logger if the validations fails. + //! \param mesh Mesh object to be filled + void ParseLV3MeshCFaceListBlock( + unsigned int iNumFaces, Mesh &mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_NORMALS block in a file + //! \param mesh Mesh object to be filled + void ParseLV3MeshNormalListBlock(Mesh &mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_WEIGHTSblock in a file + //! \param mesh Mesh object to be filled + void ParseLV3MeshWeightsBlock(Mesh &mesh); + + // ------------------------------------------------------------------- + //! Parse the bone list of a file + //! \param mesh Mesh object to be filled + //! \param iNumBones Number of bones in the mesh + void ParseLV4MeshBones(unsigned int iNumBones, Mesh &mesh); + + // ------------------------------------------------------------------- + //! Parse the bone vertices list of a file + //! \param mesh Mesh object to be filled + //! \param iNumVertices Number of vertices to be parsed + void ParseLV4MeshBonesVertices(unsigned int iNumVertices, Mesh &mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_FACE block in a file + //! \param out receive the face data + void ParseLV4MeshFace(ASE::Face &out); + + // ------------------------------------------------------------------- + //! Parse a *MESH_VERT block in a file + //! (also works for MESH_TVERT, MESH_CFACE, MESH_VERTCOL ...) + //! \param apOut Output buffer (3 floats) + //! \param rIndexOut Output index + void ParseLV4MeshFloatTriple(ai_real *apOut, unsigned int &rIndexOut); + + // ------------------------------------------------------------------- + //! Parse a *MESH_VERT block in a file + //! (also works for MESH_TVERT, MESH_CFACE, MESH_VERTCOL ...) + //! \param apOut Output buffer (3 floats) + void ParseLV4MeshFloatTriple(ai_real *apOut); + + // ------------------------------------------------------------------- + //! Parse a *MESH_TFACE block in a file + //! (also works for MESH_CFACE) + //! \param apOut Output buffer (3 ints) + //! \param rIndexOut Output index + void ParseLV4MeshLongTriple(unsigned int *apOut, unsigned int &rIndexOut); + + // ------------------------------------------------------------------- + //! Parse a *MESH_TFACE block in a file + //! (also works for MESH_CFACE) + //! \param apOut Output buffer (3 ints) + void ParseLV4MeshLongTriple(unsigned int *apOut); + + // ------------------------------------------------------------------- + //! Parse a single float element + //! \param fOut Output float + void ParseLV4MeshFloat(ai_real &fOut); + + // ------------------------------------------------------------------- + //! Parse a single int element + //! \param iOut Output integer + void ParseLV4MeshLong(unsigned int &iOut); + + // ------------------------------------------------------------------- + //! Skip everything to the next: '*' or '\0' + bool SkipToNextToken(); + + // ------------------------------------------------------------------- + //! Skip the current section until the token after the closing }. + //! This function handles embedded subsections correctly + bool SkipSection(); + + // ------------------------------------------------------------------- + //! Output a warning to the logger + //! \param szWarn Warn message + void LogWarning(const char *szWarn); + + // ------------------------------------------------------------------- + //! Output a message to the logger + //! \param szWarn Message + void LogInfo(const char *szWarn); + + // ------------------------------------------------------------------- + //! Output an error to the logger + //! \param szWarn Error message + AI_WONT_RETURN void LogError(const char *szWarn) AI_WONT_RETURN_SUFFIX; + + // ------------------------------------------------------------------- + //! Parse a string, enclosed in double quotation marks + //! \param out Output string + //! \param szName Name of the enclosing element -> used in error + //! messages. + //! \return false if an error occurred + bool ParseString(std::string &out, const char *szName); + +public: + //! Pointer to current data + const char *filePtr; + + //! background color to be passed to the viewer + //! QNAN if none was found + aiColor3D m_clrBackground; + + //! Base ambient color to be passed to all materials + //! QNAN if none was found + aiColor3D m_clrAmbient; + + //! List of all materials found in the file + std::vector<Material> m_vMaterials; + + //! List of all meshes found in the file + std::vector<Mesh> m_vMeshes; + + //! List of all dummies found in the file + std::vector<Dummy> m_vDummies; + + //! List of all lights found in the file + std::vector<Light> m_vLights; + + //! List of all cameras found in the file + std::vector<Camera> m_vCameras; + + //! Current line in the file + unsigned int iLineNumber; + + //! First frame + unsigned int iFirstFrame; + + //! Last frame + unsigned int iLastFrame; + + //! Frame speed - frames per second + unsigned int iFrameSpeed; + + //! Ticks per frame + unsigned int iTicksPerFrame; + + //! true if the last character read was an end-line character + bool bLastWasEndLine; + + //! File format version + unsigned int iFileFormat; +}; + +} // Namespace ASE +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_3DS_IMPORTER + +#endif // !! include guard diff --git a/libs/assimp/code/AssetLib/Assbin/AssbinExporter.cpp b/libs/assimp/code/AssetLib/Assbin/AssbinExporter.cpp new file mode 100644 index 0000000..149b3c5 --- /dev/null +++ b/libs/assimp/code/AssetLib/Assbin/AssbinExporter.cpp @@ -0,0 +1,68 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AssbinExporter.cpp + * ASSBIN exporter main code + */ + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER + +#include "AssbinFileWriter.h" + +#include <assimp/scene.h> +#include <assimp/Exporter.hpp> +#include <assimp/IOSystem.hpp> + +namespace Assimp { + +void ExportSceneAssbin(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) { + DumpSceneToAssbin( + pFile, + "\0", // no command(s). + pIOSystem, + pScene, + false, // shortened? + false); // compressed? +} +} // end of namespace Assimp + +#endif // ASSIMP_BUILD_NO_ASSBIN_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/Assbin/AssbinExporter.h b/libs/assimp/code/AssetLib/Assbin/AssbinExporter.h new file mode 100644 index 0000000..1801c16 --- /dev/null +++ b/libs/assimp/code/AssetLib/Assbin/AssbinExporter.h @@ -0,0 +1,60 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AssbinExporter.h + * ASSBIN Exporter Main Header + */ +#pragma once +#ifndef AI_ASSBINEXPORTER_H_INC +#define AI_ASSBINEXPORTER_H_INC + +#include <assimp/defs.h> + +#ifndef ASSIMP_BUILD_NO_EXPORT + +// nothing really needed here - reserved for future use like properties +namespace Assimp { + +void ASSIMP_API ExportSceneAssbin(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/); + +} +#endif +#endif // AI_ASSBINEXPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/Assbin/AssbinFileWriter.cpp b/libs/assimp/code/AssetLib/Assbin/AssbinFileWriter.cpp new file mode 100644 index 0000000..1d16f17 --- /dev/null +++ b/libs/assimp/code/AssetLib/Assbin/AssbinFileWriter.cpp @@ -0,0 +1,833 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AssbinFileWriter.cpp + * @brief Implementation of Assbin file writer. + */ + +#include "AssbinFileWriter.h" +#include "Common/assbin_chunks.h" +#include "PostProcessing/ProcessHelper.h" + +#include <assimp/Exceptional.h> +#include <assimp/version.h> +#include <assimp/IOStream.hpp> + +#ifdef ASSIMP_BUILD_NO_OWN_ZLIB +#include <zlib.h> +#else +#include "../contrib/zlib/zlib.h" +#endif + +#include <ctime> + +#if _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4706) +#endif // _MSC_VER + +namespace Assimp { + +template <typename T> +size_t Write(IOStream *stream, const T &v) { + return stream->Write(&v, sizeof(T), 1); +} + +// ----------------------------------------------------------------------------------- +// Serialize an aiString +template <> +inline size_t Write<aiString>(IOStream *stream, const aiString &s) { + const size_t s2 = (uint32_t)s.length; + stream->Write(&s, 4, 1); + stream->Write(s.data, s2, 1); + + return s2 + 4; +} + +// ----------------------------------------------------------------------------------- +// Serialize an unsigned int as uint32_t +template <> +inline size_t Write<unsigned int>(IOStream *stream, const unsigned int &w) { + const uint32_t t = (uint32_t)w; + if (w > t) { + // this shouldn't happen, integers in Assimp data structures never exceed 2^32 + throw DeadlyExportError("loss of data due to 64 -> 32 bit integer conversion"); + } + + stream->Write(&t, 4, 1); + + return 4; +} + +// ----------------------------------------------------------------------------------- +// Serialize an unsigned int as uint16_t +template <> +inline size_t Write<uint16_t>(IOStream *stream, const uint16_t &w) { + static_assert(sizeof(uint16_t) == 2, "sizeof(uint16_t)==2"); + stream->Write(&w, 2, 1); + + return 2; +} + +// ----------------------------------------------------------------------------------- +// Serialize a float +template <> +inline size_t Write<float>(IOStream *stream, const float &f) { + static_assert(sizeof(float) == 4, "sizeof(float)==4"); + stream->Write(&f, 4, 1); + + return 4; +} + +// ----------------------------------------------------------------------------------- +// Serialize a double +template <> +inline size_t Write<double>(IOStream *stream, const double &f) { + static_assert(sizeof(double) == 8, "sizeof(double)==8"); + stream->Write(&f, 8, 1); + + return 8; +} + +// ----------------------------------------------------------------------------------- +// Serialize a vec3 +template <> +inline size_t Write<aiVector3D>(IOStream *stream, const aiVector3D &v) { + size_t t = Write<float>(stream, v.x); + t += Write<float>(stream, v.y); + t += Write<float>(stream, v.z); + + return t; +} + +// ----------------------------------------------------------------------------------- +// Serialize a color value +template <> +inline size_t Write<aiColor3D>(IOStream *stream, const aiColor3D &v) { + size_t t = Write<float>(stream, v.r); + t += Write<float>(stream, v.g); + t += Write<float>(stream, v.b); + + return t; +} + +// ----------------------------------------------------------------------------------- +// Serialize a color value +template <> +inline size_t Write<aiColor4D>(IOStream *stream, const aiColor4D &v) { + size_t t = Write<float>(stream, v.r); + t += Write<float>(stream, v.g); + t += Write<float>(stream, v.b); + t += Write<float>(stream, v.a); + + return t; +} + +// ----------------------------------------------------------------------------------- +// Serialize a quaternion +template <> +inline size_t Write<aiQuaternion>(IOStream *stream, const aiQuaternion &v) { + size_t t = Write<float>(stream, v.w); + t += Write<float>(stream, v.x); + t += Write<float>(stream, v.y); + t += Write<float>(stream, v.z); + ai_assert(t == 16); + + return t; +} + +// ----------------------------------------------------------------------------------- +// Serialize a vertex weight +template <> +inline size_t Write<aiVertexWeight>(IOStream *stream, const aiVertexWeight &v) { + size_t t = Write<unsigned int>(stream, v.mVertexId); + + return t + Write<float>(stream, v.mWeight); +} + +constexpr size_t MatrixSize = 64; + +// ----------------------------------------------------------------------------------- +// Serialize a mat4x4 +template <> +inline size_t Write<aiMatrix4x4>(IOStream *stream, const aiMatrix4x4 &m) { + for (unsigned int i = 0; i < 4; ++i) { + for (unsigned int i2 = 0; i2 < 4; ++i2) { + Write<float>(stream, m[i][i2]); + } + } + + return MatrixSize; +} + +// ----------------------------------------------------------------------------------- +// Serialize an aiVectorKey +template <> +inline size_t Write<aiVectorKey>(IOStream *stream, const aiVectorKey &v) { + const size_t t = Write<double>(stream, v.mTime); + return t + Write<aiVector3D>(stream, v.mValue); +} + +// ----------------------------------------------------------------------------------- +// Serialize an aiQuatKey +template <> +inline size_t Write<aiQuatKey>(IOStream *stream, const aiQuatKey &v) { + const size_t t = Write<double>(stream, v.mTime); + return t + Write<aiQuaternion>(stream, v.mValue); +} + +template <typename T> +inline size_t WriteBounds(IOStream *stream, const T *in, unsigned int size) { + T minc, maxc; + ArrayBounds(in, size, minc, maxc); + + const size_t t = Write<T>(stream, minc); + return t + Write<T>(stream, maxc); +} + +// We use this to write out non-byte arrays so that we write using the specializations. +// This way we avoid writing out extra bytes that potentially come from struct alignment. +template <typename T> +inline size_t WriteArray(IOStream *stream, const T *in, unsigned int size) { + size_t n = 0; + for (unsigned int i = 0; i < size; i++) + n += Write<T>(stream, in[i]); + + return n; +} + +// ---------------------------------------------------------------------------------- +/** @class AssbinChunkWriter + * @brief Chunk writer mechanism for the .assbin file structure + * + * This is a standard in-memory IOStream (most of the code is based on BlobIOStream), + * the difference being that this takes another IOStream as a "container" in the + * constructor, and when it is destroyed, it appends the magic number, the chunk size, + * and the chunk contents to the container stream. This allows relatively easy chunk + * chunk construction, even recursively. + */ +class AssbinChunkWriter : public IOStream { +private: + uint8_t *buffer; + uint32_t magic; + IOStream *container; + size_t cur_size, cursor, initial; + +private: + // ------------------------------------------------------------------- + void Grow(size_t need = 0) { + size_t new_size = std::max(initial, std::max(need, cur_size + (cur_size >> 1))); + + const uint8_t *const old = buffer; + buffer = new uint8_t[new_size]; + + if (old) { + memcpy(buffer, old, cur_size); + delete[] old; + } + + cur_size = new_size; + } + +public: + AssbinChunkWriter(IOStream *container, uint32_t magic, size_t initial = 4096) : + buffer(nullptr), + magic(magic), + container(container), + cur_size(0), + cursor(0), + initial(initial) { + // empty + } + + ~AssbinChunkWriter() override { + if (container) { + container->Write(&magic, sizeof(uint32_t), 1); + container->Write(&cursor, sizeof(uint32_t), 1); + container->Write(buffer, 1, cursor); + } + if (buffer) delete[] buffer; + } + + void *GetBufferPointer() { return buffer; } + + size_t Read(void * /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/) override { + return 0; + } + + aiReturn Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/) override { + return aiReturn_FAILURE; + } + + size_t Tell() const override { + return cursor; + } + + void Flush() override { + // not implemented + } + + size_t FileSize() const override { + return cursor; + } + + size_t Write(const void *pvBuffer, size_t pSize, size_t pCount) override { + pSize *= pCount; + if (cursor + pSize > cur_size) { + Grow(cursor + pSize); + } + + memcpy(buffer + cursor, pvBuffer, pSize); + cursor += pSize; + + return pCount; + } +}; + +// ---------------------------------------------------------------------------------- +/** @class AssbinFileWriter + * @brief Assbin file writer class + * + * This class writes an .assbin file, and is responsible for the file layout. + */ +class AssbinFileWriter { +private: + bool shortened; + bool compressed; + +protected: + // ----------------------------------------------------------------------------------- + void WriteBinaryNode(IOStream *container, const aiNode *node) { + AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AINODE); + + unsigned int nb_metadata = (node->mMetaData != nullptr ? node->mMetaData->mNumProperties : 0); + + Write<aiString>(&chunk, node->mName); + Write<aiMatrix4x4>(&chunk, node->mTransformation); + Write<unsigned int>(&chunk, node->mNumChildren); + Write<unsigned int>(&chunk, node->mNumMeshes); + Write<unsigned int>(&chunk, nb_metadata); + + for (unsigned int i = 0; i < node->mNumMeshes; ++i) { + Write<unsigned int>(&chunk, node->mMeshes[i]); + } + + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + WriteBinaryNode(&chunk, node->mChildren[i]); + } + + for (unsigned int i = 0; i < nb_metadata; ++i) { + const aiString &key = node->mMetaData->mKeys[i]; + aiMetadataType type = node->mMetaData->mValues[i].mType; + void *value = node->mMetaData->mValues[i].mData; + + Write<aiString>(&chunk, key); + Write<uint16_t>(&chunk, (uint16_t)type); + + switch (type) { + case AI_BOOL: + Write<bool>(&chunk, *((bool *)value)); + break; + case AI_INT32: + Write<int32_t>(&chunk, *((int32_t *)value)); + break; + case AI_UINT64: + Write<uint64_t>(&chunk, *((uint64_t *)value)); + break; + case AI_FLOAT: + Write<float>(&chunk, *((float *)value)); + break; + case AI_DOUBLE: + Write<double>(&chunk, *((double *)value)); + break; + case AI_AISTRING: + Write<aiString>(&chunk, *((aiString *)value)); + break; + case AI_AIVECTOR3D: + Write<aiVector3D>(&chunk, *((aiVector3D *)value)); + break; +#ifdef SWIG + case FORCE_32BIT: +#endif // SWIG + default: + break; + } + } + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryTexture(IOStream *container, const aiTexture *tex) { + AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AITEXTURE); + + Write<unsigned int>(&chunk, tex->mWidth); + Write<unsigned int>(&chunk, tex->mHeight); + // Write the texture format, but don't include the null terminator. + chunk.Write(tex->achFormatHint, sizeof(char), HINTMAXTEXTURELEN - 1); + + if (!shortened) { + if (!tex->mHeight) { + chunk.Write(tex->pcData, 1, tex->mWidth); + } else { + chunk.Write(tex->pcData, 1, tex->mWidth * tex->mHeight * 4); + } + } + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryBone(IOStream *container, const aiBone *b) { + AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AIBONE); + + Write<aiString>(&chunk, b->mName); + Write<unsigned int>(&chunk, b->mNumWeights); + Write<aiMatrix4x4>(&chunk, b->mOffsetMatrix); + + // for the moment we write dumb min/max values for the bones, too. + // maybe I'll add a better, hash-like solution later + if (shortened) { + WriteBounds(&chunk, b->mWeights, b->mNumWeights); + } // else write as usual + else + WriteArray<aiVertexWeight>(&chunk, b->mWeights, b->mNumWeights); + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryMesh(IOStream *container, const aiMesh *mesh) { + AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AIMESH); + + Write<unsigned int>(&chunk, mesh->mPrimitiveTypes); + Write<unsigned int>(&chunk, mesh->mNumVertices); + Write<unsigned int>(&chunk, mesh->mNumFaces); + Write<unsigned int>(&chunk, mesh->mNumBones); + Write<unsigned int>(&chunk, mesh->mMaterialIndex); + + // first of all, write bits for all existent vertex components + unsigned int c = 0; + if (mesh->mVertices) { + c |= ASSBIN_MESH_HAS_POSITIONS; + } + if (mesh->mNormals) { + c |= ASSBIN_MESH_HAS_NORMALS; + } + if (mesh->mTangents && mesh->mBitangents) { + c |= ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS; + } + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++n) { + if (!mesh->mTextureCoords[n]) { + break; + } + c |= ASSBIN_MESH_HAS_TEXCOORD(n); + } + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS; ++n) { + if (!mesh->mColors[n]) { + break; + } + c |= ASSBIN_MESH_HAS_COLOR(n); + } + Write<unsigned int>(&chunk, c); + + aiVector3D minVec, maxVec; + if (mesh->mVertices) { + if (shortened) { + WriteBounds(&chunk, mesh->mVertices, mesh->mNumVertices); + } // else write as usual + else + WriteArray<aiVector3D>(&chunk, mesh->mVertices, mesh->mNumVertices); + } + if (mesh->mNormals) { + if (shortened) { + WriteBounds(&chunk, mesh->mNormals, mesh->mNumVertices); + } // else write as usual + else + WriteArray<aiVector3D>(&chunk, mesh->mNormals, mesh->mNumVertices); + } + if (mesh->mTangents && mesh->mBitangents) { + if (shortened) { + WriteBounds(&chunk, mesh->mTangents, mesh->mNumVertices); + WriteBounds(&chunk, mesh->mBitangents, mesh->mNumVertices); + } // else write as usual + else { + WriteArray<aiVector3D>(&chunk, mesh->mTangents, mesh->mNumVertices); + WriteArray<aiVector3D>(&chunk, mesh->mBitangents, mesh->mNumVertices); + } + } + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS; ++n) { + if (!mesh->mColors[n]) + break; + + if (shortened) { + WriteBounds(&chunk, mesh->mColors[n], mesh->mNumVertices); + } // else write as usual + else + WriteArray<aiColor4D>(&chunk, mesh->mColors[n], mesh->mNumVertices); + } + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++n) { + if (!mesh->mTextureCoords[n]) + break; + + // write number of UV components + Write<unsigned int>(&chunk, mesh->mNumUVComponents[n]); + + if (shortened) { + WriteBounds(&chunk, mesh->mTextureCoords[n], mesh->mNumVertices); + } // else write as usual + else + WriteArray<aiVector3D>(&chunk, mesh->mTextureCoords[n], mesh->mNumVertices); + } + + // write faces. There are no floating-point calculations involved + // in these, so we can write a simple hash over the face data + // to the dump file. We generate a single 32 Bit hash for 512 faces + // using Assimp's standard hashing function. + if (shortened) { + unsigned int processed = 0; + for (unsigned int job; (job = std::min(mesh->mNumFaces - processed, 512u)); processed += job) { + uint32_t hash = 0; + for (unsigned int a = 0; a < job; ++a) { + + const aiFace &f = mesh->mFaces[processed + a]; + uint32_t tmp = f.mNumIndices; + hash = SuperFastHash(reinterpret_cast<const char *>(&tmp), sizeof tmp, hash); + for (unsigned int i = 0; i < f.mNumIndices; ++i) { + static_assert(AI_MAX_VERTICES <= 0xffffffff, "AI_MAX_VERTICES <= 0xffffffff"); + tmp = static_cast<uint32_t>(f.mIndices[i]); + hash = SuperFastHash(reinterpret_cast<const char *>(&tmp), sizeof tmp, hash); + } + } + Write<unsigned int>(&chunk, hash); + } + } else // else write as usual + { + // if there are less than 2^16 vertices, we can simply use 16 bit integers ... + for (unsigned int i = 0; i < mesh->mNumFaces; ++i) { + const aiFace &f = mesh->mFaces[i]; + + static_assert(AI_MAX_FACE_INDICES <= 0xffff, "AI_MAX_FACE_INDICES <= 0xffff"); + Write<uint16_t>(&chunk, static_cast<uint16_t>(f.mNumIndices)); + + for (unsigned int a = 0; a < f.mNumIndices; ++a) { + if (mesh->mNumVertices < (1u << 16)) { + Write<uint16_t>(&chunk, static_cast<uint16_t>(f.mIndices[a])); + } else { + Write<unsigned int>(&chunk, f.mIndices[a]); + } + } + } + } + + // write bones + if (mesh->mNumBones) { + for (unsigned int a = 0; a < mesh->mNumBones; ++a) { + const aiBone *b = mesh->mBones[a]; + WriteBinaryBone(&chunk, b); + } + } + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryMaterialProperty(IOStream *container, const aiMaterialProperty *prop) { + AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AIMATERIALPROPERTY); + + Write<aiString>(&chunk, prop->mKey); + Write<unsigned int>(&chunk, prop->mSemantic); + Write<unsigned int>(&chunk, prop->mIndex); + + Write<unsigned int>(&chunk, prop->mDataLength); + Write<unsigned int>(&chunk, (unsigned int)prop->mType); + chunk.Write(prop->mData, 1, prop->mDataLength); + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryMaterial(IOStream *container, const aiMaterial *mat) { + AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AIMATERIAL); + + Write<unsigned int>(&chunk, mat->mNumProperties); + for (unsigned int i = 0; i < mat->mNumProperties; ++i) { + WriteBinaryMaterialProperty(&chunk, mat->mProperties[i]); + } + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryNodeAnim(IOStream *container, const aiNodeAnim *nd) { + AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AINODEANIM); + + Write<aiString>(&chunk, nd->mNodeName); + Write<unsigned int>(&chunk, nd->mNumPositionKeys); + Write<unsigned int>(&chunk, nd->mNumRotationKeys); + Write<unsigned int>(&chunk, nd->mNumScalingKeys); + Write<unsigned int>(&chunk, nd->mPreState); + Write<unsigned int>(&chunk, nd->mPostState); + + if (nd->mPositionKeys) { + if (shortened) { + WriteBounds(&chunk, nd->mPositionKeys, nd->mNumPositionKeys); + + } // else write as usual + else + WriteArray<aiVectorKey>(&chunk, nd->mPositionKeys, nd->mNumPositionKeys); + } + if (nd->mRotationKeys) { + if (shortened) { + WriteBounds(&chunk, nd->mRotationKeys, nd->mNumRotationKeys); + + } // else write as usual + else + WriteArray<aiQuatKey>(&chunk, nd->mRotationKeys, nd->mNumRotationKeys); + } + if (nd->mScalingKeys) { + if (shortened) { + WriteBounds(&chunk, nd->mScalingKeys, nd->mNumScalingKeys); + + } // else write as usual + else + WriteArray<aiVectorKey>(&chunk, nd->mScalingKeys, nd->mNumScalingKeys); + } + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryAnim(IOStream *container, const aiAnimation *anim) { + AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AIANIMATION); + + Write<aiString>(&chunk, anim->mName); + Write<double>(&chunk, anim->mDuration); + Write<double>(&chunk, anim->mTicksPerSecond); + Write<unsigned int>(&chunk, anim->mNumChannels); + + for (unsigned int a = 0; a < anim->mNumChannels; ++a) { + const aiNodeAnim *nd = anim->mChannels[a]; + WriteBinaryNodeAnim(&chunk, nd); + } + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryLight(IOStream *container, const aiLight *l) { + AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AILIGHT); + + Write<aiString>(&chunk, l->mName); + Write<unsigned int>(&chunk, l->mType); + + if (l->mType != aiLightSource_DIRECTIONAL) { + Write<float>(&chunk, l->mAttenuationConstant); + Write<float>(&chunk, l->mAttenuationLinear); + Write<float>(&chunk, l->mAttenuationQuadratic); + } + + Write<aiColor3D>(&chunk, l->mColorDiffuse); + Write<aiColor3D>(&chunk, l->mColorSpecular); + Write<aiColor3D>(&chunk, l->mColorAmbient); + + if (l->mType == aiLightSource_SPOT) { + Write<float>(&chunk, l->mAngleInnerCone); + Write<float>(&chunk, l->mAngleOuterCone); + } + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryCamera(IOStream *container, const aiCamera *cam) { + AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AICAMERA); + + Write<aiString>(&chunk, cam->mName); + Write<aiVector3D>(&chunk, cam->mPosition); + Write<aiVector3D>(&chunk, cam->mLookAt); + Write<aiVector3D>(&chunk, cam->mUp); + Write<float>(&chunk, cam->mHorizontalFOV); + Write<float>(&chunk, cam->mClipPlaneNear); + Write<float>(&chunk, cam->mClipPlaneFar); + Write<float>(&chunk, cam->mAspect); + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryScene(IOStream *container, const aiScene *scene) { + AssbinChunkWriter chunk(container, ASSBIN_CHUNK_AISCENE); + + // basic scene information + Write<unsigned int>(&chunk, scene->mFlags); + Write<unsigned int>(&chunk, scene->mNumMeshes); + Write<unsigned int>(&chunk, scene->mNumMaterials); + Write<unsigned int>(&chunk, scene->mNumAnimations); + Write<unsigned int>(&chunk, scene->mNumTextures); + Write<unsigned int>(&chunk, scene->mNumLights); + Write<unsigned int>(&chunk, scene->mNumCameras); + + // write node graph + WriteBinaryNode(&chunk, scene->mRootNode); + + // write all meshes + for (unsigned int i = 0; i < scene->mNumMeshes; ++i) { + const aiMesh *mesh = scene->mMeshes[i]; + WriteBinaryMesh(&chunk, mesh); + } + + // write materials + for (unsigned int i = 0; i < scene->mNumMaterials; ++i) { + const aiMaterial *mat = scene->mMaterials[i]; + WriteBinaryMaterial(&chunk, mat); + } + + // write all animations + for (unsigned int i = 0; i < scene->mNumAnimations; ++i) { + const aiAnimation *anim = scene->mAnimations[i]; + WriteBinaryAnim(&chunk, anim); + } + + // write all textures + for (unsigned int i = 0; i < scene->mNumTextures; ++i) { + const aiTexture *mesh = scene->mTextures[i]; + WriteBinaryTexture(&chunk, mesh); + } + + // write lights + for (unsigned int i = 0; i < scene->mNumLights; ++i) { + const aiLight *l = scene->mLights[i]; + WriteBinaryLight(&chunk, l); + } + + // write cameras + for (unsigned int i = 0; i < scene->mNumCameras; ++i) { + const aiCamera *cam = scene->mCameras[i]; + WriteBinaryCamera(&chunk, cam); + } + } + +public: + AssbinFileWriter(bool shortened, bool compressed) : + shortened(shortened), compressed(compressed) { + } + + // ----------------------------------------------------------------------------------- + // Write a binary model dump + void WriteBinaryDump(const char *pFile, const char *cmd, IOSystem *pIOSystem, const aiScene *pScene) { + IOStream *out = pIOSystem->Open(pFile, "wb"); + if (!out) + throw std::runtime_error("Unable to open output file " + std::string(pFile) + '\n'); + + auto CloseIOStream = [&]() { + if (out) { + pIOSystem->Close(out); + out = nullptr; // Ensure this is only done once. + } + }; + + try { + time_t tt = time(nullptr); +#if _WIN32 + tm *p = gmtime(&tt); +#else + struct tm now; + tm *p = gmtime_r(&tt, &now); +#endif + + // header + char s[64]; + memset(s, 0, 64); +#if _MSC_VER >= 1400 + sprintf_s(s, "ASSIMP.binary-dump.%s", asctime(p)); +#else + ai_snprintf(s, 64, "ASSIMP.binary-dump.%s", asctime(p)); +#endif + out->Write(s, 44, 1); + // == 44 bytes + + Write<unsigned int>(out, ASSBIN_VERSION_MAJOR); + Write<unsigned int>(out, ASSBIN_VERSION_MINOR); + Write<unsigned int>(out, aiGetVersionRevision()); + Write<unsigned int>(out, aiGetCompileFlags()); + Write<uint16_t>(out, shortened); + Write<uint16_t>(out, compressed); + // == 20 bytes + + char buff[256] = { 0 }; + ai_snprintf(buff, 256, "%s", pFile); + out->Write(buff, sizeof(char), 256); + + memset(buff, 0, sizeof(buff)); + ai_snprintf(buff, 128, "%s", cmd); + out->Write(buff, sizeof(char), 128); + + // leave 64 bytes free for future extensions + memset(buff, 0xcd, 64); + out->Write(buff, sizeof(char), 64); + // == 435 bytes + + // ==== total header size: 512 bytes + ai_assert(out->Tell() == ASSBIN_HEADER_LENGTH); + + // Up to here the data is uncompressed. For compressed files, the rest + // is compressed using standard DEFLATE from zlib. + if (compressed) { + AssbinChunkWriter uncompressedStream(nullptr, 0); + WriteBinaryScene(&uncompressedStream, pScene); + + uLongf uncompressedSize = static_cast<uLongf>(uncompressedStream.Tell()); + uLongf compressedSize = (uLongf)compressBound(uncompressedSize); + uint8_t *compressedBuffer = new uint8_t[compressedSize]; + + int res = compress2(compressedBuffer, &compressedSize, (const Bytef *)uncompressedStream.GetBufferPointer(), uncompressedSize, 9); + if (res != Z_OK) { + delete[] compressedBuffer; + throw DeadlyExportError("Compression failed."); + } + + out->Write(&uncompressedSize, sizeof(uint32_t), 1); + out->Write(compressedBuffer, sizeof(char), compressedSize); + + delete[] compressedBuffer; + } else { + WriteBinaryScene(out, pScene); + } + + CloseIOStream(); + } catch (...) { + CloseIOStream(); + throw; + } + } +}; + +void DumpSceneToAssbin( + const char *pFile, const char *cmd, IOSystem *pIOSystem, + const aiScene *pScene, bool shortened, bool compressed) { + AssbinFileWriter fileWriter(shortened, compressed); + fileWriter.WriteBinaryDump(pFile, cmd, pIOSystem, pScene); +} +#if _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + +} // end of namespace Assimp diff --git a/libs/assimp/code/AssetLib/Assbin/AssbinFileWriter.h b/libs/assimp/code/AssetLib/Assbin/AssbinFileWriter.h new file mode 100644 index 0000000..cfed3b4 --- /dev/null +++ b/libs/assimp/code/AssetLib/Assbin/AssbinFileWriter.h @@ -0,0 +1,65 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AssbinFileWriter.h + * @brief Declaration of Assbin file writer. + */ + +#ifndef AI_ASSBINFILEWRITER_H_INC +#define AI_ASSBINFILEWRITER_H_INC + +#include <assimp/defs.h> +#include <assimp/scene.h> +#include <assimp/IOSystem.hpp> + +namespace Assimp { + +void ASSIMP_API DumpSceneToAssbin( + const char *pFile, + const char *cmd, + IOSystem *pIOSystem, + const aiScene *pScene, + bool shortened, + bool compressed); + +} + +#endif // AI_ASSBINFILEWRITER_H_INC diff --git a/libs/assimp/code/AssetLib/Assbin/AssbinLoader.cpp b/libs/assimp/code/AssetLib/Assbin/AssbinLoader.cpp new file mode 100644 index 0000000..060ce43 --- /dev/null +++ b/libs/assimp/code/AssetLib/Assbin/AssbinLoader.cpp @@ -0,0 +1,730 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AssbinLoader.cpp + * @brief Implementation of the .assbin importer class + * + * see assbin_chunks.h + */ + +#ifndef ASSIMP_BUILD_NO_ASSBIN_IMPORTER + +// internal headers +#include "AssetLib/Assbin/AssbinLoader.h" +#include "Common/assbin_chunks.h" +#include <assimp/MemoryIOWrapper.h> +#include <assimp/anim.h> +#include <assimp/importerdesc.h> +#include <assimp/mesh.h> +#include <assimp/scene.h> +#include <memory> + +#ifdef ASSIMP_BUILD_NO_OWN_ZLIB +#include <zlib.h> +#else +#include <contrib/zlib/zlib.h> +#endif + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Assimp Binary Importer", + "Gargaj / Conspiracy", + "", + "", + aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour, + 0, + 0, + 0, + 0, + "assbin" +}; + +// ----------------------------------------------------------------------------------- +const aiImporterDesc *AssbinImporter::GetInfo() const { + return &desc; +} + +// ----------------------------------------------------------------------------------- +bool AssbinImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + IOStream *in = pIOHandler->Open(pFile); + if (nullptr == in) { + return false; + } + + char s[32]; + in->Read(s, sizeof(char), 32); + + pIOHandler->Close(in); + + return strncmp(s, "ASSIMP.binary-dump.", 19) == 0; +} + +// ----------------------------------------------------------------------------------- +template <typename T> +T Read(IOStream *stream) { + T t; + size_t res = stream->Read(&t, sizeof(T), 1); + if (res != 1) { + throw DeadlyImportError("Unexpected EOF"); + } + return t; +} + +// ----------------------------------------------------------------------------------- +template <> +aiVector3D Read<aiVector3D>(IOStream *stream) { + aiVector3D v; + v.x = Read<ai_real>(stream); + v.y = Read<ai_real>(stream); + v.z = Read<ai_real>(stream); + return v; +} + +// ----------------------------------------------------------------------------------- +template <> +aiColor4D Read<aiColor4D>(IOStream *stream) { + aiColor4D c; + c.r = Read<ai_real>(stream); + c.g = Read<ai_real>(stream); + c.b = Read<ai_real>(stream); + c.a = Read<ai_real>(stream); + return c; +} + +// ----------------------------------------------------------------------------------- +template <> +aiQuaternion Read<aiQuaternion>(IOStream *stream) { + aiQuaternion v; + v.w = Read<ai_real>(stream); + v.x = Read<ai_real>(stream); + v.y = Read<ai_real>(stream); + v.z = Read<ai_real>(stream); + return v; +} + +// ----------------------------------------------------------------------------------- +template <> +aiString Read<aiString>(IOStream *stream) { + aiString s; + stream->Read(&s.length, 4, 1); + if (s.length) { + stream->Read(s.data, s.length, 1); + } + s.data[s.length] = 0; + + return s; +} + +// ----------------------------------------------------------------------------------- +template <> +aiVertexWeight Read<aiVertexWeight>(IOStream *stream) { + aiVertexWeight w; + w.mVertexId = Read<unsigned int>(stream); + w.mWeight = Read<ai_real>(stream); + return w; +} + +// ----------------------------------------------------------------------------------- +template <> +aiMatrix4x4 Read<aiMatrix4x4>(IOStream *stream) { + aiMatrix4x4 m; + for (unsigned int i = 0; i < 4; ++i) { + for (unsigned int i2 = 0; i2 < 4; ++i2) { + m[i][i2] = Read<ai_real>(stream); + } + } + return m; +} + +// ----------------------------------------------------------------------------------- +template <> +aiVectorKey Read<aiVectorKey>(IOStream *stream) { + aiVectorKey v; + v.mTime = Read<double>(stream); + v.mValue = Read<aiVector3D>(stream); + return v; +} + +// ----------------------------------------------------------------------------------- +template <> +aiQuatKey Read<aiQuatKey>(IOStream *stream) { + aiQuatKey v; + v.mTime = Read<double>(stream); + v.mValue = Read<aiQuaternion>(stream); + return v; +} + +// ----------------------------------------------------------------------------------- +template <typename T> +void ReadArray(IOStream *stream, T *out, unsigned int size) { + ai_assert(nullptr != stream); + ai_assert(nullptr != out); + + for (unsigned int i = 0; i < size; i++) { + out[i] = Read<T>(stream); + } +} + +// ----------------------------------------------------------------------------------- +template <typename T> +void ReadBounds(IOStream *stream, T * /*p*/, unsigned int n) { + // not sure what to do here, the data isn't really useful. + stream->Seek(sizeof(T) * n, aiOrigin_CUR); +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryNode(IOStream *stream, aiNode **onode, aiNode *parent) { + if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AINODE) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/Read<uint32_t>(stream); + + std::unique_ptr<aiNode> node(new aiNode()); + + node->mName = Read<aiString>(stream); + node->mTransformation = Read<aiMatrix4x4>(stream); + unsigned numChildren = Read<unsigned int>(stream); + unsigned numMeshes = Read<unsigned int>(stream); + unsigned int nb_metadata = Read<unsigned int>(stream); + + if (parent) { + node->mParent = parent; + } + + if (numMeshes) { + node->mMeshes = new unsigned int[numMeshes]; + for (unsigned int i = 0; i < numMeshes; ++i) { + node->mMeshes[i] = Read<unsigned int>(stream); + node->mNumMeshes++; + } + } + + if (numChildren) { + node->mChildren = new aiNode *[numChildren]; + for (unsigned int i = 0; i < numChildren; ++i) { + ReadBinaryNode(stream, &node->mChildren[i], node.get()); + node->mNumChildren++; + } + } + + if (nb_metadata > 0) { + node->mMetaData = aiMetadata::Alloc(nb_metadata); + for (unsigned int i = 0; i < nb_metadata; ++i) { + node->mMetaData->mKeys[i] = Read<aiString>(stream); + node->mMetaData->mValues[i].mType = (aiMetadataType)Read<uint16_t>(stream); + void *data = nullptr; + + switch (node->mMetaData->mValues[i].mType) { + case AI_BOOL: + data = new bool(Read<bool>(stream)); + break; + case AI_INT32: + data = new int32_t(Read<int32_t>(stream)); + break; + case AI_UINT64: + data = new uint64_t(Read<uint64_t>(stream)); + break; + case AI_FLOAT: + data = new ai_real(Read<ai_real>(stream)); + break; + case AI_DOUBLE: + data = new double(Read<double>(stream)); + break; + case AI_AISTRING: + data = new aiString(Read<aiString>(stream)); + break; + case AI_AIVECTOR3D: + data = new aiVector3D(Read<aiVector3D>(stream)); + break; +#ifndef SWIG + case FORCE_32BIT: +#endif // SWIG + default: + break; + } + + node->mMetaData->mValues[i].mData = data; + } + } + *onode = node.release(); +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryBone(IOStream *stream, aiBone *b) { + if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AIBONE) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/Read<uint32_t>(stream); + + b->mName = Read<aiString>(stream); + b->mNumWeights = Read<unsigned int>(stream); + b->mOffsetMatrix = Read<aiMatrix4x4>(stream); + + // for the moment we write dumb min/max values for the bones, too. + // maybe I'll add a better, hash-like solution later + if (shortened) { + ReadBounds(stream, b->mWeights, b->mNumWeights); + } else { + // else write as usual + b->mWeights = new aiVertexWeight[b->mNumWeights]; + ReadArray<aiVertexWeight>(stream, b->mWeights, b->mNumWeights); + } +} + +// ----------------------------------------------------------------------------------- +static bool fitsIntoUI16(unsigned int mNumVertices) { + return (mNumVertices < (1u << 16)); +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryMesh(IOStream *stream, aiMesh *mesh) { + if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AIMESH) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/Read<uint32_t>(stream); + + mesh->mPrimitiveTypes = Read<unsigned int>(stream); + mesh->mNumVertices = Read<unsigned int>(stream); + mesh->mNumFaces = Read<unsigned int>(stream); + mesh->mNumBones = Read<unsigned int>(stream); + mesh->mMaterialIndex = Read<unsigned int>(stream); + + // first of all, write bits for all existent vertex components + unsigned int c = Read<unsigned int>(stream); + + if (c & ASSBIN_MESH_HAS_POSITIONS) { + if (shortened) { + ReadBounds(stream, mesh->mVertices, mesh->mNumVertices); + } else { + // else write as usual + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + ReadArray<aiVector3D>(stream, mesh->mVertices, mesh->mNumVertices); + } + } + if (c & ASSBIN_MESH_HAS_NORMALS) { + if (shortened) { + ReadBounds(stream, mesh->mNormals, mesh->mNumVertices); + } else { + // else write as usual + mesh->mNormals = new aiVector3D[mesh->mNumVertices]; + ReadArray<aiVector3D>(stream, mesh->mNormals, mesh->mNumVertices); + } + } + if (c & ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS) { + if (shortened) { + ReadBounds(stream, mesh->mTangents, mesh->mNumVertices); + ReadBounds(stream, mesh->mBitangents, mesh->mNumVertices); + } else { + // else write as usual + mesh->mTangents = new aiVector3D[mesh->mNumVertices]; + ReadArray<aiVector3D>(stream, mesh->mTangents, mesh->mNumVertices); + mesh->mBitangents = new aiVector3D[mesh->mNumVertices]; + ReadArray<aiVector3D>(stream, mesh->mBitangents, mesh->mNumVertices); + } + } + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS; ++n) { + if (!(c & ASSBIN_MESH_HAS_COLOR(n))) { + break; + } + + if (shortened) { + ReadBounds(stream, mesh->mColors[n], mesh->mNumVertices); + } else { + // else write as usual + mesh->mColors[n] = new aiColor4D[mesh->mNumVertices]; + ReadArray<aiColor4D>(stream, mesh->mColors[n], mesh->mNumVertices); + } + } + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++n) { + if (!(c & ASSBIN_MESH_HAS_TEXCOORD(n))) { + break; + } + + // write number of UV components + mesh->mNumUVComponents[n] = Read<unsigned int>(stream); + + if (shortened) { + ReadBounds(stream, mesh->mTextureCoords[n], mesh->mNumVertices); + } else { + // else write as usual + mesh->mTextureCoords[n] = new aiVector3D[mesh->mNumVertices]; + ReadArray<aiVector3D>(stream, mesh->mTextureCoords[n], mesh->mNumVertices); + } + } + + // write faces. There are no floating-point calculations involved + // in these, so we can write a simple hash over the face data + // to the dump file. We generate a single 32 Bit hash for 512 faces + // using Assimp's standard hashing function. + if (shortened) { + Read<unsigned int>(stream); + } else { + // else write as usual + // if there are less than 2^16 vertices, we can simply use 16 bit integers ... + mesh->mFaces = new aiFace[mesh->mNumFaces]; + for (unsigned int i = 0; i < mesh->mNumFaces; ++i) { + aiFace &f = mesh->mFaces[i]; + + static_assert(AI_MAX_FACE_INDICES <= 0xffff, "AI_MAX_FACE_INDICES <= 0xffff"); + f.mNumIndices = Read<uint16_t>(stream); + f.mIndices = new unsigned int[f.mNumIndices]; + + for (unsigned int a = 0; a < f.mNumIndices; ++a) { + // Check if unsigned short ( 16 bit ) are big enough for the indices + if (fitsIntoUI16(mesh->mNumVertices)) { + f.mIndices[a] = Read<uint16_t>(stream); + } else { + f.mIndices[a] = Read<unsigned int>(stream); + } + } + } + } + + // write bones + if (mesh->mNumBones) { + mesh->mBones = new C_STRUCT aiBone *[mesh->mNumBones]; + for (unsigned int a = 0; a < mesh->mNumBones; ++a) { + mesh->mBones[a] = new aiBone(); + ReadBinaryBone(stream, mesh->mBones[a]); + } + } +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryMaterialProperty(IOStream *stream, aiMaterialProperty *prop) { + if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AIMATERIALPROPERTY) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/Read<uint32_t>(stream); + + prop->mKey = Read<aiString>(stream); + prop->mSemantic = Read<unsigned int>(stream); + prop->mIndex = Read<unsigned int>(stream); + + prop->mDataLength = Read<unsigned int>(stream); + prop->mType = (aiPropertyTypeInfo)Read<unsigned int>(stream); + prop->mData = new char[prop->mDataLength]; + stream->Read(prop->mData, 1, prop->mDataLength); +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryMaterial(IOStream *stream, aiMaterial *mat) { + if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AIMATERIAL) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/Read<uint32_t>(stream); + + mat->mNumAllocated = mat->mNumProperties = Read<unsigned int>(stream); + if (mat->mNumProperties) { + if (mat->mProperties) { + delete[] mat->mProperties; + } + mat->mProperties = new aiMaterialProperty *[mat->mNumProperties]; + for (unsigned int i = 0; i < mat->mNumProperties; ++i) { + mat->mProperties[i] = new aiMaterialProperty(); + ReadBinaryMaterialProperty(stream, mat->mProperties[i]); + } + } +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryNodeAnim(IOStream *stream, aiNodeAnim *nd) { + if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AINODEANIM) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/Read<uint32_t>(stream); + + nd->mNodeName = Read<aiString>(stream); + nd->mNumPositionKeys = Read<unsigned int>(stream); + nd->mNumRotationKeys = Read<unsigned int>(stream); + nd->mNumScalingKeys = Read<unsigned int>(stream); + nd->mPreState = (aiAnimBehaviour)Read<unsigned int>(stream); + nd->mPostState = (aiAnimBehaviour)Read<unsigned int>(stream); + + if (nd->mNumPositionKeys) { + if (shortened) { + ReadBounds(stream, nd->mPositionKeys, nd->mNumPositionKeys); + + } // else write as usual + else { + nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys]; + ReadArray<aiVectorKey>(stream, nd->mPositionKeys, nd->mNumPositionKeys); + } + } + if (nd->mNumRotationKeys) { + if (shortened) { + ReadBounds(stream, nd->mRotationKeys, nd->mNumRotationKeys); + + } else { + // else write as usual + nd->mRotationKeys = new aiQuatKey[nd->mNumRotationKeys]; + ReadArray<aiQuatKey>(stream, nd->mRotationKeys, nd->mNumRotationKeys); + } + } + if (nd->mNumScalingKeys) { + if (shortened) { + ReadBounds(stream, nd->mScalingKeys, nd->mNumScalingKeys); + + } else { + // else write as usual + nd->mScalingKeys = new aiVectorKey[nd->mNumScalingKeys]; + ReadArray<aiVectorKey>(stream, nd->mScalingKeys, nd->mNumScalingKeys); + } + } +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryAnim(IOStream *stream, aiAnimation *anim) { + if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AIANIMATION) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/Read<uint32_t>(stream); + + anim->mName = Read<aiString>(stream); + anim->mDuration = Read<double>(stream); + anim->mTicksPerSecond = Read<double>(stream); + anim->mNumChannels = Read<unsigned int>(stream); + + if (anim->mNumChannels) { + anim->mChannels = new aiNodeAnim *[anim->mNumChannels]; + for (unsigned int a = 0; a < anim->mNumChannels; ++a) { + anim->mChannels[a] = new aiNodeAnim(); + ReadBinaryNodeAnim(stream, anim->mChannels[a]); + } + } +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryTexture(IOStream *stream, aiTexture *tex) { + if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AITEXTURE) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/Read<uint32_t>(stream); + + tex->mWidth = Read<unsigned int>(stream); + tex->mHeight = Read<unsigned int>(stream); + stream->Read(tex->achFormatHint, sizeof(char), HINTMAXTEXTURELEN - 1); + + if (!shortened) { + if (!tex->mHeight) { + tex->pcData = new aiTexel[tex->mWidth]; + stream->Read(tex->pcData, 1, tex->mWidth); + } else { + tex->pcData = new aiTexel[tex->mWidth * tex->mHeight]; + stream->Read(tex->pcData, 1, tex->mWidth * tex->mHeight * 4); + } + } +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryLight(IOStream *stream, aiLight *l) { + if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AILIGHT) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/Read<uint32_t>(stream); + + l->mName = Read<aiString>(stream); + l->mType = (aiLightSourceType)Read<unsigned int>(stream); + + if (l->mType != aiLightSource_DIRECTIONAL) { + l->mAttenuationConstant = Read<float>(stream); + l->mAttenuationLinear = Read<float>(stream); + l->mAttenuationQuadratic = Read<float>(stream); + } + + l->mColorDiffuse = Read<aiColor3D>(stream); + l->mColorSpecular = Read<aiColor3D>(stream); + l->mColorAmbient = Read<aiColor3D>(stream); + + if (l->mType == aiLightSource_SPOT) { + l->mAngleInnerCone = Read<float>(stream); + l->mAngleOuterCone = Read<float>(stream); + } +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryCamera(IOStream *stream, aiCamera *cam) { + if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AICAMERA) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/Read<uint32_t>(stream); + + cam->mName = Read<aiString>(stream); + cam->mPosition = Read<aiVector3D>(stream); + cam->mLookAt = Read<aiVector3D>(stream); + cam->mUp = Read<aiVector3D>(stream); + cam->mHorizontalFOV = Read<float>(stream); + cam->mClipPlaneNear = Read<float>(stream); + cam->mClipPlaneFar = Read<float>(stream); + cam->mAspect = Read<float>(stream); +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryScene(IOStream *stream, aiScene *scene) { + if (Read<uint32_t>(stream) != ASSBIN_CHUNK_AISCENE) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/Read<uint32_t>(stream); + + scene->mFlags = Read<unsigned int>(stream); + scene->mNumMeshes = Read<unsigned int>(stream); + scene->mNumMaterials = Read<unsigned int>(stream); + scene->mNumAnimations = Read<unsigned int>(stream); + scene->mNumTextures = Read<unsigned int>(stream); + scene->mNumLights = Read<unsigned int>(stream); + scene->mNumCameras = Read<unsigned int>(stream); + + // Read node graph + //scene->mRootNode = new aiNode[1]; + ReadBinaryNode(stream, &scene->mRootNode, (aiNode *)nullptr); + + // Read all meshes + if (scene->mNumMeshes) { + scene->mMeshes = new aiMesh *[scene->mNumMeshes]; + memset(scene->mMeshes, 0, scene->mNumMeshes * sizeof(aiMesh *)); + for (unsigned int i = 0; i < scene->mNumMeshes; ++i) { + scene->mMeshes[i] = new aiMesh(); + ReadBinaryMesh(stream, scene->mMeshes[i]); + } + } + + // Read materials + if (scene->mNumMaterials) { + scene->mMaterials = new aiMaterial *[scene->mNumMaterials]; + memset(scene->mMaterials, 0, scene->mNumMaterials * sizeof(aiMaterial *)); + for (unsigned int i = 0; i < scene->mNumMaterials; ++i) { + scene->mMaterials[i] = new aiMaterial(); + ReadBinaryMaterial(stream, scene->mMaterials[i]); + } + } + + // Read all animations + if (scene->mNumAnimations) { + scene->mAnimations = new aiAnimation *[scene->mNumAnimations]; + memset(scene->mAnimations, 0, scene->mNumAnimations * sizeof(aiAnimation *)); + for (unsigned int i = 0; i < scene->mNumAnimations; ++i) { + scene->mAnimations[i] = new aiAnimation(); + ReadBinaryAnim(stream, scene->mAnimations[i]); + } + } + + // Read all textures + if (scene->mNumTextures) { + scene->mTextures = new aiTexture *[scene->mNumTextures]; + memset(scene->mTextures, 0, scene->mNumTextures * sizeof(aiTexture *)); + for (unsigned int i = 0; i < scene->mNumTextures; ++i) { + scene->mTextures[i] = new aiTexture(); + ReadBinaryTexture(stream, scene->mTextures[i]); + } + } + + // Read lights + if (scene->mNumLights) { + scene->mLights = new aiLight *[scene->mNumLights]; + memset(scene->mLights, 0, scene->mNumLights * sizeof(aiLight *)); + for (unsigned int i = 0; i < scene->mNumLights; ++i) { + scene->mLights[i] = new aiLight(); + ReadBinaryLight(stream, scene->mLights[i]); + } + } + + // Read cameras + if (scene->mNumCameras) { + scene->mCameras = new aiCamera *[scene->mNumCameras]; + memset(scene->mCameras, 0, scene->mNumCameras * sizeof(aiCamera *)); + for (unsigned int i = 0; i < scene->mNumCameras; ++i) { + scene->mCameras[i] = new aiCamera(); + ReadBinaryCamera(stream, scene->mCameras[i]); + } + } +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + IOStream *stream = pIOHandler->Open(pFile, "rb"); + if (nullptr == stream) { + throw DeadlyImportError("ASSBIN: Could not open ", pFile); + } + + // signature + stream->Seek(44, aiOrigin_CUR); + + unsigned int versionMajor = Read<unsigned int>(stream); + unsigned int versionMinor = Read<unsigned int>(stream); + if (versionMinor != ASSBIN_VERSION_MINOR || versionMajor != ASSBIN_VERSION_MAJOR) { + throw DeadlyImportError("Invalid version, data format not compatible!"); + } + + /*unsigned int versionRevision =*/Read<unsigned int>(stream); + /*unsigned int compileFlags =*/Read<unsigned int>(stream); + + shortened = Read<uint16_t>(stream) > 0; + compressed = Read<uint16_t>(stream) > 0; + + if (shortened) + throw DeadlyImportError("Shortened binaries are not supported!"); + + stream->Seek(256, aiOrigin_CUR); // original filename + stream->Seek(128, aiOrigin_CUR); // options + stream->Seek(64, aiOrigin_CUR); // padding + + if (compressed) { + uLongf uncompressedSize = Read<uint32_t>(stream); + uLongf compressedSize = static_cast<uLongf>(stream->FileSize() - stream->Tell()); + + unsigned char *compressedData = new unsigned char[compressedSize]; + size_t len = stream->Read(compressedData, 1, compressedSize); + ai_assert(len == compressedSize); + + unsigned char *uncompressedData = new unsigned char[uncompressedSize]; + + int res = uncompress(uncompressedData, &uncompressedSize, compressedData, (uLong)len); + if (res != Z_OK) { + delete[] uncompressedData; + delete[] compressedData; + pIOHandler->Close(stream); + throw DeadlyImportError("Zlib decompression failed."); + } + + MemoryIOStream io(uncompressedData, uncompressedSize); + + ReadBinaryScene(&io, pScene); + + delete[] uncompressedData; + delete[] compressedData; + } else { + ReadBinaryScene(stream, pScene); + } + + pIOHandler->Close(stream); +} + +#endif // !! ASSIMP_BUILD_NO_ASSBIN_IMPORTER diff --git a/libs/assimp/code/AssetLib/Assbin/AssbinLoader.h b/libs/assimp/code/AssetLib/Assbin/AssbinLoader.h new file mode 100644 index 0000000..f922b91 --- /dev/null +++ b/libs/assimp/code/AssetLib/Assbin/AssbinLoader.h @@ -0,0 +1,99 @@ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AssbinLoader.h + * @brief .assbin File format loader + */ +#ifndef AI_ASSBINIMPORTER_H_INC +#define AI_ASSBINIMPORTER_H_INC + +#include <assimp/BaseImporter.h> + +struct aiMesh; +struct aiNode; +struct aiBone; +struct aiMaterial; +struct aiMaterialProperty; +struct aiNodeAnim; +struct aiAnimation; +struct aiTexture; +struct aiLight; +struct aiCamera; + +#ifndef ASSIMP_BUILD_NO_ASSBIN_IMPORTER + +namespace Assimp { + +// --------------------------------------------------------------------------------- +/** Importer class for 3D Studio r3 and r4 3DS files + */ +class AssbinImporter : public BaseImporter +{ +private: + bool shortened; + bool compressed; + +public: + bool CanRead(const std::string& pFile, + IOSystem* pIOHandler, bool checkSig) const override; + const aiImporterDesc* GetInfo() const override; + void InternReadFile( + const std::string& pFile,aiScene* pScene,IOSystem* pIOHandler) override; + void ReadHeader(); + void ReadBinaryScene( IOStream * stream, aiScene* pScene ); + void ReadBinaryNode( IOStream * stream, aiNode** mRootNode, aiNode* parent ); + void ReadBinaryMesh( IOStream * stream, aiMesh* mesh ); + void ReadBinaryBone( IOStream * stream, aiBone* bone ); + void ReadBinaryMaterial(IOStream * stream, aiMaterial* mat); + void ReadBinaryMaterialProperty(IOStream * stream, aiMaterialProperty* prop); + void ReadBinaryNodeAnim(IOStream * stream, aiNodeAnim* nd); + void ReadBinaryAnim( IOStream * stream, aiAnimation* anim ); + void ReadBinaryTexture(IOStream * stream, aiTexture* tex); + void ReadBinaryLight( IOStream * stream, aiLight* l ); + void ReadBinaryCamera( IOStream * stream, aiCamera* cam ); +}; + +} // end of namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_ASSBIN_IMPORTER + +#endif // AI_ASSBINIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/Assjson/cencode.c b/libs/assimp/code/AssetLib/Assjson/cencode.c new file mode 100644 index 0000000..614a267 --- /dev/null +++ b/libs/assimp/code/AssetLib/Assjson/cencode.c @@ -0,0 +1,117 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include "cencode.h" // changed from <B64/cencode.h> + +const int CHARS_PER_LINE = 72; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4244) +#endif // _MSC_VER + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + if (state_in->stepcount == CHARS_PER_LINE/4) + { + *codechar++ = '\n'; + state_in->stepcount = 0; + } + } + } + /* control should not reach here */ + return (int)(codechar - code_out); +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + *codechar++ = '\n'; + + return (int)(codechar - code_out); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER diff --git a/libs/assimp/code/AssetLib/Assjson/cencode.h b/libs/assimp/code/AssetLib/Assjson/cencode.h new file mode 100644 index 0000000..a7893c4 --- /dev/null +++ b/libs/assimp/code/AssetLib/Assjson/cencode.h @@ -0,0 +1,35 @@ +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +#ifdef _MSC_VER +#pragma warning(disable : 4127 ) +#endif // _MSC_VER + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +#endif /* BASE64_CENCODE_H */ diff --git a/libs/assimp/code/AssetLib/Assjson/json_exporter.cpp b/libs/assimp/code/AssetLib/Assjson/json_exporter.cpp new file mode 100644 index 0000000..7b2c8ec --- /dev/null +++ b/libs/assimp/code/AssetLib/Assjson/json_exporter.cpp @@ -0,0 +1,810 @@ +/* +Assimp2Json +Copyright (c) 2011, Alexander C. Gessler + +Licensed under a 3-clause BSD license. See the LICENSE file for more information. + +*/ + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER + +#include <assimp/scene.h> +#include <assimp/Exporter.hpp> +#include <assimp/IOStream.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/Importer.hpp> +#include <assimp/Exceptional.h> + +#include <cassert> +#include <limits> +#include <memory> +#include <sstream> + +#define CURRENT_FORMAT_VERSION 100 + +// grab scoped_ptr from assimp to avoid a dependency on boost. +//#include <assimp/../../code/BoostWorkaround/boost/scoped_ptr.hpp> + +#include "mesh_splitter.h" + +extern "C" { +#include "cencode.h" +} +namespace Assimp { + +void ExportAssimp2Json(const char *, Assimp::IOSystem *, const aiScene *, const Assimp::ExportProperties *); + +// small utility class to simplify serializing the aiScene to Json +class JSONWriter { +public: + enum { + Flag_DoNotIndent = 0x1, + Flag_WriteSpecialFloats = 0x2, + Flag_SkipWhitespaces = 0x4 + }; + + JSONWriter(Assimp::IOStream &out, unsigned int flags = 0u) : + out(out), indent (""), newline("\n"), space(" "), buff (), first(false), flags(flags) { + // make sure that all formatting happens using the standard, C locale and not the user's current locale + buff.imbue(std::locale("C")); + if (flags & Flag_SkipWhitespaces) { + newline = ""; + space = ""; + } + } + + ~JSONWriter() { + Flush(); + } + + void Flush() { + const std::string s = buff.str(); + out.Write(s.c_str(), s.length(), 1); + buff.clear(); + } + + void PushIndent() { + indent += '\t'; + } + + void PopIndent() { + indent.erase(indent.end() - 1); + } + + void Key(const std::string &name) { + AddIndentation(); + Delimit(); + buff << '\"' + name + "\":" << space; + } + + template <typename Literal> + void Element(const Literal &name) { + AddIndentation(); + Delimit(); + + LiteralToString(buff, name) << newline; + } + + template <typename Literal> + void SimpleValue(const Literal &s) { + LiteralToString(buff, s) << newline; + } + + void SimpleValue(const void *buffer, size_t len) { + base64_encodestate s; + base64_init_encodestate(&s); + + char *const cur_out = new char[std::max(len * 2, static_cast<size_t>(16u))]; + const int n = base64_encode_block(reinterpret_cast<const char *>(buffer), static_cast<int>(len), cur_out, &s); + cur_out[n + base64_encode_blockend(cur_out + n, &s)] = '\0'; + + // base64 encoding may add newlines, but JSON strings may not contain 'real' newlines + // (only escaped ones). Remove any newlines in out. + for (char *cur = cur_out; *cur; ++cur) { + if (*cur == '\n') { + *cur = ' '; + } + } + + buff << '\"' << cur_out << "\"" << newline; + delete[] cur_out; + } + + void StartObj(bool is_element = false) { + // if this appears as a plain array element, we need to insert a delimiter and we should also indent it + if (is_element) { + AddIndentation(); + if (!first) { + buff << ','; + } + } + first = true; + buff << "{" << newline; + PushIndent(); + } + + void EndObj() { + PopIndent(); + AddIndentation(); + first = false; + buff << "}" << newline; + } + + void StartArray(bool is_element = false) { + // if this appears as a plain array element, we need to insert a delimiter and we should also indent it + if (is_element) { + AddIndentation(); + if (!first) { + buff << ','; + } + } + first = true; + buff << "[" << newline; + PushIndent(); + } + + void EndArray() { + PopIndent(); + AddIndentation(); + buff << "]" << newline; + first = false; + } + + void AddIndentation() { + if (!(flags & Flag_DoNotIndent) && !(flags & Flag_SkipWhitespaces)) { + buff << indent; + } + } + + void Delimit() { + if (!first) { + buff << ','; + } else { + buff << space; + first = false; + } + } + +private: + template <typename Literal> + std::stringstream &LiteralToString(std::stringstream &stream, const Literal &s) { + stream << s; + return stream; + } + + std::stringstream &LiteralToString(std::stringstream &stream, const aiString &s) { + std::string t; + + // escape backslashes and single quotes, both would render the JSON invalid if left as is + t.reserve(s.length); + for (size_t i = 0; i < s.length; ++i) { + + if (s.data[i] == '\\' || s.data[i] == '\'' || s.data[i] == '\"') { + t.push_back('\\'); + } + + t.push_back(s.data[i]); + } + stream << "\""; + stream << t; + stream << "\""; + return stream; + } + + std::stringstream &LiteralToString(std::stringstream &stream, float f) { + if (!std::numeric_limits<float>::is_iec559) { + // on a non IEEE-754 platform, we make no assumptions about the representation or existence + // of special floating-point numbers. + stream << f; + return stream; + } + + // JSON does not support writing Inf/Nan + // [RFC 4672: "Numeric values that cannot be represented as sequences of digits + // (such as Infinity and NaN) are not permitted."] + // Nevertheless, many parsers will accept the special keywords Infinity, -Infinity and NaN + if (std::numeric_limits<float>::infinity() == fabs(f)) { + if (flags & Flag_WriteSpecialFloats) { + stream << (f < 0 ? "\"-" : "\"") + std::string("Infinity\""); + return stream; + } + // we should print this warning, but we can't - this is called from within a generic assimp exporter, we cannot use cerr + // std::cerr << "warning: cannot represent infinite number literal, substituting 0 instead (use -i flag to enforce Infinity/NaN)" << std::endl; + stream << "0.0"; + return stream; + } + // f!=f is the most reliable test for NaNs that I know of + else if (f != f) { + if (flags & Flag_WriteSpecialFloats) { + stream << "\"NaN\""; + return stream; + } + // we should print this warning, but we can't - this is called from within a generic assimp exporter, we cannot use cerr + // std::cerr << "warning: cannot represent infinite number literal, substituting 0 instead (use -i flag to enforce Infinity/NaN)" << std::endl; + stream << "0.0"; + return stream; + } + + stream << f; + return stream; + } + +private: + Assimp::IOStream &out; + std::string indent; + std::string newline; + std::string space; + std::stringstream buff; + bool first; + + unsigned int flags; +}; + +void Write(JSONWriter &out, const aiVector3D &ai, bool is_elem = true) { + out.StartArray(is_elem); + out.Element(ai.x); + out.Element(ai.y); + out.Element(ai.z); + out.EndArray(); +} + +void Write(JSONWriter &out, const aiQuaternion &ai, bool is_elem = true) { + out.StartArray(is_elem); + out.Element(ai.w); + out.Element(ai.x); + out.Element(ai.y); + out.Element(ai.z); + out.EndArray(); +} + +void Write(JSONWriter &out, const aiColor3D &ai, bool is_elem = true) { + out.StartArray(is_elem); + out.Element(ai.r); + out.Element(ai.g); + out.Element(ai.b); + out.EndArray(); +} + +void Write(JSONWriter &out, const aiMatrix4x4 &ai, bool is_elem = true) { + out.StartArray(is_elem); + for (unsigned int x = 0; x < 4; ++x) { + for (unsigned int y = 0; y < 4; ++y) { + out.Element(ai[x][y]); + } + } + out.EndArray(); +} + +void Write(JSONWriter &out, const aiBone &ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mName); + + out.Key("offsetmatrix"); + Write(out, ai.mOffsetMatrix, false); + + out.Key("weights"); + out.StartArray(); + for (unsigned int i = 0; i < ai.mNumWeights; ++i) { + out.StartArray(true); + out.Element(ai.mWeights[i].mVertexId); + out.Element(ai.mWeights[i].mWeight); + out.EndArray(); + } + out.EndArray(); + out.EndObj(); +} + +void Write(JSONWriter &out, const aiFace &ai, bool is_elem = true) { + out.StartArray(is_elem); + for (unsigned int i = 0; i < ai.mNumIndices; ++i) { + out.Element(ai.mIndices[i]); + } + out.EndArray(); +} + +void Write(JSONWriter &out, const aiMesh &ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mName); + + out.Key("materialindex"); + out.SimpleValue(ai.mMaterialIndex); + + out.Key("primitivetypes"); + out.SimpleValue(ai.mPrimitiveTypes); + + out.Key("vertices"); + out.StartArray(); + for (unsigned int i = 0; i < ai.mNumVertices; ++i) { + out.Element(ai.mVertices[i].x); + out.Element(ai.mVertices[i].y); + out.Element(ai.mVertices[i].z); + } + out.EndArray(); + + if (ai.HasNormals()) { + out.Key("normals"); + out.StartArray(); + for (unsigned int i = 0; i < ai.mNumVertices; ++i) { + out.Element(ai.mNormals[i].x); + out.Element(ai.mNormals[i].y); + out.Element(ai.mNormals[i].z); + } + out.EndArray(); + } + + if (ai.HasTangentsAndBitangents()) { + out.Key("tangents"); + out.StartArray(); + for (unsigned int i = 0; i < ai.mNumVertices; ++i) { + out.Element(ai.mTangents[i].x); + out.Element(ai.mTangents[i].y); + out.Element(ai.mTangents[i].z); + } + out.EndArray(); + + out.Key("bitangents"); + out.StartArray(); + for (unsigned int i = 0; i < ai.mNumVertices; ++i) { + out.Element(ai.mBitangents[i].x); + out.Element(ai.mBitangents[i].y); + out.Element(ai.mBitangents[i].z); + } + out.EndArray(); + } + + if (ai.GetNumUVChannels()) { + out.Key("numuvcomponents"); + out.StartArray(); + for (unsigned int n = 0; n < ai.GetNumUVChannels(); ++n) { + out.Element(ai.mNumUVComponents[n]); + } + out.EndArray(); + + out.Key("texturecoords"); + out.StartArray(); + for (unsigned int n = 0; n < ai.GetNumUVChannels(); ++n) { + const unsigned int numc = ai.mNumUVComponents[n] ? ai.mNumUVComponents[n] : 2; + + out.StartArray(true); + for (unsigned int i = 0; i < ai.mNumVertices; ++i) { + for (unsigned int c = 0; c < numc; ++c) { + out.Element(ai.mTextureCoords[n][i][c]); + } + } + out.EndArray(); + } + out.EndArray(); + } + + if (ai.GetNumColorChannels()) { + out.Key("colors"); + out.StartArray(); + for (unsigned int n = 0; n < ai.GetNumColorChannels(); ++n) { + out.StartArray(true); + for (unsigned int i = 0; i < ai.mNumVertices; ++i) { + out.Element(ai.mColors[n][i].r); + out.Element(ai.mColors[n][i].g); + out.Element(ai.mColors[n][i].b); + out.Element(ai.mColors[n][i].a); + } + out.EndArray(); + } + out.EndArray(); + } + + if (ai.mNumBones) { + out.Key("bones"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumBones; ++n) { + Write(out, *ai.mBones[n]); + } + out.EndArray(); + } + + out.Key("faces"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumFaces; ++n) { + Write(out, ai.mFaces[n]); + } + out.EndArray(); + + out.EndObj(); +} + +void Write(JSONWriter &out, const aiNode &ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mName); + + out.Key("transformation"); + Write(out, ai.mTransformation, false); + + if (ai.mNumMeshes) { + out.Key("meshes"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumMeshes; ++n) { + out.Element(ai.mMeshes[n]); + } + out.EndArray(); + } + + if (ai.mNumChildren) { + out.Key("children"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumChildren; ++n) { + Write(out, *ai.mChildren[n]); + } + out.EndArray(); + } + + out.EndObj(); +} + +void Write(JSONWriter &out, const aiMaterial &ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("properties"); + out.StartArray(); + for (unsigned int i = 0; i < ai.mNumProperties; ++i) { + const aiMaterialProperty *const prop = ai.mProperties[i]; + out.StartObj(true); + out.Key("key"); + out.SimpleValue(prop->mKey); + out.Key("semantic"); + out.SimpleValue(prop->mSemantic); + out.Key("index"); + out.SimpleValue(prop->mIndex); + + out.Key("type"); + out.SimpleValue(prop->mType); + + out.Key("value"); + switch (prop->mType) { + case aiPTI_Float: + if (prop->mDataLength / sizeof(float) > 1) { + out.StartArray(); + for (unsigned int ii = 0; ii < prop->mDataLength / sizeof(float); ++ii) { + out.Element(reinterpret_cast<float *>(prop->mData)[ii]); + } + out.EndArray(); + } else { + out.SimpleValue(*reinterpret_cast<float *>(prop->mData)); + } + break; + + case aiPTI_Integer: + if (prop->mDataLength / sizeof(int) > 1) { + out.StartArray(); + for (unsigned int ii = 0; ii < prop->mDataLength / sizeof(int); ++ii) { + out.Element(reinterpret_cast<int *>(prop->mData)[ii]); + } + out.EndArray(); + } else { + out.SimpleValue(*reinterpret_cast<int *>(prop->mData)); + } + break; + + case aiPTI_String: { + aiString s; + aiGetMaterialString(&ai, prop->mKey.data, prop->mSemantic, prop->mIndex, &s); + out.SimpleValue(s); + } break; + case aiPTI_Buffer: { + // binary data is written as series of hex-encoded octets + out.SimpleValue(prop->mData, prop->mDataLength); + } break; + default: + assert(false); + } + + out.EndObj(); + } + + out.EndArray(); + out.EndObj(); +} + +void Write(JSONWriter &out, const aiTexture &ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("width"); + out.SimpleValue(ai.mWidth); + + out.Key("height"); + out.SimpleValue(ai.mHeight); + + out.Key("formathint"); + out.SimpleValue(aiString(ai.achFormatHint)); + + out.Key("data"); + if (!ai.mHeight) { + out.SimpleValue(ai.pcData, ai.mWidth); + } else { + out.StartArray(); + for (unsigned int y = 0; y < ai.mHeight; ++y) { + out.StartArray(true); + for (unsigned int x = 0; x < ai.mWidth; ++x) { + const aiTexel &tx = ai.pcData[y * ai.mWidth + x]; + out.StartArray(true); + out.Element(static_cast<unsigned int>(tx.r)); + out.Element(static_cast<unsigned int>(tx.g)); + out.Element(static_cast<unsigned int>(tx.b)); + out.Element(static_cast<unsigned int>(tx.a)); + out.EndArray(); + } + out.EndArray(); + } + out.EndArray(); + } + + out.EndObj(); +} + +void Write(JSONWriter &out, const aiLight &ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mName); + + out.Key("type"); + out.SimpleValue(ai.mType); + + if (ai.mType == aiLightSource_SPOT || ai.mType == aiLightSource_UNDEFINED) { + out.Key("angleinnercone"); + out.SimpleValue(ai.mAngleInnerCone); + + out.Key("angleoutercone"); + out.SimpleValue(ai.mAngleOuterCone); + } + + out.Key("attenuationconstant"); + out.SimpleValue(ai.mAttenuationConstant); + + out.Key("attenuationlinear"); + out.SimpleValue(ai.mAttenuationLinear); + + out.Key("attenuationquadratic"); + out.SimpleValue(ai.mAttenuationQuadratic); + + out.Key("diffusecolor"); + Write(out, ai.mColorDiffuse, false); + + out.Key("specularcolor"); + Write(out, ai.mColorSpecular, false); + + out.Key("ambientcolor"); + Write(out, ai.mColorAmbient, false); + + if (ai.mType != aiLightSource_POINT) { + out.Key("direction"); + Write(out, ai.mDirection, false); + } + + if (ai.mType != aiLightSource_DIRECTIONAL) { + out.Key("position"); + Write(out, ai.mPosition, false); + } + + out.EndObj(); +} + +void Write(JSONWriter &out, const aiNodeAnim &ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mNodeName); + + out.Key("prestate"); + out.SimpleValue(ai.mPreState); + + out.Key("poststate"); + out.SimpleValue(ai.mPostState); + + if (ai.mNumPositionKeys) { + out.Key("positionkeys"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumPositionKeys; ++n) { + const aiVectorKey &pos = ai.mPositionKeys[n]; + out.StartArray(true); + out.Element(pos.mTime); + Write(out, pos.mValue); + out.EndArray(); + } + out.EndArray(); + } + + if (ai.mNumRotationKeys) { + out.Key("rotationkeys"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumRotationKeys; ++n) { + const aiQuatKey &rot = ai.mRotationKeys[n]; + out.StartArray(true); + out.Element(rot.mTime); + Write(out, rot.mValue); + out.EndArray(); + } + out.EndArray(); + } + + if (ai.mNumScalingKeys) { + out.Key("scalingkeys"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumScalingKeys; ++n) { + const aiVectorKey &scl = ai.mScalingKeys[n]; + out.StartArray(true); + out.Element(scl.mTime); + Write(out, scl.mValue); + out.EndArray(); + } + out.EndArray(); + } + out.EndObj(); +} + +void Write(JSONWriter &out, const aiAnimation &ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mName); + + out.Key("tickspersecond"); + out.SimpleValue(ai.mTicksPerSecond); + + out.Key("duration"); + out.SimpleValue(ai.mDuration); + + out.Key("channels"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumChannels; ++n) { + Write(out, *ai.mChannels[n]); + } + out.EndArray(); + out.EndObj(); +} + +void Write(JSONWriter &out, const aiCamera &ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mName); + + out.Key("aspect"); + out.SimpleValue(ai.mAspect); + + out.Key("clipplanefar"); + out.SimpleValue(ai.mClipPlaneFar); + + out.Key("clipplanenear"); + out.SimpleValue(ai.mClipPlaneNear); + + out.Key("horizontalfov"); + out.SimpleValue(ai.mHorizontalFOV); + + out.Key("up"); + Write(out, ai.mUp, false); + + out.Key("lookat"); + Write(out, ai.mLookAt, false); + + out.EndObj(); +} + +void WriteFormatInfo(JSONWriter &out) { + out.StartObj(); + out.Key("format"); + out.SimpleValue("\"assimp2json\""); + out.Key("version"); + out.SimpleValue(CURRENT_FORMAT_VERSION); + out.EndObj(); +} + +void Write(JSONWriter &out, const aiScene &ai) { + out.StartObj(); + + out.Key("__metadata__"); + WriteFormatInfo(out); + + out.Key("rootnode"); + Write(out, *ai.mRootNode, false); + + out.Key("flags"); + out.SimpleValue(ai.mFlags); + + if (ai.HasMeshes()) { + out.Key("meshes"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumMeshes; ++n) { + Write(out, *ai.mMeshes[n]); + } + out.EndArray(); + } + + if (ai.HasMaterials()) { + out.Key("materials"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumMaterials; ++n) { + Write(out, *ai.mMaterials[n]); + } + out.EndArray(); + } + + if (ai.HasAnimations()) { + out.Key("animations"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumAnimations; ++n) { + Write(out, *ai.mAnimations[n]); + } + out.EndArray(); + } + + if (ai.HasLights()) { + out.Key("lights"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumLights; ++n) { + Write(out, *ai.mLights[n]); + } + out.EndArray(); + } + + if (ai.HasCameras()) { + out.Key("cameras"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumCameras; ++n) { + Write(out, *ai.mCameras[n]); + } + out.EndArray(); + } + + if (ai.HasTextures()) { + out.Key("textures"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumTextures; ++n) { + Write(out, *ai.mTextures[n]); + } + out.EndArray(); + } + out.EndObj(); +} + +void ExportAssimp2Json(const char *file, Assimp::IOSystem *io, const aiScene *scene, const Assimp::ExportProperties *pProperties) { + std::unique_ptr<Assimp::IOStream> str(io->Open(file, "wt")); + if (!str) { + throw DeadlyExportError("could not open output file"); + } + + // get a copy of the scene so we can modify it + aiScene *scenecopy_tmp; + aiCopyScene(scene, &scenecopy_tmp); + + try { + // split meshes so they fit into a 16 bit index buffer + MeshSplitter splitter; + splitter.SetLimit(1 << 16); + splitter.Execute(scenecopy_tmp); + + // XXX Flag_WriteSpecialFloats is turned on by default, right now we don't have a configuration interface for exporters + + unsigned int flags = JSONWriter::Flag_WriteSpecialFloats; + if (pProperties->GetPropertyBool("JSON_SKIP_WHITESPACES", false)) { + flags |= JSONWriter::Flag_SkipWhitespaces; + } + JSONWriter s(*str, flags); + Write(s, *scenecopy_tmp); + + } catch (...) { + aiFreeScene(scenecopy_tmp); + throw; + } + aiFreeScene(scenecopy_tmp); +} + +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_ASSJSON_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/Assjson/mesh_splitter.cpp b/libs/assimp/code/AssetLib/Assjson/mesh_splitter.cpp new file mode 100644 index 0000000..978437c --- /dev/null +++ b/libs/assimp/code/AssetLib/Assjson/mesh_splitter.cpp @@ -0,0 +1,319 @@ +/* +Assimp2Json +Copyright (c) 2011, Alexander C. Gessler + +Licensed under a 3-clause BSD license. See the LICENSE file for more information. + +*/ + +#include "mesh_splitter.h" + +#include <assimp/scene.h> + +// ---------------------------------------------------------------------------- +// Note: this is largely based on assimp's SplitLargeMeshes_Vertex process. +// it is refactored and the coding style is slightly improved, though. +// ---------------------------------------------------------------------------- + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void MeshSplitter::Execute( aiScene* pScene) { + std::vector<std::pair<aiMesh*, unsigned int> > source_mesh_map; + + for( unsigned int a = 0; a < pScene->mNumMeshes; a++) { + SplitMesh(a, pScene->mMeshes[a],source_mesh_map); + } + + const unsigned int size = static_cast<unsigned int>(source_mesh_map.size()); + if (size != pScene->mNumMeshes) { + // it seems something has been split. rebuild the mesh list + delete[] pScene->mMeshes; + pScene->mNumMeshes = size; + pScene->mMeshes = new aiMesh*[size](); + + for (unsigned int i = 0; i < size;++i) { + pScene->mMeshes[i] = source_mesh_map[i].first; + } + + // now we need to update all nodes + UpdateNode(pScene->mRootNode,source_mesh_map); + } +} + + +// ------------------------------------------------------------------------------------------------ +void MeshSplitter::UpdateNode(aiNode* pcNode, const std::vector<std::pair<aiMesh*, unsigned int> >& source_mesh_map) { + // TODO: should better use std::(multi)set for source_mesh_map. + + // for every index in out list build a new entry + std::vector<unsigned int> aiEntries; + aiEntries.reserve(pcNode->mNumMeshes + 1); + for (unsigned int i = 0; i < pcNode->mNumMeshes;++i) { + for (unsigned int a = 0, end = static_cast<unsigned int>(source_mesh_map.size()); a < end;++a) { + if (source_mesh_map[a].second == pcNode->mMeshes[i]) { + aiEntries.push_back(a); + } + } + } + + // now build the new list + delete pcNode->mMeshes; + pcNode->mNumMeshes = static_cast<unsigned int>(aiEntries.size()); + pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes]; + + for (unsigned int b = 0; b < pcNode->mNumMeshes;++b) { + pcNode->mMeshes[b] = aiEntries[b]; + } + + // recursively update children + for (unsigned int i = 0, end = pcNode->mNumChildren; i < end;++i) { + UpdateNode ( pcNode->mChildren[i], source_mesh_map ); + } +} + +static const unsigned int WAS_NOT_COPIED = 0xffffffff; + +using PerVertexWeight = std::pair <unsigned int,float>; +using VertexWeightTable = std::vector <PerVertexWeight>; + +// ------------------------------------------------------------------------------------------------ +VertexWeightTable* ComputeVertexBoneWeightTable(const aiMesh* pMesh) { + if (!pMesh || !pMesh->mNumVertices || !pMesh->mNumBones) { + return nullptr; + } + + VertexWeightTable* const avPerVertexWeights = new VertexWeightTable[pMesh->mNumVertices]; + for (unsigned int i = 0; i < pMesh->mNumBones;++i) { + + aiBone* bone = pMesh->mBones[i]; + for (unsigned int a = 0; a < bone->mNumWeights;++a) { + const aiVertexWeight& weight = bone->mWeights[a]; + avPerVertexWeights[weight.mVertexId].emplace_back(i,weight.mWeight); + } + } + return avPerVertexWeights; +} + +// ------------------------------------------------------------------------------------------------ +void MeshSplitter :: SplitMesh(unsigned int a, aiMesh* in_mesh, std::vector<std::pair<aiMesh*, unsigned int> >& source_mesh_map) { + // TODO: should better use std::(multi)set for source_mesh_map. + + if (in_mesh->mNumVertices <= LIMIT) { + source_mesh_map.emplace_back(in_mesh,a); + return; + } + + // build a per-vertex weight list if necessary + VertexWeightTable* avPerVertexWeights = ComputeVertexBoneWeightTable(in_mesh); + + // we need to split this mesh into sub meshes. Estimate submesh size + const unsigned int sub_meshes = (in_mesh->mNumVertices / LIMIT) + 1; + + // create a std::vector<unsigned int> to remember which vertices have already + // been copied and to which position (i.e. output index) + std::vector<unsigned int> was_copied_to; + was_copied_to.resize(in_mesh->mNumVertices,WAS_NOT_COPIED); + + // Try to find a good estimate for the number of output faces + // per mesh. Add 12.5% as buffer + unsigned int size_estimated = in_mesh->mNumFaces / sub_meshes; + size_estimated += size_estimated / 8; + + // now generate all submeshes + unsigned int base = 0; + while (true) { + const unsigned int out_vertex_index = LIMIT; + + aiMesh* out_mesh = new aiMesh(); + out_mesh->mNumVertices = 0; + out_mesh->mMaterialIndex = in_mesh->mMaterialIndex; + + // the name carries the adjacency information between the meshes + out_mesh->mName = in_mesh->mName; + + typedef std::vector<aiVertexWeight> BoneWeightList; + if (in_mesh->HasBones()) { + out_mesh->mBones = new aiBone*[in_mesh->mNumBones](); + } + + // clear the temporary helper array + if (base) { + std::fill(was_copied_to.begin(), was_copied_to.end(), WAS_NOT_COPIED); + } + + std::vector<aiFace> vFaces; + + // reserve enough storage for most cases + if (in_mesh->HasPositions()) { + out_mesh->mVertices = new aiVector3D[out_vertex_index]; + } + + if (in_mesh->HasNormals()) { + out_mesh->mNormals = new aiVector3D[out_vertex_index]; + } + + if (in_mesh->HasTangentsAndBitangents()) { + out_mesh->mTangents = new aiVector3D[out_vertex_index]; + out_mesh->mBitangents = new aiVector3D[out_vertex_index]; + } + + for (unsigned int c = 0; in_mesh->HasVertexColors(c);++c) { + out_mesh->mColors[c] = new aiColor4D[out_vertex_index]; + } + + for (unsigned int c = 0; in_mesh->HasTextureCoords(c);++c) { + out_mesh->mNumUVComponents[c] = in_mesh->mNumUVComponents[c]; + out_mesh->mTextureCoords[c] = new aiVector3D[out_vertex_index]; + } + vFaces.reserve(size_estimated); + + // (we will also need to copy the array of indices) + while (base < in_mesh->mNumFaces) { + const unsigned int iNumIndices = in_mesh->mFaces[base].mNumIndices; + + // doesn't catch degenerates but is quite fast + unsigned int iNeed = 0; + for (unsigned int v = 0; v < iNumIndices;++v) { + unsigned int index = in_mesh->mFaces[base].mIndices[v]; + + // check whether we do already have this vertex + if (WAS_NOT_COPIED == was_copied_to[index]) { + iNeed++; + } + } + if (out_mesh->mNumVertices + iNeed > out_vertex_index) { + // don't use this face + break; + } + + vFaces.emplace_back(); + aiFace& rFace = vFaces.back(); + + // setup face type and number of indices + rFace.mNumIndices = iNumIndices; + rFace.mIndices = new unsigned int[iNumIndices]; + + // need to update the output primitive types + switch (rFace.mNumIndices) + { + case 1: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + case 2: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + case 3: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + default: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + } + + // and copy the contents of the old array, offset them by current base + for (unsigned int v = 0; v < iNumIndices;++v) { + const unsigned int index = in_mesh->mFaces[base].mIndices[v]; + + // check whether we do already have this vertex + if (WAS_NOT_COPIED != was_copied_to[index]) { + rFace.mIndices[v] = was_copied_to[index]; + continue; + } + + // copy positions + out_mesh->mVertices[out_mesh->mNumVertices] = (in_mesh->mVertices[index]); + + // copy normals + if (in_mesh->HasNormals()) { + out_mesh->mNormals[out_mesh->mNumVertices] = (in_mesh->mNormals[index]); + } + + // copy tangents/bi-tangents + if (in_mesh->HasTangentsAndBitangents()) { + out_mesh->mTangents[out_mesh->mNumVertices] = (in_mesh->mTangents[index]); + out_mesh->mBitangents[out_mesh->mNumVertices] = (in_mesh->mBitangents[index]); + } + + // texture coordinates + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) { + if (in_mesh->HasTextureCoords( c)) { + out_mesh->mTextureCoords[c][out_mesh->mNumVertices] = in_mesh->mTextureCoords[c][index]; + } + } + // vertex colors + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS;++c) { + if (in_mesh->HasVertexColors( c)) { + out_mesh->mColors[c][out_mesh->mNumVertices] = in_mesh->mColors[c][index]; + } + } + // check whether we have bone weights assigned to this vertex + rFace.mIndices[v] = out_mesh->mNumVertices; + if (avPerVertexWeights) { + VertexWeightTable& table = avPerVertexWeights[ out_mesh->mNumVertices ]; + for (VertexWeightTable::const_iterator iter = table.begin(), end = table.end(); iter != end;++iter) { + // allocate the bone weight array if necessary and store it in the mBones field (HACK!) + BoneWeightList* weight_list = reinterpret_cast<BoneWeightList*>(out_mesh->mBones[(*iter).first]); + if (!weight_list) { + weight_list = new BoneWeightList(); + out_mesh->mBones[(*iter).first] = reinterpret_cast<aiBone*>(weight_list); + } + weight_list->push_back(aiVertexWeight(out_mesh->mNumVertices,(*iter).second)); + } + } + + was_copied_to[index] = out_mesh->mNumVertices; + out_mesh->mNumVertices++; + } + base++; + if(out_mesh->mNumVertices == out_vertex_index) { + // break here. The face is only added if it was complete + break; + } + } + + // check which bones we'll need to create for this submesh + if (in_mesh->HasBones()) { + aiBone** ppCurrent = out_mesh->mBones; + for (unsigned int k = 0; k < in_mesh->mNumBones;++k) { + // check whether the bone exists + BoneWeightList* const weight_list = reinterpret_cast<BoneWeightList*>(out_mesh->mBones[k]); + + if (weight_list) { + const aiBone* const bone_in = in_mesh->mBones[k]; + aiBone* const bone_out = new aiBone(); + *ppCurrent++ = bone_out; + bone_out->mName = aiString(bone_in->mName); + bone_out->mOffsetMatrix =bone_in->mOffsetMatrix; + bone_out->mNumWeights = (unsigned int)weight_list->size(); + bone_out->mWeights = new aiVertexWeight[bone_out->mNumWeights]; + + // copy the vertex weights + ::memcpy(bone_out->mWeights, &(*weight_list)[0],bone_out->mNumWeights * sizeof(aiVertexWeight)); + + delete weight_list; + out_mesh->mNumBones++; + } + } + } + + // copy the face list to the mesh + out_mesh->mFaces = new aiFace[vFaces.size()]; + out_mesh->mNumFaces = (unsigned int)vFaces.size(); + + for (unsigned int p = 0; p < out_mesh->mNumFaces;++p) { + out_mesh->mFaces[p] = vFaces[p]; + } + + // add the newly created mesh to the list + source_mesh_map.push_back(std::make_pair(out_mesh,a)); + + if (base == in_mesh->mNumFaces) { + break; + } + } + + // delete the per-vertex weight list again + delete[] avPerVertexWeights; + + // now delete the old mesh data + delete in_mesh; +} diff --git a/libs/assimp/code/AssetLib/Assjson/mesh_splitter.h b/libs/assimp/code/AssetLib/Assjson/mesh_splitter.h new file mode 100644 index 0000000..f7f9a93 --- /dev/null +++ b/libs/assimp/code/AssetLib/Assjson/mesh_splitter.h @@ -0,0 +1,52 @@ +/* +Assimp2Json +Copyright (c) 2011, Alexander C. Gessler + +Licensed under a 3-clause BSD license. See the LICENSE file for more information. + +*/ + +#ifndef INCLUDED_MESH_SPLITTER +#define INCLUDED_MESH_SPLITTER + +// ---------------------------------------------------------------------------- +// Note: this is largely based on assimp's SplitLargeMeshes_Vertex process. +// it is refactored and the coding style is slightly improved, though. +// ---------------------------------------------------------------------------- + +#include <vector> + +struct aiScene; +struct aiMesh; +struct aiNode; + +// --------------------------------------------------------------------------- +/** Splits meshes of unique vertices into meshes with no more vertices than + * a given, configurable threshold value. + */ +class MeshSplitter { +public: + unsigned int LIMIT; + + void SetLimit(unsigned int l) { + LIMIT = l; + } + + unsigned int GetLimit() const { + return LIMIT; + } + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute(aiScene *pScene); + +private: + void UpdateNode(aiNode *pcNode, const std::vector<std::pair<aiMesh *, unsigned int>> &source_mesh_map); + void SplitMesh(unsigned int index, aiMesh *mesh, std::vector<std::pair<aiMesh *, unsigned int>> &source_mesh_map); + +}; + +#endif // INCLUDED_MESH_SPLITTER diff --git a/libs/assimp/code/AssetLib/Assxml/AssxmlExporter.cpp b/libs/assimp/code/AssetLib/Assxml/AssxmlExporter.cpp new file mode 100644 index 0000000..731916a --- /dev/null +++ b/libs/assimp/code/AssetLib/Assxml/AssxmlExporter.cpp @@ -0,0 +1,68 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AssxmlExporter.cpp + * ASSXML exporter main code + */ + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER + +#include "AssxmlFileWriter.h" +#include <assimp/IOSystem.hpp> +#include <assimp/Exporter.hpp> + +namespace Assimp { + +void ExportSceneAssxml(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) +{ + DumpSceneToAssxml( + pFile, + "\0", // command(s) + pIOSystem, + pScene, + false); // shortened? +} + +} // end of namespace Assimp + +#endif // ASSIMP_BUILD_NO_ASSXML_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/Assxml/AssxmlExporter.h b/libs/assimp/code/AssetLib/Assxml/AssxmlExporter.h new file mode 100644 index 0000000..6fcdebf --- /dev/null +++ b/libs/assimp/code/AssetLib/Assxml/AssxmlExporter.h @@ -0,0 +1,52 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AssxmlExporter.h + * ASSXML Exporter Main Header + */ +#pragma once +#ifndef AI_ASSXMLEXPORTER_H_INC +#define AI_ASSXMLEXPORTER_H_INC + +// nothing really needed here - reserved for future use like properties + +#endif diff --git a/libs/assimp/code/AssetLib/Assxml/AssxmlFileWriter.cpp b/libs/assimp/code/AssetLib/Assxml/AssxmlFileWriter.cpp new file mode 100644 index 0000000..640890e --- /dev/null +++ b/libs/assimp/code/AssetLib/Assxml/AssxmlFileWriter.cpp @@ -0,0 +1,662 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AssxmlFileWriter.cpp + * @brief Implementation of Assxml file writer. + */ + +#include "AssxmlFileWriter.h" + +#include "PostProcessing/ProcessHelper.h" + +#include <assimp/version.h> +#include <assimp/Exporter.hpp> +#include <assimp/IOStream.hpp> +#include <assimp/IOSystem.hpp> + +#include <stdarg.h> + +#ifdef ASSIMP_BUILD_NO_OWN_ZLIB +#include <zlib.h> +#else +#include <contrib/zlib/zlib.h> +#endif + +#include <stdio.h> +#include <time.h> +#include <memory> + +using namespace Assimp; + +namespace Assimp { + +namespace AssxmlFileWriter { + +// ----------------------------------------------------------------------------------- +static int ioprintf(IOStream *io, const char *format, ...) { + using namespace std; + if (nullptr == io) { + return -1; + } + + static const int Size = 4096; + char sz[Size]; + ::memset(sz, '\0', Size); + va_list va; + va_start(va, format); + const unsigned int nSize = vsnprintf(sz, Size - 1, format, va); + ai_assert(nSize < Size); + va_end(va); + + io->Write(sz, sizeof(char), nSize); + + return nSize; +} + +// ----------------------------------------------------------------------------------- +// Convert a name to standard XML format +static void ConvertName(aiString &out, const aiString &in) { + out.length = 0; + for (unsigned int i = 0; i < in.length; ++i) { + switch (in.data[i]) { + case '<': + out.Append("<"); + break; + case '>': + out.Append(">"); + break; + case '&': + out.Append("&"); + break; + case '\"': + out.Append("""); + break; + case '\'': + out.Append("'"); + break; + default: + out.data[out.length++] = in.data[i]; + } + } + out.data[out.length] = 0; +} + +// ----------------------------------------------------------------------------------- +// Write a single node as text dump +static void WriteNode(const aiNode *node, IOStream *io, unsigned int depth) { + char prefix[512]; + for (unsigned int i = 0; i < depth; ++i) + prefix[i] = '\t'; + prefix[depth] = '\0'; + + const aiMatrix4x4 &m = node->mTransformation; + + aiString name; + ConvertName(name, node->mName); + ioprintf(io, "%s<Node name=\"%s\"> \n" + "%s\t<Matrix4> \n" + "%s\t\t%0 6f %0 6f %0 6f %0 6f\n" + "%s\t\t%0 6f %0 6f %0 6f %0 6f\n" + "%s\t\t%0 6f %0 6f %0 6f %0 6f\n" + "%s\t\t%0 6f %0 6f %0 6f %0 6f\n" + "%s\t</Matrix4> \n", + prefix, name.data, prefix, + prefix, m.a1, m.a2, m.a3, m.a4, + prefix, m.b1, m.b2, m.b3, m.b4, + prefix, m.c1, m.c2, m.c3, m.c4, + prefix, m.d1, m.d2, m.d3, m.d4, prefix); + + if (node->mNumMeshes) { + ioprintf(io, "%s\t<MeshRefs num=\"%u\">\n%s\t", + prefix, node->mNumMeshes, prefix); + + for (unsigned int i = 0; i < node->mNumMeshes; ++i) { + ioprintf(io, "%u ", node->mMeshes[i]); + } + ioprintf(io, "\n%s\t</MeshRefs>\n", prefix); + } + + if (node->mNumChildren) { + ioprintf(io, "%s\t<NodeList num=\"%u\">\n", + prefix, node->mNumChildren); + + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + WriteNode(node->mChildren[i], io, depth + 2); + } + ioprintf(io, "%s\t</NodeList>\n", prefix); + } + ioprintf(io, "%s</Node>\n", prefix); +} + +// ----------------------------------------------------------------------------------- +// Some chunks of text will need to be encoded for XML +// http://stackoverflow.com/questions/5665231/most-efficient-way-to-escape-xml-html-in-c-string#5665377 +static std::string encodeXML(const std::string &data) { + std::string buffer; + buffer.reserve(data.size()); + for (size_t pos = 0; pos != data.size(); ++pos) { + switch (data[pos]) { + case '&': buffer.append("&"); break; + case '\"': buffer.append("""); break; + case '\'': buffer.append("'"); break; + case '<': buffer.append("<"); break; + case '>': buffer.append(">"); break; + default: buffer.append(&data[pos], 1); break; + } + } + return buffer; +} + +// ----------------------------------------------------------------------------------- +// Write a text model dump +static void WriteDump(const char *pFile, const char *cmd, const aiScene *scene, IOStream *io, bool shortened) { + time_t tt = ::time(nullptr); +#if _WIN32 + tm *p = gmtime(&tt); +#else + struct tm now; + tm *p = gmtime_r(&tt, &now); +#endif + ai_assert(nullptr != p); + + std::string c = cmd; + std::string::size_type s; + + // https://sourceforge.net/tracker/?func=detail&aid=3167364&group_id=226462&atid=1067632 + // -- not allowed in XML comments + while ((s = c.find("--")) != std::string::npos) { + c[s] = '?'; + } + + // write header + std::string header( + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<ASSIMP format_id=\"1\">\n\n" + "<!-- XML Model dump produced by assimp dump\n" + " Library version: %u.%u.%u\n" + " Source: %s\n" + " Command line: %s\n" + " %s\n" + "-->" + " \n\n" + "<Scene flags=\"%u\" postprocessing=\"%u\">\n"); + + const unsigned int majorVersion(aiGetVersionMajor()); + const unsigned int minorVersion(aiGetVersionMinor()); + const unsigned int rev(aiGetVersionRevision()); + const char *curtime(asctime(p)); + ioprintf(io, header.c_str(), majorVersion, minorVersion, rev, pFile, c.c_str(), curtime, scene->mFlags, 0u); + + // write the node graph + WriteNode(scene->mRootNode, io, 0); + +#if 0 + // write cameras + for (unsigned int i = 0; i < scene->mNumCameras;++i) { + aiCamera* cam = scene->mCameras[i]; + ConvertName(name,cam->mName); + + // camera header + ioprintf(io,"\t<Camera parent=\"%s\">\n" + "\t\t<Vector3 name=\"up\" > %0 8f %0 8f %0 8f </Vector3>\n" + "\t\t<Vector3 name=\"lookat\" > %0 8f %0 8f %0 8f </Vector3>\n" + "\t\t<Vector3 name=\"pos\" > %0 8f %0 8f %0 8f </Vector3>\n" + "\t\t<Float name=\"fov\" > %f </Float>\n" + "\t\t<Float name=\"aspect\" > %f </Float>\n" + "\t\t<Float name=\"near_clip\" > %f </Float>\n" + "\t\t<Float name=\"far_clip\" > %f </Float>\n" + "\t</Camera>\n", + name.data, + cam->mUp.x,cam->mUp.y,cam->mUp.z, + cam->mLookAt.x,cam->mLookAt.y,cam->mLookAt.z, + cam->mPosition.x,cam->mPosition.y,cam->mPosition.z, + cam->mHorizontalFOV,cam->mAspect,cam->mClipPlaneNear,cam->mClipPlaneFar,i); + } + + // write lights + for (unsigned int i = 0; i < scene->mNumLights;++i) { + aiLight* l = scene->mLights[i]; + ConvertName(name,l->mName); + + // light header + ioprintf(io,"\t<Light parent=\"%s\"> type=\"%s\"\n" + "\t\t<Vector3 name=\"diffuse\" > %0 8f %0 8f %0 8f </Vector3>\n" + "\t\t<Vector3 name=\"specular\" > %0 8f %0 8f %0 8f </Vector3>\n" + "\t\t<Vector3 name=\"ambient\" > %0 8f %0 8f %0 8f </Vector3>\n", + name.data, + (l->mType == aiLightSource_DIRECTIONAL ? "directional" : + (l->mType == aiLightSource_POINT ? "point" : "spot" )), + l->mColorDiffuse.r, l->mColorDiffuse.g, l->mColorDiffuse.b, + l->mColorSpecular.r,l->mColorSpecular.g,l->mColorSpecular.b, + l->mColorAmbient.r, l->mColorAmbient.g, l->mColorAmbient.b); + + if (l->mType != aiLightSource_DIRECTIONAL) { + ioprintf(io, + "\t\t<Vector3 name=\"pos\" > %0 8f %0 8f %0 8f </Vector3>\n" + "\t\t<Float name=\"atten_cst\" > %f </Float>\n" + "\t\t<Float name=\"atten_lin\" > %f </Float>\n" + "\t\t<Float name=\"atten_sqr\" > %f </Float>\n", + l->mPosition.x,l->mPosition.y,l->mPosition.z, + l->mAttenuationConstant,l->mAttenuationLinear,l->mAttenuationQuadratic); + } + + if (l->mType != aiLightSource_POINT) { + ioprintf(io, + "\t\t<Vector3 name=\"lookat\" > %0 8f %0 8f %0 8f </Vector3>\n", + l->mDirection.x,l->mDirection.y,l->mDirection.z); + } + + if (l->mType == aiLightSource_SPOT) { + ioprintf(io, + "\t\t<Float name=\"cone_out\" > %f </Float>\n" + "\t\t<Float name=\"cone_inn\" > %f </Float>\n", + l->mAngleOuterCone,l->mAngleInnerCone); + } + ioprintf(io,"\t</Light>\n"); + } +#endif + aiString name; + + // write textures + if (scene->mNumTextures) { + ioprintf(io, "<TextureList num=\"%u\">\n", scene->mNumTextures); + for (unsigned int i = 0; i < scene->mNumTextures; ++i) { + aiTexture *tex = scene->mTextures[i]; + bool compressed = (tex->mHeight == 0); + + // mesh header + ioprintf(io, "\t<Texture width=\"%u\" height=\"%u\" compressed=\"%s\"> \n", + (compressed ? -1 : tex->mWidth), (compressed ? -1 : tex->mHeight), + (compressed ? "true" : "false")); + + if (compressed) { + ioprintf(io, "\t\t<Data length=\"%u\"> \n", tex->mWidth); + + if (!shortened) { + for (unsigned int n = 0; n < tex->mWidth; ++n) { + ioprintf(io, "\t\t\t%2x", reinterpret_cast<uint8_t *>(tex->pcData)[n]); + if (n && !(n % 50)) { + ioprintf(io, "\n"); + } + } + } + } else if (!shortened) { + ioprintf(io, "\t\t<Data length=\"%u\"> \n", tex->mWidth * tex->mHeight * 4); + + // const unsigned int width = (unsigned int)std::log10((double)std::max(tex->mHeight,tex->mWidth))+1; + for (unsigned int y = 0; y < tex->mHeight; ++y) { + for (unsigned int x = 0; x < tex->mWidth; ++x) { + aiTexel *tx = tex->pcData + y * tex->mWidth + x; + unsigned int r = tx->r, g = tx->g, b = tx->b, a = tx->a; + ioprintf(io, "\t\t\t%2x %2x %2x %2x", r, g, b, a); + + // group by four for readability + if (0 == (x + y * tex->mWidth) % 4) { + ioprintf(io, "\n"); + } + } + } + } + ioprintf(io, "\t\t</Data>\n\t</Texture>\n"); + } + ioprintf(io, "</TextureList>\n"); + } + + // write materials + if (scene->mNumMaterials) { + ioprintf(io, "<MaterialList num=\"%u\">\n", scene->mNumMaterials); + for (unsigned int i = 0; i < scene->mNumMaterials; ++i) { + const aiMaterial *mat = scene->mMaterials[i]; + + ioprintf(io, "\t<Material>\n"); + ioprintf(io, "\t\t<MatPropertyList num=\"%u\">\n", mat->mNumProperties); + for (unsigned int n = 0; n < mat->mNumProperties; ++n) { + + const aiMaterialProperty *prop = mat->mProperties[n]; + const char *sz = ""; + if (prop->mType == aiPTI_Float) { + sz = "float"; + } else if (prop->mType == aiPTI_Integer) { + sz = "integer"; + } else if (prop->mType == aiPTI_String) { + sz = "string"; + } else if (prop->mType == aiPTI_Buffer) { + sz = "binary_buffer"; + } + + ioprintf(io, "\t\t\t<MatProperty key=\"%s\" \n\t\t\ttype=\"%s\" tex_usage=\"%s\" tex_index=\"%u\"", + prop->mKey.data, sz, + ::TextureTypeToString((aiTextureType)prop->mSemantic), prop->mIndex); + + if (prop->mType == aiPTI_Float) { + ioprintf(io, " size=\"%i\">\n\t\t\t\t", + static_cast<int>(prop->mDataLength / sizeof(float))); + + for (unsigned int pp = 0; pp < prop->mDataLength / sizeof(float); ++pp) { + ioprintf(io, "%f ", *((float *)(prop->mData + pp * sizeof(float)))); + } + } else if (prop->mType == aiPTI_Integer) { + ioprintf(io, " size=\"%i\">\n\t\t\t\t", + static_cast<int>(prop->mDataLength / sizeof(int))); + + for (unsigned int pp = 0; pp < prop->mDataLength / sizeof(int); ++pp) { + ioprintf(io, "%i ", *((int *)(prop->mData + pp * sizeof(int)))); + } + } else if (prop->mType == aiPTI_Buffer) { + ioprintf(io, " size=\"%i\">\n\t\t\t\t", + static_cast<int>(prop->mDataLength)); + + for (unsigned int pp = 0; pp < prop->mDataLength; ++pp) { + ioprintf(io, "%2x ", prop->mData[pp]); + if (pp && 0 == pp % 30) { + ioprintf(io, "\n\t\t\t\t"); + } + } + } else if (prop->mType == aiPTI_String) { + ioprintf(io, ">\n\t\t\t\t\"%s\"", encodeXML(prop->mData + 4).c_str() /* skip length */); + } + ioprintf(io, "\n\t\t\t</MatProperty>\n"); + } + ioprintf(io, "\t\t</MatPropertyList>\n"); + ioprintf(io, "\t</Material>\n"); + } + ioprintf(io, "</MaterialList>\n"); + } + + // write animations + if (scene->mNumAnimations) { + ioprintf(io, "<AnimationList num=\"%u\">\n", scene->mNumAnimations); + for (unsigned int i = 0; i < scene->mNumAnimations; ++i) { + aiAnimation *anim = scene->mAnimations[i]; + + // anim header + ConvertName(name, anim->mName); + ioprintf(io, "\t<Animation name=\"%s\" duration=\"%e\" tick_cnt=\"%e\">\n", + name.data, anim->mDuration, anim->mTicksPerSecond); + + // write bone animation channels + if (anim->mNumChannels) { + ioprintf(io, "\t\t<NodeAnimList num=\"%u\">\n", anim->mNumChannels); + for (unsigned int n = 0; n < anim->mNumChannels; ++n) { + aiNodeAnim *nd = anim->mChannels[n]; + + // node anim header + ConvertName(name, nd->mNodeName); + ioprintf(io, "\t\t\t<NodeAnim node=\"%s\">\n", name.data); + + if (!shortened) { + // write position keys + if (nd->mNumPositionKeys) { + ioprintf(io, "\t\t\t\t<PositionKeyList num=\"%u\">\n", nd->mNumPositionKeys); + for (unsigned int a = 0; a < nd->mNumPositionKeys; ++a) { + aiVectorKey *vc = nd->mPositionKeys + a; + ioprintf(io, "\t\t\t\t\t<PositionKey time=\"%e\">\n" + "\t\t\t\t\t\t%0 8f %0 8f %0 8f\n\t\t\t\t\t</PositionKey>\n", + vc->mTime, vc->mValue.x, vc->mValue.y, vc->mValue.z); + } + ioprintf(io, "\t\t\t\t</PositionKeyList>\n"); + } + + // write scaling keys + if (nd->mNumScalingKeys) { + ioprintf(io, "\t\t\t\t<ScalingKeyList num=\"%u\">\n", nd->mNumScalingKeys); + for (unsigned int a = 0; a < nd->mNumScalingKeys; ++a) { + aiVectorKey *vc = nd->mScalingKeys + a; + ioprintf(io, "\t\t\t\t\t<ScalingKey time=\"%e\">\n" + "\t\t\t\t\t\t%0 8f %0 8f %0 8f\n\t\t\t\t\t</ScalingKey>\n", + vc->mTime, vc->mValue.x, vc->mValue.y, vc->mValue.z); + } + ioprintf(io, "\t\t\t\t</ScalingKeyList>\n"); + } + + // write rotation keys + if (nd->mNumRotationKeys) { + ioprintf(io, "\t\t\t\t<RotationKeyList num=\"%u\">\n", nd->mNumRotationKeys); + for (unsigned int a = 0; a < nd->mNumRotationKeys; ++a) { + aiQuatKey *vc = nd->mRotationKeys + a; + ioprintf(io, "\t\t\t\t\t<RotationKey time=\"%e\">\n" + "\t\t\t\t\t\t%0 8f %0 8f %0 8f %0 8f\n\t\t\t\t\t</RotationKey>\n", + vc->mTime, vc->mValue.x, vc->mValue.y, vc->mValue.z, vc->mValue.w); + } + ioprintf(io, "\t\t\t\t</RotationKeyList>\n"); + } + } + ioprintf(io, "\t\t\t</NodeAnim>\n"); + } + ioprintf(io, "\t\t</NodeAnimList>\n"); + } + ioprintf(io, "\t</Animation>\n"); + } + ioprintf(io, "</AnimationList>\n"); + } + + // write meshes + if (scene->mNumMeshes) { + ioprintf(io, "<MeshList num=\"%u\">\n", scene->mNumMeshes); + for (unsigned int i = 0; i < scene->mNumMeshes; ++i) { + aiMesh *mesh = scene->mMeshes[i]; + // const unsigned int width = (unsigned int)std::log10((double)mesh->mNumVertices)+1; + + // mesh header + ioprintf(io, "\t<Mesh types=\"%s %s %s %s\" material_index=\"%u\">\n", + (mesh->mPrimitiveTypes & aiPrimitiveType_POINT ? "points" : ""), + (mesh->mPrimitiveTypes & aiPrimitiveType_LINE ? "lines" : ""), + (mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE ? "triangles" : ""), + (mesh->mPrimitiveTypes & aiPrimitiveType_POLYGON ? "polygons" : ""), + mesh->mMaterialIndex); + + // bones + if (mesh->mNumBones) { + ioprintf(io, "\t\t<BoneList num=\"%u\">\n", mesh->mNumBones); + + for (unsigned int n = 0; n < mesh->mNumBones; ++n) { + aiBone *bone = mesh->mBones[n]; + + ConvertName(name, bone->mName); + // bone header + ioprintf(io, "\t\t\t<Bone name=\"%s\">\n" + "\t\t\t\t<Matrix4> \n" + "\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n" + "\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n" + "\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n" + "\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n" + "\t\t\t\t</Matrix4> \n", + name.data, + bone->mOffsetMatrix.a1, bone->mOffsetMatrix.a2, bone->mOffsetMatrix.a3, bone->mOffsetMatrix.a4, + bone->mOffsetMatrix.b1, bone->mOffsetMatrix.b2, bone->mOffsetMatrix.b3, bone->mOffsetMatrix.b4, + bone->mOffsetMatrix.c1, bone->mOffsetMatrix.c2, bone->mOffsetMatrix.c3, bone->mOffsetMatrix.c4, + bone->mOffsetMatrix.d1, bone->mOffsetMatrix.d2, bone->mOffsetMatrix.d3, bone->mOffsetMatrix.d4); + + if (!shortened && bone->mNumWeights) { + ioprintf(io, "\t\t\t\t<WeightList num=\"%u\">\n", bone->mNumWeights); + + // bone weights + for (unsigned int a = 0; a < bone->mNumWeights; ++a) { + aiVertexWeight *wght = bone->mWeights + a; + + ioprintf(io, "\t\t\t\t\t<Weight index=\"%u\">\n\t\t\t\t\t\t%f\n\t\t\t\t\t</Weight>\n", + wght->mVertexId, wght->mWeight); + } + ioprintf(io, "\t\t\t\t</WeightList>\n"); + } + ioprintf(io, "\t\t\t</Bone>\n"); + } + ioprintf(io, "\t\t</BoneList>\n"); + } + + // faces + if (!shortened && mesh->mNumFaces) { + ioprintf(io, "\t\t<FaceList num=\"%u\">\n", mesh->mNumFaces); + for (unsigned int n = 0; n < mesh->mNumFaces; ++n) { + aiFace &f = mesh->mFaces[n]; + ioprintf(io, "\t\t\t<Face num=\"%u\">\n" + "\t\t\t\t", + f.mNumIndices); + + for (unsigned int j = 0; j < f.mNumIndices; ++j) + ioprintf(io, "%u ", f.mIndices[j]); + + ioprintf(io, "\n\t\t\t</Face>\n"); + } + ioprintf(io, "\t\t</FaceList>\n"); + } + + // vertex positions + if (mesh->HasPositions()) { + ioprintf(io, "\t\t<Positions num=\"%u\" set=\"0\" num_components=\"3\"> \n", mesh->mNumVertices); + if (!shortened) { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io, "\t\t%0 8f %0 8f %0 8f\n", + mesh->mVertices[n].x, + mesh->mVertices[n].y, + mesh->mVertices[n].z); + } + } + ioprintf(io, "\t\t</Positions>\n"); + } + + // vertex normals + if (mesh->HasNormals()) { + ioprintf(io, "\t\t<Normals num=\"%u\" set=\"0\" num_components=\"3\"> \n", mesh->mNumVertices); + if (!shortened) { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io, "\t\t%0 8f %0 8f %0 8f\n", + mesh->mNormals[n].x, + mesh->mNormals[n].y, + mesh->mNormals[n].z); + } + } + ioprintf(io, "\t\t</Normals>\n"); + } + + // vertex tangents and bitangents + if (mesh->HasTangentsAndBitangents()) { + ioprintf(io, "\t\t<Tangents num=\"%u\" set=\"0\" num_components=\"3\"> \n", mesh->mNumVertices); + if (!shortened) { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io, "\t\t%0 8f %0 8f %0 8f\n", + mesh->mTangents[n].x, + mesh->mTangents[n].y, + mesh->mTangents[n].z); + } + } + ioprintf(io, "\t\t</Tangents>\n"); + + ioprintf(io, "\t\t<Bitangents num=\"%u\" set=\"0\" num_components=\"3\"> \n", mesh->mNumVertices); + if (!shortened) { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io, "\t\t%0 8f %0 8f %0 8f\n", + mesh->mBitangents[n].x, + mesh->mBitangents[n].y, + mesh->mBitangents[n].z); + } + } + ioprintf(io, "\t\t</Bitangents>\n"); + } + + // texture coordinates + for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { + if (!mesh->mTextureCoords[a]) + break; + + ioprintf(io, "\t\t<TextureCoords num=\"%u\" set=\"%u\" name=\"%s\" num_components=\"%u\"> \n", + mesh->mNumVertices, + a, + (mesh->HasTextureCoordsName(a) ? mesh->GetTextureCoordsName(a)->C_Str() : ""), + mesh->mNumUVComponents[a]); + + if (!shortened) { + if (mesh->mNumUVComponents[a] == 3) { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io, "\t\t%0 8f %0 8f %0 8f\n", + mesh->mTextureCoords[a][n].x, + mesh->mTextureCoords[a][n].y, + mesh->mTextureCoords[a][n].z); + } + } else { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io, "\t\t%0 8f %0 8f\n", + mesh->mTextureCoords[a][n].x, + mesh->mTextureCoords[a][n].y); + } + } + } + ioprintf(io, "\t\t</TextureCoords>\n"); + } + + // vertex colors + for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) { + if (!mesh->mColors[a]) + break; + ioprintf(io, "\t\t<Colors num=\"%u\" set=\"%u\" num_components=\"4\"> \n", mesh->mNumVertices, a); + if (!shortened) { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io, "\t\t%0 8f %0 8f %0 8f %0 8f\n", + mesh->mColors[a][n].r, + mesh->mColors[a][n].g, + mesh->mColors[a][n].b, + mesh->mColors[a][n].a); + } + } + ioprintf(io, "\t\t</Colors>\n"); + } + ioprintf(io, "\t</Mesh>\n"); + } + ioprintf(io, "</MeshList>\n"); + } + ioprintf(io, "</Scene>\n</ASSIMP>"); +} + +} // end of namespace AssxmlFileWriter + +void DumpSceneToAssxml( + const char *pFile, const char *cmd, IOSystem *pIOSystem, + const aiScene *pScene, bool shortened) { + std::unique_ptr<IOStream> file(pIOSystem->Open(pFile, "wt")); + if (!file.get()) { + throw std::runtime_error("Unable to open output file " + std::string(pFile) + '\n'); + } + + AssxmlFileWriter::WriteDump(pFile, cmd, pScene, file.get(), shortened); +} + +} // end of namespace Assimp diff --git a/libs/assimp/code/AssetLib/Assxml/AssxmlFileWriter.h b/libs/assimp/code/AssetLib/Assxml/AssxmlFileWriter.h new file mode 100644 index 0000000..0620c9d --- /dev/null +++ b/libs/assimp/code/AssetLib/Assxml/AssxmlFileWriter.h @@ -0,0 +1,64 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AssxmlFileWriter.h + * @brief Declaration of Assxml file writer. + */ +#pragma once +#ifndef AI_ASSXMLFILEWRITER_H_INC +#define AI_ASSXMLFILEWRITER_H_INC + +#include <assimp/defs.h> +#include <assimp/scene.h> +#include <assimp/IOSystem.hpp> + +namespace Assimp { + +void ASSIMP_API DumpSceneToAssxml( + const char* pFile, + const char* cmd, + IOSystem* pIOSystem, + const aiScene* pScene, + bool shortened); + +} + +#endif // AI_ASSXMLFILEWRITER_H_INC diff --git a/libs/assimp/code/AssetLib/B3D/B3DImporter.cpp b/libs/assimp/code/AssetLib/B3D/B3DImporter.cpp new file mode 100644 index 0000000..c4ef75b --- /dev/null +++ b/libs/assimp/code/AssetLib/B3D/B3DImporter.cpp @@ -0,0 +1,744 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 B3DImporter.cpp + * @brief Implementation of the b3d importer class + */ + +#ifndef ASSIMP_BUILD_NO_B3D_IMPORTER + +// internal headers +#include "AssetLib/B3D/B3DImporter.h" +#include "PostProcessing/ConvertToLHProcess.h" +#include "PostProcessing/TextureTransform.h" + +#include <assimp/StringUtils.h> +#include <assimp/anim.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> + +#include <memory> + +using namespace Assimp; +using namespace std; + +static const aiImporterDesc desc = { + "BlitzBasic 3D Importer", + "", + "", + "http://www.blitzbasic.com/", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "b3d" +}; + +#ifdef _MSC_VER +#pragma warning(disable : 4018) +#endif + +//#define DEBUG_B3D + +template<typename T> +void DeleteAllBarePointers(std::vector<T> &x) { + for (auto p : x) { + delete p; + } +} + +B3DImporter::~B3DImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +bool B3DImporter::CanRead(const std::string &pFile, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const { + size_t pos = pFile.find_last_of('.'); + if (pos == string::npos) { + return false; + } + + string ext = pFile.substr(pos + 1); + if (ext.size() != 3) { + return false; + } + + return (ext[0] == 'b' || ext[0] == 'B') && (ext[1] == '3') && (ext[2] == 'd' || ext[2] == 'D'); +} + +// ------------------------------------------------------------------------------------------------ +// Loader meta information +const aiImporterDesc *B3DImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile)); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open B3D file ", pFile, "."); + } + + // check whether the .b3d file is large enough to contain + // at least one chunk. + size_t fileSize = file->FileSize(); + if (fileSize < 8) { + throw DeadlyImportError("B3D File is too small."); + } + + _pos = 0; + _buf.resize(fileSize); + file->Read(&_buf[0], 1, fileSize); + _stack.clear(); + + ReadBB3D(pScene); +} + +// ------------------------------------------------------------------------------------------------ +AI_WONT_RETURN void B3DImporter::Oops() { + throw DeadlyImportError("B3D Importer - INTERNAL ERROR"); +} + +// ------------------------------------------------------------------------------------------------ +AI_WONT_RETURN void B3DImporter::Fail(const string &str) { +#ifdef DEBUG_B3D + ASSIMP_LOG_ERROR("Error in B3D file data: ", str); +#endif + throw DeadlyImportError("B3D Importer - error in B3D file data: ", str); +} + +// ------------------------------------------------------------------------------------------------ +int B3DImporter::ReadByte() { + if (_pos > _buf.size()) { + Fail("EOF"); + } + + return _buf[_pos++]; +} + +// ------------------------------------------------------------------------------------------------ +int B3DImporter::ReadInt() { + if (_pos + 4 > _buf.size()) { + Fail("EOF"); + } + + int n; + memcpy(&n, &_buf[_pos], 4); + _pos += 4; + + return n; +} + +// ------------------------------------------------------------------------------------------------ +float B3DImporter::ReadFloat() { + if (_pos + 4 > _buf.size()) { + Fail("EOF"); + } + + float n; + memcpy(&n, &_buf[_pos], 4); + _pos += 4; + + return n; +} + +// ------------------------------------------------------------------------------------------------ +aiVector2D B3DImporter::ReadVec2() { + float x = ReadFloat(); + float y = ReadFloat(); + return aiVector2D(x, y); +} + +// ------------------------------------------------------------------------------------------------ +aiVector3D B3DImporter::ReadVec3() { + float x = ReadFloat(); + float y = ReadFloat(); + float z = ReadFloat(); + return aiVector3D(x, y, z); +} + +// ------------------------------------------------------------------------------------------------ +aiQuaternion B3DImporter::ReadQuat() { + // (aramis_acg) Fix to adapt the loader to changed quat orientation + float w = -ReadFloat(); + float x = ReadFloat(); + float y = ReadFloat(); + float z = ReadFloat(); + return aiQuaternion(w, x, y, z); +} + +// ------------------------------------------------------------------------------------------------ +string B3DImporter::ReadString() { + if (_pos > _buf.size()) { + Fail("EOF"); + } + string str; + while (_pos < _buf.size()) { + char c = (char)ReadByte(); + if (!c) { + return str; + } + str += c; + } + return string(); +} + +// ------------------------------------------------------------------------------------------------ +string B3DImporter::ReadChunk() { + string tag; + for (int i = 0; i < 4; ++i) { + tag += char(ReadByte()); + } +#ifdef DEBUG_B3D + ASSIMP_LOG_DEBUG("ReadChunk: ", tag); +#endif + unsigned sz = (unsigned)ReadInt(); + _stack.push_back(_pos + sz); + return tag; +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ExitChunk() { + _pos = _stack.back(); + _stack.pop_back(); +} + +// ------------------------------------------------------------------------------------------------ +size_t B3DImporter::ChunkSize() { + return _stack.back() - _pos; +} +// ------------------------------------------------------------------------------------------------ + +template <class T> +T *B3DImporter::to_array(const vector<T> &v) { + if (v.empty()) { + return 0; + } + T *p = new T[v.size()]; + for (size_t i = 0; i < v.size(); ++i) { + p[i] = v[i]; + } + return p; +} + +// ------------------------------------------------------------------------------------------------ +template <class T> +T **unique_to_array(vector<std::unique_ptr<T>> &v) { + if (v.empty()) { + return 0; + } + T **p = new T *[v.size()]; + for (size_t i = 0; i < v.size(); ++i) { + p[i] = v[i].release(); + } + return p; +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadTEXS() { + while (ChunkSize()) { + string name = ReadString(); + /*int flags=*/ReadInt(); + /*int blend=*/ReadInt(); + /*aiVector2D pos=*/ReadVec2(); + /*aiVector2D scale=*/ReadVec2(); + /*float rot=*/ReadFloat(); + + _textures.push_back(name); + } +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadBRUS() { + int n_texs = ReadInt(); + if (n_texs < 0 || n_texs > 8) { + Fail("Bad texture count"); + } + while (ChunkSize()) { + string name = ReadString(); + aiVector3D color = ReadVec3(); + float alpha = ReadFloat(); + float shiny = ReadFloat(); + /*int blend=**/ ReadInt(); + int fx = ReadInt(); + + std::unique_ptr<aiMaterial> mat(new aiMaterial); + + // Name + aiString ainame(name); + mat->AddProperty(&ainame, AI_MATKEY_NAME); + + // Diffuse color + mat->AddProperty(&color, 1, AI_MATKEY_COLOR_DIFFUSE); + + // Opacity + mat->AddProperty(&alpha, 1, AI_MATKEY_OPACITY); + + // Specular color + aiColor3D speccolor(shiny, shiny, shiny); + mat->AddProperty(&speccolor, 1, AI_MATKEY_COLOR_SPECULAR); + + // Specular power + float specpow = shiny * 128; + mat->AddProperty(&specpow, 1, AI_MATKEY_SHININESS); + + // Double sided + if (fx & 0x10) { + int i = 1; + mat->AddProperty(&i, 1, AI_MATKEY_TWOSIDED); + } + + //Textures + for (int i = 0; i < n_texs; ++i) { + int texid = ReadInt(); + if (texid < -1 || (texid >= 0 && texid >= static_cast<int>(_textures.size()))) { + Fail("Bad texture id"); + } + if (i == 0 && texid >= 0) { + aiString texname(_textures[texid]); + mat->AddProperty(&texname, AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + } + _materials.emplace_back(std::move(mat)); + } +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadVRTS() { + _vflags = ReadInt(); + _tcsets = ReadInt(); + _tcsize = ReadInt(); + if (_tcsets < 0 || _tcsets > 4 || _tcsize < 0 || _tcsize > 4) { + Fail("Bad texcoord data"); + } + + int sz = 12 + (_vflags & 1 ? 12 : 0) + (_vflags & 2 ? 16 : 0) + (_tcsets * _tcsize * 4); + size_t n_verts = ChunkSize() / sz; + + int v0 = static_cast<int>(_vertices.size()); + _vertices.resize(v0 + n_verts); + + for (unsigned int i = 0; i < n_verts; ++i) { + Vertex &v = _vertices[v0 + i]; + + memset(v.bones, 0, sizeof(v.bones)); + memset(v.weights, 0, sizeof(v.weights)); + + v.vertex = ReadVec3(); + + if (_vflags & 1) { + v.normal = ReadVec3(); + } + + if (_vflags & 2) { + ReadQuat(); //skip v 4bytes... + } + + for (int j = 0; j < _tcsets; ++j) { + float t[4] = { 0, 0, 0, 0 }; + for (int k = 0; k < _tcsize; ++k) { + t[k] = ReadFloat(); + } + t[1] = 1 - t[1]; + if (!j) { + v.texcoords = aiVector3D(t[0], t[1], t[2]); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadTRIS(int v0) { + int matid = ReadInt(); + if (matid == -1) { + matid = 0; + } else if (matid < 0 || matid >= (int)_materials.size()) { +#ifdef DEBUG_B3D + ASSIMP_LOG_ERROR("material id=", matid); +#endif + Fail("Bad material id"); + } + + std::unique_ptr<aiMesh> mesh(new aiMesh); + + mesh->mMaterialIndex = matid; + mesh->mNumFaces = 0; + mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + size_t n_tris = ChunkSize() / 12; + aiFace *face = mesh->mFaces = new aiFace[n_tris]; + + for (unsigned int i = 0; i < n_tris; ++i) { + int i0 = ReadInt() + v0; + int i1 = ReadInt() + v0; + int i2 = ReadInt() + v0; + if (i0 < 0 || i0 >= (int)_vertices.size() || i1 < 0 || i1 >= (int)_vertices.size() || i2 < 0 || i2 >= (int)_vertices.size()) { +#ifdef DEBUG_B3D + ASSIMP_LOG_ERROR("Bad triangle index: i0=", i0, ", i1=", i1, ", i2=", i2); +#endif + Fail("Bad triangle index"); + continue; + } + face->mNumIndices = 3; + face->mIndices = new unsigned[3]; + face->mIndices[0] = i0; + face->mIndices[1] = i1; + face->mIndices[2] = i2; + ++mesh->mNumFaces; + ++face; + } + + _meshes.emplace_back(std::move(mesh)); +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadMESH() { + /*int matid=*/ReadInt(); + + int v0 = static_cast<int>(_vertices.size()); + + while (ChunkSize()) { + string t = ReadChunk(); + if (t == "VRTS") { + ReadVRTS(); + } else if (t == "TRIS") { + ReadTRIS(v0); + } + ExitChunk(); + } +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadBONE(int id) { + while (ChunkSize()) { + int vertex = ReadInt(); + float weight = ReadFloat(); + if (vertex < 0 || vertex >= (int)_vertices.size()) { + Fail("Bad vertex index"); + } + + Vertex &v = _vertices[vertex]; + for (int i = 0; i < 4; ++i) { + if (!v.weights[i]) { + v.bones[i] = static_cast<unsigned char>(id); + v.weights[i] = weight; + break; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadKEYS(aiNodeAnim *nodeAnim) { + vector<aiVectorKey> trans, scale; + vector<aiQuatKey> rot; + int flags = ReadInt(); + while (ChunkSize()) { + int frame = ReadInt(); + if (flags & 1) { + trans.push_back(aiVectorKey(frame, ReadVec3())); + } + if (flags & 2) { + scale.push_back(aiVectorKey(frame, ReadVec3())); + } + if (flags & 4) { + rot.push_back(aiQuatKey(frame, ReadQuat())); + } + } + + if (flags & 1) { + nodeAnim->mNumPositionKeys = static_cast<unsigned int>(trans.size()); + nodeAnim->mPositionKeys = to_array(trans); + } + + if (flags & 2) { + nodeAnim->mNumScalingKeys = static_cast<unsigned int>(scale.size()); + nodeAnim->mScalingKeys = to_array(scale); + } + + if (flags & 4) { + nodeAnim->mNumRotationKeys = static_cast<unsigned int>(rot.size()); + nodeAnim->mRotationKeys = to_array(rot); + } +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadANIM() { + /*int flags=*/ReadInt(); + int frames = ReadInt(); + float fps = ReadFloat(); + + std::unique_ptr<aiAnimation> anim(new aiAnimation); + + anim->mDuration = frames; + anim->mTicksPerSecond = fps; + _animations.emplace_back(std::move(anim)); +} + +// ------------------------------------------------------------------------------------------------ +aiNode *B3DImporter::ReadNODE(aiNode *parent) { + + string name = ReadString(); + aiVector3D t = ReadVec3(); + aiVector3D s = ReadVec3(); + aiQuaternion r = ReadQuat(); + + aiMatrix4x4 trans, scale, rot; + + aiMatrix4x4::Translation(t, trans); + aiMatrix4x4::Scaling(s, scale); + rot = aiMatrix4x4(r.GetMatrix()); + + aiMatrix4x4 tform = trans * rot * scale; + + int nodeid = static_cast<int>(_nodes.size()); + + aiNode *node = new aiNode(name); + _nodes.push_back(node); + + node->mParent = parent; + node->mTransformation = tform; + + std::unique_ptr<aiNodeAnim> nodeAnim; + vector<unsigned> meshes; + vector<aiNode *> children; + + while (ChunkSize()) { + const string chunk = ReadChunk(); + if (chunk == "MESH") { + unsigned int n = static_cast<unsigned int>(_meshes.size()); + ReadMESH(); + for (unsigned int i = n; i < static_cast<unsigned int>(_meshes.size()); ++i) { + meshes.push_back(i); + } + } else if (chunk == "BONE") { + ReadBONE(nodeid); + } else if (chunk == "ANIM") { + ReadANIM(); + } else if (chunk == "KEYS") { + if (!nodeAnim) { + nodeAnim.reset(new aiNodeAnim); + nodeAnim->mNodeName = node->mName; + } + ReadKEYS(nodeAnim.get()); + } else if (chunk == "NODE") { + aiNode *child = ReadNODE(node); + children.push_back(child); + } + ExitChunk(); + } + + if (nodeAnim) { + _nodeAnims.emplace_back(std::move(nodeAnim)); + } + + node->mNumMeshes = static_cast<unsigned int>(meshes.size()); + node->mMeshes = to_array(meshes); + + node->mNumChildren = static_cast<unsigned int>(children.size()); + node->mChildren = to_array(children); + + return node; +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadBB3D(aiScene *scene) { + + _textures.clear(); + + _materials.clear(); + + _vertices.clear(); + + _meshes.clear(); + + DeleteAllBarePointers(_nodes); + _nodes.clear(); + + _nodeAnims.clear(); + + _animations.clear(); + + string t = ReadChunk(); + if (t == "BB3D") { + int version = ReadInt(); + + if (!DefaultLogger::isNullLogger()) { + char dmp[128]; + ai_snprintf(dmp, 128, "B3D file format version: %i", version); + ASSIMP_LOG_INFO(dmp); + } + + while (ChunkSize()) { + const string chunk = ReadChunk(); + if (chunk == "TEXS") { + ReadTEXS(); + } else if (chunk == "BRUS") { + ReadBRUS(); + } else if (chunk == "NODE") { + ReadNODE(0); + } + ExitChunk(); + } + } + ExitChunk(); + + if (!_nodes.size()) { + Fail("No nodes"); + } + + if (!_meshes.size()) { + Fail("No meshes"); + } + + // Fix nodes/meshes/bones + for (size_t i = 0; i < _nodes.size(); ++i) { + aiNode *node = _nodes[i]; + + for (size_t j = 0; j < node->mNumMeshes; ++j) { + aiMesh *mesh = _meshes[node->mMeshes[j]].get(); + + int n_tris = mesh->mNumFaces; + int n_verts = mesh->mNumVertices = n_tris * 3; + + aiVector3D *mv = mesh->mVertices = new aiVector3D[n_verts], *mn = 0, *mc = 0; + if (_vflags & 1) { + mn = mesh->mNormals = new aiVector3D[n_verts]; + } + if (_tcsets) { + mc = mesh->mTextureCoords[0] = new aiVector3D[n_verts]; + } + + aiFace *face = mesh->mFaces; + + vector<vector<aiVertexWeight>> vweights(_nodes.size()); + + for (int vertIdx = 0; vertIdx < n_verts; vertIdx += 3) { + for (int faceIndex = 0; faceIndex < 3; ++faceIndex) { + Vertex &v = _vertices[face->mIndices[faceIndex]]; + + *mv++ = v.vertex; + if (mn) *mn++ = v.normal; + if (mc) *mc++ = v.texcoords; + + face->mIndices[faceIndex] = vertIdx + faceIndex; + + for (int k = 0; k < 4; ++k) { + if (!v.weights[k]) + break; + + int bone = v.bones[k]; + float weight = v.weights[k]; + + vweights[bone].push_back(aiVertexWeight(vertIdx + faceIndex, weight)); + } + } + ++face; + } + + vector<aiBone *> bones; + for (size_t weightIndx = 0; weightIndx < vweights.size(); ++weightIndx) { + vector<aiVertexWeight> &weights = vweights[weightIndx]; + if (!weights.size()) { + continue; + } + + aiBone *bone = new aiBone; + bones.push_back(bone); + + aiNode *bnode = _nodes[weightIndx]; + + bone->mName = bnode->mName; + bone->mNumWeights = static_cast<unsigned int>(weights.size()); + bone->mWeights = to_array(weights); + + aiMatrix4x4 mat = bnode->mTransformation; + while (bnode->mParent) { + bnode = bnode->mParent; + mat = bnode->mTransformation * mat; + } + bone->mOffsetMatrix = mat.Inverse(); + } + mesh->mNumBones = static_cast<unsigned int>(bones.size()); + mesh->mBones = to_array(bones); + } + } + + //nodes + scene->mRootNode = _nodes[0]; + _nodes.clear(); // node ownership now belongs to scene + + //material + if (!_materials.size()) { + _materials.emplace_back(std::unique_ptr<aiMaterial>(new aiMaterial)); + } + scene->mNumMaterials = static_cast<unsigned int>(_materials.size()); + scene->mMaterials = unique_to_array(_materials); + + //meshes + scene->mNumMeshes = static_cast<unsigned int>(_meshes.size()); + scene->mMeshes = unique_to_array(_meshes); + + //animations + if (_animations.size() == 1 && _nodeAnims.size()) { + + aiAnimation *anim = _animations.back().get(); + anim->mNumChannels = static_cast<unsigned int>(_nodeAnims.size()); + anim->mChannels = unique_to_array(_nodeAnims); + + scene->mNumAnimations = static_cast<unsigned int>(_animations.size()); + scene->mAnimations = unique_to_array(_animations); + } + + // convert to RH + MakeLeftHandedProcess makeleft; + makeleft.Execute(scene); + + FlipWindingOrderProcess flip; + flip.Execute(scene); +} + +#endif // !! ASSIMP_BUILD_NO_B3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/B3D/B3DImporter.h b/libs/assimp/code/AssetLib/B3D/B3DImporter.h new file mode 100644 index 0000000..e47d907 --- /dev/null +++ b/libs/assimp/code/AssetLib/B3D/B3DImporter.h @@ -0,0 +1,132 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Definition of the .b3d importer class. + */ +#pragma once +#ifndef AI_B3DIMPORTER_H_INC +#define AI_B3DIMPORTER_H_INC + +#include <assimp/types.h> +#include <assimp/mesh.h> +#include <assimp/material.h> +#include <assimp/BaseImporter.h> + +#include <memory> +#include <vector> + +struct aiNodeAnim; +struct aiNode; +struct aiAnimation; + +namespace Assimp{ + +class B3DImporter : public BaseImporter{ +public: + B3DImporter() = default; + ~B3DImporter() override; + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override; + +protected: + const aiImporterDesc* GetInfo () const override; + void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) override; + +private: + + int ReadByte(); + int ReadInt(); + float ReadFloat(); + aiVector2D ReadVec2(); + aiVector3D ReadVec3(); + aiQuaternion ReadQuat(); + std::string ReadString(); + std::string ReadChunk(); + void ExitChunk(); + size_t ChunkSize(); + + template<class T> + T *to_array( const std::vector<T> &v ); + + struct Vertex{ + aiVector3D vertex; + aiVector3D normal; + aiVector3D texcoords; + unsigned char bones[4]; + float weights[4]; + }; + + AI_WONT_RETURN void Oops() AI_WONT_RETURN_SUFFIX; + AI_WONT_RETURN void Fail(const std::string &str) AI_WONT_RETURN_SUFFIX; + + void ReadTEXS(); + void ReadBRUS(); + + void ReadVRTS(); + void ReadTRIS( int v0 ); + void ReadMESH(); + void ReadBONE( int id ); + void ReadKEYS( aiNodeAnim *nodeAnim ); + void ReadANIM(); + + aiNode *ReadNODE( aiNode *parent ); + + void ReadBB3D( aiScene *scene ); + + size_t _pos; + std::vector<unsigned char> _buf; + std::vector<size_t> _stack; + + std::vector<std::string> _textures; + std::vector<std::unique_ptr<aiMaterial> > _materials; + + int _vflags,_tcsets,_tcsize; + std::vector<Vertex> _vertices; + + std::vector<aiNode*> _nodes; + std::vector<std::unique_ptr<aiMesh> > _meshes; + std::vector<std::unique_ptr<aiNodeAnim> > _nodeAnims; + std::vector<std::unique_ptr<aiAnimation> > _animations; +}; + +} + +#endif diff --git a/libs/assimp/code/AssetLib/BVH/BVHLoader.cpp b/libs/assimp/code/AssetLib/BVH/BVHLoader.cpp new file mode 100644 index 0000000..1748368 --- /dev/null +++ b/libs/assimp/code/AssetLib/BVH/BVHLoader.cpp @@ -0,0 +1,528 @@ +/** Implementation of the BVH loader */ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. +--------------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_BVH_IMPORTER + +#include "BVHLoader.h" +#include <assimp/SkeletonMeshBuilder.h> +#include <assimp/TinyFormatter.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/IOSystem.hpp> +#include <assimp/Importer.hpp> +#include <map> +#include <memory> + +using namespace Assimp; +using namespace Assimp::Formatter; + +static const aiImporterDesc desc = { + "BVH Importer (MoCap)", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "bvh" +}; + +// ------------------------------------------------------------------------------------------------ +// Aborts the file reading with an exception +template<typename... T> +AI_WONT_RETURN void BVHLoader::ThrowException(T&&... args) { + throw DeadlyImportError(mFileName, ":", mLine, " - ", args...); +} + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +BVHLoader::BVHLoader() : + mLine(), + mAnimTickDuration(), + mAnimNumFrames(), + noSkeletonMesh() {} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +BVHLoader::~BVHLoader() {} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool BVHLoader::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "HIERARCHY" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +void BVHLoader::SetupProperties(const Importer *pImp) { + noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, 0) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Loader meta information +const aiImporterDesc *BVHLoader::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void BVHLoader::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + mFileName = pFile; + + // read file into memory + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile)); + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open file ", pFile, "."); + } + + size_t fileSize = file->FileSize(); + if (fileSize == 0) { + throw DeadlyImportError("File is too small."); + } + + mBuffer.resize(fileSize); + file->Read(&mBuffer.front(), 1, fileSize); + + // start reading + mReader = mBuffer.begin(); + mLine = 1; + ReadStructure(pScene); + + if (!noSkeletonMesh) { + // build a dummy mesh for the skeleton so that we see something at least + SkeletonMeshBuilder meshBuilder(pScene); + } + + // construct an animation from all the motion data we read + CreateAnimation(pScene); +} + +// ------------------------------------------------------------------------------------------------ +// Reads the file +void BVHLoader::ReadStructure(aiScene *pScene) { + // first comes hierarchy + std::string header = GetNextToken(); + if (header != "HIERARCHY") + ThrowException("Expected header string \"HIERARCHY\"."); + ReadHierarchy(pScene); + + // then comes the motion data + std::string motion = GetNextToken(); + if (motion != "MOTION") + ThrowException("Expected beginning of motion data \"MOTION\"."); + ReadMotion(pScene); +} + +// ------------------------------------------------------------------------------------------------ +// Reads the hierarchy +void BVHLoader::ReadHierarchy(aiScene *pScene) { + std::string root = GetNextToken(); + if (root != "ROOT") + ThrowException("Expected root node \"ROOT\"."); + + // Go read the hierarchy from here + pScene->mRootNode = ReadNode(); +} + +// ------------------------------------------------------------------------------------------------ +// Reads a node and recursively its children and returns the created node; +aiNode *BVHLoader::ReadNode() { + // first token is name + std::string nodeName = GetNextToken(); + if (nodeName.empty() || nodeName == "{") + ThrowException("Expected node name, but found \"", nodeName, "\"."); + + // then an opening brace should follow + std::string openBrace = GetNextToken(); + if (openBrace != "{") + ThrowException("Expected opening brace \"{\", but found \"", openBrace, "\"."); + + // Create a node + aiNode *node = new aiNode(nodeName); + std::vector<aiNode *> childNodes; + + // and create an bone entry for it + mNodes.push_back(Node(node)); + Node &internNode = mNodes.back(); + + // now read the node's contents + std::string siteToken; + while (1) { + std::string token = GetNextToken(); + + // node offset to parent node + if (token == "OFFSET") + ReadNodeOffset(node); + else if (token == "CHANNELS") + ReadNodeChannels(internNode); + else if (token == "JOINT") { + // child node follows + aiNode *child = ReadNode(); + child->mParent = node; + childNodes.push_back(child); + } else if (token == "End") { + // The real symbol is "End Site". Second part comes in a separate token + siteToken.clear(); + siteToken = GetNextToken(); + if (siteToken != "Site") + ThrowException("Expected \"End Site\" keyword, but found \"", token, " ", siteToken, "\"."); + + aiNode *child = ReadEndSite(nodeName); + child->mParent = node; + childNodes.push_back(child); + } else if (token == "}") { + // we're done with that part of the hierarchy + break; + } else { + // everything else is a parse error + ThrowException("Unknown keyword \"", token, "\"."); + } + } + + // add the child nodes if there are any + if (childNodes.size() > 0) { + node->mNumChildren = static_cast<unsigned int>(childNodes.size()); + node->mChildren = new aiNode *[node->mNumChildren]; + std::copy(childNodes.begin(), childNodes.end(), node->mChildren); + } + + // and return the sub-hierarchy we built here + return node; +} + +// ------------------------------------------------------------------------------------------------ +// Reads an end node and returns the created node. +aiNode *BVHLoader::ReadEndSite(const std::string &pParentName) { + // check opening brace + std::string openBrace = GetNextToken(); + if (openBrace != "{") + ThrowException("Expected opening brace \"{\", but found \"", openBrace, "\"."); + + // Create a node + aiNode *node = new aiNode("EndSite_" + pParentName); + + // now read the node's contents. Only possible entry is "OFFSET" + std::string token; + while (1) { + token.clear(); + token = GetNextToken(); + + // end node's offset + if (token == "OFFSET") { + ReadNodeOffset(node); + } else if (token == "}") { + // we're done with the end node + break; + } else { + // everything else is a parse error + ThrowException("Unknown keyword \"", token, "\"."); + } + } + + // and return the sub-hierarchy we built here + return node; +} +// ------------------------------------------------------------------------------------------------ +// Reads a node offset for the given node +void BVHLoader::ReadNodeOffset(aiNode *pNode) { + // Offset consists of three floats to read + aiVector3D offset; + offset.x = GetNextTokenAsFloat(); + offset.y = GetNextTokenAsFloat(); + offset.z = GetNextTokenAsFloat(); + + // build a transformation matrix from it + pNode->mTransformation = aiMatrix4x4(1.0f, 0.0f, 0.0f, offset.x, + 0.0f, 1.0f, 0.0f, offset.y, + 0.0f, 0.0f, 1.0f, offset.z, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +// ------------------------------------------------------------------------------------------------ +// Reads the animation channels for the given node +void BVHLoader::ReadNodeChannels(BVHLoader::Node &pNode) { + // number of channels. Use the float reader because we're lazy + float numChannelsFloat = GetNextTokenAsFloat(); + unsigned int numChannels = (unsigned int)numChannelsFloat; + + for (unsigned int a = 0; a < numChannels; a++) { + std::string channelToken = GetNextToken(); + + if (channelToken == "Xposition") + pNode.mChannels.push_back(Channel_PositionX); + else if (channelToken == "Yposition") + pNode.mChannels.push_back(Channel_PositionY); + else if (channelToken == "Zposition") + pNode.mChannels.push_back(Channel_PositionZ); + else if (channelToken == "Xrotation") + pNode.mChannels.push_back(Channel_RotationX); + else if (channelToken == "Yrotation") + pNode.mChannels.push_back(Channel_RotationY); + else if (channelToken == "Zrotation") + pNode.mChannels.push_back(Channel_RotationZ); + else + ThrowException("Invalid channel specifier \"", channelToken, "\"."); + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the motion data +void BVHLoader::ReadMotion(aiScene * /*pScene*/) { + // Read number of frames + std::string tokenFrames = GetNextToken(); + if (tokenFrames != "Frames:") + ThrowException("Expected frame count \"Frames:\", but found \"", tokenFrames, "\"."); + + float numFramesFloat = GetNextTokenAsFloat(); + mAnimNumFrames = (unsigned int)numFramesFloat; + + // Read frame duration + std::string tokenDuration1 = GetNextToken(); + std::string tokenDuration2 = GetNextToken(); + if (tokenDuration1 != "Frame" || tokenDuration2 != "Time:") + ThrowException("Expected frame duration \"Frame Time:\", but found \"", tokenDuration1, " ", tokenDuration2, "\"."); + + mAnimTickDuration = GetNextTokenAsFloat(); + + // resize value vectors for each node + for (std::vector<Node>::iterator it = mNodes.begin(); it != mNodes.end(); ++it) + it->mChannelValues.reserve(it->mChannels.size() * mAnimNumFrames); + + // now read all the data and store it in the corresponding node's value vector + for (unsigned int frame = 0; frame < mAnimNumFrames; ++frame) { + // on each line read the values for all nodes + for (std::vector<Node>::iterator it = mNodes.begin(); it != mNodes.end(); ++it) { + // get as many values as the node has channels + for (unsigned int c = 0; c < it->mChannels.size(); ++c) + it->mChannelValues.push_back(GetNextTokenAsFloat()); + } + + // after one frame worth of values for all nodes there should be a newline, but we better don't rely on it + } +} + +// ------------------------------------------------------------------------------------------------ +// Retrieves the next token +std::string BVHLoader::GetNextToken() { + // skip any preceding whitespace + while (mReader != mBuffer.end()) { + if (!isspace((unsigned char)*mReader)) + break; + + // count lines + if (*mReader == '\n') + mLine++; + + ++mReader; + } + + // collect all chars till the next whitespace. BVH is easy in respect to that. + std::string token; + while (mReader != mBuffer.end()) { + if (isspace((unsigned char)*mReader)) + break; + + token.push_back(*mReader); + ++mReader; + + // little extra logic to make sure braces are counted correctly + if (token == "{" || token == "}") + break; + } + + // empty token means end of file, which is just fine + return token; +} + +// ------------------------------------------------------------------------------------------------ +// Reads the next token as a float +float BVHLoader::GetNextTokenAsFloat() { + std::string token = GetNextToken(); + if (token.empty()) + ThrowException("Unexpected end of file while trying to read a float"); + + // check if the float is valid by testing if the atof() function consumed every char of the token + const char *ctoken = token.c_str(); + float result = 0.0f; + ctoken = fast_atoreal_move<float>(ctoken, result); + + if (ctoken != token.c_str() + token.length()) + ThrowException("Expected a floating point number, but found \"", token, "\"."); + + return result; +} + +// ------------------------------------------------------------------------------------------------ +// Constructs an animation for the motion data and stores it in the given scene +void BVHLoader::CreateAnimation(aiScene *pScene) { + // create the animation + pScene->mNumAnimations = 1; + pScene->mAnimations = new aiAnimation *[1]; + aiAnimation *anim = new aiAnimation; + pScene->mAnimations[0] = anim; + + // put down the basic parameters + anim->mName.Set("Motion"); + anim->mTicksPerSecond = 1.0 / double(mAnimTickDuration); + anim->mDuration = double(mAnimNumFrames - 1); + + // now generate the tracks for all nodes + anim->mNumChannels = static_cast<unsigned int>(mNodes.size()); + anim->mChannels = new aiNodeAnim *[anim->mNumChannels]; + + // FIX: set the array elements to nullptr to ensure proper deletion if an exception is thrown + for (unsigned int i = 0; i < anim->mNumChannels; ++i) + anim->mChannels[i] = nullptr; + + for (unsigned int a = 0; a < anim->mNumChannels; a++) { + const Node &node = mNodes[a]; + const std::string nodeName = std::string(node.mNode->mName.data); + aiNodeAnim *nodeAnim = new aiNodeAnim; + anim->mChannels[a] = nodeAnim; + nodeAnim->mNodeName.Set(nodeName); + std::map<BVHLoader::ChannelType, int> channelMap; + + //Build map of channels + for (unsigned int channel = 0; channel < node.mChannels.size(); ++channel) { + channelMap[node.mChannels[channel]] = channel; + } + + // translational part, if given + if (node.mChannels.size() == 6) { + nodeAnim->mNumPositionKeys = mAnimNumFrames; + nodeAnim->mPositionKeys = new aiVectorKey[mAnimNumFrames]; + aiVectorKey *poskey = nodeAnim->mPositionKeys; + for (unsigned int fr = 0; fr < mAnimNumFrames; ++fr) { + poskey->mTime = double(fr); + + // Now compute all translations + for (BVHLoader::ChannelType channel = Channel_PositionX; channel <= Channel_PositionZ; channel = (BVHLoader::ChannelType)(channel + 1)) { + //Find channel in node + std::map<BVHLoader::ChannelType, int>::iterator mapIter = channelMap.find(channel); + + if (mapIter == channelMap.end()) + throw DeadlyImportError("Missing position channel in node ", nodeName); + else { + int channelIdx = mapIter->second; + switch (channel) { + case Channel_PositionX: + poskey->mValue.x = node.mChannelValues[fr * node.mChannels.size() + channelIdx]; + break; + case Channel_PositionY: + poskey->mValue.y = node.mChannelValues[fr * node.mChannels.size() + channelIdx]; + break; + case Channel_PositionZ: + poskey->mValue.z = node.mChannelValues[fr * node.mChannels.size() + channelIdx]; + break; + + default: + break; + } + } + } + ++poskey; + } + } else { + // if no translation part is given, put a default sequence + aiVector3D nodePos(node.mNode->mTransformation.a4, node.mNode->mTransformation.b4, node.mNode->mTransformation.c4); + nodeAnim->mNumPositionKeys = 1; + nodeAnim->mPositionKeys = new aiVectorKey[1]; + nodeAnim->mPositionKeys[0].mTime = 0.0; + nodeAnim->mPositionKeys[0].mValue = nodePos; + } + + // rotation part. Always present. First find value offsets + { + + // Then create the number of rotation keys + nodeAnim->mNumRotationKeys = mAnimNumFrames; + nodeAnim->mRotationKeys = new aiQuatKey[mAnimNumFrames]; + aiQuatKey *rotkey = nodeAnim->mRotationKeys; + for (unsigned int fr = 0; fr < mAnimNumFrames; ++fr) { + aiMatrix4x4 temp; + aiMatrix3x3 rotMatrix; + for (unsigned int channelIdx = 0; channelIdx < node.mChannels.size(); ++ channelIdx) { + switch (node.mChannels[channelIdx]) { + case Channel_RotationX: + { + const float angle = node.mChannelValues[fr * node.mChannels.size() + channelIdx] * float(AI_MATH_PI) / 180.0f; + aiMatrix4x4::RotationX( angle, temp); rotMatrix *= aiMatrix3x3( temp); + } + break; + case Channel_RotationY: + { + const float angle = node.mChannelValues[fr * node.mChannels.size() + channelIdx] * float(AI_MATH_PI) / 180.0f; + aiMatrix4x4::RotationY( angle, temp); rotMatrix *= aiMatrix3x3( temp); + } + break; + case Channel_RotationZ: + { + const float angle = node.mChannelValues[fr * node.mChannels.size() + channelIdx] * float(AI_MATH_PI) / 180.0f; + aiMatrix4x4::RotationZ( angle, temp); rotMatrix *= aiMatrix3x3( temp); + } + break; + default: + break; + } + } + rotkey->mTime = double(fr); + rotkey->mValue = aiQuaternion(rotMatrix); + ++rotkey; + } + } + + // scaling part. Always just a default track + { + nodeAnim->mNumScalingKeys = 1; + nodeAnim->mScalingKeys = new aiVectorKey[1]; + nodeAnim->mScalingKeys[0].mTime = 0.0; + nodeAnim->mScalingKeys[0].mValue.Set(1.0f, 1.0f, 1.0f); + } + } +} + +#endif // !! ASSIMP_BUILD_NO_BVH_IMPORTER diff --git a/libs/assimp/code/AssetLib/BVH/BVHLoader.h b/libs/assimp/code/AssetLib/BVH/BVHLoader.h new file mode 100644 index 0000000..56c32bd --- /dev/null +++ b/libs/assimp/code/AssetLib/BVH/BVHLoader.h @@ -0,0 +1,170 @@ +/** Defines the BHV motion capturing loader class */ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BVHLoader.h + * @brief Biovision BVH import + */ + +#ifndef AI_BVHLOADER_H_INC +#define AI_BVHLOADER_H_INC + +#include <assimp/BaseImporter.h> + +struct aiNode; + +namespace Assimp { + +// -------------------------------------------------------------------------------- +/** Loader class to read Motion Capturing data from a .bvh file. + * + * This format only contains a hierarchy of joints and a series of keyframes for + * the hierarchy. It contains no actual mesh data, but we generate a dummy mesh + * inside the loader just to be able to see something. +*/ +class BVHLoader : public BaseImporter { + + /** Possible animation channels for which the motion data holds the values */ + enum ChannelType { + Channel_PositionX, + Channel_PositionY, + Channel_PositionZ, + Channel_RotationX, + Channel_RotationY, + Channel_RotationZ + }; + + /** Collected list of node. Will be bones of the dummy mesh some day, addressed by their array index */ + struct Node { + const aiNode *mNode; + std::vector<ChannelType> mChannels; + std::vector<float> mChannelValues; // motion data values for that node. Of size NumChannels * NumFrames + + Node() : + mNode(nullptr) {} + + explicit Node(const aiNode *pNode) : + mNode(pNode) {} + }; + +public: + BVHLoader(); + ~BVHLoader(); + +public: + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool cs) const; + + void SetupProperties(const Importer *pImp); + const aiImporterDesc *GetInfo() const; + +protected: + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler); + +protected: + /** Reads the file */ + void ReadStructure(aiScene *pScene); + + /** Reads the hierarchy */ + void ReadHierarchy(aiScene *pScene); + + /** Reads a node and recursively its children and returns the created node. */ + aiNode *ReadNode(); + + /** Reads an end node and returns the created node. */ + aiNode *ReadEndSite(const std::string &pParentName); + + /** Reads a node offset for the given node */ + void ReadNodeOffset(aiNode *pNode); + + /** Reads the animation channels into the given node */ + void ReadNodeChannels(BVHLoader::Node &pNode); + + /** Reads the motion data */ + void ReadMotion(aiScene *pScene); + + /** Retrieves the next token */ + std::string GetNextToken(); + + /** Reads the next token as a float */ + float GetNextTokenAsFloat(); + + /** Aborts the file reading with an exception */ + template<typename... T> + AI_WONT_RETURN void ThrowException(T&&... args) AI_WONT_RETURN_SUFFIX; + + /** Constructs an animation for the motion data and stores it in the given scene */ + void CreateAnimation(aiScene *pScene); + +protected: + /** Filename, for a verbose error message */ + std::string mFileName; + + /** Buffer to hold the loaded file */ + std::vector<char> mBuffer; + + /** Next char to read from the buffer */ + std::vector<char>::const_iterator mReader; + + /** Current line, for error messages */ + unsigned int mLine; + + /** Collected list of nodes. Will be bones of the dummy mesh some day, addressed by their array index. + * Also contain the motion data for the node's channels + */ + std::vector<Node> mNodes; + + /** basic Animation parameters */ + float mAnimTickDuration; + unsigned int mAnimNumFrames; + + bool noSkeletonMesh; +}; + +} // end of namespace Assimp + +#endif // AI_BVHLOADER_H_INC diff --git a/libs/assimp/code/AssetLib/Blender/BlenderBMesh.cpp b/libs/assimp/code/AssetLib/Blender/BlenderBMesh.cpp new file mode 100644 index 0000000..be536eb --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderBMesh.cpp @@ -0,0 +1,184 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BlenderBMesh.cpp + * @brief Conversion of Blender's new BMesh stuff + */ + +#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER + +#include "BlenderBMesh.h" +#include "BlenderDNA.h" +#include "BlenderScene.h" +#include "BlenderTessellator.h" + +namespace Assimp { +template <> +const char *LogFunctions<BlenderBMeshConverter>::Prefix() { + static auto prefix = "BLEND_BMESH: "; + return prefix; +} +} // namespace Assimp + +using namespace Assimp; +using namespace Assimp::Blender; +using namespace Assimp::Formatter; + +// ------------------------------------------------------------------------------------------------ +BlenderBMeshConverter::BlenderBMeshConverter(const Mesh *mesh) : + BMesh(mesh), + triMesh(nullptr) { + ai_assert(nullptr != mesh); +} + +// ------------------------------------------------------------------------------------------------ +BlenderBMeshConverter::~BlenderBMeshConverter() { + DestroyTriMesh(); +} + +// ------------------------------------------------------------------------------------------------ +bool BlenderBMeshConverter::ContainsBMesh() const { + // TODO - Should probably do some additional verification here + return BMesh->totpoly && BMesh->totloop && BMesh->totvert; +} + +// ------------------------------------------------------------------------------------------------ +const Mesh *BlenderBMeshConverter::TriangulateBMesh() { + AssertValidMesh(); + AssertValidSizes(); + PrepareTriMesh(); + + for (int i = 0; i < BMesh->totpoly; ++i) { + const MPoly &poly = BMesh->mpoly[i]; + ConvertPolyToFaces(poly); + } + + return triMesh; +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::AssertValidMesh() { + if (!ContainsBMesh()) { + ThrowException("BlenderBMeshConverter requires a BMesh with \"polygons\" - please call BlenderBMeshConverter::ContainsBMesh to check this first"); + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::AssertValidSizes() { + if (BMesh->totpoly != static_cast<int>(BMesh->mpoly.size())) { + ThrowException("BMesh poly array has incorrect size"); + } + if (BMesh->totloop != static_cast<int>(BMesh->mloop.size())) { + ThrowException("BMesh loop array has incorrect size"); + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::PrepareTriMesh() { + if (triMesh) { + DestroyTriMesh(); + } + + triMesh = new Mesh(*BMesh); + triMesh->totface = 0; + triMesh->mface.clear(); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::DestroyTriMesh() { + delete triMesh; + triMesh = nullptr; +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::ConvertPolyToFaces(const MPoly &poly) { + const MLoop *polyLoop = &BMesh->mloop[poly.loopstart]; + + if (poly.totloop == 3 || poly.totloop == 4) { + AddFace(polyLoop[0].v, polyLoop[1].v, polyLoop[2].v, poly.totloop == 4 ? polyLoop[3].v : 0); + + // UVs are optional, so only convert when present. + if (BMesh->mloopuv.size()) { + if ((poly.loopstart + poly.totloop) > static_cast<int>(BMesh->mloopuv.size())) { + ThrowException("BMesh uv loop array has incorrect size"); + } + const MLoopUV *loopUV = &BMesh->mloopuv[poly.loopstart]; + AddTFace(loopUV[0].uv, loopUV[1].uv, loopUV[2].uv, poly.totloop == 4 ? loopUV[3].uv : 0); + } + } else if (poly.totloop > 4) { +#if ASSIMP_BLEND_WITH_GLU_TESSELLATE + BlenderTessellatorGL tessGL(*this); + tessGL.Tessellate(polyLoop, poly.totloop, triMesh->mvert); +#elif ASSIMP_BLEND_WITH_POLY_2_TRI + BlenderTessellatorP2T tessP2T(*this); + tessP2T.Tessellate(polyLoop, poly.totloop, triMesh->mvert); +#endif + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::AddFace(int v1, int v2, int v3, int v4) { + MFace face; + face.v1 = v1; + face.v2 = v2; + face.v3 = v3; + face.v4 = v4; + face.flag = 0; + // TODO - Work out how materials work + face.mat_nr = 0; + triMesh->mface.push_back(face); + triMesh->totface = static_cast<int>(triMesh->mface.size()); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::AddTFace(const float *uv1, const float *uv2, const float *uv3, const float *uv4) { + MTFace mtface; + memcpy(&mtface.uv[0], uv1, sizeof(float) * 2); + memcpy(&mtface.uv[1], uv2, sizeof(float) * 2); + memcpy(&mtface.uv[2], uv3, sizeof(float) * 2); + + if (uv4) { + memcpy(&mtface.uv[3], uv4, sizeof(float) * 2); + } + + triMesh->mtface.push_back(mtface); +} + +#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER diff --git a/libs/assimp/code/AssetLib/Blender/BlenderBMesh.h b/libs/assimp/code/AssetLib/Blender/BlenderBMesh.h new file mode 100644 index 0000000..45ca2c8 --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderBMesh.h @@ -0,0 +1,94 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BlenderBMesh.h + * @brief Conversion of Blender's new BMesh stuff + */ +#ifndef INCLUDED_AI_BLEND_BMESH_H +#define INCLUDED_AI_BLEND_BMESH_H + +#include <assimp/LogAux.h> + +namespace Assimp +{ + // TinyFormatter.h + namespace Formatter + { + template < typename T,typename TR, typename A > class basic_formatter; + typedef class basic_formatter< char, std::char_traits< char >, std::allocator< char > > format; + } + + // BlenderScene.h + namespace Blender + { + struct Mesh; + struct MPoly; + struct MLoop; + } + + class BlenderBMeshConverter: public LogFunctions< BlenderBMeshConverter > + { + public: + BlenderBMeshConverter( const Blender::Mesh* mesh ); + ~BlenderBMeshConverter( ); + + bool ContainsBMesh( ) const; + + const Blender::Mesh* TriangulateBMesh( ); + + private: + void AssertValidMesh( ); + void AssertValidSizes( ); + void PrepareTriMesh( ); + void DestroyTriMesh( ); + void ConvertPolyToFaces( const Blender::MPoly& poly ); + void AddFace( int v1, int v2, int v3, int v4 = 0 ); + void AddTFace( const float* uv1, const float* uv2, const float *uv3, const float* uv4 = 0 ); + + const Blender::Mesh* BMesh; + Blender::Mesh* triMesh; + + friend class BlenderTessellatorGL; + friend class BlenderTessellatorP2T; + }; + +} // end of namespace Assimp + +#endif // INCLUDED_AI_BLEND_BMESH_H diff --git a/libs/assimp/code/AssetLib/Blender/BlenderCustomData.cpp b/libs/assimp/code/AssetLib/Blender/BlenderCustomData.cpp new file mode 100644 index 0000000..c74a6bb --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderCustomData.cpp @@ -0,0 +1,181 @@ +#include "BlenderCustomData.h" +#include "BlenderDNA.h" +#include <array> +#include <functional> + +namespace Assimp { +namespace Blender { +/** + * @brief read/convert of Structure array to memory + */ +template <typename T> +bool read(const Structure &s, T *p, const size_t cnt, const FileDatabase &db) { + for (size_t i = 0; i < cnt; ++i) { + T read; + s.Convert(read, db); + *p = read; + p++; + } + return true; +} + +/** + * @brief pointer to function read memory for n CustomData types + */ +typedef bool (*PRead)(ElemBase *pOut, const size_t cnt, const FileDatabase &db); +typedef ElemBase *(*PCreate)(const size_t cnt); +typedef void (*PDestroy)(ElemBase *); + +#define IMPL_STRUCT_READ(ty) \ + bool read##ty(ElemBase *v, const size_t cnt, const FileDatabase &db) { \ + ty *ptr = dynamic_cast<ty *>(v); \ + if (nullptr == ptr) { \ + return false; \ + } \ + return read<ty>(db.dna[#ty], ptr, cnt, db); \ + } + +#define IMPL_STRUCT_CREATE(ty) \ + ElemBase *create##ty(const size_t cnt) { \ + return new ty[cnt]; \ + } + +#define IMPL_STRUCT_DESTROY(ty) \ + void destroy##ty(ElemBase *pE) { \ + ty *p = dynamic_cast<ty *>(pE); \ + delete[] p; \ + } + +/** + * @brief helper macro to define Structure functions + */ +#define IMPL_STRUCT(ty) \ + IMPL_STRUCT_READ(ty) \ + IMPL_STRUCT_CREATE(ty) \ + IMPL_STRUCT_DESTROY(ty) + +// supported structures for CustomData +IMPL_STRUCT(MVert) +IMPL_STRUCT(MEdge) +IMPL_STRUCT(MFace) +IMPL_STRUCT(MTFace) +IMPL_STRUCT(MTexPoly) +IMPL_STRUCT(MLoopUV) +IMPL_STRUCT(MLoopCol) +IMPL_STRUCT(MPoly) +IMPL_STRUCT(MLoop) + +/** + * @brief describes the size of data and the read function to be used for single CustomerData.type + */ +struct CustomDataTypeDescription { + PRead Read; ///< function to read one CustomData type element + PCreate Create; ///< function to allocate n type elements + PDestroy Destroy; + + CustomDataTypeDescription(PRead read, PCreate create, PDestroy destroy) : + Read(read), Create(create), Destroy(destroy) {} +}; + +/** + * @brief helper macro to define Structure type specific CustomDataTypeDescription + * @note IMPL_STRUCT_READ for same ty must be used earlier to implement the typespecific read function + */ +#define DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(ty) \ + CustomDataTypeDescription { &read##ty, &create##ty, &destroy##ty } + +/** + * @brief helper macro to define CustomDataTypeDescription for UNSUPPORTED type + */ +#define DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION \ + CustomDataTypeDescription { nullptr, nullptr, nullptr } + +/** + * @brief descriptors for data pointed to from CustomDataLayer.data + * @note some of the CustomData uses already well defined Structures + * other (like CD_ORCO, ...) uses arrays of rawtypes or even arrays of Structures + * use a special readfunction for that cases + */ +std::array<CustomDataTypeDescription, CD_NUMTYPES> customDataTypeDescriptions = { { DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MVert), + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MEdge), + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MFace), + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MTFace), + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MTexPoly), + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoopUV), + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoopCol), + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MPoly), + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoop), + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION } }; + +bool isValidCustomDataType(const int cdtype) { + return cdtype >= 0 && cdtype < CD_NUMTYPES; +} + +bool readCustomData(std::shared_ptr<ElemBase> &out, const int cdtype, const size_t cnt, const FileDatabase &db) { + if (!isValidCustomDataType(cdtype)) { + throw Error("CustomData.type ", cdtype, " out of index"); + } + + const CustomDataTypeDescription cdtd = customDataTypeDescriptions[cdtype]; + if (cdtd.Read && cdtd.Create && cdtd.Destroy && cnt > 0) { + // allocate cnt elements and parse them from file + out.reset(cdtd.Create(cnt), cdtd.Destroy); + return cdtd.Read(out.get(), cnt, db); + } + return false; +} + +std::shared_ptr<CustomDataLayer> getCustomDataLayer(const CustomData &customdata, const CustomDataType cdtype, const std::string &name) { + for (auto it = customdata.layers.begin(); it != customdata.layers.end(); ++it) { + if (it->get()->type == cdtype && name == it->get()->name) { + return *it; + } + } + return nullptr; +} + +const ElemBase *getCustomDataLayerData(const CustomData &customdata, const CustomDataType cdtype, const std::string &name) { + const std::shared_ptr<CustomDataLayer> pLayer = getCustomDataLayer(customdata, cdtype, name); + if (pLayer && pLayer->data) { + return pLayer->data.get(); + } + return nullptr; +} +} // namespace Blender +} // namespace Assimp diff --git a/libs/assimp/code/AssetLib/Blender/BlenderCustomData.h b/libs/assimp/code/AssetLib/Blender/BlenderCustomData.h new file mode 100644 index 0000000..f61d79a --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderCustomData.h @@ -0,0 +1,89 @@ +#pragma once + +#include "BlenderDNA.h" +#include "BlenderScene.h" +#include <memory> + +namespace Assimp { + namespace Blender { + /* CustomData.type from Blender (2.79b) */ + enum CustomDataType { + CD_AUTO_FROM_NAME = -1, + CD_MVERT = 0, +#ifdef DNA_DEPRECATED + CD_MSTICKY = 1, /* DEPRECATED */ +#endif + CD_MDEFORMVERT = 2, + CD_MEDGE = 3, + CD_MFACE = 4, + CD_MTFACE = 5, + CD_MCOL = 6, + CD_ORIGINDEX = 7, + CD_NORMAL = 8, + /* CD_POLYINDEX = 9, */ + CD_PROP_FLT = 10, + CD_PROP_INT = 11, + CD_PROP_STR = 12, + CD_ORIGSPACE = 13, /* for modifier stack face location mapping */ + CD_ORCO = 14, + CD_MTEXPOLY = 15, + CD_MLOOPUV = 16, + CD_MLOOPCOL = 17, + CD_TANGENT = 18, + CD_MDISPS = 19, + CD_PREVIEW_MCOL = 20, /* for displaying weightpaint colors */ + /* CD_ID_MCOL = 21, */ + CD_TEXTURE_MLOOPCOL = 22, + CD_CLOTH_ORCO = 23, + CD_RECAST = 24, + + /* BMESH ONLY START */ + CD_MPOLY = 25, + CD_MLOOP = 26, + CD_SHAPE_KEYINDEX = 27, + CD_SHAPEKEY = 28, + CD_BWEIGHT = 29, + CD_CREASE = 30, + CD_ORIGSPACE_MLOOP = 31, + CD_PREVIEW_MLOOPCOL = 32, + CD_BM_ELEM_PYPTR = 33, + /* BMESH ONLY END */ + + CD_PAINT_MASK = 34, + CD_GRID_PAINT_MASK = 35, + CD_MVERT_SKIN = 36, + CD_FREESTYLE_EDGE = 37, + CD_FREESTYLE_FACE = 38, + CD_MLOOPTANGENT = 39, + CD_TESSLOOPNORMAL = 40, + CD_CUSTOMLOOPNORMAL = 41, + + CD_NUMTYPES = 42 + }; + + /** + * @brief check if given cdtype is valid (ie >= 0 and < CD_NUMTYPES) + * @param[in] cdtype to check + * @return true when valid + */ + bool isValidCustomDataType(const int cdtype); + + /** + * @brief returns CustomDataLayer ptr for given cdtype and name + * @param[in] customdata CustomData to search for wanted layer + * @param[in] cdtype to search for + * @param[in] name to search for + * @return CustomDataLayer * or nullptr if not found + */ + std::shared_ptr<CustomDataLayer> getCustomDataLayer(const CustomData &customdata, CustomDataType cdtype, const std::string &name); + + /** + * @brief returns CustomDataLayer data ptr for given cdtype and name + * @param[in] customdata CustomData to search for wanted layer + * @param[in] cdtype to search for + * @param[in] name to search for + * @return * to struct data or nullptr if not found + */ + const ElemBase * getCustomDataLayerData(const CustomData &customdata, CustomDataType cdtype, const std::string &name); + } +} diff --git a/libs/assimp/code/AssetLib/Blender/BlenderDNA.cpp b/libs/assimp/code/AssetLib/Blender/BlenderDNA.cpp new file mode 100644 index 0000000..2910904 --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderDNA.cpp @@ -0,0 +1,351 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BlenderDNA.cpp + * @brief Implementation of the Blender `DNA`, that is its own + * serialized set of data structures. + */ + +#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER +#include "BlenderDNA.h" +#include <assimp/StreamReader.h> +#include <assimp/TinyFormatter.h> +#include <assimp/fast_atof.h> + +using namespace Assimp; +using namespace Assimp::Blender; +using namespace Assimp::Formatter; + +static bool match4(StreamReaderAny &stream, const char *string) { + ai_assert(nullptr != string); + char tmp[4]; + tmp[0] = (stream).GetI1(); + tmp[1] = (stream).GetI1(); + tmp[2] = (stream).GetI1(); + tmp[3] = (stream).GetI1(); + return (tmp[0] == string[0] && tmp[1] == string[1] && tmp[2] == string[2] && tmp[3] == string[3]); +} + +struct Type { + size_t size; + std::string name; +}; + +// ------------------------------------------------------------------------------------------------ +void DNAParser::Parse() { + StreamReaderAny &stream = *db.reader.get(); + DNA &dna = db.dna; + + if (!match4(stream, "SDNA")) { + throw DeadlyImportError("BlenderDNA: Expected SDNA chunk"); + } + + // name dictionary + if (!match4(stream, "NAME")) { + throw DeadlyImportError("BlenderDNA: Expected NAME field"); + } + + std::vector<std::string> names(stream.GetI4()); + for (std::string &s : names) { + while (char c = stream.GetI1()) { + s += c; + } + } + + // type dictionary + for (; stream.GetCurrentPos() & 0x3; stream.GetI1()) + ; + if (!match4(stream, "TYPE")) { + throw DeadlyImportError("BlenderDNA: Expected TYPE field"); + } + + std::vector<Type> types(stream.GetI4()); + for (Type &s : types) { + while (char c = stream.GetI1()) { + s.name += c; + } + } + + // type length dictionary + for (; stream.GetCurrentPos() & 0x3; stream.GetI1()) + ; + if (!match4(stream, "TLEN")) { + throw DeadlyImportError("BlenderDNA: Expected TLEN field"); + } + + for (Type &s : types) { + s.size = stream.GetI2(); + } + + // structures dictionary + for (; stream.GetCurrentPos() & 0x3; stream.GetI1()) + ; + if (!match4(stream, "STRC")) { + throw DeadlyImportError("BlenderDNA: Expected STRC field"); + } + + size_t end = stream.GetI4(), fields = 0; + + dna.structures.reserve(end); + for (size_t i = 0; i != end; ++i) { + + uint16_t n = stream.GetI2(); + if (n >= types.size()) { + throw DeadlyImportError("BlenderDNA: Invalid type index in structure name", n, " (there are only ", types.size(), " entries)"); + } + + // maintain separate indexes + dna.indices[types[n].name] = dna.structures.size(); + + dna.structures.push_back(Structure()); + Structure &s = dna.structures.back(); + s.name = types[n].name; + + n = stream.GetI2(); + s.fields.reserve(n); + + size_t offset = 0; + for (size_t m = 0; m < n; ++m, ++fields) { + + uint16_t j = stream.GetI2(); + if (j >= types.size()) { + throw DeadlyImportError("BlenderDNA: Invalid type index in structure field ", j, " (there are only ", types.size(), " entries)"); + } + s.fields.push_back(Field()); + Field &f = s.fields.back(); + f.offset = offset; + + f.type = types[j].name; + f.size = types[j].size; + + j = stream.GetI2(); + if (j >= names.size()) { + throw DeadlyImportError("BlenderDNA: Invalid name index in structure field ", j, " (there are only ", names.size(), " entries)"); + } + + f.name = names[j]; + f.flags = 0u; + + // pointers always specify the size of the pointee instead of their own. + // The pointer asterisk remains a property of the lookup name. + if (f.name[0] == '*') { + f.size = db.i64bit ? 8 : 4; + f.flags |= FieldFlag_Pointer; + } + + // arrays, however, specify the size of a single element so we + // need to parse the (possibly multi-dimensional) array declaration + // in order to obtain the actual size of the array in the file. + // Also we need to alter the lookup name to include no array + // brackets anymore or size fixup won't work (if our size does + // not match the size read from the DNA). + if (*f.name.rbegin() == ']') { + const std::string::size_type rb = f.name.find('['); + if (rb == std::string::npos) { + throw DeadlyImportError("BlenderDNA: Encountered invalid array declaration ", f.name); + } + + f.flags |= FieldFlag_Array; + DNA::ExtractArraySize(f.name, f.array_sizes); + f.name = f.name.substr(0, rb); + + f.size *= f.array_sizes[0] * f.array_sizes[1]; + } + + // maintain separate indexes + s.indices[f.name] = s.fields.size() - 1; + offset += f.size; + } + s.size = offset; + } + + ASSIMP_LOG_DEBUG("BlenderDNA: Got ", dna.structures.size(), " structures with totally ", fields, " fields"); + +#if ASSIMP_BUILD_BLENDER_DEBUG_DNA + dna.DumpToFile(); +#endif + + dna.AddPrimitiveStructures(); + dna.RegisterConverters(); +} + +#if ASSIMP_BUILD_BLENDER_DEBUG_DNA + +#include <fstream> +// ------------------------------------------------------------------------------------------------ +void DNA ::DumpToFile() { + // we don't bother using the VFS here for this is only for debugging. + // (and all your bases are belong to us). + + std::ofstream f("dna.txt"); + if (f.fail()) { + ASSIMP_LOG_ERROR("Could not dump dna to dna.txt"); + return; + } + f << "Field format: type name offset size" + << "\n"; + f << "Structure format: name size" + << "\n"; + + for (const Structure &s : structures) { + f << s.name << " " << s.size << "\n\n"; + for (const Field &ff : s.fields) { + f << "\t" << ff.type << " " << ff.name << " " << ff.offset << " " << ff.size << "\n"; + } + f << "\n"; + } + f << std::flush; + + ASSIMP_LOG_INFO("BlenderDNA: Dumped dna to dna.txt"); +} +#endif // ASSIMP_BUILD_BLENDER_DEBUG_DNA + +// ------------------------------------------------------------------------------------------------ +/*static*/ void DNA ::ExtractArraySize( + const std::string &out, + size_t array_sizes[2]) { + array_sizes[0] = array_sizes[1] = 1; + std::string::size_type pos = out.find('['); + if (pos++ == std::string::npos) { + return; + } + array_sizes[0] = strtoul10(&out[pos]); + + pos = out.find('[', pos); + if (pos++ == std::string::npos) { + return; + } + array_sizes[1] = strtoul10(&out[pos]); +} + +// ------------------------------------------------------------------------------------------------ +std::shared_ptr<ElemBase> DNA ::ConvertBlobToStructure( + const Structure &structure, + const FileDatabase &db) const { + std::map<std::string, FactoryPair>::const_iterator it = converters.find(structure.name); + if (it == converters.end()) { + return std::shared_ptr<ElemBase>(); + } + + std::shared_ptr<ElemBase> ret = (structure.*((*it).second.first))(); + (structure.*((*it).second.second))(ret, db); + + return ret; +} + +// ------------------------------------------------------------------------------------------------ +DNA::FactoryPair DNA ::GetBlobToStructureConverter( + const Structure &structure, + const FileDatabase & /*db*/ +) const { + std::map<std::string, FactoryPair>::const_iterator it = converters.find(structure.name); + return it == converters.end() ? FactoryPair() : (*it).second; +} + +// basing on http://www.blender.org/development/architecture/notes-on-sdna/ +// ------------------------------------------------------------------------------------------------ +void DNA ::AddPrimitiveStructures() { + // NOTE: these are just dummies. Their presence enforces + // Structure::Convert<target_type> to be called on these + // empty structures. These converters are special + // overloads which scan the name of the structure and + // perform the required data type conversion if one + // of these special names is found in the structure + // in question. + + indices["int"] = structures.size(); + structures.push_back(Structure()); + structures.back().name = "int"; + structures.back().size = 4; + + indices["short"] = structures.size(); + structures.push_back(Structure()); + structures.back().name = "short"; + structures.back().size = 2; + + indices["char"] = structures.size(); + structures.push_back(Structure()); + structures.back().name = "char"; + structures.back().size = 1; + + indices["float"] = structures.size(); + structures.push_back(Structure()); + structures.back().name = "float"; + structures.back().size = 4; + + indices["double"] = structures.size(); + structures.push_back(Structure()); + structures.back().name = "double"; + structures.back().size = 8; + + // no long, seemingly. +} + +// ------------------------------------------------------------------------------------------------ +void SectionParser ::Next() { + stream.SetCurrentPos(current.start + current.size); + + const char tmp[] = { + (const char)stream.GetI1(), + (const char)stream.GetI1(), + (const char)stream.GetI1(), + (const char)stream.GetI1() + }; + current.id = std::string(tmp, tmp[3] ? 4 : tmp[2] ? 3 : tmp[1] ? 2 : 1); + + current.size = stream.GetI4(); + current.address.val = ptr64 ? stream.GetU8() : stream.GetU4(); + + current.dna_index = stream.GetI4(); + current.num = stream.GetI4(); + + current.start = stream.GetCurrentPos(); + if (stream.GetRemainingSizeToLimit() < current.size) { + throw DeadlyImportError("BLEND: invalid size of file block"); + } + +#ifdef ASSIMP_BUILD_BLENDER_DEBUG + ASSIMP_LOG_VERBOSE_DEBUG(current.id); +#endif +} + +#endif diff --git a/libs/assimp/code/AssetLib/Blender/BlenderDNA.h b/libs/assimp/code/AssetLib/Blender/BlenderDNA.h new file mode 100644 index 0000000..b2158f2 --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderDNA.h @@ -0,0 +1,808 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BlenderDNA.h + * @brief Blender `DNA` (file format specification embedded in + * blend file itself) loader. + */ +#ifndef INCLUDED_AI_BLEND_DNA_H +#define INCLUDED_AI_BLEND_DNA_H + +#include <assimp/BaseImporter.h> +#include <assimp/StreamReader.h> +#include <stdint.h> +#include <assimp/DefaultLogger.hpp> +#include <map> +#include <memory> + +// enable verbose log output. really verbose, so be careful. +#ifdef ASSIMP_BUILD_DEBUG +#define ASSIMP_BUILD_BLENDER_DEBUG +#endif + +// set this to non-zero to dump BlenderDNA stuff to dna.txt. +// you could set it on the assimp build command line too without touching it here. +// !!! please make sure this is set to 0 in the repo !!! +#ifndef ASSIMP_BUILD_BLENDER_DEBUG_DNA +#define ASSIMP_BUILD_BLENDER_DEBUG_DNA 0 +#endif + +// #define ASSIMP_BUILD_BLENDER_NO_STATS + +namespace Assimp { + +template <bool, bool> +class StreamReader; +typedef StreamReader<true, true> StreamReaderAny; + +namespace Blender { + +class FileDatabase; +struct FileBlockHead; + +template <template <typename> class TOUT> +class ObjectCache; + +// ------------------------------------------------------------------------------- +/** Exception class used by the blender loader to selectively catch exceptions + * thrown in its own code (DeadlyImportErrors thrown in general utility + * functions are untouched then). If such an exception is not caught by + * the loader itself, it will still be caught by Assimp due to its + * ancestry. */ +// ------------------------------------------------------------------------------- +struct Error : DeadlyImportError { + template <typename... T> + explicit Error(T &&...args) : + DeadlyImportError(args...) { + } +}; + +// ------------------------------------------------------------------------------- +/** The only purpose of this structure is to feed a virtual dtor into its + * descendents. It serves as base class for all data structure fields. */ +// ------------------------------------------------------------------------------- +struct ElemBase { + ElemBase() : + dna_type(nullptr) { + // empty + } + + virtual ~ElemBase() { + // empty + } + + /** Type name of the element. The type + * string points is the `c_str` of the `name` attribute of the + * corresponding `Structure`, that is, it is only valid as long + * as the DNA is not modified. The dna_type is only set if the + * data type is not static, i.e. a std::shared_ptr<ElemBase> + * in the scene description would have its type resolved + * at runtime, so this member is always set. */ + const char *dna_type; +}; + +// ------------------------------------------------------------------------------- +/** Represents a generic pointer to a memory location, which can be either 32 + * or 64 bits. These pointers are loaded from the BLEND file and finally + * fixed to point to the real, converted representation of the objects + * they used to point to.*/ +// ------------------------------------------------------------------------------- +struct Pointer { + Pointer() : + val() { + // empty + } + uint64_t val; +}; + +// ------------------------------------------------------------------------------- +/** Represents a generic offset within a BLEND file */ +// ------------------------------------------------------------------------------- +struct FileOffset { + FileOffset() : + val() { + // empty + } + uint64_t val; +}; + +// ------------------------------------------------------------------------------- +/** Dummy derivate of std::vector to be able to use it in templates simultaenously + * with std::shared_ptr, which takes only one template argument + * while std::vector takes three. Also we need to provide some special member + * functions of shared_ptr */ +// ------------------------------------------------------------------------------- +template <typename T> +class vector : public std::vector<T> { +public: + using std::vector<T>::resize; + using std::vector<T>::empty; + + void reset() { + resize(0); + } + + operator bool() const { + return !empty(); + } +}; + +// ------------------------------------------------------------------------------- +/** Mixed flags for use in #Field */ +// ------------------------------------------------------------------------------- +enum FieldFlags { + FieldFlag_Pointer = 0x1, + FieldFlag_Array = 0x2 +}; + +// ------------------------------------------------------------------------------- +/** Represents a single member of a data structure in a BLEND file */ +// ------------------------------------------------------------------------------- +struct Field { + std::string name; + std::string type; + + size_t size; + size_t offset; + + /** Size of each array dimension. For flat arrays, + * the second dimension is set to 1. */ + size_t array_sizes[2]; + + /** Any of the #FieldFlags enumerated values */ + unsigned int flags; +}; + +// ------------------------------------------------------------------------------- +/** Range of possible behaviors for fields absence in the input file. Some are + * mission critical so we need them, while others can silently be default + * initialized and no animations are harmed. */ +// ------------------------------------------------------------------------------- +enum ErrorPolicy { + /** Substitute default value and ignore */ + ErrorPolicy_Igno, + /** Substitute default value and write to log */ + ErrorPolicy_Warn, + /** Substitute a massive error message and crash the whole matrix. Its time for another zion */ + ErrorPolicy_Fail +}; + +#ifdef ASSIMP_BUILD_BLENDER_DEBUG +#define ErrorPolicy_Igno ErrorPolicy_Warn +#endif + +// ------------------------------------------------------------------------------- +/** Represents a data structure in a BLEND file. A Structure defines n fields + * and their locations and encodings the input stream. Usually, every + * Structure instance pertains to one equally-named data structure in the + * BlenderScene.h header. This class defines various utilities to map a + * binary `blob` read from the file to such a structure instance with + * meaningful contents. */ +// ------------------------------------------------------------------------------- +class Structure { + template <template <typename> class> + friend class ObjectCache; + +public: + Structure() : + cache_idx(static_cast<size_t>(-1)) { + // empty + } + + // publicly accessible members + std::string name; + vector<Field> fields; + std::map<std::string, size_t> indices; + + size_t size; + + // -------------------------------------------------------- + /** Access a field of the structure by its canonical name. The pointer version + * returns nullptr on failure while the reference version raises an import error. */ + inline const Field &operator[](const std::string &ss) const; + inline const Field *Get(const std::string &ss) const; + + // -------------------------------------------------------- + /** Access a field of the structure by its index */ + inline const Field &operator[](const size_t i) const; + + // -------------------------------------------------------- + inline bool operator==(const Structure &other) const { + return name == other.name; // name is meant to be an unique identifier + } + + // -------------------------------------------------------- + inline bool operator!=(const Structure &other) const { + return name != other.name; + } + + // -------------------------------------------------------- + /** Try to read an instance of the structure from the stream + * and attempt to convert to `T`. This is done by + * an appropriate specialization. If none is available, + * a compiler complain is the result. + * @param dest Destination value to be written + * @param db File database, including input stream. */ + template <typename T> + void Convert(T &dest, const FileDatabase &db) const; + + // -------------------------------------------------------- + // generic converter + template <typename T> + void Convert(std::shared_ptr<ElemBase> in, const FileDatabase &db) const; + + // -------------------------------------------------------- + // generic allocator + template <typename T> + std::shared_ptr<ElemBase> Allocate() const; + + // -------------------------------------------------------- + // field parsing for 1d arrays + template <int error_policy, typename T, size_t M> + void ReadFieldArray(T (&out)[M], const char *name, + const FileDatabase &db) const; + + // -------------------------------------------------------- + // field parsing for 2d arrays + template <int error_policy, typename T, size_t M, size_t N> + void ReadFieldArray2(T (&out)[M][N], const char *name, + const FileDatabase &db) const; + + // -------------------------------------------------------- + // field parsing for pointer or dynamic array types + // (std::shared_ptr) + // The return value indicates whether the data was already cached. + template <int error_policy, template <typename> class TOUT, typename T> + bool ReadFieldPtr(TOUT<T> &out, const char *name, + const FileDatabase &db, + bool non_recursive = false) const; + + // -------------------------------------------------------- + // field parsing for static arrays of pointer or dynamic + // array types (std::shared_ptr[]) + // The return value indicates whether the data was already cached. + template <int error_policy, template <typename> class TOUT, typename T, size_t N> + bool ReadFieldPtr(TOUT<T> (&out)[N], const char *name, + const FileDatabase &db) const; + + // -------------------------------------------------------- + // field parsing for `normal` values + // The return value indicates whether the data was already cached. + template <int error_policy, typename T> + void ReadField(T &out, const char *name, + const FileDatabase &db) const; + + // -------------------------------------------------------- + /** + * @brief field parsing for dynamic vectors + * @param[in] out vector of struct to be filled + * @param[in] name of field + * @param[in] db to access the file, dna, ... + * @return true when read was successful + */ + template <int error_policy, template <typename> class TOUT, typename T> + bool ReadFieldPtrVector(vector<TOUT<T>> &out, const char *name, const FileDatabase &db) const; + + /** + * @brief parses raw customdata + * @param[in] out shared_ptr to be filled + * @param[in] cdtype customdata type to read + * @param[in] name of field ptr + * @param[in] db to access the file, dna, ... + * @return true when read was successful + */ + template <int error_policy> + bool ReadCustomDataPtr(std::shared_ptr<ElemBase> &out, int cdtype, const char *name, const FileDatabase &db) const; + +private: + // -------------------------------------------------------- + template <template <typename> class TOUT, typename T> + bool ResolvePointer(TOUT<T> &out, const Pointer &ptrval, + const FileDatabase &db, const Field &f, + bool non_recursive = false) const; + + // -------------------------------------------------------- + template <template <typename> class TOUT, typename T> + bool ResolvePointer(vector<TOUT<T>> &out, const Pointer &ptrval, + const FileDatabase &db, const Field &f, bool) const; + + // -------------------------------------------------------- + bool ResolvePointer(std::shared_ptr<FileOffset> &out, const Pointer &ptrval, + const FileDatabase &db, const Field &f, bool) const; + + // -------------------------------------------------------- + inline const FileBlockHead *LocateFileBlockForAddress( + const Pointer &ptrval, + const FileDatabase &db) const; + +private: + // ------------------------------------------------------------------------------ + template <typename T> + T *_allocate(std::shared_ptr<T> &out, size_t &s) const { + out = std::shared_ptr<T>(new T()); + s = 1; + return out.get(); + } + + template <typename T> + T *_allocate(vector<T> &out, size_t &s) const { + out.resize(s); + return s ? &out.front() : nullptr; + } + + // -------------------------------------------------------- + template <int error_policy> + struct _defaultInitializer { + + template <typename T, unsigned int N> + void operator()(T (&out)[N], const char * = nullptr) { + for (unsigned int i = 0; i < N; ++i) { + out[i] = T(); + } + } + + template <typename T, unsigned int N, unsigned int M> + void operator()(T (&out)[N][M], const char * = nullptr) { + for (unsigned int i = 0; i < N; ++i) { + for (unsigned int j = 0; j < M; ++j) { + out[i][j] = T(); + } + } + } + + template <typename T> + void operator()(T &out, const char * = nullptr) { + out = T(); + } + }; + +private: + mutable size_t cache_idx; +}; + +// -------------------------------------------------------- +template <> +struct Structure::_defaultInitializer<ErrorPolicy_Warn> { + + template <typename T> + void operator()(T &out, const char *reason = "<add reason>") { + ASSIMP_LOG_WARN(reason); + + // ... and let the show go on + _defaultInitializer<0 /*ErrorPolicy_Igno*/>()(out); + } +}; + +template <> +struct Structure::_defaultInitializer<ErrorPolicy_Fail> { + + template <typename T> + void operator()(T & /*out*/, const char * = "") { + // obviously, it is crucial that _DefaultInitializer is used + // only from within a catch clause. + throw DeadlyImportError("Constructing BlenderDNA Structure encountered an error"); + } +}; + +// ------------------------------------------------------------------------------------------------------- +template <> +inline bool Structure ::ResolvePointer<std::shared_ptr, ElemBase>(std::shared_ptr<ElemBase> &out, + const Pointer &ptrval, + const FileDatabase &db, + const Field &f, + bool) const; + +// ------------------------------------------------------------------------------- +/** Represents the full data structure information for a single BLEND file. + * This data is extracted from the DNA1 chunk in the file. + * #DNAParser does the reading and represents currently the only place where + * DNA is altered.*/ +// ------------------------------------------------------------------------------- +class DNA { +public: + typedef void (Structure::*ConvertProcPtr)( + std::shared_ptr<ElemBase> in, + const FileDatabase &) const; + + typedef std::shared_ptr<ElemBase> ( + Structure::*AllocProcPtr)() const; + + typedef std::pair<AllocProcPtr, ConvertProcPtr> FactoryPair; + +public: + std::map<std::string, FactoryPair> converters; + vector<Structure> structures; + std::map<std::string, size_t> indices; + +public: + // -------------------------------------------------------- + /** Access a structure by its canonical name, the pointer version returns nullptr on failure + * while the reference version raises an error. */ + inline const Structure &operator[](const std::string &ss) const; + inline const Structure *Get(const std::string &ss) const; + + // -------------------------------------------------------- + /** Access a structure by its index */ + inline const Structure &operator[](const size_t i) const; + +public: + // -------------------------------------------------------- + /** Add structure definitions for all the primitive types, + * i.e. integer, short, char, float */ + void AddPrimitiveStructures(); + + // -------------------------------------------------------- + /** Fill the @c converters member with converters for all + * known data types. The implementation of this method is + * in BlenderScene.cpp and is machine-generated. + * Converters are used to quickly handle objects whose + * exact data type is a runtime-property and not yet + * known at compile time (consider Object::data).*/ + void RegisterConverters(); + + // -------------------------------------------------------- + /** Take an input blob from the stream, interpret it according to + * a its structure name and convert it to the intermediate + * representation. + * @param structure Destination structure definition + * @param db File database. + * @return A null pointer if no appropriate converter is available.*/ + std::shared_ptr<ElemBase> ConvertBlobToStructure( + const Structure &structure, + const FileDatabase &db) const; + + // -------------------------------------------------------- + /** Find a suitable conversion function for a given Structure. + * Such a converter function takes a blob from the input + * stream, reads as much as it needs, and builds up a + * complete object in intermediate representation. + * @param structure Destination structure definition + * @param db File database. + * @return A null pointer in .first if no appropriate converter is available.*/ + FactoryPair GetBlobToStructureConverter( + const Structure &structure, + const FileDatabase &db) const; + +#if ASSIMP_BUILD_BLENDER_DEBUG_DNA + // -------------------------------------------------------- + /** Dump the DNA to a text file. This is for debugging purposes. + * The output file is `dna.txt` in the current working folder*/ + void DumpToFile(); +#endif + + // -------------------------------------------------------- + /** Extract array dimensions from a C array declaration, such + * as `...[4][6]`. Returned string would be `...[][]`. + * @param out + * @param array_sizes Receive maximally two array dimensions, + * the second element is set to 1 if the array is flat. + * Both are set to 1 if the input is not an array. + * @throw DeadlyImportError if more than 2 dimensions are + * encountered. */ + static void ExtractArraySize( + const std::string &out, + size_t array_sizes[2]); +}; + +// special converters for primitive types +template <> +inline void Structure ::Convert<int>(int &dest, const FileDatabase &db) const; +template <> +inline void Structure ::Convert<short>(short &dest, const FileDatabase &db) const; +template <> +inline void Structure ::Convert<char>(char &dest, const FileDatabase &db) const; +template <> +inline void Structure ::Convert<float>(float &dest, const FileDatabase &db) const; +template <> +inline void Structure ::Convert<double>(double &dest, const FileDatabase &db) const; +template <> +inline void Structure ::Convert<Pointer>(Pointer &dest, const FileDatabase &db) const; + +// ------------------------------------------------------------------------------- +/** Describes a master file block header. Each master file sections holds n + * elements of a certain SDNA structure (or otherwise unspecified data). */ +// ------------------------------------------------------------------------------- +struct FileBlockHead { + // points right after the header of the file block + StreamReaderAny::pos start; + + std::string id; + size_t size; + + // original memory address of the data + Pointer address; + + // index into DNA + unsigned int dna_index; + + // number of structure instances to follow + size_t num; + + // file blocks are sorted by address to quickly locate specific memory addresses + bool operator<(const FileBlockHead &o) const { + return address.val < o.address.val; + } + + // for std::upper_bound + operator const Pointer &() const { + return address; + } +}; + +// for std::upper_bound +inline bool operator<(const Pointer &a, const Pointer &b) { + return a.val < b.val; +} + +// ------------------------------------------------------------------------------- +/** Utility to read all master file blocks in turn. */ +// ------------------------------------------------------------------------------- +class SectionParser { +public: + // -------------------------------------------------------- + /** @param stream Inout stream, must point to the + * first section in the file. Call Next() once + * to have it read. + * @param ptr64 Pointer size in file is 64 bits? */ + SectionParser(StreamReaderAny &stream, bool ptr64) : + stream(stream), ptr64(ptr64) { + current.size = current.start = 0; + } + +public: + // -------------------------------------------------------- + const FileBlockHead &GetCurrent() const { + return current; + } + +public: + // -------------------------------------------------------- + /** Advance to the next section. + * @throw DeadlyImportError if the last chunk was passed. */ + void Next(); + +public: + FileBlockHead current; + StreamReaderAny &stream; + bool ptr64; +}; + +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS +// ------------------------------------------------------------------------------- +/** Import statistics, i.e. number of file blocks read*/ +// ------------------------------------------------------------------------------- +class Statistics { + +public: + Statistics() : + fields_read(), pointers_resolved(), cache_hits() + // , blocks_read () + , + cached_objects() {} + +public: + /** total number of fields we read */ + unsigned int fields_read; + + /** total number of resolved pointers */ + unsigned int pointers_resolved; + + /** number of pointers resolved from the cache */ + unsigned int cache_hits; + + /** number of blocks (from FileDatabase::entries) + we did actually read from. */ + // unsigned int blocks_read; + + /** objects in FileData::cache */ + unsigned int cached_objects; +}; +#endif + +// ------------------------------------------------------------------------------- +/** The object cache - all objects addressed by pointers are added here. This + * avoids circular references and avoids object duplication. */ +// ------------------------------------------------------------------------------- +template <template <typename> class TOUT> +class ObjectCache { +public: + typedef std::map<Pointer, TOUT<ElemBase>> StructureCache; + +public: + ObjectCache(const FileDatabase &db) : + db(db) { + // currently there are only ~400 structure records per blend file. + // we read only a small part of them and don't cache objects + // which we don't need, so this should suffice. + caches.reserve(64); + } + +public: + // -------------------------------------------------------- + /** Check whether a specific item is in the cache. + * @param s Data type of the item + * @param out Output pointer. Unchanged if the + * cache doesn't know the item yet. + * @param ptr Item address to look for. */ + template <typename T> + void get( + const Structure &s, + TOUT<T> &out, + const Pointer &ptr) const; + + // -------------------------------------------------------- + /** Add an item to the cache after the item has + * been fully read. Do not insert anything that + * may be faulty or might cause the loading + * to abort. + * @param s Data type of the item + * @param out Item to insert into the cache + * @param ptr address (cache key) of the item. */ + template <typename T> + void set(const Structure &s, + const TOUT<T> &out, + const Pointer &ptr); + +private: + mutable vector<StructureCache> caches; + const FileDatabase &db; +}; + +// ------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------- +template <> +class ObjectCache<Blender::vector> { +public: + ObjectCache(const FileDatabase &) {} + + template <typename T> + void get(const Structure &, vector<T> &, const Pointer &) {} + template <typename T> + void set(const Structure &, const vector<T> &, const Pointer &) {} +}; + +#ifdef _MSC_VER +#pragma warning(disable : 4355) +#endif + +// ------------------------------------------------------------------------------- +/** Memory representation of a full BLEND file and all its dependencies. The + * output aiScene is constructed from an instance of this data structure. */ +// ------------------------------------------------------------------------------- +class FileDatabase { + template <template <typename> class TOUT> + friend class ObjectCache; + +public: + FileDatabase() : + _cacheArrays(*this), _cache(*this), next_cache_idx() {} + +public: + // publicly accessible fields + bool i64bit; + bool little; + + DNA dna; + std::shared_ptr<StreamReaderAny> reader; + vector<FileBlockHead> entries; + +public: + Statistics &stats() const { + return _stats; + } + + // For all our templates to work on both shared_ptr's and vector's + // using the same code, a dummy cache for arrays is provided. Actually, + // arrays of objects are never cached because we can't easily + // ensure their proper destruction. + template <typename T> + ObjectCache<std::shared_ptr> &cache(std::shared_ptr<T> & /*in*/) const { + return _cache; + } + + template <typename T> + ObjectCache<vector> &cache(vector<T> & /*in*/) const { + return _cacheArrays; + } + +private: +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS + mutable Statistics _stats; +#endif + + mutable ObjectCache<vector> _cacheArrays; + mutable ObjectCache<std::shared_ptr> _cache; + + mutable size_t next_cache_idx; +}; + +#ifdef _MSC_VER +#pragma warning(default : 4355) +#endif + +// ------------------------------------------------------------------------------- +/** Factory to extract a #DNA from the DNA1 file block in a BLEND file. */ +// ------------------------------------------------------------------------------- +class DNAParser { + +public: + /** Bind the parser to a empty DNA and an input stream */ + DNAParser(FileDatabase &db) : + db(db) {} + +public: + // -------------------------------------------------------- + /** Locate the DNA in the file and parse it. The input + * stream is expected to point to the beginning of the DN1 + * chunk at the time this method is called and is + * undefined afterwards. + * @throw DeadlyImportError if the DNA cannot be read. + * @note The position of the stream pointer is undefined + * afterwards.*/ + void Parse(); + +public: + /** Obtain a reference to the extracted DNA information */ + const Blender::DNA &GetDNA() const { + return db.dna; + } + +private: + FileDatabase &db; +}; + +/** +* @brief read CustomData's data to ptr to mem +* @param[out] out memory ptr to set +* @param[in] cdtype to read +* @param[in] cnt cnt of elements to read +* @param[in] db to read elements from +* @return true when ok +*/ +bool readCustomData(std::shared_ptr<ElemBase> &out, int cdtype, size_t cnt, const FileDatabase &db); + +} // namespace Blender +} // namespace Assimp + +#include "BlenderDNA.inl" + +#endif diff --git a/libs/assimp/code/AssetLib/Blender/BlenderDNA.inl b/libs/assimp/code/AssetLib/Blender/BlenderDNA.inl new file mode 100644 index 0000000..4f64987 --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderDNA.inl @@ -0,0 +1,845 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BlenderDNA.inl + * @brief Blender `DNA` (file format specification embedded in + * blend file itself) loader. + */ +#ifndef INCLUDED_AI_BLEND_DNA_INL +#define INCLUDED_AI_BLEND_DNA_INL + +#include <memory> +#include <assimp/TinyFormatter.h> + +namespace Assimp { +namespace Blender { + +//-------------------------------------------------------------------------------- +const Field& Structure :: operator [] (const std::string& ss) const +{ + std::map<std::string, size_t>::const_iterator it = indices.find(ss); + if (it == indices.end()) { + throw Error("BlendDNA: Did not find a field named `",ss,"` in structure `",name,"`"); + } + + return fields[(*it).second]; +} + +//-------------------------------------------------------------------------------- +const Field* Structure :: Get (const std::string& ss) const +{ + std::map<std::string, size_t>::const_iterator it = indices.find(ss); + return it == indices.end() ? nullptr : &fields[(*it).second]; +} + +//-------------------------------------------------------------------------------- +const Field& Structure :: operator [] (const size_t i) const +{ + if (i >= fields.size()) { + throw Error("BlendDNA: There is no field with index `",i,"` in structure `",name,"`"); + } + + return fields[i]; +} + +//-------------------------------------------------------------------------------- +template <typename T> std::shared_ptr<ElemBase> Structure :: Allocate() const +{ + return std::shared_ptr<T>(new T()); +} + +//-------------------------------------------------------------------------------- +template <typename T> void Structure :: Convert( + std::shared_ptr<ElemBase> in, + const FileDatabase& db) const +{ + Convert<T> (*static_cast<T*> ( in.get() ),db); +} + +//-------------------------------------------------------------------------------- +template <int error_policy, typename T, size_t M> +void Structure :: ReadFieldArray(T (& out)[M], const char* name, const FileDatabase& db) const +{ + const StreamReaderAny::pos old = db.reader->GetCurrentPos(); + try { + const Field& f = (*this)[name]; + const Structure& s = db.dna[f.type]; + + // is the input actually an array? + if (!(f.flags & FieldFlag_Array)) { + throw Error("Field `",name,"` of structure `",this->name,"` ought to be an array of size ",M); + } + + db.reader->IncPtr(f.offset); + + // size conversions are always allowed, regardless of error_policy + unsigned int i = 0; + for(; i < std::min(f.array_sizes[0],M); ++i) { + s.Convert(out[i],db); + } + for(; i < M; ++i) { + _defaultInitializer<ErrorPolicy_Igno>()(out[i]); + } + } + catch (const Error& e) { + _defaultInitializer<error_policy>()(out,e.what()); + } + + // and recover the previous stream position + db.reader->SetCurrentPos(old); + +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS + ++db.stats().fields_read; +#endif +} + +//-------------------------------------------------------------------------------- +template <int error_policy, typename T, size_t M, size_t N> +void Structure :: ReadFieldArray2(T (& out)[M][N], const char* name, const FileDatabase& db) const +{ + const StreamReaderAny::pos old = db.reader->GetCurrentPos(); + try { + const Field& f = (*this)[name]; + const Structure& s = db.dna[f.type]; + + // is the input actually an array? + if (!(f.flags & FieldFlag_Array)) { + throw Error("Field `",name,"` of structure `", + this->name,"` ought to be an array of size ",M,"*",N + ); + } + + db.reader->IncPtr(f.offset); + + // size conversions are always allowed, regardless of error_policy + unsigned int i = 0; + for(; i < std::min(f.array_sizes[0],M); ++i) { + unsigned int j = 0; + for(; j < std::min(f.array_sizes[1],N); ++j) { + s.Convert(out[i][j],db); + } + for(; j < N; ++j) { + _defaultInitializer<ErrorPolicy_Igno>()(out[i][j]); + } + } + for(; i < M; ++i) { + _defaultInitializer<ErrorPolicy_Igno>()(out[i]); + } + } + catch (const Error& e) { + _defaultInitializer<error_policy>()(out,e.what()); + } + + // and recover the previous stream position + db.reader->SetCurrentPos(old); + +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS + ++db.stats().fields_read; +#endif +} + +//-------------------------------------------------------------------------------- +template <int error_policy, template <typename> class TOUT, typename T> +bool Structure :: ReadFieldPtr(TOUT<T>& out, const char* name, const FileDatabase& db, + bool non_recursive /*= false*/) const +{ + const StreamReaderAny::pos old = db.reader->GetCurrentPos(); + Pointer ptrval; + const Field* f; + try { + f = &(*this)[name]; + + // sanity check, should never happen if the genblenddna script is right + if (!(f->flags & FieldFlag_Pointer)) { + throw Error("Field `",name,"` of structure `", + this->name,"` ought to be a pointer"); + } + + db.reader->IncPtr(f->offset); + Convert(ptrval,db); + // actually it is meaningless on which Structure the Convert is called + // because the `Pointer` argument triggers a special implementation. + } + catch (const Error& e) { + _defaultInitializer<error_policy>()(out,e.what()); + + out.reset(); + return false; + } + + // resolve the pointer and load the corresponding structure + const bool res = ResolvePointer(out,ptrval,db,*f, non_recursive); + + if(!non_recursive) { + // and recover the previous stream position + db.reader->SetCurrentPos(old); + } + +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS + ++db.stats().fields_read; +#endif + + return res; +} + +//-------------------------------------------------------------------------------- +template <int error_policy, template <typename> class TOUT, typename T, size_t N> +bool Structure :: ReadFieldPtr(TOUT<T> (&out)[N], const char* name, + const FileDatabase& db) const +{ + // XXX see if we can reduce this to call to the 'normal' ReadFieldPtr + const StreamReaderAny::pos old = db.reader->GetCurrentPos(); + Pointer ptrval[N]; + const Field* f; + try { + f = &(*this)[name]; + +#ifdef _DEBUG + // sanity check, should never happen if the genblenddna script is right + if ((FieldFlag_Pointer|FieldFlag_Pointer) != (f->flags & (FieldFlag_Pointer|FieldFlag_Pointer))) { + throw Error("Field `",name,"` of structure `", + this->name,"` ought to be a pointer AND an array"); + } +#endif // _DEBUG + + db.reader->IncPtr(f->offset); + + size_t i = 0; + for(; i < std::min(f->array_sizes[0],N); ++i) { + Convert(ptrval[i],db); + } + for(; i < N; ++i) { + _defaultInitializer<ErrorPolicy_Igno>()(ptrval[i]); + } + + // actually it is meaningless on which Structure the Convert is called + // because the `Pointer` argument triggers a special implementation. + } + catch (const Error& e) { + _defaultInitializer<error_policy>()(out,e.what()); + for(size_t i = 0; i < N; ++i) { + out[i].reset(); + } + return false; + } + + bool res = true; + for(size_t i = 0; i < N; ++i) { + // resolve the pointer and load the corresponding structure + res = ResolvePointer(out[i],ptrval[i],db,*f) && res; + } + + // and recover the previous stream position + db.reader->SetCurrentPos(old); + +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS + ++db.stats().fields_read; +#endif + return res; +} + +//-------------------------------------------------------------------------------- +template <int error_policy, typename T> +void Structure :: ReadField(T& out, const char* name, const FileDatabase& db) const +{ + const StreamReaderAny::pos old = db.reader->GetCurrentPos(); + try { + const Field& f = (*this)[name]; + // find the structure definition pertaining to this field + const Structure& s = db.dna[f.type]; + + db.reader->IncPtr(f.offset); + s.Convert(out,db); + } + catch (const Error& e) { + _defaultInitializer<error_policy>()(out,e.what()); + } + + // and recover the previous stream position + db.reader->SetCurrentPos(old); + +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS + ++db.stats().fields_read; +#endif +} + + +//-------------------------------------------------------------------------------- +// field parsing for raw untyped data (like CustomDataLayer.data) +template <int error_policy> +bool Structure::ReadCustomDataPtr(std::shared_ptr<ElemBase>&out, int cdtype, const char* name, const FileDatabase& db) const { + + const StreamReaderAny::pos old = db.reader->GetCurrentPos(); + + Pointer ptrval; + const Field* f; + try { + f = &(*this)[name]; + + // sanity check, should never happen if the genblenddna script is right + if (!(f->flags & FieldFlag_Pointer)) { + throw Error("Field `", name, "` of structure `", + this->name, "` ought to be a pointer"); + } + + db.reader->IncPtr(f->offset); + Convert(ptrval, db); + // actually it is meaningless on which Structure the Convert is called + // because the `Pointer` argument triggers a special implementation. + } + catch (const Error& e) { + _defaultInitializer<error_policy>()(out, e.what()); + out.reset(); + } + + bool readOk = true; + if (ptrval.val) { + // get block for ptr + const FileBlockHead* block = LocateFileBlockForAddress(ptrval, db); + db.reader->SetCurrentPos(block->start + static_cast<size_t>((ptrval.val - block->address.val))); + // read block->num instances of given type to out + readOk = readCustomData(out, cdtype, block->num, db); + } + + // and recover the previous stream position + db.reader->SetCurrentPos(old); + +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS + ++db.stats().fields_read; +#endif + + return readOk; +} + +//-------------------------------------------------------------------------------- +template <int error_policy, template <typename> class TOUT, typename T> +bool Structure::ReadFieldPtrVector(vector<TOUT<T>>&out, const char* name, const FileDatabase& db) const { + out.clear(); + + const StreamReaderAny::pos old = db.reader->GetCurrentPos(); + + Pointer ptrval; + const Field* f; + try { + f = &(*this)[name]; + + // sanity check, should never happen if the genblenddna script is right + if (!(f->flags & FieldFlag_Pointer)) { + throw Error("Field `", name, "` of structure `", + this->name, "` ought to be a pointer"); + } + + db.reader->IncPtr(f->offset); + Convert(ptrval, db); + // actually it is meaningless on which Structure the Convert is called + // because the `Pointer` argument triggers a special implementation. + } + catch (const Error& e) { + _defaultInitializer<error_policy>()(out, e.what()); + out.clear(); + return false; + } + + + if (ptrval.val) { + // find the file block the pointer is pointing to + const FileBlockHead* block = LocateFileBlockForAddress(ptrval, db); + db.reader->SetCurrentPos(block->start + static_cast<size_t>((ptrval.val - block->address.val))); + // FIXME: basically, this could cause problems with 64 bit pointers on 32 bit systems. + // I really ought to improve StreamReader to work with 64 bit indices exclusively. + + const Structure& s = db.dna[f->type]; + for (size_t i = 0; i < block->num; ++i) { + TOUT<T> p(new T); + s.Convert(*p, db); + out.push_back(p); + } + } + + db.reader->SetCurrentPos(old); + +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS + ++db.stats().fields_read; +#endif + + return true; +} + + +//-------------------------------------------------------------------------------- +template <template <typename> class TOUT, typename T> +bool Structure :: ResolvePointer(TOUT<T>& out, const Pointer & ptrval, const FileDatabase& db, + const Field& f, + bool non_recursive /*= false*/) const +{ + out.reset(); // ensure null pointers work + if (!ptrval.val) { + return false; + } + const Structure& s = db.dna[f.type]; + // find the file block the pointer is pointing to + const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db); + + // also determine the target type from the block header + // and check if it matches the type which we expect. + const Structure& ss = db.dna[block->dna_index]; + if (ss != s) { + throw Error("Expected target to be of type `",s.name, + "` but seemingly it is a `",ss.name,"` instead" + ); + } + + // try to retrieve the object from the cache + db.cache(out).get(s,out,ptrval); + if (out) { + return true; + } + + // seek to this location, but save the previous stream pointer. + const StreamReaderAny::pos pold = db.reader->GetCurrentPos(); + db.reader->SetCurrentPos(block->start+ static_cast<size_t>((ptrval.val - block->address.val) )); + // FIXME: basically, this could cause problems with 64 bit pointers on 32 bit systems. + // I really ought to improve StreamReader to work with 64 bit indices exclusively. + + // continue conversion after allocating the required storage + size_t num = block->size / ss.size; + T* o = _allocate(out,num); + + // cache the object before we convert it to avoid cyclic recursion. + db.cache(out).set(s,out,ptrval); + + // if the non_recursive flag is set, we don't do anything but leave + // the cursor at the correct position to resolve the object. + if (!non_recursive) { + for (size_t i = 0; i < num; ++i,++o) { + s.Convert(*o,db); + } + + db.reader->SetCurrentPos(pold); + } + +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS + if(out) { + ++db.stats().pointers_resolved; + } +#endif + return false; +} + + +//-------------------------------------------------------------------------------- +inline bool Structure :: ResolvePointer( std::shared_ptr< FileOffset >& out, const Pointer & ptrval, + const FileDatabase& db, + const Field&, + bool) const +{ + // Currently used exclusively by PackedFile::data to represent + // a simple offset into the mapped BLEND file. + out.reset(); + if (!ptrval.val) { + return false; + } + + // find the file block the pointer is pointing to + const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db); + + out = std::shared_ptr< FileOffset > (new FileOffset()); + out->val = block->start+ static_cast<size_t>((ptrval.val - block->address.val) ); + return false; +} + +//-------------------------------------------------------------------------------- +template <template <typename> class TOUT, typename T> +bool Structure :: ResolvePointer(vector< TOUT<T> >& out, const Pointer & ptrval, + const FileDatabase& db, + const Field& f, + bool) const +{ + // This is a function overload, not a template specialization. According to + // the partial ordering rules, it should be selected by the compiler + // for array-of-pointer inputs, i.e. Object::mats. + + out.reset(); + if (!ptrval.val) { + return false; + } + + // find the file block the pointer is pointing to + const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db); + const size_t num = block->size / (db.i64bit?8:4); + + // keep the old stream position + const StreamReaderAny::pos pold = db.reader->GetCurrentPos(); + db.reader->SetCurrentPos(block->start+ static_cast<size_t>((ptrval.val - block->address.val) )); + + bool res = false; + // allocate raw storage for the array + out.resize(num); + for (size_t i = 0; i< num; ++i) { + Pointer val; + Convert(val,db); + + // and resolve the pointees + res = ResolvePointer(out[i],val,db,f) && res; + } + + db.reader->SetCurrentPos(pold); + return res; +} + +//-------------------------------------------------------------------------------- +template <> bool Structure :: ResolvePointer<std::shared_ptr,ElemBase>(std::shared_ptr<ElemBase>& out, + const Pointer & ptrval, + const FileDatabase& db, + const Field&, + bool +) const +{ + // Special case when the data type needs to be determined at runtime. + // Less secure than in the `strongly-typed` case. + + out.reset(); + if (!ptrval.val) { + return false; + } + + // find the file block the pointer is pointing to + const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db); + + // determine the target type from the block header + const Structure& s = db.dna[block->dna_index]; + + // try to retrieve the object from the cache + db.cache(out).get(s,out,ptrval); + if (out) { + return true; + } + + // seek to this location, but save the previous stream pointer. + const StreamReaderAny::pos pold = db.reader->GetCurrentPos(); + db.reader->SetCurrentPos(block->start+ static_cast<size_t>((ptrval.val - block->address.val) )); + // FIXME: basically, this could cause problems with 64 bit pointers on 32 bit systems. + // I really ought to improve StreamReader to work with 64 bit indices exclusively. + + // continue conversion after allocating the required storage + DNA::FactoryPair builders = db.dna.GetBlobToStructureConverter(s,db); + if (!builders.first) { + // this might happen if DNA::RegisterConverters hasn't been called so far + // or if the target type is not contained in `our` DNA. + out.reset(); + ASSIMP_LOG_WARN( "Failed to find a converter for the `",s.name,"` structure" ); + return false; + } + + // allocate the object hull + out = (s.*builders.first)(); + + // cache the object immediately to prevent infinite recursion in a + // circular list with a single element (i.e. a self-referencing element). + db.cache(out).set(s,out,ptrval); + + // and do the actual conversion + (s.*builders.second)(out,db); + db.reader->SetCurrentPos(pold); + + // store a pointer to the name string of the actual type + // in the object itself. This allows the conversion code + // to perform additional type checking. + out->dna_type = s.name.c_str(); + + +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS + ++db.stats().pointers_resolved; +#endif + return false; +} + +//-------------------------------------------------------------------------------- +const FileBlockHead* Structure :: LocateFileBlockForAddress(const Pointer & ptrval, const FileDatabase& db) const +{ + // the file blocks appear in list sorted by + // with ascending base addresses so we can run a + // binary search to locate the pointer quickly. + + // NOTE: Blender seems to distinguish between side-by-side + // data (stored in the same data block) and far pointers, + // which are only used for structures starting with an ID. + // We don't need to make this distinction, our algorithm + // works regardless where the data is stored. + vector<FileBlockHead>::const_iterator it = std::lower_bound(db.entries.begin(),db.entries.end(),ptrval); + if (it == db.entries.end()) { + // this is crucial, pointers may not be invalid. + // this is either a corrupted file or an attempted attack. + throw DeadlyImportError("Failure resolving pointer 0x", + std::hex,ptrval.val,", no file block falls into this address range"); + } + if (ptrval.val >= (*it).address.val + (*it).size) { + throw DeadlyImportError("Failure resolving pointer 0x", + std::hex,ptrval.val,", nearest file block starting at 0x", + (*it).address.val," ends at 0x", + (*it).address.val + (*it).size); + } + return &*it; +} + +// ------------------------------------------------------------------------------------------------ +// NOTE: The MSVC debugger keeps showing up this annoying `a cast to a smaller data type has +// caused a loss of data`-warning. Avoid this warning by a masking with an appropriate bitmask. + +template <typename T> struct signless; +template <> struct signless<char> {typedef unsigned char type;}; +template <> struct signless<short> {typedef unsigned short type;}; +template <> struct signless<int> {typedef unsigned int type;}; +template <> struct signless<unsigned char> { typedef unsigned char type; }; +template <typename T> +struct static_cast_silent { + template <typename V> + T operator()(V in) { + return static_cast<T>(in & static_cast<typename signless<T>::type>(-1)); + } +}; + +template <> struct static_cast_silent<float> { + template <typename V> float operator()(V in) { + return static_cast<float> (in); + } +}; + +template <> struct static_cast_silent<double> { + template <typename V> double operator()(V in) { + return static_cast<double>(in); + } +}; + +// ------------------------------------------------------------------------------------------------ +template <typename T> inline void ConvertDispatcher(T& out, const Structure& in,const FileDatabase& db) +{ + if (in.name == "int") { + out = static_cast_silent<T>()(db.reader->GetU4()); + } + else if (in.name == "short") { + out = static_cast_silent<T>()(db.reader->GetU2()); + } + else if (in.name == "char") { + out = static_cast_silent<T>()(db.reader->GetU1()); + } + else if (in.name == "float") { + out = static_cast<T>(db.reader->GetF4()); + } + else if (in.name == "double") { + out = static_cast<T>(db.reader->GetF8()); + } + else { + throw DeadlyImportError("Unknown source for conversion to primitive data type: ", in.name); + } +} + +// ------------------------------------------------------------------------------------------------ +template <> inline void Structure :: Convert<int> (int& dest,const FileDatabase& db) const +{ + ConvertDispatcher(dest,*this,db); +} + +// ------------------------------------------------------------------------------------------------ +template<> inline void Structure :: Convert<short> (short& dest,const FileDatabase& db) const +{ + // automatic rescaling from short to float and vice versa (seems to be used by normals) + if (name == "float") { + float f = db.reader->GetF4(); + if ( f > 1.0f ) + f = 1.0f; + dest = static_cast<short>( f * 32767.f); + //db.reader->IncPtr(-4); + return; + } + else if (name == "double") { + dest = static_cast<short>(db.reader->GetF8() * 32767.); + //db.reader->IncPtr(-8); + return; + } + ConvertDispatcher(dest,*this,db); +} + +// ------------------------------------------------------------------------------------------------ +template <> inline void Structure :: Convert<char> (char& dest,const FileDatabase& db) const +{ + // automatic rescaling from char to float and vice versa (seems useful for RGB colors) + if (name == "float") { + dest = static_cast<char>(db.reader->GetF4() * 255.f); + return; + } + else if (name == "double") { + dest = static_cast<char>(db.reader->GetF8() * 255.f); + return; + } + ConvertDispatcher(dest,*this,db); +} + +// ------------------------------------------------------------------------------------------------ +template <> inline void Structure::Convert<unsigned char>(unsigned char& dest, const FileDatabase& db) const +{ + // automatic rescaling from char to float and vice versa (seems useful for RGB colors) + if (name == "float") { + dest = static_cast<unsigned char>(db.reader->GetF4() * 255.f); + return; + } + else if (name == "double") { + dest = static_cast<unsigned char>(db.reader->GetF8() * 255.f); + return; + } + ConvertDispatcher(dest, *this, db); +} + + +// ------------------------------------------------------------------------------------------------ +template <> inline void Structure :: Convert<float> (float& dest,const FileDatabase& db) const +{ + // automatic rescaling from char to float and vice versa (seems useful for RGB colors) + if (name == "char") { + dest = db.reader->GetI1() / 255.f; + return; + } + // automatic rescaling from short to float and vice versa (used by normals) + else if (name == "short") { + dest = db.reader->GetI2() / 32767.f; + return; + } + ConvertDispatcher(dest,*this,db); +} + +// ------------------------------------------------------------------------------------------------ +template <> inline void Structure :: Convert<double> (double& dest,const FileDatabase& db) const +{ + if (name == "char") { + dest = db.reader->GetI1() / 255.; + return; + } + else if (name == "short") { + dest = db.reader->GetI2() / 32767.; + return; + } + ConvertDispatcher(dest,*this,db); +} + +// ------------------------------------------------------------------------------------------------ +template <> inline void Structure :: Convert<Pointer> (Pointer& dest,const FileDatabase& db) const +{ + if (db.i64bit) { + dest.val = db.reader->GetU8(); + //db.reader->IncPtr(-8); + return; + } + dest.val = db.reader->GetU4(); + //db.reader->IncPtr(-4); +} + +//-------------------------------------------------------------------------------- +const Structure& DNA :: operator [] (const std::string& ss) const +{ + std::map<std::string, size_t>::const_iterator it = indices.find(ss); + if (it == indices.end()) { + throw Error("BlendDNA: Did not find a structure named `",ss,"`"); + } + + return structures[(*it).second]; +} + +//-------------------------------------------------------------------------------- +const Structure* DNA :: Get (const std::string& ss) const +{ + std::map<std::string, size_t>::const_iterator it = indices.find(ss); + return it == indices.end() ? nullptr : &structures[(*it).second]; +} + +//-------------------------------------------------------------------------------- +const Structure& DNA :: operator [] (const size_t i) const +{ + if (i >= structures.size()) { + throw Error("BlendDNA: There is no structure with index `",i,"`"); + } + + return structures[i]; +} + +//-------------------------------------------------------------------------------- +template <template <typename> class TOUT> template <typename T> void ObjectCache<TOUT> :: get ( + const Structure& s, + TOUT<T>& out, + const Pointer& ptr +) const { + + if(s.cache_idx == static_cast<size_t>(-1)) { + s.cache_idx = db.next_cache_idx++; + caches.resize(db.next_cache_idx); + return; + } + + typename StructureCache::const_iterator it = caches[s.cache_idx].find(ptr); + if (it != caches[s.cache_idx].end()) { + out = std::static_pointer_cast<T>( (*it).second ); + +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS + ++db.stats().cache_hits; +#endif + } + // otherwise, out remains untouched +} + + +//-------------------------------------------------------------------------------- +template <template <typename> class TOUT> template <typename T> void ObjectCache<TOUT> :: set ( + const Structure& s, + const TOUT<T>& out, + const Pointer& ptr +) { + if(s.cache_idx == static_cast<size_t>(-1)) { + s.cache_idx = db.next_cache_idx++; + caches.resize(db.next_cache_idx); + } + caches[s.cache_idx][ptr] = std::static_pointer_cast<ElemBase>( out ); + +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS + ++db.stats().cached_objects; +#endif +} + +}} +#endif diff --git a/libs/assimp/code/AssetLib/Blender/BlenderIntermediate.h b/libs/assimp/code/AssetLib/Blender/BlenderIntermediate.h new file mode 100644 index 0000000..0651f71 --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderIntermediate.h @@ -0,0 +1,206 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BlenderIntermediate.h + * @brief Internal utility structures for the BlenderLoader. It also serves + * as master include file for the whole (internal) Blender subsystem. + */ +#ifndef INCLUDED_AI_BLEND_INTERMEDIATE_H +#define INCLUDED_AI_BLEND_INTERMEDIATE_H + +#include "BlenderLoader.h" +#include "BlenderDNA.h" +#include "BlenderScene.h" +#include <deque> +#include <assimp/material.h> + +struct aiTexture; + +namespace Assimp { +namespace Blender { + + // -------------------------------------------------------------------- + /** Mini smart-array to avoid pulling in even more boost stuff. usable with vector and deque */ + // -------------------------------------------------------------------- + template <template <typename,typename> class TCLASS, typename T> + struct TempArray { + typedef TCLASS< T*,std::allocator<T*> > mywrap; + + TempArray() { + } + + ~TempArray () { + for(T* elem : arr) { + delete elem; + } + } + + void dismiss() { + arr.clear(); + } + + mywrap* operator -> () { + return &arr; + } + + operator mywrap& () { + return arr; + } + + operator const mywrap& () const { + return arr; + } + + mywrap& get () { + return arr; + } + + const mywrap& get () const { + return arr; + } + + T* operator[] (size_t idx) const { + return arr[idx]; + } + + T*& operator[] (size_t idx) { + return arr[idx]; + } + + private: + // no copy semantics + void operator= (const TempArray&) { + } + + TempArray(const TempArray& /*arr*/) { + } + + private: + mywrap arr; + }; + +#ifdef _MSC_VER +# pragma warning(disable:4351) +#endif + + // As counter-intuitive as it may seem, a comparator must return false for equal values. + // The C++ standard defines and expects this behavior: true if lhs < rhs, false otherwise. + struct ObjectCompare { + bool operator() (const Object* left, const Object* right) const { + return ::strncmp(left->id.name, right->id.name, strlen( left->id.name ) ) < 0; + } + }; + + // When keeping objects in sets, sort them by their name. + typedef std::set<const Object*, ObjectCompare> ObjectSet; + + // -------------------------------------------------------------------- + /** ConversionData acts as intermediate storage location for + * the various ConvertXXX routines in BlenderImporter.*/ + // -------------------------------------------------------------------- + struct ConversionData + { + ConversionData(const FileDatabase& db) + : sentinel_cnt() + , next_texture() + , db(db) + {} + + // As counter-intuitive as it may seem, a comparator must return false for equal values. + // The C++ standard defines and expects this behavior: true if lhs < rhs, false otherwise. + struct ObjectCompare { + bool operator() (const Object* left, const Object* right) const { + return ::strncmp( left->id.name, right->id.name, strlen( left->id.name ) ) < 0; + } + }; + + ObjectSet objects; + + TempArray <std::vector, aiMesh> meshes; + TempArray <std::vector, aiCamera> cameras; + TempArray <std::vector, aiLight> lights; + TempArray <std::vector, aiMaterial> materials; + TempArray <std::vector, aiTexture> textures; + + // set of all materials referenced by at least one mesh in the scene + std::deque< std::shared_ptr< Material > > materials_raw; + + // counter to name sentinel textures inserted as substitutes for procedural textures. + unsigned int sentinel_cnt; + + // next texture ID for each texture type, respectively + unsigned int next_texture[aiTextureType_UNKNOWN+1]; + + // original file data + const FileDatabase& db; + }; +#ifdef _MSC_VER +# pragma warning(default:4351) +#endif + +// ------------------------------------------------------------------------------------------------ +inline const char* GetTextureTypeDisplayString(Tex::Type t) +{ + switch (t) { + case Tex::Type_CLOUDS : return "Clouds"; + case Tex::Type_WOOD : return "Wood"; + case Tex::Type_MARBLE : return "Marble"; + case Tex::Type_MAGIC : return "Magic"; + case Tex::Type_BLEND : return "Blend"; + case Tex::Type_STUCCI : return "Stucci"; + case Tex::Type_NOISE : return "Noise"; + case Tex::Type_PLUGIN : return "Plugin"; + case Tex::Type_MUSGRAVE : return "Musgrave"; + case Tex::Type_VORONOI : return "Voronoi"; + case Tex::Type_DISTNOISE : return "DistortedNoise"; + case Tex::Type_ENVMAP : return "EnvMap"; + case Tex::Type_IMAGE : return "Image"; + default: + break; + } + return "<Unknown>"; +} + +} // ! Blender +} // ! Assimp + +#endif // ! INCLUDED_AI_BLEND_INTERMEDIATE_H diff --git a/libs/assimp/code/AssetLib/Blender/BlenderLoader.cpp b/libs/assimp/code/AssetLib/Blender/BlenderLoader.cpp new file mode 100644 index 0000000..b3591b4 --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderLoader.cpp @@ -0,0 +1,1340 @@ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BlenderLoader.cpp + * @brief Implementation of the Blender3D importer class. + */ + +//#define ASSIMP_BUILD_NO_COMPRESSED_BLEND +// Uncomment this to disable support for (gzip)compressed .BLEND files + +#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER + +#include "BlenderBMesh.h" +#include "BlenderCustomData.h" +#include "BlenderIntermediate.h" +#include "BlenderModifier.h" +#include <assimp/StringUtils.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> + +#include <assimp/MemoryIOWrapper.h> +#include <assimp/StreamReader.h> +#include <assimp/StringComparison.h> + +#include <cctype> + +// zlib is needed for compressed blend files +#ifndef ASSIMP_BUILD_NO_COMPRESSED_BLEND +#include "Common/Compression.h" +/* #ifdef ASSIMP_BUILD_NO_OWN_ZLIB +# include <zlib.h> +# else +# include "../contrib/zlib/zlib.h" +# endif*/ +#endif + +namespace Assimp { + +template <> +const char *LogFunctions<BlenderImporter>::Prefix() { + static auto prefix = "BLEND: "; + return prefix; +} + +} // namespace Assimp + +using namespace Assimp; +using namespace Assimp::Blender; +using namespace Assimp::Formatter; + +static const aiImporterDesc blenderDesc = { + "Blender 3D Importer (http://www.blender3d.org)", + "", + "", + "No animation support yet", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 2, + 50, + "blend" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +BlenderImporter::BlenderImporter() : + modifier_cache(new BlenderModifierShowcase()) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +BlenderImporter::~BlenderImporter() { + delete modifier_cache; +} + +static const char * const Tokens[] = { "BLENDER" }; + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool BlenderImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + // note: this won't catch compressed files + static const char *tokens[] = { "<BLENDER", "blender" }; + + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Loader registry entry +const aiImporterDesc *BlenderImporter::GetInfo() const { + return &blenderDesc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties for the loader +void BlenderImporter::SetupProperties(const Importer * /*pImp*/) { + // nothing to be done for the moment +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void BlenderImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, IOSystem *pIOHandler) { +#ifndef ASSIMP_BUILD_NO_COMPRESSED_BLEND + std::vector<char> uncompressed; +#endif + + FileDatabase file; + std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb")); + if (!stream) { + ThrowException("Could not open file for reading"); + } + + char magic[8] = { 0 }; + stream->Read(magic, 7, 1); + if (strcmp(magic, Tokens[0])) { + // Check for presence of the gzip header. If yes, assume it is a + // compressed blend file and try uncompressing it, else fail. This is to + // avoid uncompressing random files which our loader might end up with. +#ifdef ASSIMP_BUILD_NO_COMPRESSED_BLEND + ThrowException("BLENDER magic bytes are missing, is this file compressed (Assimp was built without decompression support)?"); +#else + if (magic[0] != 0x1f || static_cast<uint8_t>(magic[1]) != 0x8b) { + ThrowException("BLENDER magic bytes are missing, couldn't find GZIP header either"); + } + + LogDebug("Found no BLENDER magic word but a GZIP header, might be a compressed file"); + if (magic[2] != 8) { + ThrowException("Unsupported GZIP compression method"); + } + + // http://www.gzip.org/zlib/rfc-gzip.html#header-trailer + stream->Seek(0L, aiOrigin_SET); + std::shared_ptr<StreamReaderLE> reader = std::shared_ptr<StreamReaderLE>(new StreamReaderLE(stream)); + + size_t total = 0; + Compression compression; + if (compression.open(Compression::Format::Binary, Compression::FlushMode::NoFlush, 16 + Compression::MaxWBits)) { + total = compression.decompress((unsigned char *)reader->GetPtr(), reader->GetRemainingSize(), uncompressed); + compression.close(); + } + + // replace the input stream with a memory stream + stream.reset(new MemoryIOStream(reinterpret_cast<uint8_t *>(uncompressed.data()), total)); + + // .. and retry + stream->Read(magic, 7, 1); + if (strcmp(magic, "BLENDER")) { + ThrowException("Found no BLENDER magic word in decompressed GZIP file"); + } +#endif + } + + file.i64bit = (stream->Read(magic, 1, 1), magic[0] == '-'); + file.little = (stream->Read(magic, 1, 1), magic[0] == 'v'); + + stream->Read(magic, 3, 1); + magic[3] = '\0'; + + LogInfo("Blender version is ", magic[0], ".", magic + 1, + " (64bit: ", file.i64bit ? "true" : "false", + ", little endian: ", file.little ? "true" : "false", ")"); + + ParseBlendFile(file, stream); + + Scene scene; + ExtractScene(scene, file); + + ConvertBlendFile(pScene, scene, file); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderImporter::ParseBlendFile(FileDatabase &out, std::shared_ptr<IOStream> stream) { + out.reader = std::make_shared<StreamReaderAny>(stream, out.little); + + DNAParser dna_reader(out); + const DNA *dna = nullptr; + + out.entries.reserve(128); + { // even small BLEND files tend to consist of many file blocks + SectionParser parser(*out.reader.get(), out.i64bit); + + // first parse the file in search for the DNA and insert all other sections into the database + while ((parser.Next(), 1)) { + const FileBlockHead &head = parser.GetCurrent(); + + if (head.id == "ENDB") { + break; // only valid end of the file + } else if (head.id == "DNA1") { + dna_reader.Parse(); + dna = &dna_reader.GetDNA(); + continue; + } + + out.entries.push_back(head); + } + } + if (!dna) { + ThrowException("SDNA not found"); + } + + std::sort(out.entries.begin(), out.entries.end()); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderImporter::ExtractScene(Scene &out, const FileDatabase &file) { + const FileBlockHead *block = nullptr; + std::map<std::string, size_t>::const_iterator it = file.dna.indices.find("Scene"); + if (it == file.dna.indices.end()) { + ThrowException("There is no `Scene` structure record"); + } + + const Structure &ss = file.dna.structures[(*it).second]; + + // we need a scene somewhere to start with. + for (const FileBlockHead &bl : file.entries) { + + // Fix: using the DNA index is more reliable to locate scenes + //if (bl.id == "SC") { + + if (bl.dna_index == (*it).second) { + block = &bl; + break; + } + } + + if (!block) { + ThrowException("There is not a single `Scene` record to load"); + } + + file.reader->SetCurrentPos(block->start); + ss.Convert(out, file); + +#ifndef ASSIMP_BUILD_BLENDER_NO_STATS + ASSIMP_LOG_INFO( + "(Stats) Fields read: ", file.stats().fields_read, + ", pointers resolved: ", file.stats().pointers_resolved, + ", cache hits: ", file.stats().cache_hits, + ", cached objects: ", file.stats().cached_objects); +#endif +} + +// ------------------------------------------------------------------------------------------------ +void BlenderImporter::ParseSubCollection(const Blender::Scene &in, aiNode *root, std::shared_ptr<Collection> collection, ConversionData &conv_data) { + + std::deque<Object *> root_objects; + // Count number of objects + for (std::shared_ptr<CollectionObject> cur = std::static_pointer_cast<CollectionObject>(collection->gobject.first); cur; cur = cur->next) { + if (cur->ob) { + root_objects.push_back(cur->ob); + } + } + std::deque<Collection *> root_children; + // Count number of child nodes + for (std::shared_ptr<CollectionChild> cur = std::static_pointer_cast<CollectionChild>(collection->children.first); cur; cur = cur->next) { + if (cur->collection) { + root_children.push_back(cur->collection.get()); + } + } + root->mNumChildren = static_cast<unsigned int>(root_objects.size() + root_children.size()); + root->mChildren = new aiNode *[root->mNumChildren](); + + for (unsigned int i = 0; i < static_cast<unsigned int>(root_objects.size()); ++i) { + root->mChildren[i] = ConvertNode(in, root_objects[i], conv_data, aiMatrix4x4()); + root->mChildren[i]->mParent = root; + } + + // For each subcollection create a new node to represent it + unsigned int iterator = static_cast<unsigned int>(root_objects.size()); + for (std::shared_ptr<CollectionChild> cur = std::static_pointer_cast<CollectionChild>(collection->children.first); cur; cur = cur->next) { + if (cur->collection) { + root->mChildren[iterator] = new aiNode(cur->collection->id.name + 2); // skip over the name prefix 'OB' + root->mChildren[iterator]->mParent = root; + ParseSubCollection(in, root->mChildren[iterator], cur->collection, conv_data); + } + iterator += 1; + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderImporter::ConvertBlendFile(aiScene *out, const Scene &in, const FileDatabase &file) { + ConversionData conv(file); + + aiNode *root = out->mRootNode = new aiNode("<BlenderRoot>"); + // Iterate over all objects directly under master_collection, + // If in.master_collection == null, then we're parsing something older. + if (in.master_collection) { + ParseSubCollection(in, root, in.master_collection, conv); + } else { + std::deque<const Object *> no_parents; + for (std::shared_ptr<Base> cur = std::static_pointer_cast<Base>(in.base.first); cur; cur = cur->next) { + if (cur->object) { + if (!cur->object->parent) { + no_parents.push_back(cur->object.get()); + } else { + conv.objects.insert(cur->object.get()); + } + } + } + for (std::shared_ptr<Base> cur = in.basact; cur; cur = cur->next) { + if (cur->object) { + if (cur->object->parent) { + conv.objects.insert(cur->object.get()); + } + } + } + + if (no_parents.empty()) { + ThrowException("Expected at least one object with no parent"); + } + + root->mNumChildren = static_cast<unsigned int>(no_parents.size()); + root->mChildren = new aiNode *[root->mNumChildren](); + for (unsigned int i = 0; i < root->mNumChildren; ++i) { + root->mChildren[i] = ConvertNode(in, no_parents[i], conv, aiMatrix4x4()); + root->mChildren[i]->mParent = root; + } + } + + BuildMaterials(conv); + + if (conv.meshes->size()) { + out->mMeshes = new aiMesh *[out->mNumMeshes = static_cast<unsigned int>(conv.meshes->size())]; + std::copy(conv.meshes->begin(), conv.meshes->end(), out->mMeshes); + conv.meshes.dismiss(); + } + + if (conv.lights->size()) { + out->mLights = new aiLight *[out->mNumLights = static_cast<unsigned int>(conv.lights->size())]; + std::copy(conv.lights->begin(), conv.lights->end(), out->mLights); + conv.lights.dismiss(); + } + + if (conv.cameras->size()) { + out->mCameras = new aiCamera *[out->mNumCameras = static_cast<unsigned int>(conv.cameras->size())]; + std::copy(conv.cameras->begin(), conv.cameras->end(), out->mCameras); + conv.cameras.dismiss(); + } + + if (conv.materials->size()) { + out->mMaterials = new aiMaterial *[out->mNumMaterials = static_cast<unsigned int>(conv.materials->size())]; + std::copy(conv.materials->begin(), conv.materials->end(), out->mMaterials); + conv.materials.dismiss(); + } + + if (conv.textures->size()) { + out->mTextures = new aiTexture *[out->mNumTextures = static_cast<unsigned int>(conv.textures->size())]; + std::copy(conv.textures->begin(), conv.textures->end(), out->mTextures); + conv.textures.dismiss(); + } + + // acknowledge that the scene might come out incomplete + // by Assimp's definition of `complete`: blender scenes + // can consist of thousands of cameras or lights with + // not a single mesh between them. + if (!out->mNumMeshes) { + out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderImporter::ResolveImage(aiMaterial *out, const Material *mat, const MTex *tex, const Image *img, ConversionData &conv_data) { + (void)mat; + (void)tex; + (void)conv_data; + aiString name; + + // check if the file contents are bundled with the BLEND file + if (img->packedfile) { + name.data[0] = '*'; + name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast<unsigned int>(MAXLEN - 1), static_cast<int32_t>(conv_data.textures->size())); + + conv_data.textures->push_back(new aiTexture()); + aiTexture *curTex = conv_data.textures->back(); + + // usually 'img->name' will be the original file name of the embedded textures, + // so we can extract the file extension from it. + const size_t nlen = strlen(img->name); + const char *s = img->name + nlen, *e = s; + while (s >= img->name && *s != '.') { + --s; + } + + curTex->achFormatHint[0] = s + 1 > e ? '\0' : (char)::tolower((unsigned char)s[1]); + curTex->achFormatHint[1] = s + 2 > e ? '\0' : (char)::tolower((unsigned char)s[2]); + curTex->achFormatHint[2] = s + 3 > e ? '\0' : (char)::tolower((unsigned char)s[3]); + curTex->achFormatHint[3] = '\0'; + + // tex->mHeight = 0; + curTex->mWidth = img->packedfile->size; + uint8_t *ch = new uint8_t[curTex->mWidth]; + + conv_data.db.reader->SetCurrentPos(static_cast<size_t>(img->packedfile->data->val)); + conv_data.db.reader->CopyAndAdvance(ch, curTex->mWidth); + + curTex->pcData = reinterpret_cast<aiTexel *>(ch); + + LogInfo("Reading embedded texture, original file was ", img->name); + } else { + name = aiString(img->name); + } + + aiTextureType texture_type = aiTextureType_UNKNOWN; + MTex::MapType map_type = tex->mapto; + + if (map_type & MTex::MapType_COL) + texture_type = aiTextureType_DIFFUSE; + else if (map_type & MTex::MapType_NORM) { + if (tex->tex->imaflag & Tex::ImageFlags_NORMALMAP) { + texture_type = aiTextureType_NORMALS; + } else { + texture_type = aiTextureType_HEIGHT; + } + out->AddProperty(&tex->norfac, 1, AI_MATKEY_BUMPSCALING); + } else if (map_type & MTex::MapType_COLSPEC) + texture_type = aiTextureType_SPECULAR; + else if (map_type & MTex::MapType_COLMIR) + texture_type = aiTextureType_REFLECTION; + //else if (map_type & MTex::MapType_REF) + else if (map_type & MTex::MapType_SPEC) + texture_type = aiTextureType_SHININESS; + else if (map_type & MTex::MapType_EMIT) + texture_type = aiTextureType_EMISSIVE; + //else if (map_type & MTex::MapType_ALPHA) + //else if (map_type & MTex::MapType_HAR) + //else if (map_type & MTex::MapType_RAYMIRR) + //else if (map_type & MTex::MapType_TRANSLU) + else if (map_type & MTex::MapType_AMB) + texture_type = aiTextureType_AMBIENT; + else if (map_type & MTex::MapType_DISPLACE) + texture_type = aiTextureType_DISPLACEMENT; + //else if (map_type & MTex::MapType_WARP) + + out->AddProperty(&name, AI_MATKEY_TEXTURE(texture_type, + conv_data.next_texture[texture_type]++)); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderImporter::AddSentinelTexture(aiMaterial *out, const Material *mat, const MTex *tex, ConversionData &conv_data) { + (void)mat; + (void)tex; + (void)conv_data; + + aiString name; + name.length = ai_snprintf(name.data, MAXLEN, "Procedural,num=%i,type=%s", conv_data.sentinel_cnt++, + GetTextureTypeDisplayString(tex->tex->type)); + out->AddProperty(&name, AI_MATKEY_TEXTURE_DIFFUSE( + conv_data.next_texture[aiTextureType_DIFFUSE]++)); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderImporter::ResolveTexture(aiMaterial *out, const Material *mat, const MTex *tex, ConversionData &conv_data) { + const Tex *rtex = tex->tex.get(); + if (!rtex || !rtex->type) { + return; + } + + // We can't support most of the texture types because they're mostly procedural. + // These are substituted by a dummy texture. + const char *dispnam = ""; + switch (rtex->type) { + // these are listed in blender's UI + case Tex::Type_CLOUDS: + case Tex::Type_WOOD: + case Tex::Type_MARBLE: + case Tex::Type_MAGIC: + case Tex::Type_BLEND: + case Tex::Type_STUCCI: + case Tex::Type_NOISE: + case Tex::Type_PLUGIN: + case Tex::Type_MUSGRAVE: + case Tex::Type_VORONOI: + case Tex::Type_DISTNOISE: + case Tex::Type_ENVMAP: + + // these do no appear in the UI, why? + case Tex::Type_POINTDENSITY: + case Tex::Type_VOXELDATA: + + LogWarn("Encountered a texture with an unsupported type: ", dispnam); + AddSentinelTexture(out, mat, tex, conv_data); + break; + + case Tex::Type_IMAGE: + if (!rtex->ima) { + LogError("A texture claims to be an Image, but no image reference is given"); + break; + } + ResolveImage(out, mat, tex, rtex->ima.get(), conv_data); + break; + + default: + ai_assert(false); + }; +} + +// ------------------------------------------------------------------------------------------------ +void BlenderImporter::BuildDefaultMaterial(Blender::ConversionData &conv_data) { + // add a default material if necessary + unsigned int index = static_cast<unsigned int>(-1); + for (aiMesh *mesh : conv_data.meshes.get()) { + if (mesh->mMaterialIndex == static_cast<unsigned int>(-1)) { + + if (index == static_cast<unsigned int>(-1)) { + // Setup a default material. + std::shared_ptr<Material> p(new Material()); + ai_assert(::strlen(AI_DEFAULT_MATERIAL_NAME) < sizeof(p->id.name) - 2); + strcpy(p->id.name + 2, AI_DEFAULT_MATERIAL_NAME); + + // Note: MSVC11 does not zero-initialize Material here, although it should. + // Thus all relevant fields should be explicitly initialized. We cannot add + // a default constructor to Material since the DNA codegen does not support + // parsing it. + p->r = p->g = p->b = 0.6f; + p->specr = p->specg = p->specb = 0.6f; + p->ambr = p->ambg = p->ambb = 0.0f; + p->mirr = p->mirg = p->mirb = 0.0f; + p->emit = 0.f; + p->alpha = 0.f; + p->har = 0; + + index = static_cast<unsigned int>(conv_data.materials_raw.size()); + conv_data.materials_raw.push_back(p); + LogInfo("Adding default material"); + } + mesh->mMaterialIndex = index; + } + } +} + +void BlenderImporter::AddBlendParams(aiMaterial *result, const Material *source) { + aiColor3D diffuseColor(source->r, source->g, source->b); + result->AddProperty(&diffuseColor, 1, "$mat.blend.diffuse.color", 0, 0); + + float diffuseIntensity = source->ref; + result->AddProperty(&diffuseIntensity, 1, "$mat.blend.diffuse.intensity", 0, 0); + + int diffuseShader = source->diff_shader; + result->AddProperty(&diffuseShader, 1, "$mat.blend.diffuse.shader", 0, 0); + + int diffuseRamp = 0; + result->AddProperty(&diffuseRamp, 1, "$mat.blend.diffuse.ramp", 0, 0); + + aiColor3D specularColor(source->specr, source->specg, source->specb); + result->AddProperty(&specularColor, 1, "$mat.blend.specular.color", 0, 0); + + float specularIntensity = source->spec; + result->AddProperty(&specularIntensity, 1, "$mat.blend.specular.intensity", 0, 0); + + int specularShader = source->spec_shader; + result->AddProperty(&specularShader, 1, "$mat.blend.specular.shader", 0, 0); + + int specularRamp = 0; + result->AddProperty(&specularRamp, 1, "$mat.blend.specular.ramp", 0, 0); + + int specularHardness = source->har; + result->AddProperty(&specularHardness, 1, "$mat.blend.specular.hardness", 0, 0); + + int transparencyUse = source->mode & MA_TRANSPARENCY ? 1 : 0; + result->AddProperty(&transparencyUse, 1, "$mat.blend.transparency.use", 0, 0); + + int transparencyMethod = source->mode & MA_RAYTRANSP ? 2 : (source->mode & MA_ZTRANSP ? 1 : 0); + result->AddProperty(&transparencyMethod, 1, "$mat.blend.transparency.method", 0, 0); + + float transparencyAlpha = source->alpha; + result->AddProperty(&transparencyAlpha, 1, "$mat.blend.transparency.alpha", 0, 0); + + float transparencySpecular = source->spectra; + result->AddProperty(&transparencySpecular, 1, "$mat.blend.transparency.specular", 0, 0); + + float transparencyFresnel = source->fresnel_tra; + result->AddProperty(&transparencyFresnel, 1, "$mat.blend.transparency.fresnel", 0, 0); + + float transparencyBlend = source->fresnel_tra_i; + result->AddProperty(&transparencyBlend, 1, "$mat.blend.transparency.blend", 0, 0); + + float transparencyIor = source->ang; + result->AddProperty(&transparencyIor, 1, "$mat.blend.transparency.ior", 0, 0); + + float transparencyFilter = source->filter; + result->AddProperty(&transparencyFilter, 1, "$mat.blend.transparency.filter", 0, 0); + + float transparencyFalloff = source->tx_falloff; + result->AddProperty(&transparencyFalloff, 1, "$mat.blend.transparency.falloff", 0, 0); + + float transparencyLimit = source->tx_limit; + result->AddProperty(&transparencyLimit, 1, "$mat.blend.transparency.limit", 0, 0); + + int transparencyDepth = source->ray_depth_tra; + result->AddProperty(&transparencyDepth, 1, "$mat.blend.transparency.depth", 0, 0); + + float transparencyGlossAmount = source->gloss_tra; + result->AddProperty(&transparencyGlossAmount, 1, "$mat.blend.transparency.glossAmount", 0, 0); + + float transparencyGlossThreshold = source->adapt_thresh_tra; + result->AddProperty(&transparencyGlossThreshold, 1, "$mat.blend.transparency.glossThreshold", 0, 0); + + int transparencyGlossSamples = source->samp_gloss_tra; + result->AddProperty(&transparencyGlossSamples, 1, "$mat.blend.transparency.glossSamples", 0, 0); + + int mirrorUse = source->mode & MA_RAYMIRROR ? 1 : 0; + result->AddProperty(&mirrorUse, 1, "$mat.blend.mirror.use", 0, 0); + + float mirrorReflectivity = source->ray_mirror; + result->AddProperty(&mirrorReflectivity, 1, "$mat.blend.mirror.reflectivity", 0, 0); + + aiColor3D mirrorColor(source->mirr, source->mirg, source->mirb); + result->AddProperty(&mirrorColor, 1, "$mat.blend.mirror.color", 0, 0); + + float mirrorFresnel = source->fresnel_mir; + result->AddProperty(&mirrorFresnel, 1, "$mat.blend.mirror.fresnel", 0, 0); + + float mirrorBlend = source->fresnel_mir_i; + result->AddProperty(&mirrorBlend, 1, "$mat.blend.mirror.blend", 0, 0); + + int mirrorDepth = source->ray_depth; + result->AddProperty(&mirrorDepth, 1, "$mat.blend.mirror.depth", 0, 0); + + float mirrorMaxDist = source->dist_mir; + result->AddProperty(&mirrorMaxDist, 1, "$mat.blend.mirror.maxDist", 0, 0); + + int mirrorFadeTo = source->fadeto_mir; + result->AddProperty(&mirrorFadeTo, 1, "$mat.blend.mirror.fadeTo", 0, 0); + + float mirrorGlossAmount = source->gloss_mir; + result->AddProperty(&mirrorGlossAmount, 1, "$mat.blend.mirror.glossAmount", 0, 0); + + float mirrorGlossThreshold = source->adapt_thresh_mir; + result->AddProperty(&mirrorGlossThreshold, 1, "$mat.blend.mirror.glossThreshold", 0, 0); + + int mirrorGlossSamples = source->samp_gloss_mir; + result->AddProperty(&mirrorGlossSamples, 1, "$mat.blend.mirror.glossSamples", 0, 0); + + float mirrorGlossAnisotropic = source->aniso_gloss_mir; + result->AddProperty(&mirrorGlossAnisotropic, 1, "$mat.blend.mirror.glossAnisotropic", 0, 0); +} + +void BlenderImporter::BuildMaterials(ConversionData &conv_data) { + conv_data.materials->reserve(conv_data.materials_raw.size()); + + BuildDefaultMaterial(conv_data); + + for (const std::shared_ptr<Material> &mat : conv_data.materials_raw) { + + // reset per material global counters + for (size_t i = 0; i < sizeof(conv_data.next_texture) / sizeof(conv_data.next_texture[0]); ++i) { + conv_data.next_texture[i] = 0; + } + + aiMaterial *mout = new aiMaterial(); + conv_data.materials->push_back(mout); + // For any new material field handled here, the default material above must be updated with an appropriate default value. + + // set material name + aiString name = aiString(mat->id.name + 2); // skip over the name prefix 'MA' + mout->AddProperty(&name, AI_MATKEY_NAME); + + // basic material colors + aiColor3D col(mat->r, mat->g, mat->b); + if (mat->r || mat->g || mat->b) { + + // Usually, zero diffuse color means no diffuse color at all in the equation. + // So we omit this member to express this intent. + mout->AddProperty(&col, 1, AI_MATKEY_COLOR_DIFFUSE); + + if (mat->emit) { + aiColor3D emit_col(mat->emit * mat->r, mat->emit * mat->g, mat->emit * mat->b); + mout->AddProperty(&emit_col, 1, AI_MATKEY_COLOR_EMISSIVE); + } + } + + col = aiColor3D(mat->specr, mat->specg, mat->specb); + mout->AddProperty(&col, 1, AI_MATKEY_COLOR_SPECULAR); + + // is hardness/shininess set? + if (mat->har) { + const float har = mat->har; + mout->AddProperty(&har, 1, AI_MATKEY_SHININESS); + } + + col = aiColor3D(mat->ambr, mat->ambg, mat->ambb); + mout->AddProperty(&col, 1, AI_MATKEY_COLOR_AMBIENT); + + // is mirror enabled? + if (mat->mode & MA_RAYMIRROR) { + const float ray_mirror = mat->ray_mirror; + mout->AddProperty(&ray_mirror, 1, AI_MATKEY_REFLECTIVITY); + } + + col = aiColor3D(mat->mirr, mat->mirg, mat->mirb); + mout->AddProperty(&col, 1, AI_MATKEY_COLOR_REFLECTIVE); + + for (size_t i = 0; i < sizeof(mat->mtex) / sizeof(mat->mtex[0]); ++i) { + if (!mat->mtex[i]) { + continue; + } + + ResolveTexture(mout, mat.get(), mat->mtex[i].get(), conv_data); + } + + AddBlendParams(mout, mat.get()); + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderImporter::CheckActualType(const ElemBase *dt, const char *check) { + ai_assert(dt); + if (strcmp(dt->dna_type, check)) { + ThrowException("Expected object at ", std::hex, dt, " to be of type `", check, + "`, but it claims to be a `", dt->dna_type, "`instead"); + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderImporter::NotSupportedObjectType(const Object *obj, const char *type) { + LogWarn("Object `", obj->id.name, "` - type is unsupported: `", type, "`, skipping"); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderImporter::ConvertMesh(const Scene & /*in*/, const Object * /*obj*/, const Mesh *mesh, + ConversionData &conv_data, TempArray<std::vector, aiMesh> &temp) { + // TODO: Resolve various problems with BMesh triangulation before re-enabling. + // See issues #400, #373, #318 #315 and #132. +#if defined(TODO_FIX_BMESH_CONVERSION) + BlenderBMeshConverter BMeshConverter(mesh); + if (BMeshConverter.ContainsBMesh()) { + mesh = BMeshConverter.TriangulateBMesh(); + } +#endif + + typedef std::pair<const int, size_t> MyPair; + if ((!mesh->totface && !mesh->totloop) || !mesh->totvert) { + return; + } + + // some sanity checks + if (static_cast<size_t>(mesh->totface) > mesh->mface.size()) { + ThrowException("Number of faces is larger than the corresponding array"); + } + + if (static_cast<size_t>(mesh->totvert) > mesh->mvert.size()) { + ThrowException("Number of vertices is larger than the corresponding array"); + } + + if (static_cast<size_t>(mesh->totloop) > mesh->mloop.size()) { + ThrowException("Number of vertices is larger than the corresponding array"); + } + + // collect per-submesh numbers + std::map<int, size_t> per_mat; + std::map<int, size_t> per_mat_verts; + for (int i = 0; i < mesh->totface; ++i) { + + const MFace &mf = mesh->mface[i]; + per_mat[mf.mat_nr]++; + per_mat_verts[mf.mat_nr] += mf.v4 ? 4 : 3; + } + + for (int i = 0; i < mesh->totpoly; ++i) { + const MPoly &mp = mesh->mpoly[i]; + per_mat[mp.mat_nr]++; + per_mat_verts[mp.mat_nr] += mp.totloop; + } + + // ... and allocate the corresponding meshes + const size_t old = temp->size(); + temp->reserve(temp->size() + per_mat.size()); + + std::map<size_t, size_t> mat_num_to_mesh_idx; + for (MyPair &it : per_mat) { + + mat_num_to_mesh_idx[it.first] = temp->size(); + temp->push_back(new aiMesh()); + + aiMesh *out = temp->back(); + out->mVertices = new aiVector3D[per_mat_verts[it.first]]; + out->mNormals = new aiVector3D[per_mat_verts[it.first]]; + + //out->mNumFaces = 0 + //out->mNumVertices = 0 + out->mFaces = new aiFace[it.second](); + + // all sub-meshes created from this mesh are named equally. this allows + // curious users to recover the original adjacency. + out->mName = aiString(mesh->id.name + 2); + // skip over the name prefix 'ME' + + // resolve the material reference and add this material to the set of + // output materials. The (temporary) material index is the index + // of the material entry within the list of resolved materials. + if (mesh->mat) { + + if (static_cast<size_t>(it.first) >= mesh->mat.size()) { + ThrowException("Material index is out of range"); + } + + std::shared_ptr<Material> mat = mesh->mat[it.first]; + const std::deque<std::shared_ptr<Material>>::iterator has = std::find( + conv_data.materials_raw.begin(), + conv_data.materials_raw.end(), mat); + + if (has != conv_data.materials_raw.end()) { + out->mMaterialIndex = static_cast<unsigned int>(std::distance(conv_data.materials_raw.begin(), has)); + } else { + out->mMaterialIndex = static_cast<unsigned int>(conv_data.materials_raw.size()); + conv_data.materials_raw.push_back(mat); + } + } else + out->mMaterialIndex = static_cast<unsigned int>(-1); + } + + for (int i = 0; i < mesh->totface; ++i) { + + const MFace &mf = mesh->mface[i]; + + aiMesh *const out = temp[mat_num_to_mesh_idx[mf.mat_nr]]; + aiFace &f = out->mFaces[out->mNumFaces++]; + + f.mIndices = new unsigned int[f.mNumIndices = mf.v4 ? 4 : 3]; + aiVector3D *vo = out->mVertices + out->mNumVertices; + aiVector3D *vn = out->mNormals + out->mNumVertices; + + // XXX we can't fold this easily, because we are restricted + // to the member names from the BLEND file (v1,v2,v3,v4) + // which are assigned by the genblenddna.py script and + // cannot be changed without breaking the entire + // import process. + + if (mf.v1 >= mesh->totvert) { + ThrowException("Vertex index v1 out of range"); + } + const MVert *v = &mesh->mvert[mf.v1]; + vo->x = v->co[0]; + vo->y = v->co[1]; + vo->z = v->co[2]; + vn->x = v->no[0]; + vn->y = v->no[1]; + vn->z = v->no[2]; + f.mIndices[0] = out->mNumVertices++; + ++vo; + ++vn; + + // if (f.mNumIndices >= 2) { + if (mf.v2 >= mesh->totvert) { + ThrowException("Vertex index v2 out of range"); + } + v = &mesh->mvert[mf.v2]; + vo->x = v->co[0]; + vo->y = v->co[1]; + vo->z = v->co[2]; + vn->x = v->no[0]; + vn->y = v->no[1]; + vn->z = v->no[2]; + f.mIndices[1] = out->mNumVertices++; + ++vo; + ++vn; + + if (mf.v3 >= mesh->totvert) { + ThrowException("Vertex index v3 out of range"); + } + // if (f.mNumIndices >= 3) { + v = &mesh->mvert[mf.v3]; + vo->x = v->co[0]; + vo->y = v->co[1]; + vo->z = v->co[2]; + vn->x = v->no[0]; + vn->y = v->no[1]; + vn->z = v->no[2]; + f.mIndices[2] = out->mNumVertices++; + ++vo; + ++vn; + + if (mf.v4 >= mesh->totvert) { + ThrowException("Vertex index v4 out of range"); + } + // if (f.mNumIndices >= 4) { + if (mf.v4) { + v = &mesh->mvert[mf.v4]; + vo->x = v->co[0]; + vo->y = v->co[1]; + vo->z = v->co[2]; + vn->x = v->no[0]; + vn->y = v->no[1]; + vn->z = v->no[2]; + f.mIndices[3] = out->mNumVertices++; + ++vo; + ++vn; + + out->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + } else + out->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + + // } + // } + // } + } + + for (int i = 0; i < mesh->totpoly; ++i) { + + const MPoly &mf = mesh->mpoly[i]; + + aiMesh *const out = temp[mat_num_to_mesh_idx[mf.mat_nr]]; + aiFace &f = out->mFaces[out->mNumFaces++]; + + f.mIndices = new unsigned int[f.mNumIndices = mf.totloop]; + aiVector3D *vo = out->mVertices + out->mNumVertices; + aiVector3D *vn = out->mNormals + out->mNumVertices; + + // XXX we can't fold this easily, because we are restricted + // to the member names from the BLEND file (v1,v2,v3,v4) + // which are assigned by the genblenddna.py script and + // cannot be changed without breaking the entire + // import process. + for (int j = 0; j < mf.totloop; ++j) { + const MLoop &loop = mesh->mloop[mf.loopstart + j]; + + if (loop.v >= mesh->totvert) { + ThrowException("Vertex index out of range"); + } + + const MVert &v = mesh->mvert[loop.v]; + + vo->x = v.co[0]; + vo->y = v.co[1]; + vo->z = v.co[2]; + vn->x = v.no[0]; + vn->y = v.no[1]; + vn->z = v.no[2]; + f.mIndices[j] = out->mNumVertices++; + + ++vo; + ++vn; + } + if (mf.totloop == 3) { + out->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + } else { + out->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + } + } + + // TODO should we create the TextureUVMapping map in Convert<Material> to prevent redundant processing? + + // create texture <-> uvname mapping for all materials + // key is texture number, value is data * + typedef std::map<uint32_t, const MLoopUV *> TextureUVMapping; + // key is material number, value is the TextureUVMapping for the material + typedef std::map<uint32_t, TextureUVMapping> MaterialTextureUVMappings; + MaterialTextureUVMappings matTexUvMappings; + const uint32_t maxMat = static_cast<const uint32_t>(mesh->mat.size()); + for (uint32_t m = 0; m < maxMat; ++m) { + // get material by index + const std::shared_ptr<Material> pMat = mesh->mat[m]; + TextureUVMapping texuv; + const uint32_t maxTex = sizeof(pMat->mtex) / sizeof(pMat->mtex[0]); + for (uint32_t t = 0; t < maxTex; ++t) { + if (pMat->mtex[t] && pMat->mtex[t]->uvname[0]) { + // get the CustomData layer for given uvname and correct type + const ElemBase *pLoop = getCustomDataLayerData(mesh->ldata, CD_MLOOPUV, pMat->mtex[t]->uvname); + if (pLoop) { + texuv.insert(std::make_pair(t, dynamic_cast<const MLoopUV *>(pLoop))); + } + } + } + if (texuv.size()) { + matTexUvMappings.insert(std::make_pair(m, texuv)); + } + } + + // collect texture coordinates, they're stored in a separate per-face buffer + if (mesh->mtface || mesh->mloopuv) { + if (mesh->totface > static_cast<int>(mesh->mtface.size())) { + ThrowException("Number of UV faces is larger than the corresponding UV face array (#1)"); + } + for (std::vector<aiMesh *>::iterator it = temp->begin() + old; it != temp->end(); ++it) { + ai_assert(0 != (*it)->mNumVertices); + ai_assert(0 != (*it)->mNumFaces); + const auto itMatTexUvMapping = matTexUvMappings.find((*it)->mMaterialIndex); + if (itMatTexUvMapping == matTexUvMappings.end()) { + // default behaviour like before + (*it)->mTextureCoords[0] = new aiVector3D[(*it)->mNumVertices]; + } else { + // create texture coords for every mapped tex + for (uint32_t i = 0; i < itMatTexUvMapping->second.size(); ++i) { + (*it)->mTextureCoords[i] = new aiVector3D[(*it)->mNumVertices]; + } + } + (*it)->mNumFaces = (*it)->mNumVertices = 0; + } + + for (int i = 0; i < mesh->totface; ++i) { + const MTFace *v = &mesh->mtface[i]; + + aiMesh *const out = temp[mat_num_to_mesh_idx[mesh->mface[i].mat_nr]]; + const aiFace &f = out->mFaces[out->mNumFaces++]; + + aiVector3D *vo = &out->mTextureCoords[0][out->mNumVertices]; + for (unsigned int j = 0; j < f.mNumIndices; ++j, ++vo, ++out->mNumVertices) { + vo->x = v->uv[j][0]; + vo->y = v->uv[j][1]; + } + } + + for (int i = 0; i < mesh->totpoly; ++i) { + const MPoly &v = mesh->mpoly[i]; + aiMesh *const out = temp[mat_num_to_mesh_idx[v.mat_nr]]; + const aiFace &f = out->mFaces[out->mNumFaces++]; + + const auto itMatTexUvMapping = matTexUvMappings.find(v.mat_nr); + if (itMatTexUvMapping == matTexUvMappings.end()) { + // old behavior + aiVector3D *vo = &out->mTextureCoords[0][out->mNumVertices]; + for (unsigned int j = 0; j < f.mNumIndices; ++j, ++vo, ++out->mNumVertices) { + const MLoopUV &uv = mesh->mloopuv[v.loopstart + j]; + vo->x = uv.uv[0]; + vo->y = uv.uv[1]; + } + } else { + // create textureCoords for every mapped tex + for (uint32_t m = 0; m < itMatTexUvMapping->second.size(); ++m) { + const MLoopUV *tm = itMatTexUvMapping->second[m]; + aiVector3D *vo = &out->mTextureCoords[m][out->mNumVertices]; + uint32_t j = 0; + for (; j < f.mNumIndices; ++j, ++vo) { + const MLoopUV &uv = tm[v.loopstart + j]; + vo->x = uv.uv[0]; + vo->y = uv.uv[1]; + } + // only update written mNumVertices in last loop + // TODO why must the numVertices be incremented here? + if (m == itMatTexUvMapping->second.size() - 1) { + out->mNumVertices += j; + } + } + } + } + } + + // collect texture coordinates, old-style (marked as deprecated in current blender sources) + if (mesh->tface) { + if (mesh->totface > static_cast<int>(mesh->tface.size())) { + ThrowException("Number of faces is larger than the corresponding UV face array (#2)"); + } + for (std::vector<aiMesh *>::iterator it = temp->begin() + old; it != temp->end(); ++it) { + ai_assert(0 != (*it)->mNumVertices); + ai_assert(0 != (*it)->mNumFaces); + + (*it)->mTextureCoords[0] = new aiVector3D[(*it)->mNumVertices]; + (*it)->mNumFaces = (*it)->mNumVertices = 0; + } + + for (int i = 0; i < mesh->totface; ++i) { + const TFace *v = &mesh->tface[i]; + + aiMesh *const out = temp[mat_num_to_mesh_idx[mesh->mface[i].mat_nr]]; + const aiFace &f = out->mFaces[out->mNumFaces++]; + + aiVector3D *vo = &out->mTextureCoords[0][out->mNumVertices]; + for (unsigned int j = 0; j < f.mNumIndices; ++j, ++vo, ++out->mNumVertices) { + vo->x = v->uv[j][0]; + vo->y = v->uv[j][1]; + } + } + } + + // collect vertex colors, stored separately as well + if (mesh->mcol || mesh->mloopcol) { + if (mesh->totface > static_cast<int>((mesh->mcol.size() / 4))) { + ThrowException("Number of faces is larger than the corresponding color face array"); + } + for (std::vector<aiMesh *>::iterator it = temp->begin() + old; it != temp->end(); ++it) { + ai_assert(0 != (*it)->mNumVertices); + ai_assert(0 != (*it)->mNumFaces); + + (*it)->mColors[0] = new aiColor4D[(*it)->mNumVertices]; + (*it)->mNumFaces = (*it)->mNumVertices = 0; + } + + for (int i = 0; i < mesh->totface; ++i) { + + aiMesh *const out = temp[mat_num_to_mesh_idx[mesh->mface[i].mat_nr]]; + const aiFace &f = out->mFaces[out->mNumFaces++]; + + aiColor4D *vo = &out->mColors[0][out->mNumVertices]; + for (unsigned int n = 0; n < f.mNumIndices; ++n, ++vo, ++out->mNumVertices) { + const MCol *col = &mesh->mcol[(i << 2) + n]; + + vo->r = col->r; + vo->g = col->g; + vo->b = col->b; + vo->a = col->a; + } + for (unsigned int n = f.mNumIndices; n < 4; ++n) + ; + } + + for (int i = 0; i < mesh->totpoly; ++i) { + const MPoly &v = mesh->mpoly[i]; + aiMesh *const out = temp[mat_num_to_mesh_idx[v.mat_nr]]; + const aiFace &f = out->mFaces[out->mNumFaces++]; + + aiColor4D *vo = &out->mColors[0][out->mNumVertices]; + const ai_real scaleZeroToOne = 1.f / 255.f; + for (unsigned int j = 0; j < f.mNumIndices; ++j, ++vo, ++out->mNumVertices) { + const MLoopCol &col = mesh->mloopcol[v.loopstart + j]; + vo->r = ai_real(col.r) * scaleZeroToOne; + vo->g = ai_real(col.g) * scaleZeroToOne; + vo->b = ai_real(col.b) * scaleZeroToOne; + vo->a = ai_real(col.a) * scaleZeroToOne; + } + } + } + + return; +} + +// ------------------------------------------------------------------------------------------------ +aiCamera *BlenderImporter::ConvertCamera(const Scene & /*in*/, const Object *obj, const Camera *cam, ConversionData & /*conv_data*/) { + std::unique_ptr<aiCamera> out(new aiCamera()); + out->mName = obj->id.name + 2; + out->mPosition = aiVector3D(0.f, 0.f, 0.f); + out->mUp = aiVector3D(0.f, 1.f, 0.f); + out->mLookAt = aiVector3D(0.f, 0.f, -1.f); + if (cam->sensor_x && cam->lens) { + out->mHorizontalFOV = 2.f * std::atan2(cam->sensor_x, 2.f * cam->lens); + } + out->mClipPlaneNear = cam->clipsta; + out->mClipPlaneFar = cam->clipend; + + return out.release(); +} + +// ------------------------------------------------------------------------------------------------ +aiLight *BlenderImporter::ConvertLight(const Scene & /*in*/, const Object *obj, const Lamp *lamp, ConversionData & /*conv_data*/) { + std::unique_ptr<aiLight> out(new aiLight()); + out->mName = obj->id.name + 2; + + switch (lamp->type) { + case Lamp::Type_Local: + out->mType = aiLightSource_POINT; + break; + case Lamp::Type_Spot: + out->mType = aiLightSource_SPOT; + + // blender orients directional lights as facing toward -z + out->mDirection = aiVector3D(0.f, 0.f, -1.f); + out->mUp = aiVector3D(0.f, 1.f, 0.f); + + out->mAngleInnerCone = lamp->spotsize * (1.0f - lamp->spotblend); + out->mAngleOuterCone = lamp->spotsize; + break; + case Lamp::Type_Sun: + out->mType = aiLightSource_DIRECTIONAL; + + // blender orients directional lights as facing toward -z + out->mDirection = aiVector3D(0.f, 0.f, -1.f); + out->mUp = aiVector3D(0.f, 1.f, 0.f); + break; + + case Lamp::Type_Area: + out->mType = aiLightSource_AREA; + + if (lamp->area_shape == 0) { + out->mSize = aiVector2D(lamp->area_size, lamp->area_size); + } else { + out->mSize = aiVector2D(lamp->area_size, lamp->area_sizey); + } + + // blender orients directional lights as facing toward -z + out->mDirection = aiVector3D(0.f, 0.f, -1.f); + out->mUp = aiVector3D(0.f, 1.f, 0.f); + break; + + default: + break; + } + + out->mColorAmbient = aiColor3D(lamp->r, lamp->g, lamp->b) * lamp->energy; + out->mColorSpecular = aiColor3D(lamp->r, lamp->g, lamp->b) * lamp->energy; + out->mColorDiffuse = aiColor3D(lamp->r, lamp->g, lamp->b) * lamp->energy; + + // If default values are supplied, compute the coefficients from light's max distance + // Read this: https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/ + // + if (lamp->constant_coefficient == 1.0f && lamp->linear_coefficient == 0.0f && lamp->quadratic_coefficient == 0.0f && lamp->dist > 0.0f) { + out->mAttenuationConstant = 1.0f; + out->mAttenuationLinear = 2.0f / lamp->dist; + out->mAttenuationQuadratic = 1.0f / (lamp->dist * lamp->dist); + } else { + out->mAttenuationConstant = lamp->constant_coefficient; + out->mAttenuationLinear = lamp->linear_coefficient; + out->mAttenuationQuadratic = lamp->quadratic_coefficient; + } + + return out.release(); +} + +// ------------------------------------------------------------------------------------------------ +aiNode *BlenderImporter::ConvertNode(const Scene &in, const Object *obj, ConversionData &conv_data, const aiMatrix4x4 &parentTransform) { + std::deque<const Object *> children; + for (ObjectSet::iterator it = conv_data.objects.begin(); it != conv_data.objects.end();) { + const Object *object = *it; + if (object->parent == obj) { + children.push_back(object); + + conv_data.objects.erase(it++); + continue; + } + ++it; + } + + std::unique_ptr<aiNode> node(new aiNode(obj->id.name + 2)); // skip over the name prefix 'OB' + if (obj->data) { + switch (obj->type) { + case Object ::Type_EMPTY: + break; // do nothing + + // supported object types + case Object ::Type_MESH: { + const size_t old = conv_data.meshes->size(); + + CheckActualType(obj->data.get(), "Mesh"); + ConvertMesh(in, obj, static_cast<const Mesh *>(obj->data.get()), conv_data, conv_data.meshes); + + if (conv_data.meshes->size() > old) { + node->mMeshes = new unsigned int[node->mNumMeshes = static_cast<unsigned int>(conv_data.meshes->size() - old)]; + for (unsigned int i = 0; i < node->mNumMeshes; ++i) { + node->mMeshes[i] = static_cast<unsigned int>(i + old); + } + } + } break; + case Object ::Type_LAMP: { + CheckActualType(obj->data.get(), "Lamp"); + aiLight *mesh = ConvertLight(in, obj, static_cast<const Lamp *>(obj->data.get()), conv_data); + + if (mesh) { + conv_data.lights->push_back(mesh); + } + } break; + case Object ::Type_CAMERA: { + CheckActualType(obj->data.get(), "Camera"); + aiCamera *mesh = ConvertCamera(in, obj, static_cast<const Camera *>(obj->data.get()), conv_data); + + if (mesh) { + conv_data.cameras->push_back(mesh); + } + } break; + + // unsupported object types / log, but do not break + case Object ::Type_CURVE: + NotSupportedObjectType(obj, "Curve"); + break; + case Object ::Type_SURF: + NotSupportedObjectType(obj, "Surface"); + break; + case Object ::Type_FONT: + NotSupportedObjectType(obj, "Font"); + break; + case Object ::Type_MBALL: + NotSupportedObjectType(obj, "MetaBall"); + break; + case Object ::Type_WAVE: + NotSupportedObjectType(obj, "Wave"); + break; + case Object ::Type_LATTICE: + NotSupportedObjectType(obj, "Lattice"); + break; + + // invalid or unknown type + default: + break; + } + } + + for (unsigned int x = 0; x < 4; ++x) { + for (unsigned int y = 0; y < 4; ++y) { + node->mTransformation[y][x] = obj->obmat[x][y]; + } + } + + aiMatrix4x4 m = parentTransform; + m = m.Inverse(); + + node->mTransformation = m * node->mTransformation; + + if (children.size()) { + node->mNumChildren = static_cast<unsigned int>(children.size()); + aiNode **nd = node->mChildren = new aiNode *[node->mNumChildren](); + for (const Object *nobj : children) { + *nd = ConvertNode(in, nobj, conv_data, node->mTransformation * parentTransform); + (*nd++)->mParent = node.get(); + } + } + + // apply modifiers + modifier_cache->ApplyModifiers(*node, conv_data, in, *obj); + + return node.release(); +} + +#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER diff --git a/libs/assimp/code/AssetLib/Blender/BlenderLoader.h b/libs/assimp/code/AssetLib/Blender/BlenderLoader.h new file mode 100644 index 0000000..0299ae3 --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderLoader.h @@ -0,0 +1,198 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BlenderLoader.h + * @brief Declaration of the Blender 3D (*.blend) importer class. + */ +#pragma once +#ifndef INCLUDED_AI_BLEND_LOADER_H +#define INCLUDED_AI_BLEND_LOADER_H + +#include <assimp/BaseImporter.h> +#include <assimp/LogAux.h> +#include <memory> + +struct aiNode; +struct aiMesh; +struct aiLight; +struct aiCamera; +struct aiMaterial; + +namespace Assimp { + +// TinyFormatter.h +namespace Formatter { + +template <typename T, typename TR, typename A> +class basic_formatter; + +typedef class basic_formatter<char, std::char_traits<char>, std::allocator<char>> format; + +} // namespace Formatter + +// BlenderDNA.h +namespace Blender { +class FileDatabase; +struct ElemBase; +} // namespace Blender + +// BlenderScene.h +namespace Blender { +struct Scene; +struct Object; +struct Collection; +struct Mesh; +struct Camera; +struct Lamp; +struct MTex; +struct Image; +struct Material; +} // namespace Blender + +// BlenderIntermediate.h +namespace Blender { +struct ConversionData; +template <template <typename, typename> class TCLASS, typename T> +struct TempArray; +} // namespace Blender + +// BlenderModifier.h +namespace Blender { +class BlenderModifierShowcase; +class BlenderModifier; +} // namespace Blender + +// ------------------------------------------------------------------------------------------- +/** Load blenders official binary format. The actual file structure (the `DNA` how they + * call it is outsourced to BlenderDNA.cpp/BlenderDNA.h. This class only performs the + * conversion from intermediate format to aiScene. */ +// ------------------------------------------------------------------------------------------- +class BlenderImporter : public BaseImporter, public LogFunctions<BlenderImporter> { +public: + BlenderImporter(); + ~BlenderImporter() override; + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + +protected: + const aiImporterDesc *GetInfo() const override; + void SetupProperties(const Importer *pImp) override; + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + void ParseBlendFile(Blender::FileDatabase &out, std::shared_ptr<IOStream> stream); + void ExtractScene(Blender::Scene &out, const Blender::FileDatabase &file); + void ParseSubCollection(const Blender::Scene &in, aiNode *root, std::shared_ptr<Blender::Collection> collection, Blender::ConversionData &conv_data); + void ConvertBlendFile(aiScene *out, const Blender::Scene &in, const Blender::FileDatabase &file); + +private: + aiNode *ConvertNode(const Blender::Scene &in, + const Blender::Object *obj, + Blender::ConversionData &conv_info, + const aiMatrix4x4 &parentTransform); + + // -------------------- + void ConvertMesh(const Blender::Scene &in, + const Blender::Object *obj, + const Blender::Mesh *mesh, + Blender::ConversionData &conv_data, + Blender::TempArray<std::vector, aiMesh> &temp); + + // -------------------- + aiLight *ConvertLight(const Blender::Scene &in, + const Blender::Object *obj, + const Blender::Lamp *mesh, + Blender::ConversionData &conv_data); + + // -------------------- + aiCamera *ConvertCamera(const Blender::Scene &in, + const Blender::Object *obj, + const Blender::Camera *mesh, + Blender::ConversionData &conv_data); + + // -------------------- + void BuildDefaultMaterial( + Blender::ConversionData &conv_data); + + // -------------------- + void AddBlendParams( + aiMaterial *result, + const Blender::Material *source); + + // -------------------- + void BuildMaterials( + Blender::ConversionData &conv_data); + + // -------------------- + void ResolveTexture( + aiMaterial *out, + const Blender::Material *mat, + const Blender::MTex *tex, + Blender::ConversionData &conv_data); + + // -------------------- + void ResolveImage( + aiMaterial *out, + const Blender::Material *mat, + const Blender::MTex *tex, + const Blender::Image *img, + Blender::ConversionData &conv_data); + + // -------------------- + void AddSentinelTexture( + aiMaterial *out, + const Blender::Material *mat, + const Blender::MTex *tex, + Blender::ConversionData &conv_data); + +private: // static stuff, mostly logging and error reporting. + // -------------------- + static void CheckActualType(const Blender::ElemBase *dt, + const char *check); + + // -------------------- + static void NotSupportedObjectType(const Blender::Object *obj, + const char *type); + +private: + Blender::BlenderModifierShowcase *modifier_cache; + +}; // !class BlenderImporter + +} // end of namespace Assimp +#endif // AI_UNREALIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/Blender/BlenderModifier.cpp b/libs/assimp/code/AssetLib/Blender/BlenderModifier.cpp new file mode 100644 index 0000000..d2b393d --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderModifier.cpp @@ -0,0 +1,299 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BlenderModifier.cpp + * @brief Implementation of some blender modifiers (i.e subdivision, mirror). + */ + +#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER + +#include "BlenderModifier.h" +#include <assimp/SceneCombiner.h> +#include <assimp/Subdivision.h> +#include <assimp/scene.h> +#include <memory> + +#include <functional> + +using namespace Assimp; +using namespace Assimp::Blender; + +template <typename T> +BlenderModifier *god() { + return new T(); +} + +// add all available modifiers here +typedef BlenderModifier *(*fpCreateModifier)(); +static const fpCreateModifier creators[] = { + &god<BlenderModifier_Mirror>, + &god<BlenderModifier_Subdivision>, + + nullptr // sentinel +}; + +// ------------------------------------------------------------------------------------------------ +struct SharedModifierData : ElemBase { + ModifierData modifier; +}; + +// ------------------------------------------------------------------------------------------------ +void BlenderModifierShowcase::ApplyModifiers(aiNode &out, ConversionData &conv_data, const Scene &in, const Object &orig_object) { + size_t cnt = 0u, ful = 0u; + + // NOTE: this cast is potentially unsafe by design, so we need to perform type checks before + // we're allowed to dereference the pointers without risking to crash. We might still be + // invoking UB btw - we're assuming that the ModifierData member of the respective modifier + // structures is at offset sizeof(vftable) with no padding. + const SharedModifierData *cur = static_cast<const SharedModifierData *>(orig_object.modifiers.first.get()); + for (; cur; cur = static_cast<const SharedModifierData *>(cur->modifier.next.get()), ++ful) { + ai_assert(cur->dna_type); + + const Structure *s = conv_data.db.dna.Get(cur->dna_type); + if (!s) { + ASSIMP_LOG_WARN("BlendModifier: could not resolve DNA name: ", cur->dna_type); + continue; + } + + // this is a common trait of all XXXMirrorData structures in BlenderDNA + const Field *f = s->Get("modifier"); + if (!f || f->offset != 0) { + ASSIMP_LOG_WARN("BlendModifier: expected a `modifier` member at offset 0"); + continue; + } + + s = conv_data.db.dna.Get(f->type); + if (!s || s->name != "ModifierData") { + ASSIMP_LOG_WARN("BlendModifier: expected a ModifierData structure as first member"); + continue; + } + + // now, we can be sure that we should be fine to dereference *cur* as + // ModifierData (with the above note). + const ModifierData &dat = cur->modifier; + + const fpCreateModifier *curgod = creators; + std::vector<BlenderModifier *>::iterator curmod = cached_modifiers->begin(), endmod = cached_modifiers->end(); + + for (; *curgod; ++curgod, ++curmod) { // allocate modifiers on the fly + if (curmod == endmod) { + cached_modifiers->push_back((*curgod)()); + + endmod = cached_modifiers->end(); + curmod = endmod - 1; + } + + BlenderModifier *const modifier = *curmod; + if (modifier->IsActive(dat)) { + modifier->DoIt(out, conv_data, *static_cast<const ElemBase *>(cur), in, orig_object); + cnt++; + + curgod = nullptr; + break; + } + } + if (curgod) { + ASSIMP_LOG_WARN("Couldn't find a handler for modifier: ", dat.name); + } + } + + // Even though we managed to resolve some or all of the modifiers on this + // object, we still can't say whether our modifier implementations were + // able to fully do their job. + if (ful) { + ASSIMP_LOG_DEBUG("BlendModifier: found handlers for ", cnt, " of ", ful, " modifiers on `", orig_object.id.name, + "`, check log messages above for errors"); + } +} + +// ------------------------------------------------------------------------------------------------ +bool BlenderModifier_Mirror ::IsActive(const ModifierData &modin) { + return modin.type == ModifierData::eModifierType_Mirror; +} + +// ------------------------------------------------------------------------------------------------ +void BlenderModifier_Mirror ::DoIt(aiNode &out, ConversionData &conv_data, const ElemBase &orig_modifier, + const Scene & /*in*/, + const Object &orig_object) { + // hijacking the ABI, see the big note in BlenderModifierShowcase::ApplyModifiers() + const MirrorModifierData &mir = static_cast<const MirrorModifierData &>(orig_modifier); + ai_assert(mir.modifier.type == ModifierData::eModifierType_Mirror); + + conv_data.meshes->reserve(conv_data.meshes->size() + out.mNumMeshes); + + // XXX not entirely correct, mirroring on two axes results in 4 distinct objects in blender ... + + // take all input meshes and clone them + for (unsigned int i = 0; i < out.mNumMeshes; ++i) { + aiMesh *mesh; + SceneCombiner::Copy(&mesh, conv_data.meshes[out.mMeshes[i]]); + + const float xs = mir.flag & MirrorModifierData::Flags_AXIS_X ? -1.f : 1.f; + const float ys = mir.flag & MirrorModifierData::Flags_AXIS_Y ? -1.f : 1.f; + const float zs = mir.flag & MirrorModifierData::Flags_AXIS_Z ? -1.f : 1.f; + + if (mir.mirror_ob) { + const aiVector3D center(mir.mirror_ob->obmat[3][0], mir.mirror_ob->obmat[3][1], mir.mirror_ob->obmat[3][2]); + for (unsigned int j = 0; j < mesh->mNumVertices; ++j) { + aiVector3D &v = mesh->mVertices[j]; + + v.x = center.x + xs * (center.x - v.x); + v.y = center.y + ys * (center.y - v.y); + v.z = center.z + zs * (center.z - v.z); + } + } else { + for (unsigned int j = 0; j < mesh->mNumVertices; ++j) { + aiVector3D &v = mesh->mVertices[j]; + v.x *= xs; + v.y *= ys; + v.z *= zs; + } + } + + if (mesh->mNormals) { + for (unsigned int j = 0; j < mesh->mNumVertices; ++j) { + aiVector3D &v = mesh->mNormals[j]; + v.x *= xs; + v.y *= ys; + v.z *= zs; + } + } + + if (mesh->mTangents) { + for (unsigned int j = 0; j < mesh->mNumVertices; ++j) { + aiVector3D &v = mesh->mTangents[j]; + v.x *= xs; + v.y *= ys; + v.z *= zs; + } + } + + if (mesh->mBitangents) { + for (unsigned int j = 0; j < mesh->mNumVertices; ++j) { + aiVector3D &v = mesh->mBitangents[j]; + v.x *= xs; + v.y *= ys; + v.z *= zs; + } + } + + const float us = mir.flag & MirrorModifierData::Flags_MIRROR_U ? -1.f : 1.f; + const float vs = mir.flag & MirrorModifierData::Flags_MIRROR_V ? -1.f : 1.f; + + for (unsigned int n = 0; mesh->HasTextureCoords(n); ++n) { + for (unsigned int j = 0; j < mesh->mNumVertices; ++j) { + aiVector3D &v = mesh->mTextureCoords[n][j]; + v.x *= us; + v.y *= vs; + } + } + + // Only reverse the winding order if an odd number of axes were mirrored. + if (xs * ys * zs < 0) { + for (unsigned int j = 0; j < mesh->mNumFaces; ++j) { + aiFace &face = mesh->mFaces[j]; + for (unsigned int fi = 0; fi < face.mNumIndices / 2; ++fi) + std::swap(face.mIndices[fi], face.mIndices[face.mNumIndices - 1 - fi]); + } + } + + conv_data.meshes->push_back(mesh); + } + unsigned int *nind = new unsigned int[out.mNumMeshes * 2]; + + std::copy(out.mMeshes, out.mMeshes + out.mNumMeshes, nind); + std::transform(out.mMeshes, out.mMeshes + out.mNumMeshes, nind + out.mNumMeshes, + [&out](unsigned int n) { return out.mNumMeshes + n; }); + + delete[] out.mMeshes; + out.mMeshes = nind; + out.mNumMeshes *= 2; + + ASSIMP_LOG_INFO("BlendModifier: Applied the `Mirror` modifier to `", + orig_object.id.name, "`"); +} + +// ------------------------------------------------------------------------------------------------ +bool BlenderModifier_Subdivision ::IsActive(const ModifierData &modin) { + return modin.type == ModifierData::eModifierType_Subsurf; +} + +// ------------------------------------------------------------------------------------------------ +void BlenderModifier_Subdivision ::DoIt(aiNode &out, ConversionData &conv_data, const ElemBase &orig_modifier, + const Scene & /*in*/, + const Object &orig_object) { + // hijacking the ABI, see the big note in BlenderModifierShowcase::ApplyModifiers() + const SubsurfModifierData &mir = static_cast<const SubsurfModifierData &>(orig_modifier); + ai_assert(mir.modifier.type == ModifierData::eModifierType_Subsurf); + + Subdivider::Algorithm algo; + switch (mir.subdivType) { + case SubsurfModifierData::TYPE_CatmullClarke: + algo = Subdivider::CATMULL_CLARKE; + break; + + case SubsurfModifierData::TYPE_Simple: + ASSIMP_LOG_WARN("BlendModifier: The `SIMPLE` subdivision algorithm is not currently implemented, using Catmull-Clarke"); + algo = Subdivider::CATMULL_CLARKE; + break; + + default: + ASSIMP_LOG_WARN("BlendModifier: Unrecognized subdivision algorithm: ", mir.subdivType); + return; + }; + + std::unique_ptr<Subdivider> subd(Subdivider::Create(algo)); + ai_assert(subd); + if (conv_data.meshes->empty()) { + return; + } + aiMesh **const meshes = &conv_data.meshes[conv_data.meshes->size() - out.mNumMeshes]; + std::unique_ptr<aiMesh *[]> tempmeshes(new aiMesh *[out.mNumMeshes]()); + + subd->Subdivide(meshes, out.mNumMeshes, tempmeshes.get(), std::max(mir.renderLevels, mir.levels), true); + std::copy(tempmeshes.get(), tempmeshes.get() + out.mNumMeshes, meshes); + + ASSIMP_LOG_INFO("BlendModifier: Applied the `Subdivision` modifier to `", + orig_object.id.name, "`"); +} + +#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER diff --git a/libs/assimp/code/AssetLib/Blender/BlenderModifier.h b/libs/assimp/code/AssetLib/Blender/BlenderModifier.h new file mode 100644 index 0000000..5af560c --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderModifier.h @@ -0,0 +1,155 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BlenderModifier.h + * @brief Declare dedicated helper classes to simulate some blender modifiers (i.e. mirror) + */ +#ifndef INCLUDED_AI_BLEND_MODIFIER_H +#define INCLUDED_AI_BLEND_MODIFIER_H + +#include "BlenderIntermediate.h" + +namespace Assimp { +namespace Blender { + +// ------------------------------------------------------------------------------------------- +/** + * Dummy base class for all blender modifiers. Modifiers are reused between imports, so + * they should be stateless and not try to cache model data. + */ +// ------------------------------------------------------------------------------------------- +class BlenderModifier { +public: + /** + * The class destructor, virtual. + */ + virtual ~BlenderModifier() { + // empty + } + + // -------------------- + /** + * Check if *this* modifier is active, given a ModifierData& block. + */ + virtual bool IsActive( const ModifierData& /*modin*/) { + return false; + } + + // -------------------- + /** + * Apply the modifier to a given output node. The original data used + * to construct the node is given as well. Not called unless IsActive() + * was called and gave positive response. + */ + virtual void DoIt(aiNode& /*out*/, + ConversionData& /*conv_data*/, + const ElemBase& orig_modifier, + const Scene& /*in*/, + const Object& /*orig_object*/ + ) { + ASSIMP_LOG_INFO("This modifier is not supported, skipping: ",orig_modifier.dna_type ); + return; + } +}; + +// ------------------------------------------------------------------------------------------- +/** + * Manage all known modifiers and instance and apply them if necessary + */ +// ------------------------------------------------------------------------------------------- +class BlenderModifierShowcase { +public: + // -------------------- + /** Apply all requested modifiers provided we support them. */ + void ApplyModifiers(aiNode& out, + ConversionData& conv_data, + const Scene& in, + const Object& orig_object + ); + +private: + TempArray< std::vector,BlenderModifier > cached_modifiers; +}; + +// MODIFIERS ///////////////////////////////////////////////////////////////////////////////// + +// ------------------------------------------------------------------------------------------- +/** + * Mirror modifier. Status: implemented. + */ +// ------------------------------------------------------------------------------------------- +class BlenderModifier_Mirror : public BlenderModifier { +public: + // -------------------- + virtual bool IsActive( const ModifierData& modin); + + // -------------------- + virtual void DoIt(aiNode& out, + ConversionData& conv_data, + const ElemBase& orig_modifier, + const Scene& in, + const Object& orig_object + ) ; +}; + +// ------------------------------------------------------------------------------------------- +/** Subdivision modifier. Status: dummy. */ +// ------------------------------------------------------------------------------------------- +class BlenderModifier_Subdivision : public BlenderModifier { +public: + + // -------------------- + virtual bool IsActive( const ModifierData& modin); + + // -------------------- + virtual void DoIt(aiNode& out, + ConversionData& conv_data, + const ElemBase& orig_modifier, + const Scene& in, + const Object& orig_object + ) ; +}; + +} +} + +#endif // !INCLUDED_AI_BLEND_MODIFIER_H diff --git a/libs/assimp/code/AssetLib/Blender/BlenderScene.cpp b/libs/assimp/code/AssetLib/Blender/BlenderScene.cpp new file mode 100644 index 0000000..9ad086f --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderScene.cpp @@ -0,0 +1,891 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, ASSIMP Development 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 Development 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 BlenderScene.cpp + * @brief MACHINE GENERATED BY ./scripts/BlenderImporter/genblenddna.py + */ + +#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER + +#include "BlenderScene.h" +#include "BlenderCustomData.h" +#include "BlenderDNA.h" +#include "BlenderSceneGen.h" + +namespace Assimp { +namespace Blender { + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<Object>( + Object &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.id, "id", db); + int temp = 0; + ReadField<ErrorPolicy_Fail>(temp, "type", db); + dest.type = static_cast<Assimp::Blender::Object::Type>(temp); + ReadFieldArray2<ErrorPolicy_Warn>(dest.obmat, "obmat", db); + ReadFieldArray2<ErrorPolicy_Warn>(dest.parentinv, "parentinv", db); + ReadFieldArray<ErrorPolicy_Warn>(dest.parsubstr, "parsubstr", db); + { + std::shared_ptr<Object> parent; + ReadFieldPtr<ErrorPolicy_Warn>(parent, "*parent", db); + dest.parent = parent.get(); + } + ReadFieldPtr<ErrorPolicy_Warn>(dest.track, "*track", db); + ReadFieldPtr<ErrorPolicy_Warn>(dest.proxy, "*proxy", db); + ReadFieldPtr<ErrorPolicy_Warn>(dest.proxy_from, "*proxy_from", db); + ReadFieldPtr<ErrorPolicy_Warn>(dest.proxy_group, "*proxy_group", db); + ReadFieldPtr<ErrorPolicy_Warn>(dest.dup_group, "*dup_group", db); + ReadFieldPtr<ErrorPolicy_Fail>(dest.data, "*data", db); + ReadField<ErrorPolicy_Igno>(dest.modifiers, "modifiers", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<Group>( + Group &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.id, "id", db); + ReadField<ErrorPolicy_Igno>(dest.layer, "layer", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.gobject, "*gobject", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure::Convert<CollectionObject>( + CollectionObject &dest, + const FileDatabase &db) const { + + ReadFieldPtr<ErrorPolicy_Fail>(dest.next, "*next", db); + { + //std::shared_ptr<CollectionObject> prev; + //ReadFieldPtr<ErrorPolicy_Fail>(prev, "*prev", db); + //dest.prev = prev.get(); + + std::shared_ptr<Object> ob; + ReadFieldPtr<ErrorPolicy_Igno>(ob, "*ob", db); + dest.ob = ob.get(); + } + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure::Convert<CollectionChild>( + CollectionChild &dest, + const FileDatabase &db) const { + + ReadFieldPtr<ErrorPolicy_Fail>(dest.prev, "*prev", db); + ReadFieldPtr<ErrorPolicy_Fail>(dest.next, "*next", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.collection, "*collection", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure::Convert<Collection>( + Collection &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.id, "id", db); + ReadField<ErrorPolicy_Fail>(dest.gobject, "gobject", db); + ReadField<ErrorPolicy_Fail>(dest.children, "children", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MTex>( + MTex &dest, + const FileDatabase &db) const { + + int temp_short = 0; + ReadField<ErrorPolicy_Igno>(temp_short, "mapto", db); + dest.mapto = static_cast<Assimp::Blender::MTex::MapType>(temp_short); + int temp = 0; + ReadField<ErrorPolicy_Igno>(temp, "blendtype", db); + dest.blendtype = static_cast<Assimp::Blender::MTex::BlendType>(temp); + ReadFieldPtr<ErrorPolicy_Igno>(dest.object, "*object", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.tex, "*tex", db); + ReadFieldArray<ErrorPolicy_Igno>(dest.uvname, "uvname", db); + ReadField<ErrorPolicy_Igno>(temp, "projx", db); + dest.projx = static_cast<Assimp::Blender::MTex::Projection>(temp); + ReadField<ErrorPolicy_Igno>(temp, "projy", db); + dest.projy = static_cast<Assimp::Blender::MTex::Projection>(temp); + ReadField<ErrorPolicy_Igno>(temp, "projz", db); + dest.projz = static_cast<Assimp::Blender::MTex::Projection>(temp); + ReadField<ErrorPolicy_Igno>(dest.mapping, "mapping", db); + ReadFieldArray<ErrorPolicy_Igno>(dest.ofs, "ofs", db); + ReadFieldArray<ErrorPolicy_Igno>(dest.size, "size", db); + ReadField<ErrorPolicy_Igno>(dest.rot, "rot", db); + ReadField<ErrorPolicy_Igno>(dest.texflag, "texflag", db); + ReadField<ErrorPolicy_Igno>(dest.colormodel, "colormodel", db); + ReadField<ErrorPolicy_Igno>(dest.pmapto, "pmapto", db); + ReadField<ErrorPolicy_Igno>(dest.pmaptoneg, "pmaptoneg", db); + ReadField<ErrorPolicy_Warn>(dest.r, "r", db); + ReadField<ErrorPolicy_Warn>(dest.g, "g", db); + ReadField<ErrorPolicy_Warn>(dest.b, "b", db); + ReadField<ErrorPolicy_Warn>(dest.k, "k", db); + ReadField<ErrorPolicy_Igno>(dest.colspecfac, "colspecfac", db); + ReadField<ErrorPolicy_Igno>(dest.mirrfac, "mirrfac", db); + ReadField<ErrorPolicy_Igno>(dest.alphafac, "alphafac", db); + ReadField<ErrorPolicy_Igno>(dest.difffac, "difffac", db); + ReadField<ErrorPolicy_Igno>(dest.specfac, "specfac", db); + ReadField<ErrorPolicy_Igno>(dest.emitfac, "emitfac", db); + ReadField<ErrorPolicy_Igno>(dest.hardfac, "hardfac", db); + ReadField<ErrorPolicy_Igno>(dest.norfac, "norfac", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<TFace>( + TFace &dest, + const FileDatabase &db) const { + + ReadFieldArray2<ErrorPolicy_Fail>(dest.uv, "uv", db); + ReadFieldArray<ErrorPolicy_Fail>(dest.col, "col", db); + ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db); + ReadField<ErrorPolicy_Igno>(dest.mode, "mode", db); + ReadField<ErrorPolicy_Igno>(dest.tile, "tile", db); + ReadField<ErrorPolicy_Igno>(dest.unwrap, "unwrap", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<SubsurfModifierData>( + SubsurfModifierData &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.modifier, "modifier", db); + ReadField<ErrorPolicy_Warn>(dest.subdivType, "subdivType", db); + ReadField<ErrorPolicy_Fail>(dest.levels, "levels", db); + ReadField<ErrorPolicy_Igno>(dest.renderLevels, "renderLevels", db); + ReadField<ErrorPolicy_Igno>(dest.flags, "flags", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MFace>( + MFace &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.v1, "v1", db); + ReadField<ErrorPolicy_Fail>(dest.v2, "v2", db); + ReadField<ErrorPolicy_Fail>(dest.v3, "v3", db); + ReadField<ErrorPolicy_Fail>(dest.v4, "v4", db); + ReadField<ErrorPolicy_Fail>(dest.mat_nr, "mat_nr", db); + ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<Lamp>( + Lamp &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.id, "id", db); + int temp = 0; + ReadField<ErrorPolicy_Fail>(temp, "type", db); + dest.type = static_cast<Assimp::Blender::Lamp::Type>(temp); + ReadField<ErrorPolicy_Igno>(dest.flags, "flag", db); + ReadField<ErrorPolicy_Igno>(dest.colormodel, "colormodel", db); + ReadField<ErrorPolicy_Igno>(dest.totex, "totex", db); + ReadField<ErrorPolicy_Warn>(dest.r, "r", db); + ReadField<ErrorPolicy_Warn>(dest.g, "g", db); + ReadField<ErrorPolicy_Warn>(dest.b, "b", db); + ReadField<ErrorPolicy_Warn>(dest.k, "k", db); + ReadField<ErrorPolicy_Igno>(dest.energy, "energy", db); + ReadField<ErrorPolicy_Warn>(dest.dist, "dist", db); + ReadField<ErrorPolicy_Igno>(dest.spotsize, "spotsize", db); + ReadField<ErrorPolicy_Igno>(dest.spotblend, "spotblend", db); + ReadField<ErrorPolicy_Warn>(dest.constant_coefficient, "coeff_const", db); + ReadField<ErrorPolicy_Warn>(dest.linear_coefficient, "coeff_lin", db); + ReadField<ErrorPolicy_Warn>(dest.quadratic_coefficient, "coeff_quad", db); + ReadField<ErrorPolicy_Igno>(dest.att1, "att1", db); + ReadField<ErrorPolicy_Igno>(dest.att2, "att2", db); + ReadField<ErrorPolicy_Igno>(temp, "falloff_type", db); + dest.falloff_type = static_cast<Assimp::Blender::Lamp::FalloffType>(temp); + ReadField<ErrorPolicy_Igno>(dest.sun_brightness, "sun_brightness", db); + ReadField<ErrorPolicy_Igno>(dest.area_size, "area_size", db); + ReadField<ErrorPolicy_Igno>(dest.area_sizey, "area_sizey", db); + ReadField<ErrorPolicy_Igno>(dest.area_sizez, "area_sizez", db); + ReadField<ErrorPolicy_Igno>(dest.area_shape, "area_shape", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MDeformWeight>( + MDeformWeight &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.def_nr, "def_nr", db); + ReadField<ErrorPolicy_Fail>(dest.weight, "weight", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<PackedFile>( + PackedFile &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Warn>(dest.size, "size", db); + ReadField<ErrorPolicy_Warn>(dest.seek, "seek", db); + ReadFieldPtr<ErrorPolicy_Warn>(dest.data, "*data", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<Base>( + Base &dest, + const FileDatabase &db) const { + // note: as per https://github.com/assimp/assimp/issues/128, + // reading the Object linked list recursively is prone to stack overflow. + // This structure converter is therefore an hand-written exception that + // does it iteratively. + + const int initial_pos = db.reader->GetCurrentPos(); + + std::pair<Base *, int> todo = std::make_pair(&dest, initial_pos); + for (;;) { + + Base &cur_dest = *todo.first; + db.reader->SetCurrentPos(todo.second); + + // we know that this is a double-linked, circular list which we never + // traverse backwards, so don't bother resolving the back links. + cur_dest.prev = nullptr; + + ReadFieldPtr<ErrorPolicy_Warn>(cur_dest.object, "*object", db); + + // the return value of ReadFieldPtr indicates whether the object + // was already cached. In this case, we don't need to resolve + // it again. + if (!ReadFieldPtr<ErrorPolicy_Warn>(cur_dest.next, "*next", db, true) && cur_dest.next) { + todo = std::make_pair(&*cur_dest.next, db.reader->GetCurrentPos()); + continue; + } + break; + } + + db.reader->SetCurrentPos(initial_pos + size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MTFace>( + MTFace &dest, + const FileDatabase &db) const { + + ReadFieldArray2<ErrorPolicy_Fail>(dest.uv, "uv", db); + ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db); + ReadField<ErrorPolicy_Igno>(dest.mode, "mode", db); + ReadField<ErrorPolicy_Igno>(dest.tile, "tile", db); + ReadField<ErrorPolicy_Igno>(dest.unwrap, "unwrap", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<Material>( + Material &dest, + const FileDatabase &db) const { + ReadField<ErrorPolicy_Fail>(dest.id, "id", db); + ReadField<ErrorPolicy_Warn>(dest.r, "r", db); + ReadField<ErrorPolicy_Warn>(dest.g, "g", db); + ReadField<ErrorPolicy_Warn>(dest.b, "b", db); + ReadField<ErrorPolicy_Warn>(dest.specr, "specr", db); + ReadField<ErrorPolicy_Warn>(dest.specg, "specg", db); + ReadField<ErrorPolicy_Warn>(dest.specb, "specb", db); + ReadField<ErrorPolicy_Igno>(dest.har, "har", db); + ReadField<ErrorPolicy_Warn>(dest.ambr, "ambr", db); + ReadField<ErrorPolicy_Warn>(dest.ambg, "ambg", db); + ReadField<ErrorPolicy_Warn>(dest.ambb, "ambb", db); + ReadField<ErrorPolicy_Igno>(dest.mirr, "mirr", db); + ReadField<ErrorPolicy_Igno>(dest.mirg, "mirg", db); + ReadField<ErrorPolicy_Igno>(dest.mirb, "mirb", db); + ReadField<ErrorPolicy_Warn>(dest.emit, "emit", db); + ReadField<ErrorPolicy_Igno>(dest.ray_mirror, "ray_mirror", db); + ReadField<ErrorPolicy_Warn>(dest.alpha, "alpha", db); + ReadField<ErrorPolicy_Igno>(dest.ref, "ref", db); + ReadField<ErrorPolicy_Igno>(dest.translucency, "translucency", db); + ReadField<ErrorPolicy_Igno>(dest.mode, "mode", db); + ReadField<ErrorPolicy_Igno>(dest.roughness, "roughness", db); + ReadField<ErrorPolicy_Igno>(dest.darkness, "darkness", db); + ReadField<ErrorPolicy_Igno>(dest.refrac, "refrac", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.group, "*group", db); + ReadField<ErrorPolicy_Warn>(dest.diff_shader, "diff_shader", db); + ReadField<ErrorPolicy_Warn>(dest.spec_shader, "spec_shader", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.mtex, "*mtex", db); + + ReadField<ErrorPolicy_Igno>(dest.amb, "amb", db); + ReadField<ErrorPolicy_Igno>(dest.ang, "ang", db); + ReadField<ErrorPolicy_Igno>(dest.spectra, "spectra", db); + ReadField<ErrorPolicy_Igno>(dest.spec, "spec", db); + ReadField<ErrorPolicy_Igno>(dest.zoffs, "zoffs", db); + ReadField<ErrorPolicy_Igno>(dest.add, "add", db); + ReadField<ErrorPolicy_Igno>(dest.fresnel_mir, "fresnel_mir", db); + ReadField<ErrorPolicy_Igno>(dest.fresnel_mir_i, "fresnel_mir_i", db); + ReadField<ErrorPolicy_Igno>(dest.fresnel_tra, "fresnel_tra", db); + ReadField<ErrorPolicy_Igno>(dest.fresnel_tra_i, "fresnel_tra_i", db); + ReadField<ErrorPolicy_Igno>(dest.filter, "filter", db); + ReadField<ErrorPolicy_Igno>(dest.tx_limit, "tx_limit", db); + ReadField<ErrorPolicy_Igno>(dest.tx_falloff, "tx_falloff", db); + ReadField<ErrorPolicy_Igno>(dest.gloss_mir, "gloss_mir", db); + ReadField<ErrorPolicy_Igno>(dest.gloss_tra, "gloss_tra", db); + ReadField<ErrorPolicy_Igno>(dest.adapt_thresh_mir, "adapt_thresh_mir", db); + ReadField<ErrorPolicy_Igno>(dest.adapt_thresh_tra, "adapt_thresh_tra", db); + ReadField<ErrorPolicy_Igno>(dest.aniso_gloss_mir, "aniso_gloss_mir", db); + ReadField<ErrorPolicy_Igno>(dest.dist_mir, "dist_mir", db); + ReadField<ErrorPolicy_Igno>(dest.hasize, "hasize", db); + ReadField<ErrorPolicy_Igno>(dest.flaresize, "flaresize", db); + ReadField<ErrorPolicy_Igno>(dest.subsize, "subsize", db); + ReadField<ErrorPolicy_Igno>(dest.flareboost, "flareboost", db); + ReadField<ErrorPolicy_Igno>(dest.strand_sta, "strand_sta", db); + ReadField<ErrorPolicy_Igno>(dest.strand_end, "strand_end", db); + ReadField<ErrorPolicy_Igno>(dest.strand_ease, "strand_ease", db); + ReadField<ErrorPolicy_Igno>(dest.strand_surfnor, "strand_surfnor", db); + ReadField<ErrorPolicy_Igno>(dest.strand_min, "strand_min", db); + ReadField<ErrorPolicy_Igno>(dest.strand_widthfade, "strand_widthfade", db); + ReadField<ErrorPolicy_Igno>(dest.sbias, "sbias", db); + ReadField<ErrorPolicy_Igno>(dest.lbias, "lbias", db); + ReadField<ErrorPolicy_Igno>(dest.shad_alpha, "shad_alpha", db); + ReadField<ErrorPolicy_Igno>(dest.param, "param", db); + ReadField<ErrorPolicy_Igno>(dest.rms, "rms", db); + ReadField<ErrorPolicy_Igno>(dest.rampfac_col, "rampfac_col", db); + ReadField<ErrorPolicy_Igno>(dest.rampfac_spec, "rampfac_spec", db); + ReadField<ErrorPolicy_Igno>(dest.friction, "friction", db); + ReadField<ErrorPolicy_Igno>(dest.fh, "fh", db); + ReadField<ErrorPolicy_Igno>(dest.reflect, "reflect", db); + ReadField<ErrorPolicy_Igno>(dest.fhdist, "fhdist", db); + ReadField<ErrorPolicy_Igno>(dest.xyfrict, "xyfrict", db); + ReadField<ErrorPolicy_Igno>(dest.sss_radius, "sss_radius", db); + ReadField<ErrorPolicy_Igno>(dest.sss_col, "sss_col", db); + ReadField<ErrorPolicy_Igno>(dest.sss_error, "sss_error", db); + ReadField<ErrorPolicy_Igno>(dest.sss_scale, "sss_scale", db); + ReadField<ErrorPolicy_Igno>(dest.sss_ior, "sss_ior", db); + ReadField<ErrorPolicy_Igno>(dest.sss_colfac, "sss_colfac", db); + ReadField<ErrorPolicy_Igno>(dest.sss_texfac, "sss_texfac", db); + ReadField<ErrorPolicy_Igno>(dest.sss_front, "sss_front", db); + ReadField<ErrorPolicy_Igno>(dest.sss_back, "sss_back", db); + + ReadField<ErrorPolicy_Igno>(dest.material_type, "material_type", db); + ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db); + ReadField<ErrorPolicy_Igno>(dest.ray_depth, "ray_depth", db); + ReadField<ErrorPolicy_Igno>(dest.ray_depth_tra, "ray_depth_tra", db); + ReadField<ErrorPolicy_Igno>(dest.samp_gloss_mir, "samp_gloss_mir", db); + ReadField<ErrorPolicy_Igno>(dest.samp_gloss_tra, "samp_gloss_tra", db); + ReadField<ErrorPolicy_Igno>(dest.fadeto_mir, "fadeto_mir", db); + ReadField<ErrorPolicy_Igno>(dest.shade_flag, "shade_flag", db); + ReadField<ErrorPolicy_Igno>(dest.flarec, "flarec", db); + ReadField<ErrorPolicy_Igno>(dest.starc, "starc", db); + ReadField<ErrorPolicy_Igno>(dest.linec, "linec", db); + ReadField<ErrorPolicy_Igno>(dest.ringc, "ringc", db); + ReadField<ErrorPolicy_Igno>(dest.pr_lamp, "pr_lamp", db); + ReadField<ErrorPolicy_Igno>(dest.pr_texture, "pr_texture", db); + ReadField<ErrorPolicy_Igno>(dest.ml_flag, "ml_flag", db); + ReadField<ErrorPolicy_Igno>(dest.diff_shader, "diff_shader", db); + ReadField<ErrorPolicy_Igno>(dest.spec_shader, "spec_shader", db); + ReadField<ErrorPolicy_Igno>(dest.texco, "texco", db); + ReadField<ErrorPolicy_Igno>(dest.mapto, "mapto", db); + ReadField<ErrorPolicy_Igno>(dest.ramp_show, "ramp_show", db); + ReadField<ErrorPolicy_Igno>(dest.pad3, "pad3", db); + ReadField<ErrorPolicy_Igno>(dest.dynamode, "dynamode", db); + ReadField<ErrorPolicy_Igno>(dest.pad2, "pad2", db); + ReadField<ErrorPolicy_Igno>(dest.sss_flag, "sss_flag", db); + ReadField<ErrorPolicy_Igno>(dest.sss_preset, "sss_preset", db); + ReadField<ErrorPolicy_Igno>(dest.shadowonly_flag, "shadowonly_flag", db); + ReadField<ErrorPolicy_Igno>(dest.index, "index", db); + ReadField<ErrorPolicy_Igno>(dest.vcol_alpha, "vcol_alpha", db); + ReadField<ErrorPolicy_Igno>(dest.pad4, "pad4", db); + + ReadField<ErrorPolicy_Igno>(dest.seed1, "seed1", db); + ReadField<ErrorPolicy_Igno>(dest.seed2, "seed2", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MTexPoly>( + MTexPoly &dest, + const FileDatabase &db) const { + + { + std::shared_ptr<Image> tpage; + ReadFieldPtr<ErrorPolicy_Igno>(tpage, "*tpage", db); + dest.tpage = tpage.get(); + } + ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db); + ReadField<ErrorPolicy_Igno>(dest.transp, "transp", db); + ReadField<ErrorPolicy_Igno>(dest.mode, "mode", db); + ReadField<ErrorPolicy_Igno>(dest.tile, "tile", db); + ReadField<ErrorPolicy_Igno>(dest.pad, "pad", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<Mesh>( + Mesh &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.id, "id", db); + ReadField<ErrorPolicy_Fail>(dest.totface, "totface", db); + ReadField<ErrorPolicy_Fail>(dest.totedge, "totedge", db); + ReadField<ErrorPolicy_Fail>(dest.totvert, "totvert", db); + ReadField<ErrorPolicy_Igno>(dest.totloop, "totloop", db); + ReadField<ErrorPolicy_Igno>(dest.totpoly, "totpoly", db); + ReadField<ErrorPolicy_Igno>(dest.subdiv, "subdiv", db); + ReadField<ErrorPolicy_Igno>(dest.subdivr, "subdivr", db); + ReadField<ErrorPolicy_Igno>(dest.subsurftype, "subsurftype", db); + ReadField<ErrorPolicy_Igno>(dest.smoothresh, "smoothresh", db); + ReadFieldPtr<ErrorPolicy_Fail>(dest.mface, "*mface", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.mtface, "*mtface", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.tface, "*tface", db); + ReadFieldPtr<ErrorPolicy_Fail>(dest.mvert, "*mvert", db); + ReadFieldPtr<ErrorPolicy_Warn>(dest.medge, "*medge", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.mloop, "*mloop", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.mloopuv, "*mloopuv", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.mloopcol, "*mloopcol", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.mpoly, "*mpoly", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.mtpoly, "*mtpoly", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.dvert, "*dvert", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.mcol, "*mcol", db); + ReadFieldPtr<ErrorPolicy_Fail>(dest.mat, "**mat", db); + + ReadField<ErrorPolicy_Igno>(dest.vdata, "vdata", db); + ReadField<ErrorPolicy_Igno>(dest.edata, "edata", db); + ReadField<ErrorPolicy_Igno>(dest.fdata, "fdata", db); + ReadField<ErrorPolicy_Igno>(dest.pdata, "pdata", db); + ReadField<ErrorPolicy_Warn>(dest.ldata, "ldata", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MDeformVert>( + MDeformVert &dest, + const FileDatabase &db) const { + + ReadFieldPtr<ErrorPolicy_Warn>(dest.dw, "*dw", db); + ReadField<ErrorPolicy_Igno>(dest.totweight, "totweight", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<World>( + World &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.id, "id", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MLoopCol>( + MLoopCol &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Igno>(dest.r, "r", db); + ReadField<ErrorPolicy_Igno>(dest.g, "g", db); + ReadField<ErrorPolicy_Igno>(dest.b, "b", db); + ReadField<ErrorPolicy_Igno>(dest.a, "a", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MVert>( + MVert &dest, + const FileDatabase &db) const { + + ReadFieldArray<ErrorPolicy_Fail>(dest.co, "co", db); + ReadFieldArray<ErrorPolicy_Fail>(dest.no, "no", db); + ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db); + //ReadField<ErrorPolicy_Warn>(dest.mat_nr,"mat_nr",db); + ReadField<ErrorPolicy_Igno>(dest.bweight, "bweight", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MEdge>( + MEdge &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.v1, "v1", db); + ReadField<ErrorPolicy_Fail>(dest.v2, "v2", db); + ReadField<ErrorPolicy_Igno>(dest.crease, "crease", db); + ReadField<ErrorPolicy_Igno>(dest.bweight, "bweight", db); + ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MLoopUV>( + MLoopUV &dest, + const FileDatabase &db) const { + + ReadFieldArray<ErrorPolicy_Igno>(dest.uv, "uv", db); + ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<GroupObject>( + GroupObject &dest, + const FileDatabase &db) const { + + ReadFieldPtr<ErrorPolicy_Fail>(dest.prev, "*prev", db); + ReadFieldPtr<ErrorPolicy_Fail>(dest.next, "*next", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.ob, "*ob", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<ListBase>( + ListBase &dest, + const FileDatabase &db) const { + + ReadFieldPtr<ErrorPolicy_Igno>(dest.first, "*first", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.last, "*last", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MLoop>( + MLoop &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Igno>(dest.v, "v", db); + ReadField<ErrorPolicy_Igno>(dest.e, "e", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<ModifierData>( + ModifierData &dest, + const FileDatabase &db) const { + + ReadFieldPtr<ErrorPolicy_Warn>(dest.next, "*next", db); + ReadFieldPtr<ErrorPolicy_Warn>(dest.prev, "*prev", db); + ReadField<ErrorPolicy_Igno>(dest.type, "type", db); + ReadField<ErrorPolicy_Igno>(dest.mode, "mode", db); + ReadFieldArray<ErrorPolicy_Igno>(dest.name, "name", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<ID>( + ID &dest, + const FileDatabase &db) const { + + ReadFieldArray<ErrorPolicy_Warn>(dest.name, "name", db); + ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MCol>( + MCol &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.r, "r", db); + ReadField<ErrorPolicy_Fail>(dest.g, "g", db); + ReadField<ErrorPolicy_Fail>(dest.b, "b", db); + ReadField<ErrorPolicy_Fail>(dest.a, "a", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MPoly>( + MPoly &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Igno>(dest.loopstart, "loopstart", db); + ReadField<ErrorPolicy_Igno>(dest.totloop, "totloop", db); + ReadField<ErrorPolicy_Igno>(dest.mat_nr, "mat_nr", db); + ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<Scene>( + Scene &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.id, "id", db); + ReadFieldPtr<ErrorPolicy_Warn>(dest.camera, "*camera", db); + ReadFieldPtr<ErrorPolicy_Warn>(dest.world, "*world", db); + ReadFieldPtr<ErrorPolicy_Warn>(dest.basact, "*basact", db); + ReadFieldPtr<ErrorPolicy_Warn>(dest.master_collection, "*master_collection", db); + ReadField<ErrorPolicy_Igno>(dest.base, "base", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<Library>( + Library &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.id, "id", db); + ReadFieldArray<ErrorPolicy_Warn>(dest.name, "name", db); + ReadFieldArray<ErrorPolicy_Fail>(dest.filename, "filename", db); + ReadFieldPtr<ErrorPolicy_Warn>(dest.parent, "*parent", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<Tex>( + Tex &dest, + const FileDatabase &db) const { + short temp_short = 0; + ReadField<ErrorPolicy_Igno>(temp_short, "imaflag", db); + dest.imaflag = static_cast<Assimp::Blender::Tex::ImageFlags>(temp_short); + int temp = 0; + ReadField<ErrorPolicy_Fail>(temp, "type", db); + dest.type = static_cast<Assimp::Blender::Tex::Type>(temp); + ReadFieldPtr<ErrorPolicy_Warn>(dest.ima, "*ima", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<Camera>( + Camera &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.id, "id", db); + int temp = 0; + ReadField<ErrorPolicy_Warn>(temp, "type", db); + dest.type = static_cast<Assimp::Blender::Camera::Type>(temp); + ReadField<ErrorPolicy_Warn>(temp, "flag", db); + dest.flag = static_cast<Assimp::Blender::Camera::Type>(temp); + ReadField<ErrorPolicy_Warn>(dest.lens, "lens", db); + ReadField<ErrorPolicy_Warn>(dest.sensor_x, "sensor_x", db); + ReadField<ErrorPolicy_Igno>(dest.clipsta, "clipsta", db); + ReadField<ErrorPolicy_Igno>(dest.clipend, "clipend", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<MirrorModifierData>( + MirrorModifierData &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.modifier, "modifier", db); + ReadField<ErrorPolicy_Igno>(dest.axis, "axis", db); + ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db); + ReadField<ErrorPolicy_Igno>(dest.tolerance, "tolerance", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.mirror_ob, "*mirror_ob", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure ::Convert<Image>( + Image &dest, + const FileDatabase &db) const { + + ReadField<ErrorPolicy_Fail>(dest.id, "id", db); + ReadFieldArray<ErrorPolicy_Warn>(dest.name, "name", db); + ReadField<ErrorPolicy_Igno>(dest.ok, "ok", db); + ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db); + ReadField<ErrorPolicy_Igno>(dest.source, "source", db); + ReadField<ErrorPolicy_Igno>(dest.type, "type", db); + ReadField<ErrorPolicy_Igno>(dest.pad, "pad", db); + ReadField<ErrorPolicy_Igno>(dest.pad1, "pad1", db); + ReadField<ErrorPolicy_Igno>(dest.lastframe, "lastframe", db); + ReadField<ErrorPolicy_Igno>(dest.tpageflag, "tpageflag", db); + ReadField<ErrorPolicy_Igno>(dest.totbind, "totbind", db); + ReadField<ErrorPolicy_Igno>(dest.xrep, "xrep", db); + ReadField<ErrorPolicy_Igno>(dest.yrep, "yrep", db); + ReadField<ErrorPolicy_Igno>(dest.twsta, "twsta", db); + ReadField<ErrorPolicy_Igno>(dest.twend, "twend", db); + ReadFieldPtr<ErrorPolicy_Igno>(dest.packedfile, "*packedfile", db); + ReadField<ErrorPolicy_Igno>(dest.lastupdate, "lastupdate", db); + ReadField<ErrorPolicy_Igno>(dest.lastused, "lastused", db); + ReadField<ErrorPolicy_Igno>(dest.animspeed, "animspeed", db); + ReadField<ErrorPolicy_Igno>(dest.gen_x, "gen_x", db); + ReadField<ErrorPolicy_Igno>(dest.gen_y, "gen_y", db); + ReadField<ErrorPolicy_Igno>(dest.gen_type, "gen_type", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure::Convert<CustomData>( + CustomData &dest, + const FileDatabase &db) const { + ReadFieldArray<ErrorPolicy_Warn>(dest.typemap, "typemap", db); + ReadField<ErrorPolicy_Warn>(dest.totlayer, "totlayer", db); + ReadField<ErrorPolicy_Warn>(dest.maxlayer, "maxlayer", db); + ReadField<ErrorPolicy_Warn>(dest.totsize, "totsize", db); + ReadFieldPtrVector<ErrorPolicy_Warn>(dest.layers, "*layers", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +template <> +void Structure::Convert<CustomDataLayer>( + CustomDataLayer &dest, + const FileDatabase &db) const { + ReadField<ErrorPolicy_Fail>(dest.type, "type", db); + ReadField<ErrorPolicy_Fail>(dest.offset, "offset", db); + ReadField<ErrorPolicy_Fail>(dest.flag, "flag", db); + ReadField<ErrorPolicy_Fail>(dest.active, "active", db); + ReadField<ErrorPolicy_Fail>(dest.active_rnd, "active_rnd", db); + ReadField<ErrorPolicy_Fail>(dest.active_clone, "active_clone", db); + ReadField<ErrorPolicy_Fail>(dest.active_mask, "active_mask", db); + ReadField<ErrorPolicy_Fail>(dest.uid, "uid", db); + ReadFieldArray<ErrorPolicy_Warn>(dest.name, "name", db); + ReadCustomDataPtr<ErrorPolicy_Fail>(dest.data, dest.type, "*data", db); + + db.reader->IncPtr(size); +} + +//-------------------------------------------------------------------------------- +void DNA::RegisterConverters() { + + converters["Object"] = DNA::FactoryPair(&Structure::Allocate<Object>, &Structure::Convert<Object>); + converters["Group"] = DNA::FactoryPair(&Structure::Allocate<Group>, &Structure::Convert<Group>); + converters["MTex"] = DNA::FactoryPair(&Structure::Allocate<MTex>, &Structure::Convert<MTex>); + converters["TFace"] = DNA::FactoryPair(&Structure::Allocate<TFace>, &Structure::Convert<TFace>); + converters["SubsurfModifierData"] = DNA::FactoryPair(&Structure::Allocate<SubsurfModifierData>, &Structure::Convert<SubsurfModifierData>); + converters["MFace"] = DNA::FactoryPair(&Structure::Allocate<MFace>, &Structure::Convert<MFace>); + converters["Lamp"] = DNA::FactoryPair(&Structure::Allocate<Lamp>, &Structure::Convert<Lamp>); + converters["MDeformWeight"] = DNA::FactoryPair(&Structure::Allocate<MDeformWeight>, &Structure::Convert<MDeformWeight>); + converters["PackedFile"] = DNA::FactoryPair(&Structure::Allocate<PackedFile>, &Structure::Convert<PackedFile>); + converters["Base"] = DNA::FactoryPair(&Structure::Allocate<Base>, &Structure::Convert<Base>); + converters["MTFace"] = DNA::FactoryPair(&Structure::Allocate<MTFace>, &Structure::Convert<MTFace>); + converters["Material"] = DNA::FactoryPair(&Structure::Allocate<Material>, &Structure::Convert<Material>); + converters["MTexPoly"] = DNA::FactoryPair(&Structure::Allocate<MTexPoly>, &Structure::Convert<MTexPoly>); + converters["Mesh"] = DNA::FactoryPair(&Structure::Allocate<Mesh>, &Structure::Convert<Mesh>); + converters["MDeformVert"] = DNA::FactoryPair(&Structure::Allocate<MDeformVert>, &Structure::Convert<MDeformVert>); + converters["World"] = DNA::FactoryPair(&Structure::Allocate<World>, &Structure::Convert<World>); + converters["MLoopCol"] = DNA::FactoryPair(&Structure::Allocate<MLoopCol>, &Structure::Convert<MLoopCol>); + converters["MVert"] = DNA::FactoryPair(&Structure::Allocate<MVert>, &Structure::Convert<MVert>); + converters["MEdge"] = DNA::FactoryPair(&Structure::Allocate<MEdge>, &Structure::Convert<MEdge>); + converters["MLoopUV"] = DNA::FactoryPair(&Structure::Allocate<MLoopUV>, &Structure::Convert<MLoopUV>); + converters["GroupObject"] = DNA::FactoryPair(&Structure::Allocate<GroupObject>, &Structure::Convert<GroupObject>); + converters["ListBase"] = DNA::FactoryPair(&Structure::Allocate<ListBase>, &Structure::Convert<ListBase>); + converters["MLoop"] = DNA::FactoryPair(&Structure::Allocate<MLoop>, &Structure::Convert<MLoop>); + converters["ModifierData"] = DNA::FactoryPair(&Structure::Allocate<ModifierData>, &Structure::Convert<ModifierData>); + converters["ID"] = DNA::FactoryPair(&Structure::Allocate<ID>, &Structure::Convert<ID>); + converters["MCol"] = DNA::FactoryPair(&Structure::Allocate<MCol>, &Structure::Convert<MCol>); + converters["MPoly"] = DNA::FactoryPair(&Structure::Allocate<MPoly>, &Structure::Convert<MPoly>); + converters["Scene"] = DNA::FactoryPair(&Structure::Allocate<Scene>, &Structure::Convert<Scene>); + converters["Library"] = DNA::FactoryPair(&Structure::Allocate<Library>, &Structure::Convert<Library>); + converters["Tex"] = DNA::FactoryPair(&Structure::Allocate<Tex>, &Structure::Convert<Tex>); + converters["Camera"] = DNA::FactoryPair(&Structure::Allocate<Camera>, &Structure::Convert<Camera>); + converters["MirrorModifierData"] = DNA::FactoryPair(&Structure::Allocate<MirrorModifierData>, &Structure::Convert<MirrorModifierData>); + converters["Image"] = DNA::FactoryPair(&Structure::Allocate<Image>, &Structure::Convert<Image>); + converters["CustomData"] = DNA::FactoryPair(&Structure::Allocate<CustomData>, &Structure::Convert<CustomData>); + converters["CustomDataLayer"] = DNA::FactoryPair(&Structure::Allocate<CustomDataLayer>, &Structure::Convert<CustomDataLayer>); + converters["Collection"] = DNA::FactoryPair(&Structure::Allocate<Collection>, &Structure::Convert<Collection>); + converters["CollectionChild"] = DNA::FactoryPair(&Structure::Allocate<CollectionChild>, &Structure::Convert<CollectionChild>); + converters["CollectionObject"] = DNA::FactoryPair(&Structure::Allocate<CollectionObject>, &Structure::Convert<CollectionObject>); +} + +} // namespace Blender +} //namespace Assimp + +#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER diff --git a/libs/assimp/code/AssetLib/Blender/BlenderScene.h b/libs/assimp/code/AssetLib/Blender/BlenderScene.h new file mode 100644 index 0000000..c153d3c --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderScene.h @@ -0,0 +1,983 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BlenderScene.h + * @brief Intermediate representation of a BLEND scene. + */ +#ifndef INCLUDED_AI_BLEND_SCENE_H +#define INCLUDED_AI_BLEND_SCENE_H + +#include "BlenderDNA.h" + +namespace Assimp { +namespace Blender { + +// Minor parts of this file are extracts from blender data structures, +// declared in the ./source/blender/makesdna directory. +// Stuff that is not used by Assimp is commented. + +// NOTE +// this file serves as input data to the `./scripts/genblenddna.py` +// script. This script generates the actual binding code to read a +// blender file with a possibly different DNA into our structures. +// Only `struct` declarations are considered and the following +// rules must be obeyed in order for the script to work properly: +// +// * C++ style comments only +// +// * Structures may include the primitive types char, int, short, +// float, double. Signed specifiers are not allowed on +// integers. Enum types are allowed, but they must have been +// defined in this header. +// +// * Structures may aggregate other structures, unless not defined +// in this header. +// +// * Pointers to other structures or primitive types are allowed. +// No references or double pointers or arrays of pointers. +// A pointer to a T is normally written as std::shared_ptr, while a +// pointer to an array of elements is written as boost:: +// shared_array. To avoid cyclic pointers, use raw pointers in +// one direction. +// +// * Arrays can have maximally two-dimensions. Any non-pointer +// type can form them. +// +// * Multiple fields can be declare in a single line (i.e `int a,b;`) +// provided they are neither pointers nor arrays. +// +// * One of WARN, FAIL can be appended to the declaration ( +// prior to the semicolon to specify the error handling policy if +// this field is missing in the input DNA). If none of those +// is specified the default policy is to substitute a default +// value for the field. +// + +// warn if field is missing, substitute default value +#ifdef WARN +#undef WARN +#endif +#define WARN + +// fail the import if the field does not exist +#ifdef FAIL +#undef FAIL +#endif +#define FAIL + +struct Object; +struct MTex; +struct Image; +struct Collection; + +#include <memory> + +#define AI_BLEND_MESH_MAX_VERTS 2000000000L + +static const size_t MaxNameLen = 1024; + +// ------------------------------------------------------------------------------- +struct ID : ElemBase { + char name[MaxNameLen] WARN; + short flag; +}; + +// ------------------------------------------------------------------------------- +struct ListBase : ElemBase { + std::shared_ptr<ElemBase> first; + std::shared_ptr<ElemBase> last; +}; + +// ------------------------------------------------------------------------------- +struct PackedFile : ElemBase { + int size WARN; + int seek WARN; + std::shared_ptr<FileOffset> data WARN; +}; + +// ------------------------------------------------------------------------------- +struct GroupObject : ElemBase { + std::shared_ptr<GroupObject> prev, next FAIL; + std::shared_ptr<Object> ob; +}; + +// ------------------------------------------------------------------------------- +struct Group : ElemBase { + ID id FAIL; + int layer; + + std::shared_ptr<GroupObject> gobject; +}; + +// ------------------------------------------------------------------------------- +struct CollectionObject : ElemBase { + //CollectionObject* prev; + std::shared_ptr<CollectionObject> next; + Object *ob; +}; + +// ------------------------------------------------------------------------------- +struct CollectionChild : ElemBase { + std::shared_ptr<CollectionChild> next, prev; + std::shared_ptr<Collection> collection; +}; + +// ------------------------------------------------------------------------------- +struct Collection : ElemBase { + ID id FAIL; + ListBase gobject; // CollectionObject + ListBase children; // CollectionChild +}; + +// ------------------------------------------------------------------------------- +struct World : ElemBase { + ID id FAIL; +}; + +// ------------------------------------------------------------------------------- +struct MVert : ElemBase { + float co[3] FAIL; + float no[3] FAIL; // read as short and divided through / 32767.f + char flag; + int mat_nr WARN; + int bweight; + + MVert() : + ElemBase(), flag(0), mat_nr(0), bweight(0) {} +}; + +// ------------------------------------------------------------------------------- +struct MEdge : ElemBase { + int v1, v2 FAIL; + char crease, bweight; + short flag; +}; + +// ------------------------------------------------------------------------------- +struct MLoop : ElemBase { + int v, e; +}; + +// ------------------------------------------------------------------------------- +struct MLoopUV : ElemBase { + float uv[2]; + int flag; +}; + +// ------------------------------------------------------------------------------- +// Note that red and blue are not swapped, as with MCol +struct MLoopCol : ElemBase { + unsigned char r, g, b, a; +}; + +// ------------------------------------------------------------------------------- +struct MPoly : ElemBase { + int loopstart; + int totloop; + short mat_nr; + char flag; +}; + +// ------------------------------------------------------------------------------- +struct MTexPoly : ElemBase { + Image *tpage; + char flag, transp; + short mode, tile, pad; +}; + +// ------------------------------------------------------------------------------- +struct MCol : ElemBase { + char r, g, b, a FAIL; +}; + +// ------------------------------------------------------------------------------- +struct MFace : ElemBase { + int v1, v2, v3, v4 FAIL; + int mat_nr FAIL; + char flag; +}; + +// ------------------------------------------------------------------------------- +struct TFace : ElemBase { + float uv[4][2] FAIL; + int col[4] FAIL; + char flag; + short mode; + short tile; + short unwrap; +}; + +// ------------------------------------------------------------------------------- +struct MTFace : ElemBase { + MTFace() : + flag(0), + mode(0), + tile(0), + unwrap(0) { + } + + float uv[4][2] FAIL; + char flag; + short mode; + short tile; + short unwrap; + + // std::shared_ptr<Image> tpage; +}; + +// ------------------------------------------------------------------------------- +struct MDeformWeight : ElemBase { + int def_nr FAIL; + float weight FAIL; +}; + +// ------------------------------------------------------------------------------- +struct MDeformVert : ElemBase { + vector<MDeformWeight> dw WARN; + int totweight; +}; + +// ------------------------------------------------------------------------------- +#define MA_RAYMIRROR 0x40000 +#define MA_TRANSPARENCY 0x10000 +#define MA_RAYTRANSP 0x20000 +#define MA_ZTRANSP 0x00040 + +struct Material : ElemBase { + ID id FAIL; + + float r, g, b WARN; + float specr, specg, specb WARN; + short har; + float ambr, ambg, ambb WARN; + float mirr, mirg, mirb; + float emit WARN; + float ray_mirror; + float alpha WARN; + float ref; + float translucency; + int mode; + float roughness; + float darkness; + float refrac; + + float amb; + float ang; + float spectra; + float spec; + float zoffs; + float add; + float fresnel_mir; + float fresnel_mir_i; + float fresnel_tra; + float fresnel_tra_i; + float filter; + float tx_limit; + float tx_falloff; + float gloss_mir; + float gloss_tra; + float adapt_thresh_mir; + float adapt_thresh_tra; + float aniso_gloss_mir; + float dist_mir; + float hasize; + float flaresize; + float subsize; + float flareboost; + float strand_sta; + float strand_end; + float strand_ease; + float strand_surfnor; + float strand_min; + float strand_widthfade; + float sbias; + float lbias; + float shad_alpha; + float param; + float rms; + float rampfac_col; + float rampfac_spec; + float friction; + float fh; + float reflect; + float fhdist; + float xyfrict; + float sss_radius; + float sss_col; + float sss_error; + float sss_scale; + float sss_ior; + float sss_colfac; + float sss_texfac; + float sss_front; + float sss_back; + + short material_type; + short flag; + short ray_depth; + short ray_depth_tra; + short samp_gloss_mir; + short samp_gloss_tra; + short fadeto_mir; + short shade_flag; + short flarec; + short starc; + short linec; + short ringc; + short pr_lamp; + short pr_texture; + short ml_flag; + short texco; + short mapto; + short ramp_show; + short pad3; + short dynamode; + short pad2; + short sss_flag; + short sss_preset; + short shadowonly_flag; + short index; + short vcol_alpha; + short pad4; + + char seed1; + char seed2; + + std::shared_ptr<Group> group; + + short diff_shader WARN; + short spec_shader WARN; + + std::shared_ptr<MTex> mtex[18]; +}; + +/* +CustomDataLayer 104 + + int type 0 4 + int offset 4 4 + int flag 8 4 + int active 12 4 + int active_rnd 16 4 + int active_clone 20 4 + int active_mask 24 4 + int uid 28 4 + char name 32 64 + void *data 96 8 +*/ +struct CustomDataLayer : ElemBase { + int type; + int offset; + int flag; + int active; + int active_rnd; + int active_clone; + int active_mask; + int uid; + char name[64]; + std::shared_ptr<ElemBase> data; // must be converted to real type according type member + + CustomDataLayer() : + ElemBase(), + type(0), + offset(0), + flag(0), + active(0), + active_rnd(0), + active_clone(0), + active_mask(0), + uid(0), + data(nullptr) { + memset(name, 0, sizeof name); + } +}; + +/* +CustomData 208 + + CustomDataLayer *layers 0 8 + int typemap 8 168 + int pad_i1 176 4 + int totlayer 180 4 + int maxlayer 184 4 + int totsize 188 4 + BLI_mempool *pool 192 8 + CustomDataExternal *external 200 8 +*/ +struct CustomData : ElemBase { + vector<std::shared_ptr<struct CustomDataLayer>> layers; + int typemap[42]; // CD_NUMTYPES + int totlayer; + int maxlayer; + int totsize; + /* + std::shared_ptr<BLI_mempool> pool; + std::shared_ptr<CustomDataExternal> external; + */ +}; + +// ------------------------------------------------------------------------------- +struct Mesh : ElemBase { + ID id FAIL; + + int totface FAIL; + int totedge FAIL; + int totvert FAIL; + int totloop; + int totpoly; + + short subdiv; + short subdivr; + short subsurftype; + short smoothresh; + + vector<MFace> mface FAIL; + vector<MTFace> mtface; + vector<TFace> tface; + vector<MVert> mvert FAIL; + vector<MEdge> medge WARN; + vector<MLoop> mloop; + vector<MLoopUV> mloopuv; + vector<MLoopCol> mloopcol; + vector<MPoly> mpoly; + vector<MTexPoly> mtpoly; + vector<MDeformVert> dvert; + vector<MCol> mcol; + + vector<std::shared_ptr<Material>> mat FAIL; + + struct CustomData vdata; + struct CustomData edata; + struct CustomData fdata; + struct CustomData pdata; + struct CustomData ldata; +}; + +// ------------------------------------------------------------------------------- +struct Library : ElemBase { + ID id FAIL; + + char name[240] WARN; + char filename[240] FAIL; + std::shared_ptr<Library> parent WARN; +}; + +// ------------------------------------------------------------------------------- +struct Camera : ElemBase { + enum Type { + Type_PERSP = 0, + Type_ORTHO = 1 + }; + + ID id FAIL; + + Type type, flag WARN; + float lens WARN; + float sensor_x WARN; + float clipsta, clipend; +}; + +// ------------------------------------------------------------------------------- +struct Lamp : ElemBase { + + enum FalloffType { + FalloffType_Constant = 0x0, + FalloffType_InvLinear = 0x1, + FalloffType_InvSquare = 0x2 + //,FalloffType_Curve = 0x3 + //,FalloffType_Sliders = 0x4 + }; + + enum Type { + Type_Local = 0x0, + Type_Sun = 0x1, + Type_Spot = 0x2, + Type_Hemi = 0x3, + Type_Area = 0x4 + //,Type_YFPhoton = 0x5 + }; + + ID id FAIL; + //AnimData *adt; + + Type type FAIL; + short flags; + + //int mode; + + short colormodel, totex; + float r, g, b, k WARN; + //float shdwr, shdwg, shdwb; + + float energy, dist, spotsize, spotblend; + //float haint; + + float constant_coefficient; + float linear_coefficient; + float quadratic_coefficient; + + float att1, att2; + //struct CurveMapping *curfalloff; + FalloffType falloff_type; + + //float clipsta, clipend, shadspotsize; + //float bias, soft, compressthresh; + //short bufsize, samp, buffers, filtertype; + //char bufflag, buftype; + + //short ray_samp, ray_sampy, ray_sampz; + //short ray_samp_type; + short area_shape; + float area_size, area_sizey, area_sizez; + //float adapt_thresh; + //short ray_samp_method; + + //short texact, shadhalostep; + + //short sun_effect_type; + //short skyblendtype; + //float horizon_brightness; + //float spread; + float sun_brightness; + //float sun_size; + //float backscattered_light; + //float sun_intensity; + //float atm_turbidity; + //float atm_inscattering_factor; + //float atm_extinction_factor; + //float atm_distance_factor; + //float skyblendfac; + //float sky_exposure; + //short sky_colorspace; + + // int YF_numphotons, YF_numsearch; + // short YF_phdepth, YF_useqmc, YF_bufsize, YF_pad; + // float YF_causticblur, YF_ltradius; + + // float YF_glowint, YF_glowofs; + // short YF_glowtype, YF_pad2; + + //struct Ipo *ipo; + //struct MTex *mtex[18]; + // short pr_texture; + + //struct PreviewImage *preview; +}; + +// ------------------------------------------------------------------------------- +struct ModifierData : ElemBase { + enum ModifierType { + eModifierType_None = 0, + eModifierType_Subsurf, + eModifierType_Lattice, + eModifierType_Curve, + eModifierType_Build, + eModifierType_Mirror, + eModifierType_Decimate, + eModifierType_Wave, + eModifierType_Armature, + eModifierType_Hook, + eModifierType_Softbody, + eModifierType_Boolean, + eModifierType_Array, + eModifierType_EdgeSplit, + eModifierType_Displace, + eModifierType_UVProject, + eModifierType_Smooth, + eModifierType_Cast, + eModifierType_MeshDeform, + eModifierType_ParticleSystem, + eModifierType_ParticleInstance, + eModifierType_Explode, + eModifierType_Cloth, + eModifierType_Collision, + eModifierType_Bevel, + eModifierType_Shrinkwrap, + eModifierType_Fluidsim, + eModifierType_Mask, + eModifierType_SimpleDeform, + eModifierType_Multires, + eModifierType_Surface, + eModifierType_Smoke, + eModifierType_ShapeKey + }; + + std::shared_ptr<ElemBase> next WARN; + std::shared_ptr<ElemBase> prev WARN; + + int type, mode; + char name[32]; +}; + +// ------------------------------------------------------------------------------- +struct SubsurfModifierData : ElemBase { + + enum Type { + + TYPE_CatmullClarke = 0x0, + TYPE_Simple = 0x1 + }; + + enum Flags { + // some omitted + FLAGS_SubsurfUV = 1 << 3 + }; + + ModifierData modifier FAIL; + short subdivType WARN; + short levels FAIL; + short renderLevels; + short flags; +}; + +// ------------------------------------------------------------------------------- +struct MirrorModifierData : ElemBase { + + enum Flags { + Flags_CLIPPING = 1 << 0, + Flags_MIRROR_U = 1 << 1, + Flags_MIRROR_V = 1 << 2, + Flags_AXIS_X = 1 << 3, + Flags_AXIS_Y = 1 << 4, + Flags_AXIS_Z = 1 << 5, + Flags_VGROUP = 1 << 6 + }; + + ModifierData modifier FAIL; + + short axis, flag; + float tolerance; + std::shared_ptr<Object> mirror_ob; +}; + +// ------------------------------------------------------------------------------- +struct Object : ElemBase { + ID id FAIL; + + enum Type { + Type_EMPTY = 0, + Type_MESH = 1, + Type_CURVE = 2, + Type_SURF = 3, + Type_FONT = 4, + Type_MBALL = 5 + + , + Type_LAMP = 10, + Type_CAMERA = 11 + + , + Type_WAVE = 21, + Type_LATTICE = 22 + }; + + Type type FAIL; + float obmat[4][4] WARN; + float parentinv[4][4] WARN; + char parsubstr[32] WARN; + + Object *parent WARN; + std::shared_ptr<Object> track WARN; + + std::shared_ptr<Object> proxy, proxy_from, proxy_group WARN; + std::shared_ptr<Group> dup_group WARN; + std::shared_ptr<ElemBase> data FAIL; + + ListBase modifiers; + + Object() : + ElemBase(), type(Type_EMPTY), parent(nullptr), track(), proxy(), proxy_from(), data() { + // empty + } +}; + +// ------------------------------------------------------------------------------- +struct Base : ElemBase { + Base *prev WARN; + std::shared_ptr<Base> next WARN; + std::shared_ptr<Object> object WARN; + + Base() : + ElemBase(), prev(nullptr), next(), object() { + // empty + // empty + } +}; + +// ------------------------------------------------------------------------------- +struct Scene : ElemBase { + ID id FAIL; + + std::shared_ptr<Object> camera WARN; + std::shared_ptr<World> world WARN; + std::shared_ptr<Base> basact WARN; + std::shared_ptr<Collection> master_collection WARN; + + ListBase base; + + Scene() : + ElemBase(), camera(), world(), basact(), master_collection() { + // empty + } +}; + +// ------------------------------------------------------------------------------- +struct Image : ElemBase { + ID id FAIL; + + char name[240] WARN; + + //struct anim *anim; + + short ok, flag; + short source, type, pad, pad1; + int lastframe; + + short tpageflag, totbind; + short xrep, yrep; + short twsta, twend; + //unsigned int bindcode; + //unsigned int *repbind; + + std::shared_ptr<PackedFile> packedfile; + //struct PreviewImage * preview; + + float lastupdate; + int lastused; + short animspeed; + + short gen_x, gen_y, gen_type; + + Image() : + ElemBase() { + // empty + } +}; + +// ------------------------------------------------------------------------------- +struct Tex : ElemBase { + + // actually, the only texture type we support is Type_IMAGE + enum Type { + Type_CLOUDS = 1, + Type_WOOD = 2, + Type_MARBLE = 3, + Type_MAGIC = 4, + Type_BLEND = 5, + Type_STUCCI = 6, + Type_NOISE = 7, + Type_IMAGE = 8, + Type_PLUGIN = 9, + Type_ENVMAP = 10, + Type_MUSGRAVE = 11, + Type_VORONOI = 12, + Type_DISTNOISE = 13, + Type_POINTDENSITY = 14, + Type_VOXELDATA = 15 + }; + + enum ImageFlags { + ImageFlags_INTERPOL = 1, + ImageFlags_USEALPHA = 2, + ImageFlags_MIPMAP = 4, + ImageFlags_IMAROT = 16, + ImageFlags_CALCALPHA = 32, + ImageFlags_NORMALMAP = 2048, + ImageFlags_GAUSS_MIP = 4096, + ImageFlags_FILTER_MIN = 8192, + ImageFlags_DERIVATIVEMAP = 16384 + }; + + ID id FAIL; + // AnimData *adt; + + //float noisesize, turbul; + //float bright, contrast, rfac, gfac, bfac; + //float filtersize; + + //float mg_H, mg_lacunarity, mg_octaves, mg_offset, mg_gain; + //float dist_amount, ns_outscale; + + //float vn_w1; + //float vn_w2; + //float vn_w3; + //float vn_w4; + //float vn_mexp; + //short vn_distm, vn_coltype; + + //short noisedepth, noisetype; + //short noisebasis, noisebasis2; + + //short flag; + ImageFlags imaflag; + Type type FAIL; + //short stype; + + //float cropxmin, cropymin, cropxmax, cropymax; + //int texfilter; + //int afmax; + //short xrepeat, yrepeat; + //short extend; + + //short fie_ima; + //int len; + //int frames, offset, sfra; + + //float checkerdist, nabla; + //float norfac; + + //ImageUser iuser; + + //bNodeTree *nodetree; + //Ipo *ipo; + std::shared_ptr<Image> ima WARN; + //PluginTex *plugin; + //ColorBand *coba; + //EnvMap *env; + //PreviewImage * preview; + //PointDensity *pd; + //VoxelData *vd; + + //char use_nodes; + + Tex() : + ElemBase(), imaflag(ImageFlags_INTERPOL), type(Type_CLOUDS), ima() { + // empty + } +}; + +// ------------------------------------------------------------------------------- +struct MTex : ElemBase { + + enum Projection { + Proj_N = 0, + Proj_X = 1, + Proj_Y = 2, + Proj_Z = 3 + }; + + enum Flag { + Flag_RGBTOINT = 0x1, + Flag_STENCIL = 0x2, + Flag_NEGATIVE = 0x4, + Flag_ALPHAMIX = 0x8, + Flag_VIEWSPACE = 0x10 + }; + + enum BlendType { + BlendType_BLEND = 0, + BlendType_MUL = 1, + BlendType_ADD = 2, + BlendType_SUB = 3, + BlendType_DIV = 4, + BlendType_DARK = 5, + BlendType_DIFF = 6, + BlendType_LIGHT = 7, + BlendType_SCREEN = 8, + BlendType_OVERLAY = 9, + BlendType_BLEND_HUE = 10, + BlendType_BLEND_SAT = 11, + BlendType_BLEND_VAL = 12, + BlendType_BLEND_COLOR = 13 + }; + + enum MapType { + MapType_COL = 1, + MapType_NORM = 2, + MapType_COLSPEC = 4, + MapType_COLMIR = 8, + MapType_REF = 16, + MapType_SPEC = 32, + MapType_EMIT = 64, + MapType_ALPHA = 128, + MapType_HAR = 256, + MapType_RAYMIRR = 512, + MapType_TRANSLU = 1024, + MapType_AMB = 2048, + MapType_DISPLACE = 4096, + MapType_WARP = 8192 + }; + + // short texco, maptoneg; + MapType mapto; + + BlendType blendtype; + std::shared_ptr<Object> object; + std::shared_ptr<Tex> tex; + char uvname[32]; + + Projection projx, projy, projz; + char mapping; + float ofs[3], size[3], rot; + + int texflag; + short colormodel, pmapto, pmaptoneg; + //short normapspace, which_output; + //char brush_map_mode; + float r, g, b, k WARN; + //float def_var, rt; + + //float colfac, varfac; + + float norfac; + //float dispfac, warpfac; + float colspecfac, mirrfac, alphafac; + float difffac, specfac, emitfac, hardfac; + //float raymirrfac, translfac, ambfac; + //float colemitfac, colreflfac, coltransfac; + //float densfac, scatterfac, reflfac; + + //float timefac, lengthfac, clumpfac; + //float kinkfac, roughfac, padensfac; + //float lifefac, sizefac, ivelfac, pvelfac; + //float shadowfac; + //float zenupfac, zendownfac, blendfac; + + MTex() : + ElemBase() { + // empty + } +}; + +} // namespace Blender +} // namespace Assimp +#endif diff --git a/libs/assimp/code/AssetLib/Blender/BlenderSceneGen.h b/libs/assimp/code/AssetLib/Blender/BlenderSceneGen.h new file mode 100644 index 0000000..762fdd3 --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderSceneGen.h @@ -0,0 +1,272 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, ASSIMP Development 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 Development 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 BlenderSceneGen.h + * @brief MACHINE GENERATED BY ./scripts/BlenderImporter/genblenddna.py + */ +#ifndef INCLUDED_AI_BLEND_SCENEGEN_H +#define INCLUDED_AI_BLEND_SCENEGEN_H + +#include "BlenderDNA.h" +#include "BlenderScene.h" + +namespace Assimp { +namespace Blender { + +template <> void Structure :: Convert<Object> ( + Object& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<Group> ( + Group& dest, + const FileDatabase& db + ) const +; + +template <> void Structure::Convert<Collection>( + Collection& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MTex> ( + MTex& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<TFace> ( + TFace& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<SubsurfModifierData> ( + SubsurfModifierData& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MFace> ( + MFace& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<Lamp> ( + Lamp& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MDeformWeight> ( + MDeformWeight& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<PackedFile> ( + PackedFile& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<Base> ( + Base& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MTFace> ( + MTFace& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<Material> ( + Material& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MTexPoly> ( + MTexPoly& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<Mesh> ( + Mesh& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MDeformVert> ( + MDeformVert& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<World> ( + World& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MLoopCol> ( + MLoopCol& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MVert> ( + MVert& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MEdge> ( + MEdge& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MLoopUV> ( + MLoopUV& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<GroupObject> ( + GroupObject& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<ListBase> ( + ListBase& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MLoop> ( + MLoop& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<ModifierData> ( + ModifierData& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<ID> ( + ID& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MCol> ( + MCol& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MPoly> ( + MPoly& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<Scene> ( + Scene& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<Library> ( + Library& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<Tex> ( + Tex& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<Camera> ( + Camera& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<MirrorModifierData> ( + MirrorModifierData& dest, + const FileDatabase& db + ) const +; + +template <> void Structure :: Convert<Image> ( + Image& dest, + const FileDatabase& db + ) const +; + +template <> void Structure::Convert<CustomData>( + CustomData& dest, + const FileDatabase& db + ) const + ; + +template <> void Structure::Convert<CustomDataLayer>( + CustomDataLayer& dest, + const FileDatabase& db + ) const + ; + + } +} + +#endif diff --git a/libs/assimp/code/AssetLib/Blender/BlenderTessellator.cpp b/libs/assimp/code/AssetLib/Blender/BlenderTessellator.cpp new file mode 100644 index 0000000..d3d463e --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderTessellator.cpp @@ -0,0 +1,532 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BlenderTessellator.cpp + * @brief A simple tessellation wrapper + */ + + +#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER + +#include "BlenderDNA.h" +#include "BlenderScene.h" +#include "BlenderBMesh.h" +#include "BlenderTessellator.h" + +#include <stddef.h> + +static const unsigned int BLEND_TESS_MAGIC = 0x83ed9ac3; + +#if ASSIMP_BLEND_WITH_GLU_TESSELLATE + +namspace Assimp +{ + template< > const char* LogFunctions< BlenderTessellatorGL >::Prefix() + { + static auto prefix = "BLEND_TESS_GL: "; + return prefix; + } +} + +using namespace Assimp; +using namespace Assimp::Blender; + +#ifndef CALLBACK +#define CALLBACK +#endif + +// ------------------------------------------------------------------------------------------------ +BlenderTessellatorGL::BlenderTessellatorGL( BlenderBMeshConverter& converter ): + converter( &converter ) +{ +} + +// ------------------------------------------------------------------------------------------------ +BlenderTessellatorGL::~BlenderTessellatorGL( ) +{ +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::Tessellate( const MLoop* polyLoop, int vertexCount, const std::vector< MVert >& vertices ) +{ + AssertVertexCount( vertexCount ); + + std::vector< VertexGL > polyLoopGL; + GenerateLoopVerts( polyLoopGL, polyLoop, vertexCount, vertices ); + + TessDataGL tessData; + Tesssellate( polyLoopGL, tessData ); + + TriangulateDrawCalls( tessData ); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::AssertVertexCount( int vertexCount ) +{ + if ( vertexCount <= 4 ) + { + ThrowException( "Expected more than 4 vertices for tessellation" ); + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::GenerateLoopVerts( std::vector< VertexGL >& polyLoopGL, const MLoop* polyLoop, int vertexCount, const std::vector< MVert >& vertices ) +{ + for ( int i = 0; i < vertexCount; ++i ) + { + const MLoop& loopItem = polyLoop[ i ]; + const MVert& vertex = vertices[ loopItem.v ]; + polyLoopGL.push_back( VertexGL( vertex.co[ 0 ], vertex.co[ 1 ], vertex.co[ 2 ], loopItem.v, BLEND_TESS_MAGIC ) ); + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::Tesssellate( std::vector< VertexGL >& polyLoopGL, TessDataGL& tessData ) +{ + GLUtesselator* tessellator = gluNewTess( ); + gluTessCallback( tessellator, GLU_TESS_BEGIN_DATA, reinterpret_cast< void ( CALLBACK * )( ) >( TessellateBegin ) ); + gluTessCallback( tessellator, GLU_TESS_END_DATA, reinterpret_cast< void ( CALLBACK * )( ) >( TessellateEnd ) ); + gluTessCallback( tessellator, GLU_TESS_VERTEX_DATA, reinterpret_cast< void ( CALLBACK * )( ) >( TessellateVertex ) ); + gluTessCallback( tessellator, GLU_TESS_COMBINE_DATA, reinterpret_cast< void ( CALLBACK * )( ) >( TessellateCombine ) ); + gluTessCallback( tessellator, GLU_TESS_EDGE_FLAG_DATA, reinterpret_cast< void ( CALLBACK * )( ) >( TessellateEdgeFlag ) ); + gluTessCallback( tessellator, GLU_TESS_ERROR_DATA, reinterpret_cast< void ( CALLBACK * )( ) >( TessellateError ) ); + gluTessProperty( tessellator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO ); + + gluTessBeginPolygon( tessellator, &tessData ); + gluTessBeginContour( tessellator ); + + for ( unsigned int i = 0; i < polyLoopGL.size( ); ++i ) + { + gluTessVertex( tessellator, reinterpret_cast< GLdouble* >( &polyLoopGL[ i ] ), &polyLoopGL[ i ] ); + } + + gluTessEndContour( tessellator ); + gluTessEndPolygon( tessellator ); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::TriangulateDrawCalls( const TessDataGL& tessData ) +{ + // NOTE - Because we are supplying a callback to GLU_TESS_EDGE_FLAG_DATA we don't technically + // need support for GL_TRIANGLE_STRIP and GL_TRIANGLE_FAN but we'll keep it here in case + // GLU tessellate changes or tri-strips and fans are wanted. + // See: http://www.opengl.org/sdk/docs/man2/xhtml/gluTessCallback.xml + for ( unsigned int i = 0; i < tessData.drawCalls.size( ); ++i ) + { + const DrawCallGL& drawCallGL = tessData.drawCalls[ i ]; + const VertexGL* vertices = &tessData.vertices[ drawCallGL.baseVertex ]; + if ( drawCallGL.drawMode == GL_TRIANGLES ) + { + MakeFacesFromTris( vertices, drawCallGL.vertexCount ); + } + else if ( drawCallGL.drawMode == GL_TRIANGLE_STRIP ) + { + MakeFacesFromTriStrip( vertices, drawCallGL.vertexCount ); + } + else if ( drawCallGL.drawMode == GL_TRIANGLE_FAN ) + { + MakeFacesFromTriFan( vertices, drawCallGL.vertexCount ); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::MakeFacesFromTris( const VertexGL* vertices, int vertexCount ) +{ + const int triangleCount = vertexCount / 3; + for ( int i = 0; i < triangleCount; ++i ) + { + int vertexBase = i * 3; + converter->AddFace( vertices[ vertexBase + 0 ].index, vertices[ vertexBase + 1 ].index, vertices[ vertexBase + 2 ].index ); + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::MakeFacesFromTriStrip( const VertexGL* vertices, int vertexCount ) +{ + const int triangleCount = vertexCount - 2; + for ( int i = 0; i < triangleCount; ++i ) + { + int vertexBase = i; + converter->AddFace( vertices[ vertexBase + 0 ].index, vertices[ vertexBase + 1 ].index, vertices[ vertexBase + 2 ].index ); + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::MakeFacesFromTriFan( const VertexGL* vertices, int vertexCount ) +{ + const int triangleCount = vertexCount - 2; + for ( int i = 0; i < triangleCount; ++i ) + { + int vertexBase = i; + converter->AddFace( vertices[ 0 ].index, vertices[ vertexBase + 1 ].index, vertices[ vertexBase + 2 ].index ); + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::TessellateBegin( GLenum drawModeGL, void* userData ) +{ + TessDataGL& tessData = *reinterpret_cast< TessDataGL* >( userData ); + tessData.drawCalls.push_back( DrawCallGL( drawModeGL, tessData.vertices.size( ) ) ); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::TessellateEnd( void* ) +{ + // Do nothing +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::TessellateVertex( const void* vtxData, void* userData ) +{ + TessDataGL& tessData = *reinterpret_cast< TessDataGL* >( userData ); + + const VertexGL& vertex = *reinterpret_cast< const VertexGL* >( vtxData ); + if ( vertex.magic != BLEND_TESS_MAGIC ) + { + ThrowException( "Point returned by GLU Tessellate was probably not one of ours. This indicates we need a new way to store vertex information" ); + } + tessData.vertices.push_back( vertex ); + if ( tessData.drawCalls.size( ) == 0 ) + { + ThrowException( "\"Vertex\" callback received before \"Begin\"" ); + } + ++( tessData.drawCalls.back( ).vertexCount ); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::TessellateCombine( const GLdouble intersection[ 3 ], const GLdouble* [ 4 ], const GLfloat [ 4 ], GLdouble** out, void* userData ) +{ + ThrowException( "Intersected polygon loops are not yet supported" ); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::TessellateEdgeFlag( GLboolean, void* ) +{ + // Do nothing +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorGL::TessellateError( GLenum errorCode, void* ) +{ + ThrowException( reinterpret_cast< const char* >( gluErrorString( errorCode ) ) ); +} + +#endif // ASSIMP_BLEND_WITH_GLU_TESSELLATE + +#if ASSIMP_BLEND_WITH_POLY_2_TRI + +namespace Assimp +{ + template< > const char* LogFunctions< BlenderTessellatorP2T >::Prefix() + { + static auto prefix = "BLEND_TESS_P2T: "; + return prefix; + } +} + +using namespace Assimp; +using namespace Assimp::Blender; + +// ------------------------------------------------------------------------------------------------ +BlenderTessellatorP2T::BlenderTessellatorP2T( BlenderBMeshConverter& converter ): + converter( &converter ) +{ +} + +// ------------------------------------------------------------------------------------------------ +BlenderTessellatorP2T::~BlenderTessellatorP2T( ) +{ +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorP2T::Tessellate( const MLoop* polyLoop, int vertexCount, const std::vector< MVert >& vertices ) +{ + AssertVertexCount( vertexCount ); + + // NOTE - We have to hope that points in a Blender polygon are roughly on the same plane. + // There may be some triangulation artifacts if they are wildly different. + + std::vector< PointP2T > points; + Copy3DVertices( polyLoop, vertexCount, vertices, points ); + + PlaneP2T plane = FindLLSQPlane( points ); + + aiMatrix4x4 transform = GeneratePointTransformMatrix( plane ); + + TransformAndFlattenVectices( transform, points ); + + std::vector< p2t::Point* > pointRefs; + ReferencePoints( points, pointRefs ); + + p2t::CDT cdt( pointRefs ); + + cdt.Triangulate( ); + std::vector< p2t::Triangle* > triangles = cdt.GetTriangles( ); + + MakeFacesFromTriangles( triangles ); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorP2T::AssertVertexCount( int vertexCount ) +{ + if ( vertexCount <= 4 ) + { + ThrowException( "Expected more than 4 vertices for tessellation" ); + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorP2T::Copy3DVertices( const MLoop* polyLoop, int vertexCount, const std::vector< MVert >& vertices, std::vector< PointP2T >& points ) const +{ + points.resize( vertexCount ); + for ( int i = 0; i < vertexCount; ++i ) + { + const MLoop& loop = polyLoop[ i ]; + const MVert& vert = vertices[ loop.v ]; + + PointP2T& point = points[ i ]; + point.point3D.Set( vert.co[ 0 ], vert.co[ 1 ], vert.co[ 2 ] ); + point.index = loop.v; + point.magic = BLEND_TESS_MAGIC; + } +} + +// ------------------------------------------------------------------------------------------------ +aiMatrix4x4 BlenderTessellatorP2T::GeneratePointTransformMatrix( const Blender::PlaneP2T& plane ) const +{ + aiVector3D sideA( 1.0f, 0.0f, 0.0f ); + if ( std::fabs( plane.normal * sideA ) > 0.999f ) + { + sideA = aiVector3D( 0.0f, 1.0f, 0.0f ); + } + + aiVector3D sideB( plane.normal ^ sideA ); + sideB.Normalize( ); + sideA = sideB ^ plane.normal; + + aiMatrix4x4 result; + result.a1 = sideA.x; + result.a2 = sideA.y; + result.a3 = sideA.z; + result.b1 = sideB.x; + result.b2 = sideB.y; + result.b3 = sideB.z; + result.c1 = plane.normal.x; + result.c2 = plane.normal.y; + result.c3 = plane.normal.z; + result.a4 = plane.centre.x; + result.b4 = plane.centre.y; + result.c4 = plane.centre.z; + result.Inverse( ); + + return result; +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorP2T::TransformAndFlattenVectices( const aiMatrix4x4& transform, std::vector< Blender::PointP2T >& vertices ) const +{ + for ( size_t i = 0; i < vertices.size( ); ++i ) + { + PointP2T& point = vertices[ i ]; + point.point3D = transform * point.point3D; + point.point2D.set( point.point3D.y, point.point3D.z ); + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorP2T::ReferencePoints( std::vector< Blender::PointP2T >& points, std::vector< p2t::Point* >& pointRefs ) const +{ + pointRefs.resize( points.size( ) ); + for ( size_t i = 0; i < points.size( ); ++i ) + { + pointRefs[ i ] = &points[ i ].point2D; + } +} + +// ------------------------------------------------------------------------------------------------ +inline PointP2T& BlenderTessellatorP2T::GetActualPointStructure( p2t::Point& point ) const +{ +#if defined __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Winvalid-offsetof" +#endif // __clang__ + unsigned int pointOffset = offsetof( PointP2T, point2D ); +#if defined __clang__ +# pragma clang diagnostic pop +#endif + PointP2T& pointStruct = *reinterpret_cast< PointP2T* >( reinterpret_cast< char* >( &point ) - pointOffset ); + if ( pointStruct.magic != static_cast<int>( BLEND_TESS_MAGIC ) ) + { + ThrowException( "Point returned by poly2tri was probably not one of ours. This indicates we need a new way to store vertex information" ); + } + return pointStruct; +} +// ------------------------------------------------------------------------------------------------ +void BlenderTessellatorP2T::MakeFacesFromTriangles( std::vector< p2t::Triangle* >& triangles ) const +{ + for ( size_t i = 0; i < triangles.size( ); ++i ) + { + p2t::Triangle& Triangle = *triangles[ i ]; + + PointP2T& pointA = GetActualPointStructure( *Triangle.GetPoint( 0 ) ); + PointP2T& pointB = GetActualPointStructure( *Triangle.GetPoint( 1 ) ); + PointP2T& pointC = GetActualPointStructure( *Triangle.GetPoint( 2 ) ); + + converter->AddFace( pointA.index, pointB.index, pointC.index ); + } +} + +// ------------------------------------------------------------------------------------------------ +inline float p2tMax( float a, float b ) +{ + return a > b ? a : b; +} + +// ------------------------------------------------------------------------------------------------ +// Adapted from: http://missingbytes.blogspot.co.uk/2012/06/fitting-plane-to-point-cloud.html +float BlenderTessellatorP2T::FindLargestMatrixElem( const aiMatrix3x3& mtx ) const +{ + float result = 0.0f; + + for ( unsigned int x = 0; x < 3; ++x ) + { + for ( unsigned int y = 0; y < 3; ++y ) + { + result = p2tMax( std::fabs( mtx[ x ][ y ] ), result ); + } + } + + return result; +} + +// ------------------------------------------------------------------------------------------------ +// Apparently Assimp doesn't have matrix scaling +aiMatrix3x3 BlenderTessellatorP2T::ScaleMatrix( const aiMatrix3x3& mtx, float scale ) const +{ + aiMatrix3x3 result; + + for ( unsigned int x = 0; x < 3; ++x ) + { + for ( unsigned int y = 0; y < 3; ++y ) + { + result[ x ][ y ] = mtx[ x ][ y ] * scale; + } + } + + return result; +} + + +// ------------------------------------------------------------------------------------------------ +// Adapted from: http://missingbytes.blogspot.co.uk/2012/06/fitting-plane-to-point-cloud.html +aiVector3D BlenderTessellatorP2T::GetEigenVectorFromLargestEigenValue( const aiMatrix3x3& mtx ) const +{ + const float scale = FindLargestMatrixElem( mtx ); + aiMatrix3x3 mc = ScaleMatrix( mtx, 1.0f / scale ); + mc = mc * mc * mc; + + aiVector3D v( 1.0f ); + aiVector3D lastV = v; + for ( int i = 0; i < 100; ++i ) + { + v = mc * v; + v.Normalize( ); + if ( ( v - lastV ).SquareLength( ) < 1e-16f ) + { + break; + } + lastV = v; + } + return v; +} + +// ------------------------------------------------------------------------------------------------ +// Adapted from: http://missingbytes.blogspot.co.uk/2012/06/fitting-plane-to-point-cloud.html +PlaneP2T BlenderTessellatorP2T::FindLLSQPlane( const std::vector< PointP2T >& points ) const +{ + PlaneP2T result; + + aiVector3D sum( 0.0 ); + for ( size_t i = 0; i < points.size( ); ++i ) + { + sum += points[ i ].point3D; + } + result.centre = sum * (ai_real)( 1.0 / points.size( ) ); + + ai_real sumXX = 0.0; + ai_real sumXY = 0.0; + ai_real sumXZ = 0.0; + ai_real sumYY = 0.0; + ai_real sumYZ = 0.0; + ai_real sumZZ = 0.0; + for ( size_t i = 0; i < points.size( ); ++i ) + { + aiVector3D offset = points[ i ].point3D - result.centre; + sumXX += offset.x * offset.x; + sumXY += offset.x * offset.y; + sumXZ += offset.x * offset.z; + sumYY += offset.y * offset.y; + sumYZ += offset.y * offset.z; + sumZZ += offset.z * offset.z; + } + + aiMatrix3x3 mtx( sumXX, sumXY, sumXZ, sumXY, sumYY, sumYZ, sumXZ, sumYZ, sumZZ ); + + const ai_real det = mtx.Determinant( ); + if ( det == 0.0f ) + { + result.normal = aiVector3D( 0.0f ); + } + else + { + aiMatrix3x3 invMtx = mtx; + invMtx.Inverse( ); + result.normal = GetEigenVectorFromLargestEigenValue( invMtx ); + } + + return result; +} + +#endif // ASSIMP_BLEND_WITH_POLY_2_TRI + +#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER diff --git a/libs/assimp/code/AssetLib/Blender/BlenderTessellator.h b/libs/assimp/code/AssetLib/Blender/BlenderTessellator.h new file mode 100644 index 0000000..0d0ba32 --- /dev/null +++ b/libs/assimp/code/AssetLib/Blender/BlenderTessellator.h @@ -0,0 +1,214 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BlenderTessellator.h + * @brief A simple tessellation wrapper + */ +#ifndef INCLUDED_AI_BLEND_TESSELLATOR_H +#define INCLUDED_AI_BLEND_TESSELLATOR_H + +// Use these to toggle between GLU Tessellate or poly2tri +// Note (acg) keep GLU Tessellate disabled by default - if it is turned on, +// assimp needs to be linked against GLU, which is currently not yet +// made configurable in CMake and potentially not wanted by most users +// as it requires a Gl environment. +#ifndef ASSIMP_BLEND_WITH_GLU_TESSELLATE +# define ASSIMP_BLEND_WITH_GLU_TESSELLATE 0 +#endif + +#ifndef ASSIMP_BLEND_WITH_POLY_2_TRI +# define ASSIMP_BLEND_WITH_POLY_2_TRI 1 +#endif + +#include <assimp/LogAux.h> + +#if ASSIMP_BLEND_WITH_GLU_TESSELLATE + +#if defined( WIN32 ) || defined( _WIN32 ) || defined( _MSC_VER ) +#include <windows.h> +#endif +#include <GL/glu.h> + +namespace Assimp +{ + class BlenderBMeshConverter; + + // TinyFormatter.h + namespace Formatter + { + template < typename T,typename TR, typename A > class basic_formatter; + typedef class basic_formatter< char, std::char_traits< char >, std::allocator< char > > format; + } + + // BlenderScene.h + namespace Blender + { + struct MLoop; + struct MVert; + + struct VertexGL + { + GLdouble X; + GLdouble Y; + GLdouble Z; + int index; + int magic; + + VertexGL( GLdouble X, GLdouble Y, GLdouble Z, int index, int magic ): X( X ), Y( Y ), Z( Z ), index( index ), magic( magic ) { } + }; + + struct DrawCallGL + { + GLenum drawMode; + int baseVertex; + int vertexCount; + + DrawCallGL( GLenum drawMode, int baseVertex ): drawMode( drawMode ), baseVertex( baseVertex ), vertexCount( 0 ) { } + }; + + struct TessDataGL + { + std::vector< DrawCallGL > drawCalls; + std::vector< VertexGL > vertices; + }; + } + + class BlenderTessellatorGL: public LogFunctions< BlenderTessellatorGL > + { + public: + BlenderTessellatorGL( BlenderBMeshConverter& converter ); + ~BlenderTessellatorGL( ); + + void Tessellate( const Blender::MLoop* polyLoop, int vertexCount, const std::vector< Blender::MVert >& vertices ); + + private: + void AssertVertexCount( int vertexCount ); + void GenerateLoopVerts( std::vector< Blender::VertexGL >& polyLoopGL, const Blender::MLoop* polyLoop, int vertexCount, const std::vector< Blender::MVert >& vertices ); + void Tesssellate( std::vector< Blender::VertexGL >& polyLoopGL, Blender::TessDataGL& tessData ); + void TriangulateDrawCalls( const Blender::TessDataGL& tessData ); + void MakeFacesFromTris( const Blender::VertexGL* vertices, int vertexCount ); + void MakeFacesFromTriStrip( const Blender::VertexGL* vertices, int vertexCount ); + void MakeFacesFromTriFan( const Blender::VertexGL* vertices, int vertexCount ); + + static void TessellateBegin( GLenum drawModeGL, void* userData ); + static void TessellateEnd( void* userData ); + static void TessellateVertex( const void* vtxData, void* userData ); + static void TessellateCombine( const GLdouble intersection[ 3 ], const GLdouble* [ 4 ], const GLfloat [ 4 ], GLdouble** out, void* userData ); + static void TessellateEdgeFlag( GLboolean edgeFlag, void* userData ); + static void TessellateError( GLenum errorCode, void* userData ); + + BlenderBMeshConverter* converter; + }; +} // end of namespace Assimp + +#endif // ASSIMP_BLEND_WITH_GLU_TESSELLATE + +#if ASSIMP_BLEND_WITH_POLY_2_TRI + +#ifdef ASSIMP_USE_HUNTER +# include <poly2tri/poly2tri.h> +#else +# include "../contrib/poly2tri/poly2tri/poly2tri.h" +#endif + +namespace Assimp +{ + class BlenderBMeshConverter; + + // TinyFormatter.h + namespace Formatter + { + template < typename T,typename TR, typename A > class basic_formatter; + typedef class basic_formatter< char, std::char_traits< char >, std::allocator< char > > format; + } + + // BlenderScene.h + namespace Blender + { + struct MLoop; + struct MVert; + + struct PointP2T + { + aiVector3D point3D; + p2t::Point point2D; + int magic; + int index; + }; + + struct PlaneP2T + { + aiVector3D centre; + aiVector3D normal; + }; + } + + class BlenderTessellatorP2T: public LogFunctions< BlenderTessellatorP2T > + { + public: + BlenderTessellatorP2T( BlenderBMeshConverter& converter ); + ~BlenderTessellatorP2T( ); + + void Tessellate( const Blender::MLoop* polyLoop, int vertexCount, const std::vector< Blender::MVert >& vertices ); + + private: + void AssertVertexCount( int vertexCount ); + void Copy3DVertices( const Blender::MLoop* polyLoop, int vertexCount, const std::vector< Blender::MVert >& vertices, std::vector< Blender::PointP2T >& targetVertices ) const; + aiMatrix4x4 GeneratePointTransformMatrix( const Blender::PlaneP2T& plane ) const; + void TransformAndFlattenVectices( const aiMatrix4x4& transform, std::vector< Blender::PointP2T >& vertices ) const; + void ReferencePoints( std::vector< Blender::PointP2T >& points, std::vector< p2t::Point* >& pointRefs ) const; + inline Blender::PointP2T& GetActualPointStructure( p2t::Point& point ) const; + void MakeFacesFromTriangles( std::vector< p2t::Triangle* >& triangles ) const; + + // Adapted from: http://missingbytes.blogspot.co.uk/2012/06/fitting-plane-to-point-cloud.html + float FindLargestMatrixElem( const aiMatrix3x3& mtx ) const; + aiMatrix3x3 ScaleMatrix( const aiMatrix3x3& mtx, float scale ) const; + aiVector3D GetEigenVectorFromLargestEigenValue( const aiMatrix3x3& mtx ) const; + Blender::PlaneP2T FindLLSQPlane( const std::vector< Blender::PointP2T >& points ) const; + + BlenderBMeshConverter* converter; + }; +} // end of namespace Assimp + +#endif // ASSIMP_BLEND_WITH_POLY_2_TRI + +#endif // INCLUDED_AI_BLEND_TESSELLATOR_H diff --git a/libs/assimp/code/AssetLib/C4D/C4DImporter.cpp b/libs/assimp/code/AssetLib/C4D/C4DImporter.cpp new file mode 100644 index 0000000..06d7a34 --- /dev/null +++ b/libs/assimp/code/AssetLib/C4D/C4DImporter.cpp @@ -0,0 +1,620 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2021, 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. + */ +#ifndef ASSIMP_BUILD_NO_C4D_IMPORTER + +// 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 <memory> +#include <assimp/IOSystem.hpp> +#include <assimp/scene.h> +#include <assimp/ai_assert.h> + +#if defined(_M_X64) || defined(__amd64__) +# define __C4D_64BIT +#endif + +#define __PC +#include "c4d_file.h" +#include "default_alien_overloads.h" + +namespace { + +aiString aiStringFrom(cineware::String const & cinestring) { + aiString result; + cinestring.GetCString(result.data, MAXLEN-1); + result.length = static_cast<ai_uint32>(cinestring.GetLength()); + return result; +} + +} + +using namespace Assimp; +using namespace cineware; + +// overload this function and fill in your own unique data +void GetWriterInfo(int &id, String &appname) { + id = 2424226; + appname = "Open Asset Import Library"; +} + +namespace Assimp { + template<> const char* LogFunctions<C4DImporter>::Prefix() { + static auto prefix = "C4D: "; + return prefix; + } +} + +static const aiImporterDesc desc = { + "Cinema4D Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "c4d" +}; + + +// ------------------------------------------------------------------------------------------------ +C4DImporter::C4DImporter() +: BaseImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +C4DImporter::~C4DImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +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; +} + + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void C4DImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) { + std::unique_ptr<IOStream> file( pIOHandler->Open( pFile)); + + if( file.get() == nullptr ) { + ThrowException("failed to open file " + pFile); + } + + const size_t file_size = file->FileSize(); + + std::vector<uint8_t> 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 == nullptr ) { + ThrowException("failed to read document " + pFile); + } + + // Generate the root-node + pScene->mRootNode = new aiNode("<C4DRoot>"); + + // convert left-handed to right-handed + pScene->mRootNode->mTransformation.a1 = 0.01f; + pScene->mRootNode->mTransformation.b2 = 0.01f; + pScene->mRootNode->mTransformation.c3 = -0.01f; + + // first convert all materials + ReadMaterials(doc->GetFirstMaterial()); + + // process C4D scene-graph recursively + try { + RecurseHierarchy(doc->GetFirstObject(), pScene->mRootNode); + } catch(...) { + for(aiMesh* mesh : meshes) { + delete mesh; + } + BaseDocument::Free(doc); + throw; + } + BaseDocument::Free(doc); + + // copy meshes over + pScene->mNumMeshes = static_cast<unsigned int>(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<unsigned int>(materials.size()); + for(aiMesh* mesh : meshes) { + ai_assert(mesh->mMaterialIndex <= mat_count); + if(mesh->mMaterialIndex >= mat_count) { + ++mat_count; + + std::unique_ptr<aiMaterial> def_material(new aiMaterial()); + const aiString name(AI_DEFAULT_MATERIAL_NAME); + def_material->AddProperty(&name, AI_MATKEY_NAME); + + materials.push_back(def_material.release()); + break; + } + } + + pScene->mNumMaterials = static_cast<unsigned int>(materials.size()); + pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials](); + std::copy(materials.begin(), materials.end(), pScene->mMaterials); +} + + +// ------------------------------------------------------------------------------------------------ +bool C4DImporter::ReadShader(aiMaterial* out, BaseShader* shader) { + // based on Cineware 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<iBlendDataType*>(blend.GetCustomDataType(CUSTOMDATA_BLEND_LIST)); + if (!blend_list) + { + LogWarn("ignoring XLayer shader: no blend list given"); + continue; + } + + LayerShaderLayer *lsl = dynamic_cast<LayerShaderLayer*>(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<BlendFolder*>(lsl); + LayerShaderLayer *subLsl = dynamic_cast<LayerShaderLayer*>(folder->m_Children.GetObject(0)); + + while (subLsl) { + if (subLsl->GetType() == TypeShader) { + BlendShader* const shader = dynamic_cast<BlendShader*>(subLsl); + if(ReadShader(out, static_cast<BaseShader*>(shader->m_pLink->GetLink()))) { + return true; + } + } + + subLsl = subLsl->GetNext(); + } + } else if (lsl->GetType() == TypeShader) { + BlendShader* const shader = dynamic_cast<BlendShader*>(lsl); + if(ReadShader(out, static_cast<BaseShader*>(shader->m_pLink->GetLink()))) { + return true; + } + } + + lsl = lsl->GetNext(); + } + } else if ( shader->GetType() == Xbitmap ) { + auto const path = aiStringFrom(shader->GetFileName().GetString()); + out->AddProperty(&path, AI_MATKEY_TEXTURE_DIFFUSE(0)); + return true; + } else { + LogWarn("ignoring shader type: ", GetObjectTypeName(shader->GetType())); + } + shader = shader->GetNext(); + } + + return false; +} + +// ------------------------------------------------------------------------------------------------ +void C4DImporter::ReadMaterials(BaseMaterial* mat) { + // based on Cineware sample code + while (mat) { + if (mat->GetType() == Mmaterial) { + aiMaterial* out = new aiMaterial(); + material_mapping[mat] = static_cast<unsigned int>(materials.size()); + materials.push_back(out); + + auto const ai_name = aiStringFrom(mat->GetName()); + out->AddProperty(&ai_name, AI_MATKEY_NAME); + + Material& m = dynamic_cast<Material&>(*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 Float brightness = data.GetFloat(); + + 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: ", GetObjectTypeName(mat->GetType())); + } + mat = mat->GetNext(); + } +} + +// ------------------------------------------------------------------------------------------------ +void C4DImporter::RecurseHierarchy(BaseObject* object, aiNode* parent) { + ai_assert(parent != nullptr ); + std::vector<aiNode*> nodes; + + // based on Cineware sample code + while (object) { + const LONG type = object->GetType(); + const Matrix& ml = object->GetMl(); + + aiNode* const nd = new aiNode(); + + nd->mParent = parent; + nd->mName = aiStringFrom(object->GetName()); + + 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 != nullptr) { + nd->mNumMeshes = 1; + nd->mMeshes = new unsigned int[1]; + nd->mMeshes[0] = static_cast<unsigned int>(meshes.size()); + meshes.push_back(mesh); + } + } else { + LogWarn("ignoring object: ", GetObjectTypeName(type)); + } + + RecurseHierarchy(object->GetDown(), nd); + object = object->GetNext(); + } + + // copy nodes over to parent + parent->mNumChildren = static_cast<unsigned int>(nodes.size()); + parent->mChildren = new aiNode*[parent->mNumChildren](); + std::copy(nodes.begin(), nodes.end(), parent->mChildren); +} + +// ------------------------------------------------------------------------------------------------ +aiMesh* C4DImporter::ReadMesh(BaseObject* object) { + ai_assert(object != nullptr); + ai_assert( object->GetType() == Opolygon ); + + // based on Cineware sample code + PolygonObject* const polyObject = dynamic_cast<PolygonObject*>(object); + ai_assert(polyObject != nullptr); + + const LONG pointCount = polyObject->GetPointCount(); + const LONG polyCount = polyObject->GetPolygonCount(); + if(!polyObject || !pointCount) { + LogWarn("ignoring mesh with zero vertices or faces"); + return nullptr; + } + + const Vector* points = polyObject->GetPointR(); + ai_assert(points != nullptr); + + const CPolygon* polys = polyObject->GetPolygonR(); + ai_assert(polys != nullptr); + + std::unique_ptr<aiMesh> mesh(new aiMesh()); + mesh->mNumFaces = static_cast<unsigned int>(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 = nullptr; + if(tag) { + normals_src = dynamic_cast<NormalTag*>(tag); + normals = mesh->mNormals = new aiVector3D[mesh->mNumVertices](); + } + + tag = object->GetTag(Ttangent); + TangentTag* tangents_src = nullptr; + if(tag) { + tangents_src = dynamic_cast<TangentTag*>(tag); + tangents = mesh->mTangents = new aiVector3D[mesh->mNumVertices](); + bitangents = mesh->mBitangents = new aiVector3D[mesh->mNumVertices](); + } + + tag = object->GetTag(Tuvw); + UVWTag* uvs_src = nullptr; + if(tag) { + uvs_src = dynamic_cast<UVWTag*>(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->GetDataCount()) { + LogError("unexpected number of normals, ignoring"); + } else { + ConstNormalHandle normal_handle = normals_src->GetDataAddressR(); + NormalStruct nor; + NormalTag::Get(normal_handle, i, nor); + 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.release(); +} + +// ------------------------------------------------------------------------------------------------ +unsigned int C4DImporter::ResolveMaterial(PolygonObject* obj) { + ai_assert(obj != nullptr); + + const unsigned int mat_count = static_cast<unsigned int>(materials.size()); + + BaseTag* tag = obj->GetTag(Ttexture); + if(tag == nullptr) { + return mat_count; + } + + TextureTag& ttag = dynamic_cast<TextureTag&>(*tag); + + BaseMaterial* const mat = ttag.GetMaterial(); + ai_assert(mat != nullptr); + + 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; +} + +#endif // ASSIMP_BUILD_NO_C4D_IMPORTER diff --git a/libs/assimp/code/AssetLib/C4D/C4DImporter.h b/libs/assimp/code/AssetLib/C4D/C4DImporter.h new file mode 100644 index 0000000..c44cf5e --- /dev/null +++ b/libs/assimp/code/AssetLib/C4D/C4DImporter.h @@ -0,0 +1,108 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2021, 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 <assimp/BaseImporter.h> +#include <assimp/LogAux.h> + +#include <map> + +// Forward declarations +struct aiNode; +struct aiMesh; +struct aiMaterial; + +struct aiImporterDesc; + +namespace cineware { + class BaseObject; + class PolygonObject; + class BaseMaterial; + class BaseShader; +} + +namespace Assimp { + // TinyFormatter.h + namespace Formatter { + template <typename T,typename TR, typename A> class basic_formatter; + typedef class basic_formatter< char, std::char_traits<char>, std::allocator<char> > format; + } + +// ------------------------------------------------------------------------------------------- +/** Importer class to load Cinema4D files using the Cineware library to be obtained from + * https://developers.maxon.net + * + * Note that Cineware is not free software. */ +// ------------------------------------------------------------------------------------------- +class C4DImporter : public BaseImporter, public LogFunctions<C4DImporter> { +public: + bool CanRead( const std::string& pFile, IOSystem*, bool checkSig) const override; + +protected: + + const aiImporterDesc* GetInfo () const override; + + void InternReadFile( const std::string& pFile, aiScene*, IOSystem* ) override; + +private: + + void ReadMaterials(cineware::BaseMaterial* mat); + void RecurseHierarchy(cineware::BaseObject* object, aiNode* parent); + aiMesh* ReadMesh(cineware::BaseObject* object); + unsigned int ResolveMaterial(cineware::PolygonObject* obj); + + bool ReadShader(aiMaterial* out, cineware::BaseShader* shader); + + std::vector<aiMesh*> meshes; + std::vector<aiMaterial*> materials; + + typedef std::map<cineware::BaseMaterial*, unsigned int> MaterialMap; + MaterialMap material_mapping; + +}; // !class C4DImporter + +} // end of namespace Assimp + +#endif // INCLUDED_AI_CINEMA_4D_LOADER_H diff --git a/libs/assimp/code/AssetLib/COB/COBLoader.cpp b/libs/assimp/code/AssetLib/COB/COBLoader.cpp new file mode 100644 index 0000000..1c83100 --- /dev/null +++ b/libs/assimp/code/AssetLib/COB/COBLoader.cpp @@ -0,0 +1,1179 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 COBLoader.cpp + * @brief Implementation of the TrueSpace COB/SCN importer class. + */ + +#ifndef ASSIMP_BUILD_NO_COB_IMPORTER + +#include "AssetLib/COB/COBLoader.h" +#include "AssetLib/COB/COBScene.h" +#include "PostProcessing/ConvertToLHProcess.h" + +#include <assimp/LineSplitter.h> +#include <assimp/ParsingUtils.h> +#include <assimp/StreamReader.h> +#include <assimp/TinyFormatter.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> + +#include <memory> + +using namespace Assimp; +using namespace Assimp::COB; +using namespace Assimp::Formatter; + +static const float units[] = { + 1000.f, + 100.f, + 1.f, + 0.001f, + 1.f / 0.0254f, + 1.f / 0.3048f, + 1.f / 0.9144f, + 1.f / 1609.344f +}; + +static const aiImporterDesc desc = { + "TrueSpace Object Importer", + "", + "", + "little-endian files only", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "cob scn" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +COBImporter::COBImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +COBImporter::~COBImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool COBImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "Caligary" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Loader meta information +const aiImporterDesc *COBImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties for the loader +void COBImporter::SetupProperties(const Importer * /*pImp*/) { + // nothing to be done for the moment +} + +// ------------------------------------------------------------------------------------------------ +/*static*/ AI_WONT_RETURN void COBImporter::ThrowException(const std::string &msg) { + throw DeadlyImportError("COB: ", msg); +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void COBImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + COB::Scene scene; + + auto file = pIOHandler->Open(pFile, "rb"); + if (!file) { + ThrowException("Could not open " + pFile); + } + + std::unique_ptr<StreamReaderLE> stream(new StreamReaderLE(file)); + + // check header + char head[32]; + stream->CopyAndAdvance(head, 32); + if (strncmp(head, "Caligari ", 9) != 0) { + ThrowException("Could not found magic id: `Caligari`"); + } + + ASSIMP_LOG_INFO("File format tag: ", std::string(head + 9, 6)); + if (head[16] != 'L') { + ThrowException("File is big-endian, which is not supported"); + } + + // load data into intermediate structures + if (head[15] == 'A') { + ReadAsciiFile(scene, stream.get()); + } else { + ReadBinaryFile(scene, stream.get()); + } + if (scene.nodes.empty()) { + ThrowException("No nodes loaded"); + } + + // sort faces by material indices + for (std::shared_ptr<Node> &n : scene.nodes) { + if (n->type == Node::TYPE_MESH) { + Mesh &mesh = (Mesh &)(*n.get()); + for (Face &f : mesh.faces) { + mesh.temp_map[f.material].push_back(&f); + } + } + } + + // count meshes + for (std::shared_ptr<Node> &n : scene.nodes) { + if (n->type == Node::TYPE_MESH) { + Mesh &mesh = (Mesh &)(*n.get()); + if (mesh.vertex_positions.size() && mesh.texture_coords.size()) { + pScene->mNumMeshes += static_cast<unsigned int>(mesh.temp_map.size()); + } + } + } + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes](); + pScene->mMaterials = new aiMaterial *[pScene->mNumMeshes](); + pScene->mNumMeshes = 0; + + // count lights and cameras + for (std::shared_ptr<Node> &n : scene.nodes) { + if (n->type == Node::TYPE_LIGHT) { + ++pScene->mNumLights; + } else if (n->type == Node::TYPE_CAMERA) { + ++pScene->mNumCameras; + } + } + + if (pScene->mNumLights) { + pScene->mLights = new aiLight *[pScene->mNumLights](); + } + if (pScene->mNumCameras) { + pScene->mCameras = new aiCamera *[pScene->mNumCameras](); + } + pScene->mNumLights = pScene->mNumCameras = 0; + + // resolve parents by their IDs and build the output graph + std::unique_ptr<Node> root(new Group()); + for (size_t n = 0; n < scene.nodes.size(); ++n) { + const Node &nn = *scene.nodes[n].get(); + if (nn.parent_id == 0) { + root->temp_children.push_back(&nn); + } + + for (size_t m = n; m < scene.nodes.size(); ++m) { + const Node &mm = *scene.nodes[m].get(); + if (mm.parent_id == nn.id) { + nn.temp_children.push_back(&mm); + } + } + } + + pScene->mRootNode = BuildNodes(*root.get(), scene, pScene); + //flip normals after import + FlipWindingOrderProcess flip; + flip.Execute(pScene); +} + +// ------------------------------------------------------------------------------------------------ +void ConvertTexture(const std::shared_ptr<Texture> &tex, aiMaterial *out, aiTextureType type) { + const aiString path(tex->path); + out->AddProperty(&path, AI_MATKEY_TEXTURE(type, 0)); + out->AddProperty(&tex->transform, 1, AI_MATKEY_UVTRANSFORM(type, 0)); +} + +// ------------------------------------------------------------------------------------------------ +aiNode *COBImporter::BuildNodes(const Node &root, const Scene &scin, aiScene *fill) { + aiNode *nd = new aiNode(); + nd->mName.Set(root.name); + nd->mTransformation = root.transform; + + // Note to everybody believing Voodoo is appropriate here: + // I know polymorphism, run as fast as you can ;-) + if (Node::TYPE_MESH == root.type) { + const Mesh &ndmesh = (const Mesh &)(root); + if (ndmesh.vertex_positions.size() && ndmesh.texture_coords.size()) { + + typedef std::pair<const unsigned int, Mesh::FaceRefList> Entry; + for (const Entry &reflist : ndmesh.temp_map) { + { // create mesh + size_t n = 0; + for (Face *f : reflist.second) { + n += f->indices.size(); + } + if (!n) { + continue; + } + aiMesh *outmesh = fill->mMeshes[fill->mNumMeshes++] = new aiMesh(); + ++nd->mNumMeshes; + + outmesh->mVertices = new aiVector3D[n]; + outmesh->mTextureCoords[0] = new aiVector3D[n]; + + outmesh->mFaces = new aiFace[reflist.second.size()](); + for (Face *f : reflist.second) { + if (f->indices.empty()) { + continue; + } + + aiFace &fout = outmesh->mFaces[outmesh->mNumFaces++]; + fout.mIndices = new unsigned int[f->indices.size()]; + + for (VertexIndex &v : f->indices) { + if (v.pos_idx >= ndmesh.vertex_positions.size()) { + ThrowException("Position index out of range"); + } + if (v.uv_idx >= ndmesh.texture_coords.size()) { + ThrowException("UV index out of range"); + } + outmesh->mVertices[outmesh->mNumVertices] = ndmesh.vertex_positions[v.pos_idx]; + outmesh->mTextureCoords[0][outmesh->mNumVertices] = aiVector3D( + ndmesh.texture_coords[v.uv_idx].x, + ndmesh.texture_coords[v.uv_idx].y, + 0.f); + + fout.mIndices[fout.mNumIndices++] = outmesh->mNumVertices++; + } + } + outmesh->mMaterialIndex = fill->mNumMaterials; + } + { // create material + const Material *min = nullptr; + for (const Material &m : scin.materials) { + if (m.parent_id == ndmesh.id && m.matnum == reflist.first) { + min = &m; + break; + } + } + std::unique_ptr<const Material> defmat; + if (!min) { + ASSIMP_LOG_VERBOSE_DEBUG("Could not resolve material index ", reflist.first, " - creating default material for this slot"); + + defmat.reset(min = new Material()); + } + + aiMaterial *mat = new aiMaterial(); + fill->mMaterials[fill->mNumMaterials++] = mat; + + const aiString s(format("#mat_") << fill->mNumMeshes << "_" << min->matnum); + mat->AddProperty(&s, AI_MATKEY_NAME); + + if (int tmp = ndmesh.draw_flags & Mesh::WIRED ? 1 : 0) { + mat->AddProperty(&tmp, 1, AI_MATKEY_ENABLE_WIREFRAME); + } + + { + int shader; + switch (min->shader) { + case Material::FLAT: + shader = aiShadingMode_Gouraud; + break; + + case Material::PHONG: + shader = aiShadingMode_Phong; + break; + + case Material::METAL: + shader = aiShadingMode_CookTorrance; + break; + + default: + ASSIMP_LOG_ERROR("Unknown option."); + ai_assert(false); // shouldn't be here + break; + } + mat->AddProperty(&shader, 1, AI_MATKEY_SHADING_MODEL); + if (shader != aiShadingMode_Gouraud) { + mat->AddProperty(&min->exp, 1, AI_MATKEY_SHININESS); + } + } + + mat->AddProperty(&min->ior, 1, AI_MATKEY_REFRACTI); + mat->AddProperty(&min->rgb, 1, AI_MATKEY_COLOR_DIFFUSE); + + aiColor3D c = aiColor3D(min->rgb) * min->ks; + mat->AddProperty(&c, 1, AI_MATKEY_COLOR_SPECULAR); + + c = aiColor3D(min->rgb) * min->ka; + mat->AddProperty(&c, 1, AI_MATKEY_COLOR_AMBIENT); + + // convert textures if some exist. + if (min->tex_color) { + ConvertTexture(min->tex_color, mat, aiTextureType_DIFFUSE); + } + if (min->tex_env) { + ConvertTexture(min->tex_env, mat, aiTextureType_UNKNOWN); + } + if (min->tex_bump) { + ConvertTexture(min->tex_bump, mat, aiTextureType_HEIGHT); + } + } + } + } + } else if (Node::TYPE_LIGHT == root.type) { + const Light &ndlight = (const Light &)(root); + aiLight *outlight = fill->mLights[fill->mNumLights++] = new aiLight(); + + outlight->mName.Set(ndlight.name); + outlight->mColorDiffuse = outlight->mColorAmbient = outlight->mColorSpecular = ndlight.color; + + outlight->mAngleOuterCone = AI_DEG_TO_RAD(ndlight.angle); + outlight->mAngleInnerCone = AI_DEG_TO_RAD(ndlight.inner_angle); + + // XXX + outlight->mType = ndlight.ltype == Light::SPOT ? aiLightSource_SPOT : aiLightSource_DIRECTIONAL; + } else if (Node::TYPE_CAMERA == root.type) { + const Camera &ndcam = (const Camera &)(root); + aiCamera *outcam = fill->mCameras[fill->mNumCameras++] = new aiCamera(); + + outcam->mName.Set(ndcam.name); + } + + // add meshes + if (nd->mNumMeshes) { // mMeshes must be nullptr if count is 0 + nd->mMeshes = new unsigned int[nd->mNumMeshes]; + for (unsigned int i = 0; i < nd->mNumMeshes; ++i) { + nd->mMeshes[i] = fill->mNumMeshes - i - 1; + } + } + + // add children recursively + nd->mChildren = new aiNode *[root.temp_children.size()](); + for (const Node *n : root.temp_children) { + (nd->mChildren[nd->mNumChildren++] = BuildNodes(*n, scin, fill))->mParent = nd; + } + + return nd; +} + +// ------------------------------------------------------------------------------------------------ +// Read an ASCII file into the given scene data structure +void COBImporter::ReadAsciiFile(Scene &out, StreamReaderLE *stream) { + ChunkInfo ci; + for (LineSplitter splitter(*stream); splitter; ++splitter) { + + // add all chunks to be recognized here. /else ../ omitted intentionally. + if (splitter.match_start("PolH ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadPolH_Ascii(out, splitter, ci); + } + if (splitter.match_start("BitM ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadBitM_Ascii(out, splitter, ci); + } + if (splitter.match_start("Mat1 ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadMat1_Ascii(out, splitter, ci); + } + if (splitter.match_start("Grou ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadGrou_Ascii(out, splitter, ci); + } + if (splitter.match_start("Lght ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadLght_Ascii(out, splitter, ci); + } + if (splitter.match_start("Came ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadCame_Ascii(out, splitter, ci); + } + if (splitter.match_start("Bone ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadBone_Ascii(out, splitter, ci); + } + if (splitter.match_start("Chan ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadChan_Ascii(out, splitter, ci); + } + if (splitter.match_start("Unit ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadUnit_Ascii(out, splitter, ci); + } + if (splitter.match_start("END ")) { + // we don't need this, but I guess there is a reason this + // chunk has been implemented into COB for. + return; + } + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadChunkInfo_Ascii(ChunkInfo &out, const LineSplitter &splitter) { + const char *all_tokens[8]; + splitter.get_tokens(all_tokens); + + out.version = (all_tokens[1][1] - '0') * 100 + (all_tokens[1][3] - '0') * 10 + (all_tokens[1][4] - '0'); + out.id = strtoul10(all_tokens[3]); + out.parent_id = strtoul10(all_tokens[5]); + out.size = strtol10(all_tokens[7]); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::UnsupportedChunk_Ascii(LineSplitter &splitter, const ChunkInfo &nfo, const char *name) { + const std::string error = format("Encountered unsupported chunk: ") << name << " [version: " << nfo.version << ", size: " << nfo.size << "]"; + + // we can recover if the chunk size was specified. + if (nfo.size != static_cast<unsigned int>(-1)) { + ASSIMP_LOG_ERROR(error); + + // (HACK) - our current position in the stream is the beginning of the + // head line of the next chunk. That's fine, but the caller is going + // to call ++ on `splitter`, which we need to swallow to avoid + // missing the next line. + splitter.get_stream().IncPtr(nfo.size); + splitter.swallow_next_increment(); + } else { + ThrowException(error); + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadBasicNodeInfo_Ascii(Node &msh, LineSplitter &splitter, const ChunkInfo & /*nfo*/) { + for (; splitter; ++splitter) { + if (splitter.match_start("Name")) { + msh.name = std::string(splitter[1]); + + // make nice names by merging the dupe count + std::replace(msh.name.begin(), msh.name.end(), + ',', '_'); + } else if (splitter.match_start("Transform")) { + for (unsigned int y = 0; y < 4 && ++splitter; ++y) { + const char *s = splitter->c_str(); + for (unsigned int x = 0; x < 4; ++x) { + SkipSpaces(&s); + msh.transform[y][x] = fast_atof(&s); + } + } + // we need the transform chunk, so we won't return until we have it. + return; + } + } +} + +// ------------------------------------------------------------------------------------------------ +template <typename T> +void COBImporter::ReadFloat3Tuple_Ascii(T &fill, const char **in) { + const char *rgb = *in; + for (unsigned int i = 0; i < 3; ++i) { + SkipSpaces(&rgb); + if (*rgb == ',') ++rgb; + SkipSpaces(&rgb); + + fill[i] = fast_atof(&rgb); + } + *in = rgb; +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadMat1_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 8) { + return UnsupportedChunk_Ascii(splitter, nfo, "Mat1"); + } + + ++splitter; + if (!splitter.match_start("mat# ")) { + ASSIMP_LOG_WARN("Expected `mat#` line in `Mat1` chunk ", nfo.id); + return; + } + + out.materials.push_back(Material()); + Material &mat = out.materials.back(); + mat = nfo; + + mat.matnum = strtoul10(splitter[1]); + ++splitter; + + if (!splitter.match_start("shader: ")) { + ASSIMP_LOG_WARN("Expected `mat#` line in `Mat1` chunk ", nfo.id); + return; + } + std::string shader = std::string(splitter[1]); + shader = shader.substr(0, shader.find_first_of(" \t")); + + if (shader == "metal") { + mat.shader = Material::METAL; + } else if (shader == "phong") { + mat.shader = Material::PHONG; + } else if (shader != "flat") { + ASSIMP_LOG_WARN("Unknown value for `shader` in `Mat1` chunk ", nfo.id); + } + + ++splitter; + if (!splitter.match_start("rgb ")) { + ASSIMP_LOG_WARN("Expected `rgb` line in `Mat1` chunk ", nfo.id); + } + + const char *rgb = splitter[1]; + ReadFloat3Tuple_Ascii(mat.rgb, &rgb); + + ++splitter; + if (!splitter.match_start("alpha ")) { + ASSIMP_LOG_WARN("Expected `alpha` line in `Mat1` chunk ", nfo.id); + } + + const char *tokens[10]; + splitter.get_tokens(tokens); + + mat.alpha = fast_atof(tokens[1]); + mat.ka = fast_atof(tokens[3]); + mat.ks = fast_atof(tokens[5]); + mat.exp = fast_atof(tokens[7]); + mat.ior = fast_atof(tokens[9]); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadUnit_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 1) { + return UnsupportedChunk_Ascii(splitter, nfo, "Unit"); + } + ++splitter; + if (!splitter.match_start("Units ")) { + ASSIMP_LOG_WARN("Expected `Units` line in `Unit` chunk ", nfo.id); + return; + } + + // parent chunks preceede their children, so we should have the + // corresponding chunk already. + for (std::shared_ptr<Node> &nd : out.nodes) { + if (nd->id == nfo.parent_id) { + const unsigned int t = strtoul10(splitter[1]); + + nd->unit_scale = t >= sizeof(units) / sizeof(units[0]) ? ( + ASSIMP_LOG_WARN(t, " is not a valid value for `Units` attribute in `Unit chunk` ", nfo.id), 1.f) : + units[t]; + return; + } + } + ASSIMP_LOG_WARN("`Unit` chunk ", nfo.id, " is a child of ", nfo.parent_id, " which does not exist"); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadChan_Ascii(Scene & /*out*/, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 8) { + return UnsupportedChunk_Ascii(splitter, nfo, "Chan"); + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadLght_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 8) { + return UnsupportedChunk_Ascii(splitter, nfo, "Lght"); + } + + out.nodes.push_back(std::shared_ptr<Light>(new Light())); + Light &msh = (Light &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo); + + if (splitter.match_start("Infinite ")) { + msh.ltype = Light::INFINITE; + } else if (splitter.match_start("Local ")) { + msh.ltype = Light::LOCAL; + } else if (splitter.match_start("Spot ")) { + msh.ltype = Light::SPOT; + } else { + ASSIMP_LOG_WARN("Unknown kind of light source in `Lght` chunk ", nfo.id, " : ", *splitter); + msh.ltype = Light::SPOT; + } + + ++splitter; + if (!splitter.match_start("color ")) { + ASSIMP_LOG_WARN("Expected `color` line in `Lght` chunk ", nfo.id); + } + + const char *rgb = splitter[1]; + ReadFloat3Tuple_Ascii(msh.color, &rgb); + + SkipSpaces(&rgb); + if (strncmp(rgb, "cone angle", 10) != 0) { + ASSIMP_LOG_WARN("Expected `cone angle` entity in `color` line in `Lght` chunk ", nfo.id); + } + SkipSpaces(rgb + 10, &rgb); + msh.angle = fast_atof(&rgb); + + SkipSpaces(&rgb); + if (strncmp(rgb, "inner angle", 11) != 0) { + ASSIMP_LOG_WARN("Expected `inner angle` entity in `color` line in `Lght` chunk ", nfo.id); + } + SkipSpaces(rgb + 11, &rgb); + msh.inner_angle = fast_atof(&rgb); + + // skip the rest for we can't handle this kind of physically-based lighting information. +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadCame_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 2) { + return UnsupportedChunk_Ascii(splitter, nfo, "Came"); + } + + out.nodes.push_back(std::shared_ptr<Camera>(new Camera())); + Camera &msh = (Camera &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo); + + // skip the next line, we don't know this differentiation between a + // standard camera and a panoramic camera. + ++splitter; +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadBone_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 5) { + return UnsupportedChunk_Ascii(splitter, nfo, "Bone"); + } + + out.nodes.push_back(std::shared_ptr<Bone>(new Bone())); + Bone &msh = (Bone &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo); + + // TODO +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadGrou_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 1) { + return UnsupportedChunk_Ascii(splitter, nfo, "Grou"); + } + + out.nodes.push_back(std::shared_ptr<Group>(new Group())); + Group &msh = (Group &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadPolH_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 8) { + return UnsupportedChunk_Ascii(splitter, nfo, "PolH"); + } + + out.nodes.push_back(std::shared_ptr<Mesh>(new Mesh())); + Mesh &msh = (Mesh &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo); + + // the chunk has a fixed order of components, but some are not interesting of us so + // we're just looking for keywords in arbitrary order. The end of the chunk is + // either the last `Face` or the `DrawFlags` attribute, depending on the format ver. + for (; splitter; ++splitter) { + if (splitter.match_start("World Vertices")) { + const unsigned int cnt = strtoul10(splitter[2]); + msh.vertex_positions.resize(cnt); + + for (unsigned int cur = 0; cur < cnt && ++splitter; ++cur) { + const char *s = splitter->c_str(); + + aiVector3D &v = msh.vertex_positions[cur]; + + SkipSpaces(&s); + v.x = fast_atof(&s); + SkipSpaces(&s); + v.y = fast_atof(&s); + SkipSpaces(&s); + v.z = fast_atof(&s); + } + } else if (splitter.match_start("Texture Vertices")) { + const unsigned int cnt = strtoul10(splitter[2]); + msh.texture_coords.resize(cnt); + + for (unsigned int cur = 0; cur < cnt && ++splitter; ++cur) { + const char *s = splitter->c_str(); + + aiVector2D &v = msh.texture_coords[cur]; + + SkipSpaces(&s); + v.x = fast_atof(&s); + SkipSpaces(&s); + v.y = fast_atof(&s); + } + } else if (splitter.match_start("Faces")) { + const unsigned int cnt = strtoul10(splitter[1]); + msh.faces.reserve(cnt); + + for (unsigned int cur = 0; cur < cnt && ++splitter; ++cur) { + if (splitter.match_start("Hole")) { + ASSIMP_LOG_WARN("Skipping unsupported `Hole` line"); + continue; + } + + if (!splitter.match_start("Face")) { + ThrowException("Expected Face line"); + } + + msh.faces.push_back(Face()); + Face &face = msh.faces.back(); + + face.indices.resize(strtoul10(splitter[2])); + face.flags = strtoul10(splitter[4]); + face.material = strtoul10(splitter[6]); + + const char *s = (++splitter)->c_str(); + for (size_t i = 0; i < face.indices.size(); ++i) { + if (!SkipSpaces(&s)) { + ThrowException("Expected EOL token in Face entry"); + } + if ('<' != *s++) { + ThrowException("Expected < token in Face entry"); + } + face.indices[i].pos_idx = strtoul10(s, &s); + if (',' != *s++) { + ThrowException("Expected , token in Face entry"); + } + face.indices[i].uv_idx = strtoul10(s, &s); + if ('>' != *s++) { + ThrowException("Expected < token in Face entry"); + } + } + } + if (nfo.version <= 4) { + break; + } + } else if (splitter.match_start("DrawFlags")) { + msh.draw_flags = strtoul10(splitter[1]); + break; + } + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadBitM_Ascii(Scene & /*out*/, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 1) { + return UnsupportedChunk_Ascii(splitter, nfo, "BitM"); + } + + const unsigned int head = strtoul10((++splitter)[1]); + if (head != sizeof(Bitmap::BitmapHeader)) { + ASSIMP_LOG_WARN("Unexpected ThumbNailHdrSize, skipping this chunk"); + return; + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadString_Binary(std::string &out, StreamReaderLE &reader) { + out.resize(reader.GetI2()); + for (char &c : out) { + c = reader.GetI1(); + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadBasicNodeInfo_Binary(Node &msh, StreamReaderLE &reader, const ChunkInfo & /*nfo*/) { + const unsigned int dupes = reader.GetI2(); + ReadString_Binary(msh.name, reader); + + msh.name = format(msh.name) << '_' << dupes; + + // skip local axes for the moment + reader.IncPtr(48); + + msh.transform = aiMatrix4x4(); + for (unsigned int y = 0; y < 3; ++y) { + for (unsigned int x = 0; x < 4; ++x) { + msh.transform[y][x] = reader.GetF4(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::UnsupportedChunk_Binary(StreamReaderLE &reader, const ChunkInfo &nfo, const char *name) { + const std::string error = format("Encountered unsupported chunk: ") << name << " [version: " << nfo.version << ", size: " << nfo.size << "]"; + + // we can recover if the chunk size was specified. + if (nfo.size != static_cast<unsigned int>(-1)) { + ASSIMP_LOG_ERROR(error); + reader.IncPtr(nfo.size); + } else + ThrowException(error); +} + +// ------------------------------------------------------------------------------------------------ +// tiny utility guard to aid me at staying within chunk boundaries. +class chunk_guard { +public: + chunk_guard(const COB::ChunkInfo &nfo, StreamReaderLE &reader) : + nfo(nfo), reader(reader), cur(reader.GetCurrentPos()) { + // empty + } + + ~chunk_guard() { + // don't do anything if the size is not given + if (nfo.size != static_cast<unsigned int>(-1)) { + try { + reader.IncPtr(static_cast<int>(nfo.size) - reader.GetCurrentPos() + cur); + } catch (const DeadlyImportError &) { + // out of limit so correct the value + reader.IncPtr(reader.GetReadLimit()); + } + } + } + +private: + const COB::ChunkInfo &nfo; + StreamReaderLE &reader; + long cur; +}; + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadBinaryFile(Scene &out, StreamReaderLE *reader) { + if (nullptr == reader) { + return; + } + + while (1) { + std::string type; + type += reader->GetI1(); + type += reader->GetI1(); + type += reader->GetI1(); + type += reader->GetI1(); + + ChunkInfo nfo; + nfo.version = reader->GetI2() * 10; + nfo.version += reader->GetI2(); + + nfo.id = reader->GetI4(); + nfo.parent_id = reader->GetI4(); + nfo.size = reader->GetI4(); + + if (type == "PolH") { + ReadPolH_Binary(out, *reader, nfo); + } else if (type == "BitM") { + ReadBitM_Binary(out, *reader, nfo); + } else if (type == "Grou") { + ReadGrou_Binary(out, *reader, nfo); + } else if (type == "Lght") { + ReadLght_Binary(out, *reader, nfo); + } else if (type == "Came") { + ReadCame_Binary(out, *reader, nfo); + } else if (type == "Mat1") { + ReadMat1_Binary(out, *reader, nfo); + } else if (type == "Unit") { + ReadUnit_Binary(out, *reader, nfo); + } else if (type == "OLay") { + // ignore layer index silently. + if (nfo.size != static_cast<unsigned int>(-1)) { + reader->IncPtr(nfo.size); + } else + return UnsupportedChunk_Binary(*reader, nfo, type.c_str()); + } else if (type == "END ") { + return; + } else { + UnsupportedChunk_Binary(*reader, nfo, type.c_str()); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadPolH_Binary(COB::Scene &out, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 8) { + return UnsupportedChunk_Binary(reader, nfo, "PolH"); + } + const chunk_guard cn(nfo, reader); + + out.nodes.push_back(std::shared_ptr<Mesh>(new Mesh())); + Mesh &msh = (Mesh &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Binary(msh, reader, nfo); + + msh.vertex_positions.resize(reader.GetI4()); + for (aiVector3D &v : msh.vertex_positions) { + v.x = reader.GetF4(); + v.y = reader.GetF4(); + v.z = reader.GetF4(); + } + + msh.texture_coords.resize(reader.GetI4()); + for (aiVector2D &v : msh.texture_coords) { + v.x = reader.GetF4(); + v.y = reader.GetF4(); + } + + const size_t numf = reader.GetI4(); + msh.faces.reserve(numf); + for (size_t i = 0; i < numf; ++i) { + // XXX backface culling flag is 0x10 in flags + + // hole? + bool hole = (reader.GetI1() & 0x08) != 0; + if (hole) { + // XXX Basically this should just work fine - then triangulator + // should output properly triangulated data even for polygons + // with holes. Test data specific to COB is needed to confirm it. + if (msh.faces.empty()) { + ThrowException(format("A hole is the first entity in the `PolH` chunk with id ") << nfo.id); + } + } else + msh.faces.push_back(Face()); + Face &f = msh.faces.back(); + + const size_t num = reader.GetI2(); + f.indices.reserve(f.indices.size() + num); + + if (!hole) { + f.material = reader.GetI2(); + f.flags = 0; + } + + for (size_t x = 0; x < num; ++x) { + f.indices.push_back(VertexIndex()); + + VertexIndex &v = f.indices.back(); + v.pos_idx = reader.GetI4(); + v.uv_idx = reader.GetI4(); + } + + if (hole) { + std::reverse(f.indices.rbegin(), f.indices.rbegin() + num); + } + } + if (nfo.version > 4) { + msh.draw_flags = reader.GetI4(); + } + nfo.version > 5 && nfo.version < 8 ? reader.GetI4() : 0; +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadBitM_Binary(COB::Scene & /*out*/, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 1) { + return UnsupportedChunk_Binary(reader, nfo, "BitM"); + } + + const chunk_guard cn(nfo, reader); + + const uint32_t len = reader.GetI4(); + reader.IncPtr(len); + + reader.GetI4(); + reader.IncPtr(reader.GetI4()); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadMat1_Binary(COB::Scene &out, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 8) { + return UnsupportedChunk_Binary(reader, nfo, "Mat1"); + } + + const chunk_guard cn(nfo, reader); + + out.materials.push_back(Material()); + Material &mat = out.materials.back(); + mat = nfo; + + mat.matnum = reader.GetI2(); + switch (reader.GetI1()) { + case 'f': + mat.type = Material::FLAT; + break; + case 'p': + mat.type = Material::PHONG; + break; + case 'm': + mat.type = Material::METAL; + break; + default: + ASSIMP_LOG_ERROR("Unrecognized shader type in `Mat1` chunk with id ", nfo.id); + mat.type = Material::FLAT; + } + + switch (reader.GetI1()) { + case 'f': + mat.autofacet = Material::FACETED; + break; + case 'a': + mat.autofacet = Material::AUTOFACETED; + break; + case 's': + mat.autofacet = Material::SMOOTH; + break; + default: + ASSIMP_LOG_ERROR("Unrecognized faceting mode in `Mat1` chunk with id ", nfo.id); + mat.autofacet = Material::FACETED; + } + mat.autofacet_angle = static_cast<float>(reader.GetI1()); + + mat.rgb.r = reader.GetF4(); + mat.rgb.g = reader.GetF4(); + mat.rgb.b = reader.GetF4(); + + mat.alpha = reader.GetF4(); + mat.ka = reader.GetF4(); + mat.ks = reader.GetF4(); + mat.exp = reader.GetF4(); + mat.ior = reader.GetF4(); + + char id[2]; + id[0] = reader.GetI1(), id[1] = reader.GetI1(); + + if (id[0] == 'e' && id[1] == ':') { + mat.tex_env.reset(new Texture()); + + reader.GetI1(); + ReadString_Binary(mat.tex_env->path, reader); + + // advance to next texture-id + id[0] = reader.GetI1(), id[1] = reader.GetI1(); + } + + if (id[0] == 't' && id[1] == ':') { + mat.tex_color.reset(new Texture()); + + reader.GetI1(); + ReadString_Binary(mat.tex_color->path, reader); + + mat.tex_color->transform.mTranslation.x = reader.GetF4(); + mat.tex_color->transform.mTranslation.y = reader.GetF4(); + + mat.tex_color->transform.mScaling.x = reader.GetF4(); + mat.tex_color->transform.mScaling.y = reader.GetF4(); + + // advance to next texture-id + id[0] = reader.GetI1(), id[1] = reader.GetI1(); + } + + if (id[0] == 'b' && id[1] == ':') { + mat.tex_bump.reset(new Texture()); + + reader.GetI1(); + ReadString_Binary(mat.tex_bump->path, reader); + + mat.tex_bump->transform.mTranslation.x = reader.GetF4(); + mat.tex_bump->transform.mTranslation.y = reader.GetF4(); + + mat.tex_bump->transform.mScaling.x = reader.GetF4(); + mat.tex_bump->transform.mScaling.y = reader.GetF4(); + + // skip amplitude for I don't know its purpose. + reader.GetF4(); + } + reader.IncPtr(-2); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadCame_Binary(COB::Scene &out, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 2) { + return UnsupportedChunk_Binary(reader, nfo, "Came"); + } + + const chunk_guard cn(nfo, reader); + + out.nodes.push_back(std::shared_ptr<Camera>(new Camera())); + Camera &msh = (Camera &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Binary(msh, reader, nfo); + + // the rest is not interesting for us, so we skip over it. + if (nfo.version > 1) { + if (reader.GetI2() == 512) { + reader.IncPtr(42); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadLght_Binary(COB::Scene &out, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 2) { + return UnsupportedChunk_Binary(reader, nfo, "Lght"); + } + + const chunk_guard cn(nfo, reader); + + out.nodes.push_back(std::shared_ptr<Light>(new Light())); + Light &msh = (Light &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Binary(msh, reader, nfo); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadGrou_Binary(COB::Scene &out, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 2) { + return UnsupportedChunk_Binary(reader, nfo, "Grou"); + } + + const chunk_guard cn(nfo, reader); + + out.nodes.push_back(std::make_shared<Group>()); + Group &msh = (Group &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Binary(msh, reader, nfo); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadUnit_Binary(COB::Scene &out, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 1) { + return UnsupportedChunk_Binary(reader, nfo, "Unit"); + } + + const chunk_guard cn(nfo, reader); + + // parent chunks preceede their children, so we should have the + // corresponding chunk already. + for (std::shared_ptr<Node> &nd : out.nodes) { + if (nd->id == nfo.parent_id) { + const unsigned int t = reader.GetI2(); + nd->unit_scale = t >= sizeof(units) / sizeof(units[0]) ? ( + ASSIMP_LOG_WARN(t, " is not a valid value for `Units` attribute in `Unit chunk` ", nfo.id), 1.f) : + units[t]; + + return; + } + } + ASSIMP_LOG_WARN("`Unit` chunk ", nfo.id, " is a child of ", nfo.parent_id, " which does not exist"); +} + +#endif // ASSIMP_BUILD_NO_COB_IMPORTER diff --git a/libs/assimp/code/AssetLib/COB/COBLoader.h b/libs/assimp/code/AssetLib/COB/COBLoader.h new file mode 100644 index 0000000..e6eb96d --- /dev/null +++ b/libs/assimp/code/AssetLib/COB/COBLoader.h @@ -0,0 +1,152 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 COBLoader.h + * @brief Declaration of the TrueSpace (*.cob,*.scn) importer class. + */ +#ifndef INCLUDED_AI_COB_LOADER_H +#define INCLUDED_AI_COB_LOADER_H + +#include <assimp/BaseImporter.h> +#include <assimp/StreamReader.h> + +struct aiNode; + +namespace Assimp { +class LineSplitter; + +// TinyFormatter.h +namespace Formatter { +template <typename T, typename TR, typename A> +class basic_formatter; +typedef class basic_formatter<char, std::char_traits<char>, std::allocator<char>> format; +} // namespace Formatter + +// COBScene.h +namespace COB { +struct ChunkInfo; +struct Node; +struct Scene; +} // namespace COB + +// ------------------------------------------------------------------------------------------- +/** Importer class to load TrueSpace files (cob,scn) up to v6. + * + * Currently relatively limited, loads only ASCII files and needs more test coverage. */ +// ------------------------------------------------------------------------------------------- +class COBImporter : public BaseImporter { +public: + COBImporter(); + ~COBImporter() override; + + // -------------------- + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // -------------------- + const aiImporterDesc *GetInfo() const override; + + // -------------------- + void SetupProperties(const Importer *pImp) override; + + // -------------------- + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + +private: + // ------------------------------------------------------------------- + /** Prepend 'COB: ' and throw msg.*/ + AI_WONT_RETURN static void ThrowException(const std::string &msg) AI_WONT_RETURN_SUFFIX; + + // ------------------------------------------------------------------- + /** @brief Read from an ascii scene/object file + * @param out Receives output data. + * @param stream Stream to read from. */ + void ReadAsciiFile(COB::Scene &out, StreamReaderLE *stream); + + // ------------------------------------------------------------------- + /** @brief Read from a binary scene/object file + * @param out Receives output data. + * @param stream Stream to read from. */ + void ReadBinaryFile(COB::Scene &out, StreamReaderLE *stream); + + // Conversion to Assimp output format + aiNode *BuildNodes(const COB::Node &root, const COB::Scene &scin, aiScene *fill); + +private: + // ASCII file support + + void UnsupportedChunk_Ascii(LineSplitter &splitter, const COB::ChunkInfo &nfo, const char *name); + void ReadChunkInfo_Ascii(COB::ChunkInfo &out, const LineSplitter &splitter); + void ReadBasicNodeInfo_Ascii(COB::Node &msh, LineSplitter &splitter, const COB::ChunkInfo &nfo); + template <typename T> + void ReadFloat3Tuple_Ascii(T &fill, const char **in); + + void ReadPolH_Ascii(COB::Scene &out, LineSplitter &splitter, const COB::ChunkInfo &nfo); + void ReadBitM_Ascii(COB::Scene &out, LineSplitter &splitter, const COB::ChunkInfo &nfo); + void ReadMat1_Ascii(COB::Scene &out, LineSplitter &splitter, const COB::ChunkInfo &nfo); + void ReadGrou_Ascii(COB::Scene &out, LineSplitter &splitter, const COB::ChunkInfo &nfo); + void ReadBone_Ascii(COB::Scene &out, LineSplitter &splitter, const COB::ChunkInfo &nfo); + void ReadCame_Ascii(COB::Scene &out, LineSplitter &splitter, const COB::ChunkInfo &nfo); + void ReadLght_Ascii(COB::Scene &out, LineSplitter &splitter, const COB::ChunkInfo &nfo); + void ReadUnit_Ascii(COB::Scene &out, LineSplitter &splitter, const COB::ChunkInfo &nfo); + void ReadChan_Ascii(COB::Scene &out, LineSplitter &splitter, const COB::ChunkInfo &nfo); + + // Binary file support + + void UnsupportedChunk_Binary(StreamReaderLE &reader, const COB::ChunkInfo &nfo, const char *name); + void ReadString_Binary(std::string &out, StreamReaderLE &reader); + void ReadBasicNodeInfo_Binary(COB::Node &msh, StreamReaderLE &reader, const COB::ChunkInfo &nfo); + + void ReadPolH_Binary(COB::Scene &out, StreamReaderLE &reader, const COB::ChunkInfo &nfo); + void ReadBitM_Binary(COB::Scene &out, StreamReaderLE &reader, const COB::ChunkInfo &nfo); + void ReadMat1_Binary(COB::Scene &out, StreamReaderLE &reader, const COB::ChunkInfo &nfo); + void ReadCame_Binary(COB::Scene &out, StreamReaderLE &reader, const COB::ChunkInfo &nfo); + void ReadLght_Binary(COB::Scene &out, StreamReaderLE &reader, const COB::ChunkInfo &nfo); + void ReadGrou_Binary(COB::Scene &out, StreamReaderLE &reader, const COB::ChunkInfo &nfo); + void ReadUnit_Binary(COB::Scene &out, StreamReaderLE &reader, const COB::ChunkInfo &nfo); + +}; // !class COBImporter + +} // end of namespace Assimp +#endif // AI_UNREALIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/COB/COBScene.h b/libs/assimp/code/AssetLib/COB/COBScene.h new file mode 100644 index 0000000..57620ca --- /dev/null +++ b/libs/assimp/code/AssetLib/COB/COBScene.h @@ -0,0 +1,276 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 COBScene.h +* @brief Utilities for the COB importer. +*/ +#pragma once +#ifndef INCLUDED_AI_COB_SCENE_H +#define INCLUDED_AI_COB_SCENE_H + +#include <assimp/BaseImporter.h> +#include <assimp/material.h> + +#include <deque> +#include <map> + +namespace Assimp { +namespace COB { + +// ------------------ +/** Represents a single vertex index in a face */ +struct VertexIndex +{ + // intentionally uninitialized + unsigned int pos_idx,uv_idx; +}; + +// ------------------ +/** COB Face data structure */ +struct Face +{ + // intentionally uninitialized + unsigned int material, flags; + std::vector<VertexIndex> indices; +}; + +// ------------------ +/** COB chunk header information */ +const unsigned int NO_SIZE = UINT_MAX; + +struct ChunkInfo +{ + ChunkInfo () + : id (0) + , parent_id (0) + , version (0) + , size (NO_SIZE) + {} + + // Id of this chunk, unique within file + unsigned int id; + + // and the corresponding parent + unsigned int parent_id; + + // version. v1.23 becomes 123 + unsigned int version; + + // chunk size in bytes, only relevant for binary files + // NO_SIZE is also valid. + unsigned int size; +}; + +// ------------------ +/** A node in the scenegraph */ +struct Node : public ChunkInfo +{ + enum Type { + TYPE_MESH,TYPE_GROUP,TYPE_LIGHT,TYPE_CAMERA,TYPE_BONE + }; + + virtual ~Node() {} + Node(Type type) : type(type), unit_scale(1.f){} + + Type type; + + // used during resolving + typedef std::deque<const Node*> ChildList; + mutable ChildList temp_children; + + // unique name + std::string name; + + // local mesh transformation + aiMatrix4x4 transform; + + // scaling for this node to get to the metric system + float unit_scale; +}; + +// ------------------ +/** COB Mesh data structure */ +struct Mesh : public Node +{ + using ChunkInfo::operator=; + enum DrawFlags { + SOLID = 0x1, + TRANS = 0x2, + WIRED = 0x4, + BBOX = 0x8, + HIDE = 0x10 + }; + + Mesh() + : Node(TYPE_MESH) + , draw_flags(SOLID) + {} + + // vertex elements + std::vector<aiVector2D> texture_coords; + std::vector<aiVector3D> vertex_positions; + + // face data + std::vector<Face> faces; + + // misc. drawing flags + unsigned int draw_flags; + + // used during resolving + typedef std::deque<Face*> FaceRefList; + typedef std::map< unsigned int,FaceRefList > TempMap; + TempMap temp_map; +}; + +// ------------------ +/** COB Group data structure */ +struct Group : public Node +{ + using ChunkInfo::operator=; + Group() : Node(TYPE_GROUP) {} +}; + +// ------------------ +/** COB Bone data structure */ +struct Bone : public Node +{ + using ChunkInfo::operator=; + Bone() : Node(TYPE_BONE) {} +}; + +// ------------------ +/** COB Light data structure */ +struct Light : public Node +{ + enum LightType { + SPOT,LOCAL,INFINITE + }; + + using ChunkInfo::operator=; + Light() : Node(TYPE_LIGHT),angle(),inner_angle(),ltype(SPOT) {} + + aiColor3D color; + float angle,inner_angle; + + LightType ltype; +}; + +// ------------------ +/** COB Camera data structure */ +struct Camera : public Node +{ + using ChunkInfo::operator=; + Camera() : Node(TYPE_CAMERA) {} +}; + +// ------------------ +/** COB Texture data structure */ +struct Texture +{ + std::string path; + aiUVTransform transform; +}; + +// ------------------ +/** COB Material data structure */ +struct Material : ChunkInfo +{ + using ChunkInfo::operator=; + enum Shader { + FLAT,PHONG,METAL + }; + + enum AutoFacet { + FACETED,AUTOFACETED,SMOOTH + }; + + Material() : alpha(),exp(),ior(),ka(),ks(1.f), + matnum(UINT_MAX), + shader(FLAT),autofacet(FACETED), + autofacet_angle() + {} + + std::string type; + + aiColor3D rgb; + float alpha, exp, ior,ka,ks; + + unsigned int matnum; + Shader shader; + + AutoFacet autofacet; + float autofacet_angle; + + std::shared_ptr<Texture> tex_env,tex_bump,tex_color; +}; + +// ------------------ +/** Embedded bitmap, for instance for the thumbnail image */ +struct Bitmap : ChunkInfo +{ + Bitmap() : orig_size() {} + struct BitmapHeader + { + }; + + BitmapHeader head; + size_t orig_size; + std::vector<char> buff_zipped; +}; + +typedef std::deque< std::shared_ptr<Node> > NodeList; +typedef std::vector< Material > MaterialList; + +// ------------------ +/** Represents a master COB scene, even if we loaded just a single COB file */ +struct Scene +{ + NodeList nodes; + MaterialList materials; + + // becomes *0 later + Bitmap thumbnail; +}; + + } // end COB +} // end Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/CSM/CSMLoader.cpp b/libs/assimp/code/AssetLib/CSM/CSMLoader.cpp new file mode 100644 index 0000000..681f860 --- /dev/null +++ b/libs/assimp/code/AssetLib/CSM/CSMLoader.cpp @@ -0,0 +1,299 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 CSMLoader.cpp + * Implementation of the CSM importer class. + */ + + + +#ifndef ASSIMP_BUILD_NO_CSM_IMPORTER + +#include "CSMLoader.h" +#include <assimp/SkeletonMeshBuilder.h> +#include <assimp/ParsingUtils.h> +#include <assimp/fast_atof.h> +#include <assimp/Importer.hpp> +#include <memory> +#include <assimp/IOSystem.hpp> +#include <assimp/anim.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/scene.h> +#include <assimp/importerdesc.h> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "CharacterStudio Motion Importer (MoCap)", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "csm" +}; + + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +CSMImporter::CSMImporter() +: noSkeletonMesh() +{} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +CSMImporter::~CSMImporter() +{} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool CSMImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const +{ + static const char* tokens[] = {"$Filename"}; + return SearchFileHeaderForToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Build a string of all file extensions supported +const aiImporterDesc* CSMImporter::GetInfo () const +{ + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties for the loader +void CSMImporter::SetupProperties(const Importer* pImp) +{ + noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void CSMImporter::InternReadFile( const std::string& pFile, + aiScene* pScene, IOSystem* pIOHandler) +{ + std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb")); + + // Check whether we can read from the file + if( file.get() == nullptr) { + throw DeadlyImportError( "Failed to open CSM file ", pFile, "."); + } + + // allocate storage and copy the contents of the file to a memory buffer + std::vector<char> mBuffer2; + TextFileToBuffer(file.get(),mBuffer2); + const char* buffer = &mBuffer2[0]; + + std::unique_ptr<aiAnimation> anim(new aiAnimation()); + int first = 0, last = 0x00ffffff; + + // now process the file and look out for '$' sections + while (1) { + SkipSpaces(&buffer); + if ('\0' == *buffer) + break; + + if ('$' == *buffer) { + ++buffer; + if (TokenMatchI(buffer,"firstframe",10)) { + SkipSpaces(&buffer); + first = strtol10(buffer,&buffer); + } + else if (TokenMatchI(buffer,"lastframe",9)) { + SkipSpaces(&buffer); + last = strtol10(buffer,&buffer); + } + else if (TokenMatchI(buffer,"rate",4)) { + SkipSpaces(&buffer); + float d; + buffer = fast_atoreal_move<float>(buffer,d); + anim->mTicksPerSecond = d; + } + else if (TokenMatchI(buffer,"order",5)) { + std::vector< aiNodeAnim* > anims_temp; + anims_temp.reserve(30); + while (1) { + SkipSpaces(&buffer); + if (IsLineEnd(*buffer) && SkipSpacesAndLineEnd(&buffer) && *buffer == '$') + break; // next section + + // Construct a new node animation channel and setup its name + anims_temp.push_back(new aiNodeAnim()); + aiNodeAnim* nda = anims_temp.back(); + + char* ot = nda->mNodeName.data; + while (!IsSpaceOrNewLine(*buffer)) + *ot++ = *buffer++; + + *ot = '\0'; + nda->mNodeName.length = static_cast<ai_uint32>(ot-nda->mNodeName.data); + } + + anim->mNumChannels = static_cast<unsigned int>(anims_temp.size()); + if (!anim->mNumChannels) + throw DeadlyImportError("CSM: Empty $order section"); + + // copy over to the output animation + anim->mChannels = new aiNodeAnim*[anim->mNumChannels]; + ::memcpy(anim->mChannels,&anims_temp[0],sizeof(aiNodeAnim*)*anim->mNumChannels); + } + else if (TokenMatchI(buffer,"points",6)) { + if (!anim->mNumChannels) + throw DeadlyImportError("CSM: \'$order\' section is required to appear prior to \'$points\'"); + + // If we know how many frames we'll read, we can preallocate some storage + unsigned int alloc = 100; + if (last != 0x00ffffff) + { + alloc = last-first; + alloc += alloc>>2u; // + 25% + for (unsigned int i = 0; i < anim->mNumChannels;++i) + anim->mChannels[i]->mPositionKeys = new aiVectorKey[alloc]; + } + + unsigned int filled = 0; + + // Now read all point data. + while (1) { + SkipSpaces(&buffer); + if (IsLineEnd(*buffer) && (!SkipSpacesAndLineEnd(&buffer) || *buffer == '$')) { + break; // next section + } + + // read frame + const int frame = ::strtoul10(buffer,&buffer); + last = std::max(frame,last); + first = std::min(frame,last); + for (unsigned int i = 0; i < anim->mNumChannels;++i) { + + aiNodeAnim* s = anim->mChannels[i]; + if (s->mNumPositionKeys == alloc) { /* need to reallocate? */ + + aiVectorKey* old = s->mPositionKeys; + s->mPositionKeys = new aiVectorKey[s->mNumPositionKeys = alloc*2]; + ::memcpy(s->mPositionKeys,old,sizeof(aiVectorKey)*alloc); + delete[] old; + } + + // read x,y,z + if(!SkipSpacesAndLineEnd(&buffer)) + throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample x coord"); + + if (TokenMatchI(buffer, "DROPOUT", 7)) { + // seems this is invalid marker data; at least the doc says it's possible + ASSIMP_LOG_WARN("CSM: Encountered invalid marker data (DROPOUT)"); + } + else { + aiVectorKey* sub = s->mPositionKeys + s->mNumPositionKeys; + sub->mTime = (double)frame; + buffer = fast_atoreal_move<float>(buffer, (float&)sub->mValue.x); + + if(!SkipSpacesAndLineEnd(&buffer)) + throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample y coord"); + buffer = fast_atoreal_move<float>(buffer, (float&)sub->mValue.y); + + if(!SkipSpacesAndLineEnd(&buffer)) + throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample z coord"); + buffer = fast_atoreal_move<float>(buffer, (float&)sub->mValue.z); + + ++s->mNumPositionKeys; + } + } + + // update allocation granularity + if (filled == alloc) + alloc *= 2; + + ++filled; + } + // all channels must be complete in order to continue safely. + for (unsigned int i = 0; i < anim->mNumChannels;++i) { + + if (!anim->mChannels[i]->mNumPositionKeys) + throw DeadlyImportError("CSM: Invalid marker track"); + } + } + } + else { + // advance to the next line + SkipLine(&buffer); + } + } + + // Setup a proper animation duration + anim->mDuration = last - std::min( first, 0 ); + + // build a dummy root node with the tiny markers as children + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mName.Set("$CSM_DummyRoot"); + + pScene->mRootNode->mNumChildren = anim->mNumChannels; + pScene->mRootNode->mChildren = new aiNode* [anim->mNumChannels]; + + for (unsigned int i = 0; i < anim->mNumChannels;++i) { + aiNodeAnim* na = anim->mChannels[i]; + + aiNode* nd = pScene->mRootNode->mChildren[i] = new aiNode(); + nd->mName = anim->mChannels[i]->mNodeName; + nd->mParent = pScene->mRootNode; + + aiMatrix4x4::Translation(na->mPositionKeys[0].mValue, nd->mTransformation); + } + + // Store the one and only animation in the scene + pScene->mAnimations = new aiAnimation*[pScene->mNumAnimations=1]; + anim->mName.Set("$CSM_MasterAnim"); + pScene->mAnimations[0] = anim.release(); + + // mark the scene as incomplete and run SkeletonMeshBuilder on it + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + + if (!noSkeletonMesh) { + SkeletonMeshBuilder maker(pScene,pScene->mRootNode,true); + } +} + +#endif // !! ASSIMP_BUILD_NO_CSM_IMPORTER diff --git a/libs/assimp/code/AssetLib/CSM/CSMLoader.h b/libs/assimp/code/AssetLib/CSM/CSMLoader.h new file mode 100644 index 0000000..e9c4cd5 --- /dev/null +++ b/libs/assimp/code/AssetLib/CSM/CSMLoader.h @@ -0,0 +1,89 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 CSMLoader.h + * Declaration of the CharacterStudio Motion importer class. + */ +#ifndef INCLUDED_AI_CSM_LOADER_H +#define INCLUDED_AI_CSM_LOADER_H + +#include <assimp/BaseImporter.h> + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** Importer class to load MOCAPs in CharacterStudio Motion format. + * + * A very rudimentary loader for the moment. No support for the hierarchy, + * every marker is returned as child of root. + * + * Link to file format specification: + * <max_8_dvd>\samples\Motion\Docs\CSM.rtf +*/ +class CSMImporter : public BaseImporter { +public: + CSMImporter(); + ~CSMImporter() override; + + // ------------------------------------------------------------------- + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + void SetupProperties(const Importer *pImp) override; + + // ------------------------------------------------------------------- + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + +private: + bool noSkeletonMesh; + +}; // end of class CSMImporter + +} // end of namespace Assimp + +#endif // AI_AC3DIMPORTER_H_INC + diff --git a/libs/assimp/code/AssetLib/Collada/ColladaExporter.cpp b/libs/assimp/code/AssetLib/Collada/ColladaExporter.cpp new file mode 100644 index 0000000..5c91daa --- /dev/null +++ b/libs/assimp/code/AssetLib/Collada/ColladaExporter.cpp @@ -0,0 +1,1748 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER + +#include "ColladaExporter.h" + +#include <assimp/Bitmap.h> +#include <assimp/ColladaMetaData.h> +#include <assimp/DefaultIOSystem.h> +#include <assimp/Exceptional.h> +#include <assimp/MathFunctions.h> +#include <assimp/SceneCombiner.h> +#include <assimp/StringUtils.h> +#include <assimp/XMLTools.h> +#include <assimp/commonMetaData.h> +#include <assimp/fast_atof.h> +#include <assimp/scene.h> +#include <assimp/Exporter.hpp> +#include <assimp/IOSystem.hpp> + +#include <ctime> +#include <memory> + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +// Worker function for exporting a scene to Collada. Prototyped and registered in Exporter.cpp +void ExportSceneCollada(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) { + std::string path = DefaultIOSystem::absolutePath(std::string(pFile)); + std::string file = DefaultIOSystem::completeBaseName(std::string(pFile)); + + // invoke the exporter + ColladaExporter iDoTheExportThing(pScene, pIOSystem, path, file); + + if (iDoTheExportThing.mOutput.fail()) { + throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); + } + + // we're still here - export successfully completed. Write result to the given IOSYstem + std::unique_ptr<IOStream> outfile(pIOSystem->Open(pFile, "wt")); + if (outfile == nullptr) { + throw DeadlyExportError("could not open output .dae file: " + std::string(pFile)); + } + + // XXX maybe use a small wrapper around IOStream that behaves like std::stringstream in order to avoid the extra copy. + outfile->Write(iDoTheExportThing.mOutput.str().c_str(), static_cast<size_t>(iDoTheExportThing.mOutput.tellp()), 1); +} + +// ------------------------------------------------------------------------------------------------ +// Encodes a string into a valid XML ID using the xsd:ID schema qualifications. +static const std::string XMLIDEncode(const std::string &name) { + const char XML_ID_CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-."; + const unsigned int XML_ID_CHARS_COUNT = sizeof(XML_ID_CHARS) / sizeof(char); + + if (name.length() == 0) { + return name; + } + + std::stringstream idEncoded; + + // xsd:ID must start with letter or underscore + if (!((name[0] >= 'A' && name[0] <= 'z') || name[0] == '_')) { + idEncoded << '_'; + } + + for (std::string::const_iterator it = name.begin(); it != name.end(); ++it) { + // xsd:ID can only contain letters, digits, underscores, hyphens and periods + if (strchr(XML_ID_CHARS, *it) != nullptr) { + idEncoded << *it; + } else { + // Select placeholder character based on invalid character to reduce ID collisions + idEncoded << XML_ID_CHARS[(*it) % XML_ID_CHARS_COUNT]; + } + } + + return idEncoded.str(); +} + +// ------------------------------------------------------------------------------------------------ +// Helper functions to create unique ids +inline bool IsUniqueId(const std::unordered_set<std::string> &idSet, const std::string &idStr) { + return (idSet.find(idStr) == idSet.end()); +} + +inline std::string MakeUniqueId(const std::unordered_set<std::string> &idSet, const std::string &idPrefix, const std::string &postfix) { + std::string result(idPrefix + postfix); + if (!IsUniqueId(idSet, result)) { + // Select a number to append + size_t idnum = 1; + do { + result = idPrefix + '_' + ai_to_string(idnum) + postfix; + ++idnum; + } while (!IsUniqueId(idSet, result)); + } + return result; +} + +// ------------------------------------------------------------------------------------------------ +// Constructor for a specific scene to export +ColladaExporter::ColladaExporter(const aiScene *pScene, IOSystem *pIOSystem, const std::string &path, const std::string &file) : + mIOSystem(pIOSystem), + mPath(path), + mFile(file), + mScene(pScene), + endstr("\n") { + // make sure that all formatting happens using the standard, C locale and not the user's current locale + mOutput.imbue(std::locale("C")); + mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); + + // start writing the file + WriteFile(); +} + +// ------------------------------------------------------------------------------------------------ +// Destructor +ColladaExporter::~ColladaExporter() { +} + +// ------------------------------------------------------------------------------------------------ +// Starts writing the contents +void ColladaExporter::WriteFile() { + // write the DTD + mOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>" << endstr; + // COLLADA element start + mOutput << "<COLLADA xmlns=\"http://www.collada.org/2005/11/COLLADASchema\" version=\"1.4.1\">" << endstr; + PushTag(); + + WriteTextures(); + WriteHeader(); + + // Add node names to the unique id database first so they are most likely to use their names as unique ids + CreateNodeIds(mScene->mRootNode); + + WriteCamerasLibrary(); + WriteLightsLibrary(); + WriteMaterials(); + WriteGeometryLibrary(); + WriteControllerLibrary(); + + WriteSceneLibrary(); + + // customized, Writes the animation library + WriteAnimationsLibrary(); + + // instantiate the scene(s) + // For Assimp there will only ever be one + mOutput << startstr << "<scene>" << endstr; + PushTag(); + mOutput << startstr << "<instance_visual_scene url=\"#" + mSceneId + "\" />" << endstr; + PopTag(); + mOutput << startstr << "</scene>" << endstr; + PopTag(); + mOutput << "</COLLADA>" << endstr; +} + +// ------------------------------------------------------------------------------------------------ +// Writes the asset header +void ColladaExporter::WriteHeader() { + static const ai_real epsilon = Math::getEpsilon<ai_real>(); + static const aiQuaternion x_rot(aiMatrix3x3( + 0, -1, 0, + 1, 0, 0, + 0, 0, 1)); + static const aiQuaternion y_rot(aiMatrix3x3( + 1, 0, 0, + 0, 1, 0, + 0, 0, 1)); + static const aiQuaternion z_rot(aiMatrix3x3( + 1, 0, 0, + 0, 0, 1, + 0, -1, 0)); + + static const unsigned int date_nb_chars = 20; + char date_str[date_nb_chars]; + std::time_t date = std::time(nullptr); + std::strftime(date_str, date_nb_chars, "%Y-%m-%dT%H:%M:%S", std::localtime(&date)); + + aiVector3D scaling; + aiQuaternion rotation; + aiVector3D position; + mScene->mRootNode->mTransformation.Decompose(scaling, rotation, position); + rotation.Normalize(); + + mAdd_root_node = false; + + ai_real scale = 1.0; + if (std::abs(scaling.x - scaling.y) <= epsilon && std::abs(scaling.x - scaling.z) <= epsilon && std::abs(scaling.y - scaling.z) <= epsilon) { + scale = (ai_real)((((double)scaling.x) + ((double)scaling.y) + ((double)scaling.z)) / 3.0); + } else { + mAdd_root_node = true; + } + + std::string up_axis = "Y_UP"; + if (rotation.Equal(x_rot, epsilon)) { + up_axis = "X_UP"; + } else if (rotation.Equal(y_rot, epsilon)) { + up_axis = "Y_UP"; + } else if (rotation.Equal(z_rot, epsilon)) { + up_axis = "Z_UP"; + } else { + mAdd_root_node = true; + } + + if (!position.Equal(aiVector3D(0, 0, 0))) { + mAdd_root_node = true; + } + + // Assimp root nodes can have meshes, Collada Scenes cannot + if (mScene->mRootNode->mNumChildren == 0 || mScene->mRootNode->mMeshes != 0) { + mAdd_root_node = true; + } + + if (mAdd_root_node) { + up_axis = "Y_UP"; + scale = 1.0; + } + + mOutput << startstr << "<asset>" << endstr; + PushTag(); + mOutput << startstr << "<contributor>" << endstr; + PushTag(); + + // If no Scene metadata, use root node metadata + aiMetadata *meta = mScene->mMetaData; + if (nullptr == meta) { + meta = mScene->mRootNode->mMetaData; + } + + aiString value; + if (!meta || !meta->Get("Author", value)) { + mOutput << startstr << "<author>" + << "Assimp" + << "</author>" << endstr; + } else { + mOutput << startstr << "<author>" << XMLEscape(value.C_Str()) << "</author>" << endstr; + } + + if (nullptr == meta || !meta->Get(AI_METADATA_SOURCE_GENERATOR, value)) { + mOutput << startstr << "<authoring_tool>" + << "Assimp Exporter" + << "</authoring_tool>" << endstr; + } else { + mOutput << startstr << "<authoring_tool>" << XMLEscape(value.C_Str()) << "</authoring_tool>" << endstr; + } + + if (meta) { + if (meta->Get("Comments", value)) { + mOutput << startstr << "<comments>" << XMLEscape(value.C_Str()) << "</comments>" << endstr; + } + if (meta->Get(AI_METADATA_SOURCE_COPYRIGHT, value)) { + mOutput << startstr << "<copyright>" << XMLEscape(value.C_Str()) << "</copyright>" << endstr; + } + if (meta->Get("SourceData", value)) { + mOutput << startstr << "<source_data>" << XMLEscape(value.C_Str()) << "</source_data>" << endstr; + } + } + + PopTag(); + mOutput << startstr << "</contributor>" << endstr; + + if (nullptr == meta || !meta->Get("Created", value)) { + mOutput << startstr << "<created>" << date_str << "</created>" << endstr; + } else { + mOutput << startstr << "<created>" << XMLEscape(value.C_Str()) << "</created>" << endstr; + } + + // Modified date is always the date saved + mOutput << startstr << "<modified>" << date_str << "</modified>" << endstr; + + if (meta) { + if (meta->Get("Keywords", value)) { + mOutput << startstr << "<keywords>" << XMLEscape(value.C_Str()) << "</keywords>" << endstr; + } + if (meta->Get("Revision", value)) { + mOutput << startstr << "<revision>" << XMLEscape(value.C_Str()) << "</revision>" << endstr; + } + if (meta->Get("Subject", value)) { + mOutput << startstr << "<subject>" << XMLEscape(value.C_Str()) << "</subject>" << endstr; + } + if (meta->Get("Title", value)) { + mOutput << startstr << "<title>" << XMLEscape(value.C_Str()) << "</title>" << endstr; + } + } + + mOutput << startstr << "<unit name=\"meter\" meter=\"" << scale << "\" />" << endstr; + mOutput << startstr << "<up_axis>" << up_axis << "</up_axis>" << endstr; + PopTag(); + mOutput << startstr << "</asset>" << endstr; +} + +// ------------------------------------------------------------------------------------------------ +// Write the embedded textures +void ColladaExporter::WriteTextures() { + static const unsigned int buffer_size = 1024; + char str[buffer_size]; + + if (mScene->HasTextures()) { + for (unsigned int i = 0; i < mScene->mNumTextures; i++) { + // It would be great to be able to create a directory in portable standard C++, but it's not the case, + // so we just write the textures in the current directory. + + aiTexture *texture = mScene->mTextures[i]; + if (nullptr == texture) { + continue; + } + + ASSIMP_itoa10(str, buffer_size, i + 1); + + std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char *)texture->achFormatHint); + + std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + mIOSystem->getOsSeparator() + name, "wb")); + if (outfile == nullptr) { + throw DeadlyExportError("could not open output texture file: " + mPath + name); + } + + if (texture->mHeight == 0) { + outfile->Write((void *)texture->pcData, texture->mWidth, 1); + } else { + Bitmap::Save(texture, outfile.get()); + } + + outfile->Flush(); + + textures.insert(std::make_pair(i, name)); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Write the embedded textures +void ColladaExporter::WriteCamerasLibrary() { + if (mScene->HasCameras()) { + + mOutput << startstr << "<library_cameras>" << endstr; + PushTag(); + + for (size_t a = 0; a < mScene->mNumCameras; ++a) + WriteCamera(a); + + PopTag(); + mOutput << startstr << "</library_cameras>" << endstr; + } +} + +void ColladaExporter::WriteCamera(size_t pIndex) { + + const aiCamera *cam = mScene->mCameras[pIndex]; + const std::string cameraId = GetObjectUniqueId(AiObjectType::Camera, pIndex); + const std::string cameraName = GetObjectName(AiObjectType::Camera, pIndex); + + mOutput << startstr << "<camera id=\"" << cameraId << "\" name=\"" << cameraName << "\" >" << endstr; + PushTag(); + mOutput << startstr << "<optics>" << endstr; + PushTag(); + mOutput << startstr << "<technique_common>" << endstr; + PushTag(); + //assimp doesn't support the import of orthographic cameras! se we write + //always perspective + mOutput << startstr << "<perspective>" << endstr; + PushTag(); + mOutput << startstr << "<xfov sid=\"xfov\">" << AI_RAD_TO_DEG(cam->mHorizontalFOV) + << "</xfov>" << endstr; + mOutput << startstr << "<aspect_ratio>" + << cam->mAspect + << "</aspect_ratio>" << endstr; + mOutput << startstr << "<znear sid=\"znear\">" + << cam->mClipPlaneNear + << "</znear>" << endstr; + mOutput << startstr << "<zfar sid=\"zfar\">" + << cam->mClipPlaneFar + << "</zfar>" << endstr; + PopTag(); + mOutput << startstr << "</perspective>" << endstr; + PopTag(); + mOutput << startstr << "</technique_common>" << endstr; + PopTag(); + mOutput << startstr << "</optics>" << endstr; + PopTag(); + mOutput << startstr << "</camera>" << endstr; +} + +// ------------------------------------------------------------------------------------------------ +// Write the embedded textures +void ColladaExporter::WriteLightsLibrary() { + if (mScene->HasLights()) { + + mOutput << startstr << "<library_lights>" << endstr; + PushTag(); + + for (size_t a = 0; a < mScene->mNumLights; ++a) + WriteLight(a); + + PopTag(); + mOutput << startstr << "</library_lights>" << endstr; + } +} + +void ColladaExporter::WriteLight(size_t pIndex) { + + const aiLight *light = mScene->mLights[pIndex]; + const std::string lightId = GetObjectUniqueId(AiObjectType::Light, pIndex); + const std::string lightName = GetObjectName(AiObjectType::Light, pIndex); + + mOutput << startstr << "<light id=\"" << lightId << "\" name=\"" + << lightName << "\" >" << endstr; + PushTag(); + mOutput << startstr << "<technique_common>" << endstr; + PushTag(); + switch (light->mType) { + case aiLightSource_AMBIENT: + WriteAmbienttLight(light); + break; + case aiLightSource_DIRECTIONAL: + WriteDirectionalLight(light); + break; + case aiLightSource_POINT: + WritePointLight(light); + break; + case aiLightSource_SPOT: + WriteSpotLight(light); + break; + case aiLightSource_AREA: + case aiLightSource_UNDEFINED: + case _aiLightSource_Force32Bit: + break; + } + PopTag(); + mOutput << startstr << "</technique_common>" << endstr; + + PopTag(); + mOutput << startstr << "</light>" << endstr; +} + +void ColladaExporter::WritePointLight(const aiLight *const light) { + const aiColor3D &color = light->mColorDiffuse; + mOutput << startstr << "<point>" << endstr; + PushTag(); + mOutput << startstr << "<color sid=\"color\">" + << color.r << " " << color.g << " " << color.b + << "</color>" << endstr; + mOutput << startstr << "<constant_attenuation>" + << light->mAttenuationConstant + << "</constant_attenuation>" << endstr; + mOutput << startstr << "<linear_attenuation>" + << light->mAttenuationLinear + << "</linear_attenuation>" << endstr; + mOutput << startstr << "<quadratic_attenuation>" + << light->mAttenuationQuadratic + << "</quadratic_attenuation>" << endstr; + + PopTag(); + mOutput << startstr << "</point>" << endstr; +} + +void ColladaExporter::WriteDirectionalLight(const aiLight *const light) { + const aiColor3D &color = light->mColorDiffuse; + mOutput << startstr << "<directional>" << endstr; + PushTag(); + mOutput << startstr << "<color sid=\"color\">" + << color.r << " " << color.g << " " << color.b + << "</color>" << endstr; + + PopTag(); + mOutput << startstr << "</directional>" << endstr; +} + +void ColladaExporter::WriteSpotLight(const aiLight *const light) { + + const aiColor3D &color = light->mColorDiffuse; + mOutput << startstr << "<spot>" << endstr; + PushTag(); + mOutput << startstr << "<color sid=\"color\">" + << color.r << " " << color.g << " " << color.b + << "</color>" << endstr; + mOutput << startstr << "<constant_attenuation>" + << light->mAttenuationConstant + << "</constant_attenuation>" << endstr; + mOutput << startstr << "<linear_attenuation>" + << light->mAttenuationLinear + << "</linear_attenuation>" << endstr; + mOutput << startstr << "<quadratic_attenuation>" + << light->mAttenuationQuadratic + << "</quadratic_attenuation>" << endstr; + /* + out->mAngleOuterCone = AI_DEG_TO_RAD (std::acos(std::pow(0.1f,1.f/srcLight->mFalloffExponent))+ + srcLight->mFalloffAngle); + */ + + const ai_real fallOffAngle = AI_RAD_TO_DEG(light->mAngleInnerCone); + mOutput << startstr << "<falloff_angle sid=\"fall_off_angle\">" + << fallOffAngle + << "</falloff_angle>" << endstr; + double temp = light->mAngleOuterCone - light->mAngleInnerCone; + + temp = std::cos(temp); + temp = std::log(temp) / std::log(0.1); + temp = 1 / temp; + mOutput << startstr << "<falloff_exponent sid=\"fall_off_exponent\">" + << temp + << "</falloff_exponent>" << endstr; + + PopTag(); + mOutput << startstr << "</spot>" << endstr; +} + +void ColladaExporter::WriteAmbienttLight(const aiLight *const light) { + + const aiColor3D &color = light->mColorAmbient; + mOutput << startstr << "<ambient>" << endstr; + PushTag(); + mOutput << startstr << "<color sid=\"color\">" + << color.r << " " << color.g << " " << color.b + << "</color>" << endstr; + + PopTag(); + mOutput << startstr << "</ambient>" << endstr; +} + +// ------------------------------------------------------------------------------------------------ +// Reads a single surface entry from the given material keys +bool ColladaExporter::ReadMaterialSurface(Surface &poSurface, const aiMaterial &pSrcMat, aiTextureType pTexture, const char *pKey, size_t pType, size_t pIndex) { + if (pSrcMat.GetTextureCount(pTexture) > 0) { + aiString texfile; + unsigned int uvChannel = 0; + pSrcMat.GetTexture(pTexture, 0, &texfile, nullptr, &uvChannel); + + std::string index_str(texfile.C_Str()); + + if (index_str.size() != 0 && index_str[0] == '*') { + unsigned int index; + + index_str = index_str.substr(1, std::string::npos); + + try { + index = (unsigned int)strtoul10_64<DeadlyExportError>(index_str.c_str()); + } catch (std::exception &error) { + throw DeadlyExportError(error.what()); + } + + std::map<unsigned int, std::string>::const_iterator name = textures.find(index); + + if (name != textures.end()) { + poSurface.texture = name->second; + } else { + throw DeadlyExportError("could not find embedded texture at index " + index_str); + } + } else { + poSurface.texture = texfile.C_Str(); + } + + poSurface.channel = uvChannel; + poSurface.exist = true; + } else { + if (pKey) + poSurface.exist = pSrcMat.Get(pKey, static_cast<unsigned int>(pType), static_cast<unsigned int>(pIndex), poSurface.color) == aiReturn_SUCCESS; + } + return poSurface.exist; +} + +// ------------------------------------------------------------------------------------------------ +// Reimplementation of isalnum(,C locale), because AppVeyor does not see standard version. +static bool isalnum_C(char c) { + return (nullptr != strchr("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", c)); +} + +// ------------------------------------------------------------------------------------------------ +// Writes an image entry for the given surface +void ColladaExporter::WriteImageEntry(const Surface &pSurface, const std::string &imageId) { + if (!pSurface.texture.empty()) { + mOutput << startstr << "<image id=\"" << imageId << "\">" << endstr; + PushTag(); + mOutput << startstr << "<init_from>"; + + // URL encode image file name first, then XML encode on top + std::stringstream imageUrlEncoded; + for (std::string::const_iterator it = pSurface.texture.begin(); it != pSurface.texture.end(); ++it) { + if (isalnum_C((unsigned char)*it) || *it == ':' || *it == '_' || *it == '-' || *it == '.' || *it == '/' || *it == '\\') + imageUrlEncoded << *it; + else + imageUrlEncoded << '%' << std::hex << size_t((unsigned char)*it) << std::dec; + } + mOutput << XMLEscape(imageUrlEncoded.str()); + mOutput << "</init_from>" << endstr; + PopTag(); + mOutput << startstr << "</image>" << endstr; + } +} + +// ------------------------------------------------------------------------------------------------ +// Writes a color-or-texture entry into an effect definition +void ColladaExporter::WriteTextureColorEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &imageId) { + if (pSurface.exist) { + mOutput << startstr << "<" << pTypeName << ">" << endstr; + PushTag(); + if (pSurface.texture.empty()) { + mOutput << startstr << "<color sid=\"" << pTypeName << "\">" << pSurface.color.r << " " << pSurface.color.g << " " << pSurface.color.b << " " << pSurface.color.a << "</color>" << endstr; + } else { + mOutput << startstr << "<texture texture=\"" << imageId << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr; + } + PopTag(); + mOutput << startstr << "</" << pTypeName << ">" << endstr; + } +} + +// ------------------------------------------------------------------------------------------------ +// Writes the two parameters necessary for referencing a texture in an effect entry +void ColladaExporter::WriteTextureParamEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &materialId) { + // if surface is a texture, write out the sampler and the surface parameters necessary to reference the texture + if (!pSurface.texture.empty()) { + mOutput << startstr << "<newparam sid=\"" << materialId << "-" << pTypeName << "-surface\">" << endstr; + PushTag(); + mOutput << startstr << "<surface type=\"2D\">" << endstr; + PushTag(); + mOutput << startstr << "<init_from>" << materialId << "-" << pTypeName << "-image</init_from>" << endstr; + PopTag(); + mOutput << startstr << "</surface>" << endstr; + PopTag(); + mOutput << startstr << "</newparam>" << endstr; + + mOutput << startstr << "<newparam sid=\"" << materialId << "-" << pTypeName << "-sampler\">" << endstr; + PushTag(); + mOutput << startstr << "<sampler2D>" << endstr; + PushTag(); + mOutput << startstr << "<source>" << materialId << "-" << pTypeName << "-surface</source>" << endstr; + PopTag(); + mOutput << startstr << "</sampler2D>" << endstr; + PopTag(); + mOutput << startstr << "</newparam>" << endstr; + } +} + +// ------------------------------------------------------------------------------------------------ +// Writes a scalar property +void ColladaExporter::WriteFloatEntry(const Property &pProperty, const std::string &pTypeName) { + if (pProperty.exist) { + mOutput << startstr << "<" << pTypeName << ">" << endstr; + PushTag(); + mOutput << startstr << "<float sid=\"" << pTypeName << "\">" << pProperty.value << "</float>" << endstr; + PopTag(); + mOutput << startstr << "</" << pTypeName << ">" << endstr; + } +} + +// ------------------------------------------------------------------------------------------------ +// Writes the material setup +void ColladaExporter::WriteMaterials() { + std::vector<Material> materials; + materials.resize(mScene->mNumMaterials); + + /// collect all materials from the scene + size_t numTextures = 0; + for (size_t a = 0; a < mScene->mNumMaterials; ++a) { + Material &material = materials[a]; + material.id = GetObjectUniqueId(AiObjectType::Material, a); + material.name = GetObjectName(AiObjectType::Material, a); + + const aiMaterial &mat = *(mScene->mMaterials[a]); + aiShadingMode shading = aiShadingMode_Flat; + material.shading_model = "phong"; + if (mat.Get(AI_MATKEY_SHADING_MODEL, shading) == aiReturn_SUCCESS) { + if (shading == aiShadingMode_Phong) { + material.shading_model = "phong"; + } else if (shading == aiShadingMode_Blinn) { + material.shading_model = "blinn"; + } else if (shading == aiShadingMode_NoShading) { + material.shading_model = "constant"; + } else if (shading == aiShadingMode_Gouraud) { + material.shading_model = "lambert"; + } + } + + if (ReadMaterialSurface(material.ambient, mat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT)) + ++numTextures; + if (ReadMaterialSurface(material.diffuse, mat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE)) + ++numTextures; + if (ReadMaterialSurface(material.specular, mat, aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR)) + ++numTextures; + if (ReadMaterialSurface(material.emissive, mat, aiTextureType_EMISSIVE, AI_MATKEY_COLOR_EMISSIVE)) + ++numTextures; + if (ReadMaterialSurface(material.reflective, mat, aiTextureType_REFLECTION, AI_MATKEY_COLOR_REFLECTIVE)) + ++numTextures; + if (ReadMaterialSurface(material.transparent, mat, aiTextureType_OPACITY, AI_MATKEY_COLOR_TRANSPARENT)) + ++numTextures; + if (ReadMaterialSurface(material.normal, mat, aiTextureType_NORMALS, nullptr, 0, 0)) + ++numTextures; + + material.shininess.exist = mat.Get(AI_MATKEY_SHININESS, material.shininess.value) == aiReturn_SUCCESS; + material.transparency.exist = mat.Get(AI_MATKEY_OPACITY, material.transparency.value) == aiReturn_SUCCESS; + material.index_refraction.exist = mat.Get(AI_MATKEY_REFRACTI, material.index_refraction.value) == aiReturn_SUCCESS; + } + + // output textures if present + if (numTextures > 0) { + mOutput << startstr << "<library_images>" << endstr; + PushTag(); + for (const Material &mat : materials) { + WriteImageEntry(mat.ambient, mat.id + "-ambient-image"); + WriteImageEntry(mat.diffuse, mat.id + "-diffuse-image"); + WriteImageEntry(mat.specular, mat.id + "-specular-image"); + WriteImageEntry(mat.emissive, mat.id + "-emission-image"); + WriteImageEntry(mat.reflective, mat.id + "-reflective-image"); + WriteImageEntry(mat.transparent, mat.id + "-transparent-image"); + WriteImageEntry(mat.normal, mat.id + "-normal-image"); + } + PopTag(); + mOutput << startstr << "</library_images>" << endstr; + } + + // output effects - those are the actual carriers of information + if (!materials.empty()) { + mOutput << startstr << "<library_effects>" << endstr; + PushTag(); + for (const Material &mat : materials) { + // this is so ridiculous it must be right + mOutput << startstr << "<effect id=\"" << mat.id << "-fx\" name=\"" << mat.name << "\">" << endstr; + PushTag(); + mOutput << startstr << "<profile_COMMON>" << endstr; + PushTag(); + + // write sampler- and surface params for the texture entries + WriteTextureParamEntry(mat.emissive, "emission", mat.id); + WriteTextureParamEntry(mat.ambient, "ambient", mat.id); + WriteTextureParamEntry(mat.diffuse, "diffuse", mat.id); + WriteTextureParamEntry(mat.specular, "specular", mat.id); + WriteTextureParamEntry(mat.reflective, "reflective", mat.id); + WriteTextureParamEntry(mat.transparent, "transparent", mat.id); + WriteTextureParamEntry(mat.normal, "normal", mat.id); + + mOutput << startstr << "<technique sid=\"standard\">" << endstr; + PushTag(); + mOutput << startstr << "<" << mat.shading_model << ">" << endstr; + PushTag(); + + WriteTextureColorEntry(mat.emissive, "emission", mat.id + "-emission-sampler"); + WriteTextureColorEntry(mat.ambient, "ambient", mat.id + "-ambient-sampler"); + WriteTextureColorEntry(mat.diffuse, "diffuse", mat.id + "-diffuse-sampler"); + WriteTextureColorEntry(mat.specular, "specular", mat.id + "-specular-sampler"); + WriteFloatEntry(mat.shininess, "shininess"); + WriteTextureColorEntry(mat.reflective, "reflective", mat.id + "-reflective-sampler"); + WriteTextureColorEntry(mat.transparent, "transparent", mat.id + "-transparent-sampler"); + WriteFloatEntry(mat.transparency, "transparency"); + WriteFloatEntry(mat.index_refraction, "index_of_refraction"); + + if (!mat.normal.texture.empty()) { + WriteTextureColorEntry(mat.normal, "bump", mat.id + "-normal-sampler"); + } + + PopTag(); + mOutput << startstr << "</" << mat.shading_model << ">" << endstr; + PopTag(); + mOutput << startstr << "</technique>" << endstr; + PopTag(); + mOutput << startstr << "</profile_COMMON>" << endstr; + PopTag(); + mOutput << startstr << "</effect>" << endstr; + } + PopTag(); + mOutput << startstr << "</library_effects>" << endstr; + + // write materials - they're just effect references + mOutput << startstr << "<library_materials>" << endstr; + PushTag(); + for (std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it) { + const Material &mat = *it; + mOutput << startstr << "<material id=\"" << mat.id << "\" name=\"" << mat.name << "\">" << endstr; + PushTag(); + mOutput << startstr << "<instance_effect url=\"#" << mat.id << "-fx\"/>" << endstr; + PopTag(); + mOutput << startstr << "</material>" << endstr; + } + PopTag(); + mOutput << startstr << "</library_materials>" << endstr; + } +} + +// ------------------------------------------------------------------------------------------------ +// Writes the controller library +void ColladaExporter::WriteControllerLibrary() { + mOutput << startstr << "<library_controllers>" << endstr; + PushTag(); + + for (size_t a = 0; a < mScene->mNumMeshes; ++a) { + WriteController(a); + } + + PopTag(); + mOutput << startstr << "</library_controllers>" << endstr; +} + +// ------------------------------------------------------------------------------------------------ +// Writes a skin controller of the given mesh +void ColladaExporter::WriteController(size_t pIndex) { + const aiMesh *mesh = mScene->mMeshes[pIndex]; + // Is there a skin controller? + if (mesh->mNumBones == 0 || mesh->mNumFaces == 0 || mesh->mNumVertices == 0) + return; + + const std::string idstr = GetObjectUniqueId(AiObjectType::Mesh, pIndex); + const std::string namestr = GetObjectName(AiObjectType::Mesh, pIndex); + + mOutput << startstr << "<controller id=\"" << idstr << "-skin\" "; + mOutput << "name=\"skinCluster" << pIndex << "\">" << endstr; + PushTag(); + + mOutput << startstr << "<skin source=\"#" << idstr << "\">" << endstr; + PushTag(); + + // bind pose matrix + mOutput << startstr << "<bind_shape_matrix>" << endstr; + PushTag(); + + // I think it is identity in general cases. + aiMatrix4x4 mat; + mOutput << startstr << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << endstr; + mOutput << startstr << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4 << endstr; + mOutput << startstr << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4 << endstr; + mOutput << startstr << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4 << endstr; + + PopTag(); + mOutput << startstr << "</bind_shape_matrix>" << endstr; + + mOutput << startstr << "<source id=\"" << idstr << "-skin-joints\" name=\"" << namestr << "-skin-joints\">" << endstr; + PushTag(); + + mOutput << startstr << "<Name_array id=\"" << idstr << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">"; + + for (size_t i = 0; i < mesh->mNumBones; ++i) + mOutput << GetBoneUniqueId(mesh->mBones[i]) << ' '; + + mOutput << "</Name_array>" << endstr; + + mOutput << startstr << "<technique_common>" << endstr; + PushTag(); + + mOutput << startstr << "<accessor source=\"#" << idstr << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\" stride=\"" << 1 << "\">" << endstr; + PushTag(); + + mOutput << startstr << "<param name=\"JOINT\" type=\"Name\"></param>" << endstr; + + PopTag(); + mOutput << startstr << "</accessor>" << endstr; + + PopTag(); + mOutput << startstr << "</technique_common>" << endstr; + + PopTag(); + mOutput << startstr << "</source>" << endstr; + + std::vector<ai_real> bind_poses; + bind_poses.reserve(mesh->mNumBones * 16); + for (unsigned int i = 0; i < mesh->mNumBones; ++i) + for (unsigned int j = 0; j < 4; ++j) + bind_poses.insert(bind_poses.end(), mesh->mBones[i]->mOffsetMatrix[j], mesh->mBones[i]->mOffsetMatrix[j] + 4); + + WriteFloatArray(idstr + "-skin-bind_poses", FloatType_Mat4x4, (const ai_real *)bind_poses.data(), bind_poses.size() / 16); + + bind_poses.clear(); + + std::vector<ai_real> skin_weights; + skin_weights.reserve(mesh->mNumVertices * mesh->mNumBones); + for (size_t i = 0; i < mesh->mNumBones; ++i) + for (size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j) + skin_weights.push_back(mesh->mBones[i]->mWeights[j].mWeight); + + WriteFloatArray(idstr + "-skin-weights", FloatType_Weight, (const ai_real *)skin_weights.data(), skin_weights.size()); + + skin_weights.clear(); + + mOutput << startstr << "<joints>" << endstr; + PushTag(); + + mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstr << "-skin-joints\"></input>" << endstr; + mOutput << startstr << "<input semantic=\"INV_BIND_MATRIX\" source=\"#" << idstr << "-skin-bind_poses\"></input>" << endstr; + + PopTag(); + mOutput << startstr << "</joints>" << endstr; + + mOutput << startstr << "<vertex_weights count=\"" << mesh->mNumVertices << "\">" << endstr; + PushTag(); + + mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstr << "-skin-joints\" offset=\"0\"></input>" << endstr; + mOutput << startstr << "<input semantic=\"WEIGHT\" source=\"#" << idstr << "-skin-weights\" offset=\"1\"></input>" << endstr; + + mOutput << startstr << "<vcount>"; + + std::vector<ai_uint> num_influences(mesh->mNumVertices, (ai_uint)0); + for (size_t i = 0; i < mesh->mNumBones; ++i) + for (size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j) + ++num_influences[mesh->mBones[i]->mWeights[j].mVertexId]; + + for (size_t i = 0; i < mesh->mNumVertices; ++i) + mOutput << num_influences[i] << " "; + + mOutput << "</vcount>" << endstr; + + mOutput << startstr << "<v>"; + + ai_uint joint_weight_indices_length = 0; + std::vector<ai_uint> accum_influences; + accum_influences.reserve(num_influences.size()); + for (size_t i = 0; i < num_influences.size(); ++i) { + accum_influences.push_back(joint_weight_indices_length); + joint_weight_indices_length += num_influences[i]; + } + + ai_uint weight_index = 0; + std::vector<ai_int> joint_weight_indices(2 * joint_weight_indices_length, (ai_int)-1); + for (unsigned int i = 0; i < mesh->mNumBones; ++i) + for (unsigned j = 0; j < mesh->mBones[i]->mNumWeights; ++j) { + unsigned int vId = mesh->mBones[i]->mWeights[j].mVertexId; + for (ai_uint k = 0; k < num_influences[vId]; ++k) { + if (joint_weight_indices[2 * (accum_influences[vId] + k)] == -1) { + joint_weight_indices[2 * (accum_influences[vId] + k)] = i; + joint_weight_indices[2 * (accum_influences[vId] + k) + 1] = weight_index; + break; + } + } + ++weight_index; + } + + for (size_t i = 0; i < joint_weight_indices.size(); ++i) + mOutput << joint_weight_indices[i] << " "; + + num_influences.clear(); + accum_influences.clear(); + joint_weight_indices.clear(); + + mOutput << "</v>" << endstr; + + PopTag(); + mOutput << startstr << "</vertex_weights>" << endstr; + + PopTag(); + mOutput << startstr << "</skin>" << endstr; + + PopTag(); + mOutput << startstr << "</controller>" << endstr; +} + +// ------------------------------------------------------------------------------------------------ +// Writes the geometry library +void ColladaExporter::WriteGeometryLibrary() { + mOutput << startstr << "<library_geometries>" << endstr; + PushTag(); + + for (size_t a = 0; a < mScene->mNumMeshes; ++a) + WriteGeometry(a); + + PopTag(); + mOutput << startstr << "</library_geometries>" << endstr; +} + +// ------------------------------------------------------------------------------------------------ +// Writes the given mesh +void ColladaExporter::WriteGeometry(size_t pIndex) { + const aiMesh *mesh = mScene->mMeshes[pIndex]; + const std::string geometryId = GetObjectUniqueId(AiObjectType::Mesh, pIndex); + const std::string geometryName = GetObjectName(AiObjectType::Mesh, pIndex); + + if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0) + return; + + // opening tag + mOutput << startstr << "<geometry id=\"" << geometryId << "\" name=\"" << geometryName << "\" >" << endstr; + PushTag(); + + mOutput << startstr << "<mesh>" << endstr; + PushTag(); + + // Positions + WriteFloatArray(geometryId + "-positions", FloatType_Vector, (ai_real *)mesh->mVertices, mesh->mNumVertices); + // Normals, if any + if (mesh->HasNormals()) + WriteFloatArray(geometryId + "-normals", FloatType_Vector, (ai_real *)mesh->mNormals, mesh->mNumVertices); + + // texture coords + for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { + if (mesh->HasTextureCoords(static_cast<unsigned int>(a))) { + WriteFloatArray(geometryId + "-tex" + ai_to_string(a), mesh->mNumUVComponents[a] == 3 ? FloatType_TexCoord3 : FloatType_TexCoord2, + (ai_real *)mesh->mTextureCoords[a], mesh->mNumVertices); + } + } + + // vertex colors + for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { + if (mesh->HasVertexColors(static_cast<unsigned int>(a))) + WriteFloatArray(geometryId + "-color" + ai_to_string(a), FloatType_Color, (ai_real *)mesh->mColors[a], mesh->mNumVertices); + } + + // assemble vertex structure + // Only write input for POSITION since we will write other as shared inputs in polygon definition + mOutput << startstr << "<vertices id=\"" << geometryId << "-vertices" + << "\">" << endstr; + PushTag(); + mOutput << startstr << "<input semantic=\"POSITION\" source=\"#" << geometryId << "-positions\" />" << endstr; + PopTag(); + mOutput << startstr << "</vertices>" << endstr; + + // count the number of lines, triangles and polygon meshes + int countLines = 0; + int countPoly = 0; + for (size_t a = 0; a < mesh->mNumFaces; ++a) { + if (mesh->mFaces[a].mNumIndices == 2) + countLines++; + else if (mesh->mFaces[a].mNumIndices >= 3) + countPoly++; + } + + // lines + if (countLines) { + mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr; + PushTag(); + mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr; + if (mesh->HasNormals()) + mOutput << startstr << "<input semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr; + for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { + if (mesh->HasTextureCoords(static_cast<unsigned int>(a))) + mOutput << startstr << "<input semantic=\"TEXCOORD\" source=\"#" << geometryId << "-tex" << a << "\" " + << "set=\"" << a << "\"" + << " />" << endstr; + } + for (size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) { + if (mesh->HasVertexColors(static_cast<unsigned int>(a))) + mOutput << startstr << "<input semantic=\"COLOR\" source=\"#" << geometryId << "-color" << a << "\" " + << "set=\"" << a << "\"" + << " />" << endstr; + } + + mOutput << startstr << "<p>"; + for (size_t a = 0; a < mesh->mNumFaces; ++a) { + const aiFace &face = mesh->mFaces[a]; + if (face.mNumIndices != 2) continue; + for (size_t b = 0; b < face.mNumIndices; ++b) + mOutput << face.mIndices[b] << " "; + } + mOutput << "</p>" << endstr; + PopTag(); + mOutput << startstr << "</lines>" << endstr; + } + + // triangle - don't use it, because compatibility problems + + // polygons + if (countPoly) { + mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr; + PushTag(); + mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr; + if (mesh->HasNormals()) + mOutput << startstr << "<input offset=\"0\" semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr; + for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { + if (mesh->HasTextureCoords(static_cast<unsigned int>(a))) + mOutput << startstr << "<input offset=\"0\" semantic=\"TEXCOORD\" source=\"#" << geometryId << "-tex" << a << "\" " + << "set=\"" << a << "\"" + << " />" << endstr; + } + for (size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) { + if (mesh->HasVertexColors(static_cast<unsigned int>(a))) + mOutput << startstr << "<input offset=\"0\" semantic=\"COLOR\" source=\"#" << geometryId << "-color" << a << "\" " + << "set=\"" << a << "\"" + << " />" << endstr; + } + + mOutput << startstr << "<vcount>"; + for (size_t a = 0; a < mesh->mNumFaces; ++a) { + if (mesh->mFaces[a].mNumIndices < 3) continue; + mOutput << mesh->mFaces[a].mNumIndices << " "; + } + mOutput << "</vcount>" << endstr; + + mOutput << startstr << "<p>"; + for (size_t a = 0; a < mesh->mNumFaces; ++a) { + const aiFace &face = mesh->mFaces[a]; + if (face.mNumIndices < 3) continue; + for (size_t b = 0; b < face.mNumIndices; ++b) + mOutput << face.mIndices[b] << " "; + } + mOutput << "</p>" << endstr; + PopTag(); + mOutput << startstr << "</polylist>" << endstr; + } + + // closing tags + PopTag(); + mOutput << startstr << "</mesh>" << endstr; + PopTag(); + mOutput << startstr << "</geometry>" << endstr; +} + +// ------------------------------------------------------------------------------------------------ +// Writes a float array of the given type +void ColladaExporter::WriteFloatArray(const std::string &pIdString, FloatDataType pType, const ai_real *pData, size_t pElementCount) { + size_t floatsPerElement = 0; + switch (pType) { + case FloatType_Vector: floatsPerElement = 3; break; + case FloatType_TexCoord2: floatsPerElement = 2; break; + case FloatType_TexCoord3: floatsPerElement = 3; break; + case FloatType_Color: floatsPerElement = 3; break; + case FloatType_Mat4x4: floatsPerElement = 16; break; + case FloatType_Weight: floatsPerElement = 1; break; + case FloatType_Time: floatsPerElement = 1; break; + default: + return; + } + + std::string arrayId = XMLIDEncode(pIdString) + "-array"; + + mOutput << startstr << "<source id=\"" << XMLIDEncode(pIdString) << "\" name=\"" << XMLEscape(pIdString) << "\">" << endstr; + PushTag(); + + // source array + mOutput << startstr << "<float_array id=\"" << arrayId << "\" count=\"" << pElementCount * floatsPerElement << "\"> "; + PushTag(); + + if (pType == FloatType_TexCoord2) { + for (size_t a = 0; a < pElementCount; ++a) { + mOutput << pData[a * 3 + 0] << " "; + mOutput << pData[a * 3 + 1] << " "; + } + } else if (pType == FloatType_Color) { + for (size_t a = 0; a < pElementCount; ++a) { + mOutput << pData[a * 4 + 0] << " "; + mOutput << pData[a * 4 + 1] << " "; + mOutput << pData[a * 4 + 2] << " "; + } + } else { + for (size_t a = 0; a < pElementCount * floatsPerElement; ++a) + mOutput << pData[a] << " "; + } + mOutput << "</float_array>" << endstr; + PopTag(); + + // the usual Collada fun. Let's bloat it even more! + mOutput << startstr << "<technique_common>" << endstr; + PushTag(); + mOutput << startstr << "<accessor count=\"" << pElementCount << "\" offset=\"0\" source=\"#" << arrayId << "\" stride=\"" << floatsPerElement << "\">" << endstr; + PushTag(); + + switch (pType) { + case FloatType_Vector: + mOutput << startstr << "<param name=\"X\" type=\"float\" />" << endstr; + mOutput << startstr << "<param name=\"Y\" type=\"float\" />" << endstr; + mOutput << startstr << "<param name=\"Z\" type=\"float\" />" << endstr; + break; + + case FloatType_TexCoord2: + mOutput << startstr << "<param name=\"S\" type=\"float\" />" << endstr; + mOutput << startstr << "<param name=\"T\" type=\"float\" />" << endstr; + break; + + case FloatType_TexCoord3: + mOutput << startstr << "<param name=\"S\" type=\"float\" />" << endstr; + mOutput << startstr << "<param name=\"T\" type=\"float\" />" << endstr; + mOutput << startstr << "<param name=\"P\" type=\"float\" />" << endstr; + break; + + case FloatType_Color: + mOutput << startstr << "<param name=\"R\" type=\"float\" />" << endstr; + mOutput << startstr << "<param name=\"G\" type=\"float\" />" << endstr; + mOutput << startstr << "<param name=\"B\" type=\"float\" />" << endstr; + break; + + case FloatType_Mat4x4: + mOutput << startstr << "<param name=\"TRANSFORM\" type=\"float4x4\" />" << endstr; + break; + + case FloatType_Weight: + mOutput << startstr << "<param name=\"WEIGHT\" type=\"float\" />" << endstr; + break; + + // customized, add animation related + case FloatType_Time: + mOutput << startstr << "<param name=\"TIME\" type=\"float\" />" << endstr; + break; + } + + PopTag(); + mOutput << startstr << "</accessor>" << endstr; + PopTag(); + mOutput << startstr << "</technique_common>" << endstr; + PopTag(); + mOutput << startstr << "</source>" << endstr; +} + +// ------------------------------------------------------------------------------------------------ +// Writes the scene library +void ColladaExporter::WriteSceneLibrary() { + // Determine if we are using the aiScene root or our own + std::string sceneName("Scene"); + if (mAdd_root_node) { + mSceneId = MakeUniqueId(mUniqueIds, sceneName, std::string()); + mUniqueIds.insert(mSceneId); + } else { + mSceneId = GetNodeUniqueId(mScene->mRootNode); + sceneName = GetNodeName(mScene->mRootNode); + } + + mOutput << startstr << "<library_visual_scenes>" << endstr; + PushTag(); + mOutput << startstr << "<visual_scene id=\"" + mSceneId + "\" name=\"" + sceneName + "\">" << endstr; + PushTag(); + + if (mAdd_root_node) { + // Export the root node + WriteNode(mScene->mRootNode); + } else { + // Have already exported the root node + for (size_t a = 0; a < mScene->mRootNode->mNumChildren; ++a) + WriteNode(mScene->mRootNode->mChildren[a]); + } + + PopTag(); + mOutput << startstr << "</visual_scene>" << endstr; + PopTag(); + mOutput << startstr << "</library_visual_scenes>" << endstr; +} +// ------------------------------------------------------------------------------------------------ +void ColladaExporter::WriteAnimationLibrary(size_t pIndex) { + const aiAnimation *anim = mScene->mAnimations[pIndex]; + + if (anim->mNumChannels == 0 && anim->mNumMeshChannels == 0 && anim->mNumMorphMeshChannels == 0) + return; + + const std::string animationNameEscaped = GetObjectName(AiObjectType::Animation, pIndex); + const std::string idstrEscaped = GetObjectUniqueId(AiObjectType::Animation, pIndex); + + mOutput << startstr << "<animation id=\"" + idstrEscaped + "\" name=\"" + animationNameEscaped + "\">" << endstr; + PushTag(); + + std::string cur_node_idstr; + for (size_t a = 0; a < anim->mNumChannels; ++a) { + const aiNodeAnim *nodeAnim = anim->mChannels[a]; + + // sanity check + if (nodeAnim->mNumPositionKeys != nodeAnim->mNumScalingKeys || nodeAnim->mNumPositionKeys != nodeAnim->mNumRotationKeys) { + continue; + } + + { + cur_node_idstr.clear(); + cur_node_idstr += nodeAnim->mNodeName.data; + cur_node_idstr += std::string("_matrix-input"); + + std::vector<ai_real> frames; + for (size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) { + frames.push_back(static_cast<ai_real>(nodeAnim->mPositionKeys[i].mTime)); + } + + WriteFloatArray(cur_node_idstr, FloatType_Time, (const ai_real *)frames.data(), frames.size()); + frames.clear(); + } + + { + cur_node_idstr.clear(); + + cur_node_idstr += nodeAnim->mNodeName.data; + cur_node_idstr += std::string("_matrix-output"); + + std::vector<ai_real> keyframes; + keyframes.reserve(nodeAnim->mNumPositionKeys * 16); + for (size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) { + aiVector3D Scaling = nodeAnim->mScalingKeys[i].mValue; + aiMatrix4x4 ScalingM; // identity + ScalingM[0][0] = Scaling.x; + ScalingM[1][1] = Scaling.y; + ScalingM[2][2] = Scaling.z; + + aiQuaternion RotationQ = nodeAnim->mRotationKeys[i].mValue; + aiMatrix4x4 s = aiMatrix4x4(RotationQ.GetMatrix()); + aiMatrix4x4 RotationM(s.a1, s.a2, s.a3, 0, s.b1, s.b2, s.b3, 0, s.c1, s.c2, s.c3, 0, 0, 0, 0, 1); + + aiVector3D Translation = nodeAnim->mPositionKeys[i].mValue; + aiMatrix4x4 TranslationM; // identity + TranslationM[0][3] = Translation.x; + TranslationM[1][3] = Translation.y; + TranslationM[2][3] = Translation.z; + + // Combine the above transformations + aiMatrix4x4 mat = TranslationM * RotationM * ScalingM; + + for (unsigned int j = 0; j < 4; ++j) { + keyframes.insert(keyframes.end(), mat[j], mat[j] + 4); + } + } + + WriteFloatArray(cur_node_idstr, FloatType_Mat4x4, (const ai_real *)keyframes.data(), keyframes.size() / 16); + } + + { + std::vector<std::string> names; + for (size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) { + if (nodeAnim->mPreState == aiAnimBehaviour_DEFAULT || nodeAnim->mPreState == aiAnimBehaviour_LINEAR || nodeAnim->mPreState == aiAnimBehaviour_REPEAT) { + names.push_back("LINEAR"); + } else if (nodeAnim->mPostState == aiAnimBehaviour_CONSTANT) { + names.push_back("STEP"); + } + } + + const std::string cur_node_idstr2 = nodeAnim->mNodeName.data + std::string("_matrix-interpolation"); + std::string arrayId = XMLIDEncode(cur_node_idstr2) + "-array"; + + mOutput << startstr << "<source id=\"" << XMLIDEncode(cur_node_idstr2) << "\">" << endstr; + PushTag(); + + // source array + mOutput << startstr << "<Name_array id=\"" << arrayId << "\" count=\"" << names.size() << "\"> "; + for (size_t aa = 0; aa < names.size(); ++aa) { + mOutput << names[aa] << " "; + } + mOutput << "</Name_array>" << endstr; + + mOutput << startstr << "<technique_common>" << endstr; + PushTag(); + + mOutput << startstr << "<accessor source=\"#" << arrayId << "\" count=\"" << names.size() << "\" stride=\"" << 1 << "\">" << endstr; + PushTag(); + + mOutput << startstr << "<param name=\"INTERPOLATION\" type=\"name\"></param>" << endstr; + + PopTag(); + mOutput << startstr << "</accessor>" << endstr; + + PopTag(); + mOutput << startstr << "</technique_common>" << endstr; + + PopTag(); + mOutput << startstr << "</source>" << endstr; + } + } + + for (size_t a = 0; a < anim->mNumChannels; ++a) { + const aiNodeAnim *nodeAnim = anim->mChannels[a]; + + { + // samplers + const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-sampler"); + mOutput << startstr << "<sampler id=\"" << XMLIDEncode(node_idstr) << "\">" << endstr; + PushTag(); + + mOutput << startstr << "<input semantic=\"INPUT\" source=\"#" << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-input")) << "\"/>" << endstr; + mOutput << startstr << "<input semantic=\"OUTPUT\" source=\"#" << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-output")) << "\"/>" << endstr; + mOutput << startstr << "<input semantic=\"INTERPOLATION\" source=\"#" << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-interpolation")) << "\"/>" << endstr; + + PopTag(); + mOutput << startstr << "</sampler>" << endstr; + } + } + + for (size_t a = 0; a < anim->mNumChannels; ++a) { + const aiNodeAnim *nodeAnim = anim->mChannels[a]; + + { + // channels + mOutput << startstr << "<channel source=\"#" << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-sampler")) << "\" target=\"" << XMLIDEncode(nodeAnim->mNodeName.data) << "/matrix\"/>" << endstr; + } + } + + PopTag(); + mOutput << startstr << "</animation>" << endstr; +} +// ------------------------------------------------------------------------------------------------ +void ColladaExporter::WriteAnimationsLibrary() { + if (mScene->mNumAnimations > 0) { + mOutput << startstr << "<library_animations>" << endstr; + PushTag(); + + // start recursive write at the root node + for (size_t a = 0; a < mScene->mNumAnimations; ++a) + WriteAnimationLibrary(a); + + PopTag(); + mOutput << startstr << "</library_animations>" << endstr; + } +} +// ------------------------------------------------------------------------------------------------ +// Helper to find a bone by name in the scene +aiBone *findBone(const aiScene *scene, const aiString &name) { + for (size_t m = 0; m < scene->mNumMeshes; m++) { + aiMesh *mesh = scene->mMeshes[m]; + for (size_t b = 0; b < mesh->mNumBones; b++) { + aiBone *bone = mesh->mBones[b]; + if (name == bone->mName) { + return bone; + } + } + } + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// Helper to find the node associated with a bone in the scene +const aiNode *findBoneNode(const aiNode *aNode, const aiBone *bone) { + if (aNode && bone && aNode->mName == bone->mName) { + return aNode; + } + + if (aNode && bone) { + for (unsigned int i = 0; i < aNode->mNumChildren; ++i) { + aiNode *aChild = aNode->mChildren[i]; + const aiNode *foundFromChild = nullptr; + if (aChild) { + foundFromChild = findBoneNode(aChild, bone); + if (foundFromChild) { + return foundFromChild; + } + } + } + } + + return nullptr; +} + +const aiNode *findSkeletonRootNode(const aiScene *scene, const aiMesh *mesh) { + std::set<const aiNode *> topParentBoneNodes; + if (mesh && mesh->mNumBones > 0) { + for (unsigned int i = 0; i < mesh->mNumBones; ++i) { + aiBone *bone = mesh->mBones[i]; + + const aiNode *node = findBoneNode(scene->mRootNode, bone); + if (node) { + while (node->mParent && findBone(scene, node->mParent->mName) != nullptr) { + node = node->mParent; + } + topParentBoneNodes.insert(node); + } + } + } + + if (!topParentBoneNodes.empty()) { + const aiNode *parentBoneNode = *topParentBoneNodes.begin(); + if (topParentBoneNodes.size() == 1) { + return parentBoneNode; + } else { + for (auto it : topParentBoneNodes) { + if (it->mParent) return it->mParent; + } + return parentBoneNode; + } + } + + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// Recursively writes the given node +void ColladaExporter::WriteNode(const aiNode *pNode) { + // If the node is associated with a bone, it is a joint node (JOINT) + // otherwise it is a normal node (NODE) + // Assimp-specific: nodes with no name cannot be associated with bones + const char *node_type; + bool is_joint, is_skeleton_root = false; + if (pNode->mName.length == 0 || nullptr == findBone(mScene, pNode->mName)) { + node_type = "NODE"; + is_joint = false; + } else { + node_type = "JOINT"; + is_joint = true; + if (!pNode->mParent || nullptr == findBone(mScene, pNode->mParent->mName)) { + is_skeleton_root = true; + } + } + + const std::string node_id = GetNodeUniqueId(pNode); + const std::string node_name = GetNodeName(pNode); + mOutput << startstr << "<node "; + if (is_skeleton_root) { + mFoundSkeletonRootNodeID = node_id; // For now, only support one skeleton in a scene. + } + mOutput << "id=\"" << node_id << "\" " << (is_joint ? "sid=\"" + node_id + "\" " : ""); + mOutput << "name=\"" << node_name + << "\" type=\"" << node_type + << "\">" << endstr; + PushTag(); + + // write transformation - we can directly put the matrix there + // TODO: (thom) decompose into scale - rot - quad to allow addressing it by animations afterwards + aiMatrix4x4 mat = pNode->mTransformation; + + // If this node is a Camera node, the camera coordinate system needs to be multiplied in. + // When importing from Collada, the mLookAt is set to 0, 0, -1, and the node transform is unchanged. + // When importing from a different format, mLookAt is set to 0, 0, 1. Therefore, the local camera + // coordinate system must be changed to matche the Collada specification. + for (size_t i = 0; i < mScene->mNumCameras; i++) { + if (mScene->mCameras[i]->mName == pNode->mName) { + aiMatrix4x4 sourceView; + mScene->mCameras[i]->GetCameraMatrix(sourceView); + + aiMatrix4x4 colladaView; + colladaView.a1 = colladaView.c3 = -1; // move into -z space. + mat *= (sourceView * colladaView); + break; + } + } + + // customized, sid should be 'matrix' to match with loader code. + //mOutput << startstr << "<matrix sid=\"transform\">"; + mOutput << startstr << "<matrix sid=\"matrix\">"; + + mOutput << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << " "; + mOutput << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4 << " "; + mOutput << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4 << " "; + mOutput << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4; + mOutput << "</matrix>" << endstr; + + if (pNode->mNumMeshes == 0) { + //check if it is a camera node + for (size_t i = 0; i < mScene->mNumCameras; i++) { + if (mScene->mCameras[i]->mName == pNode->mName) { + mOutput << startstr << "<instance_camera url=\"#" << GetObjectUniqueId(AiObjectType::Camera, i) << "\"/>" << endstr; + break; + } + } + //check if it is a light node + for (size_t i = 0; i < mScene->mNumLights; i++) { + if (mScene->mLights[i]->mName == pNode->mName) { + mOutput << startstr << "<instance_light url=\"#" << GetObjectUniqueId(AiObjectType::Light, i) << "\"/>" << endstr; + break; + } + } + + } else + // instance every geometry + for (size_t a = 0; a < pNode->mNumMeshes; ++a) { + const aiMesh *mesh = mScene->mMeshes[pNode->mMeshes[a]]; + // do not instantiate mesh if empty. I wonder how this could happen + if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0) + continue; + + const std::string meshId = GetObjectUniqueId(AiObjectType::Mesh, pNode->mMeshes[a]); + + if (mesh->mNumBones == 0) { + mOutput << startstr << "<instance_geometry url=\"#" << meshId << "\">" << endstr; + PushTag(); + } else { + mOutput << startstr + << "<instance_controller url=\"#" << meshId << "-skin\">" + << endstr; + PushTag(); + + // note! this mFoundSkeletonRootNodeID some how affects animation, it makes the mesh attaches to armature skeleton root node. + // use the first bone to find skeleton root + const aiNode *skeletonRootBoneNode = findSkeletonRootNode(mScene, mesh); + if (skeletonRootBoneNode) { + mFoundSkeletonRootNodeID = GetNodeUniqueId(skeletonRootBoneNode); + } + mOutput << startstr << "<skeleton>#" << mFoundSkeletonRootNodeID << "</skeleton>" << endstr; + } + mOutput << startstr << "<bind_material>" << endstr; + PushTag(); + mOutput << startstr << "<technique_common>" << endstr; + PushTag(); + mOutput << startstr << "<instance_material symbol=\"defaultMaterial\" target=\"#" << GetObjectUniqueId(AiObjectType::Material, mesh->mMaterialIndex) << "\">" << endstr; + PushTag(); + for (size_t aa = 0; aa < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++aa) { + if (mesh->HasTextureCoords(static_cast<unsigned int>(aa))) + // semantic as in <texture texcoord=...> + // input_semantic as in <input semantic=...> + // input_set as in <input set=...> + mOutput << startstr << "<bind_vertex_input semantic=\"CHANNEL" << aa << "\" input_semantic=\"TEXCOORD\" input_set=\"" << aa << "\"/>" << endstr; + } + PopTag(); + mOutput << startstr << "</instance_material>" << endstr; + PopTag(); + mOutput << startstr << "</technique_common>" << endstr; + PopTag(); + mOutput << startstr << "</bind_material>" << endstr; + + PopTag(); + if (mesh->mNumBones == 0) + mOutput << startstr << "</instance_geometry>" << endstr; + else + mOutput << startstr << "</instance_controller>" << endstr; + } + + // recurse into subnodes + for (size_t a = 0; a < pNode->mNumChildren; ++a) + WriteNode(pNode->mChildren[a]); + + PopTag(); + mOutput << startstr << "</node>" << endstr; +} + +void ColladaExporter::CreateNodeIds(const aiNode *node) { + GetNodeUniqueId(node); + for (size_t a = 0; a < node->mNumChildren; ++a) + CreateNodeIds(node->mChildren[a]); +} + +std::string ColladaExporter::GetNodeUniqueId(const aiNode *node) { + // Use the pointer as the key. This is safe because the scene is immutable. + auto idIt = mNodeIdMap.find(node); + if (idIt != mNodeIdMap.cend()) + return idIt->second; + + // Prefer the requested Collada Id if extant + std::string idStr; + aiString origId; + if (node->mMetaData && node->mMetaData->Get(AI_METADATA_COLLADA_ID, origId)) { + idStr = origId.C_Str(); + } else { + idStr = node->mName.C_Str(); + } + // Make sure the requested id is valid + if (idStr.empty()) + idStr = "node"; + else + idStr = XMLIDEncode(idStr); + + // Ensure it's unique + idStr = MakeUniqueId(mUniqueIds, idStr, std::string()); + mUniqueIds.insert(idStr); + mNodeIdMap.insert(std::make_pair(node, idStr)); + return idStr; +} + +std::string ColladaExporter::GetNodeName(const aiNode *node) { + + return XMLEscape(node->mName.C_Str()); +} + +std::string ColladaExporter::GetBoneUniqueId(const aiBone *bone) { + // Find the Node that is this Bone + const aiNode *boneNode = findBoneNode(mScene->mRootNode, bone); + if (boneNode == nullptr) + return std::string(); + + return GetNodeUniqueId(boneNode); +} + +std::string ColladaExporter::GetObjectUniqueId(AiObjectType type, size_t pIndex) { + auto idIt = GetObjectIdMap(type).find(pIndex); + if (idIt != GetObjectIdMap(type).cend()) + return idIt->second; + + // Not seen this object before, create and add + NameIdPair result = AddObjectIndexToMaps(type, pIndex); + return result.second; +} + +std::string ColladaExporter::GetObjectName(AiObjectType type, size_t pIndex) { + auto objectName = GetObjectNameMap(type).find(pIndex); + if (objectName != GetObjectNameMap(type).cend()) + return objectName->second; + + // Not seen this object before, create and add + NameIdPair result = AddObjectIndexToMaps(type, pIndex); + return result.first; +} + +// Determine unique id and add the name and id to the maps +// @param type object type +// @param index object index +// @param name in/out. Caller to set the original name if known. +// @param idStr in/out. Caller to set the preferred id if known. +ColladaExporter::NameIdPair ColladaExporter::AddObjectIndexToMaps(AiObjectType type, size_t index) { + + std::string name; + std::string idStr; + std::string idPostfix; + + // Get the name and id postfix + switch (type) { + case AiObjectType::Mesh: name = mScene->mMeshes[index]->mName.C_Str(); break; + case AiObjectType::Material: name = mScene->mMaterials[index]->GetName().C_Str(); break; + case AiObjectType::Animation: name = mScene->mAnimations[index]->mName.C_Str(); break; + case AiObjectType::Light: + name = mScene->mLights[index]->mName.C_Str(); + idPostfix = "-light"; + break; + case AiObjectType::Camera: + name = mScene->mCameras[index]->mName.C_Str(); + idPostfix = "-camera"; + break; + case AiObjectType::Count: throw std::logic_error("ColladaExporter::AiObjectType::Count is not an object type"); + } + + if (name.empty()) { + // Default ids if empty name + switch (type) { + case AiObjectType::Mesh: idStr = std::string("mesh_"); break; + case AiObjectType::Material: idStr = std::string("material_"); break; // This one should never happen + case AiObjectType::Animation: idStr = std::string("animation_"); break; + case AiObjectType::Light: idStr = std::string("light_"); break; + case AiObjectType::Camera: idStr = std::string("camera_"); break; + case AiObjectType::Count: throw std::logic_error("ColladaExporter::AiObjectType::Count is not an object type"); + } + idStr.append(ai_to_string(index)); + } else { + idStr = XMLIDEncode(name); + } + + if (!name.empty()) + name = XMLEscape(name); + + idStr = MakeUniqueId(mUniqueIds, idStr, idPostfix); + + // Add to maps + mUniqueIds.insert(idStr); + GetObjectIdMap(type).insert(std::make_pair(index, idStr)); + GetObjectNameMap(type).insert(std::make_pair(index, name)); + + return std::make_pair(name, idStr); +} + +} // end of namespace Assimp + +#endif +#endif diff --git a/libs/assimp/code/AssetLib/Collada/ColladaExporter.h b/libs/assimp/code/AssetLib/Collada/ColladaExporter.h new file mode 100644 index 0000000..56415fb --- /dev/null +++ b/libs/assimp/code/AssetLib/Collada/ColladaExporter.h @@ -0,0 +1,257 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 ColladaExporter.h + * Declares the exporter class to write a scene to a Collada file + */ +#ifndef AI_COLLADAEXPORTER_H_INC +#define AI_COLLADAEXPORTER_H_INC + +#include <assimp/ai_assert.h> +#include <assimp/material.h> + +#include <array> +#include <map> +#include <sstream> +#include <unordered_set> +#include <vector> + +struct aiScene; +struct aiNode; +struct aiLight; +struct aiBone; + +namespace Assimp { + +class IOSystem; + +/// Helper class to export a given scene to a Collada file. Just for my personal +/// comfort when implementing it. +class ColladaExporter { +public: + /// Constructor for a specific scene to export + ColladaExporter(const aiScene *pScene, IOSystem *pIOSystem, const std::string &path, const std::string &file); + + /// Destructor + virtual ~ColladaExporter(); + +protected: + /// Starts writing the contents + void WriteFile(); + + /// Writes the asset header + void WriteHeader(); + + /// Writes the embedded textures + void WriteTextures(); + + /// Writes the material setup + void WriteMaterials(); + + /// Writes the cameras library + void WriteCamerasLibrary(); + + // Write a camera entry + void WriteCamera(size_t pIndex); + + /// Writes the cameras library + void WriteLightsLibrary(); + + // Write a camera entry + void WriteLight(size_t pIndex); + void WritePointLight(const aiLight *const light); + void WriteDirectionalLight(const aiLight *const light); + void WriteSpotLight(const aiLight *const light); + void WriteAmbienttLight(const aiLight *const light); + + /// Writes the controller library + void WriteControllerLibrary(); + + /// Writes a skin controller of the given mesh + void WriteController(size_t pIndex); + + /// Writes the geometry library + void WriteGeometryLibrary(); + + /// Writes the given mesh + void WriteGeometry(size_t pIndex); + + //enum FloatDataType { FloatType_Vector, FloatType_TexCoord2, FloatType_TexCoord3, FloatType_Color, FloatType_Mat4x4, FloatType_Weight }; + // customized to add animation related type + enum FloatDataType { FloatType_Vector, + FloatType_TexCoord2, + FloatType_TexCoord3, + FloatType_Color, + FloatType_Mat4x4, + FloatType_Weight, + FloatType_Time }; + + /// Writes a float array of the given type + void WriteFloatArray(const std::string &pIdString, FloatDataType pType, const ai_real *pData, size_t pElementCount); + + /// Writes the scene library + void WriteSceneLibrary(); + + // customized, Writes the animation library + void WriteAnimationsLibrary(); + void WriteAnimationLibrary(size_t pIndex); + std::string mFoundSkeletonRootNodeID = "skeleton_root"; // will be replaced by found node id in the WriteNode call. + + /// Recursively writes the given node + void WriteNode(const aiNode *pNode); + + /// Enters a new xml element, which increases the indentation + void PushTag() { startstr.append(" "); } + /// Leaves an element, decreasing the indentation + void PopTag() { + ai_assert(startstr.length() > 1); + startstr.erase(startstr.length() - 2); + } + + void CreateNodeIds(const aiNode *node); + + /// Get or Create a unique Node ID string for the given Node + std::string GetNodeUniqueId(const aiNode *node); + std::string GetNodeName(const aiNode *node); + + std::string GetBoneUniqueId(const aiBone *bone); + + enum class AiObjectType { + Mesh, + Material, + Animation, + Light, + Camera, + Count, + }; + /// Get or Create a unique ID string for the given scene object index + std::string GetObjectUniqueId(AiObjectType type, size_t pIndex); + /// Get or Create a name string for the given scene object index + std::string GetObjectName(AiObjectType type, size_t pIndex); + + typedef std::map<size_t, std::string> IndexIdMap; + typedef std::pair<std::string, std::string> NameIdPair; + NameIdPair AddObjectIndexToMaps(AiObjectType type, size_t pIndex); + + // Helpers + inline IndexIdMap &GetObjectIdMap(AiObjectType type) { return mObjectIdMap[static_cast<size_t>(type)]; } + inline IndexIdMap &GetObjectNameMap(AiObjectType type) { return mObjectNameMap[static_cast<size_t>(type)]; } + +private: + std::unordered_set<std::string> mUniqueIds; // Cache of used unique ids + std::map<const void *, std::string> mNodeIdMap; // Cache of encoded node and bone ids + std::array<IndexIdMap, static_cast<size_t>(AiObjectType::Count)> mObjectIdMap; // Cache of encoded unique IDs + std::array<IndexIdMap, static_cast<size_t>(AiObjectType::Count)> mObjectNameMap; // Cache of encoded names + +public: + /// Stringstream to write all output into + std::stringstream mOutput; + + /// The IOSystem for output + IOSystem *mIOSystem; + + /// Path of the directory where the scene will be exported + const std::string mPath; + + /// Name of the file (without extension) where the scene will be exported + const std::string mFile; + + /// The scene to be written + const aiScene *const mScene; + std::string mSceneId; + bool mAdd_root_node = false; + + /// current line start string, contains the current indentation for simple stream insertion + std::string startstr; + /// current line end string for simple stream insertion + const std::string endstr; + + // pair of color and texture - texture precedences color + struct Surface { + bool exist; + aiColor4D color; + std::string texture; + size_t channel; + Surface() { + exist = false; + channel = 0; + } + }; + + struct Property { + bool exist; + ai_real value; + Property() : + exist(false), + value(0.0) {} + }; + + // summarize a material in an convenient way. + struct Material { + std::string id; + std::string name; + std::string shading_model; + Surface ambient, diffuse, specular, emissive, reflective, transparent, normal; + Property shininess, transparency, index_refraction; + + Material() {} + }; + + std::map<unsigned int, std::string> textures; + +public: + /// Dammit C++ - y u no compile two-pass? No I have to add all methods below the struct definitions + /// Reads a single surface entry from the given material keys + bool ReadMaterialSurface(Surface &poSurface, const aiMaterial &pSrcMat, aiTextureType pTexture, const char *pKey, size_t pType, size_t pIndex); + /// Writes an image entry for the given surface + void WriteImageEntry(const Surface &pSurface, const std::string &imageId); + /// Writes the two parameters necessary for referencing a texture in an effect entry + void WriteTextureParamEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &materialId); + /// Writes a color-or-texture entry into an effect definition + void WriteTextureColorEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &imageId); + /// Writes a scalar property + void WriteFloatEntry(const Property &pProperty, const std::string &pTypeName); +}; + +} // namespace Assimp + +#endif // !! AI_COLLADAEXPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/Collada/ColladaHelper.cpp b/libs/assimp/code/AssetLib/Collada/ColladaHelper.cpp new file mode 100644 index 0000000..0fb172f --- /dev/null +++ b/libs/assimp/code/AssetLib/Collada/ColladaHelper.cpp @@ -0,0 +1,99 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +/** Helper structures for the Collada loader */ + +#include "ColladaHelper.h" + +#include <assimp/ParsingUtils.h> +#include <assimp/commonMetaData.h> + +namespace Assimp { +namespace Collada { + +const MetaKeyPairVector MakeColladaAssimpMetaKeys() { + MetaKeyPairVector result; + result.emplace_back("authoring_tool", AI_METADATA_SOURCE_GENERATOR); + result.emplace_back("copyright", AI_METADATA_SOURCE_COPYRIGHT); + return result; +} + +const MetaKeyPairVector &GetColladaAssimpMetaKeys() { + static const MetaKeyPairVector result = MakeColladaAssimpMetaKeys(); + return result; +} + +const MetaKeyPairVector MakeColladaAssimpMetaKeysCamelCase() { + MetaKeyPairVector result = MakeColladaAssimpMetaKeys(); + for (auto &val : result) { + ToCamelCase(val.first); + } + return result; +} + +const MetaKeyPairVector &GetColladaAssimpMetaKeysCamelCase() { + static const MetaKeyPairVector result = MakeColladaAssimpMetaKeysCamelCase(); + return result; +} + +// ------------------------------------------------------------------------------------------------ +// Convert underscore_separated to CamelCase: "authoring_tool" becomes "AuthoringTool" +void ToCamelCase(std::string &text) { + if (text.empty()) + return; + // Capitalise first character + auto it = text.begin(); + (*it) = ai_toupper(*it); + ++it; + for (/*started above*/; it != text.end(); /*iterated below*/) { + if ((*it) == '_') { + it = text.erase(it); + if (it != text.end()) + (*it) = ai_toupper(*it); + } else { + // Make lower case + (*it) = ai_tolower(*it); + ++it; + } + } +} + +} // namespace Collada +} // namespace Assimp diff --git a/libs/assimp/code/AssetLib/Collada/ColladaHelper.h b/libs/assimp/code/AssetLib/Collada/ColladaHelper.h new file mode 100644 index 0000000..31d7b5a --- /dev/null +++ b/libs/assimp/code/AssetLib/Collada/ColladaHelper.h @@ -0,0 +1,679 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +/** Helper structures for the Collada loader */ + +#ifndef AI_COLLADAHELPER_H_INC +#define AI_COLLADAHELPER_H_INC + +#include <assimp/light.h> +#include <assimp/material.h> +#include <assimp/mesh.h> + +#include <cstdint> +#include <map> +#include <set> +#include <vector> + +struct aiMaterial; + +namespace Assimp { +namespace Collada { + +/// Collada file versions which evolved during the years ... +enum FormatVersion { + FV_1_5_n, + FV_1_4_n, + FV_1_3_n +}; + +/// Transformation types that can be applied to a node +enum TransformType { + TF_LOOKAT, + TF_ROTATE, + TF_TRANSLATE, + TF_SCALE, + TF_SKEW, + TF_MATRIX +}; + +/// Different types of input data to a vertex or face +enum InputType { + IT_Invalid, + IT_Vertex, // special type for per-index data referring to the <vertices> element carrying the per-vertex data. + IT_Position, + IT_Normal, + IT_Texcoord, + IT_Color, + IT_Tangent, + IT_Bitangent +}; + +/// Supported controller types +enum ControllerType { + Skin, + Morph +}; + +/// Supported morph methods +enum MorphMethod { + Normalized, + Relative +}; + +/// Common metadata keys as <Collada, Assimp> +using MetaKeyPair = std::pair<std::string, std::string>; +using MetaKeyPairVector = std::vector<MetaKeyPair>; + +/// Collada as lower_case (native) +const MetaKeyPairVector &GetColladaAssimpMetaKeys(); + +// Collada as CamelCase (used by Assimp for consistency) +const MetaKeyPairVector &GetColladaAssimpMetaKeysCamelCase(); + +/// Convert underscore_separated to CamelCase "authoring_tool" becomes "AuthoringTool" +void ToCamelCase(std::string &text); + +/// Contains all data for one of the different transformation types +struct Transform { + std::string mID; ///< SID of the transform step, by which anim channels address their target node + TransformType mType; + ai_real f[16]; ///< Interpretation of data depends on the type of the transformation +}; + +/// A collada camera. +struct Camera { + Camera() : + mOrtho(false), + mHorFov(10e10f), + mVerFov(10e10f), + mAspect(10e10f), + mZNear(0.1f), + mZFar(1000.f) {} + + /// Name of camera + std::string mName; + + /// True if it is an orthographic camera + bool mOrtho; + + /// Horizontal field of view in degrees + ai_real mHorFov; + + /// Vertical field of view in degrees + ai_real mVerFov; + + /// Screen aspect + ai_real mAspect; + + /// Near& far z + ai_real mZNear, mZFar; +}; + +#define ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET 1e9f + +/** A collada light source. */ +struct Light { + Light() : + mType(aiLightSource_UNDEFINED), + mAttConstant(1.f), + mAttLinear(0.f), + mAttQuadratic(0.f), + mFalloffAngle(180.f), + mFalloffExponent(0.f), + mPenumbraAngle(ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET), + mOuterAngle(ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET), + mIntensity(1.f) {} + + /// Type of the light source aiLightSourceType + ambient + unsigned int mType; + + /// Color of the light + aiColor3D mColor; + + /// Light attenuation + ai_real mAttConstant, mAttLinear, mAttQuadratic; + + /// Spot light falloff + ai_real mFalloffAngle; + ai_real mFalloffExponent; + + // ----------------------------------------------------- + // FCOLLADA extension from here + + /// ... related stuff from maja and max extensions + ai_real mPenumbraAngle; + ai_real mOuterAngle; + + /// Common light intensity + ai_real mIntensity; +}; + +/** Short vertex index description */ +struct InputSemanticMapEntry { + InputSemanticMapEntry() : + mSet(0), + mType(IT_Invalid) {} + + /// Index of set, optional + unsigned int mSet; + + /// Type of referenced vertex input + InputType mType; +}; + +/// Table to map from effect to vertex input semantics +struct SemanticMappingTable { + /// Name of material + std::string mMatName; + + /// List of semantic map commands, grouped by effect semantic name + using InputSemanticMap = std::map<std::string, InputSemanticMapEntry>; + InputSemanticMap mMap; + + /// For std::find + bool operator==(const std::string &s) const { + return s == mMatName; + } +}; + +/// A reference to a mesh inside a node, including materials assigned to the various subgroups. +/// The ID refers to either a mesh or a controller which specifies the mesh +struct MeshInstance { + ///< ID of the mesh or controller to be instanced + std::string mMeshOrController; + + ///< Map of materials by the subgroup ID they're applied to + std::map<std::string, SemanticMappingTable> mMaterials; +}; + +/// A reference to a camera inside a node +struct CameraInstance { + ///< ID of the camera + std::string mCamera; +}; + +/// A reference to a light inside a node +struct LightInstance { + ///< ID of the camera + std::string mLight; +}; + +/// A reference to a node inside a node +struct NodeInstance { + ///< ID of the node + std::string mNode; +}; + +/// A node in a scene hierarchy +struct Node { + std::string mName; + std::string mID; + std::string mSID; + Node *mParent; + std::vector<Node *> mChildren; + + /// Operations in order to calculate the resulting transformation to parent. + std::vector<Transform> mTransforms; + + /// Meshes at this node + std::vector<MeshInstance> mMeshes; + + /// Lights at this node + std::vector<LightInstance> mLights; + + /// Cameras at this node + std::vector<CameraInstance> mCameras; + + /// Node instances at this node + std::vector<NodeInstance> mNodeInstances; + + /// Root-nodes: Name of primary camera, if any + std::string mPrimaryCamera; + + /// Constructor. Begin with a zero parent + Node() : + mParent(nullptr) { + // empty + } + + /// Destructor: delete all children subsequently + ~Node() { + for (std::vector<Node *>::iterator it = mChildren.begin(); it != mChildren.end(); ++it) { + delete *it; + } + } +}; + +/// Data source array: either floats or strings +struct Data { + bool mIsStringArray; + std::vector<ai_real> mValues; + std::vector<std::string> mStrings; +}; + +/// Accessor to a data array +struct Accessor { + size_t mCount; // in number of objects + size_t mSize; // size of an object, in elements (floats or strings, mostly 1) + size_t mOffset; // in number of values + size_t mStride; // Stride in number of values + std::vector<std::string> mParams; // names of the data streams in the accessors. Empty string tells to ignore. + size_t mSubOffset[4]; // Sub-offset inside the object for the common 4 elements. For a vector, that's XYZ, for a color RGBA and so on. + // For example, SubOffset[0] denotes which of the values inside the object is the vector X component. + std::string mSource; // URL of the source array + mutable const Data *mData; // Pointer to the source array, if resolved. nullptr else + + Accessor() { + mCount = 0; + mSize = 0; + mOffset = 0; + mStride = 0; + mData = nullptr; + mSubOffset[0] = mSubOffset[1] = mSubOffset[2] = mSubOffset[3] = 0; + } +}; + +/// A single face in a mesh +struct Face { + std::vector<size_t> mIndices; +}; + +/// An input channel for mesh data, referring to a single accessor +struct InputChannel { + InputType mType; // Type of the data + size_t mIndex; // Optional index, if multiple sets of the same data type are given + size_t mOffset; // Index offset in the indices array of per-face indices. Don't ask, can't explain that any better. + std::string mAccessor; // ID of the accessor where to read the actual values from. + mutable const Accessor *mResolved; // Pointer to the accessor, if resolved. nullptr else + + InputChannel() { + mType = IT_Invalid; + mIndex = 0; + mOffset = 0; + mResolved = nullptr; + } +}; + +/// Subset of a mesh with a certain material +struct SubMesh { + std::string mMaterial; ///< subgroup identifier + size_t mNumFaces; ///< number of faces in this sub-mesh +}; + +/// Contains data for a single mesh +struct Mesh { + Mesh(const std::string &id) : + mId(id) { + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + mNumUVComponents[i] = 2; + } + } + + const std::string mId; + std::string mName; + + // just to check if there's some sophisticated addressing involved... + // which we don't support, and therefore should warn about. + std::string mVertexID; + + // Vertex data addressed by vertex indices + std::vector<InputChannel> mPerVertexData; + + // actual mesh data, assembled on encounter of a <p> element. Verbose format, not indexed + std::vector<aiVector3D> mPositions; + std::vector<aiVector3D> mNormals; + std::vector<aiVector3D> mTangents; + std::vector<aiVector3D> mBitangents; + std::vector<aiVector3D> mTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + std::vector<aiColor4D> mColors[AI_MAX_NUMBER_OF_COLOR_SETS]; + + unsigned int mNumUVComponents[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + + // Faces. Stored are only the number of vertices for each face. + // 1 == point, 2 == line, 3 == triangle, 4+ == poly + std::vector<size_t> mFaceSize; + + // Position indices for all faces in the sequence given in mFaceSize - + // necessary for bone weight assignment + std::vector<size_t> mFacePosIndices; + + // Sub-meshes in this mesh, each with a given material + std::vector<SubMesh> mSubMeshes; +}; + +/// Which type of primitives the ReadPrimitives() function is going to read +enum PrimitiveType { + Prim_Invalid, + Prim_Lines, + Prim_LineStrip, + Prim_Triangles, + Prim_TriStrips, + Prim_TriFans, + Prim_Polylist, + Prim_Polygon +}; + +/// A skeleton controller to deform a mesh with the use of joints +struct Controller { + // controller type + ControllerType mType; + + // Morphing method if type is Morph + MorphMethod mMethod; + + // the URL of the mesh deformed by the controller. + std::string mMeshId; + + // accessor URL of the joint names + std::string mJointNameSource; + + ///< The bind shape matrix, as array of floats. I'm not sure what this matrix actually describes, but it can't be ignored in all cases + ai_real mBindShapeMatrix[16]; + + // accessor URL of the joint inverse bind matrices + std::string mJointOffsetMatrixSource; + + // input channel: joint names. + InputChannel mWeightInputJoints; + // input channel: joint weights + InputChannel mWeightInputWeights; + + // Number of weights per vertex. + std::vector<size_t> mWeightCounts; + + // JointIndex-WeightIndex pairs for all vertices + std::vector<std::pair<size_t, size_t>> mWeights; + + std::string mMorphTarget; + std::string mMorphWeight; +}; + +/// A collada material. Pretty much the only member is a reference to an effect. +struct Material { + std::string mName; + std::string mEffect; +}; + +/// Type of the effect param +enum ParamType { + Param_Sampler, + Param_Surface +}; + +/// A param for an effect. Might be of several types, but they all just refer to each other, so I summarize them +struct EffectParam { + ParamType mType; + std::string mReference; // to which other thing the param is referring to. +}; + +/// Shading type supported by the standard effect spec of Collada +enum ShadeType { + Shade_Invalid, + Shade_Constant, + Shade_Lambert, + Shade_Phong, + Shade_Blinn +}; + +/// Represents a texture sampler in collada +struct Sampler { + Sampler() : + mWrapU(true), + mWrapV(true), + mMirrorU(), + mMirrorV(), + mOp(aiTextureOp_Multiply), + mUVId(UINT_MAX), + mWeighting(1.f), + mMixWithPrevious(1.f) {} + + /// Name of image reference + std::string mName; + + /// Wrap U? + bool mWrapU; + + /// Wrap V? + bool mWrapV; + + /// Mirror U? + bool mMirrorU; + + /// Mirror V? + bool mMirrorV; + + /// Blend mode + aiTextureOp mOp; + + /// UV transformation + aiUVTransform mTransform; + + /// Name of source UV channel + std::string mUVChannel; + + /// Resolved UV channel index or UINT_MAX if not known + unsigned int mUVId; + + // OKINO/MAX3D extensions from here + // ------------------------------------------------------- + + /// Weighting factor + ai_real mWeighting; + + /// Mixing factor from OKINO + ai_real mMixWithPrevious; +}; + +/// A collada effect. Can contain about anything according to the Collada spec, +/// but we limit our version to a reasonable subset. +struct Effect { + /// Shading mode + ShadeType mShadeType; + + /// Colors + aiColor4D mEmissive, mAmbient, mDiffuse, mSpecular, + mTransparent, mReflective; + + /// Textures + Sampler mTexEmissive, mTexAmbient, mTexDiffuse, mTexSpecular, + mTexTransparent, mTexBump, mTexReflective; + + /// Scalar factory + ai_real mShininess, mRefractIndex, mReflectivity; + ai_real mTransparency; + bool mHasTransparency; + bool mRGBTransparency; + bool mInvertTransparency; + + /// local params referring to each other by their SID + using ParamLibrary = std::map<std::string, Collada::EffectParam>; + ParamLibrary mParams; + + // MAX3D extensions + // --------------------------------------------------------- + // Double-sided? + bool mDoubleSided, mWireframe, mFaceted; + + Effect() : + mShadeType(Shade_Phong), + mEmissive(0, 0, 0, 1), + mAmbient(0.1f, 0.1f, 0.1f, 1), + mDiffuse(0.6f, 0.6f, 0.6f, 1), + mSpecular(0.4f, 0.4f, 0.4f, 1), + mTransparent(0, 0, 0, 1), + mShininess(10.0f), + mRefractIndex(1.f), + mReflectivity(0.f), + mTransparency(1.f), + mHasTransparency(false), + mRGBTransparency(false), + mInvertTransparency(false), + mDoubleSided(false), + mWireframe(false), + mFaceted(false) { + } +}; + +/// An image, meaning texture +struct Image { + std::string mFileName; + + /// Embedded image data + std::vector<uint8_t> mImageData; + + /// File format hint of embedded image data + std::string mEmbeddedFormat; +}; + +/// An animation channel. +struct AnimationChannel { + /// URL of the data to animate. Could be about anything, but we support only the + /// "NodeID/TransformID.SubElement" notation + std::string mTarget; + + /// Source URL of the time values. Collada calls them "input". Meh. + std::string mSourceTimes; + /// Source URL of the value values. Collada calls them "output". + std::string mSourceValues; + /// Source URL of the IN_TANGENT semantic values. + std::string mInTanValues; + /// Source URL of the OUT_TANGENT semantic values. + std::string mOutTanValues; + /// Source URL of the INTERPOLATION semantic values. + std::string mInterpolationValues; +}; + +/// An animation. Container for 0-x animation channels or 0-x animations +struct Animation { + /// Anim name + std::string mName; + + /// the animation channels, if any + std::vector<AnimationChannel> mChannels; + + /// the sub-animations, if any + std::vector<Animation *> mSubAnims; + + /// Destructor + ~Animation() { + for (std::vector<Animation *>::iterator it = mSubAnims.begin(); it != mSubAnims.end(); ++it) { + delete *it; + } + } + + /// Collect all channels in the animation hierarchy into a single channel list. + void CollectChannelsRecursively(std::vector<AnimationChannel> &channels) { + channels.insert(channels.end(), mChannels.begin(), mChannels.end()); + + for (std::vector<Animation *>::iterator it = mSubAnims.begin(); it != mSubAnims.end(); ++it) { + Animation *pAnim = (*it); + pAnim->CollectChannelsRecursively(channels); + } + } + + /// Combine all single-channel animations' channel into the same (parent) animation channel list. + void CombineSingleChannelAnimations() { + CombineSingleChannelAnimationsRecursively(this); + } + + void CombineSingleChannelAnimationsRecursively(Animation *pParent) { + std::set<std::string> childrenTargets; + bool childrenAnimationsHaveDifferentChannels = true; + + for (std::vector<Animation *>::iterator it = pParent->mSubAnims.begin(); it != pParent->mSubAnims.end();) { + Animation *anim = *it; + CombineSingleChannelAnimationsRecursively(anim); + + if (childrenAnimationsHaveDifferentChannels && anim->mChannels.size() == 1 && + childrenTargets.find(anim->mChannels[0].mTarget) == childrenTargets.end()) { + childrenTargets.insert(anim->mChannels[0].mTarget); + } else { + childrenAnimationsHaveDifferentChannels = false; + } + + ++it; + } + + // We only want to combine animations if they have different channels + if (childrenAnimationsHaveDifferentChannels) { + for (std::vector<Animation *>::iterator it = pParent->mSubAnims.begin(); it != pParent->mSubAnims.end();) { + Animation *anim = *it; + + pParent->mChannels.push_back(anim->mChannels[0]); + + it = pParent->mSubAnims.erase(it); + + delete anim; + continue; + } + } + } +}; + +/// Description of a collada animation channel which has been determined to affect the current node +struct ChannelEntry { + const Collada::AnimationChannel *mChannel; ///< the source channel + std::string mTargetId; + std::string mTransformId; // the ID of the transformation step of the node which is influenced + size_t mTransformIndex; // Index into the node's transform chain to apply the channel to + size_t mSubElement; // starting index inside the transform data + + // resolved data references + const Collada::Accessor *mTimeAccessor; ///> Collada accessor to the time values + const Collada::Data *mTimeData; ///> Source data array for the time values + const Collada::Accessor *mValueAccessor; ///> Collada accessor to the key value values + const Collada::Data *mValueData; ///> Source datat array for the key value values + + ChannelEntry() : + mChannel(), + mTransformIndex(), + mSubElement(), + mTimeAccessor(), + mTimeData(), + mValueAccessor(), + mValueData() {} +}; + +} // end of namespace Collada +} // end of namespace Assimp + +#endif // AI_COLLADAHELPER_H_INC diff --git a/libs/assimp/code/AssetLib/Collada/ColladaLoader.cpp b/libs/assimp/code/AssetLib/Collada/ColladaLoader.cpp new file mode 100644 index 0000000..775ba44 --- /dev/null +++ b/libs/assimp/code/AssetLib/Collada/ColladaLoader.cpp @@ -0,0 +1,1828 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the Collada loader */ + +#ifndef ASSIMP_BUILD_NO_COLLADA_IMPORTER + +#include "ColladaLoader.h" +#include "ColladaParser.h" +#include <assimp/ColladaMetaData.h> +#include <assimp/CreateAnimMesh.h> +#include <assimp/ParsingUtils.h> +#include <assimp/SkeletonMeshBuilder.h> +#include <assimp/ZipArchiveIOSystem.h> +#include <assimp/anim.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> + +#include <numeric> + +namespace Assimp { + +using namespace Assimp::Formatter; +using namespace Assimp::Collada; + +static const aiImporterDesc desc = { + "Collada Importer", + "", + "", + "http://collada.org", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportCompressedFlavour, + 1, + 3, + 1, + 5, + "dae xml zae" +}; + +static const float kMillisecondsFromSeconds = 1000.f; + +// Add an item of metadata to a node +// Assumes the key is not already in the list +template <typename T> +inline void AddNodeMetaData(aiNode *node, const std::string &key, const T &value) { + if (nullptr == node->mMetaData) { + node->mMetaData = new aiMetadata(); + } + node->mMetaData->Add(key, value); +} + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +ColladaLoader::ColladaLoader() : + mFileName(), + mMeshIndexByID(), + mMaterialIndexByName(), + mMeshes(), + newMats(), + mCameras(), + mLights(), + mTextures(), + mAnims(), + noSkeletonMesh(false), + ignoreUpDirection(false), + useColladaName(false), + mNodeNameCounter(0) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +ColladaLoader::~ColladaLoader() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool ColladaLoader::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + // Look for a DAE file inside, but don't extract it + ZipArchiveIOSystem zip_archive(pIOHandler, pFile); + if (zip_archive.isOpen()) { + return !ColladaParser::ReadZaeManifest(zip_archive).empty(); + } + + static const char *tokens[] = { "<collada" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +void ColladaLoader::SetupProperties(const Importer *pImp) { + noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, 0) != 0; + ignoreUpDirection = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION, 0) != 0; + useColladaName = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES, 0) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Get file extension list +const aiImporterDesc *ColladaLoader::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void ColladaLoader::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + mFileName = pFile; + + // clean all member arrays - just for safety, it should work even if we did not + mMeshIndexByID.clear(); + mMaterialIndexByName.clear(); + mMeshes.clear(); + mTargetMeshes.clear(); + newMats.clear(); + mLights.clear(); + mCameras.clear(); + mTextures.clear(); + mAnims.clear(); + + // parse the input file + ColladaParser parser(pIOHandler, pFile); + + if (!parser.mRootNode) { + throw DeadlyImportError("Collada: File came out empty. Something is wrong here."); + } + + // reserve some storage to avoid unnecessary reallocs + newMats.reserve(parser.mMaterialLibrary.size() * 2u); + mMeshes.reserve(parser.mMeshLibrary.size() * 2u); + + mCameras.reserve(parser.mCameraLibrary.size()); + mLights.reserve(parser.mLightLibrary.size()); + + // create the materials first, for the meshes to find + BuildMaterials(parser, pScene); + + // build the node hierarchy from it + pScene->mRootNode = BuildHierarchy(parser, parser.mRootNode); + + // ... then fill the materials with the now adjusted settings + FillMaterials(parser, pScene); + + // Apply unit-size scale calculation + + pScene->mRootNode->mTransformation *= aiMatrix4x4(parser.mUnitSize, 0, 0, 0, + 0, parser.mUnitSize, 0, 0, + 0, 0, parser.mUnitSize, 0, + 0, 0, 0, 1); + if (!ignoreUpDirection) { + // Convert to Y_UP, if different orientation + if (parser.mUpDirection == ColladaParser::UP_X) { + pScene->mRootNode->mTransformation *= aiMatrix4x4( + 0, -1, 0, 0, + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + } else if (parser.mUpDirection == ColladaParser::UP_Z) { + pScene->mRootNode->mTransformation *= aiMatrix4x4( + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, -1, 0, 0, + 0, 0, 0, 1); + } + } + + // Store scene metadata + if (!parser.mAssetMetaData.empty()) { + const size_t numMeta(parser.mAssetMetaData.size()); + pScene->mMetaData = aiMetadata::Alloc(static_cast<unsigned int>(numMeta)); + size_t i = 0; + for (auto it = parser.mAssetMetaData.cbegin(); it != parser.mAssetMetaData.cend(); ++it, ++i) { + pScene->mMetaData->Set(static_cast<unsigned int>(i), (*it).first, (*it).second); + } + } + + StoreSceneMeshes(pScene); + StoreSceneMaterials(pScene); + StoreSceneTextures(pScene); + StoreSceneLights(pScene); + StoreSceneCameras(pScene); + StoreAnimations(pScene, parser); + + // If no meshes have been loaded, it's probably just an animated skeleton. + if (0u == pScene->mNumMeshes) { + if (!noSkeletonMesh) { + SkeletonMeshBuilder hero(pScene); + } + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } +} + +// ------------------------------------------------------------------------------------------------ +// Recursively constructs a scene node for the given parser node and returns it. +aiNode *ColladaLoader::BuildHierarchy(const ColladaParser &pParser, const Collada::Node *pNode) { + // create a node for it + aiNode *node = new aiNode(); + + // find a name for the new node. It's more complicated than you might think + node->mName.Set(FindNameForNode(pNode)); + // if we're not using the unique IDs, hold onto them for reference and export + if (useColladaName) { + if (!pNode->mID.empty()) { + AddNodeMetaData(node, AI_METADATA_COLLADA_ID, aiString(pNode->mID)); + } + if (!pNode->mSID.empty()) { + AddNodeMetaData(node, AI_METADATA_COLLADA_SID, aiString(pNode->mSID)); + } + } + + // calculate the transformation matrix for it + node->mTransformation = pParser.CalculateResultTransform(pNode->mTransforms); + + // now resolve node instances + std::vector<const Node*> instances; + ResolveNodeInstances(pParser, pNode, instances); + + // add children. first the *real* ones + node->mNumChildren = static_cast<unsigned int>(pNode->mChildren.size() + instances.size()); + node->mChildren = new aiNode *[node->mNumChildren]; + + for (size_t a = 0; a < pNode->mChildren.size(); ++a) { + node->mChildren[a] = BuildHierarchy(pParser, pNode->mChildren[a]); + node->mChildren[a]->mParent = node; + } + + // ... and finally the resolved node instances + for (size_t a = 0; a < instances.size(); ++a) { + node->mChildren[pNode->mChildren.size() + a] = BuildHierarchy(pParser, instances[a]); + node->mChildren[pNode->mChildren.size() + a]->mParent = node; + } + + BuildMeshesForNode(pParser, pNode, node); + BuildCamerasForNode(pParser, pNode, node); + BuildLightsForNode(pParser, pNode, node); + + return node; +} + +// ------------------------------------------------------------------------------------------------ +// Resolve node instances +void ColladaLoader::ResolveNodeInstances(const ColladaParser &pParser, const Node *pNode, + std::vector<const Node*> &resolved) { + // reserve enough storage + resolved.reserve(pNode->mNodeInstances.size()); + + // ... and iterate through all nodes to be instanced as children of pNode + for (const auto &nodeInst : pNode->mNodeInstances) { + // find the corresponding node in the library + const ColladaParser::NodeLibrary::const_iterator itt = pParser.mNodeLibrary.find(nodeInst.mNode); + const Node *nd = itt == pParser.mNodeLibrary.end() ? nullptr : (*itt).second; + + // FIX for http://sourceforge.net/tracker/?func=detail&aid=3054873&group_id=226462&atid=1067632 + // need to check for both name and ID to catch all. To avoid breaking valid files, + // the workaround is only enabled when the first attempt to resolve the node has failed. + if (nullptr == nd) { + nd = FindNode(pParser.mRootNode, nodeInst.mNode); + } + if (nullptr == nd) { + ASSIMP_LOG_ERROR("Collada: Unable to resolve reference to instanced node ", nodeInst.mNode); + } else { + // attach this node to the list of children + resolved.push_back(nd); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Resolve UV channels +void ColladaLoader::ApplyVertexToEffectSemanticMapping(Sampler &sampler, const SemanticMappingTable &table) { + SemanticMappingTable::InputSemanticMap::const_iterator it = table.mMap.find(sampler.mUVChannel); + if (it == table.mMap.end()) { + return; + } + + if (it->second.mType != IT_Texcoord) { + ASSIMP_LOG_ERROR("Collada: Unexpected effect input mapping"); + } + + sampler.mUVId = it->second.mSet; +} + +// ------------------------------------------------------------------------------------------------ +// Builds lights for the given node and references them +void ColladaLoader::BuildLightsForNode(const ColladaParser &pParser, const Node *pNode, aiNode *pTarget) { + for (const LightInstance &lid : pNode->mLights) { + // find the referred light + ColladaParser::LightLibrary::const_iterator srcLightIt = pParser.mLightLibrary.find(lid.mLight); + if (srcLightIt == pParser.mLightLibrary.end()) { + ASSIMP_LOG_WARN("Collada: Unable to find light for ID \"", lid.mLight, "\". Skipping."); + continue; + } + const Collada::Light *srcLight = &srcLightIt->second; + + // now fill our ai data structure + aiLight *out = new aiLight(); + out->mName = pTarget->mName; + out->mType = (aiLightSourceType)srcLight->mType; + + // collada lights point in -Z by default, rest is specified in node transform + out->mDirection = aiVector3D(0.f, 0.f, -1.f); + + out->mAttenuationConstant = srcLight->mAttConstant; + out->mAttenuationLinear = srcLight->mAttLinear; + out->mAttenuationQuadratic = srcLight->mAttQuadratic; + + out->mColorDiffuse = out->mColorSpecular = out->mColorAmbient = srcLight->mColor * srcLight->mIntensity; + if (out->mType == aiLightSource_AMBIENT) { + out->mColorDiffuse = out->mColorSpecular = aiColor3D(0, 0, 0); + out->mColorAmbient = srcLight->mColor * srcLight->mIntensity; + } else { + // collada doesn't differentiate between these color types + out->mColorDiffuse = out->mColorSpecular = srcLight->mColor * srcLight->mIntensity; + out->mColorAmbient = aiColor3D(0, 0, 0); + } + + // convert falloff angle and falloff exponent in our representation, if given + if (out->mType == aiLightSource_SPOT) { + out->mAngleInnerCone = AI_DEG_TO_RAD(srcLight->mFalloffAngle); + + // ... some extension magic. + if (srcLight->mOuterAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET * (1 - ai_epsilon)) { + // ... some deprecation magic. + if (srcLight->mPenumbraAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET * (1 - ai_epsilon)) { + // Need to rely on falloff_exponent. I don't know how to interpret it, so I need to guess .... + // epsilon chosen to be 0.1 + float f = 1.0f; + if ( 0.0f != srcLight->mFalloffExponent ) { + f = 1.f / srcLight->mFalloffExponent; + } + out->mAngleOuterCone = std::acos(std::pow(0.1f, f)) + + out->mAngleInnerCone; + } else { + out->mAngleOuterCone = out->mAngleInnerCone + AI_DEG_TO_RAD(srcLight->mPenumbraAngle); + if (out->mAngleOuterCone < out->mAngleInnerCone) + std::swap(out->mAngleInnerCone, out->mAngleOuterCone); + } + } else { + out->mAngleOuterCone = AI_DEG_TO_RAD(srcLight->mOuterAngle); + } + } + + // add to light list + mLights.push_back(out); + } +} + +// ------------------------------------------------------------------------------------------------ +// Builds cameras for the given node and references them +void ColladaLoader::BuildCamerasForNode(const ColladaParser &pParser, const Node *pNode, aiNode *pTarget) { + for (const CameraInstance &cid : pNode->mCameras) { + // find the referred light + ColladaParser::CameraLibrary::const_iterator srcCameraIt = pParser.mCameraLibrary.find(cid.mCamera); + if (srcCameraIt == pParser.mCameraLibrary.end()) { + ASSIMP_LOG_WARN("Collada: Unable to find camera for ID \"", cid.mCamera, "\". Skipping."); + continue; + } + const Collada::Camera *srcCamera = &srcCameraIt->second; + + // orthographic cameras not yet supported in Assimp + if (srcCamera->mOrtho) { + ASSIMP_LOG_WARN("Collada: Orthographic cameras are not supported."); + } + + // now fill our ai data structure + aiCamera *out = new aiCamera(); + out->mName = pTarget->mName; + + // collada cameras point in -Z by default, rest is specified in node transform + out->mLookAt = aiVector3D(0.f, 0.f, -1.f); + + // near/far z is already ok + out->mClipPlaneFar = srcCamera->mZFar; + out->mClipPlaneNear = srcCamera->mZNear; + + // ... but for the rest some values are optional + // and we need to compute the others in any combination. + if (srcCamera->mAspect != 10e10f) { + out->mAspect = srcCamera->mAspect; + } + + if (srcCamera->mHorFov != 10e10f) { + out->mHorizontalFOV = srcCamera->mHorFov; + + if (srcCamera->mVerFov != 10e10f && srcCamera->mAspect == 10e10f) { + out->mAspect = std::tan(AI_DEG_TO_RAD(srcCamera->mHorFov)) / + std::tan(AI_DEG_TO_RAD(srcCamera->mVerFov)); + } + + } else if (srcCamera->mAspect != 10e10f && srcCamera->mVerFov != 10e10f) { + out->mHorizontalFOV = 2.0f * AI_RAD_TO_DEG(std::atan(srcCamera->mAspect * + std::tan(AI_DEG_TO_RAD(srcCamera->mVerFov) * 0.5f))); + } + + // Collada uses degrees, we use radians + out->mHorizontalFOV = AI_DEG_TO_RAD(out->mHorizontalFOV); + + // add to camera list + mCameras.push_back(out); + } +} + +// ------------------------------------------------------------------------------------------------ +// Builds meshes for the given node and references them +void ColladaLoader::BuildMeshesForNode(const ColladaParser &pParser, const Node *pNode, aiNode *pTarget) { + // accumulated mesh references by this node + std::vector<size_t> newMeshRefs; + newMeshRefs.reserve(pNode->mMeshes.size()); + + // add a mesh for each subgroup in each collada mesh + for (const MeshInstance &mid : pNode->mMeshes) { + const Mesh *srcMesh = nullptr; + const Controller *srcController = nullptr; + + // find the referred mesh + ColladaParser::MeshLibrary::const_iterator srcMeshIt = pParser.mMeshLibrary.find(mid.mMeshOrController); + if (srcMeshIt == pParser.mMeshLibrary.end()) { + // if not found in the mesh-library, it might also be a controller referring to a mesh + ColladaParser::ControllerLibrary::const_iterator srcContrIt = pParser.mControllerLibrary.find(mid.mMeshOrController); + if (srcContrIt != pParser.mControllerLibrary.end()) { + srcController = &srcContrIt->second; + srcMeshIt = pParser.mMeshLibrary.find(srcController->mMeshId); + if (srcMeshIt != pParser.mMeshLibrary.end()) { + srcMesh = srcMeshIt->second; + } + } + + if (nullptr == srcMesh) { + ASSIMP_LOG_WARN("Collada: Unable to find geometry for ID \"", mid.mMeshOrController, "\". Skipping."); + continue; + } + } else { + // ID found in the mesh library -> direct reference to an unskinned mesh + srcMesh = srcMeshIt->second; + } + + // build a mesh for each of its subgroups + size_t vertexStart = 0, faceStart = 0; + for (size_t sm = 0; sm < srcMesh->mSubMeshes.size(); ++sm) { + const Collada::SubMesh &submesh = srcMesh->mSubMeshes[sm]; + if (submesh.mNumFaces == 0) { + continue; + } + + // find material assigned to this submesh + std::string meshMaterial; + std::map<std::string, SemanticMappingTable>::const_iterator meshMatIt = mid.mMaterials.find(submesh.mMaterial); + + const Collada::SemanticMappingTable *table = nullptr; + if (meshMatIt != mid.mMaterials.end()) { + table = &meshMatIt->second; + meshMaterial = table->mMatName; + } else { + ASSIMP_LOG_WARN("Collada: No material specified for subgroup <", submesh.mMaterial, "> in geometry <", + mid.mMeshOrController, ">."); + if (!mid.mMaterials.empty()) { + meshMaterial = mid.mMaterials.begin()->second.mMatName; + } + } + + // OK ... here the *real* fun starts ... we have the vertex-input-to-effect-semantic-table + // given. The only mapping stuff which we do actually support is the UV channel. + std::map<std::string, size_t>::const_iterator matIt = mMaterialIndexByName.find(meshMaterial); + unsigned int matIdx = 0; + if (matIt != mMaterialIndexByName.end()) { + matIdx = static_cast<unsigned int>(matIt->second); + } + + if (table && !table->mMap.empty()) { + std::pair<Collada::Effect *, aiMaterial *> &mat = newMats[matIdx]; + + // Iterate through all texture channels assigned to the effect and + // check whether we have mapping information for it. + ApplyVertexToEffectSemanticMapping(mat.first->mTexDiffuse, *table); + ApplyVertexToEffectSemanticMapping(mat.first->mTexAmbient, *table); + ApplyVertexToEffectSemanticMapping(mat.first->mTexSpecular, *table); + ApplyVertexToEffectSemanticMapping(mat.first->mTexEmissive, *table); + ApplyVertexToEffectSemanticMapping(mat.first->mTexTransparent, *table); + ApplyVertexToEffectSemanticMapping(mat.first->mTexBump, *table); + } + + // built lookup index of the Mesh-Submesh-Material combination + ColladaMeshIndex index(mid.mMeshOrController, sm, meshMaterial); + + // if we already have the mesh at the library, just add its index to the node's array + std::map<ColladaMeshIndex, size_t>::const_iterator dstMeshIt = mMeshIndexByID.find(index); + if (dstMeshIt != mMeshIndexByID.end()) { + newMeshRefs.push_back(dstMeshIt->second); + } else { + // else we have to add the mesh to the collection and store its newly assigned index at the node + aiMesh *dstMesh = CreateMesh(pParser, srcMesh, submesh, srcController, vertexStart, faceStart); + + // store the mesh, and store its new index in the node + newMeshRefs.push_back(mMeshes.size()); + mMeshIndexByID[index] = mMeshes.size(); + mMeshes.push_back(dstMesh); + vertexStart += dstMesh->mNumVertices; + faceStart += submesh.mNumFaces; + + // assign the material index + std::map<std::string, size_t>::const_iterator subMatIt = mMaterialIndexByName.find(submesh.mMaterial); + if (subMatIt != mMaterialIndexByName.end()) { + dstMesh->mMaterialIndex = static_cast<unsigned int>(subMatIt->second); + } else { + dstMesh->mMaterialIndex = matIdx; + } + if (dstMesh->mName.length == 0) { + dstMesh->mName = mid.mMeshOrController; + } + } + } + } + + // now place all mesh references we gathered in the target node + pTarget->mNumMeshes = static_cast<unsigned int>(newMeshRefs.size()); + if (!newMeshRefs.empty()) { + struct UIntTypeConverter { + unsigned int operator()(const size_t &v) const { + return static_cast<unsigned int>(v); + } + }; + + pTarget->mMeshes = new unsigned int[pTarget->mNumMeshes]; + std::transform(newMeshRefs.begin(), newMeshRefs.end(), pTarget->mMeshes, UIntTypeConverter()); + } +} + +// ------------------------------------------------------------------------------------------------ +// Find mesh from either meshes or morph target meshes +aiMesh *ColladaLoader::findMesh(const std::string &meshid) { + if (meshid.empty()) { + return nullptr; + } + + for (auto & mMeshe : mMeshes) { + if (std::string(mMeshe->mName.data) == meshid) { + return mMeshe; + } + } + + for (auto & mTargetMeshe : mTargetMeshes) { + if (std::string(mTargetMeshe->mName.data) == meshid) { + return mTargetMeshe; + } + } + + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh +aiMesh *ColladaLoader::CreateMesh(const ColladaParser &pParser, const Mesh *pSrcMesh, const SubMesh &pSubMesh, + const Controller *pSrcController, size_t pStartVertex, size_t pStartFace) { + std::unique_ptr<aiMesh> dstMesh(new aiMesh); + + if (useColladaName) { + dstMesh->mName = pSrcMesh->mName; + } else { + dstMesh->mName = pSrcMesh->mId; + } + + if (pSrcMesh->mPositions.empty()) { + return dstMesh.release(); + } + + // count the vertices addressed by its faces + const size_t numVertices = std::accumulate(pSrcMesh->mFaceSize.begin() + pStartFace, + pSrcMesh->mFaceSize.begin() + pStartFace + pSubMesh.mNumFaces, size_t(0)); + + // copy positions + dstMesh->mNumVertices = static_cast<unsigned int>(numVertices); + dstMesh->mVertices = new aiVector3D[numVertices]; + std::copy(pSrcMesh->mPositions.begin() + pStartVertex, pSrcMesh->mPositions.begin() + pStartVertex + numVertices, dstMesh->mVertices); + + // normals, if given. HACK: (thom) Due to the glorious Collada spec we never + // know if we have the same number of normals as there are positions. So we + // also ignore any vertex attribute if it has a different count + if (pSrcMesh->mNormals.size() >= pStartVertex + numVertices) { + dstMesh->mNormals = new aiVector3D[numVertices]; + std::copy(pSrcMesh->mNormals.begin() + pStartVertex, pSrcMesh->mNormals.begin() + pStartVertex + numVertices, dstMesh->mNormals); + } + + // tangents, if given. + if (pSrcMesh->mTangents.size() >= pStartVertex + numVertices) { + dstMesh->mTangents = new aiVector3D[numVertices]; + std::copy(pSrcMesh->mTangents.begin() + pStartVertex, pSrcMesh->mTangents.begin() + pStartVertex + numVertices, dstMesh->mTangents); + } + + // bitangents, if given. + if (pSrcMesh->mBitangents.size() >= pStartVertex + numVertices) { + dstMesh->mBitangents = new aiVector3D[numVertices]; + std::copy(pSrcMesh->mBitangents.begin() + pStartVertex, pSrcMesh->mBitangents.begin() + pStartVertex + numVertices, dstMesh->mBitangents); + } + + // same for texture coords, as many as we have + // empty slots are not allowed, need to pack and adjust UV indexes accordingly + for (size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { + if (pSrcMesh->mTexCoords[a].size() >= pStartVertex + numVertices) { + dstMesh->mTextureCoords[real] = new aiVector3D[numVertices]; + for (size_t b = 0; b < numVertices; ++b) { + dstMesh->mTextureCoords[real][b] = pSrcMesh->mTexCoords[a][pStartVertex + b]; + } + + dstMesh->mNumUVComponents[real] = pSrcMesh->mNumUVComponents[a]; + ++real; + } + } + + // same for vertex colors, as many as we have. again the same packing to avoid empty slots + for (size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) { + if (pSrcMesh->mColors[a].size() >= pStartVertex + numVertices) { + dstMesh->mColors[real] = new aiColor4D[numVertices]; + std::copy(pSrcMesh->mColors[a].begin() + pStartVertex, pSrcMesh->mColors[a].begin() + pStartVertex + numVertices, dstMesh->mColors[real]); + ++real; + } + } + + // create faces. Due to the fact that each face uses unique vertices, we can simply count up on each vertex + size_t vertex = 0; + dstMesh->mNumFaces = static_cast<unsigned int>(pSubMesh.mNumFaces); + dstMesh->mFaces = new aiFace[dstMesh->mNumFaces]; + for (size_t a = 0; a < dstMesh->mNumFaces; ++a) { + size_t s = pSrcMesh->mFaceSize[pStartFace + a]; + aiFace &face = dstMesh->mFaces[a]; + face.mNumIndices = static_cast<unsigned int>(s); + face.mIndices = new unsigned int[s]; + for (size_t b = 0; b < s; ++b) { + face.mIndices[b] = static_cast<unsigned int>(vertex++); + } + } + + // create morph target meshes if any + std::vector<aiMesh *> targetMeshes; + std::vector<float> targetWeights; + Collada::MorphMethod method = Normalized; + + for (std::map<std::string, Controller>::const_iterator it = pParser.mControllerLibrary.begin(); + it != pParser.mControllerLibrary.end(); ++it) { + const Controller &c = it->second; + const Collada::Mesh *baseMesh = pParser.ResolveLibraryReference(pParser.mMeshLibrary, c.mMeshId); + + if (c.mType == Collada::Morph && baseMesh->mName == pSrcMesh->mName) { + const Collada::Accessor &targetAccessor = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, c.mMorphTarget); + const Collada::Accessor &weightAccessor = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, c.mMorphWeight); + const Collada::Data &targetData = pParser.ResolveLibraryReference(pParser.mDataLibrary, targetAccessor.mSource); + const Collada::Data &weightData = pParser.ResolveLibraryReference(pParser.mDataLibrary, weightAccessor.mSource); + + // take method + method = c.mMethod; + + if (!targetData.mIsStringArray) { + throw DeadlyImportError("target data must contain id. "); + } + if (weightData.mIsStringArray) { + throw DeadlyImportError("target weight data must not be textual "); + } + + for (const auto & mString : targetData.mStrings) { + const Mesh *targetMesh = pParser.ResolveLibraryReference(pParser.mMeshLibrary, mString); + + aiMesh *aimesh = findMesh(useColladaName ? targetMesh->mName : targetMesh->mId); + if (!aimesh) { + if (targetMesh->mSubMeshes.size() > 1) { + throw DeadlyImportError("Morphing target mesh must be a single"); + } + aimesh = CreateMesh(pParser, targetMesh, targetMesh->mSubMeshes.at(0), nullptr, 0, 0); + mTargetMeshes.push_back(aimesh); + } + targetMeshes.push_back(aimesh); + } + for (float mValue : weightData.mValues) { + targetWeights.push_back(mValue); + } + } + } + if (!targetMeshes.empty() && targetWeights.size() == targetMeshes.size()) { + std::vector<aiAnimMesh *> animMeshes; + for (unsigned int i = 0; i < targetMeshes.size(); ++i) { + aiMesh *targetMesh = targetMeshes.at(i); + aiAnimMesh *animMesh = aiCreateAnimMesh(targetMesh); + float weight = targetWeights[i]; + animMesh->mWeight = weight == 0 ? 1.0f : weight; + animMesh->mName = targetMesh->mName; + animMeshes.push_back(animMesh); + } + dstMesh->mMethod = (method == Relative) ? aiMorphingMethod_MORPH_RELATIVE : aiMorphingMethod_MORPH_NORMALIZED; + dstMesh->mAnimMeshes = new aiAnimMesh *[animMeshes.size()]; + dstMesh->mNumAnimMeshes = static_cast<unsigned int>(animMeshes.size()); + for (unsigned int i = 0; i < animMeshes.size(); ++i) { + dstMesh->mAnimMeshes[i] = animMeshes.at(i); + } + } + + // create bones if given + if (pSrcController && pSrcController->mType == Collada::Skin) { + // resolve references - joint names + const Collada::Accessor &jointNamesAcc = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, pSrcController->mJointNameSource); + const Collada::Data &jointNames = pParser.ResolveLibraryReference(pParser.mDataLibrary, jointNamesAcc.mSource); + // joint offset matrices + const Collada::Accessor &jointMatrixAcc = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, pSrcController->mJointOffsetMatrixSource); + const Collada::Data &jointMatrices = pParser.ResolveLibraryReference(pParser.mDataLibrary, jointMatrixAcc.mSource); + // joint vertex_weight name list - should refer to the same list as the joint names above. If not, report and reconsider + const Collada::Accessor &weightNamesAcc = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, pSrcController->mWeightInputJoints.mAccessor); + if (&weightNamesAcc != &jointNamesAcc) + throw DeadlyImportError("Temporary implementational laziness. If you read this, please report to the author."); + // vertex weights + const Collada::Accessor &weightsAcc = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, pSrcController->mWeightInputWeights.mAccessor); + const Collada::Data &weights = pParser.ResolveLibraryReference(pParser.mDataLibrary, weightsAcc.mSource); + + if (!jointNames.mIsStringArray || jointMatrices.mIsStringArray || weights.mIsStringArray) { + throw DeadlyImportError("Data type mismatch while resolving mesh joints"); + } + // sanity check: we rely on the vertex weights always coming as pairs of BoneIndex-WeightIndex + if (pSrcController->mWeightInputJoints.mOffset != 0 || pSrcController->mWeightInputWeights.mOffset != 1) { + throw DeadlyImportError("Unsupported vertex_weight addressing scheme. "); + } + + // create containers to collect the weights for each bone + size_t numBones = jointNames.mStrings.size(); + std::vector<std::vector<aiVertexWeight>> dstBones(numBones); + + // build a temporary array of pointers to the start of each vertex's weights + using IndexPairVector = std::vector<std::pair<size_t, size_t>>; + std::vector<IndexPairVector::const_iterator> weightStartPerVertex; + weightStartPerVertex.resize(pSrcController->mWeightCounts.size(), pSrcController->mWeights.end()); + + IndexPairVector::const_iterator pit = pSrcController->mWeights.begin(); + for (size_t a = 0; a < pSrcController->mWeightCounts.size(); ++a) { + weightStartPerVertex[a] = pit; + pit += pSrcController->mWeightCounts[a]; + } + + // now for each vertex put the corresponding vertex weights into each bone's weight collection + for (size_t a = pStartVertex; a < pStartVertex + numVertices; ++a) { + // which position index was responsible for this vertex? that's also the index by which + // the controller assigns the vertex weights + size_t orgIndex = pSrcMesh->mFacePosIndices[a]; + // find the vertex weights for this vertex + IndexPairVector::const_iterator iit = weightStartPerVertex[orgIndex]; + size_t pairCount = pSrcController->mWeightCounts[orgIndex]; + + for (size_t b = 0; b < pairCount; ++b, ++iit) { + const size_t jointIndex = iit->first; + const size_t vertexIndex = iit->second; + ai_real weight = 1.0f; + if (!weights.mValues.empty()) { + weight = ReadFloat(weightsAcc, weights, vertexIndex, 0); + } + + // one day I gonna kill that XSI Collada exporter + if (weight > 0.0f) { + aiVertexWeight w; + w.mVertexId = static_cast<unsigned int>(a - pStartVertex); + w.mWeight = weight; + dstBones[jointIndex].push_back(w); + } + } + } + + // count the number of bones which influence vertices of the current submesh + size_t numRemainingBones = 0; + for (const auto & dstBone : dstBones) { + if (!dstBone.empty()) { + ++numRemainingBones; + } + } + + // create bone array and copy bone weights one by one + dstMesh->mNumBones = static_cast<unsigned int>(numRemainingBones); + dstMesh->mBones = new aiBone *[numRemainingBones]; + size_t boneCount = 0; + for (size_t a = 0; a < numBones; ++a) { + // omit bones without weights + if (dstBones[a].empty()) { + continue; + } + + // create bone with its weights + aiBone *bone = new aiBone; + bone->mName = ReadString(jointNamesAcc, jointNames, a); + bone->mOffsetMatrix.a1 = ReadFloat(jointMatrixAcc, jointMatrices, a, 0); + bone->mOffsetMatrix.a2 = ReadFloat(jointMatrixAcc, jointMatrices, a, 1); + bone->mOffsetMatrix.a3 = ReadFloat(jointMatrixAcc, jointMatrices, a, 2); + bone->mOffsetMatrix.a4 = ReadFloat(jointMatrixAcc, jointMatrices, a, 3); + bone->mOffsetMatrix.b1 = ReadFloat(jointMatrixAcc, jointMatrices, a, 4); + bone->mOffsetMatrix.b2 = ReadFloat(jointMatrixAcc, jointMatrices, a, 5); + bone->mOffsetMatrix.b3 = ReadFloat(jointMatrixAcc, jointMatrices, a, 6); + bone->mOffsetMatrix.b4 = ReadFloat(jointMatrixAcc, jointMatrices, a, 7); + bone->mOffsetMatrix.c1 = ReadFloat(jointMatrixAcc, jointMatrices, a, 8); + bone->mOffsetMatrix.c2 = ReadFloat(jointMatrixAcc, jointMatrices, a, 9); + bone->mOffsetMatrix.c3 = ReadFloat(jointMatrixAcc, jointMatrices, a, 10); + bone->mOffsetMatrix.c4 = ReadFloat(jointMatrixAcc, jointMatrices, a, 11); + bone->mNumWeights = static_cast<unsigned int>(dstBones[a].size()); + bone->mWeights = new aiVertexWeight[bone->mNumWeights]; + std::copy(dstBones[a].begin(), dstBones[a].end(), bone->mWeights); + + // apply bind shape matrix to offset matrix + aiMatrix4x4 bindShapeMatrix; + bindShapeMatrix.a1 = pSrcController->mBindShapeMatrix[0]; + bindShapeMatrix.a2 = pSrcController->mBindShapeMatrix[1]; + bindShapeMatrix.a3 = pSrcController->mBindShapeMatrix[2]; + bindShapeMatrix.a4 = pSrcController->mBindShapeMatrix[3]; + bindShapeMatrix.b1 = pSrcController->mBindShapeMatrix[4]; + bindShapeMatrix.b2 = pSrcController->mBindShapeMatrix[5]; + bindShapeMatrix.b3 = pSrcController->mBindShapeMatrix[6]; + bindShapeMatrix.b4 = pSrcController->mBindShapeMatrix[7]; + bindShapeMatrix.c1 = pSrcController->mBindShapeMatrix[8]; + bindShapeMatrix.c2 = pSrcController->mBindShapeMatrix[9]; + bindShapeMatrix.c3 = pSrcController->mBindShapeMatrix[10]; + bindShapeMatrix.c4 = pSrcController->mBindShapeMatrix[11]; + bindShapeMatrix.d1 = pSrcController->mBindShapeMatrix[12]; + bindShapeMatrix.d2 = pSrcController->mBindShapeMatrix[13]; + bindShapeMatrix.d3 = pSrcController->mBindShapeMatrix[14]; + bindShapeMatrix.d4 = pSrcController->mBindShapeMatrix[15]; + bone->mOffsetMatrix *= bindShapeMatrix; + + // HACK: (thom) Some exporters address the bone nodes by SID, others address them by ID or even name. + // Therefore I added a little name replacement here: I search for the bone's node by either name, ID or SID, + // and replace the bone's name by the node's name so that the user can use the standard + // find-by-name method to associate nodes with bones. + const Collada::Node *bnode = FindNode(pParser.mRootNode, bone->mName.data); + if (nullptr == bnode) { + bnode = FindNodeBySID(pParser.mRootNode, bone->mName.data); + } + + // assign the name that we would have assigned for the source node + if (nullptr != bnode) { + bone->mName.Set(FindNameForNode(bnode)); + } else { + ASSIMP_LOG_WARN("ColladaLoader::CreateMesh(): could not find corresponding node for joint \"", bone->mName.data, "\"."); + } + + // and insert bone + dstMesh->mBones[boneCount++] = bone; + } + } + + return dstMesh.release(); +} + +// ------------------------------------------------------------------------------------------------ +// Stores all meshes in the given scene +void ColladaLoader::StoreSceneMeshes(aiScene *pScene) { + pScene->mNumMeshes = static_cast<unsigned int>(mMeshes.size()); + if (mMeshes.empty()) { + return; + } + pScene->mMeshes = new aiMesh *[mMeshes.size()]; + std::copy(mMeshes.begin(), mMeshes.end(), pScene->mMeshes); + mMeshes.clear(); +} + +// ------------------------------------------------------------------------------------------------ +// Stores all cameras in the given scene +void ColladaLoader::StoreSceneCameras(aiScene *pScene) { + pScene->mNumCameras = static_cast<unsigned int>(mCameras.size()); + if (mCameras.empty()) { + return; + } + pScene->mCameras = new aiCamera *[mCameras.size()]; + std::copy(mCameras.begin(), mCameras.end(), pScene->mCameras); + mCameras.clear(); +} + +// ------------------------------------------------------------------------------------------------ +// Stores all lights in the given scene +void ColladaLoader::StoreSceneLights(aiScene *pScene) { + pScene->mNumLights = static_cast<unsigned int>(mLights.size()); + if (mLights.empty()) { + return; + } + pScene->mLights = new aiLight *[mLights.size()]; + std::copy(mLights.begin(), mLights.end(), pScene->mLights); + mLights.clear(); +} + +// ------------------------------------------------------------------------------------------------ +// Stores all textures in the given scene +void ColladaLoader::StoreSceneTextures(aiScene *pScene) { + pScene->mNumTextures = static_cast<unsigned int>(mTextures.size()); + if (mTextures.empty()) { + return; + } + pScene->mTextures = new aiTexture *[mTextures.size()]; + std::copy(mTextures.begin(), mTextures.end(), pScene->mTextures); + mTextures.clear(); +} + +// ------------------------------------------------------------------------------------------------ +// Stores all materials in the given scene +void ColladaLoader::StoreSceneMaterials(aiScene *pScene) { + pScene->mNumMaterials = static_cast<unsigned int>(newMats.size()); + if (newMats.empty()) { + return; + } + pScene->mMaterials = new aiMaterial *[newMats.size()]; + for (unsigned int i = 0; i < newMats.size(); ++i) { + pScene->mMaterials[i] = newMats[i].second; + } + newMats.clear(); +} + +// ------------------------------------------------------------------------------------------------ +// Stores all animations +void ColladaLoader::StoreAnimations(aiScene *pScene, const ColladaParser &pParser) { + // recursively collect all animations from the collada scene + StoreAnimations(pScene, pParser, &pParser.mAnims, ""); + + // catch special case: many animations with the same length, each affecting only a single node. + // we need to unite all those single-node-anims to a proper combined animation + for (size_t a = 0; a < mAnims.size(); ++a) { + aiAnimation *templateAnim = mAnims[a]; + + if (templateAnim->mNumChannels == 1) { + // search for other single-channel-anims with the same duration + std::vector<size_t> collectedAnimIndices; + for (size_t b = a + 1; b < mAnims.size(); ++b) { + aiAnimation *other = mAnims[b]; + if (other->mNumChannels == 1 && other->mDuration == templateAnim->mDuration && + other->mTicksPerSecond == templateAnim->mTicksPerSecond) + collectedAnimIndices.push_back(b); + } + + // We only want to combine the animations if they have different channels + std::set<std::string> animTargets; + animTargets.insert(templateAnim->mChannels[0]->mNodeName.C_Str()); + bool collectedAnimationsHaveDifferentChannels = true; + for (unsigned long long collectedAnimIndice : collectedAnimIndices) { + aiAnimation *srcAnimation = mAnims[(int)collectedAnimIndice]; + std::string channelName = std::string(srcAnimation->mChannels[0]->mNodeName.C_Str()); + if (animTargets.find(channelName) == animTargets.end()) { + animTargets.insert(channelName); + } else { + collectedAnimationsHaveDifferentChannels = false; + break; + } + } + + if (!collectedAnimationsHaveDifferentChannels) { + continue; + } + + // if there are other animations which fit the template anim, combine all channels into a single anim + if (!collectedAnimIndices.empty()) { + aiAnimation *combinedAnim = new aiAnimation(); + combinedAnim->mName = aiString(std::string("combinedAnim_") + char('0' + a)); + combinedAnim->mDuration = templateAnim->mDuration; + combinedAnim->mTicksPerSecond = templateAnim->mTicksPerSecond; + combinedAnim->mNumChannels = static_cast<unsigned int>(collectedAnimIndices.size() + 1); + combinedAnim->mChannels = new aiNodeAnim *[combinedAnim->mNumChannels]; + // add the template anim as first channel by moving its aiNodeAnim to the combined animation + combinedAnim->mChannels[0] = templateAnim->mChannels[0]; + templateAnim->mChannels[0] = nullptr; + delete templateAnim; + // combined animation replaces template animation in the anim array + mAnims[a] = combinedAnim; + + // move the memory of all other anims to the combined anim and erase them from the source anims + for (size_t b = 0; b < collectedAnimIndices.size(); ++b) { + aiAnimation *srcAnimation = mAnims[collectedAnimIndices[b]]; + combinedAnim->mChannels[1 + b] = srcAnimation->mChannels[0]; + srcAnimation->mChannels[0] = nullptr; + delete srcAnimation; + } + + // in a second go, delete all the single-channel-anims that we've stripped from their channels + // back to front to preserve indices - you know, removing an element from a vector moves all elements behind the removed one + while (!collectedAnimIndices.empty()) { + mAnims.erase(mAnims.begin() + collectedAnimIndices.back()); + collectedAnimIndices.pop_back(); + } + } + } + } + + // now store all anims in the scene + if (!mAnims.empty()) { + pScene->mNumAnimations = static_cast<unsigned int>(mAnims.size()); + pScene->mAnimations = new aiAnimation *[mAnims.size()]; + std::copy(mAnims.begin(), mAnims.end(), pScene->mAnimations); + } + + mAnims.clear(); +} + +// ------------------------------------------------------------------------------------------------ +// Constructs the animations for the given source anim +void ColladaLoader::StoreAnimations(aiScene *pScene, const ColladaParser &pParser, const Animation *pSrcAnim, const std::string &pPrefix) { + std::string animName = pPrefix.empty() ? pSrcAnim->mName : pPrefix + "_" + pSrcAnim->mName; + + // create nested animations, if given + for (auto mSubAnim : pSrcAnim->mSubAnims) { + StoreAnimations(pScene, pParser, mSubAnim, animName); + } + + // create animation channels, if any + if (!pSrcAnim->mChannels.empty()) { + CreateAnimation(pScene, pParser, pSrcAnim, animName); + } +} + +struct MorphTimeValues { + float mTime; + struct key { + float mWeight; + unsigned int mValue; + }; + std::vector<key> mKeys; +}; + +void insertMorphTimeValue(std::vector<MorphTimeValues> &values, float time, float weight, unsigned int value) { + MorphTimeValues::key k; + k.mValue = value; + k.mWeight = weight; + if (values.empty() || time < values[0].mTime) { + MorphTimeValues val; + val.mTime = time; + val.mKeys.push_back(k); + values.insert(values.begin(), val); + return; + } + if (time > values.back().mTime) { + MorphTimeValues val; + val.mTime = time; + val.mKeys.push_back(k); + values.insert(values.end(), val); + return; + } + for (unsigned int i = 0; i < values.size(); i++) { + if (std::abs(time - values[i].mTime) < ai_epsilon) { + values[i].mKeys.push_back(k); + return; + } else if (time > values[i].mTime && time < values[i + 1].mTime) { + MorphTimeValues val; + val.mTime = time; + val.mKeys.push_back(k); + values.insert(values.begin() + i, val); + return; + } + } +} + +static float getWeightAtKey(const std::vector<MorphTimeValues> &values, int key, unsigned int value) { + for (auto mKey : values[key].mKeys) { + if (mKey.mValue == value) { + return mKey.mWeight; + } + } + // no value at key found, try to interpolate if present at other keys. if not, return zero + // TODO: interpolation + return 0.0f; +} + +// ------------------------------------------------------------------------------------------------ +// Constructs the animation for the given source anim +void ColladaLoader::CreateAnimation(aiScene *pScene, const ColladaParser &pParser, const Animation *pSrcAnim, const std::string &pName) { + // collect a list of animatable nodes + std::vector<const aiNode *> nodes; + CollectNodes(pScene->mRootNode, nodes); + + std::vector<aiNodeAnim *> anims; + std::vector<aiMeshMorphAnim *> morphAnims; + + for (auto node : nodes) { + // find all the collada anim channels which refer to the current node + std::vector<ChannelEntry> entries; + std::string nodeName = node->mName.data; + + // find the collada node corresponding to the aiNode + const Node *srcNode = FindNode(pParser.mRootNode, nodeName); + if (!srcNode) { + continue; + } + + // now check all channels if they affect the current node + std::string targetID, subElement; + for (std::vector<AnimationChannel>::const_iterator cit = pSrcAnim->mChannels.begin(); + cit != pSrcAnim->mChannels.end(); ++cit) { + const AnimationChannel &srcChannel = *cit; + ChannelEntry entry; + + // we expect the animation target to be of type "nodeName/transformID.subElement". Ignore all others + // find the slash that separates the node name - there should be only one + std::string::size_type slashPos = srcChannel.mTarget.find('/'); + if (slashPos == std::string::npos) { + std::string::size_type targetPos = srcChannel.mTarget.find(srcNode->mID); + if (targetPos == std::string::npos) { + continue; + } + + // not node transform, but something else. store as unknown animation channel for now + entry.mChannel = &(*cit); + entry.mTargetId = srcChannel.mTarget.substr(targetPos + pSrcAnim->mName.length(), + srcChannel.mTarget.length() - targetPos - pSrcAnim->mName.length()); + if (entry.mTargetId.front() == '-') { + entry.mTargetId = entry.mTargetId.substr(1); + } + entries.push_back(entry); + continue; + } + if (srcChannel.mTarget.find('/', slashPos + 1) != std::string::npos) { + continue; + } + + targetID.clear(); + targetID = srcChannel.mTarget.substr(0, slashPos); + if (targetID != srcNode->mID) { + continue; + } + + // find the dot that separates the transformID - there should be only one or zero + std::string::size_type dotPos = srcChannel.mTarget.find('.'); + if (dotPos != std::string::npos) { + if (srcChannel.mTarget.find('.', dotPos + 1) != std::string::npos) { + continue; + } + + entry.mTransformId = srcChannel.mTarget.substr(slashPos + 1, dotPos - slashPos - 1); + + subElement.clear(); + subElement = srcChannel.mTarget.substr(dotPos + 1); + if (subElement == "ANGLE") + entry.mSubElement = 3; // last number in an Axis-Angle-Transform is the angle + else if (subElement == "X") + entry.mSubElement = 0; + else if (subElement == "Y") + entry.mSubElement = 1; + else if (subElement == "Z") + entry.mSubElement = 2; + else + ASSIMP_LOG_WARN("Unknown anim subelement <", subElement, ">. Ignoring"); + } else { + // no sub-element following, transformId is remaining string + entry.mTransformId = srcChannel.mTarget.substr(slashPos + 1); + } + + std::string::size_type bracketPos = srcChannel.mTarget.find('('); + if (bracketPos != std::string::npos) { + entry.mTransformId = srcChannel.mTarget.substr(slashPos + 1, bracketPos - slashPos - 1); + subElement.clear(); + subElement = srcChannel.mTarget.substr(bracketPos); + + if (subElement == "(0)(0)") + entry.mSubElement = 0; + else if (subElement == "(1)(0)") + entry.mSubElement = 1; + else if (subElement == "(2)(0)") + entry.mSubElement = 2; + else if (subElement == "(3)(0)") + entry.mSubElement = 3; + else if (subElement == "(0)(1)") + entry.mSubElement = 4; + else if (subElement == "(1)(1)") + entry.mSubElement = 5; + else if (subElement == "(2)(1)") + entry.mSubElement = 6; + else if (subElement == "(3)(1)") + entry.mSubElement = 7; + else if (subElement == "(0)(2)") + entry.mSubElement = 8; + else if (subElement == "(1)(2)") + entry.mSubElement = 9; + else if (subElement == "(2)(2)") + entry.mSubElement = 10; + else if (subElement == "(3)(2)") + entry.mSubElement = 11; + else if (subElement == "(0)(3)") + entry.mSubElement = 12; + else if (subElement == "(1)(3)") + entry.mSubElement = 13; + else if (subElement == "(2)(3)") + entry.mSubElement = 14; + else if (subElement == "(3)(3)") + entry.mSubElement = 15; + } + + // determine which transform step is affected by this channel + entry.mTransformIndex = SIZE_MAX; + for (size_t a = 0; a < srcNode->mTransforms.size(); ++a) + if (srcNode->mTransforms[a].mID == entry.mTransformId) + entry.mTransformIndex = a; + + if (entry.mTransformIndex == SIZE_MAX) { + if (entry.mTransformId.find("morph-weights") == std::string::npos) { + continue; + } + entry.mTargetId = entry.mTransformId; + entry.mTransformId = std::string(); + } + + entry.mChannel = &(*cit); + entries.push_back(entry); + } + + // if there's no channel affecting the current node, we skip it + if (entries.empty()) { + continue; + } + + // resolve the data pointers for all anim channels. Find the minimum time while we're at it + ai_real startTime = ai_real(1e20), endTime = ai_real(-1e20); + for (ChannelEntry & e : entries) { + e.mTimeAccessor = &pParser.ResolveLibraryReference(pParser.mAccessorLibrary, e.mChannel->mSourceTimes); + e.mTimeData = &pParser.ResolveLibraryReference(pParser.mDataLibrary, e.mTimeAccessor->mSource); + e.mValueAccessor = &pParser.ResolveLibraryReference(pParser.mAccessorLibrary, e.mChannel->mSourceValues); + e.mValueData = &pParser.ResolveLibraryReference(pParser.mDataLibrary, e.mValueAccessor->mSource); + + // time count and value count must match + if (e.mTimeAccessor->mCount != e.mValueAccessor->mCount) { + throw DeadlyImportError("Time count / value count mismatch in animation channel \"", e.mChannel->mTarget, "\"."); + } + + if (e.mTimeAccessor->mCount > 0) { + // find bounding times + startTime = std::min(startTime, ReadFloat(*e.mTimeAccessor, *e.mTimeData, 0, 0)); + endTime = std::max(endTime, ReadFloat(*e.mTimeAccessor, *e.mTimeData, e.mTimeAccessor->mCount - 1, 0)); + } + } + + std::vector<aiMatrix4x4> resultTrafos; + if (!entries.empty() && entries.front().mTimeAccessor->mCount > 0) { + // create a local transformation chain of the node's transforms + std::vector<Collada::Transform> transforms = srcNode->mTransforms; + + // now for every unique point in time, find or interpolate the key values for that time + // and apply them to the transform chain. Then the node's present transformation can be calculated. + ai_real time = startTime; + while (1) { + for (ChannelEntry & e : entries) { + // find the keyframe behind the current point in time + size_t pos = 0; + ai_real postTime = 0.0; + while (1) { + if (pos >= e.mTimeAccessor->mCount) { + break; + } + postTime = ReadFloat(*e.mTimeAccessor, *e.mTimeData, pos, 0); + if (postTime >= time) { + break; + } + ++pos; + } + + pos = std::min(pos, e.mTimeAccessor->mCount - 1); + + // read values from there + ai_real temp[16]; + for (size_t c = 0; c < e.mValueAccessor->mSize; ++c) { + temp[c] = ReadFloat(*e.mValueAccessor, *e.mValueData, pos, c); + } + + // if not exactly at the key time, interpolate with previous value set + if (postTime > time && pos > 0) { + ai_real preTime = ReadFloat(*e.mTimeAccessor, *e.mTimeData, pos - 1, 0); + ai_real factor = (time - postTime) / (preTime - postTime); + + for (size_t c = 0; c < e.mValueAccessor->mSize; ++c) { + ai_real v = ReadFloat(*e.mValueAccessor, *e.mValueData, pos - 1, c); + temp[c] += (v - temp[c]) * factor; + } + } + + // Apply values to current transformation + std::copy(temp, temp + e.mValueAccessor->mSize, transforms[e.mTransformIndex].f + e.mSubElement); + } + + // Calculate resulting transformation + aiMatrix4x4 mat = pParser.CalculateResultTransform(transforms); + + // out of laziness: we store the time in matrix.d4 + mat.d4 = time; + resultTrafos.push_back(mat); + + // find next point in time to evaluate. That's the closest frame larger than the current in any channel + ai_real nextTime = ai_real(1e20); + for (ChannelEntry & channelElement : entries) { + // find the next time value larger than the current + size_t pos = 0; + while (pos < channelElement.mTimeAccessor->mCount) { + const ai_real t = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos, 0); + if (t > time) { + nextTime = std::min(nextTime, t); + break; + } + ++pos; + } + + // https://github.com/assimp/assimp/issues/458 + // Sub-sample axis-angle channels if the delta between two consecutive + // key-frame angles is >= 180 degrees. + if (transforms[channelElement.mTransformIndex].mType == TF_ROTATE && channelElement.mSubElement == 3 && pos > 0 && pos < channelElement.mTimeAccessor->mCount) { + const ai_real cur_key_angle = ReadFloat(*channelElement.mValueAccessor, *channelElement.mValueData, pos, 0); + const ai_real last_key_angle = ReadFloat(*channelElement.mValueAccessor, *channelElement.mValueData, pos - 1, 0); + const ai_real cur_key_time = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos, 0); + const ai_real last_key_time = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos - 1, 0); + const ai_real last_eval_angle = last_key_angle + (cur_key_angle - last_key_angle) * (time - last_key_time) / (cur_key_time - last_key_time); + const ai_real delta = std::abs(cur_key_angle - last_eval_angle); + if (delta >= 180.0) { + const int subSampleCount = static_cast<int>(std::floor(delta / 90.0)); + if (cur_key_time != time) { + const ai_real nextSampleTime = time + (cur_key_time - time) / subSampleCount; + nextTime = std::min(nextTime, nextSampleTime); + } + } + } + } + + // no more keys on any channel after the current time -> we're done + if (nextTime > 1e19) { + break; + } + + // else construct next key-frame at this following time point + time = nextTime; + } + } + + // build an animation channel for the given node out of these trafo keys + if (!resultTrafos.empty()) { + aiNodeAnim *dstAnim = new aiNodeAnim; + dstAnim->mNodeName = nodeName; + dstAnim->mNumPositionKeys = static_cast<unsigned int>(resultTrafos.size()); + dstAnim->mNumRotationKeys = static_cast<unsigned int>(resultTrafos.size()); + dstAnim->mNumScalingKeys = static_cast<unsigned int>(resultTrafos.size()); + dstAnim->mPositionKeys = new aiVectorKey[resultTrafos.size()]; + dstAnim->mRotationKeys = new aiQuatKey[resultTrafos.size()]; + dstAnim->mScalingKeys = new aiVectorKey[resultTrafos.size()]; + + for (size_t a = 0; a < resultTrafos.size(); ++a) { + aiMatrix4x4 mat = resultTrafos[a]; + double time = double(mat.d4); // remember? time is stored in mat.d4 + mat.d4 = 1.0f; + + dstAnim->mPositionKeys[a].mTime = time * kMillisecondsFromSeconds; + dstAnim->mRotationKeys[a].mTime = time * kMillisecondsFromSeconds; + dstAnim->mScalingKeys[a].mTime = time * kMillisecondsFromSeconds; + mat.Decompose(dstAnim->mScalingKeys[a].mValue, dstAnim->mRotationKeys[a].mValue, dstAnim->mPositionKeys[a].mValue); + } + + anims.push_back(dstAnim); + } else { + ASSIMP_LOG_WARN("Collada loader: found empty animation channel, ignored. Please check your exporter."); + } + + if (!entries.empty() && entries.front().mTimeAccessor->mCount > 0) { + std::vector<ChannelEntry> morphChannels; + for (ChannelEntry & e : entries) { + // skip non-transform types + if (e.mTargetId.empty()) { + continue; + } + + if (e.mTargetId.find("morph-weights") != std::string::npos) { + morphChannels.push_back(e); + } + } + if (!morphChannels.empty()) { + // either 1) morph weight animation count should contain morph target count channels + // or 2) one channel with morph target count arrays + // assume first + + aiMeshMorphAnim *morphAnim = new aiMeshMorphAnim; + morphAnim->mName.Set(nodeName); + + std::vector<MorphTimeValues> morphTimeValues; + int morphAnimChannelIndex = 0; + for (ChannelEntry & e : morphChannels) { + std::string::size_type apos = e.mTargetId.find('('); + std::string::size_type bpos = e.mTargetId.find(')'); + + // If unknown way to specify weight -> ignore this animation + if (apos == std::string::npos || bpos == std::string::npos) { + continue; + } + + // weight target can be in format Weight_M_N, Weight_N, WeightN, or some other way + // we ignore the name and just assume the channels are in the right order + for (unsigned int i = 0; i < e.mTimeData->mValues.size(); i++) { + insertMorphTimeValue(morphTimeValues, e.mTimeData->mValues[i], e.mValueData->mValues[i], morphAnimChannelIndex); + } + + ++morphAnimChannelIndex; + } + + morphAnim->mNumKeys = static_cast<unsigned int>(morphTimeValues.size()); + morphAnim->mKeys = new aiMeshMorphKey[morphAnim->mNumKeys]; + for (unsigned int key = 0; key < morphAnim->mNumKeys; key++) { + morphAnim->mKeys[key].mNumValuesAndWeights = static_cast<unsigned int>(morphChannels.size()); + morphAnim->mKeys[key].mValues = new unsigned int[morphChannels.size()]; + morphAnim->mKeys[key].mWeights = new double[morphChannels.size()]; + + morphAnim->mKeys[key].mTime = morphTimeValues[key].mTime * kMillisecondsFromSeconds; + for (unsigned int valueIndex = 0; valueIndex < morphChannels.size(); ++valueIndex) { + morphAnim->mKeys[key].mValues[valueIndex] = valueIndex; + morphAnim->mKeys[key].mWeights[valueIndex] = getWeightAtKey(morphTimeValues, key, valueIndex); + } + } + + morphAnims.push_back(morphAnim); + } + } + } + + if (!anims.empty() || !morphAnims.empty()) { + aiAnimation *anim = new aiAnimation; + anim->mName.Set(pName); + anim->mNumChannels = static_cast<unsigned int>(anims.size()); + if (anim->mNumChannels > 0) { + anim->mChannels = new aiNodeAnim *[anims.size()]; + std::copy(anims.begin(), anims.end(), anim->mChannels); + } + anim->mNumMorphMeshChannels = static_cast<unsigned int>(morphAnims.size()); + if (anim->mNumMorphMeshChannels > 0) { + anim->mMorphMeshChannels = new aiMeshMorphAnim *[anim->mNumMorphMeshChannels]; + std::copy(morphAnims.begin(), morphAnims.end(), anim->mMorphMeshChannels); + } + anim->mDuration = 0.0f; + for (auto & a : anims) { + anim->mDuration = std::max(anim->mDuration, a->mPositionKeys[a->mNumPositionKeys - 1].mTime); + anim->mDuration = std::max(anim->mDuration, a->mRotationKeys[a->mNumRotationKeys - 1].mTime); + anim->mDuration = std::max(anim->mDuration, a->mScalingKeys[a->mNumScalingKeys - 1].mTime); + } + for (auto & morphAnim : morphAnims) { + anim->mDuration = std::max(anim->mDuration, morphAnim->mKeys[morphAnim->mNumKeys - 1].mTime); + } + anim->mTicksPerSecond = 1000.0; + mAnims.push_back(anim); + } +} + +// ------------------------------------------------------------------------------------------------ +// Add a texture to a material structure +void ColladaLoader::AddTexture(aiMaterial &mat, + const ColladaParser &pParser, + const Effect &effect, + const Sampler &sampler, + aiTextureType type, + unsigned int idx) { + // first of all, basic file name + const aiString name = FindFilenameForEffectTexture(pParser, effect, sampler.mName); + mat.AddProperty(&name, _AI_MATKEY_TEXTURE_BASE, type, idx); + + // mapping mode + int map = aiTextureMapMode_Clamp; + if (sampler.mWrapU) { + map = aiTextureMapMode_Wrap; + } + if (sampler.mWrapU && sampler.mMirrorU) { + map = aiTextureMapMode_Mirror; + } + + mat.AddProperty(&map, 1, _AI_MATKEY_MAPPINGMODE_U_BASE, type, idx); + + map = aiTextureMapMode_Clamp; + if (sampler.mWrapV) { + map = aiTextureMapMode_Wrap; + } + if (sampler.mWrapV && sampler.mMirrorV) { + map = aiTextureMapMode_Mirror; + } + + mat.AddProperty(&map, 1, _AI_MATKEY_MAPPINGMODE_V_BASE, type, idx); + + // UV transformation + mat.AddProperty(&sampler.mTransform, 1, + _AI_MATKEY_UVTRANSFORM_BASE, type, idx); + + // Blend mode + mat.AddProperty((int *)&sampler.mOp, 1, + _AI_MATKEY_TEXBLEND_BASE, type, idx); + + // Blend factor + mat.AddProperty((ai_real *)&sampler.mWeighting, 1, + _AI_MATKEY_TEXBLEND_BASE, type, idx); + + // UV source index ... if we didn't resolve the mapping, it is actually just + // a guess but it works in most cases. We search for the frst occurrence of a + // number in the channel name. We assume it is the zero-based index into the + // UV channel array of all corresponding meshes. It could also be one-based + // for some exporters, but we won't care of it unless someone complains about. + if (sampler.mUVId != UINT_MAX) { + map = sampler.mUVId; + } else { + map = -1; + for (std::string::const_iterator it = sampler.mUVChannel.begin(); it != sampler.mUVChannel.end(); ++it) { + if (IsNumeric(*it)) { + map = strtoul10(&(*it)); + break; + } + } + if (-1 == map) { + ASSIMP_LOG_WARN("Collada: unable to determine UV channel for texture"); + map = 0; + } + } + mat.AddProperty(&map, 1, _AI_MATKEY_UVWSRC_BASE, type, idx); +} + +// ------------------------------------------------------------------------------------------------ +// Fills materials from the collada material definitions +void ColladaLoader::FillMaterials(const ColladaParser &pParser, aiScene * /*pScene*/) { + for (auto &elem : newMats) { + aiMaterial &mat = (aiMaterial &)*elem.second; + Collada::Effect &effect = *elem.first; + + // resolve shading mode + int shadeMode; + if (effect.mFaceted) { + shadeMode = aiShadingMode_Flat; + } else { + switch (effect.mShadeType) { + case Collada::Shade_Constant: + shadeMode = aiShadingMode_NoShading; + break; + case Collada::Shade_Lambert: + shadeMode = aiShadingMode_Gouraud; + break; + case Collada::Shade_Blinn: + shadeMode = aiShadingMode_Blinn; + break; + case Collada::Shade_Phong: + shadeMode = aiShadingMode_Phong; + break; + + default: + ASSIMP_LOG_WARN("Collada: Unrecognized shading mode, using gouraud shading"); + shadeMode = aiShadingMode_Gouraud; + break; + } + } + mat.AddProperty<int>(&shadeMode, 1, AI_MATKEY_SHADING_MODEL); + + // double-sided? + shadeMode = effect.mDoubleSided; + mat.AddProperty<int>(&shadeMode, 1, AI_MATKEY_TWOSIDED); + + // wire-frame? + shadeMode = effect.mWireframe; + mat.AddProperty<int>(&shadeMode, 1, AI_MATKEY_ENABLE_WIREFRAME); + + // add material colors + mat.AddProperty(&effect.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT); + mat.AddProperty(&effect.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + mat.AddProperty(&effect.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); + mat.AddProperty(&effect.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); + mat.AddProperty(&effect.mReflective, 1, AI_MATKEY_COLOR_REFLECTIVE); + + // scalar properties + mat.AddProperty(&effect.mShininess, 1, AI_MATKEY_SHININESS); + mat.AddProperty(&effect.mReflectivity, 1, AI_MATKEY_REFLECTIVITY); + mat.AddProperty(&effect.mRefractIndex, 1, AI_MATKEY_REFRACTI); + + // transparency, a very hard one. seemingly not all files are following the + // specification here (1.0 transparency => completely opaque)... + // therefore, we let the opportunity for the user to manually invert + // the transparency if necessary and we add preliminary support for RGB_ZERO mode + if (effect.mTransparency >= 0.f && effect.mTransparency <= 1.f) { + // handle RGB transparency completely, cf Collada specs 1.5.0 pages 249 and 304 + if (effect.mRGBTransparency) { + // use luminance as defined by ISO/CIE color standards (see ITU-R Recommendation BT.709-4) + effect.mTransparency *= (0.212671f * effect.mTransparent.r + + 0.715160f * effect.mTransparent.g + + 0.072169f * effect.mTransparent.b); + + effect.mTransparent.a = 1.f; + + mat.AddProperty(&effect.mTransparent, 1, AI_MATKEY_COLOR_TRANSPARENT); + } else { + effect.mTransparency *= effect.mTransparent.a; + } + + if (effect.mInvertTransparency) { + effect.mTransparency = 1.f - effect.mTransparency; + } + + // Is the material finally transparent ? + if (effect.mHasTransparency || effect.mTransparency < 1.f) { + mat.AddProperty(&effect.mTransparency, 1, AI_MATKEY_OPACITY); + } + } + + // add textures, if given + if (!effect.mTexAmbient.mName.empty()) { + // It is merely a light-map + AddTexture(mat, pParser, effect, effect.mTexAmbient, aiTextureType_LIGHTMAP); + } + + if (!effect.mTexEmissive.mName.empty()) + AddTexture(mat, pParser, effect, effect.mTexEmissive, aiTextureType_EMISSIVE); + + if (!effect.mTexSpecular.mName.empty()) + AddTexture(mat, pParser, effect, effect.mTexSpecular, aiTextureType_SPECULAR); + + if (!effect.mTexDiffuse.mName.empty()) + AddTexture(mat, pParser, effect, effect.mTexDiffuse, aiTextureType_DIFFUSE); + + if (!effect.mTexBump.mName.empty()) + AddTexture(mat, pParser, effect, effect.mTexBump, aiTextureType_NORMALS); + + if (!effect.mTexTransparent.mName.empty()) + AddTexture(mat, pParser, effect, effect.mTexTransparent, aiTextureType_OPACITY); + + if (!effect.mTexReflective.mName.empty()) + AddTexture(mat, pParser, effect, effect.mTexReflective, aiTextureType_REFLECTION); + } +} + +// ------------------------------------------------------------------------------------------------ +// Constructs materials from the collada material definitions +void ColladaLoader::BuildMaterials(ColladaParser &pParser, aiScene * /*pScene*/) { + newMats.reserve(pParser.mMaterialLibrary.size()); + + for (ColladaParser::MaterialLibrary::const_iterator matIt = pParser.mMaterialLibrary.begin(); + matIt != pParser.mMaterialLibrary.end(); ++matIt) { + const Material &material = matIt->second; + // a material is only a reference to an effect + ColladaParser::EffectLibrary::iterator effIt = pParser.mEffectLibrary.find(material.mEffect); + if (effIt == pParser.mEffectLibrary.end()) + continue; + Effect &effect = effIt->second; + + // create material + aiMaterial *mat = new aiMaterial; + aiString name(material.mName.empty() ? matIt->first : material.mName); + mat->AddProperty(&name, AI_MATKEY_NAME); + + // store the material + mMaterialIndexByName[matIt->first] = newMats.size(); + newMats.emplace_back(&effect, mat); + } + // ScenePreprocessor generates a default material automatically if none is there. + // All further code here in this loader works well without a valid material so + // we can safely let it to ScenePreprocessor. +} + +// ------------------------------------------------------------------------------------------------ +// Resolves the texture name for the given effect texture entry and loads the texture data +aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser &pParser, + const Effect &pEffect, const std::string &pName) { + aiString result; + + // recurse through the param references until we end up at an image + std::string name = pName; + while (1) { + // the given string is a param entry. Find it + Effect::ParamLibrary::const_iterator it = pEffect.mParams.find(name); + // if not found, we're at the end of the recursion. The resulting string should be the image ID + if (it == pEffect.mParams.end()) + break; + + // else recurse on + name = it->second.mReference; + } + + // find the image referred by this name in the image library of the scene + ColladaParser::ImageLibrary::const_iterator imIt = pParser.mImageLibrary.find(name); + if (imIt == pParser.mImageLibrary.end()) { + ASSIMP_LOG_WARN("Collada: Unable to resolve effect texture entry \"", pName, "\", ended up at ID \"", name, "\"."); + + //set default texture file name + result.Set(name + ".jpg"); + ColladaParser::UriDecodePath(result); + return result; + } + + // if this is an embedded texture image setup an aiTexture for it + if (!imIt->second.mImageData.empty()) { + aiTexture *tex = new aiTexture(); + + // Store embedded texture name reference + tex->mFilename.Set(imIt->second.mFileName.c_str()); + result.Set(imIt->second.mFileName); + + // setup format hint + if (imIt->second.mEmbeddedFormat.length() >= HINTMAXTEXTURELEN) { + ASSIMP_LOG_WARN("Collada: texture format hint is too long, truncating to 3 characters"); + } + strncpy(tex->achFormatHint, imIt->second.mEmbeddedFormat.c_str(), 3); + + // and copy texture data + tex->mHeight = 0; + tex->mWidth = static_cast<unsigned int>(imIt->second.mImageData.size()); + tex->pcData = (aiTexel *)new char[tex->mWidth]; + memcpy(tex->pcData, &imIt->second.mImageData[0], tex->mWidth); + + // and add this texture to the list + mTextures.push_back(tex); + return result; + } + + if (imIt->second.mFileName.empty()) { + throw DeadlyImportError("Collada: Invalid texture, no data or file reference given"); + } + + result.Set(imIt->second.mFileName); + + return result; +} + +// ------------------------------------------------------------------------------------------------ +// Reads a float value from an accessor and its data array. +ai_real ColladaLoader::ReadFloat(const Accessor &pAccessor, const Data &pData, size_t pIndex, size_t pOffset) const { + size_t pos = pAccessor.mStride * pIndex + pAccessor.mOffset + pOffset; + ai_assert(pos < pData.mValues.size()); + return pData.mValues[pos]; +} + +// ------------------------------------------------------------------------------------------------ +// Reads a string value from an accessor and its data array. +const std::string &ColladaLoader::ReadString(const Accessor &pAccessor, const Data &pData, size_t pIndex) const { + size_t pos = pAccessor.mStride * pIndex + pAccessor.mOffset; + ai_assert(pos < pData.mStrings.size()); + return pData.mStrings[pos]; +} + +// ------------------------------------------------------------------------------------------------ +// Collects all nodes into the given array +void ColladaLoader::CollectNodes(const aiNode *pNode, std::vector<const aiNode *> &poNodes) const { + poNodes.push_back(pNode); + for (size_t a = 0; a < pNode->mNumChildren; ++a) { + CollectNodes(pNode->mChildren[a], poNodes); + } +} + +// ------------------------------------------------------------------------------------------------ +// Finds a node in the collada scene by the given name +const Node *ColladaLoader::FindNode(const Node *pNode, const std::string &pName) const { + if (pNode->mName == pName || pNode->mID == pName) + return pNode; + + for (auto a : pNode->mChildren) { + const Collada::Node *node = FindNode(a, pName); + if (node) { + return node; + } + } + + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// Finds a node in the collada scene by the given SID +const Node *ColladaLoader::FindNodeBySID(const Node *pNode, const std::string &pSID) const { + if (nullptr == pNode) { + return nullptr; + } + + if (pNode->mSID == pSID) { + return pNode; + } + + for (auto a : pNode->mChildren) { + const Collada::Node *node = FindNodeBySID(a, pSID); + if (node) { + return node; + } + } + + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// Finds a proper unique name for a node derived from the collada-node's properties. +// The name must be unique for proper node-bone association. +std::string ColladaLoader::FindNameForNode(const Node *pNode) { + // If explicitly requested, just use the collada name. + if (useColladaName) { + if (!pNode->mName.empty()) { + return pNode->mName; + } else { + return format() << "$ColladaAutoName$_" << mNodeNameCounter++; + } + } else { + // Now setup the name of the assimp node. The collada name might not be + // unique, so we use the collada ID. + if (!pNode->mID.empty()) + return pNode->mID; + else if (!pNode->mSID.empty()) + return pNode->mSID; + else { + // No need to worry. Unnamed nodes are no problem at all, except + // if cameras or lights need to be assigned to them. + return format() << "$ColladaAutoName$_" << mNodeNameCounter++; + } + } +} + +} // Namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER diff --git a/libs/assimp/code/AssetLib/Collada/ColladaLoader.h b/libs/assimp/code/AssetLib/Collada/ColladaLoader.h new file mode 100644 index 0000000..246abed --- /dev/null +++ b/libs/assimp/code/AssetLib/Collada/ColladaLoader.h @@ -0,0 +1,249 @@ +/** Defines the collada loader class */ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef AI_COLLADALOADER_H_INC +#define AI_COLLADALOADER_H_INC + +#include "ColladaParser.h" +#include <assimp/BaseImporter.h> + +struct aiNode; +struct aiCamera; +struct aiLight; +struct aiTexture; +struct aiAnimation; + +namespace Assimp { + +struct ColladaMeshIndex { + std::string mMeshID; + size_t mSubMesh; + std::string mMaterial; + ColladaMeshIndex(const std::string &pMeshID, size_t pSubMesh, const std::string &pMaterial) : + mMeshID(pMeshID), mSubMesh(pSubMesh), mMaterial(pMaterial) { + ai_assert(!pMeshID.empty()); + } + + bool operator<(const ColladaMeshIndex &p) const { + if (mMeshID == p.mMeshID) { + if (mSubMesh == p.mSubMesh) + return mMaterial < p.mMaterial; + else + return mSubMesh < p.mSubMesh; + } else { + return mMeshID < p.mMeshID; + } + } +}; + +/** Loader class to read Collada scenes. Collada is over-engineered to death, with every new iteration bringing + * more useless stuff, so I limited the data to what I think is useful for games. +*/ +class ColladaLoader : public BaseImporter { +public: + /// The class constructor. + ColladaLoader(); + + /// The class destructor. + ~ColladaLoader() override; + + /// Returns whether the class can handle the format of the given file. + /// @see BaseImporter::CanRead() for more details. + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + +protected: + /// See #BaseImporter::GetInfo for the details + const aiImporterDesc *GetInfo() const override; + + /// See #BaseImporter::SetupProperties for the details + void SetupProperties(const Importer *pImp) override; + + /// See #BaseImporter::InternReadFile for the details + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + + /** Recursively constructs a scene node for the given parser node and returns it. */ + aiNode *BuildHierarchy(const ColladaParser &pParser, const Collada::Node *pNode); + + /** Resolve node instances */ + void ResolveNodeInstances(const ColladaParser &pParser, const Collada::Node *pNode, + std::vector<const Collada::Node *> &resolved); + + /** Builds meshes for the given node and references them */ + void BuildMeshesForNode(const ColladaParser &pParser, const Collada::Node *pNode, + aiNode *pTarget); + + aiMesh *findMesh(const std::string &meshid); + + /** Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh */ + aiMesh *CreateMesh(const ColladaParser &pParser, const Collada::Mesh *pSrcMesh, const Collada::SubMesh &pSubMesh, + const Collada::Controller *pSrcController, size_t pStartVertex, size_t pStartFace); + + /** Builds cameras for the given node and references them */ + void BuildCamerasForNode(const ColladaParser &pParser, const Collada::Node *pNode, + aiNode *pTarget); + + /** Builds lights for the given node and references them */ + void BuildLightsForNode(const ColladaParser &pParser, const Collada::Node *pNode, + aiNode *pTarget); + + /** Stores all meshes in the given scene */ + void StoreSceneMeshes(aiScene *pScene); + + /** Stores all materials in the given scene */ + void StoreSceneMaterials(aiScene *pScene); + + /** Stores all lights in the given scene */ + void StoreSceneLights(aiScene *pScene); + + /** Stores all cameras in the given scene */ + void StoreSceneCameras(aiScene *pScene); + + /** Stores all textures in the given scene */ + void StoreSceneTextures(aiScene *pScene); + + /** Stores all animations + * @param pScene target scene to store the anims + */ + void StoreAnimations(aiScene *pScene, const ColladaParser &pParser); + + /** Stores all animations for the given source anim and its nested child animations + * @param pScene target scene to store the anims + * @param pSrcAnim the source animation to process + * @param pPrefix Prefix to the name in case of nested animations + */ + void StoreAnimations(aiScene *pScene, const ColladaParser &pParser, const Collada::Animation *pSrcAnim, const std::string &pPrefix); + + /** Constructs the animation for the given source anim */ + void CreateAnimation(aiScene *pScene, const ColladaParser &pParser, const Collada::Animation *pSrcAnim, const std::string &pName); + + /** Constructs materials from the collada material definitions */ + void BuildMaterials(ColladaParser &pParser, aiScene *pScene); + + /** Fill materials from the collada material definitions */ + void FillMaterials(const ColladaParser &pParser, aiScene *pScene); + + /** Resolve UV channel mappings*/ + void ApplyVertexToEffectSemanticMapping(Collada::Sampler &sampler, + const Collada::SemanticMappingTable &table); + + /** Add a texture and all of its sampling properties to a material*/ + void AddTexture(aiMaterial &mat, const ColladaParser &pParser, + const Collada::Effect &effect, + const Collada::Sampler &sampler, + aiTextureType type, unsigned int idx = 0); + + /** Resolves the texture name for the given effect texture entry */ + aiString FindFilenameForEffectTexture(const ColladaParser &pParser, + const Collada::Effect &pEffect, const std::string &pName); + + /** Reads a float value from an accessor and its data array. + * @param pAccessor The accessor to use for reading + * @param pData The data array to read from + * @param pIndex The index of the element to retrieve + * @param pOffset Offset into the element, for multipart elements such as vectors or matrices + * @return the specified value + */ + ai_real ReadFloat(const Collada::Accessor &pAccessor, const Collada::Data &pData, size_t pIndex, size_t pOffset) const; + + /** Reads a string value from an accessor and its data array. + * @param pAccessor The accessor to use for reading + * @param pData The data array to read from + * @param pIndex The index of the element to retrieve + * @return the specified value + */ + const std::string &ReadString(const Collada::Accessor &pAccessor, const Collada::Data &pData, size_t pIndex) const; + + /** Recursively collects all nodes into the given array */ + void CollectNodes(const aiNode *pNode, std::vector<const aiNode *> &poNodes) const; + + /** Finds a node in the collada scene by the given name */ + const Collada::Node *FindNode(const Collada::Node *pNode, const std::string &pName) const; + /** Finds a node in the collada scene by the given SID */ + const Collada::Node *FindNodeBySID(const Collada::Node *pNode, const std::string &pSID) const; + + /** Finds a proper name for a node derived from the collada-node's properties */ + std::string FindNameForNode(const Collada::Node *pNode); + +protected: + /** Filename, for a verbose error message */ + std::string mFileName; + + /** Which mesh-material compound was stored under which mesh ID */ + std::map<ColladaMeshIndex, size_t> mMeshIndexByID; + + /** Which material was stored under which index in the scene */ + std::map<std::string, size_t> mMaterialIndexByName; + + /** Accumulated meshes for the target scene */ + std::vector<aiMesh *> mMeshes; + + /** Accumulated morph target meshes */ + std::vector<aiMesh *> mTargetMeshes; + + /** Temporary material list */ + std::vector<std::pair<Collada::Effect *, aiMaterial *>> newMats; + + /** Temporary camera list */ + std::vector<aiCamera *> mCameras; + + /** Temporary light list */ + std::vector<aiLight *> mLights; + + /** Temporary texture list */ + std::vector<aiTexture *> mTextures; + + /** Accumulated animations for the target scene */ + std::vector<aiAnimation *> mAnims; + + bool noSkeletonMesh; + bool ignoreUpDirection; + bool useColladaName; + + /** Used by FindNameForNode() to generate unique node names */ + unsigned int mNodeNameCounter; +}; + +} // end of namespace Assimp + +#endif // AI_COLLADALOADER_H_INC diff --git a/libs/assimp/code/AssetLib/Collada/ColladaParser.cpp b/libs/assimp/code/AssetLib/Collada/ColladaParser.cpp new file mode 100644 index 0000000..922d1f6 --- /dev/null +++ b/libs/assimp/code/AssetLib/Collada/ColladaParser.cpp @@ -0,0 +1,2402 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 ColladaParser.cpp + * @brief Implementation of the Collada parser helper + */ + +#ifndef ASSIMP_BUILD_NO_COLLADA_IMPORTER + +#include "ColladaParser.h" +#include <assimp/ParsingUtils.h> +#include <assimp/StringUtils.h> +#include <assimp/ZipArchiveIOSystem.h> +#include <assimp/commonMetaData.h> +#include <assimp/fast_atof.h> +#include <assimp/light.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <memory> + +using namespace Assimp; +using namespace Assimp::Collada; +using namespace Assimp::Formatter; + +static void ReportWarning(const char *msg, ...) { + ai_assert(nullptr != msg); + + va_list args; + va_start(args, msg); + + char szBuffer[3000]; + const int iLen = vsprintf(szBuffer, msg, args); + ai_assert(iLen > 0); + + va_end(args); + ASSIMP_LOG_WARN("Validation warning: ", std::string(szBuffer, iLen)); +} + +static bool FindCommonKey(const std::string &collada_key, const MetaKeyPairVector &key_renaming, size_t &found_index) { + for (size_t i = 0; i < key_renaming.size(); ++i) { + if (key_renaming[i].first == collada_key) { + found_index = i; + return true; + } + } + found_index = std::numeric_limits<size_t>::max(); + + return false; +} + +static void readUrlAttribute(XmlNode &node, std::string &url) { + url.clear(); + if (!XmlParser::getStdStrAttribute(node, "url", url)) { + return; + } + if (url[0] != '#') { + throw DeadlyImportError("Unknown reference format"); + } + url = url.c_str() + 1; +} + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +ColladaParser::ColladaParser(IOSystem *pIOHandler, const std::string &pFile) : + mFileName(pFile), + mXmlParser(), + mDataLibrary(), + mAccessorLibrary(), + mMeshLibrary(), + mNodeLibrary(), + mImageLibrary(), + mEffectLibrary(), + mMaterialLibrary(), + mLightLibrary(), + mCameraLibrary(), + mControllerLibrary(), + mRootNode(nullptr), + mAnims(), + mUnitSize(1.0f), + mUpDirection(UP_Y), + mFormat(FV_1_5_n) { + if (nullptr == pIOHandler) { + throw DeadlyImportError("IOSystem is nullptr."); + } + + std::unique_ptr<IOStream> daefile; + std::unique_ptr<ZipArchiveIOSystem> zip_archive; + + // Determine type + std::string extension = BaseImporter::GetExtension(pFile); + if (extension != "dae") { + zip_archive.reset(new ZipArchiveIOSystem(pIOHandler, pFile)); + } + + if (zip_archive && zip_archive->isOpen()) { + std::string dae_filename = ReadZaeManifest(*zip_archive); + + if (dae_filename.empty()) { + throw DeadlyImportError("Invalid ZAE"); + } + + daefile.reset(zip_archive->Open(dae_filename.c_str())); + if (daefile == nullptr) { + throw DeadlyImportError("Invalid ZAE manifest: '", dae_filename, "' is missing"); + } + } else { + // attempt to open the file directly + daefile.reset(pIOHandler->Open(pFile)); + if (daefile.get() == nullptr) { + throw DeadlyImportError("Failed to open file '", pFile, "'."); + } + } + + // generate a XML reader for it + if (!mXmlParser.parse(daefile.get())) { + throw DeadlyImportError("Unable to read file, malformed XML"); + } + // start reading + XmlNode node = mXmlParser.getRootNode(); + XmlNode colladaNode = node.child("COLLADA"); + if (colladaNode.empty()) { + return; + } + + // Read content and embedded textures + ReadContents(colladaNode); + if (zip_archive && zip_archive->isOpen()) { + ReadEmbeddedTextures(*zip_archive); + } +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +ColladaParser::~ColladaParser() { + for (auto &it : mNodeLibrary) { + delete it.second; + } + for (auto &it : mMeshLibrary) { + delete it.second; + } +} + +// ------------------------------------------------------------------------------------------------ +// Read a ZAE manifest and return the filename to attempt to open +std::string ColladaParser::ReadZaeManifest(ZipArchiveIOSystem &zip_archive) { + // Open the manifest + std::unique_ptr<IOStream> manifestfile(zip_archive.Open("manifest.xml")); + if (manifestfile == nullptr) { + // No manifest, hope there is only one .DAE inside + std::vector<std::string> file_list; + zip_archive.getFileListExtension(file_list, "dae"); + + if (file_list.empty()) { + return std::string(); + } + + return file_list.front(); + } + XmlParser manifestParser; + if (!manifestParser.parse(manifestfile.get())) { + return std::string(); + } + + XmlNode root = manifestParser.getRootNode(); + const std::string &name = root.name(); + if (name != "dae_root") { + root = *manifestParser.findNode("dae_root"); + if (nullptr == root) { + return std::string(); + } + std::string v; + XmlParser::getValueAsString(root, v); + aiString ai_str(v); + UriDecodePath(ai_str); + return std::string(ai_str.C_Str()); + } + + return std::string(); +} + +// ------------------------------------------------------------------------------------------------ +// Convert a path read from a collada file to the usual representation +void ColladaParser::UriDecodePath(aiString &ss) { + // TODO: collada spec, p 22. Handle URI correctly. + // For the moment we're just stripping the file:// away to make it work. + // Windows doesn't seem to be able to find stuff like + // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg' + if (0 == strncmp(ss.data, "file://", 7)) { + ss.length -= 7; + memmove(ss.data, ss.data + 7, ss.length); + ss.data[ss.length] = '\0'; + } + + // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes... + // I need to filter it without destroying linux paths starting with "/somewhere" + if (ss.data[0] == '/' && isalpha((unsigned char)ss.data[1]) && ss.data[2] == ':') { + --ss.length; + ::memmove(ss.data, ss.data + 1, ss.length); + ss.data[ss.length] = 0; + } + + // find and convert all %xy special chars + char *out = ss.data; + for (const char *it = ss.data; it != ss.data + ss.length; /**/) { + if (*it == '%' && (it + 3) < ss.data + ss.length) { + // separate the number to avoid dragging in chars from behind into the parsing + char mychar[3] = { it[1], it[2], 0 }; + size_t nbr = strtoul16(mychar); + it += 3; + *out++ = (char)(nbr & 0xFF); + } else { + *out++ = *it++; + } + } + + // adjust length and terminator of the shortened string + *out = 0; + ai_assert(out > ss.data); + ss.length = static_cast<ai_uint32>(out - ss.data); +} + +// ------------------------------------------------------------------------------------------------ +// Reads the contents of the file +void ColladaParser::ReadContents(XmlNode &node) { + const std::string name = node.name(); + if (name == "COLLADA") { + std::string version; + if (XmlParser::getStdStrAttribute(node, "version", version)) { + aiString v; + v.Set(version.c_str()); + mAssetMetaData.emplace(AI_METADATA_SOURCE_FORMAT_VERSION, v); + if (!::strncmp(version.c_str(), "1.5", 3)) { + mFormat = FV_1_5_n; + ASSIMP_LOG_DEBUG("Collada schema version is 1.5.n"); + } else if (!::strncmp(version.c_str(), "1.4", 3)) { + mFormat = FV_1_4_n; + ASSIMP_LOG_DEBUG("Collada schema version is 1.4.n"); + } else if (!::strncmp(version.c_str(), "1.3", 3)) { + mFormat = FV_1_3_n; + ASSIMP_LOG_DEBUG("Collada schema version is 1.3.n"); + } + } + ReadStructure(node); + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the structure of the file +void ColladaParser::ReadStructure(XmlNode &node) { + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "asset") { + ReadAssetInfo(currentNode); + } else if (currentName == "library_animations") { + ReadAnimationLibrary(currentNode); + } else if (currentName == "library_animation_clips") { + ReadAnimationClipLibrary(currentNode); + } else if (currentName == "library_controllers") { + ReadControllerLibrary(currentNode); + } else if (currentName == "library_images") { + ReadImageLibrary(currentNode); + } else if (currentName == "library_materials") { + ReadMaterialLibrary(currentNode); + } else if (currentName == "library_effects") { + ReadEffectLibrary(currentNode); + } else if (currentName == "library_geometries") { + ReadGeometryLibrary(currentNode); + } else if (currentName == "library_visual_scenes") { + ReadSceneLibrary(currentNode); + } else if (currentName == "library_lights") { + ReadLightLibrary(currentNode); + } else if (currentName == "library_cameras") { + ReadCameraLibrary(currentNode); + } else if (currentName == "library_nodes") { + ReadSceneNode(currentNode, nullptr); /* some hacking to reuse this piece of code */ + } else if (currentName == "scene") { + ReadScene(currentNode); + } + } + + PostProcessRootAnimations(); + PostProcessControllers(); +} + +// ------------------------------------------------------------------------------------------------ +// Reads asset information such as coordinate system information and legal blah +void ColladaParser::ReadAssetInfo(XmlNode &node) { + if (node.empty()) { + return; + } + + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "unit") { + mUnitSize = 1.f; + std::string tUnitSizeString; + if (XmlParser::getStdStrAttribute(currentNode, "meter", tUnitSizeString)) { + try { + fast_atoreal_move<ai_real>(tUnitSizeString.data(), mUnitSize); + } catch (const DeadlyImportError& die) { + std::string warning("Collada: Failed to parse meter parameter to real number. Exception:\n"); + warning.append(die.what()); + ASSIMP_LOG_WARN(warning.data()); + } + } + } else if (currentName == "up_axis") { + std::string v; + if (!XmlParser::getValueAsString(currentNode, v)) { + continue; + } + if (v == "X_UP") { + mUpDirection = UP_X; + } else if (v == "Z_UP") { + mUpDirection = UP_Z; + } else { + mUpDirection = UP_Y; + } + } else if (currentName == "contributor") { + for (XmlNode currentChildNode : currentNode.children()) { + ReadMetaDataItem(currentChildNode, mAssetMetaData); + } + } else { + ReadMetaDataItem(currentNode, mAssetMetaData); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads a single string metadata item +void ColladaParser::ReadMetaDataItem(XmlNode &node, StringMetaData &metadata) { + const Collada::MetaKeyPairVector &key_renaming = GetColladaAssimpMetaKeysCamelCase(); + const std::string name = node.name(); + if (name.empty()) { + return; + } + + std::string v; + if (!XmlParser::getValueAsString(node, v)) { + return; + } + + v = ai_trim(v); + aiString aistr; + aistr.Set(v); + + std::string camel_key_str(name); + ToCamelCase(camel_key_str); + + size_t found_index; + if (FindCommonKey(camel_key_str, key_renaming, found_index)) { + metadata.emplace(key_renaming[found_index].second, aistr); + } else { + metadata.emplace(camel_key_str, aistr); + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the animation clips +void ColladaParser::ReadAnimationClipLibrary(XmlNode &node) { + if (node.empty()) { + return; + } + + std::string animName; + if (!XmlParser::getStdStrAttribute(node, "name", animName)) { + if (!XmlParser::getStdStrAttribute(node, "id", animName)) { + animName = std::string("animation_") + ai_to_string(mAnimationClipLibrary.size()); + } + } + + std::pair<std::string, std::vector<std::string>> clip; + clip.first = animName; + + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "instance_animation") { + std::string url; + readUrlAttribute(currentNode, url); + clip.second.push_back(url); + } + + if (clip.second.size() > 0) { + mAnimationClipLibrary.push_back(clip); + } + } +} + +void ColladaParser::PostProcessControllers() { + std::string meshId; + for (auto &it : mControllerLibrary) { + meshId = it.second.mMeshId; + if (meshId.empty()) { + continue; + } + + ControllerLibrary::iterator findItr = mControllerLibrary.find(meshId); + while (findItr != mControllerLibrary.end()) { + meshId = findItr->second.mMeshId; + findItr = mControllerLibrary.find(meshId); + } + + it.second.mMeshId = meshId; + } +} + +// ------------------------------------------------------------------------------------------------ +// Re-build animations from animation clip library, if present, otherwise combine single-channel animations +void ColladaParser::PostProcessRootAnimations() { + if (mAnimationClipLibrary.empty()) { + mAnims.CombineSingleChannelAnimations(); + return; + } + + Animation temp; + for (auto &it : mAnimationClipLibrary) { + std::string clipName = it.first; + + Animation *clip = new Animation(); + clip->mName = clipName; + + temp.mSubAnims.push_back(clip); + + for (const std::string &animationID : it.second) { + AnimationLibrary::iterator animation = mAnimationLibrary.find(animationID); + + if (animation != mAnimationLibrary.end()) { + Animation *pSourceAnimation = animation->second; + pSourceAnimation->CollectChannelsRecursively(clip->mChannels); + } + } + } + + mAnims = temp; + + // Ensure no double deletes. + temp.mSubAnims.clear(); +} + +// ------------------------------------------------------------------------------------------------ +// Reads the animation library +void ColladaParser::ReadAnimationLibrary(XmlNode &node) { + if (node.empty()) { + return; + } + + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "animation") { + ReadAnimation(currentNode, &mAnims); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads an animation into the given parent structure +void ColladaParser::ReadAnimation(XmlNode &node, Collada::Animation *pParent) { + if (node.empty()) { + return; + } + + // an <animation> element may be a container for grouping sub-elements or an animation channel + // this is the channel collection by ID, in case it has channels + using ChannelMap = std::map<std::string, AnimationChannel>; + ChannelMap channels; + // this is the anim container in case we're a container + Animation *anim = nullptr; + + // optional name given as an attribute + std::string animName; + if (!XmlParser::getStdStrAttribute(node, "name", animName)) { + animName = "animation"; + } + + std::string animID; + pugi::xml_attribute idAttr = node.attribute("id"); + if (idAttr) { + animID = idAttr.as_string(); + } + + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "animation") { + if (!anim) { + anim = new Animation; + anim->mName = animName; + pParent->mSubAnims.push_back(anim); + } + + // recurse into the sub-element + ReadAnimation(currentNode, anim); + } else if (currentName == "source") { + ReadSource(currentNode); + } else if (currentName == "sampler") { + std::string id; + if (XmlParser::getStdStrAttribute(currentNode, "id", id)) { + // have it read into a channel + ChannelMap::iterator newChannel = channels.insert(std::make_pair(id, AnimationChannel())).first; + ReadAnimationSampler(currentNode, newChannel->second); + } + } else if (currentName == "channel") { + std::string source_name, target; + XmlParser::getStdStrAttribute(currentNode, "source", source_name); + XmlParser::getStdStrAttribute(currentNode, "target", target); + if (source_name[0] == '#') { + source_name = source_name.substr(1, source_name.size() - 1); + } + ChannelMap::iterator cit = channels.find(source_name); + if (cit != channels.end()) { + cit->second.mTarget = target; + } + } + } + + // it turned out to have channels - add them + if (!channels.empty()) { + if (nullptr == anim) { + anim = new Animation; + anim->mName = animName; + pParent->mSubAnims.push_back(anim); + } + + for (const auto &channel : channels) { + anim->mChannels.push_back(channel.second); + } + + if (idAttr) { + mAnimationLibrary[animID] = anim; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads an animation sampler into the given anim channel +void ColladaParser::ReadAnimationSampler(XmlNode &node, Collada::AnimationChannel &pChannel) { + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "input") { + if (XmlParser::hasAttribute(currentNode, "semantic")) { + std::string semantic, sourceAttr; + XmlParser::getStdStrAttribute(currentNode, "semantic", semantic); + if (XmlParser::hasAttribute(currentNode, "source")) { + XmlParser::getStdStrAttribute(currentNode, "source", sourceAttr); + const char *source = sourceAttr.c_str(); + if (source[0] != '#') { + throw DeadlyImportError("Unsupported URL format"); + } + source++; + + if (semantic == "INPUT") { + pChannel.mSourceTimes = source; + } else if (semantic == "OUTPUT") { + pChannel.mSourceValues = source; + } else if (semantic == "IN_TANGENT") { + pChannel.mInTanValues = source; + } else if (semantic == "OUT_TANGENT") { + pChannel.mOutTanValues = source; + } else if (semantic == "INTERPOLATION") { + pChannel.mInterpolationValues = source; + } + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the skeleton controller library +void ColladaParser::ReadControllerLibrary(XmlNode &node) { + if (node.empty()) { + return; + } + + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName != "controller") { + continue; + } + std::string id; + if (XmlParser::getStdStrAttribute(currentNode, "id", id)) { + mControllerLibrary[id] = Controller(); + ReadController(currentNode, mControllerLibrary[id]); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads a controller into the given mesh structure +void ColladaParser::ReadController(XmlNode &node, Collada::Controller &controller) { + // initial values + controller.mType = Skin; + controller.mMethod = Normalized; + + XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode); + XmlNode currentNode; + while (xmlIt.getNext(currentNode)) { + const std::string ¤tName = currentNode.name(); + if (currentName == "morph") { + controller.mType = Morph; + controller.mMeshId = currentNode.attribute("source").as_string(); + int methodIndex = currentNode.attribute("method").as_int(); + if (methodIndex > 0) { + std::string method; + XmlParser::getValueAsString(currentNode, method); + + if (method == "RELATIVE") { + controller.mMethod = Relative; + } + } + } else if (currentName == "skin") { + std::string id; + if (XmlParser::getStdStrAttribute(currentNode, "source", id)) { + controller.mMeshId = id.substr(1, id.size() - 1); + } + } else if (currentName == "bind_shape_matrix") { + std::string v; + XmlParser::getValueAsString(currentNode, v); + const char *content = v.c_str(); + for (unsigned int a = 0; a < 16; a++) { + SkipSpacesAndLineEnd(&content); + // read a number + content = fast_atoreal_move<ai_real>(content, controller.mBindShapeMatrix[a]); + // skip whitespace after it + SkipSpacesAndLineEnd(&content); + } + } else if (currentName == "source") { + ReadSource(currentNode); + } else if (currentName == "joints") { + ReadControllerJoints(currentNode, controller); + } else if (currentName == "vertex_weights") { + ReadControllerWeights(currentNode, controller); + } else if (currentName == "targets") { + for (XmlNode currentChildNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) { + const std::string ¤tChildName = currentChildNode.name(); + if (currentChildName == "input") { + const char *semantics = currentChildNode.attribute("semantic").as_string(); + const char *source = currentChildNode.attribute("source").as_string(); + if (strcmp(semantics, "MORPH_TARGET") == 0) { + controller.mMorphTarget = source + 1; + } else if (strcmp(semantics, "MORPH_WEIGHT") == 0) { + controller.mMorphWeight = source + 1; + } + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the joint definitions for the given controller +void ColladaParser::ReadControllerJoints(XmlNode &node, Collada::Controller &pController) { + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "input") { + const char *attrSemantic = currentNode.attribute("semantic").as_string(); + const char *attrSource = currentNode.attribute("source").as_string(); + if (attrSource[0] != '#') { + throw DeadlyImportError("Unsupported URL format in \"", attrSource, "\" in source attribute of <joints> data <input> element"); + } + ++attrSource; + // parse source URL to corresponding source + if (strcmp(attrSemantic, "JOINT") == 0) { + pController.mJointNameSource = attrSource; + } else if (strcmp(attrSemantic, "INV_BIND_MATRIX") == 0) { + pController.mJointOffsetMatrixSource = attrSource; + } else { + throw DeadlyImportError("Unknown semantic \"", attrSemantic, "\" in <joints> data <input> element"); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the joint weights for the given controller +void ColladaParser::ReadControllerWeights(XmlNode &node, Collada::Controller &pController) { + // Read vertex count from attributes and resize the array accordingly + int vertexCount = 0; + XmlParser::getIntAttribute(node, "count", vertexCount); + pController.mWeightCounts.resize(vertexCount); + + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "input") { + InputChannel channel; + + const char *attrSemantic = currentNode.attribute("semantic").as_string(); + const char *attrSource = currentNode.attribute("source").as_string(); + channel.mOffset = currentNode.attribute("offset").as_int(); + + // local URLS always start with a '#'. We don't support global URLs + if (attrSource[0] != '#') { + throw DeadlyImportError("Unsupported URL format in \"", attrSource, "\" in source attribute of <vertex_weights> data <input> element"); + } + channel.mAccessor = attrSource + 1; + + // parse source URL to corresponding source + if (strcmp(attrSemantic, "JOINT") == 0) { + pController.mWeightInputJoints = channel; + } else if (strcmp(attrSemantic, "WEIGHT") == 0) { + pController.mWeightInputWeights = channel; + } else { + throw DeadlyImportError("Unknown semantic \"", attrSemantic, "\" in <vertex_weights> data <input> element"); + } + } else if (currentName == "vcount" && vertexCount > 0) { + const char *text = currentNode.text().as_string(); + size_t numWeights = 0; + for (std::vector<size_t>::iterator it = pController.mWeightCounts.begin(); it != pController.mWeightCounts.end(); ++it) { + if (*text == 0) { + throw DeadlyImportError("Out of data while reading <vcount>"); + } + + *it = strtoul10(text, &text); + numWeights += *it; + SkipSpacesAndLineEnd(&text); + } + // reserve weight count + pController.mWeights.resize(numWeights); + } else if (currentName == "v" && vertexCount > 0) { + // read JointIndex - WeightIndex pairs + std::string stdText; + XmlParser::getValueAsString(currentNode, stdText); + const char *text = stdText.c_str(); + for (std::vector<std::pair<size_t, size_t>>::iterator it = pController.mWeights.begin(); it != pController.mWeights.end(); ++it) { + if (text == 0) { + throw DeadlyImportError("Out of data while reading <vertex_weights>"); + } + it->first = strtoul10(text, &text); + SkipSpacesAndLineEnd(&text); + if (*text == 0) { + throw DeadlyImportError("Out of data while reading <vertex_weights>"); + } + it->second = strtoul10(text, &text); + SkipSpacesAndLineEnd(&text); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the image library contents +void ColladaParser::ReadImageLibrary(XmlNode &node) { + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "image") { + std::string id; + if (XmlParser::getStdStrAttribute(currentNode, "id", id)) { + mImageLibrary[id] = Image(); + // read on from there + ReadImage(currentNode, mImageLibrary[id]); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads an image entry into the given image +void ColladaParser::ReadImage(XmlNode &node, Collada::Image &pImage) { + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == "image") { + // Ignore + continue; + } else if (currentName == "init_from") { + if (mFormat == FV_1_4_n) { + // FIX: C4D exporter writes empty <init_from/> tags + if (!currentNode.empty()) { + // element content is filename - hopefully + const char *sz = currentNode.text().as_string(); + if (nullptr != sz) { + aiString filepath(sz); + UriDecodePath(filepath); + pImage.mFileName = filepath.C_Str(); + } + } + if (!pImage.mFileName.length()) { + pImage.mFileName = "unknown_texture"; + } + } + } else if (mFormat == FV_1_5_n) { + std::string value; + XmlNode refChild = currentNode.child("ref"); + XmlNode hexChild = currentNode.child("hex"); + if (refChild) { + // element content is filename - hopefully + if (XmlParser::getValueAsString(refChild, value)) { + aiString filepath(value); + UriDecodePath(filepath); + pImage.mFileName = filepath.C_Str(); + } + } else if (hexChild && !pImage.mFileName.length()) { + // embedded image. get format + pImage.mEmbeddedFormat = hexChild.attribute("format").as_string(); + if (pImage.mEmbeddedFormat.empty()) { + ASSIMP_LOG_WARN("Collada: Unknown image file format"); + } + + XmlParser::getValueAsString(hexChild, value); + const char *data = value.c_str(); + // hexadecimal-encoded binary octets. First of all, find the + // required buffer size to reserve enough storage. + const char *cur = data; + while (!IsSpaceOrNewLine(*cur)) { + ++cur; + } + + const unsigned int size = (unsigned int)(cur - data) * 2; + pImage.mImageData.resize(size); + for (unsigned int i = 0; i < size; ++i) { + pImage.mImageData[i] = HexOctetToDecimal(data + (i << 1)); + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the material library +void ColladaParser::ReadMaterialLibrary(XmlNode &node) { + std::map<std::string, int> names; + for (XmlNode ¤tNode : node.children()) { + std::string id = currentNode.attribute("id").as_string(); + std::string name = currentNode.attribute("name").as_string(); + mMaterialLibrary[id] = Material(); + + if (!name.empty()) { + std::map<std::string, int>::iterator it = names.find(name); + if (it != names.end()) { + std::ostringstream strStream; + strStream << ++it->second; + name.append(" " + strStream.str()); + } else { + names[name] = 0; + } + + mMaterialLibrary[id].mName = name; + } + + ReadMaterial(currentNode, mMaterialLibrary[id]); + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the light library +void ColladaParser::ReadLightLibrary(XmlNode &node) { + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "light") { + std::string id; + if (XmlParser::getStdStrAttribute(currentNode, "id", id)) { + ReadLight(currentNode, mLightLibrary[id] = Light()); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the camera library +void ColladaParser::ReadCameraLibrary(XmlNode &node) { + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "camera") { + std::string id; + if (!XmlParser::getStdStrAttribute(currentNode, "id", id)) { + continue; + } + + // create an entry and store it in the library under its ID + Camera &cam = mCameraLibrary[id]; + std::string name; + if (!XmlParser::getStdStrAttribute(currentNode, "name", name)) { + continue; + } + if (!name.empty()) { + cam.mName = name; + } + ReadCamera(currentNode, cam); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads a material entry into the given material +void ColladaParser::ReadMaterial(XmlNode &node, Collada::Material &pMaterial) { + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "instance_effect") { + std::string url; + readUrlAttribute(currentNode, url); + pMaterial.mEffect = url; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads a light entry into the given light +void ColladaParser::ReadLight(XmlNode &node, Collada::Light &pLight) { + XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode); + XmlNode currentNode; + // TODO: Check the current technique and skip over unsupported extra techniques + + while (xmlIt.getNext(currentNode)) { + const std::string ¤tName = currentNode.name(); + if (currentName == "spot") { + pLight.mType = aiLightSource_SPOT; + } else if (currentName == "ambient") { + pLight.mType = aiLightSource_AMBIENT; + } else if (currentName == "directional") { + pLight.mType = aiLightSource_DIRECTIONAL; + } else if (currentName == "point") { + pLight.mType = aiLightSource_POINT; + } else if (currentName == "color") { + // text content contains 3 floats + std::string v; + XmlParser::getValueAsString(currentNode, v); + const char *content = v.c_str(); + + content = fast_atoreal_move<ai_real>(content, (ai_real &)pLight.mColor.r); + SkipSpacesAndLineEnd(&content); + + content = fast_atoreal_move<ai_real>(content, (ai_real &)pLight.mColor.g); + SkipSpacesAndLineEnd(&content); + + content = fast_atoreal_move<ai_real>(content, (ai_real &)pLight.mColor.b); + SkipSpacesAndLineEnd(&content); + } else if (currentName == "constant_attenuation") { + XmlParser::getValueAsFloat(currentNode, pLight.mAttConstant); + } else if (currentName == "linear_attenuation") { + XmlParser::getValueAsFloat(currentNode, pLight.mAttLinear); + } else if (currentName == "quadratic_attenuation") { + XmlParser::getValueAsFloat(currentNode, pLight.mAttQuadratic); + } else if (currentName == "falloff_angle") { + XmlParser::getValueAsFloat(currentNode, pLight.mFalloffAngle); + } else if (currentName == "falloff_exponent") { + XmlParser::getValueAsFloat(currentNode, pLight.mFalloffExponent); + } + // FCOLLADA extensions + // ------------------------------------------------------- + else if (currentName == "outer_cone") { + XmlParser::getValueAsFloat(currentNode, pLight.mOuterAngle); + } else if (currentName == "penumbra_angle") { // this one is deprecated, now calculated using outer_cone + XmlParser::getValueAsFloat(currentNode, pLight.mPenumbraAngle); + } else if (currentName == "intensity") { + XmlParser::getValueAsFloat(currentNode, pLight.mIntensity); + } + else if (currentName == "falloff") { + XmlParser::getValueAsFloat(currentNode, pLight.mOuterAngle); + } else if (currentName == "hotspot_beam") { + XmlParser::getValueAsFloat(currentNode, pLight.mFalloffAngle); + } + // OpenCOLLADA extensions + // ------------------------------------------------------- + else if (currentName == "decay_falloff") { + XmlParser::getValueAsFloat(currentNode, pLight.mOuterAngle); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads a camera entry into the given light +void ColladaParser::ReadCamera(XmlNode &node, Collada::Camera &camera) { + XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode); + XmlNode currentNode; + while (xmlIt.getNext(currentNode)) { + const std::string ¤tName = currentNode.name(); + if (currentName == "orthographic") { + camera.mOrtho = true; + } else if (currentName == "xfov" || currentName == "xmag") { + XmlParser::getValueAsFloat(currentNode, camera.mHorFov); + } else if (currentName == "yfov" || currentName == "ymag") { + XmlParser::getValueAsFloat(currentNode, camera.mVerFov); + } else if (currentName == "aspect_ratio") { + XmlParser::getValueAsFloat(currentNode, camera.mAspect); + } else if (currentName == "znear") { + XmlParser::getValueAsFloat(currentNode, camera.mZNear); + } else if (currentName == "zfar") { + XmlParser::getValueAsFloat(currentNode, camera.mZFar); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the effect library +void ColladaParser::ReadEffectLibrary(XmlNode &node) { + if (node.empty()) { + return; + } + + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "effect") { + // read ID. Do I have to repeat my ranting about "optional" attributes? + std::string id; + XmlParser::getStdStrAttribute(currentNode, "id", id); + + // create an entry and store it in the library under its ID + mEffectLibrary[id] = Effect(); + + // read on from there + ReadEffect(currentNode, mEffectLibrary[id]); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads an effect entry into the given effect +void ColladaParser::ReadEffect(XmlNode &node, Collada::Effect &pEffect) { + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "profile_COMMON") { + ReadEffectProfileCommon(currentNode, pEffect); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads an COMMON effect profile +void ColladaParser::ReadEffectProfileCommon(XmlNode &node, Collada::Effect &pEffect) { + XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode); + XmlNode currentNode; + while (xmlIt.getNext(currentNode)) { + const std::string currentName = currentNode.name(); + if (currentName == "newparam") { + // save ID + std::string sid = currentNode.attribute("sid").as_string(); + pEffect.mParams[sid] = EffectParam(); + ReadEffectParam(currentNode, pEffect.mParams[sid]); + } else if (currentName == "technique" || currentName == "extra") { + // just syntactic sugar + } else if (mFormat == FV_1_4_n && currentName == "image") { + // read ID. Another entry which is "optional" by design but obligatory in reality + std::string id = currentNode.attribute("id").as_string(); + + // create an entry and store it in the library under its ID + mImageLibrary[id] = Image(); + + // read on from there + ReadImage(currentNode, mImageLibrary[id]); + } else if (currentName == "phong") + pEffect.mShadeType = Shade_Phong; + else if (currentName == "constant") + pEffect.mShadeType = Shade_Constant; + else if (currentName == "lambert") + pEffect.mShadeType = Shade_Lambert; + else if (currentName == "blinn") + pEffect.mShadeType = Shade_Blinn; + + /* Color + texture properties */ + else if (currentName == "emission") + ReadEffectColor(currentNode, pEffect.mEmissive, pEffect.mTexEmissive); + else if (currentName == "ambient") + ReadEffectColor(currentNode, pEffect.mAmbient, pEffect.mTexAmbient); + else if (currentName == "diffuse") + ReadEffectColor(currentNode, pEffect.mDiffuse, pEffect.mTexDiffuse); + else if (currentName == "specular") + ReadEffectColor(currentNode, pEffect.mSpecular, pEffect.mTexSpecular); + else if (currentName == "reflective") { + ReadEffectColor(currentNode, pEffect.mReflective, pEffect.mTexReflective); + } else if (currentName == "transparent") { + pEffect.mHasTransparency = true; + const char *opaque = currentNode.attribute("opaque").as_string(); + //const char *opaque = mReader->getAttributeValueSafe("opaque"); + + if (::strcmp(opaque, "RGB_ZERO") == 0 || ::strcmp(opaque, "RGB_ONE") == 0) { + pEffect.mRGBTransparency = true; + } + + // In RGB_ZERO mode, the transparency is interpreted in reverse, go figure... + if (::strcmp(opaque, "RGB_ZERO") == 0 || ::strcmp(opaque, "A_ZERO") == 0) { + pEffect.mInvertTransparency = true; + } + + ReadEffectColor(currentNode, pEffect.mTransparent, pEffect.mTexTransparent); + } else if (currentName == "shininess") + ReadEffectFloat(currentNode, pEffect.mShininess); + else if (currentName == "reflectivity") + ReadEffectFloat(currentNode, pEffect.mReflectivity); + + /* Single scalar properties */ + else if (currentName == "transparency") + ReadEffectFloat(currentNode, pEffect.mTransparency); + else if (currentName == "index_of_refraction") + ReadEffectFloat(currentNode, pEffect.mRefractIndex); + + // GOOGLEEARTH/OKINO extensions + // ------------------------------------------------------- + else if (currentName == "double_sided") + XmlParser::getValueAsBool(currentNode, pEffect.mDoubleSided); + + // FCOLLADA extensions + // ------------------------------------------------------- + else if (currentName == "bump") { + aiColor4D dummy; + ReadEffectColor(currentNode, dummy, pEffect.mTexBump); + } + + // MAX3D extensions + // ------------------------------------------------------- + else if (currentName == "wireframe") { + XmlParser::getValueAsBool(currentNode, pEffect.mWireframe); + } else if (currentName == "faceted") { + XmlParser::getValueAsBool(currentNode, pEffect.mFaceted); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Read texture wrapping + UV transform settings from a profile==Maya chunk +void ColladaParser::ReadSamplerProperties(XmlNode &node, Sampler &out) { + if (node.empty()) { + return; + } + + XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode); + XmlNode currentNode; + while (xmlIt.getNext(currentNode)) { + const std::string ¤tName = currentNode.name(); + // MAYA extensions + // ------------------------------------------------------- + if (currentName == "wrapU") { + XmlParser::getValueAsBool(currentNode, out.mWrapU); + } else if (currentName == "wrapV") { + XmlParser::getValueAsBool(currentNode, out.mWrapV); + } else if (currentName == "mirrorU") { + XmlParser::getValueAsBool(currentNode, out.mMirrorU); + } else if (currentName == "mirrorV") { + XmlParser::getValueAsBool(currentNode, out.mMirrorV); + } else if (currentName == "repeatU") { + XmlParser::getValueAsFloat(currentNode, out.mTransform.mScaling.x); + } else if (currentName == "repeatV") { + XmlParser::getValueAsFloat(currentNode, out.mTransform.mScaling.y); + } else if (currentName == "offsetU") { + XmlParser::getValueAsFloat(currentNode, out.mTransform.mTranslation.x); + } else if (currentName == "offsetV") { + XmlParser::getValueAsFloat(currentNode, out.mTransform.mTranslation.y); + } else if (currentName == "rotateUV") { + XmlParser::getValueAsFloat(currentNode, out.mTransform.mRotation); + } else if (currentName == "blend_mode") { + std::string v; + XmlParser::getValueAsString(currentNode, v); + const char *sz = v.c_str(); + // http://www.feelingsoftware.com/content/view/55/72/lang,en/ + // NONE, OVER, IN, OUT, ADD, SUBTRACT, MULTIPLY, DIFFERENCE, LIGHTEN, DARKEN, SATURATE, DESATURATE and ILLUMINATE + if (0 == ASSIMP_strincmp(sz, "ADD", 3)) + out.mOp = aiTextureOp_Add; + else if (0 == ASSIMP_strincmp(sz, "SUBTRACT", 8)) + out.mOp = aiTextureOp_Subtract; + else if (0 == ASSIMP_strincmp(sz, "MULTIPLY", 8)) + out.mOp = aiTextureOp_Multiply; + else { + ASSIMP_LOG_WARN("Collada: Unsupported MAYA texture blend mode"); + } + } + // OKINO extensions + // ------------------------------------------------------- + else if (currentName == "weighting") { + XmlParser::getValueAsFloat(currentNode, out.mWeighting); + } else if (currentName == "mix_with_previous_layer") { + XmlParser::getValueAsFloat(currentNode, out.mMixWithPrevious); + } + // MAX3D extensions + // ------------------------------------------------------- + else if (currentName == "amount") { + XmlParser::getValueAsFloat(currentNode, out.mWeighting); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads an effect entry containing a color or a texture defining that color +void ColladaParser::ReadEffectColor(XmlNode &node, aiColor4D &pColor, Sampler &pSampler) { + if (node.empty()) { + return; + } + + XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode); + XmlNode currentNode; + while (xmlIt.getNext(currentNode)) { + const std::string ¤tName = currentNode.name(); + if (currentName == "color") { + // text content contains 4 floats + std::string v; + XmlParser::getValueAsString(currentNode, v); + const char *content = v.c_str(); + + content = fast_atoreal_move<ai_real>(content, (ai_real &)pColor.r); + SkipSpacesAndLineEnd(&content); + + content = fast_atoreal_move<ai_real>(content, (ai_real &)pColor.g); + SkipSpacesAndLineEnd(&content); + + content = fast_atoreal_move<ai_real>(content, (ai_real &)pColor.b); + SkipSpacesAndLineEnd(&content); + + content = fast_atoreal_move<ai_real>(content, (ai_real &)pColor.a); + SkipSpacesAndLineEnd(&content); + } else if (currentName == "texture") { + // get name of source texture/sampler + XmlParser::getStdStrAttribute(currentNode, "texture", pSampler.mName); + + // get name of UV source channel. Specification demands it to be there, but some exporters + // don't write it. It will be the default UV channel in case it's missing. + XmlParser::getStdStrAttribute(currentNode, "texcoord", pSampler.mUVChannel); + + // as we've read texture, the color needs to be 1,1,1,1 + pColor = aiColor4D(1.f, 1.f, 1.f, 1.f); + } else if (currentName == "technique") { + std::string profile; + XmlParser::getStdStrAttribute(currentNode, "profile", profile); + + // Some extensions are quite useful ... ReadSamplerProperties processes + // several extensions in MAYA, OKINO and MAX3D profiles. + if (!::strcmp(profile.c_str(), "MAYA") || !::strcmp(profile.c_str(), "MAX3D") || !::strcmp(profile.c_str(), "OKINO")) { + // get more information on this sampler + ReadSamplerProperties(currentNode, pSampler); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads an effect entry containing a float +void ColladaParser::ReadEffectFloat(XmlNode &node, ai_real &pFloat) { + pFloat = 0.f; + XmlNode floatNode = node.child("float"); + if (floatNode.empty()) { + return; + } + XmlParser::getValueAsFloat(floatNode, pFloat); +} + +// ------------------------------------------------------------------------------------------------ +// Reads an effect parameter specification of any kind +void ColladaParser::ReadEffectParam(XmlNode &node, Collada::EffectParam &pParam) { + if (node.empty()) { + return; + } + + XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode); + XmlNode currentNode; + while (xmlIt.getNext(currentNode)) { + const std::string ¤tName = currentNode.name(); + if (currentName == "surface") { + // image ID given inside <init_from> tags + XmlNode initNode = currentNode.child("init_from"); + if (initNode) { + std::string v; + XmlParser::getValueAsString(initNode, v); + pParam.mType = Param_Surface; + pParam.mReference = v.c_str(); + } + } else if (currentName == "sampler2D" && (FV_1_4_n == mFormat || FV_1_3_n == mFormat)) { + // surface ID is given inside <source> tags + const char *content = currentNode.value(); + pParam.mType = Param_Sampler; + pParam.mReference = content; + } else if (currentName == "sampler2D") { + // surface ID is given inside <instance_image> tags + std::string url; + XmlParser::getStdStrAttribute(currentNode, "url", url); + if (url[0] != '#') { + throw DeadlyImportError("Unsupported URL format in instance_image"); + } + pParam.mType = Param_Sampler; + pParam.mReference = url.c_str() + 1; + } else if (currentName == "source") { + const char *source = currentNode.child_value(); + if (nullptr != source) { + pParam.mReference = source; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the geometry library contents +void ColladaParser::ReadGeometryLibrary(XmlNode &node) { + if (node.empty()) { + return; + } + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "geometry") { + // read ID. Another entry which is "optional" by design but obligatory in reality + + std::string id; + XmlParser::getStdStrAttribute(currentNode, "id", id); + // create a mesh and store it in the library under its (resolved) ID + // Skip and warn if ID is not unique + if (mMeshLibrary.find(id) == mMeshLibrary.cend()) { + std::unique_ptr<Mesh> mesh(new Mesh(id)); + + XmlParser::getStdStrAttribute(currentNode, "name", mesh->mName); + + // read on from there + ReadGeometry(currentNode, *mesh); + // Read successfully, add to library + mMeshLibrary.insert({ id, mesh.release() }); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads a geometry from the geometry library. +void ColladaParser::ReadGeometry(XmlNode &node, Collada::Mesh &pMesh) { + if (node.empty()) { + return; + } + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "mesh") { + ReadMesh(currentNode, pMesh); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads a mesh from the geometry library +void ColladaParser::ReadMesh(XmlNode &node, Mesh &pMesh) { + if (node.empty()) { + return; + } + + XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode); + XmlNode currentNode; + while (xmlIt.getNext(currentNode)) { + const std::string ¤tName = currentNode.name(); + if (currentName == "source") { + ReadSource(currentNode); + } else if (currentName == "vertices") { + ReadVertexData(currentNode, pMesh); + } else if (currentName == "triangles" || currentName == "lines" || currentName == "linestrips" || + currentName == "polygons" || currentName == "polylist" || currentName == "trifans" || + currentName == "tristrips") { + ReadIndexData(currentNode, pMesh); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads a source element +void ColladaParser::ReadSource(XmlNode &node) { + if (node.empty()) { + return; + } + + std::string sourceID; + XmlParser::getStdStrAttribute(node, "id", sourceID); + XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode); + XmlNode currentNode; + while (xmlIt.getNext(currentNode)) { + const std::string ¤tName = currentNode.name(); + if (currentName == "float_array" || currentName == "IDREF_array" || currentName == "Name_array") { + ReadDataArray(currentNode); + } else if (currentName == "technique_common") { + XmlNode technique = currentNode.child("accessor"); + if (!technique.empty()) { + ReadAccessor(technique, sourceID); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads a data array holding a number of floats, and stores it in the global library +void ColladaParser::ReadDataArray(XmlNode &node) { + std::string name = node.name(); + bool isStringArray = (name == "IDREF_array" || name == "Name_array"); + + // read attributes + std::string id; + XmlParser::getStdStrAttribute(node, "id", id); + unsigned int count = 0; + XmlParser::getUIntAttribute(node, "count", count); + std::string v; + XmlParser::getValueAsString(node, v); + v = ai_trim(v); + const char *content = v.c_str(); + + // read values and store inside an array in the data library + mDataLibrary[id] = Data(); + Data &data = mDataLibrary[id]; + data.mIsStringArray = isStringArray; + + // some exporters write empty data arrays, but we need to conserve them anyways because others might reference them + if (content) { + if (isStringArray) { + data.mStrings.reserve(count); + std::string s; + + for (unsigned int a = 0; a < count; a++) { + if (*content == 0) { + throw DeadlyImportError("Expected more values while reading IDREF_array contents."); + } + + s.clear(); + while (!IsSpaceOrNewLine(*content)) + s += *content++; + data.mStrings.push_back(s); + + SkipSpacesAndLineEnd(&content); + } + } else { + data.mValues.reserve(count); + + for (unsigned int a = 0; a < count; a++) { + if (*content == 0) { + throw DeadlyImportError("Expected more values while reading float_array contents."); + } + + // read a number + ai_real value; + content = fast_atoreal_move<ai_real>(content, value); + data.mValues.push_back(value); + // skip whitespace after it + SkipSpacesAndLineEnd(&content); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads an accessor and stores it in the global library +void ColladaParser::ReadAccessor(XmlNode &node, const std::string &pID) { + // read accessor attributes + std::string source; + XmlParser::getStdStrAttribute(node, "source", source); + if (source[0] != '#') { + throw DeadlyImportError("Unknown reference format in url \"", source, "\" in source attribute of <accessor> element."); + } + int count = 0; + XmlParser::getIntAttribute(node, "count", count); + + unsigned int offset = 0; + if (XmlParser::hasAttribute(node, "offset")) { + XmlParser::getUIntAttribute(node, "offset", offset); + } + unsigned int stride = 1; + if (XmlParser::hasAttribute(node, "stride")) { + XmlParser::getUIntAttribute(node, "stride", stride); + } + // store in the library under the given ID + mAccessorLibrary[pID] = Accessor(); + Accessor &acc = mAccessorLibrary[pID]; + acc.mCount = count; + acc.mOffset = offset; + acc.mStride = stride; + acc.mSource = source.c_str() + 1; // ignore the leading '#' + acc.mSize = 0; // gets incremented with every param + + XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode); + XmlNode currentNode; + while (xmlIt.getNext(currentNode)) { + const std::string ¤tName = currentNode.name(); + if (currentName == "param") { + // read data param + std::string name; + if (XmlParser::hasAttribute(currentNode, "name")) { + XmlParser::getStdStrAttribute(currentNode, "name", name); + + // analyse for common type components and store it's sub-offset in the corresponding field + + // Cartesian coordinates + if (name == "X") + acc.mSubOffset[0] = acc.mParams.size(); + else if (name == "Y") + acc.mSubOffset[1] = acc.mParams.size(); + else if (name == "Z") + acc.mSubOffset[2] = acc.mParams.size(); + + /* RGBA colors */ + else if (name == "R") + acc.mSubOffset[0] = acc.mParams.size(); + else if (name == "G") + acc.mSubOffset[1] = acc.mParams.size(); + else if (name == "B") + acc.mSubOffset[2] = acc.mParams.size(); + else if (name == "A") + acc.mSubOffset[3] = acc.mParams.size(); + + /* UVWQ (STPQ) texture coordinates */ + else if (name == "S") + acc.mSubOffset[0] = acc.mParams.size(); + else if (name == "T") + acc.mSubOffset[1] = acc.mParams.size(); + else if (name == "P") + acc.mSubOffset[2] = acc.mParams.size(); + /* Generic extra data, interpreted as UV data, too*/ + else if (name == "U") + acc.mSubOffset[0] = acc.mParams.size(); + else if (name == "V") + acc.mSubOffset[1] = acc.mParams.size(); + } + if (XmlParser::hasAttribute(currentNode, "type")) { + // read data type + // TODO: (thom) I don't have a spec here at work. Check if there are other multi-value types + // which should be tested for here. + std::string type; + + XmlParser::getStdStrAttribute(currentNode, "type", type); + if (type == "float4x4") + acc.mSize += 16; + else + acc.mSize += 1; + } + + acc.mParams.push_back(name); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads input declarations of per-vertex mesh data into the given mesh +void ColladaParser::ReadVertexData(XmlNode &node, Mesh &pMesh) { + // extract the ID of the <vertices> element. Not that we care, but to catch strange referencing schemes we should warn about + XmlParser::getStdStrAttribute(node, "id", pMesh.mVertexID); + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "input") { + ReadInputChannel(currentNode, pMesh.mPerVertexData); + } else { + throw DeadlyImportError("Unexpected sub element <", currentName, "> in tag <vertices>"); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads input declarations of per-index mesh data into the given mesh +void ColladaParser::ReadIndexData(XmlNode &node, Mesh &pMesh) { + std::vector<size_t> vcount; + std::vector<InputChannel> perIndexData; + + unsigned int numPrimitives = 0; + XmlParser::getUIntAttribute(node, "count", numPrimitives); + // read primitive count from the attribute + //int attrCount = GetAttribute("count"); + //size_t numPrimitives = (size_t)mReader->getAttributeValueAsInt(attrCount); + // some mesh types (e.g. tristrips) don't specify primitive count upfront, + // so we need to sum up the actual number of primitives while we read the <p>-tags + size_t actualPrimitives = 0; + SubMesh subgroup; + if (XmlParser::hasAttribute(node, "material")) { + XmlParser::getStdStrAttribute(node, "material", subgroup.mMaterial); + } + + // distinguish between polys and triangles + std::string elementName = node.name(); + PrimitiveType primType = Prim_Invalid; + if (elementName == "lines") + primType = Prim_Lines; + else if (elementName == "linestrips") + primType = Prim_LineStrip; + else if (elementName == "polygons") + primType = Prim_Polygon; + else if (elementName == "polylist") + primType = Prim_Polylist; + else if (elementName == "triangles") + primType = Prim_Triangles; + else if (elementName == "trifans") + primType = Prim_TriFans; + else if (elementName == "tristrips") + primType = Prim_TriStrips; + + ai_assert(primType != Prim_Invalid); + + // also a number of <input> elements, but in addition a <p> primitive collection and probably index counts for all primitives + XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode); + XmlNode currentNode; + while (xmlIt.getNext(currentNode)) { + const std::string ¤tName = currentNode.name(); + if (currentName == "input") { + ReadInputChannel(currentNode, perIndexData); + } else if (currentName == "vcount") { + if (!currentNode.empty()) { + if (numPrimitives) // It is possible to define a mesh without any primitives + { + // case <polylist> - specifies the number of indices for each polygon + std::string v; + XmlParser::getValueAsString(currentNode, v); + const char *content = v.c_str(); + vcount.reserve(numPrimitives); + for (unsigned int a = 0; a < numPrimitives; a++) { + if (*content == 0) { + throw DeadlyImportError("Expected more values while reading <vcount> contents."); + } + // read a number + vcount.push_back((size_t)strtoul10(content, &content)); + // skip whitespace after it + SkipSpacesAndLineEnd(&content); + } + } + } + } else if (currentName == "p") { + if (!currentNode.empty()) { + // now here the actual fun starts - these are the indices to construct the mesh data from + actualPrimitives += ReadPrimitives(currentNode, pMesh, perIndexData, numPrimitives, vcount, primType); + } + } else if (currentName == "extra") { + // skip + } else if (currentName == "ph") { + // skip + } else { + throw DeadlyImportError("Unexpected sub element <", currentName, "> in tag <", elementName, ">"); + } + } + +#ifdef ASSIMP_BUILD_DEBUG + if (primType != Prim_TriFans && primType != Prim_TriStrips && primType != Prim_LineStrip && + primType != Prim_Lines) { // this is ONLY to workaround a bug in SketchUp 15.3.331 where it writes the wrong 'count' when it writes out the 'lines'. + ai_assert(actualPrimitives == numPrimitives); + } +#endif + + // only when we're done reading all <p> tags (and thus know the final vertex count) can we commit the submesh + subgroup.mNumFaces = actualPrimitives; + pMesh.mSubMeshes.push_back(subgroup); +} + +// ------------------------------------------------------------------------------------------------ +// Reads a single input channel element and stores it in the given array, if valid +void ColladaParser::ReadInputChannel(XmlNode &node, std::vector<InputChannel> &poChannels) { + InputChannel channel; + + // read semantic + std::string semantic; + XmlParser::getStdStrAttribute(node, "semantic", semantic); + channel.mType = GetTypeForSemantic(semantic); + + // read source + std::string source; + XmlParser::getStdStrAttribute(node, "source", source); + if (source[0] != '#') { + throw DeadlyImportError("Unknown reference format in url \"", source, "\" in source attribute of <input> element."); + } + channel.mAccessor = source.c_str() + 1; // skipping the leading #, hopefully the remaining text is the accessor ID only + + // read index offset, if per-index <input> + if (XmlParser::hasAttribute(node, "offset")) { + XmlParser::getUIntAttribute(node, "offset", (unsigned int &)channel.mOffset); + } + + // read set if texture coordinates + if (channel.mType == IT_Texcoord || channel.mType == IT_Color) { + unsigned int attrSet = 0; + if (XmlParser::getUIntAttribute(node, "set", attrSet)) + channel.mIndex = attrSet; + } + + // store, if valid type + if (channel.mType != IT_Invalid) + poChannels.push_back(channel); +} + +// ------------------------------------------------------------------------------------------------ +// Reads a <p> primitive index list and assembles the mesh data into the given mesh +size_t ColladaParser::ReadPrimitives(XmlNode &node, Mesh &pMesh, std::vector<InputChannel> &pPerIndexChannels, + size_t pNumPrimitives, const std::vector<size_t> &pVCount, PrimitiveType pPrimType) { + // determine number of indices coming per vertex + // find the offset index for all per-vertex channels + size_t numOffsets = 1; + size_t perVertexOffset = SIZE_MAX; // invalid value + for (const InputChannel &channel : pPerIndexChannels) { + numOffsets = std::max(numOffsets, channel.mOffset + 1); + if (channel.mType == IT_Vertex) + perVertexOffset = channel.mOffset; + } + + // determine the expected number of indices + size_t expectedPointCount = 0; + switch (pPrimType) { + case Prim_Polylist: { + for (size_t i : pVCount) + expectedPointCount += i; + break; + } + case Prim_Lines: + expectedPointCount = 2 * pNumPrimitives; + break; + case Prim_Triangles: + expectedPointCount = 3 * pNumPrimitives; + break; + default: + // other primitive types don't state the index count upfront... we need to guess + break; + } + + // and read all indices into a temporary array + std::vector<size_t> indices; + if (expectedPointCount > 0) { + indices.reserve(expectedPointCount * numOffsets); + } + + // It is possible to not contain any indices + if (pNumPrimitives > 0) { + std::string v; + XmlParser::getValueAsString(node, v); + const char *content = v.c_str(); + SkipSpacesAndLineEnd(&content); + while (*content != 0) { + // read a value. + // Hack: (thom) Some exporters put negative indices sometimes. We just try to carry on anyways. + int value = std::max(0, strtol10(content, &content)); + indices.push_back(size_t(value)); + // skip whitespace after it + SkipSpacesAndLineEnd(&content); + } + } + + // complain if the index count doesn't fit + if (expectedPointCount > 0 && indices.size() != expectedPointCount * numOffsets) { + if (pPrimType == Prim_Lines) { + // HACK: We just fix this number since SketchUp 15.3.331 writes the wrong 'count' for 'lines' + ReportWarning("Expected different index count in <p> element, %zu instead of %zu.", indices.size(), expectedPointCount * numOffsets); + pNumPrimitives = (indices.size() / numOffsets) / 2; + } else { + throw DeadlyImportError("Expected different index count in <p> element."); + } + } else if (expectedPointCount == 0 && (indices.size() % numOffsets) != 0) { + throw DeadlyImportError("Expected different index count in <p> element."); + } + + // find the data for all sources + for (std::vector<InputChannel>::iterator it = pMesh.mPerVertexData.begin(); it != pMesh.mPerVertexData.end(); ++it) { + InputChannel &input = *it; + if (input.mResolved) { + continue; + } + + // find accessor + input.mResolved = &ResolveLibraryReference(mAccessorLibrary, input.mAccessor); + // resolve accessor's data pointer as well, if necessary + const Accessor *acc = input.mResolved; + if (!acc->mData) { + acc->mData = &ResolveLibraryReference(mDataLibrary, acc->mSource); + } + } + // and the same for the per-index channels + for (std::vector<InputChannel>::iterator it = pPerIndexChannels.begin(); it != pPerIndexChannels.end(); ++it) { + InputChannel &input = *it; + if (input.mResolved) { + continue; + } + + // ignore vertex pointer, it doesn't refer to an accessor + if (input.mType == IT_Vertex) { + // warn if the vertex channel does not refer to the <vertices> element in the same mesh + if (input.mAccessor != pMesh.mVertexID) { + throw DeadlyImportError("Unsupported vertex referencing scheme."); + } + continue; + } + + // find accessor + input.mResolved = &ResolveLibraryReference(mAccessorLibrary, input.mAccessor); + // resolve accessor's data pointer as well, if necessary + const Accessor *acc = input.mResolved; + if (!acc->mData) { + acc->mData = &ResolveLibraryReference(mDataLibrary, acc->mSource); + } + } + + // For continued primitives, the given count does not come all in one <p>, but only one primitive per <p> + size_t numPrimitives = pNumPrimitives; + if (pPrimType == Prim_TriFans || pPrimType == Prim_Polygon) + numPrimitives = 1; + // For continued primitives, the given count is actually the number of <p>'s inside the parent tag + if (pPrimType == Prim_TriStrips) { + size_t numberOfVertices = indices.size() / numOffsets; + numPrimitives = numberOfVertices - 2; + } + if (pPrimType == Prim_LineStrip) { + size_t numberOfVertices = indices.size() / numOffsets; + numPrimitives = numberOfVertices - 1; + } + + pMesh.mFaceSize.reserve(numPrimitives); + pMesh.mFacePosIndices.reserve(indices.size() / numOffsets); + + size_t polylistStartVertex = 0; + for (size_t currentPrimitive = 0; currentPrimitive < numPrimitives; currentPrimitive++) { + // determine number of points for this primitive + size_t numPoints = 0; + switch (pPrimType) { + case Prim_Lines: + numPoints = 2; + for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) + CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + break; + case Prim_LineStrip: + numPoints = 2; + for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) + CopyVertex(currentVertex, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + break; + case Prim_Triangles: + numPoints = 3; + for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) + CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + break; + case Prim_TriStrips: + numPoints = 3; + ReadPrimTriStrips(numOffsets, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + break; + case Prim_Polylist: + numPoints = pVCount[currentPrimitive]; + for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) + CopyVertex(polylistStartVertex + currentVertex, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, 0, indices); + polylistStartVertex += numPoints; + break; + case Prim_TriFans: + case Prim_Polygon: + numPoints = indices.size() / numOffsets; + for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) + CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + break; + default: + // LineStrip is not supported due to expected index unmangling + throw DeadlyImportError("Unsupported primitive type."); + break; + } + + // store the face size to later reconstruct the face from + pMesh.mFaceSize.push_back(numPoints); + } + + // if I ever get my hands on that guy who invented this steaming pile of indirection... + return numPrimitives; +} + +///@note This function won't work correctly if both PerIndex and PerVertex channels have same channels. +///For example if TEXCOORD present in both <vertices> and <polylist> tags this function will create wrong uv coordinates. +///It's not clear from COLLADA documentation is this allowed or not. For now only exporter fixed to avoid such behavior +void ColladaParser::CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset, Mesh &pMesh, + std::vector<InputChannel> &pPerIndexChannels, size_t currentPrimitive, const std::vector<size_t> &indices) { + // calculate the base offset of the vertex whose attributes we ant to copy + size_t baseOffset = currentPrimitive * numOffsets * numPoints + currentVertex * numOffsets; + + // don't overrun the boundaries of the index list + ai_assert((baseOffset + numOffsets - 1) < indices.size()); + + // extract per-vertex channels using the global per-vertex offset + for (std::vector<InputChannel>::iterator it = pMesh.mPerVertexData.begin(); it != pMesh.mPerVertexData.end(); ++it) { + ExtractDataObjectFromChannel(*it, indices[baseOffset + perVertexOffset], pMesh); + } + // and extract per-index channels using there specified offset + for (std::vector<InputChannel>::iterator it = pPerIndexChannels.begin(); it != pPerIndexChannels.end(); ++it) { + ExtractDataObjectFromChannel(*it, indices[baseOffset + it->mOffset], pMesh); + } + + // store the vertex-data index for later assignment of bone vertex weights + pMesh.mFacePosIndices.push_back(indices[baseOffset + perVertexOffset]); +} + +void ColladaParser::ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Mesh &pMesh, std::vector<InputChannel> &pPerIndexChannels, + size_t currentPrimitive, const std::vector<size_t> &indices) { + if (currentPrimitive % 2 != 0) { + //odd tristrip triangles need their indices mangled, to preserve winding direction + CopyVertex(1, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + CopyVertex(0, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + CopyVertex(2, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + } else { //for non tristrips or even tristrip triangles + CopyVertex(0, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + CopyVertex(1, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + CopyVertex(2, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + } +} + +// ------------------------------------------------------------------------------------------------ +// Extracts a single object from an input channel and stores it in the appropriate mesh data array +void ColladaParser::ExtractDataObjectFromChannel(const InputChannel &pInput, size_t pLocalIndex, Mesh &pMesh) { + // ignore vertex referrer - we handle them that separate + if (pInput.mType == IT_Vertex) { + return; + } + + const Accessor &acc = *pInput.mResolved; + if (pLocalIndex >= acc.mCount) { + throw DeadlyImportError("Invalid data index (", pLocalIndex, "/", acc.mCount, ") in primitive specification"); + } + + // get a pointer to the start of the data object referred to by the accessor and the local index + const ai_real *dataObject = &(acc.mData->mValues[0]) + acc.mOffset + pLocalIndex * acc.mStride; + + // assemble according to the accessors component sub-offset list. We don't care, yet, + // what kind of object exactly we're extracting here + ai_real obj[4]; + for (size_t c = 0; c < 4; ++c) { + obj[c] = dataObject[acc.mSubOffset[c]]; + } + + // now we reinterpret it according to the type we're reading here + switch (pInput.mType) { + case IT_Position: // ignore all position streams except 0 - there can be only one position + if (pInput.mIndex == 0) { + pMesh.mPositions.push_back(aiVector3D(obj[0], obj[1], obj[2])); + } else { + ASSIMP_LOG_ERROR("Collada: just one vertex position stream supported"); + } + break; + case IT_Normal: + // pad to current vertex count if necessary + if (pMesh.mNormals.size() < pMesh.mPositions.size() - 1) + pMesh.mNormals.insert(pMesh.mNormals.end(), pMesh.mPositions.size() - pMesh.mNormals.size() - 1, aiVector3D(0, 1, 0)); + + // ignore all normal streams except 0 - there can be only one normal + if (pInput.mIndex == 0) { + pMesh.mNormals.push_back(aiVector3D(obj[0], obj[1], obj[2])); + } else { + ASSIMP_LOG_ERROR("Collada: just one vertex normal stream supported"); + } + break; + case IT_Tangent: + // pad to current vertex count if necessary + if (pMesh.mTangents.size() < pMesh.mPositions.size() - 1) + pMesh.mTangents.insert(pMesh.mTangents.end(), pMesh.mPositions.size() - pMesh.mTangents.size() - 1, aiVector3D(1, 0, 0)); + + // ignore all tangent streams except 0 - there can be only one tangent + if (pInput.mIndex == 0) { + pMesh.mTangents.push_back(aiVector3D(obj[0], obj[1], obj[2])); + } else { + ASSIMP_LOG_ERROR("Collada: just one vertex tangent stream supported"); + } + break; + case IT_Bitangent: + // pad to current vertex count if necessary + if (pMesh.mBitangents.size() < pMesh.mPositions.size() - 1) { + pMesh.mBitangents.insert(pMesh.mBitangents.end(), pMesh.mPositions.size() - pMesh.mBitangents.size() - 1, aiVector3D(0, 0, 1)); + } + + // ignore all bitangent streams except 0 - there can be only one bitangent + if (pInput.mIndex == 0) { + pMesh.mBitangents.push_back(aiVector3D(obj[0], obj[1], obj[2])); + } else { + ASSIMP_LOG_ERROR("Collada: just one vertex bitangent stream supported"); + } + break; + case IT_Texcoord: + // up to 4 texture coord sets are fine, ignore the others + if (pInput.mIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS) { + // pad to current vertex count if necessary + if (pMesh.mTexCoords[pInput.mIndex].size() < pMesh.mPositions.size() - 1) + pMesh.mTexCoords[pInput.mIndex].insert(pMesh.mTexCoords[pInput.mIndex].end(), + pMesh.mPositions.size() - pMesh.mTexCoords[pInput.mIndex].size() - 1, aiVector3D(0, 0, 0)); + + pMesh.mTexCoords[pInput.mIndex].push_back(aiVector3D(obj[0], obj[1], obj[2])); + if (0 != acc.mSubOffset[2] || 0 != acc.mSubOffset[3]) { + pMesh.mNumUVComponents[pInput.mIndex] = 3; + } + } else { + ASSIMP_LOG_ERROR("Collada: too many texture coordinate sets. Skipping."); + } + break; + case IT_Color: + // up to 4 color sets are fine, ignore the others + if (pInput.mIndex < AI_MAX_NUMBER_OF_COLOR_SETS) { + // pad to current vertex count if necessary + if (pMesh.mColors[pInput.mIndex].size() < pMesh.mPositions.size() - 1) + pMesh.mColors[pInput.mIndex].insert(pMesh.mColors[pInput.mIndex].end(), + pMesh.mPositions.size() - pMesh.mColors[pInput.mIndex].size() - 1, aiColor4D(0, 0, 0, 1)); + + aiColor4D result(0, 0, 0, 1); + for (size_t i = 0; i < pInput.mResolved->mSize; ++i) { + result[static_cast<unsigned int>(i)] = obj[pInput.mResolved->mSubOffset[i]]; + } + pMesh.mColors[pInput.mIndex].push_back(result); + } else { + ASSIMP_LOG_ERROR("Collada: too many vertex color sets. Skipping."); + } + + break; + default: + // IT_Invalid and IT_Vertex + ai_assert(false && "shouldn't ever get here"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the library of node hierarchies and scene parts +void ColladaParser::ReadSceneLibrary(XmlNode &node) { + if (node.empty()) { + return; + } + + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "visual_scene") { + // read ID. Is optional according to the spec, but how on earth should a scene_instance refer to it then? + std::string id; + XmlParser::getStdStrAttribute(currentNode, "id", id); + + // read name if given. + std::string attrName = "Scene"; + if (XmlParser::hasAttribute(currentNode, "name")) { + XmlParser::getStdStrAttribute(currentNode, "name", attrName); + } + + // create a node and store it in the library under its ID + Node *sceneNode = new Node; + sceneNode->mID = id; + sceneNode->mName = attrName; + mNodeLibrary[sceneNode->mID] = sceneNode; + + ReadSceneNode(currentNode, sceneNode); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads a scene node's contents including children and stores it in the given node +void ColladaParser::ReadSceneNode(XmlNode &node, Node *pNode) { + // quit immediately on <bla/> elements + if (node.empty()) { + return; + } + + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "node") { + Node *child = new Node; + if (XmlParser::hasAttribute(currentNode, "id")) { + XmlParser::getStdStrAttribute(currentNode, "id", child->mID); + } + if (XmlParser::hasAttribute(currentNode, "sid")) { + XmlParser::getStdStrAttribute(currentNode, "id", child->mSID); + } + if (XmlParser::hasAttribute(currentNode, "name")) { + XmlParser::getStdStrAttribute(currentNode, "name", child->mName); + } + if (pNode) { + pNode->mChildren.push_back(child); + child->mParent = pNode; + } else { + // no parent node given, probably called from <library_nodes> element. + // create new node in node library + mNodeLibrary[child->mID] = child; + } + + // read on recursively from there + ReadSceneNode(currentNode, child); + continue; + } else if (!pNode) { + // For any further stuff we need a valid node to work on + continue; + } + if (currentName == "lookat") { + ReadNodeTransformation(currentNode, pNode, TF_LOOKAT); + } else if (currentName == "matrix") { + ReadNodeTransformation(currentNode, pNode, TF_MATRIX); + } else if (currentName == "rotate") { + ReadNodeTransformation(currentNode, pNode, TF_ROTATE); + } else if (currentName == "scale") { + ReadNodeTransformation(currentNode, pNode, TF_SCALE); + } else if (currentName == "skew") { + ReadNodeTransformation(currentNode, pNode, TF_SKEW); + } else if (currentName == "translate") { + ReadNodeTransformation(currentNode, pNode, TF_TRANSLATE); + } else if (currentName == "render" && pNode->mParent == nullptr && 0 == pNode->mPrimaryCamera.length()) { + // ... scene evaluation or, in other words, postprocessing pipeline, + // or, again in other words, a turing-complete description how to + // render a Collada scene. The only thing that is interesting for + // us is the primary camera. + if (XmlParser::hasAttribute(currentNode, "camera_node")) { + std::string s; + XmlParser::getStdStrAttribute(currentNode, "camera_node", s); + if (s[0] != '#') { + ASSIMP_LOG_ERROR("Collada: Unresolved reference format of camera"); + } else { + pNode->mPrimaryCamera = s.c_str() + 1; + } + } + } else if (currentName == "instance_node") { + // find the node in the library + if (XmlParser::hasAttribute(currentNode, "url")) { + std::string s; + XmlParser::getStdStrAttribute(currentNode, "url", s); + if (s[0] != '#') { + ASSIMP_LOG_ERROR("Collada: Unresolved reference format of node"); + } else { + pNode->mNodeInstances.push_back(NodeInstance()); + pNode->mNodeInstances.back().mNode = s.c_str() + 1; + } + } + } else if (currentName == "instance_geometry" || currentName == "instance_controller") { + // Reference to a mesh or controller, with possible material associations + ReadNodeGeometry(currentNode, pNode); + } else if (currentName == "instance_light") { + // Reference to a light, name given in 'url' attribute + if (XmlParser::hasAttribute(currentNode, "url")) { + std::string url; + XmlParser::getStdStrAttribute(currentNode, "url", url); + if (url[0] != '#') { + throw DeadlyImportError("Unknown reference format in <instance_light> element"); + } + + pNode->mLights.push_back(LightInstance()); + pNode->mLights.back().mLight = url.c_str() + 1; + } + } else if (currentName == "instance_camera") { + // Reference to a camera, name given in 'url' attribute + if (XmlParser::hasAttribute(currentNode, "url")) { + std::string url; + XmlParser::getStdStrAttribute(currentNode, "url", url); + if (url[0] != '#') { + throw DeadlyImportError("Unknown reference format in <instance_camera> element"); + } + pNode->mCameras.push_back(CameraInstance()); + pNode->mCameras.back().mCamera = url.c_str() + 1; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads a node transformation entry of the given type and adds it to the given node's transformation list. +void ColladaParser::ReadNodeTransformation(XmlNode &node, Node *pNode, TransformType pType) { + if (node.empty()) { + return; + } + + std::string tagName = node.name(); + + Transform tf; + tf.mType = pType; + + // read SID + if (XmlParser::hasAttribute(node, "sid")) { + XmlParser::getStdStrAttribute(node, "sid", tf.mID); + } + + // how many parameters to read per transformation type + static const unsigned int sNumParameters[] = { 9, 4, 3, 3, 7, 16 }; + std::string value; + XmlParser::getValueAsString(node, value); + const char *content = value.c_str(); + + // read as many parameters and store in the transformation + for (unsigned int a = 0; a < sNumParameters[pType]; a++) { + // skip whitespace before the number + SkipSpacesAndLineEnd(&content); + // read a number + content = fast_atoreal_move<ai_real>(content, tf.f[a]); + } + + // place the transformation at the queue of the node + pNode->mTransforms.push_back(tf); +} + +// ------------------------------------------------------------------------------------------------ +// Processes bind_vertex_input and bind elements +void ColladaParser::ReadMaterialVertexInputBinding(XmlNode &node, Collada::SemanticMappingTable &tbl) { + std::string name = node.name(); + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "bind_vertex_input") { + Collada::InputSemanticMapEntry vn; + + // effect semantic + if (XmlParser::hasAttribute(currentNode, "semantic")) { + std::string s; + XmlParser::getStdStrAttribute(currentNode, "semantic", s); + XmlParser::getUIntAttribute(currentNode, "input_semantic", (unsigned int &)vn.mType); + } + std::string s; + XmlParser::getStdStrAttribute(currentNode, "semantic", s); + + // input semantic + XmlParser::getUIntAttribute(currentNode, "input_semantic", (unsigned int &)vn.mType); + + // index of input set + if (XmlParser::hasAttribute(currentNode, "input_set")) { + XmlParser::getUIntAttribute(currentNode, "input_set", vn.mSet); + } + + tbl.mMap[s] = vn; + } else if (currentName == "bind") { + ASSIMP_LOG_WARN("Collada: Found unsupported <bind> element"); + } + } +} + +void ColladaParser::ReadEmbeddedTextures(ZipArchiveIOSystem &zip_archive) { + // Attempt to load any undefined Collada::Image in ImageLibrary + for (auto &it : mImageLibrary) { + Collada::Image &image = it.second; + + if (image.mImageData.empty()) { + std::unique_ptr<IOStream> image_file(zip_archive.Open(image.mFileName.c_str())); + if (image_file) { + image.mImageData.resize(image_file->FileSize()); + image_file->Read(image.mImageData.data(), image_file->FileSize(), 1); + image.mEmbeddedFormat = BaseImporter::GetExtension(image.mFileName); + if (image.mEmbeddedFormat == "jpeg") { + image.mEmbeddedFormat = "jpg"; + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads a mesh reference in a node and adds it to the node's mesh list +void ColladaParser::ReadNodeGeometry(XmlNode &node, Node *pNode) { + // referred mesh is given as an attribute of the <instance_geometry> element + std::string url; + XmlParser::getStdStrAttribute(node, "url", url); + if (url[0] != '#') { + throw DeadlyImportError("Unknown reference format"); + } + + Collada::MeshInstance instance; + instance.mMeshOrController = url.c_str() + 1; // skipping the leading # + + for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "bind_material") { + XmlNode techNode = currentNode.child("technique_common"); + if (techNode) { + for (XmlNode instanceMatNode = techNode.child("instance_material"); instanceMatNode; instanceMatNode = instanceMatNode.next_sibling()) + { + const std::string &instance_name = instanceMatNode.name(); + if (instance_name == "instance_material") + { + // read ID of the geometry subgroup and the target material + std::string group; + XmlParser::getStdStrAttribute(instanceMatNode, "symbol", group); + XmlParser::getStdStrAttribute(instanceMatNode, "target", url); + const char *urlMat = url.c_str(); + Collada::SemanticMappingTable s; + if (urlMat[0] == '#') + urlMat++; + + s.mMatName = urlMat; + // store the association + instance.mMaterials[group] = s; + ReadMaterialVertexInputBinding(instanceMatNode, s); + } + } + } + } + } + + // store it + pNode->mMeshes.push_back(instance); +} + +// ------------------------------------------------------------------------------------------------ +// Reads the collada scene +void ColladaParser::ReadScene(XmlNode &node) { + if (node.empty()) { + return; + } + + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "instance_visual_scene") { + // should be the first and only occurrence + if (mRootNode) { + throw DeadlyImportError("Invalid scene containing multiple root nodes in <instance_visual_scene> element"); + } + + // read the url of the scene to instance. Should be of format "#some_name" + std::string url; + XmlParser::getStdStrAttribute(currentNode, "url", url); + if (url[0] != '#') { + throw DeadlyImportError("Unknown reference format in <instance_visual_scene> element"); + } + + // find the referred scene, skip the leading # + NodeLibrary::const_iterator sit = mNodeLibrary.find(url.c_str() + 1); + if (sit == mNodeLibrary.end()) { + throw DeadlyImportError("Unable to resolve visual_scene reference \"", std::string(url), "\" in <instance_visual_scene> element."); + } + mRootNode = sit->second; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Calculates the resulting transformation from all the given transform steps +aiMatrix4x4 ColladaParser::CalculateResultTransform(const std::vector<Transform> &pTransforms) const { + aiMatrix4x4 res; + + for (std::vector<Transform>::const_iterator it = pTransforms.begin(); it != pTransforms.end(); ++it) { + const Transform &tf = *it; + switch (tf.mType) { + case TF_LOOKAT: { + aiVector3D pos(tf.f[0], tf.f[1], tf.f[2]); + aiVector3D dstPos(tf.f[3], tf.f[4], tf.f[5]); + aiVector3D up = aiVector3D(tf.f[6], tf.f[7], tf.f[8]).Normalize(); + aiVector3D dir = aiVector3D(dstPos - pos).Normalize(); + aiVector3D right = (dir ^ up).Normalize(); + + res *= aiMatrix4x4( + right.x, up.x, -dir.x, pos.x, + right.y, up.y, -dir.y, pos.y, + right.z, up.z, -dir.z, pos.z, + 0, 0, 0, 1); + break; + } + case TF_ROTATE: { + aiMatrix4x4 rot; + ai_real angle = tf.f[3] * ai_real(AI_MATH_PI) / ai_real(180.0); + aiVector3D axis(tf.f[0], tf.f[1], tf.f[2]); + aiMatrix4x4::Rotation(angle, axis, rot); + res *= rot; + break; + } + case TF_TRANSLATE: { + aiMatrix4x4 trans; + aiMatrix4x4::Translation(aiVector3D(tf.f[0], tf.f[1], tf.f[2]), trans); + res *= trans; + break; + } + case TF_SCALE: { + aiMatrix4x4 scale(tf.f[0], 0.0f, 0.0f, 0.0f, 0.0f, tf.f[1], 0.0f, 0.0f, 0.0f, 0.0f, tf.f[2], 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + res *= scale; + break; + } + case TF_SKEW: + // TODO: (thom) + ai_assert(false); + break; + case TF_MATRIX: { + aiMatrix4x4 mat(tf.f[0], tf.f[1], tf.f[2], tf.f[3], tf.f[4], tf.f[5], tf.f[6], tf.f[7], + tf.f[8], tf.f[9], tf.f[10], tf.f[11], tf.f[12], tf.f[13], tf.f[14], tf.f[15]); + res *= mat; + break; + } + default: + ai_assert(false); + break; + } + } + + return res; +} + +// ------------------------------------------------------------------------------------------------ +// Determines the input data type for the given semantic string +Collada::InputType ColladaParser::GetTypeForSemantic(const std::string &semantic) { + if (semantic.empty()) { + ASSIMP_LOG_WARN("Vertex input type is empty."); + return IT_Invalid; + } + + if (semantic == "POSITION") + return IT_Position; + else if (semantic == "TEXCOORD") + return IT_Texcoord; + else if (semantic == "NORMAL") + return IT_Normal; + else if (semantic == "COLOR") + return IT_Color; + else if (semantic == "VERTEX") + return IT_Vertex; + else if (semantic == "BINORMAL" || semantic == "TEXBINORMAL") + return IT_Bitangent; + else if (semantic == "TANGENT" || semantic == "TEXTANGENT") + return IT_Tangent; + + ASSIMP_LOG_WARN("Unknown vertex input type \"", semantic, "\". Ignoring."); + return IT_Invalid; +} + +#endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER diff --git a/libs/assimp/code/AssetLib/Collada/ColladaParser.h b/libs/assimp/code/AssetLib/Collada/ColladaParser.h new file mode 100644 index 0000000..1598293 --- /dev/null +++ b/libs/assimp/code/AssetLib/Collada/ColladaParser.h @@ -0,0 +1,348 @@ +/* + Open Asset Import Library (assimp) + ---------------------------------------------------------------------- + + Copyright (c) 2006-2022, 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 ColladaParser.h + * @brief Defines the parser helper class for the collada loader + */ + +#pragma once +#ifndef AI_COLLADAPARSER_H_INC +#define AI_COLLADAPARSER_H_INC + +#include "ColladaHelper.h" +#include <assimp/TinyFormatter.h> +#include <assimp/ai_assert.h> +#include <assimp/XmlParser.h> + +#include <map> + +namespace Assimp { + +class ZipArchiveIOSystem; + +// ------------------------------------------------------------------------------------------ +/** Parser helper class for the Collada loader. + * + * Does all the XML reading and builds internal data structures from it, + * but leaves the resolving of all the references to the loader. + */ +class ColladaParser { + friend class ColladaLoader; + + /** Converts a path read from a collada file to the usual representation */ + static void UriDecodePath(aiString &ss); + +protected: + /** Map for generic metadata as aiString */ + typedef std::map<std::string, aiString> StringMetaData; + + /** Constructor from XML file */ + ColladaParser(IOSystem *pIOHandler, const std::string &pFile); + + /** Destructor */ + ~ColladaParser(); + + /** Attempts to read the ZAE manifest and returns the DAE to open */ + static std::string ReadZaeManifest(ZipArchiveIOSystem &zip_archive); + + /** Reads the contents of the file */ + void ReadContents(XmlNode &node); + + /** Reads the structure of the file */ + void ReadStructure(XmlNode &node); + + /** Reads asset information such as coordinate system information and legal blah */ + void ReadAssetInfo(XmlNode &node); + + /** Reads contributor information such as author and legal blah */ + void ReadContributorInfo(XmlNode &node); + + /** Reads generic metadata into provided map and renames keys for Assimp */ + void ReadMetaDataItem(XmlNode &node, StringMetaData &metadata); + + /** Reads the animation library */ + void ReadAnimationLibrary(XmlNode &node); + + /** Reads the animation clip library */ + void ReadAnimationClipLibrary(XmlNode &node); + + /** Unwrap controllers dependency hierarchy */ + void PostProcessControllers(); + + /** Re-build animations from animation clip library, if present, otherwise combine single-channel animations */ + void PostProcessRootAnimations(); + + /** Reads an animation into the given parent structure */ + void ReadAnimation(XmlNode &node, Collada::Animation *pParent); + + /** Reads an animation sampler into the given anim channel */ + void ReadAnimationSampler(XmlNode &node, Collada::AnimationChannel &pChannel); + + /** Reads the skeleton controller library */ + void ReadControllerLibrary(XmlNode &node); + + /** Reads a controller into the given mesh structure */ + void ReadController(XmlNode &node, Collada::Controller &pController); + + /** Reads the joint definitions for the given controller */ + void ReadControllerJoints(XmlNode &node, Collada::Controller &pController); + + /** Reads the joint weights for the given controller */ + void ReadControllerWeights(XmlNode &node, Collada::Controller &pController); + + /** Reads the image library contents */ + void ReadImageLibrary(XmlNode &node); + + /** Reads an image entry into the given image */ + void ReadImage(XmlNode &node, Collada::Image &pImage); + + /** Reads the material library */ + void ReadMaterialLibrary(XmlNode &node); + + /** Reads a material entry into the given material */ + void ReadMaterial(XmlNode &node, Collada::Material &pMaterial); + + /** Reads the camera library */ + void ReadCameraLibrary(XmlNode &node); + + /** Reads a camera entry into the given camera */ + void ReadCamera(XmlNode &node, Collada::Camera &pCamera); + + /** Reads the light library */ + void ReadLightLibrary(XmlNode &node); + + /** Reads a light entry into the given light */ + void ReadLight(XmlNode &node, Collada::Light &pLight); + + /** Reads the effect library */ + void ReadEffectLibrary(XmlNode &node); + + /** Reads an effect entry into the given effect*/ + void ReadEffect(XmlNode &node, Collada::Effect &pEffect); + + /** Reads an COMMON effect profile */ + void ReadEffectProfileCommon(XmlNode &node, Collada::Effect &pEffect); + + /** Read sampler properties */ + void ReadSamplerProperties(XmlNode &node, Collada::Sampler &pSampler); + + /** Reads an effect entry containing a color or a texture defining that color */ + void ReadEffectColor(XmlNode &node, aiColor4D &pColor, Collada::Sampler &pSampler); + + /** Reads an effect entry containing a float */ + void ReadEffectFloat(XmlNode &node, ai_real &pFloat); + + /** Reads an effect parameter specification of any kind */ + void ReadEffectParam(XmlNode &node, Collada::EffectParam &pParam); + + /** Reads the geometry library contents */ + void ReadGeometryLibrary(XmlNode &node); + + /** Reads a geometry from the geometry library. */ + void ReadGeometry(XmlNode &node, Collada::Mesh &pMesh); + + /** Reads a mesh from the geometry library */ + void ReadMesh(XmlNode &node, Collada::Mesh &pMesh); + + /** Reads a source element - a combination of raw data and an accessor defining + * things that should not be redefinable. Yes, that's another rant. + */ + void ReadSource(XmlNode &node); + + /** Reads a data array holding a number of elements, and stores it in the global library. + * Currently supported are array of floats and arrays of strings. + */ + void ReadDataArray(XmlNode &node); + + /** Reads an accessor and stores it in the global library under the given ID - + * accessors use the ID of the parent <source> element + */ + void ReadAccessor(XmlNode &node, const std::string &pID); + + /** Reads input declarations of per-vertex mesh data into the given mesh */ + void ReadVertexData(XmlNode &node, Collada::Mesh &pMesh); + + /** Reads input declarations of per-index mesh data into the given mesh */ + void ReadIndexData(XmlNode &node, Collada::Mesh &pMesh); + + /** Reads a single input channel element and stores it in the given array, if valid */ + void ReadInputChannel(XmlNode &node, std::vector<Collada::InputChannel> &poChannels); + + /** Reads a <p> primitive index list and assembles the mesh data into the given mesh */ + size_t ReadPrimitives(XmlNode &node, Collada::Mesh &pMesh, std::vector<Collada::InputChannel> &pPerIndexChannels, + size_t pNumPrimitives, const std::vector<size_t> &pVCount, Collada::PrimitiveType pPrimType); + + /** Copies the data for a single primitive into the mesh, based on the InputChannels */ + void CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset, + Collada::Mesh &pMesh, std::vector<Collada::InputChannel> &pPerIndexChannels, + size_t currentPrimitive, const std::vector<size_t> &indices); + + /** Reads one triangle of a tristrip into the mesh */ + void ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Collada::Mesh &pMesh, + std::vector<Collada::InputChannel> &pPerIndexChannels, size_t currentPrimitive, const std::vector<size_t> &indices); + + /** Extracts a single object from an input channel and stores it in the appropriate mesh data array */ + void ExtractDataObjectFromChannel(const Collada::InputChannel &pInput, size_t pLocalIndex, Collada::Mesh &pMesh); + + /** Reads the library of node hierarchies and scene parts */ + void ReadSceneLibrary(XmlNode &node); + + /** Reads a scene node's contents including children and stores it in the given node */ + void ReadSceneNode(XmlNode &node, Collada::Node *pNode); + + /** Reads a node transformation entry of the given type and adds it to the given node's transformation list. */ + void ReadNodeTransformation(XmlNode &node, Collada::Node *pNode, Collada::TransformType pType); + + /** Reads a mesh reference in a node and adds it to the node's mesh list */ + void ReadNodeGeometry(XmlNode &node, Collada::Node *pNode); + + /** Reads the collada scene */ + void ReadScene(XmlNode &node); + + // Processes bind_vertex_input and bind elements + void ReadMaterialVertexInputBinding(XmlNode &node, Collada::SemanticMappingTable &tbl); + + /** Reads embedded textures from a ZAE archive*/ + void ReadEmbeddedTextures(ZipArchiveIOSystem &zip_archive); + +protected: + /** Calculates the resulting transformation from all the given transform steps */ + aiMatrix4x4 CalculateResultTransform(const std::vector<Collada::Transform> &pTransforms) const; + + /** Determines the input data type for the given semantic string */ + Collada::InputType GetTypeForSemantic(const std::string &pSemantic); + + /** Finds the item in the given library by its reference, throws if not found */ + template <typename Type> + const Type &ResolveLibraryReference(const std::map<std::string, Type> &pLibrary, const std::string &pURL) const; + +protected: + // Filename, for a verbose error message + std::string mFileName; + + // XML reader, member for everyday use + XmlParser mXmlParser; + + /** All data arrays found in the file by ID. Might be referred to by actually + everyone. Collada, you are a steaming pile of indirection. */ + using DataLibrary = std::map<std::string, Collada::Data> ; + DataLibrary mDataLibrary; + + /** Same for accessors which define how the data in a data array is accessed. */ + using AccessorLibrary = std::map<std::string, Collada::Accessor> ; + AccessorLibrary mAccessorLibrary; + + /** Mesh library: mesh by ID */ + using MeshLibrary = std::map<std::string, Collada::Mesh *>; + MeshLibrary mMeshLibrary; + + /** node library: root node of the hierarchy part by ID */ + using NodeLibrary = std::map<std::string, Collada::Node *>; + NodeLibrary mNodeLibrary; + + /** Image library: stores texture properties by ID */ + using ImageLibrary = std::map<std::string, Collada::Image> ; + ImageLibrary mImageLibrary; + + /** Effect library: surface attributes by ID */ + using EffectLibrary = std::map<std::string, Collada::Effect> ; + EffectLibrary mEffectLibrary; + + /** Material library: surface material by ID */ + using MaterialLibrary = std::map<std::string, Collada::Material> ; + MaterialLibrary mMaterialLibrary; + + /** Light library: surface light by ID */ + using LightLibrary = std::map<std::string, Collada::Light> ; + LightLibrary mLightLibrary; + + /** Camera library: surface material by ID */ + using CameraLibrary = std::map<std::string, Collada::Camera> ; + CameraLibrary mCameraLibrary; + + /** Controller library: joint controllers by ID */ + using ControllerLibrary = std::map<std::string, Collada::Controller> ; + ControllerLibrary mControllerLibrary; + + /** Animation library: animation references by ID */ + using AnimationLibrary = std::map<std::string, Collada::Animation *> ; + AnimationLibrary mAnimationLibrary; + + /** Animation clip library: clip animation references by ID */ + using AnimationClipLibrary = std::vector<std::pair<std::string, std::vector<std::string>>> ; + AnimationClipLibrary mAnimationClipLibrary; + + /** Pointer to the root node. Don't delete, it just points to one of + the nodes in the node library. */ + Collada::Node *mRootNode; + + /** Root animation container */ + Collada::Animation mAnims; + + /** Size unit: how large compared to a meter */ + ai_real mUnitSize; + + /** Which is the up vector */ + enum { UP_X, + UP_Y, + UP_Z } mUpDirection; + + /** Asset metadata (global for scene) */ + StringMetaData mAssetMetaData; + + /** Collada file format version */ + Collada::FormatVersion mFormat; +}; + +// ------------------------------------------------------------------------------------------------ +// Finds the item in the given library by its reference, throws if not found +template <typename Type> +const Type &ColladaParser::ResolveLibraryReference(const std::map<std::string, Type> &pLibrary, const std::string &pURL) const { + typename std::map<std::string, Type>::const_iterator it = pLibrary.find(pURL); + if (it == pLibrary.end()) { + throw DeadlyImportError("Unable to resolve library reference \"", pURL, "\"."); + } + return it->second; +} + +} // end of namespace Assimp + +#endif // AI_COLLADAPARSER_H_INC diff --git a/libs/assimp/code/AssetLib/DXF/DXFHelper.h b/libs/assimp/code/AssetLib/DXF/DXFHelper.h new file mode 100644 index 0000000..e50c471 --- /dev/null +++ b/libs/assimp/code/AssetLib/DXF/DXFHelper.h @@ -0,0 +1,222 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 DXFHelper.h + * @brief Internal utilities for the DXF loader. + */ + +#ifndef INCLUDED_DXFHELPER_H +#define INCLUDED_DXFHELPER_H + +#include <assimp/LineSplitter.h> +#include <assimp/TinyFormatter.h> +#include <assimp/StreamReader.h> +#include <assimp/fast_atof.h> +#include <vector> +#include <assimp/DefaultLogger.hpp> + +namespace Assimp { +namespace DXF { + +// read pairs of lines, parse group code and value and provide utilities +// to convert the data to the target data type. +// do NOT skip empty lines. In DXF files, they count as valid data. +class LineReader { +public: + LineReader(StreamReaderLE& reader) + : splitter(reader,false,true) + , groupcode( 0 ) + , value() + , end() { + // empty + } + + // ----------------------------------------- + bool Is(int gc, const char* what) const { + return groupcode == gc && !strcmp(what,value.c_str()); + } + + // ----------------------------------------- + bool Is(int gc) const { + return groupcode == gc; + } + + // ----------------------------------------- + int GroupCode() const { + return groupcode; + } + + // ----------------------------------------- + const std::string& Value() const { + return value; + } + + // ----------------------------------------- + bool End() const { + return !((bool)*this); + } + + // ----------------------------------------- + unsigned int ValueAsUnsignedInt() const { + return strtoul10(value.c_str()); + } + + // ----------------------------------------- + int ValueAsSignedInt() const { + return strtol10(value.c_str()); + } + + // ----------------------------------------- + float ValueAsFloat() const { + return fast_atof(value.c_str()); + } + + // ----------------------------------------- + /** pseudo-iterator increment to advance to the next (groupcode/value) pair */ + LineReader& operator++() { + if (end) { + if (end == 1) { + ++end; + } + return *this; + } + + try { + groupcode = strtol10(splitter->c_str()); + splitter++; + + value = *splitter; + splitter++; + + // automatically skip over {} meta blocks (these are for application use + // and currently not relevant for Assimp). + if (value.length() && value[0] == '{') { + + size_t cnt = 0; + for(;splitter->length() && splitter->at(0) != '}'; splitter++, cnt++); + + splitter++; + ASSIMP_LOG_VERBOSE_DEBUG("DXF: skipped over control group (",cnt," lines)"); + } + } catch(std::logic_error&) { + ai_assert(!splitter); + } + if (!splitter) { + end = 1; + } + return *this; + } + + // ----------------------------------------- + LineReader& operator++(int) { + return ++(*this); + } + + + // ----------------------------------------- + operator bool() const { + return end <= 1; + } + +private: + LineSplitter splitter; + int groupcode; + std::string value; + int end; +}; + +// represents a POLYLINE or a LWPOLYLINE. or even a 3DFACE The data is converted as needed. +struct PolyLine { + PolyLine() + : flags() { + // empty + } + + std::vector<aiVector3D> positions; + std::vector<aiColor4D> colors; + std::vector<unsigned int> indices; + std::vector<unsigned int> counts; + unsigned int flags; + + std::string layer; + std::string desc; +}; + +// reference to a BLOCK. Specifies its own coordinate system. +struct InsertBlock { + InsertBlock() + : pos() + , scale(1.f,1.f,1.f) + , angle() + , name() { + // empty + } + + aiVector3D pos; + aiVector3D scale; + float angle; + + std::string name; +}; + + +// keeps track of all geometry in a single BLOCK. +struct Block +{ + std::vector< std::shared_ptr<PolyLine> > lines; + std::vector<InsertBlock> insertions; + + std::string name; + aiVector3D base; +}; + + +struct FileData +{ + // note: the LAST block always contains the stuff from ENTITIES. + std::vector<Block> blocks; +}; + +} +} // Namespace Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/DXF/DXFLoader.cpp b/libs/assimp/code/AssetLib/DXF/DXFLoader.cpp new file mode 100644 index 0000000..6b2dbbe --- /dev/null +++ b/libs/assimp/code/AssetLib/DXF/DXFLoader.cpp @@ -0,0 +1,912 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 DXFLoader.cpp + * @brief Implementation of the DXF importer class + */ + + +#ifndef ASSIMP_BUILD_NO_DXF_IMPORTER + +#include "AssetLib/DXF/DXFLoader.h" +#include "AssetLib/DXF/DXFHelper.h" +#include "PostProcessing/ConvertToLHProcess.h" + +#include <assimp/ParsingUtils.h> +#include <assimp/fast_atof.h> +#include <assimp/IOSystem.hpp> +#include <assimp/scene.h> +#include <assimp/importerdesc.h> + +#include <numeric> + +using namespace Assimp; + +// AutoCAD Binary DXF<CR><LF><SUB><NULL> +static constexpr char AI_DXF_BINARY_IDENT[] = "AutoCAD Binary DXF\r\n\x1a"; +static constexpr size_t AI_DXF_BINARY_IDENT_LEN = sizeof AI_DXF_BINARY_IDENT; + +// default vertex color that all uncolored vertices will receive +static const aiColor4D AI_DXF_DEFAULT_COLOR(aiColor4D(0.6f, 0.6f, 0.6f, 0.6f)); + +// color indices for DXF - 16 are supported, the table is +// taken directly from the DXF spec. +static aiColor4D g_aclrDxfIndexColors[] = { + aiColor4D (0.6f, 0.6f, 0.6f, 1.0f), + aiColor4D (1.0f, 0.0f, 0.0f, 1.0f), // red + aiColor4D (0.0f, 1.0f, 0.0f, 1.0f), // green + aiColor4D (0.0f, 0.0f, 1.0f, 1.0f), // blue + aiColor4D (0.3f, 1.0f, 0.3f, 1.0f), // light green + aiColor4D (0.3f, 0.3f, 1.0f, 1.0f), // light blue + aiColor4D (1.0f, 0.3f, 0.3f, 1.0f), // light red + aiColor4D (1.0f, 0.0f, 1.0f, 1.0f), // pink + aiColor4D (1.0f, 0.6f, 0.0f, 1.0f), // orange + aiColor4D (0.6f, 0.3f, 0.0f, 1.0f), // dark orange + aiColor4D (1.0f, 1.0f, 0.0f, 1.0f), // yellow + aiColor4D (0.3f, 0.3f, 0.3f, 1.0f), // dark gray + aiColor4D (0.8f, 0.8f, 0.8f, 1.0f), // light gray + aiColor4D (0.0f, 00.f, 0.0f, 1.0f), // black + aiColor4D (1.0f, 1.0f, 1.0f, 1.0f), // white + aiColor4D (0.6f, 0.0f, 1.0f, 1.0f) // violet +}; +#define AI_DXF_NUM_INDEX_COLORS (sizeof(g_aclrDxfIndexColors)/sizeof(g_aclrDxfIndexColors[0])) +#define AI_DXF_ENTITIES_MAGIC_BLOCK "$ASSIMP_ENTITIES_MAGIC" + +static const int GroupCode_Name = 2; +static const int GroupCode_XComp = 10; +static const int GroupCode_YComp = 20; +static const int GroupCode_ZComp = 30; + +static const aiImporterDesc desc = { + "Drawing Interchange Format (DXF) Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_LimitedSupport, + 0, + 0, + 0, + 0, + "dxf" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +DXFImporter::DXFImporter() +: BaseImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +DXFImporter::~DXFImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool DXFImporter::CanRead( const std::string& filename, IOSystem* pIOHandler, bool /*checkSig*/ ) const { + static const char *tokens[] = { "SECTION", "HEADER", "ENDSEC", "BLOCKS" }; + return SearchFileHeaderForToken(pIOHandler, filename, tokens, AI_COUNT_OF(tokens), 32); +} + +// ------------------------------------------------------------------------------------------------ +// Get a list of all supported file extensions +const aiImporterDesc* DXFImporter::GetInfo () const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void DXFImporter::InternReadFile( const std::string& filename, aiScene* pScene, IOSystem* pIOHandler) { + std::shared_ptr<IOStream> file = std::shared_ptr<IOStream>( pIOHandler->Open( filename) ); + + // Check whether we can read the file + if( file.get() == nullptr ) { + throw DeadlyImportError( "Failed to open DXF file ", filename, ""); + } + + // Check whether this is a binary DXF file - we can't read binary DXF files :-( + char buff[AI_DXF_BINARY_IDENT_LEN] = {0}; + file->Read(buff,AI_DXF_BINARY_IDENT_LEN,1); + + if (0 == memcmp(AI_DXF_BINARY_IDENT,buff,AI_DXF_BINARY_IDENT_LEN)) { + throw DeadlyImportError("DXF: Binary files are not supported at the moment"); + } + + // DXF files can grow very large, so read them via the StreamReader, + // which will choose a suitable strategy. + file->Seek(0,aiOrigin_SET); + StreamReaderLE stream( file ); + + DXF::LineReader reader (stream); + DXF::FileData output; + + // now get all lines of the file and process top-level sections + bool eof = false; + while(!reader.End()) { + + // blocks table - these 'build blocks' are later (in ENTITIES) + // referenced an included via INSERT statements. + if (reader.Is(2,"BLOCKS")) { + ParseBlocks(reader,output); + continue; + } + + // primary entity table + if (reader.Is(2,"ENTITIES")) { + ParseEntities(reader,output); + continue; + } + + // skip unneeded sections entirely to avoid any problems with them + // altogether. + else if (reader.Is(2,"CLASSES") || reader.Is(2,"TABLES")) { + SkipSection(reader); + continue; + } + + else if (reader.Is(2,"HEADER")) { + ParseHeader(reader,output); + continue; + } + + // comments + else if (reader.Is(999)) { + ASSIMP_LOG_INFO("DXF Comment: ", reader.Value()); + } + + // don't read past the official EOF sign + else if (reader.Is(0,"EOF")) { + eof = true; + break; + } + + ++reader; + } + if (!eof) { + ASSIMP_LOG_WARN("DXF: EOF reached, but did not encounter DXF EOF marker"); + } + + ConvertMeshes(pScene,output); + + // Now rotate the whole scene by 90 degrees around the x axis to convert from AutoCAD's to Assimp's coordinate system + pScene->mRootNode->mTransformation = aiMatrix4x4( + 1.f,0.f,0.f,0.f, + 0.f,0.f,1.f,0.f, + 0.f,-1.f,0.f,0.f, + 0.f,0.f,0.f,1.f) * pScene->mRootNode->mTransformation; +} + +// ------------------------------------------------------------------------------------------------ +void DXFImporter::ConvertMeshes(aiScene* pScene, DXF::FileData& output) { + // the process of resolving all the INSERT statements can grow the + // poly-count excessively, so log the original number. + // XXX Option to import blocks as separate nodes? + if (!DefaultLogger::isNullLogger()) { + unsigned int vcount = 0, icount = 0; + for (const DXF::Block& bl : output.blocks) { + for (std::shared_ptr<const DXF::PolyLine> pl : bl.lines) { + vcount += static_cast<unsigned int>(pl->positions.size()); + icount += static_cast<unsigned int>(pl->counts.size()); + } + } + + ASSIMP_LOG_VERBOSE_DEBUG("DXF: Unexpanded polycount is ", icount, ", vertex count is ", vcount); + } + + if (! output.blocks.size() ) { + throw DeadlyImportError("DXF: no data blocks loaded"); + } + + DXF::Block* entities( nullptr ); + + // index blocks by name + DXF::BlockMap blocks_by_name; + for (DXF::Block& bl : output.blocks) { + blocks_by_name[bl.name] = &bl; + if ( !entities && bl.name == AI_DXF_ENTITIES_MAGIC_BLOCK ) { + entities = &bl; + } + } + + if (!entities) { + throw DeadlyImportError("DXF: no ENTITIES data block loaded"); + } + + typedef std::map<std::string, unsigned int> LayerMap; + + LayerMap layers; + std::vector< std::vector< const DXF::PolyLine*> > corr; + + // now expand all block references in the primary ENTITIES block + // XXX this involves heavy memory copying, consider a faster solution for future versions. + ExpandBlockReferences(*entities,blocks_by_name); + + unsigned int cur = 0; + for (std::shared_ptr<const DXF::PolyLine> pl : entities->lines) { + if (pl->positions.size()) { + + std::map<std::string, unsigned int>::iterator it = layers.find(pl->layer); + if (it == layers.end()) { + ++pScene->mNumMeshes; + + layers[pl->layer] = cur++; + + std::vector< const DXF::PolyLine* > pv; + pv.push_back(&*pl); + + corr.push_back(pv); + } + else { + corr[(*it).second].push_back(&*pl); + } + } + } + + if ( 0 == pScene->mNumMeshes) { + throw DeadlyImportError("DXF: this file contains no 3d data"); + } + + pScene->mMeshes = new aiMesh*[ pScene->mNumMeshes ] (); + + for(const LayerMap::value_type& elem : layers){ + aiMesh* const mesh = pScene->mMeshes[elem.second] = new aiMesh(); + mesh->mName.Set(elem.first); + + unsigned int cvert = 0,cface = 0; + for(const DXF::PolyLine* pl : corr[elem.second]){ + // sum over all faces since we need to 'verbosify' them. + cvert += std::accumulate(pl->counts.begin(),pl->counts.end(),0); + cface += static_cast<unsigned int>(pl->counts.size()); + } + + aiVector3D* verts = mesh->mVertices = new aiVector3D[cvert]; + aiColor4D* colors = mesh->mColors[0] = new aiColor4D[cvert]; + aiFace* faces = mesh->mFaces = new aiFace[cface]; + + mesh->mNumVertices = cvert; + mesh->mNumFaces = cface; + + unsigned int prims = 0; + unsigned int overall_indices = 0; + for(const DXF::PolyLine* pl : corr[elem.second]){ + + std::vector<unsigned int>::const_iterator it = pl->indices.begin(); + for(unsigned int facenumv : pl->counts) { + aiFace& face = *faces++; + face.mIndices = new unsigned int[face.mNumIndices = facenumv]; + + for (unsigned int i = 0; i < facenumv; ++i) { + face.mIndices[i] = overall_indices++; + + ai_assert(pl->positions.size() == pl->colors.size()); + if (*it >= pl->positions.size()) { + throw DeadlyImportError("DXF: vertex index out of bounds"); + } + + *verts++ = pl->positions[*it]; + *colors++ = pl->colors[*it++]; + } + + // set primitive flags now, this saves the extra pass in ScenePreprocessor. + switch(face.mNumIndices) { + case 1: + prims |= aiPrimitiveType_POINT; + break; + case 2: + prims |= aiPrimitiveType_LINE; + break; + case 3: + prims |= aiPrimitiveType_TRIANGLE; + break; + default: + prims |= aiPrimitiveType_POLYGON; + break; + } + } + } + + mesh->mPrimitiveTypes = prims; + mesh->mMaterialIndex = 0; + } + + GenerateHierarchy(pScene,output); + GenerateMaterials(pScene,output); +} + + +// ------------------------------------------------------------------------------------------------ +void DXFImporter::ExpandBlockReferences(DXF::Block& bl,const DXF::BlockMap& blocks_by_name) { + for (const DXF::InsertBlock& insert : bl.insertions) { + + // first check if the referenced blocks exists ... + const DXF::BlockMap::const_iterator it = blocks_by_name.find(insert.name); + if (it == blocks_by_name.end()) { + ASSIMP_LOG_ERROR("DXF: Failed to resolve block reference: ", insert.name,"; skipping" ); + continue; + } + + // XXX this would be the place to implement recursive expansion if needed. + const DXF::Block& bl_src = *(*it).second; + + for (std::shared_ptr<const DXF::PolyLine> pl_in : bl_src.lines) { + if (!pl_in) { + ASSIMP_LOG_ERROR("DXF: PolyLine instance is nullptr, skipping."); + continue; + } + + std::shared_ptr<DXF::PolyLine> pl_out = std::shared_ptr<DXF::PolyLine>(new DXF::PolyLine(*pl_in)); + + if (bl_src.base.Length() || insert.scale.x!=1.f || insert.scale.y!=1.f || insert.scale.z!=1.f || insert.angle || insert.pos.Length()) { + // manual coordinate system transformation + // XXX order + aiMatrix4x4 trafo, tmp; + aiMatrix4x4::Translation(-bl_src.base,trafo); + trafo *= aiMatrix4x4::Scaling(insert.scale,tmp); + trafo *= aiMatrix4x4::Translation(insert.pos,tmp); + + // XXX rotation currently ignored - I didn't find an appropriate sample model. + if (insert.angle != 0.f) { + ASSIMP_LOG_WARN("DXF: BLOCK rotation not currently implemented"); + } + + for (aiVector3D& v : pl_out->positions) { + v *= trafo; + } + } + + bl.lines.push_back(pl_out); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void DXFImporter::GenerateMaterials(aiScene* pScene, DXF::FileData& /*output*/) { + // generate an almost-white default material. Reason: + // the default vertex color is GREY, so we are + // already at Assimp's usual default color. + // generate a default material + aiMaterial* pcMat = new aiMaterial(); + aiString s; + s.Set(AI_DEFAULT_MATERIAL_NAME); + pcMat->AddProperty(&s, AI_MATKEY_NAME); + + aiColor4D clrDiffuse(0.9f,0.9f,0.9f,1.0f); + pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_DIFFUSE); + + clrDiffuse = aiColor4D(1.0f,1.0f,1.0f,1.0f); + pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_SPECULAR); + + clrDiffuse = aiColor4D(0.05f,0.05f,0.05f,1.0f); + pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_AMBIENT); + + pScene->mNumMaterials = 1; + pScene->mMaterials = new aiMaterial*[1]; + pScene->mMaterials[0] = pcMat; +} + +// ------------------------------------------------------------------------------------------------ +void DXFImporter::GenerateHierarchy(aiScene* pScene, DXF::FileData& /*output*/) { + // generate the output scene graph, which is just the root node with a single child for each layer. + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mName.Set("<DXF_ROOT>"); + + if (1 == pScene->mNumMeshes) { + pScene->mRootNode->mMeshes = new unsigned int[ pScene->mRootNode->mNumMeshes = 1 ]; + pScene->mRootNode->mMeshes[0] = 0; + } else { + pScene->mRootNode->mChildren = new aiNode*[ pScene->mRootNode->mNumChildren = pScene->mNumMeshes ]; + for (unsigned int m = 0; m < pScene->mRootNode->mNumChildren;++m) { + aiNode* p = pScene->mRootNode->mChildren[m] = new aiNode(); + p->mName = pScene->mMeshes[m]->mName; + + p->mMeshes = new unsigned int[p->mNumMeshes = 1]; + p->mMeshes[0] = m; + p->mParent = pScene->mRootNode; + } + } +} + + +// ------------------------------------------------------------------------------------------------ +void DXFImporter::SkipSection(DXF::LineReader& reader) { + for( ;!reader.End() && !reader.Is(0,"ENDSEC"); reader++); +} + +// ------------------------------------------------------------------------------------------------ +void DXFImporter::ParseHeader(DXF::LineReader& reader, DXF::FileData& ) { + for( ;!reader.End() && !reader.Is(0,"ENDSEC"); reader++); +} + +// ------------------------------------------------------------------------------------------------ +void DXFImporter::ParseBlocks(DXF::LineReader& reader, DXF::FileData& output) { + while( !reader.End() && !reader.Is(0,"ENDSEC")) { + if (reader.Is(0,"BLOCK")) { + ParseBlock(++reader,output); + continue; + } + ++reader; + } + + ASSIMP_LOG_VERBOSE_DEBUG("DXF: got ", output.blocks.size()," entries in BLOCKS" ); +} + +// ------------------------------------------------------------------------------------------------ +void DXFImporter::ParseBlock(DXF::LineReader& reader, DXF::FileData& output) { + // push a new block onto the stack. + output.blocks.push_back( DXF::Block() ); + DXF::Block& block = output.blocks.back(); + + while( !reader.End() && !reader.Is(0,"ENDBLK")) { + + switch(reader.GroupCode()) { + case GroupCode_Name: + block.name = reader.Value(); + break; + + case GroupCode_XComp: + block.base.x = reader.ValueAsFloat(); + break; + case GroupCode_YComp: + block.base.y = reader.ValueAsFloat(); + break; + case GroupCode_ZComp: + block.base.z = reader.ValueAsFloat(); + break; + } + + if (reader.Is(0,"POLYLINE")) { + ParsePolyLine(++reader,output); + continue; + } + + // XXX is this a valid case? + if (reader.Is(0,"INSERT")) { + ASSIMP_LOG_WARN("DXF: INSERT within a BLOCK not currently supported; skipping"); + for( ;!reader.End() && !reader.Is(0,"ENDBLK"); ++reader); + break; + } + + else if (reader.Is(0,"3DFACE") || reader.Is(0,"LINE") || reader.Is(0,"3DLINE")) { + //http://sourceforge.net/tracker/index.php?func=detail&aid=2970566&group_id=226462&atid=1067632 + Parse3DFace(++reader, output); + continue; + } + ++reader; + } +} + +// ------------------------------------------------------------------------------------------------ +void DXFImporter::ParseEntities(DXF::LineReader& reader, DXF::FileData& output) { + // Push a new block onto the stack. + output.blocks.push_back( DXF::Block() ); + DXF::Block& block = output.blocks.back(); + + block.name = AI_DXF_ENTITIES_MAGIC_BLOCK; + + while( !reader.End() && !reader.Is(0,"ENDSEC")) { + if (reader.Is(0,"POLYLINE")) { + ParsePolyLine(++reader,output); + continue; + } + + else if (reader.Is(0,"INSERT")) { + ParseInsertion(++reader,output); + continue; + } + + else if (reader.Is(0,"3DFACE") || reader.Is(0,"LINE") || reader.Is(0,"3DLINE")) { + //http://sourceforge.net/tracker/index.php?func=detail&aid=2970566&group_id=226462&atid=1067632 + Parse3DFace(++reader, output); + continue; + } + + ++reader; + } + + ASSIMP_LOG_VERBOSE_DEBUG( "DXF: got ", block.lines.size()," polylines and ", block.insertions.size(), + " inserted blocks in ENTITIES" ); +} + +void DXFImporter::ParseInsertion(DXF::LineReader& reader, DXF::FileData& output) { + output.blocks.back().insertions.push_back( DXF::InsertBlock() ); + DXF::InsertBlock& bl = output.blocks.back().insertions.back(); + + while( !reader.End() && !reader.Is(0)) { + switch(reader.GroupCode()) { + // name of referenced block + case GroupCode_Name: + bl.name = reader.Value(); + break; + + // translation + case GroupCode_XComp: + bl.pos.x = reader.ValueAsFloat(); + break; + case GroupCode_YComp: + bl.pos.y = reader.ValueAsFloat(); + break; + case GroupCode_ZComp: + bl.pos.z = reader.ValueAsFloat(); + break; + + // scaling + case 41: + bl.scale.x = reader.ValueAsFloat(); + break; + case 42: + bl.scale.y = reader.ValueAsFloat(); + break; + case 43: + bl.scale.z = reader.ValueAsFloat(); + break; + + // rotation angle + case 50: + bl.angle = reader.ValueAsFloat(); + break; + } + reader++; + } +} + +#define DXF_POLYLINE_FLAG_CLOSED 0x1 +#define DXF_POLYLINE_FLAG_3D_POLYLINE 0x8 +#define DXF_POLYLINE_FLAG_3D_POLYMESH 0x10 +#define DXF_POLYLINE_FLAG_POLYFACEMESH 0x40 + +// ------------------------------------------------------------------------------------------------ +void DXFImporter::ParsePolyLine(DXF::LineReader& reader, DXF::FileData& output) { + output.blocks.back().lines.push_back( std::shared_ptr<DXF::PolyLine>( new DXF::PolyLine() ) ); + DXF::PolyLine& line = *output.blocks.back().lines.back(); + + unsigned int iguess = 0, vguess = 0; + while( !reader.End() && !reader.Is(0,"ENDSEC")) { + + if (reader.Is(0,"VERTEX")) { + ParsePolyLineVertex(++reader,line); + if (reader.Is(0,"SEQEND")) { + break; + } + continue; + } + + switch(reader.GroupCode()) + { + // flags --- important that we know whether it is a + // polyface mesh or 'just' a line. + case 70: + if (!line.flags) { + line.flags = reader.ValueAsSignedInt(); + } + break; + + // optional number of vertices + case 71: + vguess = reader.ValueAsSignedInt(); + line.positions.reserve(vguess); + break; + + // optional number of faces + case 72: + iguess = reader.ValueAsSignedInt(); + line.indices.reserve(iguess); + break; + + // 8 specifies the layer on which this line is placed on + case 8: + line.layer = reader.Value(); + break; + } + + reader++; + } + + //if (!(line.flags & DXF_POLYLINE_FLAG_POLYFACEMESH)) { + // DefaultLogger::get()->warn((Formatter::format("DXF: polyline not currently supported: "),line.flags)); + // output.blocks.back().lines.pop_back(); + // return; + //} + + if (vguess && line.positions.size() != vguess) { + ASSIMP_LOG_WARN("DXF: unexpected vertex count in polymesh: ", + line.positions.size(),", expected ", vguess ); + } + + if (line.flags & DXF_POLYLINE_FLAG_POLYFACEMESH ) { + if (line.positions.size() < 3 || line.indices.size() < 3) { + ASSIMP_LOG_WARN("DXF: not enough vertices for polymesh; ignoring"); + output.blocks.back().lines.pop_back(); + return; + } + + // if these numbers are wrong, parsing might have gone wild. + // however, the docs state that applications are not required + // to set the 71 and 72 fields, respectively, to valid values. + // So just fire a warning. + if (iguess && line.counts.size() != iguess) { + ASSIMP_LOG_WARN( "DXF: unexpected face count in polymesh: ", line.counts.size(),", expected ", iguess ); + } + } + else if (!line.indices.size() && !line.counts.size()) { + // a poly-line - so there are no indices yet. + size_t guess = line.positions.size() + (line.flags & DXF_POLYLINE_FLAG_CLOSED ? 1 : 0); + line.indices.reserve(guess); + + line.counts.reserve(guess/2); + for (unsigned int i = 0; i < line.positions.size()/2; ++i) { + line.indices.push_back(i*2); + line.indices.push_back(i*2+1); + line.counts.push_back(2); + } + + // closed polyline? + if (line.flags & DXF_POLYLINE_FLAG_CLOSED) { + line.indices.push_back(static_cast<unsigned int>(line.positions.size()-1)); + line.indices.push_back(0); + line.counts.push_back(2); + } + } +} + +#define DXF_VERTEX_FLAG_PART_OF_POLYFACE 0x80 +#define DXF_VERTEX_FLAG_HAS_POSITIONS 0x40 + +// ------------------------------------------------------------------------------------------------ +void DXFImporter::ParsePolyLineVertex(DXF::LineReader& reader, DXF::PolyLine& line) { + unsigned int cnti = 0, flags = 0; + unsigned int indices[4]; + + aiVector3D out; + aiColor4D clr = AI_DXF_DEFAULT_COLOR; + + while( !reader.End() ) { + + if (reader.Is(0)) { // SEQEND or another VERTEX + break; + } + + switch (reader.GroupCode()) { + case 8: + // layer to which the vertex belongs to - assume that + // this is always the layer the top-level poly-line + // entity resides on as well. + if(reader.Value() != line.layer) { + ASSIMP_LOG_WARN("DXF: expected vertex to be part of a poly-face but the 0x128 flag isn't set"); + } + break; + + case 70: + flags = reader.ValueAsUnsignedInt(); + break; + + // VERTEX COORDINATES + case GroupCode_XComp: + out.x = reader.ValueAsFloat(); + break; + + case GroupCode_YComp: + out.y = reader.ValueAsFloat(); + break; + + case GroupCode_ZComp: + out.z = reader.ValueAsFloat(); + break; + + // POLYFACE vertex indices + case 71: + case 72: + case 73: + case 74: + if (cnti == 4) { + ASSIMP_LOG_WARN("DXF: more than 4 indices per face not supported; ignoring"); + break; + } + indices[cnti++] = reader.ValueAsUnsignedInt(); + break; + + // color + case 62: + clr = g_aclrDxfIndexColors[reader.ValueAsUnsignedInt() % AI_DXF_NUM_INDEX_COLORS]; + break; + }; + + reader++; + } + + if (line.flags & DXF_POLYLINE_FLAG_POLYFACEMESH && !(flags & DXF_VERTEX_FLAG_PART_OF_POLYFACE)) { + ASSIMP_LOG_WARN("DXF: expected vertex to be part of a polyface but the 0x128 flag isn't set"); + } + + if (cnti) { + line.counts.push_back(cnti); + for (unsigned int i = 0; i < cnti; ++i) { + // IMPORTANT NOTE: POLYMESH indices are ONE-BASED + if (indices[i] == 0) { + ASSIMP_LOG_WARN("DXF: invalid vertex index, indices are one-based."); + --line.counts.back(); + // Workaround to fix issue 2229 + if (line.counts.back() == 0) { + line.counts.pop_back(); + } + continue; + } + line.indices.push_back(indices[i]-1); + } + } else { + line.positions.push_back(out); + line.colors.push_back(clr); + } +} + +// ------------------------------------------------------------------------------------------------ +void DXFImporter::Parse3DFace(DXF::LineReader& reader, DXF::FileData& output) +{ + // (note) this is also used for for parsing line entities, so we + // must handle the vertex_count == 2 case as well. + + output.blocks.back().lines.push_back( std::shared_ptr<DXF::PolyLine>( new DXF::PolyLine() ) ); + DXF::PolyLine& line = *output.blocks.back().lines.back(); + + aiVector3D vip[4]; + aiColor4D clr = AI_DXF_DEFAULT_COLOR; + + bool b[4] = {false,false,false,false}; + while( !reader.End() ) { + + // next entity with a groupcode == 0 is probably already the next vertex or polymesh entity + if (reader.GroupCode() == 0) { + break; + } + switch (reader.GroupCode()) + { + + // 8 specifies the layer + case 8: + line.layer = reader.Value(); + break; + + // x position of the first corner + case 10: + vip[0].x = reader.ValueAsFloat(); + b[2] = true; + break; + + // y position of the first corner + case 20: + vip[0].y = reader.ValueAsFloat(); + b[2] = true; + break; + + // z position of the first corner + case 30: + vip[0].z = reader.ValueAsFloat(); + b[2] = true; + break; + + // x position of the second corner + case 11: + vip[1].x = reader.ValueAsFloat(); + b[3] = true; + break; + + // y position of the second corner + case 21: + vip[1].y = reader.ValueAsFloat(); + b[3] = true; + break; + + // z position of the second corner + case 31: + vip[1].z = reader.ValueAsFloat(); + b[3] = true; + break; + + // x position of the third corner + case 12: + vip[2].x = reader.ValueAsFloat(); + b[0] = true; + break; + + // y position of the third corner + case 22: + vip[2].y = reader.ValueAsFloat(); + b[0] = true; + break; + + // z position of the third corner + case 32: + vip[2].z = reader.ValueAsFloat(); + b[0] = true; + break; + + // x position of the fourth corner + case 13: + vip[3].x = reader.ValueAsFloat(); + b[1] = true; + break; + + // y position of the fourth corner + case 23: + vip[3].y = reader.ValueAsFloat(); + b[1] = true; + break; + + // z position of the fourth corner + case 33: + vip[3].z = reader.ValueAsFloat(); + b[1] = true; + break; + + // color + case 62: + clr = g_aclrDxfIndexColors[reader.ValueAsUnsignedInt() % AI_DXF_NUM_INDEX_COLORS]; + break; + }; + + ++reader; + } + + // the fourth corner may even be identical to the third, + // in this case we treat it as if it didn't exist. + if (vip[3] == vip[2]) { + b[1] = false; + } + + // sanity checks to see if we got something meaningful + if ((b[1] && !b[0]) || !b[2] || !b[3]) { + ASSIMP_LOG_WARN("DXF: unexpected vertex setup in 3DFACE/LINE/FACE entity; ignoring"); + output.blocks.back().lines.pop_back(); + return; + } + + const unsigned int cnt = (2+(b[0]?1:0)+(b[1]?1:0)); + line.counts.push_back(cnt); + + for (unsigned int i = 0; i < cnt; ++i) { + line.indices.push_back(static_cast<unsigned int>(line.positions.size())); + line.positions.push_back(vip[i]); + line.colors.push_back(clr); + } +} + +#endif // !! ASSIMP_BUILD_NO_DXF_IMPORTER diff --git a/libs/assimp/code/AssetLib/DXF/DXFLoader.h b/libs/assimp/code/AssetLib/DXF/DXFLoader.h new file mode 100644 index 0000000..b32ae10 --- /dev/null +++ b/libs/assimp/code/AssetLib/DXF/DXFLoader.h @@ -0,0 +1,146 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 DXFLoader.h + * @brief Declaration of the .dxf importer class. + */ +#pragma once +#ifndef AI_DXFLOADER_H_INCLUDED +#define AI_DXFLOADER_H_INCLUDED + +#include <assimp/BaseImporter.h> +#include <map> + +namespace Assimp { + +// Forward declarations +namespace DXF { + class LineReader; + struct FileData; + struct PolyLine; + struct Block; + struct InsertBlock; + + using BlockMap = std::map<std::string, const DXF::Block*>; +} + +// --------------------------------------------------------------------------- +/** + * @brief DXF importer implementation. + */ +class DXFImporter : public BaseImporter { +public: + DXFImporter(); + ~DXFImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details*/ + const aiImporterDesc* GetInfo () const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details */ + void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) override; + +private: + // ----------------------------------------------------- + void SkipSection(DXF::LineReader& reader); + + // ----------------------------------------------------- + void ParseHeader(DXF::LineReader& reader, + DXF::FileData& output); + + // ----------------------------------------------------- + void ParseEntities(DXF::LineReader& reader, + DXF::FileData& output); + + // ----------------------------------------------------- + void ParseBlocks(DXF::LineReader& reader, + DXF::FileData& output); + + // ----------------------------------------------------- + void ParseBlock(DXF::LineReader& reader, + DXF::FileData& output); + + // ----------------------------------------------------- + void ParseInsertion(DXF::LineReader& reader, + DXF::FileData& output); + + // ----------------------------------------------------- + void ParsePolyLine(DXF::LineReader& reader, + DXF::FileData& output); + + // ----------------------------------------------------- + void ParsePolyLineVertex(DXF::LineReader& reader, + DXF::PolyLine& line); + + // ----------------------------------------------------- + void Parse3DFace(DXF::LineReader& reader, + DXF::FileData& output); + + // ----------------------------------------------------- + void ConvertMeshes(aiScene* pScene, + DXF::FileData& output); + + // ----------------------------------------------------- + void GenerateHierarchy(aiScene* pScene, + DXF::FileData& output); + + // ----------------------------------------------------- + void GenerateMaterials(aiScene* pScene, + DXF::FileData& output); + + // ----------------------------------------------------- + void ExpandBlockReferences(DXF::Block& bl, + const DXF::BlockMap& blocks_by_name); +}; + +} // end of namespace Assimp + +#endif // AI_3DSIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/FBX/FBXAnimation.cpp b/libs/assimp/code/AssetLib/FBX/FBXAnimation.cpp new file mode 100644 index 0000000..2fa3b7b --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXAnimation.cpp @@ -0,0 +1,290 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXAnimation.cpp + * @brief Assimp::FBX::AnimationCurve, Assimp::FBX::AnimationCurveNode, + * Assimp::FBX::AnimationLayer, Assimp::FBX::AnimationStack + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +#include "FBXDocument.h" +#include "FBXDocumentUtil.h" +#include "FBXImporter.h" +#include "FBXParser.h" + +namespace Assimp { +namespace FBX { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +AnimationCurve::AnimationCurve(uint64_t id, const Element &element, const std::string &name, const Document & /*doc*/) : + Object(id, element, name) { + const Scope &sc = GetRequiredScope(element); + const Element &KeyTime = GetRequiredElement(sc, "KeyTime"); + const Element &KeyValueFloat = GetRequiredElement(sc, "KeyValueFloat"); + + ParseVectorDataArray(keys, KeyTime); + ParseVectorDataArray(values, KeyValueFloat); + + if (keys.size() != values.size()) { + DOMError("the number of key times does not match the number of keyframe values", &KeyTime); + } + + // check if the key times are well-ordered + if (!std::equal(keys.begin(), keys.end() - 1, keys.begin() + 1, std::less<KeyTimeList::value_type>())) { + DOMError("the keyframes are not in ascending order", &KeyTime); + } + + const Element *KeyAttrDataFloat = sc["KeyAttrDataFloat"]; + if (KeyAttrDataFloat) { + ParseVectorDataArray(attributes, *KeyAttrDataFloat); + } + + const Element *KeyAttrFlags = sc["KeyAttrFlags"]; + if (KeyAttrFlags) { + ParseVectorDataArray(flags, *KeyAttrFlags); + } +} + +// ------------------------------------------------------------------------------------------------ +AnimationCurve::~AnimationCurve() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +AnimationCurveNode::AnimationCurveNode(uint64_t id, const Element &element, const std::string &name, + const Document &doc, const char *const *target_prop_whitelist /*= nullptr*/, + size_t whitelist_size /*= 0*/) : + Object(id, element, name), target(), doc(doc) { + const Scope &sc = GetRequiredScope(element); + + // find target node + const char *whitelist[] = { "Model", "NodeAttribute", "Deformer" }; + const std::vector<const Connection *> &conns = doc.GetConnectionsBySourceSequenced(ID(), whitelist, 3); + + for (const Connection *con : conns) { + + // link should go for a property + if (!con->PropertyName().length()) { + continue; + } + + if (target_prop_whitelist) { + const char *const s = con->PropertyName().c_str(); + bool ok = false; + for (size_t i = 0; i < whitelist_size; ++i) { + if (!strcmp(s, target_prop_whitelist[i])) { + ok = true; + break; + } + } + + if (!ok) { + throw std::range_error("AnimationCurveNode target property is not in whitelist"); + } + } + + const Object *const ob = con->DestinationObject(); + if (!ob) { + DOMWarning("failed to read destination object for AnimationCurveNode->Model link, ignoring", &element); + continue; + } + + target = ob; + if (!target) { + continue; + } + + prop = con->PropertyName(); + break; + } + + if (!target) { + DOMWarning("failed to resolve target Model/NodeAttribute/Constraint for AnimationCurveNode", &element); + } + + props = GetPropertyTable(doc, "AnimationCurveNode.FbxAnimCurveNode", element, sc, false); +} + +// ------------------------------------------------------------------------------------------------ +AnimationCurveNode::~AnimationCurveNode() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +const AnimationCurveMap &AnimationCurveNode::Curves() const { + if (curves.empty()) { + // resolve attached animation curves + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationCurve"); + + for (const Connection *con : conns) { + + // link should go for a property + if (!con->PropertyName().length()) { + continue; + } + + const Object *const ob = con->SourceObject(); + if (nullptr == ob) { + DOMWarning("failed to read source object for AnimationCurve->AnimationCurveNode link, ignoring", &element); + continue; + } + + const AnimationCurve *const anim = dynamic_cast<const AnimationCurve *>(ob); + if (nullptr == anim) { + DOMWarning("source object for ->AnimationCurveNode link is not an AnimationCurve", &element); + continue; + } + + curves[con->PropertyName()] = anim; + } + } + + return curves; +} + +// ------------------------------------------------------------------------------------------------ +AnimationLayer::AnimationLayer(uint64_t id, const Element &element, const std::string &name, const Document &doc) : + Object(id, element, name), doc(doc) { + const Scope &sc = GetRequiredScope(element); + + // note: the props table here bears little importance and is usually absent + props = GetPropertyTable(doc, "AnimationLayer.FbxAnimLayer", element, sc, true); +} + +// ------------------------------------------------------------------------------------------------ +AnimationLayer::~AnimationLayer() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +AnimationCurveNodeList AnimationLayer::Nodes(const char *const *target_prop_whitelist /*= nullptr*/, + size_t whitelist_size /*= 0*/) const { + AnimationCurveNodeList nodes; + + // resolve attached animation nodes + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationCurveNode"); + nodes.reserve(conns.size()); + + for (const Connection *con : conns) { + + // link should not go to a property + if (con->PropertyName().length()) { + continue; + } + + const Object *const ob = con->SourceObject(); + if (!ob) { + DOMWarning("failed to read source object for AnimationCurveNode->AnimationLayer link, ignoring", &element); + continue; + } + + const AnimationCurveNode *const anim = dynamic_cast<const AnimationCurveNode *>(ob); + if (!anim) { + DOMWarning("source object for ->AnimationLayer link is not an AnimationCurveNode", &element); + continue; + } + + if (target_prop_whitelist) { + const char *s = anim->TargetProperty().c_str(); + bool ok = false; + for (size_t i = 0; i < whitelist_size; ++i) { + if (!strcmp(s, target_prop_whitelist[i])) { + ok = true; + break; + } + } + if (!ok) { + continue; + } + } + nodes.push_back(anim); + } + + return nodes; // pray for NRVO +} + +// ------------------------------------------------------------------------------------------------ +AnimationStack::AnimationStack(uint64_t id, const Element &element, const std::string &name, const Document &doc) : + Object(id, element, name) { + const Scope &sc = GetRequiredScope(element); + + // note: we don't currently use any of these properties so we shouldn't bother if it is missing + props = GetPropertyTable(doc, "AnimationStack.FbxAnimStack", element, sc, true); + + // resolve attached animation layers + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationLayer"); + layers.reserve(conns.size()); + + for (const Connection *con : conns) { + + // link should not go to a property + if (con->PropertyName().length()) { + continue; + } + + const Object *const ob = con->SourceObject(); + if (!ob) { + DOMWarning("failed to read source object for AnimationLayer->AnimationStack link, ignoring", &element); + continue; + } + + const AnimationLayer *const anim = dynamic_cast<const AnimationLayer *>(ob); + if (!anim) { + DOMWarning("source object for ->AnimationStack link is not an AnimationLayer", &element); + continue; + } + layers.push_back(anim); + } +} + +// ------------------------------------------------------------------------------------------------ +AnimationStack::~AnimationStack() { + // empty +} + +} // namespace FBX +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_FBX_IMPORTER diff --git a/libs/assimp/code/AssetLib/FBX/FBXBinaryTokenizer.cpp b/libs/assimp/code/AssetLib/FBX/FBXBinaryTokenizer.cpp new file mode 100644 index 0000000..1a4d118 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXBinaryTokenizer.cpp @@ -0,0 +1,485 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXBinaryTokenizer.cpp + * @brief Implementation of a fake lexer for binary fbx files - + * we emit tokens so the parser needs almost no special handling + * for binary files. + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +#include "FBXTokenizer.h" +#include "FBXUtil.h" +#include <assimp/defs.h> +#include <stdint.h> +#include <assimp/Exceptional.h> +#include <assimp/ByteSwapper.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/StringUtils.h> + +namespace Assimp { +namespace FBX { + +//enum Flag +//{ +// e_unknown_0 = 1 << 0, +// e_unknown_1 = 1 << 1, +// e_unknown_2 = 1 << 2, +// e_unknown_3 = 1 << 3, +// e_unknown_4 = 1 << 4, +// e_unknown_5 = 1 << 5, +// e_unknown_6 = 1 << 6, +// e_unknown_7 = 1 << 7, +// e_unknown_8 = 1 << 8, +// e_unknown_9 = 1 << 9, +// e_unknown_10 = 1 << 10, +// e_unknown_11 = 1 << 11, +// e_unknown_12 = 1 << 12, +// e_unknown_13 = 1 << 13, +// e_unknown_14 = 1 << 14, +// e_unknown_15 = 1 << 15, +// e_unknown_16 = 1 << 16, +// e_unknown_17 = 1 << 17, +// e_unknown_18 = 1 << 18, +// e_unknown_19 = 1 << 19, +// e_unknown_20 = 1 << 20, +// e_unknown_21 = 1 << 21, +// e_unknown_22 = 1 << 22, +// e_unknown_23 = 1 << 23, +// e_flag_field_size_64_bit = 1 << 24, // Not sure what is +// e_unknown_25 = 1 << 25, +// e_unknown_26 = 1 << 26, +// e_unknown_27 = 1 << 27, +// e_unknown_28 = 1 << 28, +// e_unknown_29 = 1 << 29, +// e_unknown_30 = 1 << 30, +// e_unknown_31 = 1 << 31 +//}; +// +//bool check_flag(uint32_t flags, Flag to_check) +//{ +// return (flags & to_check) != 0; +//} +// ------------------------------------------------------------------------------------------------ +Token::Token(const char* sbegin, const char* send, TokenType type, size_t offset) + : + #ifdef DEBUG + contents(sbegin, static_cast<size_t>(send-sbegin)), + #endif + sbegin(sbegin) + , send(send) + , type(type) + , line(offset) + , column(BINARY_MARKER) +{ + ai_assert(sbegin); + ai_assert(send); + + // binary tokens may have zero length because they are sometimes dummies + // inserted by TokenizeBinary() + ai_assert(send >= sbegin); +} + + +namespace { + +// ------------------------------------------------------------------------------------------------ +// signal tokenization error, this is always unrecoverable. Throws DeadlyImportError. +AI_WONT_RETURN void TokenizeError(const std::string& message, size_t offset) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN void TokenizeError(const std::string& message, size_t offset) +{ + throw DeadlyImportError("FBX-Tokenize", Util::GetOffsetText(offset), message); +} + + +// ------------------------------------------------------------------------------------------------ +size_t Offset(const char* begin, const char* cursor) { + ai_assert(begin <= cursor); + + return cursor - begin; +} + +// ------------------------------------------------------------------------------------------------ +void TokenizeError(const std::string& message, const char* begin, const char* cursor) { + TokenizeError(message, Offset(begin, cursor)); +} + +// ------------------------------------------------------------------------------------------------ +uint32_t ReadWord(const char* input, const char*& cursor, const char* end) { + const size_t k_to_read = sizeof( uint32_t ); + if(Offset(cursor, end) < k_to_read ) { + TokenizeError("cannot ReadWord, out of bounds",input, cursor); + } + + uint32_t word; + ::memcpy(&word, cursor, 4); + AI_SWAP4(word); + + cursor += k_to_read; + + return word; +} + +// ------------------------------------------------------------------------------------------------ +uint64_t ReadDoubleWord(const char* input, const char*& cursor, const char* end) { + const size_t k_to_read = sizeof(uint64_t); + if(Offset(cursor, end) < k_to_read) { + TokenizeError("cannot ReadDoubleWord, out of bounds",input, cursor); + } + + uint64_t dword /*= *reinterpret_cast<const uint64_t*>(cursor)*/; + ::memcpy( &dword, cursor, sizeof( uint64_t ) ); + AI_SWAP8(dword); + + cursor += k_to_read; + + return dword; +} + +// ------------------------------------------------------------------------------------------------ +uint8_t ReadByte(const char* input, const char*& cursor, const char* end) { + if(Offset(cursor, end) < sizeof( uint8_t ) ) { + TokenizeError("cannot ReadByte, out of bounds",input, cursor); + } + + uint8_t word;/* = *reinterpret_cast< const uint8_t* >( cursor )*/ + ::memcpy( &word, cursor, sizeof( uint8_t ) ); + ++cursor; + + return word; +} + +// ------------------------------------------------------------------------------------------------ +unsigned int ReadString(const char*& sbegin_out, const char*& send_out, const char* input, + const char*& cursor, const char* end, bool long_length = false, bool allow_null = false) { + const uint32_t len_len = long_length ? 4 : 1; + if(Offset(cursor, end) < len_len) { + TokenizeError("cannot ReadString, out of bounds reading length",input, cursor); + } + + const uint32_t length = long_length ? ReadWord(input, cursor, end) : ReadByte(input, cursor, end); + + if (Offset(cursor, end) < length) { + TokenizeError("cannot ReadString, length is out of bounds",input, cursor); + } + + sbegin_out = cursor; + cursor += length; + + send_out = cursor; + + if(!allow_null) { + for (unsigned int i = 0; i < length; ++i) { + if(sbegin_out[i] == '\0') { + TokenizeError("failed ReadString, unexpected NUL character in string",input, cursor); + } + } + } + + return length; +} + +// ------------------------------------------------------------------------------------------------ +void ReadData(const char*& sbegin_out, const char*& send_out, const char* input, const char*& cursor, const char* end) { + if(Offset(cursor, end) < 1) { + TokenizeError("cannot ReadData, out of bounds reading length",input, cursor); + } + + const char type = *cursor; + sbegin_out = cursor++; + + switch(type) + { + // 16 bit int + case 'Y': + cursor += 2; + break; + + // 1 bit bool flag (yes/no) + case 'C': + cursor += 1; + break; + + // 32 bit int + case 'I': + // <- fall through + + // float + case 'F': + cursor += 4; + break; + + // double + case 'D': + cursor += 8; + break; + + // 64 bit int + case 'L': + cursor += 8; + break; + + // note: do not write cursor += ReadWord(...cursor) as this would be UB + + // raw binary data + case 'R': + { + const uint32_t length = ReadWord(input, cursor, end); + cursor += length; + break; + } + + case 'b': + // TODO: what is the 'b' type code? Right now we just skip over it / + // take the full range we could get + cursor = end; + break; + + // array of * + case 'f': + case 'd': + case 'l': + case 'i': + case 'c': { + const uint32_t length = ReadWord(input, cursor, end); + const uint32_t encoding = ReadWord(input, cursor, end); + + const uint32_t comp_len = ReadWord(input, cursor, end); + + // compute length based on type and check against the stored value + if(encoding == 0) { + uint32_t stride = 0; + switch(type) + { + case 'f': + case 'i': + stride = 4; + break; + + case 'd': + case 'l': + stride = 8; + break; + + case 'c': + stride = 1; + break; + + default: + ai_assert(false); + }; + ai_assert(stride > 0); + if(length * stride != comp_len) { + TokenizeError("cannot ReadData, calculated data stride differs from what the file claims",input, cursor); + } + } + // zip/deflate algorithm (encoding==1)? take given length. anything else? die + else if (encoding != 1) { + TokenizeError("cannot ReadData, unknown encoding",input, cursor); + } + cursor += comp_len; + break; + } + + // string + case 'S': { + const char* sb, *se; + // 0 characters can legally happen in such strings + ReadString(sb, se, input, cursor, end, true, true); + break; + } + default: + TokenizeError("cannot ReadData, unexpected type code: " + std::string(&type, 1),input, cursor); + } + + if(cursor > end) { + TokenizeError("cannot ReadData, the remaining size is too small for the data type: " + std::string(&type, 1),input, cursor); + } + + // the type code is contained in the returned range + send_out = cursor; +} + + +// ------------------------------------------------------------------------------------------------ +bool ReadScope(TokenList& output_tokens, const char* input, const char*& cursor, const char* end, bool const is64bits) +{ + // the first word contains the offset at which this block ends + const uint64_t end_offset = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end); + + // we may get 0 if reading reached the end of the file - + // fbx files have a mysterious extra footer which I don't know + // how to extract any information from, but at least it always + // starts with a 0. + if(!end_offset) { + return false; + } + + if(end_offset > Offset(input, end)) { + TokenizeError("block offset is out of range",input, cursor); + } + else if(end_offset < Offset(input, cursor)) { + TokenizeError("block offset is negative out of range",input, cursor); + } + + // the second data word contains the number of properties in the scope + const uint64_t prop_count = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end); + + // the third data word contains the length of the property list + const uint64_t prop_length = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end); + + // now comes the name of the scope/key + const char* sbeg, *send; + ReadString(sbeg, send, input, cursor, end); + + output_tokens.push_back(new_Token(sbeg, send, TokenType_KEY, Offset(input, cursor) )); + + // now come the individual properties + const char* begin_cursor = cursor; + + if ((begin_cursor + prop_length) > end) { + TokenizeError("property length out of bounds reading length ", input, cursor); + } + + for (unsigned int i = 0; i < prop_count; ++i) { + ReadData(sbeg, send, input, cursor, begin_cursor + prop_length); + + output_tokens.push_back(new_Token(sbeg, send, TokenType_DATA, Offset(input, cursor) )); + + if(i != prop_count-1) { + output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_COMMA, Offset(input, cursor) )); + } + } + + if (Offset(begin_cursor, cursor) != prop_length) { + TokenizeError("property length not reached, something is wrong",input, cursor); + } + + // at the end of each nested block, there is a NUL record to indicate + // that the sub-scope exists (i.e. to distinguish between P: and P : {}) + // this NUL record is 13 bytes long on 32 bit version and 25 bytes long on 64 bit. + const size_t sentinel_block_length = is64bits ? (sizeof(uint64_t)* 3 + 1) : (sizeof(uint32_t)* 3 + 1); + + if (Offset(input, cursor) < end_offset) { + if (end_offset - Offset(input, cursor) < sentinel_block_length) { + TokenizeError("insufficient padding bytes at block end",input, cursor); + } + + output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_OPEN_BRACKET, Offset(input, cursor) )); + + // XXX this is vulnerable to stack overflowing .. + while(Offset(input, cursor) < end_offset - sentinel_block_length) { + ReadScope(output_tokens, input, cursor, input + end_offset - sentinel_block_length, is64bits); + } + output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_CLOSE_BRACKET, Offset(input, cursor) )); + + for (unsigned int i = 0; i < sentinel_block_length; ++i) { + if(cursor[i] != '\0') { + TokenizeError("failed to read nested block sentinel, expected all bytes to be 0",input, cursor); + } + } + cursor += sentinel_block_length; + } + + if (Offset(input, cursor) != end_offset) { + TokenizeError("scope length not reached, something is wrong",input, cursor); + } + + return true; +} + +} // anonymous namespace + +// ------------------------------------------------------------------------------------------------ +// TODO: Test FBX Binary files newer than the 7500 version to check if the 64 bits address behaviour is consistent +void TokenizeBinary(TokenList& output_tokens, const char* input, size_t length) +{ + ai_assert(input); + ASSIMP_LOG_DEBUG("Tokenizing binary FBX file"); + + if(length < 0x1b) { + TokenizeError("file is too short",0); + } + + //uint32_t offset = 0x15; +/* const char* cursor = input + 0x15; + + const uint32_t flags = ReadWord(input, cursor, input + length); + + const uint8_t padding_0 = ReadByte(input, cursor, input + length); // unused + const uint8_t padding_1 = ReadByte(input, cursor, input + length); // unused*/ + + if (strncmp(input,"Kaydara FBX Binary",18)) { + TokenizeError("magic bytes not found",0); + } + + const char* cursor = input + 18; + /*Result ignored*/ ReadByte(input, cursor, input + length); + /*Result ignored*/ ReadByte(input, cursor, input + length); + /*Result ignored*/ ReadByte(input, cursor, input + length); + /*Result ignored*/ ReadByte(input, cursor, input + length); + /*Result ignored*/ ReadByte(input, cursor, input + length); + const uint32_t version = ReadWord(input, cursor, input + length); + ASSIMP_LOG_DEBUG("FBX version: ", version); + const bool is64bits = version >= 7500; + const char *end = input + length; + try + { + while (cursor < end ) { + if (!ReadScope(output_tokens, input, cursor, input + length, is64bits)) { + break; + } + } + } + catch (const DeadlyImportError& e) + { + if (!is64bits && (length > std::numeric_limits<std::uint32_t>::max())) { + throw DeadlyImportError("The FBX file is invalid. This may be because the content is too big for this older version (", ai_to_string(version), ") of the FBX format. (", e.what(), ")"); + } + throw; + } +} + +} // !FBX +} // !Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/FBX/FBXCommon.h b/libs/assimp/code/AssetLib/FBX/FBXCommon.h new file mode 100644 index 0000000..ec7459c --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXCommon.h @@ -0,0 +1,89 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXCommon.h +* Some useful constants and enums for dealing with FBX files. +*/ +#ifndef AI_FBXCOMMON_H_INC +#define AI_FBXCOMMON_H_INC + +#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER + +namespace Assimp { +namespace FBX { + +const std::string NULL_RECORD = { // 25 null bytes in 64-bit and 13 null bytes in 32-bit + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' +}; // who knows why, it looks like two integers 32/64 bit (compressed and uncompressed sizes?) + 1 byte (might be compression type?) +const std::string SEPARATOR = { '\x00', '\x01' }; // for use inside strings +const std::string MAGIC_NODE_TAG = "_$AssimpFbx$"; // from import +const int64_t SECOND = 46186158000; // FBX's kTime unit + +// rotation order. We'll probably use EulerXYZ for everything +enum RotOrder { + RotOrder_EulerXYZ = 0, + RotOrder_EulerXZY, + RotOrder_EulerYZX, + RotOrder_EulerYXZ, + RotOrder_EulerZXY, + RotOrder_EulerZYX, + + RotOrder_SphericXYZ, + + RotOrder_MAX // end-of-enum sentinel +}; + +// transformation inheritance method. Most of the time RSrs +enum TransformInheritance { + TransformInheritance_RrSs = 0, + TransformInheritance_RSrs, + TransformInheritance_Rrs, + + TransformInheritance_MAX // end-of-enum sentinel +}; + +} // namespace FBX +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_FBX_EXPORTER + +#endif // AI_FBXCOMMON_H_INC diff --git a/libs/assimp/code/AssetLib/FBX/FBXCompileConfig.h b/libs/assimp/code/AssetLib/FBX/FBXCompileConfig.h new file mode 100644 index 0000000..75787d3 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXCompileConfig.h @@ -0,0 +1,78 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXCompileConfig.h + * @brief FBX importer compile-time switches + */ +#ifndef INCLUDED_AI_FBX_COMPILECONFIG_H +#define INCLUDED_AI_FBX_COMPILECONFIG_H + +#include <map> +#include <set> + +// +#if _MSC_VER > 1500 || (defined __GNUC___) +# define ASSIMP_FBX_USE_UNORDERED_MULTIMAP +# else +# define fbx_unordered_map map +# define fbx_unordered_multimap multimap +# define fbx_unordered_set set +# define fbx_unordered_multiset multiset +#endif + +#ifdef ASSIMP_FBX_USE_UNORDERED_MULTIMAP +# include <unordered_map> +# include <unordered_set> +# if defined(_MSC_VER) && _MSC_VER <= 1600 +# define fbx_unordered_map tr1::unordered_map +# define fbx_unordered_multimap tr1::unordered_multimap +# define fbx_unordered_set tr1::unordered_set +# define fbx_unordered_multiset tr1::unordered_multiset +# else +# define fbx_unordered_map unordered_map +# define fbx_unordered_multimap unordered_multimap +# define fbx_unordered_set unordered_set +# define fbx_unordered_multiset unordered_multiset +# endif +#endif + +#endif // INCLUDED_AI_FBX_COMPILECONFIG_H diff --git a/libs/assimp/code/AssetLib/FBX/FBXConverter.cpp b/libs/assimp/code/AssetLib/FBX/FBXConverter.cpp new file mode 100644 index 0000000..3287210 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXConverter.cpp @@ -0,0 +1,3679 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXConverter.cpp + * @brief Implementation of the FBX DOM -> aiScene converter + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +#include "FBXConverter.h" +#include "FBXDocument.h" +#include "FBXImporter.h" +#include "FBXMeshGeometry.h" +#include "FBXParser.h" +#include "FBXProperties.h" +#include "FBXUtil.h" + +#include <assimp/MathFunctions.h> +#include <assimp/StringComparison.h> + +#include <assimp/scene.h> + +#include <assimp/CreateAnimMesh.h> +#include <assimp/StringUtils.h> +#include <assimp/commonMetaData.h> + +#include <stdlib.h> +#include <cstdint> +#include <iomanip> +#include <iostream> +#include <iterator> +#include <memory> +#include <sstream> +#include <tuple> +#include <vector> + +namespace Assimp { +namespace FBX { + +using namespace Util; + +#define MAGIC_NODE_TAG "_$AssimpFbx$" + +#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000LL + +FBXConverter::FBXConverter(aiScene *out, const Document &doc, bool removeEmptyBones) : + defaultMaterialIndex(), + mMeshes(), + lights(), + cameras(), + textures(), + materials_converted(), + textures_converted(), + meshes_converted(), + node_anim_chain_bits(), + mNodeNames(), + anim_fps(), + mSceneOut(out), + doc(doc), + mRemoveEmptyBones(removeEmptyBones) { + // animations need to be converted first since this will + // populate the node_anim_chain_bits map, which is needed + // to determine which nodes need to be generated. + ConvertAnimations(); + // Embedded textures in FBX could be connected to nothing but to itself, + // for instance Texture -> Video connection only but not to the main graph, + // The idea here is to traverse all objects to find these Textures and convert them, + // so later during material conversion it will find converted texture in the textures_converted array. + if (doc.Settings().readTextures) { + ConvertOrphanedEmbeddedTextures(); + } + ConvertRootNode(); + + if (doc.Settings().readAllMaterials) { + // unfortunately this means we have to evaluate all objects + for (const ObjectMap::value_type &v : doc.Objects()) { + + const Object *ob = v.second->Get(); + if (!ob) { + continue; + } + + const Material *mat = dynamic_cast<const Material *>(ob); + if (mat) { + + if (materials_converted.find(mat) == materials_converted.end()) { + ConvertMaterial(*mat, 0); + } + } + } + } + + ConvertGlobalSettings(); + TransferDataToScene(); + + // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE + // to make sure the scene passes assimp's validation. FBX files + // need not contain geometry (i.e. camera animations, raw armatures). + if (out->mNumMeshes == 0) { + out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } +} + +FBXConverter::~FBXConverter() { + std::for_each(mMeshes.begin(), mMeshes.end(), Util::delete_fun<aiMesh>()); + std::for_each(materials.begin(), materials.end(), Util::delete_fun<aiMaterial>()); + std::for_each(animations.begin(), animations.end(), Util::delete_fun<aiAnimation>()); + std::for_each(lights.begin(), lights.end(), Util::delete_fun<aiLight>()); + std::for_each(cameras.begin(), cameras.end(), Util::delete_fun<aiCamera>()); + std::for_each(textures.begin(), textures.end(), Util::delete_fun<aiTexture>()); +} + +void FBXConverter::ConvertRootNode() { + mSceneOut->mRootNode = new aiNode(); + std::string unique_name; + GetUniqueName("RootNode", unique_name); + mSceneOut->mRootNode->mName.Set(unique_name); + + // root has ID 0 + ConvertNodes(0L, mSceneOut->mRootNode, mSceneOut->mRootNode); +} + +static std::string getAncestorBaseName(const aiNode *node) { + const char *nodeName = nullptr; + size_t length = 0; + while (node && (!nodeName || length == 0)) { + nodeName = node->mName.C_Str(); + length = node->mName.length; + node = node->mParent; + } + + if (!nodeName || length == 0) { + return {}; + } + // could be std::string_view if c++17 available + return std::string(nodeName, length); +} + +// Make unique name +std::string FBXConverter::MakeUniqueNodeName(const Model *const model, const aiNode &parent) { + std::string original_name = FixNodeName(model->Name()); + if (original_name.empty()) { + original_name = getAncestorBaseName(&parent); + } + std::string unique_name; + GetUniqueName(original_name, unique_name); + return unique_name; +} + +/// This struct manages nodes which may or may not end up in the node hierarchy. +/// When a node becomes a child of another node, that node becomes its owner and mOwnership should be released. +struct FBXConverter::PotentialNode +{ + PotentialNode() : mOwnership(new aiNode), mNode(mOwnership.get()) {} + PotentialNode(const std::string& name) : mOwnership(new aiNode(name)), mNode(mOwnership.get()) {} + aiNode* operator->() { return mNode; } + std::unique_ptr<aiNode> mOwnership; + aiNode* mNode; +}; + +/// todo: pre-build node hierarchy +/// todo: get bone from stack +/// todo: make map of aiBone* to aiNode* +/// then update convert clusters to the new format +void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) { + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(id, "Model"); + + std::vector<PotentialNode> nodes; + nodes.reserve(conns.size()); + + std::vector<PotentialNode> nodes_chain; + std::vector<PotentialNode> post_nodes_chain; + + for (const Connection *con : conns) { + // ignore object-property links + if (con->PropertyName().length()) { + // really important we document why this is ignored. + FBXImporter::LogInfo("ignoring property link - no docs on why this is ignored"); + continue; //? + } + + // convert connection source object into Object base class + const Object *const object = con->SourceObject(); + if (nullptr == object) { + FBXImporter::LogError("failed to convert source object for Model link"); + continue; + } + + // FBX Model::Cube, Model::Bone001, etc elements + // This detects if we can cast the object into this model structure. + const Model *const model = dynamic_cast<const Model *>(object); + + if (nullptr != model) { + nodes_chain.clear(); + post_nodes_chain.clear(); + + aiMatrix4x4 new_abs_transform = parent->mTransformation; + std::string node_name = FixNodeName(model->Name()); + // even though there is only a single input node, the design of + // assimp (or rather: the complicated transformation chain that + // is employed by fbx) means that we may need multiple aiNode's + // to represent a fbx node's transformation. + + // generate node transforms - this includes pivot data + // if need_additional_node is true then you t + const bool need_additional_node = GenerateTransformationNodeChain(*model, node_name, nodes_chain, post_nodes_chain); + + // assert that for the current node we must have at least a single transform + ai_assert(nodes_chain.size()); + + if (need_additional_node) { + nodes_chain.emplace_back(PotentialNode(node_name)); + } + + //setup metadata on newest node + SetupNodeMetadata(*model, *nodes_chain.back().mNode); + + // link all nodes in a row + aiNode *last_parent = parent; + for (PotentialNode& child : nodes_chain) { + ai_assert(child.mNode); + + if (last_parent != parent) { + last_parent->mNumChildren = 1; + last_parent->mChildren = new aiNode *[1]; + last_parent->mChildren[0] = child.mOwnership.release(); + } + + child->mParent = last_parent; + last_parent = child.mNode; + + new_abs_transform *= child->mTransformation; + } + + // attach geometry + ConvertModel(*model, nodes_chain.back().mNode, root_node, new_abs_transform); + + // check if there will be any child nodes + const std::vector<const Connection *> &child_conns = doc.GetConnectionsByDestinationSequenced(model->ID(), "Model"); + + // if so, link the geometric transform inverse nodes + // before we attach any child nodes + if (child_conns.size()) { + for (PotentialNode& postnode : post_nodes_chain) { + ai_assert(postnode.mNode); + + if (last_parent != parent) { + last_parent->mNumChildren = 1; + last_parent->mChildren = new aiNode *[1]; + last_parent->mChildren[0] = postnode.mOwnership.release(); + } + + postnode->mParent = last_parent; + last_parent = postnode.mNode; + + new_abs_transform *= postnode->mTransformation; + } + } else { + // free the nodes we allocated as we don't need them + post_nodes_chain.clear(); + } + + // recursion call - child nodes + ConvertNodes(model->ID(), last_parent, root_node); + + if (doc.Settings().readLights) { + ConvertLights(*model, node_name); + } + + if (doc.Settings().readCameras) { + ConvertCameras(*model, node_name); + } + + nodes.push_back(std::move(nodes_chain.front())); + nodes_chain.clear(); + } + } + + if (nodes.size()) { + parent->mChildren = new aiNode *[nodes.size()](); + parent->mNumChildren = static_cast<unsigned int>(nodes.size()); + + for (unsigned int i = 0; i < nodes.size(); ++i) + { + parent->mChildren[i] = nodes[i].mOwnership.release(); + } + nodes.clear(); + } else { + parent->mNumChildren = 0; + parent->mChildren = nullptr; + } +} + +void FBXConverter::ConvertLights(const Model &model, const std::string &orig_name) { + const std::vector<const NodeAttribute *> &node_attrs = model.GetAttributes(); + for (const NodeAttribute *attr : node_attrs) { + const Light *const light = dynamic_cast<const Light *>(attr); + if (light) { + ConvertLight(*light, orig_name); + } + } +} + +void FBXConverter::ConvertCameras(const Model &model, const std::string &orig_name) { + const std::vector<const NodeAttribute *> &node_attrs = model.GetAttributes(); + for (const NodeAttribute *attr : node_attrs) { + const Camera *const cam = dynamic_cast<const Camera *>(attr); + if (cam) { + ConvertCamera(*cam, orig_name); + } + } +} + +void FBXConverter::ConvertLight(const Light &light, const std::string &orig_name) { + lights.push_back(new aiLight()); + aiLight *const out_light = lights.back(); + + out_light->mName.Set(orig_name); + + const float intensity = light.Intensity() / 100.0f; + const aiVector3D &col = light.Color(); + + out_light->mColorDiffuse = aiColor3D(col.x, col.y, col.z); + out_light->mColorDiffuse.r *= intensity; + out_light->mColorDiffuse.g *= intensity; + out_light->mColorDiffuse.b *= intensity; + + out_light->mColorSpecular = out_light->mColorDiffuse; + + //lights are defined along negative y direction + out_light->mPosition = aiVector3D(0.0f); + out_light->mDirection = aiVector3D(0.0f, -1.0f, 0.0f); + out_light->mUp = aiVector3D(0.0f, 0.0f, -1.0f); + + switch (light.LightType()) { + case Light::Type_Point: + out_light->mType = aiLightSource_POINT; + break; + + case Light::Type_Directional: + out_light->mType = aiLightSource_DIRECTIONAL; + break; + + case Light::Type_Spot: + out_light->mType = aiLightSource_SPOT; + out_light->mAngleOuterCone = AI_DEG_TO_RAD(light.OuterAngle()); + out_light->mAngleInnerCone = AI_DEG_TO_RAD(light.InnerAngle()); + break; + + case Light::Type_Area: + FBXImporter::LogWarn("cannot represent area light, set to UNDEFINED"); + out_light->mType = aiLightSource_UNDEFINED; + break; + + case Light::Type_Volume: + FBXImporter::LogWarn("cannot represent volume light, set to UNDEFINED"); + out_light->mType = aiLightSource_UNDEFINED; + break; + default: + ai_assert(false); + } + + float decay = light.DecayStart(); + switch (light.DecayType()) { + case Light::Decay_None: + out_light->mAttenuationConstant = decay; + out_light->mAttenuationLinear = 0.0f; + out_light->mAttenuationQuadratic = 0.0f; + break; + case Light::Decay_Linear: + out_light->mAttenuationConstant = 0.0f; + out_light->mAttenuationLinear = 2.0f / decay; + out_light->mAttenuationQuadratic = 0.0f; + break; + case Light::Decay_Quadratic: + out_light->mAttenuationConstant = 0.0f; + out_light->mAttenuationLinear = 0.0f; + out_light->mAttenuationQuadratic = 2.0f / (decay * decay); + break; + case Light::Decay_Cubic: + FBXImporter::LogWarn("cannot represent cubic attenuation, set to Quadratic"); + out_light->mAttenuationQuadratic = 1.0f; + break; + default: + ai_assert(false); + break; + } +} + +void FBXConverter::ConvertCamera(const Camera &cam, const std::string &orig_name) { + cameras.push_back(new aiCamera()); + aiCamera *const out_camera = cameras.back(); + + out_camera->mName.Set(orig_name); + + out_camera->mAspect = cam.AspectWidth() / cam.AspectHeight(); + + out_camera->mPosition = aiVector3D(0.0f); + out_camera->mLookAt = aiVector3D(1.0f, 0.0f, 0.0f); + out_camera->mUp = aiVector3D(0.0f, 1.0f, 0.0f); + + out_camera->mHorizontalFOV = AI_DEG_TO_RAD(cam.FieldOfView()); + + out_camera->mClipPlaneNear = cam.NearPlane(); + out_camera->mClipPlaneFar = cam.FarPlane(); + + out_camera->mHorizontalFOV = AI_DEG_TO_RAD(cam.FieldOfView()); + out_camera->mClipPlaneNear = cam.NearPlane(); + out_camera->mClipPlaneFar = cam.FarPlane(); +} + +void FBXConverter::GetUniqueName(const std::string &name, std::string &uniqueName) { + uniqueName = name; + auto it_pair = mNodeNames.insert({ name, 0 }); // duplicate node name instance count + unsigned int &i = it_pair.first->second; + while (!it_pair.second) { + i++; + std::ostringstream ext; + ext << name << std::setfill('0') << std::setw(3) << i; + uniqueName = ext.str(); + it_pair = mNodeNames.insert({ uniqueName, 0 }); + } +} + +const char *FBXConverter::NameTransformationComp(TransformationComp comp) { + switch (comp) { + case TransformationComp_Translation: + return "Translation"; + case TransformationComp_RotationOffset: + return "RotationOffset"; + case TransformationComp_RotationPivot: + return "RotationPivot"; + case TransformationComp_PreRotation: + return "PreRotation"; + case TransformationComp_Rotation: + return "Rotation"; + case TransformationComp_PostRotation: + return "PostRotation"; + case TransformationComp_RotationPivotInverse: + return "RotationPivotInverse"; + case TransformationComp_ScalingOffset: + return "ScalingOffset"; + case TransformationComp_ScalingPivot: + return "ScalingPivot"; + case TransformationComp_Scaling: + return "Scaling"; + case TransformationComp_ScalingPivotInverse: + return "ScalingPivotInverse"; + case TransformationComp_GeometricScaling: + return "GeometricScaling"; + case TransformationComp_GeometricRotation: + return "GeometricRotation"; + case TransformationComp_GeometricTranslation: + return "GeometricTranslation"; + case TransformationComp_GeometricScalingInverse: + return "GeometricScalingInverse"; + case TransformationComp_GeometricRotationInverse: + return "GeometricRotationInverse"; + case TransformationComp_GeometricTranslationInverse: + return "GeometricTranslationInverse"; + case TransformationComp_MAXIMUM: // this is to silence compiler warnings + default: + break; + } + + ai_assert(false); + + return nullptr; +} + +const char *FBXConverter::NameTransformationCompProperty(TransformationComp comp) { + switch (comp) { + case TransformationComp_Translation: + return "Lcl Translation"; + case TransformationComp_RotationOffset: + return "RotationOffset"; + case TransformationComp_RotationPivot: + return "RotationPivot"; + case TransformationComp_PreRotation: + return "PreRotation"; + case TransformationComp_Rotation: + return "Lcl Rotation"; + case TransformationComp_PostRotation: + return "PostRotation"; + case TransformationComp_RotationPivotInverse: + return "RotationPivotInverse"; + case TransformationComp_ScalingOffset: + return "ScalingOffset"; + case TransformationComp_ScalingPivot: + return "ScalingPivot"; + case TransformationComp_Scaling: + return "Lcl Scaling"; + case TransformationComp_ScalingPivotInverse: + return "ScalingPivotInverse"; + case TransformationComp_GeometricScaling: + return "GeometricScaling"; + case TransformationComp_GeometricRotation: + return "GeometricRotation"; + case TransformationComp_GeometricTranslation: + return "GeometricTranslation"; + case TransformationComp_GeometricScalingInverse: + return "GeometricScalingInverse"; + case TransformationComp_GeometricRotationInverse: + return "GeometricRotationInverse"; + case TransformationComp_GeometricTranslationInverse: + return "GeometricTranslationInverse"; + case TransformationComp_MAXIMUM: // this is to silence compiler warnings + break; + } + + ai_assert(false); + + return nullptr; +} + +aiVector3D FBXConverter::TransformationCompDefaultValue(TransformationComp comp) { + // XXX a neat way to solve the never-ending special cases for scaling + // would be to do everything in log space! + return comp == TransformationComp_Scaling ? aiVector3D(1.f, 1.f, 1.f) : aiVector3D(); +} + +void FBXConverter::GetRotationMatrix(Model::RotOrder mode, const aiVector3D &rotation, aiMatrix4x4 &out) { + if (mode == Model::RotOrder_SphericXYZ) { + FBXImporter::LogError("Unsupported RotationMode: SphericXYZ"); + out = aiMatrix4x4(); + return; + } + + const float angle_epsilon = Math::getEpsilon<float>(); + + out = aiMatrix4x4(); + + bool is_id[3] = { true, true, true }; + + aiMatrix4x4 temp[3]; + if (std::fabs(rotation.z) > angle_epsilon) { + aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(rotation.z), temp[2]); + is_id[2] = false; + } + if (std::fabs(rotation.y) > angle_epsilon) { + aiMatrix4x4::RotationY(AI_DEG_TO_RAD(rotation.y), temp[1]); + is_id[1] = false; + } + if (std::fabs(rotation.x) > angle_epsilon) { + aiMatrix4x4::RotationX(AI_DEG_TO_RAD(rotation.x), temp[0]); + is_id[0] = false; + } + + int order[3] = { -1, -1, -1 }; + + // note: rotation order is inverted since we're left multiplying as is usual in assimp + switch (mode) { + case Model::RotOrder_EulerXYZ: + order[0] = 2; + order[1] = 1; + order[2] = 0; + break; + + case Model::RotOrder_EulerXZY: + order[0] = 1; + order[1] = 2; + order[2] = 0; + break; + + case Model::RotOrder_EulerYZX: + order[0] = 0; + order[1] = 2; + order[2] = 1; + break; + + case Model::RotOrder_EulerYXZ: + order[0] = 2; + order[1] = 0; + order[2] = 1; + break; + + case Model::RotOrder_EulerZXY: + order[0] = 1; + order[1] = 0; + order[2] = 2; + break; + + case Model::RotOrder_EulerZYX: + order[0] = 0; + order[1] = 1; + order[2] = 2; + break; + + default: + ai_assert(false); + break; + } + + ai_assert(order[0] >= 0); + ai_assert(order[0] <= 2); + ai_assert(order[1] >= 0); + ai_assert(order[1] <= 2); + ai_assert(order[2] >= 0); + ai_assert(order[2] <= 2); + + if (!is_id[order[0]]) { + out = temp[order[0]]; + } + + if (!is_id[order[1]]) { + out = out * temp[order[1]]; + } + + if (!is_id[order[2]]) { + out = out * temp[order[2]]; + } +} + +bool FBXConverter::NeedsComplexTransformationChain(const Model &model) { + const PropertyTable &props = model.Props(); + bool ok; + + const float zero_epsilon = ai_epsilon; + const aiVector3D all_ones(1.0f, 1.0f, 1.0f); + for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) { + const TransformationComp comp = static_cast<TransformationComp>(i); + + if (comp == TransformationComp_Rotation || comp == TransformationComp_Scaling || comp == TransformationComp_Translation || + comp == TransformationComp_PreRotation || comp == TransformationComp_PostRotation) { + continue; + } + + bool scale_compare = (comp == TransformationComp_GeometricScaling || comp == TransformationComp_Scaling); + + const aiVector3D &v = PropertyGet<aiVector3D>(props, NameTransformationCompProperty(comp), ok); + if (ok && scale_compare) { + if ((v - all_ones).SquareLength() > zero_epsilon) { + return true; + } + } else if (ok) { + if (v.SquareLength() > zero_epsilon) { + return true; + } + } + } + + return false; +} + +std::string FBXConverter::NameTransformationChainNode(const std::string &name, TransformationComp comp) { + return name + std::string(MAGIC_NODE_TAG) + "_" + NameTransformationComp(comp); +} + +bool FBXConverter::GenerateTransformationNodeChain(const Model &model, const std::string &name, std::vector<PotentialNode> &output_nodes, + std::vector<PotentialNode> &post_output_nodes) { + const PropertyTable &props = model.Props(); + const Model::RotOrder rot = model.RotationOrder(); + + bool ok; + + aiMatrix4x4 chain[TransformationComp_MAXIMUM]; + + ai_assert(TransformationComp_MAXIMUM < 32); + std::uint32_t chainBits = 0; + // A node won't need a node chain if it only has these. + const std::uint32_t chainMaskSimple = (1 << TransformationComp_Translation) + (1 << TransformationComp_Scaling) + (1 << TransformationComp_Rotation); + // A node will need a node chain if it has any of these. + const std::uint32_t chainMaskComplex = ((1 << (TransformationComp_MAXIMUM)) - 1) - chainMaskSimple; + + std::fill_n(chain, static_cast<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4()); + + // generate transformation matrices for all the different transformation components + const float zero_epsilon = Math::getEpsilon<float>(); + const aiVector3D all_ones(1.0f, 1.0f, 1.0f); + + const aiVector3D &PreRotation = PropertyGet<aiVector3D>(props, "PreRotation", ok); + if (ok && PreRotation.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_PreRotation); + + GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PreRotation, chain[TransformationComp_PreRotation]); + } + + const aiVector3D &PostRotation = PropertyGet<aiVector3D>(props, "PostRotation", ok); + if (ok && PostRotation.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_PostRotation); + + GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PostRotation, chain[TransformationComp_PostRotation]); + } + + const aiVector3D &RotationPivot = PropertyGet<aiVector3D>(props, "RotationPivot", ok); + if (ok && RotationPivot.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_RotationPivot) | (1 << TransformationComp_RotationPivotInverse); + + aiMatrix4x4::Translation(RotationPivot, chain[TransformationComp_RotationPivot]); + aiMatrix4x4::Translation(-RotationPivot, chain[TransformationComp_RotationPivotInverse]); + } + + const aiVector3D &RotationOffset = PropertyGet<aiVector3D>(props, "RotationOffset", ok); + if (ok && RotationOffset.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_RotationOffset); + + aiMatrix4x4::Translation(RotationOffset, chain[TransformationComp_RotationOffset]); + } + + const aiVector3D &ScalingOffset = PropertyGet<aiVector3D>(props, "ScalingOffset", ok); + if (ok && ScalingOffset.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_ScalingOffset); + + aiMatrix4x4::Translation(ScalingOffset, chain[TransformationComp_ScalingOffset]); + } + + const aiVector3D &ScalingPivot = PropertyGet<aiVector3D>(props, "ScalingPivot", ok); + if (ok && ScalingPivot.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_ScalingPivot) | (1 << TransformationComp_ScalingPivotInverse); + + aiMatrix4x4::Translation(ScalingPivot, chain[TransformationComp_ScalingPivot]); + aiMatrix4x4::Translation(-ScalingPivot, chain[TransformationComp_ScalingPivotInverse]); + } + + const aiVector3D &Translation = PropertyGet<aiVector3D>(props, "Lcl Translation", ok); + if (ok && Translation.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_Translation); + + aiMatrix4x4::Translation(Translation, chain[TransformationComp_Translation]); + } + + const aiVector3D &Scaling = PropertyGet<aiVector3D>(props, "Lcl Scaling", ok); + if (ok && (Scaling - all_ones).SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_Scaling); + + aiMatrix4x4::Scaling(Scaling, chain[TransformationComp_Scaling]); + } + + const aiVector3D &Rotation = PropertyGet<aiVector3D>(props, "Lcl Rotation", ok); + if (ok && Rotation.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_Rotation); + + GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]); + } + + const aiVector3D &GeometricScaling = PropertyGet<aiVector3D>(props, "GeometricScaling", ok); + if (ok && (GeometricScaling - all_ones).SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_GeometricScaling); + aiMatrix4x4::Scaling(GeometricScaling, chain[TransformationComp_GeometricScaling]); + aiVector3D GeometricScalingInverse = GeometricScaling; + bool canscale = true; + for (unsigned int i = 0; i < 3; ++i) { + if (std::fabs(GeometricScalingInverse[i]) > zero_epsilon) { + GeometricScalingInverse[i] = 1.0f / GeometricScaling[i]; + } else { + FBXImporter::LogError("cannot invert geometric scaling matrix with a 0.0 scale component"); + canscale = false; + break; + } + } + if (canscale) { + chainBits = chainBits | (1 << TransformationComp_GeometricScalingInverse); + aiMatrix4x4::Scaling(GeometricScalingInverse, chain[TransformationComp_GeometricScalingInverse]); + } + } + + const aiVector3D &GeometricRotation = PropertyGet<aiVector3D>(props, "GeometricRotation", ok); + if (ok && GeometricRotation.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_GeometricRotation) | (1 << TransformationComp_GeometricRotationInverse); + GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotation]); + GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotationInverse]); + chain[TransformationComp_GeometricRotationInverse].Inverse(); + } + + const aiVector3D &GeometricTranslation = PropertyGet<aiVector3D>(props, "GeometricTranslation", ok); + if (ok && GeometricTranslation.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_GeometricTranslation) | (1 << TransformationComp_GeometricTranslationInverse); + aiMatrix4x4::Translation(GeometricTranslation, chain[TransformationComp_GeometricTranslation]); + aiMatrix4x4::Translation(-GeometricTranslation, chain[TransformationComp_GeometricTranslationInverse]); + } + + // now, if we have more than just Translation, Scaling and Rotation, + // we need to generate a full node chain to accommodate for assimp's + // lack to express pivots and offsets. + if ((chainBits & chainMaskComplex) && doc.Settings().preservePivots) { + FBXImporter::LogInfo("generating full transformation chain for node: ", name); + + // query the anim_chain_bits dictionary to find out which chain elements + // have associated node animation channels. These can not be dropped + // even if they have identity transform in bind pose. + NodeAnimBitMap::const_iterator it = node_anim_chain_bits.find(name); + const unsigned int anim_chain_bitmask = (it == node_anim_chain_bits.end() ? 0 : (*it).second); + + unsigned int bit = 0x1; + for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) { + const TransformationComp comp = static_cast<TransformationComp>(i); + + if ((chainBits & bit) == 0 && (anim_chain_bitmask & bit) == 0) { + continue; + } + + if (comp == TransformationComp_PostRotation) { + chain[i] = chain[i].Inverse(); + } + + PotentialNode nd; + nd->mName.Set(NameTransformationChainNode(name, comp)); + nd->mTransformation = chain[i]; + + // geometric inverses go in a post-node chain + if (comp == TransformationComp_GeometricScalingInverse || + comp == TransformationComp_GeometricRotationInverse || + comp == TransformationComp_GeometricTranslationInverse) { + post_output_nodes.emplace_back(std::move(nd)); + } else { + output_nodes.emplace_back(std::move(nd)); + } + } + + ai_assert(output_nodes.size()); + return true; + } + + // else, we can just multiply the matrices together + PotentialNode nd; + + // name passed to the method is already unique + nd->mName.Set(name); + // for (const auto &transform : chain) { + // skip inverse chain for no preservePivots + for (unsigned int i = TransformationComp_Translation; i < TransformationComp_MAXIMUM; i++) { + nd->mTransformation = nd->mTransformation * chain[i]; + } + output_nodes.push_back(std::move(nd)); + return false; +} + +void FBXConverter::SetupNodeMetadata(const Model &model, aiNode &nd) { + const PropertyTable &props = model.Props(); + DirectPropertyMap unparsedProperties = props.GetUnparsedProperties(); + + // create metadata on node + const std::size_t numStaticMetaData = 2; + aiMetadata *data = aiMetadata::Alloc(static_cast<unsigned int>(unparsedProperties.size() + numStaticMetaData)); + nd.mMetaData = data; + int index = 0; + + // find user defined properties (3ds Max) + data->Set(index++, "UserProperties", aiString(PropertyGet<std::string>(props, "UDP3DSMAX", ""))); + // preserve the info that a node was marked as Null node in the original file. + data->Set(index++, "IsNull", model.IsNull() ? true : false); + + // add unparsed properties to the node's metadata + for (const DirectPropertyMap::value_type &prop : unparsedProperties) { + // Interpret the property as a concrete type + if (const TypedProperty<bool> *interpretedBool = prop.second->As<TypedProperty<bool>>()) { + data->Set(index++, prop.first, interpretedBool->Value()); + } else if (const TypedProperty<int> *interpretedInt = prop.second->As<TypedProperty<int>>()) { + data->Set(index++, prop.first, interpretedInt->Value()); + } else if (const TypedProperty<uint64_t> *interpretedUint64 = prop.second->As<TypedProperty<uint64_t>>()) { + data->Set(index++, prop.first, interpretedUint64->Value()); + } else if (const TypedProperty<float> *interpretedFloat = prop.second->As<TypedProperty<float>>()) { + data->Set(index++, prop.first, interpretedFloat->Value()); + } else if (const TypedProperty<std::string> *interpretedString = prop.second->As<TypedProperty<std::string>>()) { + data->Set(index++, prop.first, aiString(interpretedString->Value())); + } else if (const TypedProperty<aiVector3D> *interpretedVec3 = prop.second->As<TypedProperty<aiVector3D>>()) { + data->Set(index++, prop.first, interpretedVec3->Value()); + } else { + ai_assert(false); + } + } +} + +void FBXConverter::ConvertModel(const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { + const std::vector<const Geometry *> &geos = model.GetGeometry(); + + std::vector<unsigned int> meshes; + meshes.reserve(geos.size()); + + for (const Geometry *geo : geos) { + + const MeshGeometry *const mesh = dynamic_cast<const MeshGeometry *>(geo); + const LineGeometry *const line = dynamic_cast<const LineGeometry *>(geo); + if (mesh) { + const std::vector<unsigned int> &indices = ConvertMesh(*mesh, model, parent, root_node, + absolute_transform); + std::copy(indices.begin(), indices.end(), std::back_inserter(meshes)); + } else if (line) { + const std::vector<unsigned int> &indices = ConvertLine(*line, root_node); + std::copy(indices.begin(), indices.end(), std::back_inserter(meshes)); + } else if (geo) { + FBXImporter::LogWarn("ignoring unrecognized geometry: ", geo->Name()); + } else { + FBXImporter::LogWarn("skipping null geometry"); + } + } + + if (meshes.size()) { + parent->mMeshes = new unsigned int[meshes.size()](); + parent->mNumMeshes = static_cast<unsigned int>(meshes.size()); + + std::swap_ranges(meshes.begin(), meshes.end(), parent->mMeshes); + } +} + +std::vector<unsigned int> +FBXConverter::ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { + std::vector<unsigned int> temp; + + MeshMap::const_iterator it = meshes_converted.find(&mesh); + if (it != meshes_converted.end()) { + std::copy((*it).second.begin(), (*it).second.end(), std::back_inserter(temp)); + return temp; + } + + const std::vector<aiVector3D> &vertices = mesh.GetVertices(); + const std::vector<unsigned int> &faces = mesh.GetFaceIndexCounts(); + if (vertices.empty() || faces.empty()) { + FBXImporter::LogWarn("ignoring empty geometry: ", mesh.Name()); + return temp; + } + + // one material per mesh maps easily to aiMesh. Multiple material + // meshes need to be split. + const MatIndexArray &mindices = mesh.GetMaterialIndices(); + if (doc.Settings().readMaterials && !mindices.empty()) { + const MatIndexArray::value_type base = mindices[0]; + for (MatIndexArray::value_type index : mindices) { + if (index != base) { + return ConvertMeshMultiMaterial(mesh, model, parent, root_node, absolute_transform); + } + } + } + + // faster code-path, just copy the data + temp.push_back(ConvertMeshSingleMaterial(mesh, model, absolute_transform, parent, root_node)); + return temp; +} + +std::vector<unsigned int> FBXConverter::ConvertLine(const LineGeometry &line, aiNode *root_node) { + std::vector<unsigned int> temp; + + const std::vector<aiVector3D> &vertices = line.GetVertices(); + const std::vector<int> &indices = line.GetIndices(); + if (vertices.empty() || indices.empty()) { + FBXImporter::LogWarn("ignoring empty line: ", line.Name()); + return temp; + } + + aiMesh *const out_mesh = SetupEmptyMesh(line, root_node); + out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + + // copy vertices + out_mesh->mNumVertices = static_cast<unsigned int>(vertices.size()); + out_mesh->mVertices = new aiVector3D[out_mesh->mNumVertices]; + std::copy(vertices.begin(), vertices.end(), out_mesh->mVertices); + + //Number of line segments (faces) is "Number of Points - Number of Endpoints" + //N.B.: Endpoints in FbxLine are denoted by negative indices. + //If such an Index is encountered, add 1 and multiply by -1 to get the real index. + unsigned int epcount = 0; + for (unsigned i = 0; i < indices.size(); i++) { + if (indices[i] < 0) { + epcount++; + } + } + unsigned int pcount = static_cast<unsigned int>(indices.size()); + unsigned int scount = out_mesh->mNumFaces = pcount - epcount; + + aiFace *fac = out_mesh->mFaces = new aiFace[scount](); + for (unsigned int i = 0; i < pcount; ++i) { + if (indices[i] < 0) continue; + aiFace &f = *fac++; + f.mNumIndices = 2; //2 == aiPrimitiveType_LINE + f.mIndices = new unsigned int[2]; + f.mIndices[0] = indices[i]; + int segid = indices[(i + 1 == pcount ? 0 : i + 1)]; //If we have reached he last point, wrap around + f.mIndices[1] = (segid < 0 ? (segid + 1) * -1 : segid); //Convert EndPoint Index to normal Index + } + temp.push_back(static_cast<unsigned int>(mMeshes.size() - 1)); + return temp; +} + +aiMesh *FBXConverter::SetupEmptyMesh(const Geometry &mesh, aiNode *parent) { + aiMesh *const out_mesh = new aiMesh(); + mMeshes.push_back(out_mesh); + meshes_converted[&mesh].push_back(static_cast<unsigned int>(mMeshes.size() - 1)); + + // set name + std::string name = mesh.Name(); + if (name.substr(0, 10) == "Geometry::") { + name = name.substr(10); + } + + if (name.length()) { + out_mesh->mName.Set(name); + } else { + out_mesh->mName = parent->mName; + } + + return out_mesh; +} + +unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model, + const aiMatrix4x4 &absolute_transform, aiNode *parent, + aiNode *) { + const MatIndexArray &mindices = mesh.GetMaterialIndices(); + aiMesh *const out_mesh = SetupEmptyMesh(mesh, parent); + + const std::vector<aiVector3D> &vertices = mesh.GetVertices(); + const std::vector<unsigned int> &faces = mesh.GetFaceIndexCounts(); + + // copy vertices + out_mesh->mNumVertices = static_cast<unsigned int>(vertices.size()); + out_mesh->mVertices = new aiVector3D[vertices.size()]; + + std::copy(vertices.begin(), vertices.end(), out_mesh->mVertices); + + // generate dummy faces + out_mesh->mNumFaces = static_cast<unsigned int>(faces.size()); + aiFace *fac = out_mesh->mFaces = new aiFace[faces.size()](); + + unsigned int cursor = 0; + for (unsigned int pcount : faces) { + aiFace &f = *fac++; + f.mNumIndices = pcount; + f.mIndices = new unsigned int[pcount]; + switch (pcount) { + case 1: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + case 2: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + case 3: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + default: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + break; + } + for (unsigned int i = 0; i < pcount; ++i) { + f.mIndices[i] = cursor++; + } + } + + // copy normals + const std::vector<aiVector3D> &normals = mesh.GetNormals(); + if (normals.size()) { + ai_assert(normals.size() == vertices.size()); + + out_mesh->mNormals = new aiVector3D[vertices.size()]; + std::copy(normals.begin(), normals.end(), out_mesh->mNormals); + } + + // copy tangents - assimp requires both tangents and bitangents (binormals) + // to be present, or neither of them. Compute binormals from normals + // and tangents if needed. + const std::vector<aiVector3D> &tangents = mesh.GetTangents(); + const std::vector<aiVector3D> *binormals = &mesh.GetBinormals(); + + if (tangents.size()) { + std::vector<aiVector3D> tempBinormals; + if (!binormals->size()) { + if (normals.size()) { + tempBinormals.resize(normals.size()); + for (unsigned int i = 0; i < tangents.size(); ++i) { + tempBinormals[i] = normals[i] ^ tangents[i]; + } + + binormals = &tempBinormals; + } else { + binormals = nullptr; + } + } + + if (binormals) { + ai_assert(tangents.size() == vertices.size()); + ai_assert(binormals->size() == vertices.size()); + + out_mesh->mTangents = new aiVector3D[vertices.size()]; + std::copy(tangents.begin(), tangents.end(), out_mesh->mTangents); + + out_mesh->mBitangents = new aiVector3D[vertices.size()]; + std::copy(binormals->begin(), binormals->end(), out_mesh->mBitangents); + } + } + + // copy texture coords + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + const std::vector<aiVector2D> &uvs = mesh.GetTextureCoords(i); + if (uvs.empty()) { + break; + } + + aiVector3D *out_uv = out_mesh->mTextureCoords[i] = new aiVector3D[vertices.size()]; + for (const aiVector2D &v : uvs) { + *out_uv++ = aiVector3D(v.x, v.y, 0.0f); + } + + out_mesh->SetTextureCoordsName(i, aiString(mesh.GetTextureCoordChannelName(i))); + + out_mesh->mNumUVComponents[i] = 2; + } + + // copy vertex colors + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) { + const std::vector<aiColor4D> &colors = mesh.GetVertexColors(i); + if (colors.empty()) { + break; + } + + out_mesh->mColors[i] = new aiColor4D[vertices.size()]; + std::copy(colors.begin(), colors.end(), out_mesh->mColors[i]); + } + + if (!doc.Settings().readMaterials || mindices.empty()) { + FBXImporter::LogError("no material assigned to mesh, setting default material"); + out_mesh->mMaterialIndex = GetDefaultMaterial(); + } else { + ConvertMaterialForMesh(out_mesh, model, mesh, mindices[0]); + } + + if (doc.Settings().readWeights && mesh.DeformerSkin() != nullptr) { + ConvertWeights(out_mesh, mesh, absolute_transform, parent, NO_MATERIAL_SEPARATION, nullptr); + } + + std::vector<aiAnimMesh *> animMeshes; + for (const BlendShape *blendShape : mesh.GetBlendShapes()) { + for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) { + const std::vector<const ShapeGeometry *> &shapeGeometries = blendShapeChannel->GetShapeGeometries(); + for (size_t i = 0; i < shapeGeometries.size(); i++) { + aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh); + const ShapeGeometry *shapeGeometry = shapeGeometries.at(i); + const std::vector<aiVector3D> &curVertices = shapeGeometry->GetVertices(); + const std::vector<aiVector3D> &curNormals = shapeGeometry->GetNormals(); + const std::vector<unsigned int> &curIndices = shapeGeometry->GetIndices(); + //losing channel name if using shapeGeometry->Name() + animMesh->mName.Set(FixAnimMeshName(blendShapeChannel->Name())); + for (size_t j = 0; j < curIndices.size(); j++) { + const unsigned int curIndex = curIndices.at(j); + aiVector3D vertex = curVertices.at(j); + aiVector3D normal = curNormals.at(j); + unsigned int count = 0; + const unsigned int *outIndices = mesh.ToOutputVertexIndex(curIndex, count); + for (unsigned int k = 0; k < count; k++) { + unsigned int index = outIndices[k]; + animMesh->mVertices[index] += vertex; + if (animMesh->mNormals != nullptr) { + animMesh->mNormals[index] += normal; + animMesh->mNormals[index].NormalizeSafe(); + } + } + } + animMesh->mWeight = shapeGeometries.size() > 1 ? blendShapeChannel->DeformPercent() / 100.0f : 1.0f; + animMeshes.push_back(animMesh); + } + } + } + const size_t numAnimMeshes = animMeshes.size(); + if (numAnimMeshes > 0) { + out_mesh->mNumAnimMeshes = static_cast<unsigned int>(numAnimMeshes); + out_mesh->mAnimMeshes = new aiAnimMesh *[numAnimMeshes]; + for (size_t i = 0; i < numAnimMeshes; i++) { + out_mesh->mAnimMeshes[i] = animMeshes.at(i); + } + } + return static_cast<unsigned int>(mMeshes.size() - 1); +} + +std::vector<unsigned int> +FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent, + aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { + const MatIndexArray &mindices = mesh.GetMaterialIndices(); + ai_assert(mindices.size()); + + std::set<MatIndexArray::value_type> had; + std::vector<unsigned int> indices; + + for (MatIndexArray::value_type index : mindices) { + if (had.find(index) == had.end()) { + + indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, parent, root_node, absolute_transform)); + had.insert(index); + } + } + + return indices; +} + +unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, + MatIndexArray::value_type index, + aiNode *parent, aiNode *, + const aiMatrix4x4 &absolute_transform) { + aiMesh *const out_mesh = SetupEmptyMesh(mesh, parent); + + const MatIndexArray &mindices = mesh.GetMaterialIndices(); + const std::vector<aiVector3D> &vertices = mesh.GetVertices(); + const std::vector<unsigned int> &faces = mesh.GetFaceIndexCounts(); + + const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != nullptr; + + unsigned int count_faces = 0; + unsigned int count_vertices = 0; + + // count faces + std::vector<unsigned int>::const_iterator itf = faces.begin(); + for (MatIndexArray::const_iterator it = mindices.begin(), + end = mindices.end(); + it != end; ++it, ++itf) { + if ((*it) != index) { + continue; + } + ++count_faces; + count_vertices += *itf; + } + + ai_assert(count_faces); + ai_assert(count_vertices); + + // mapping from output indices to DOM indexing, needed to resolve weights or blendshapes + std::vector<unsigned int> reverseMapping; + std::map<unsigned int, unsigned int> translateIndexMap; + if (process_weights || mesh.GetBlendShapes().size() > 0) { + reverseMapping.resize(count_vertices); + } + + // allocate output data arrays, but don't fill them yet + out_mesh->mNumVertices = count_vertices; + out_mesh->mVertices = new aiVector3D[count_vertices]; + + out_mesh->mNumFaces = count_faces; + aiFace *fac = out_mesh->mFaces = new aiFace[count_faces](); + + // allocate normals + const std::vector<aiVector3D> &normals = mesh.GetNormals(); + if (normals.size()) { + ai_assert(normals.size() == vertices.size()); + out_mesh->mNormals = new aiVector3D[count_vertices]; + } + + // allocate tangents, binormals. + const std::vector<aiVector3D> &tangents = mesh.GetTangents(); + const std::vector<aiVector3D> *binormals = &mesh.GetBinormals(); + std::vector<aiVector3D> tempBinormals; + + if (tangents.size()) { + if (!binormals->size()) { + if (normals.size()) { + // XXX this computes the binormals for the entire mesh, not only + // the part for which we need them. + tempBinormals.resize(normals.size()); + for (unsigned int i = 0; i < tangents.size(); ++i) { + tempBinormals[i] = normals[i] ^ tangents[i]; + } + + binormals = &tempBinormals; + } else { + binormals = nullptr; + } + } + + if (binormals) { + ai_assert(tangents.size() == vertices.size()); + ai_assert(binormals->size() == vertices.size()); + + out_mesh->mTangents = new aiVector3D[count_vertices]; + out_mesh->mBitangents = new aiVector3D[count_vertices]; + } + } + + // allocate texture coords + unsigned int num_uvs = 0; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i, ++num_uvs) { + const std::vector<aiVector2D> &uvs = mesh.GetTextureCoords(i); + if (uvs.empty()) { + break; + } + + out_mesh->mTextureCoords[i] = new aiVector3D[count_vertices]; + out_mesh->mNumUVComponents[i] = 2; + } + + // allocate vertex colors + unsigned int num_vcs = 0; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i, ++num_vcs) { + const std::vector<aiColor4D> &colors = mesh.GetVertexColors(i); + if (colors.empty()) { + break; + } + + out_mesh->mColors[i] = new aiColor4D[count_vertices]; + } + + unsigned int cursor = 0, in_cursor = 0; + + itf = faces.begin(); + for (MatIndexArray::const_iterator it = mindices.begin(), end = mindices.end(); it != end; ++it, ++itf) { + const unsigned int pcount = *itf; + if ((*it) != index) { + in_cursor += pcount; + continue; + } + + aiFace &f = *fac++; + + f.mNumIndices = pcount; + f.mIndices = new unsigned int[pcount]; + switch (pcount) { + case 1: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + case 2: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + case 3: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + default: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + break; + } + for (unsigned int i = 0; i < pcount; ++i, ++cursor, ++in_cursor) { + f.mIndices[i] = cursor; + + if (reverseMapping.size()) { + reverseMapping[cursor] = in_cursor; + translateIndexMap[in_cursor] = cursor; + } + + out_mesh->mVertices[cursor] = vertices[in_cursor]; + + if (out_mesh->mNormals) { + out_mesh->mNormals[cursor] = normals[in_cursor]; + } + + if (out_mesh->mTangents) { + out_mesh->mTangents[cursor] = tangents[in_cursor]; + out_mesh->mBitangents[cursor] = (*binormals)[in_cursor]; + } + + for (unsigned int j = 0; j < num_uvs; ++j) { + const std::vector<aiVector2D> &uvs = mesh.GetTextureCoords(j); + out_mesh->mTextureCoords[j][cursor] = aiVector3D(uvs[in_cursor].x, uvs[in_cursor].y, 0.0f); + } + + for (unsigned int j = 0; j < num_vcs; ++j) { + const std::vector<aiColor4D> &cols = mesh.GetVertexColors(j); + out_mesh->mColors[j][cursor] = cols[in_cursor]; + } + } + } + + ConvertMaterialForMesh(out_mesh, model, mesh, index); + + if (process_weights) { + ConvertWeights(out_mesh, mesh, absolute_transform, parent, index, &reverseMapping); + } + + std::vector<aiAnimMesh *> animMeshes; + for (const BlendShape *blendShape : mesh.GetBlendShapes()) { + for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) { + const std::vector<const ShapeGeometry *> &shapeGeometries = blendShapeChannel->GetShapeGeometries(); + for (size_t i = 0; i < shapeGeometries.size(); i++) { + aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh); + const ShapeGeometry *shapeGeometry = shapeGeometries.at(i); + const std::vector<aiVector3D> &curVertices = shapeGeometry->GetVertices(); + const std::vector<aiVector3D> &curNormals = shapeGeometry->GetNormals(); + const std::vector<unsigned int> &curIndices = shapeGeometry->GetIndices(); + animMesh->mName.Set(FixAnimMeshName(shapeGeometry->Name())); + for (size_t j = 0; j < curIndices.size(); j++) { + unsigned int curIndex = curIndices.at(j); + aiVector3D vertex = curVertices.at(j); + aiVector3D normal = curNormals.at(j); + unsigned int count = 0; + const unsigned int *outIndices = mesh.ToOutputVertexIndex(curIndex, count); + for (unsigned int k = 0; k < count; k++) { + unsigned int outIndex = outIndices[k]; + if (translateIndexMap.find(outIndex) == translateIndexMap.end()) + continue; + unsigned int transIndex = translateIndexMap[outIndex]; + animMesh->mVertices[transIndex] += vertex; + if (animMesh->mNormals != nullptr) { + animMesh->mNormals[transIndex] += normal; + animMesh->mNormals[transIndex].NormalizeSafe(); + } + } + } + animMesh->mWeight = shapeGeometries.size() > 1 ? blendShapeChannel->DeformPercent() / 100.0f : 1.0f; + animMeshes.push_back(animMesh); + } + } + } + + const size_t numAnimMeshes = animMeshes.size(); + if (numAnimMeshes > 0) { + out_mesh->mNumAnimMeshes = static_cast<unsigned int>(numAnimMeshes); + out_mesh->mAnimMeshes = new aiAnimMesh *[numAnimMeshes]; + for (size_t i = 0; i < numAnimMeshes; i++) { + out_mesh->mAnimMeshes[i] = animMeshes.at(i); + } + } + + return static_cast<unsigned int>(mMeshes.size() - 1); +} + +void FBXConverter::ConvertWeights(aiMesh *out, const MeshGeometry &geo, + const aiMatrix4x4 &absolute_transform, + aiNode *parent, unsigned int materialIndex, + std::vector<unsigned int> *outputVertStartIndices) { + ai_assert(geo.DeformerSkin()); + + std::vector<size_t> out_indices; + std::vector<size_t> index_out_indices; + std::vector<size_t> count_out_indices; + + const Skin &sk = *geo.DeformerSkin(); + + std::vector<aiBone *> bones; + + const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION; + ai_assert(no_mat_check || outputVertStartIndices); + + try { + // iterate over the sub deformers + for (const Cluster *cluster : sk.Clusters()) { + ai_assert(cluster); + + const WeightIndexArray &indices = cluster->GetIndices(); + + const MatIndexArray &mats = geo.GetMaterialIndices(); + + const size_t no_index_sentinel = std::numeric_limits<size_t>::max(); + + count_out_indices.clear(); + index_out_indices.clear(); + out_indices.clear(); + + // now check if *any* of these weights is contained in the output mesh, + // taking notes so we don't need to do it twice. + for (WeightIndexArray::value_type index : indices) { + + unsigned int count = 0; + const unsigned int *const out_idx = geo.ToOutputVertexIndex(index, count); + // ToOutputVertexIndex only returns nullptr if index is out of bounds + // which should never happen + ai_assert(out_idx != nullptr); + + index_out_indices.push_back(no_index_sentinel); + count_out_indices.push_back(0); + + for (unsigned int i = 0; i < count; ++i) { + if (no_mat_check || static_cast<size_t>(mats[geo.FaceForVertexIndex(out_idx[i])]) == materialIndex) { + + if (index_out_indices.back() == no_index_sentinel) { + index_out_indices.back() = out_indices.size(); + } + + if (no_mat_check) { + out_indices.push_back(out_idx[i]); + } else { + // this extra lookup is in O(logn), so the entire algorithm becomes O(nlogn) + const std::vector<unsigned int>::iterator it = std::lower_bound( + outputVertStartIndices->begin(), + outputVertStartIndices->end(), + out_idx[i]); + + out_indices.push_back(std::distance(outputVertStartIndices->begin(), it)); + } + + ++count_out_indices.back(); + } + } + } + + // if we found at least one, generate the output bones + // XXX this could be heavily simplified by collecting the bone + // data in a single step. + ConvertCluster(bones, cluster, out_indices, index_out_indices, + count_out_indices, absolute_transform, parent); + } + + bone_map.clear(); + } catch (std::exception &) { + std::for_each(bones.begin(), bones.end(), Util::delete_fun<aiBone>()); + throw; + } + + if (bones.empty()) { + out->mBones = nullptr; + out->mNumBones = 0; + return; + } else { + out->mBones = new aiBone *[bones.size()](); + out->mNumBones = static_cast<unsigned int>(bones.size()); + + std::swap_ranges(bones.begin(), bones.end(), out->mBones); + } +} + +const aiNode *GetNodeByName(aiNode *current_node) { + aiNode *iter = current_node; + //printf("Child count: %d", iter->mNumChildren); + return iter; +} + +void FBXConverter::ConvertCluster(std::vector<aiBone *> &local_mesh_bones, const Cluster *cl, + std::vector<size_t> &out_indices, std::vector<size_t> &index_out_indices, + std::vector<size_t> &count_out_indices, const aiMatrix4x4 &absolute_transform, + aiNode *) { + ai_assert(cl); // make sure cluster valid + std::string deformer_name = cl->TargetNode()->Name(); + aiString bone_name = aiString(FixNodeName(deformer_name)); + + aiBone *bone = nullptr; + + if (bone_map.count(deformer_name)) { + ASSIMP_LOG_VERBOSE_DEBUG("retrieved bone from lookup ", bone_name.C_Str(), ". Deformer:", deformer_name); + bone = bone_map[deformer_name]; + } else { + ASSIMP_LOG_VERBOSE_DEBUG("created new bone ", bone_name.C_Str(), ". Deformer: ", deformer_name); + bone = new aiBone(); + bone->mName = bone_name; + + // store local transform link for post processing + bone->mOffsetMatrix = cl->TransformLink(); + bone->mOffsetMatrix.Inverse(); + + aiMatrix4x4 matrix = (aiMatrix4x4)absolute_transform; + + bone->mOffsetMatrix = bone->mOffsetMatrix * matrix; // * mesh_offset + + // + // Now calculate the aiVertexWeights + // + + aiVertexWeight *cursor = nullptr; + + bone->mNumWeights = static_cast<unsigned int>(out_indices.size()); + cursor = bone->mWeights = new aiVertexWeight[out_indices.size()]; + + const size_t no_index_sentinel = std::numeric_limits<size_t>::max(); + const WeightArray &weights = cl->GetWeights(); + + const size_t c = index_out_indices.size(); + for (size_t i = 0; i < c; ++i) { + const size_t index_index = index_out_indices[i]; + + if (index_index == no_index_sentinel) { + continue; + } + + const size_t cc = count_out_indices[i]; + for (size_t j = 0; j < cc; ++j) { + // cursor runs from first element relative to the start + // or relative to the start of the next indexes. + aiVertexWeight &out_weight = *cursor++; + + out_weight.mVertexId = static_cast<unsigned int>(out_indices[index_index + j]); + out_weight.mWeight = weights[i]; + } + } + + bone_map.insert(std::pair<const std::string, aiBone *>(deformer_name, bone)); + } + + ASSIMP_LOG_DEBUG("bone research: Indices size: ", out_indices.size()); + + // lookup must be populated in case something goes wrong + // this also allocates bones to mesh instance outside + local_mesh_bones.push_back(bone); +} + +void FBXConverter::ConvertMaterialForMesh(aiMesh *out, const Model &model, const MeshGeometry &geo, + MatIndexArray::value_type materialIndex) { + // locate source materials for this mesh + const std::vector<const Material *> &mats = model.GetMaterials(); + if (static_cast<unsigned int>(materialIndex) >= mats.size() || materialIndex < 0) { + FBXImporter::LogError("material index out of bounds, setting default material"); + out->mMaterialIndex = GetDefaultMaterial(); + return; + } + + const Material *const mat = mats[materialIndex]; + MaterialMap::const_iterator it = materials_converted.find(mat); + if (it != materials_converted.end()) { + out->mMaterialIndex = (*it).second; + return; + } + + out->mMaterialIndex = ConvertMaterial(*mat, &geo); + materials_converted[mat] = out->mMaterialIndex; +} + +unsigned int FBXConverter::GetDefaultMaterial() { + if (defaultMaterialIndex) { + return defaultMaterialIndex - 1; + } + + aiMaterial *out_mat = new aiMaterial(); + materials.push_back(out_mat); + + const aiColor3D diffuse = aiColor3D(0.8f, 0.8f, 0.8f); + out_mat->AddProperty(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + + aiString s; + s.Set(AI_DEFAULT_MATERIAL_NAME); + + out_mat->AddProperty(&s, AI_MATKEY_NAME); + + defaultMaterialIndex = static_cast<unsigned int>(materials.size()); + return defaultMaterialIndex - 1; +} + +unsigned int FBXConverter::ConvertMaterial(const Material &material, const MeshGeometry *const mesh) { + const PropertyTable &props = material.Props(); + + // generate empty output material + aiMaterial *out_mat = new aiMaterial(); + materials_converted[&material] = static_cast<unsigned int>(materials.size()); + + materials.push_back(out_mat); + + aiString str; + + // strip Material:: prefix + std::string name = material.Name(); + if (name.substr(0, 10) == "Material::") { + name = name.substr(10); + } + + // set material name if not empty - this could happen + // and there should be no key for it in this case. + if (name.length()) { + str.Set(name); + out_mat->AddProperty(&str, AI_MATKEY_NAME); + } + + // Set the shading mode as best we can: The FBX specification only mentions Lambert and Phong, and only Phong is mentioned in Assimp's aiShadingMode enum. + if (material.GetShadingModel() == "phong") { + aiShadingMode shadingMode = aiShadingMode_Phong; + out_mat->AddProperty<aiShadingMode>(&shadingMode, 1, AI_MATKEY_SHADING_MODEL); + } + + // shading stuff and colors + SetShadingPropertiesCommon(out_mat, props); + SetShadingPropertiesRaw(out_mat, props, material.Textures(), mesh); + + // texture assignments + SetTextureProperties(out_mat, material.Textures(), mesh); + SetTextureProperties(out_mat, material.LayeredTextures(), mesh); + + return static_cast<unsigned int>(materials.size() - 1); +} + +unsigned int FBXConverter::ConvertVideo(const Video &video) { + // generate empty output texture + aiTexture *out_tex = new aiTexture(); + textures.push_back(out_tex); + + // assuming the texture is compressed + out_tex->mWidth = static_cast<unsigned int>(video.ContentLength()); // total data size + out_tex->mHeight = 0; // fixed to 0 + + // steal the data from the Video to avoid an additional copy + out_tex->pcData = reinterpret_cast<aiTexel *>(const_cast<Video &>(video).RelinquishContent()); + + // try to extract a hint from the file extension + const std::string &filename = video.RelativeFilename().empty() ? video.FileName() : video.RelativeFilename(); + std::string ext = BaseImporter::GetExtension(filename); + + if (ext == "jpeg") { + ext = "jpg"; + } + + if (ext.size() <= 3) { + memcpy(out_tex->achFormatHint, ext.c_str(), ext.size()); + } + + out_tex->mFilename.Set(filename.c_str()); + + return static_cast<unsigned int>(textures.size() - 1); +} + +aiString FBXConverter::GetTexturePath(const Texture *tex) { + aiString path; + path.Set(tex->RelativeFilename()); + + const Video *media = tex->Media(); + if (media != nullptr) { + bool textureReady = false; //tells if our texture is ready (if it was loaded or if it was found) + unsigned int index=0; + + VideoMap::const_iterator it = textures_converted.find(media); + if (it != textures_converted.end()) { + index = (*it).second; + textureReady = true; + } else { + if (media->ContentLength() > 0) { + index = ConvertVideo(*media); + textures_converted[media] = index; + textureReady = true; + } + } + + // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture), if the texture is ready + if (doc.Settings().useLegacyEmbeddedTextureNaming) { + if (textureReady) { + // TODO: check the possibility of using the flag "AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING" + // In FBX files textures are now stored internally by Assimp with their filename included + // Now Assimp can lookup through the loaded textures after all data is processed + // We need to load all textures before referencing them, as FBX file format order may reference a texture before loading it + // This may occur on this case too, it has to be studied + path.data[0] = '*'; + path.length = 1 + ASSIMP_itoa10(path.data + 1, MAXLEN - 1, index); + } + } + } + + return path; +} + +void FBXConverter::TrySetTextureProperties(aiMaterial *out_mat, const TextureMap &_textures, + const std::string &propName, + aiTextureType target, const MeshGeometry *const mesh) { + TextureMap::const_iterator it = _textures.find(propName); + if (it == _textures.end()) { + return; + } + + const Texture *const tex = (*it).second; + if (tex != nullptr) { + aiString path = GetTexturePath(tex); + out_mat->AddProperty(&path, _AI_MATKEY_TEXTURE_BASE, target, 0); + + aiUVTransform uvTrafo; + // XXX handle all kinds of UV transformations + uvTrafo.mScaling = tex->UVScaling(); + uvTrafo.mTranslation = tex->UVTranslation(); + uvTrafo.mRotation = tex->UVRotation(); + out_mat->AddProperty(&uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, 0); + + const PropertyTable &props = tex->Props(); + + int uvIndex = 0; + + bool ok; + const std::string &uvSet = PropertyGet<std::string>(props, "UVSet", ok); + if (ok) { + // "default" is the name which usually appears in the FbxFileTexture template + if (uvSet != "default" && uvSet.length()) { + // this is a bit awkward - we need to find a mesh that uses this + // material and scan its UV channels for the given UV name because + // assimp references UV channels by index, not by name. + + // XXX: the case that UV channels may appear in different orders + // in meshes is unhandled. A possible solution would be to sort + // the UV channels alphabetically, but this would have the side + // effect that the primary (first) UV channel would sometimes + // be moved, causing trouble when users read only the first + // UV channel and ignore UV channel assignments altogether. + + const unsigned int matIndex = static_cast<unsigned int>(std::distance(materials.begin(), + std::find(materials.begin(), materials.end(), out_mat))); + + uvIndex = -1; + if (!mesh) { + for (const MeshMap::value_type &v : meshes_converted) { + const MeshGeometry *const meshGeom = dynamic_cast<const MeshGeometry *>(v.first); + if (!meshGeom) { + continue; + } + + const MatIndexArray &mats = meshGeom->GetMaterialIndices(); + MatIndexArray::const_iterator curIt = std::find(mats.begin(), mats.end(), (int) matIndex); + if (curIt == mats.end()) { + continue; + } + + int index = -1; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (meshGeom->GetTextureCoords(i).empty()) { + break; + } + const std::string &name = meshGeom->GetTextureCoordChannelName(i); + if (name == uvSet) { + index = static_cast<int>(i); + break; + } + } + if (index == -1) { + FBXImporter::LogWarn("did not find UV channel named ", uvSet, " in a mesh using this material"); + continue; + } + + if (uvIndex == -1) { + uvIndex = index; + } else { + FBXImporter::LogWarn("the UV channel named ", uvSet, + " appears at different positions in meshes, results will be wrong"); + } + } + } else { + int index = -1; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (mesh->GetTextureCoords(i).empty()) { + break; + } + const std::string &name = mesh->GetTextureCoordChannelName(i); + if (name == uvSet) { + index = static_cast<int>(i); + break; + } + } + if (index == -1) { + FBXImporter::LogWarn("did not find UV channel named ", uvSet, " in a mesh using this material"); + } + + if (uvIndex == -1) { + uvIndex = index; + } + } + + if (uvIndex == -1) { + FBXImporter::LogWarn("failed to resolve UV channel ", uvSet, ", using first UV channel"); + uvIndex = 0; + } + } + } + + out_mat->AddProperty(&uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, 0); + } +} + +void FBXConverter::TrySetTextureProperties(aiMaterial *out_mat, const LayeredTextureMap &layeredTextures, + const std::string &propName, + aiTextureType target, const MeshGeometry *const mesh) { + LayeredTextureMap::const_iterator it = layeredTextures.find(propName); + if (it == layeredTextures.end()) { + return; + } + + int texCount = (*it).second->textureCount(); + + // Set the blend mode for layered textures + int blendmode = (*it).second->GetBlendMode(); + out_mat->AddProperty(&blendmode, 1, _AI_MATKEY_TEXOP_BASE, target, 0); + + for (int texIndex = 0; texIndex < texCount; texIndex++) { + + const Texture *const tex = (*it).second->getTexture(texIndex); + + aiString path = GetTexturePath(tex); + out_mat->AddProperty(&path, _AI_MATKEY_TEXTURE_BASE, target, texIndex); + + aiUVTransform uvTrafo; + // XXX handle all kinds of UV transformations + uvTrafo.mScaling = tex->UVScaling(); + uvTrafo.mTranslation = tex->UVTranslation(); + uvTrafo.mRotation = tex->UVRotation(); + out_mat->AddProperty(&uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, texIndex); + + const PropertyTable &props = tex->Props(); + + int uvIndex = 0; + + bool ok; + const std::string &uvSet = PropertyGet<std::string>(props, "UVSet", ok); + if (ok) { + // "default" is the name which usually appears in the FbxFileTexture template + if (uvSet != "default" && uvSet.length()) { + // this is a bit awkward - we need to find a mesh that uses this + // material and scan its UV channels for the given UV name because + // assimp references UV channels by index, not by name. + + // XXX: the case that UV channels may appear in different orders + // in meshes is unhandled. A possible solution would be to sort + // the UV channels alphabetically, but this would have the side + // effect that the primary (first) UV channel would sometimes + // be moved, causing trouble when users read only the first + // UV channel and ignore UV channel assignments altogether. + + const unsigned int matIndex = static_cast<unsigned int>(std::distance(materials.begin(), + std::find(materials.begin(), materials.end(), out_mat))); + + uvIndex = -1; + if (!mesh) { + for (const MeshMap::value_type &v : meshes_converted) { + const MeshGeometry *const meshGeom = dynamic_cast<const MeshGeometry *>(v.first); + if (!meshGeom) { + continue; + } + + const MatIndexArray &mats = meshGeom->GetMaterialIndices(); + MatIndexArray::const_iterator curIt = std::find(mats.begin(), mats.end(), (int) matIndex); + if ( curIt == mats.end()) { + continue; + } + + int index = -1; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (meshGeom->GetTextureCoords(i).empty()) { + break; + } + const std::string &name = meshGeom->GetTextureCoordChannelName(i); + if (name == uvSet) { + index = static_cast<int>(i); + break; + } + } + if (index == -1) { + FBXImporter::LogWarn("did not find UV channel named ", uvSet, " in a mesh using this material"); + continue; + } + + if (uvIndex == -1) { + uvIndex = index; + } else { + FBXImporter::LogWarn("the UV channel named ", uvSet, + " appears at different positions in meshes, results will be wrong"); + } + } + } else { + int index = -1; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (mesh->GetTextureCoords(i).empty()) { + break; + } + const std::string &name = mesh->GetTextureCoordChannelName(i); + if (name == uvSet) { + index = static_cast<int>(i); + break; + } + } + if (index == -1) { + FBXImporter::LogWarn("did not find UV channel named ", uvSet, " in a mesh using this material"); + } + + if (uvIndex == -1) { + uvIndex = index; + } + } + + if (uvIndex == -1) { + FBXImporter::LogWarn("failed to resolve UV channel ", uvSet, ", using first UV channel"); + uvIndex = 0; + } + } + } + + out_mat->AddProperty(&uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, texIndex); + } +} + +void FBXConverter::SetTextureProperties(aiMaterial *out_mat, const TextureMap &_textures, const MeshGeometry *const mesh) { + TrySetTextureProperties(out_mat, _textures, "DiffuseColor", aiTextureType_DIFFUSE, mesh); + TrySetTextureProperties(out_mat, _textures, "AmbientColor", aiTextureType_AMBIENT, mesh); + TrySetTextureProperties(out_mat, _textures, "EmissiveColor", aiTextureType_EMISSIVE, mesh); + TrySetTextureProperties(out_mat, _textures, "SpecularColor", aiTextureType_SPECULAR, mesh); + TrySetTextureProperties(out_mat, _textures, "SpecularFactor", aiTextureType_SPECULAR, mesh); + TrySetTextureProperties(out_mat, _textures, "TransparentColor", aiTextureType_OPACITY, mesh); + TrySetTextureProperties(out_mat, _textures, "ReflectionColor", aiTextureType_REFLECTION, mesh); + TrySetTextureProperties(out_mat, _textures, "DisplacementColor", aiTextureType_DISPLACEMENT, mesh); + TrySetTextureProperties(out_mat, _textures, "NormalMap", aiTextureType_NORMALS, mesh); + TrySetTextureProperties(out_mat, _textures, "Bump", aiTextureType_HEIGHT, mesh); + TrySetTextureProperties(out_mat, _textures, "ShininessExponent", aiTextureType_SHININESS, mesh); + TrySetTextureProperties(out_mat, _textures, "TransparencyFactor", aiTextureType_OPACITY, mesh); + TrySetTextureProperties(out_mat, _textures, "EmissiveFactor", aiTextureType_EMISSIVE, mesh); + TrySetTextureProperties(out_mat, _textures, "ReflectionFactor", aiTextureType_METALNESS, mesh); + //Maya counterparts + TrySetTextureProperties(out_mat, _textures, "Maya|DiffuseTexture", aiTextureType_DIFFUSE, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|NormalTexture", aiTextureType_NORMALS, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|SpecularTexture", aiTextureType_SPECULAR, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|FalloffTexture", aiTextureType_OPACITY, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|ReflectionMapTexture", aiTextureType_REFLECTION, mesh); + + // Maya PBR + TrySetTextureProperties(out_mat, _textures, "Maya|baseColor", aiTextureType_BASE_COLOR, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|normalCamera", aiTextureType_NORMAL_CAMERA, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|emissionColor", aiTextureType_EMISSION_COLOR, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|metalness", aiTextureType_METALNESS, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|diffuseRoughness", aiTextureType_DIFFUSE_ROUGHNESS, mesh); + + // Maya stingray + TrySetTextureProperties(out_mat, _textures, "Maya|TEX_color_map", aiTextureType_BASE_COLOR, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|TEX_normal_map", aiTextureType_NORMAL_CAMERA, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|TEX_emissive_map", aiTextureType_EMISSION_COLOR, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|TEX_metallic_map", aiTextureType_METALNESS, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|TEX_roughness_map", aiTextureType_DIFFUSE_ROUGHNESS, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|TEX_ao_map", aiTextureType_AMBIENT_OCCLUSION, mesh); + + // 3DSMax Physical material + TrySetTextureProperties(out_mat, _textures, "3dsMax|Parameters|base_color_map", aiTextureType_BASE_COLOR, mesh); + TrySetTextureProperties(out_mat, _textures, "3dsMax|Parameters|bump_map", aiTextureType_NORMAL_CAMERA, mesh); + TrySetTextureProperties(out_mat, _textures, "3dsMax|Parameters|emission_map", aiTextureType_EMISSION_COLOR, mesh); + TrySetTextureProperties(out_mat, _textures, "3dsMax|Parameters|metalness_map", aiTextureType_METALNESS, mesh); + TrySetTextureProperties(out_mat, _textures, "3dsMax|Parameters|roughness_map", aiTextureType_DIFFUSE_ROUGHNESS, mesh); + + // 3DSMax PBR materials + TrySetTextureProperties(out_mat, _textures, "3dsMax|main|base_color_map", aiTextureType_BASE_COLOR, mesh); + TrySetTextureProperties(out_mat, _textures, "3dsMax|main|norm_map", aiTextureType_NORMAL_CAMERA, mesh); + TrySetTextureProperties(out_mat, _textures, "3dsMax|main|emit_color_map", aiTextureType_EMISSION_COLOR, mesh); + TrySetTextureProperties(out_mat, _textures, "3dsMax|main|ao_map", aiTextureType_AMBIENT_OCCLUSION, mesh); + TrySetTextureProperties(out_mat, _textures, "3dsMax|main|opacity_map", aiTextureType_OPACITY, mesh); + // Metalness/Roughness material type + TrySetTextureProperties(out_mat, _textures, "3dsMax|main|metalness_map", aiTextureType_METALNESS, mesh); + // Specular/Gloss material type + TrySetTextureProperties(out_mat, _textures, "3dsMax|main|specular_map", aiTextureType_SPECULAR, mesh); + + // Glossiness vs roughness in 3ds Max Pbr Materials + int useGlossiness; + if (out_mat->Get("$raw.3dsMax|main|useGlossiness", aiTextureType_NONE, 0, useGlossiness) == aiReturn_SUCCESS) { + // These textures swap meaning if ((useGlossiness == 1) != (material type is Specular/Gloss)) + if (useGlossiness == 1) { + TrySetTextureProperties(out_mat, _textures, "3dsMax|main|roughness_map", aiTextureType_SHININESS, mesh); + TrySetTextureProperties(out_mat, _textures, "3dsMax|main|glossiness_map", aiTextureType_SHININESS, mesh); + } + else if (useGlossiness == 2) { + TrySetTextureProperties(out_mat, _textures, "3dsMax|main|roughness_map", aiTextureType_DIFFUSE_ROUGHNESS, mesh); + TrySetTextureProperties(out_mat, _textures, "3dsMax|main|glossiness_map", aiTextureType_DIFFUSE_ROUGHNESS, mesh); + } + else { + FBXImporter::LogWarn("A 3dsMax Pbr Material must have a useGlossiness value to correctly interpret roughness and glossiness textures."); + } + } +} + +void FBXConverter::SetTextureProperties(aiMaterial *out_mat, const LayeredTextureMap &layeredTextures, const MeshGeometry *const mesh) { + TrySetTextureProperties(out_mat, layeredTextures, "DiffuseColor", aiTextureType_DIFFUSE, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "AmbientColor", aiTextureType_AMBIENT, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "EmissiveColor", aiTextureType_EMISSIVE, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "SpecularColor", aiTextureType_SPECULAR, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "SpecularFactor", aiTextureType_SPECULAR, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "TransparentColor", aiTextureType_OPACITY, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "ReflectionColor", aiTextureType_REFLECTION, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "DisplacementColor", aiTextureType_DISPLACEMENT, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "NormalMap", aiTextureType_NORMALS, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "Bump", aiTextureType_HEIGHT, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "ShininessExponent", aiTextureType_SHININESS, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "EmissiveFactor", aiTextureType_EMISSIVE, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "TransparencyFactor", aiTextureType_OPACITY, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "ReflectionFactor", aiTextureType_METALNESS, mesh); +} + +aiColor3D FBXConverter::GetColorPropertyFactored(const PropertyTable &props, const std::string &colorName, + const std::string &factorName, bool &result, bool useTemplate) { + result = true; + + bool ok; + aiVector3D BaseColor = PropertyGet<aiVector3D>(props, colorName, ok, useTemplate); + if (!ok) { + result = false; + return aiColor3D(0.0f, 0.0f, 0.0f); + } + + // if no factor name, return the colour as is + if (factorName.empty()) { + return aiColor3D(BaseColor.x, BaseColor.y, BaseColor.z); + } + + // otherwise it should be multiplied by the factor, if found. + float factor = PropertyGet<float>(props, factorName, ok, useTemplate); + if (ok) { + BaseColor *= factor; + } + return aiColor3D(BaseColor.x, BaseColor.y, BaseColor.z); +} + +aiColor3D FBXConverter::GetColorPropertyFromMaterial(const PropertyTable &props, const std::string &baseName, + bool &result) { + return GetColorPropertyFactored(props, baseName + "Color", baseName + "Factor", result, true); +} + +aiColor3D FBXConverter::GetColorProperty(const PropertyTable &props, const std::string &colorName, + bool &result, bool useTemplate) { + result = true; + bool ok; + const aiVector3D &ColorVec = PropertyGet<aiVector3D>(props, colorName, ok, useTemplate); + if (!ok) { + result = false; + return aiColor3D(0.0f, 0.0f, 0.0f); + } + return aiColor3D(ColorVec.x, ColorVec.y, ColorVec.z); +} + +void FBXConverter::SetShadingPropertiesCommon(aiMaterial *out_mat, const PropertyTable &props) { + // Set shading properties. + // Modern FBX Files have two separate systems for defining these, + // with only the more comprehensive one described in the property template. + // Likely the other values are a legacy system, + // which is still always exported by the official FBX SDK. + // + // Blender's FBX import and export mostly ignore this legacy system, + // and as we only support recent versions of FBX anyway, we can do the same. + bool ok; + + const aiColor3D &Diffuse = GetColorPropertyFromMaterial(props, "Diffuse", ok); + if (ok) { + out_mat->AddProperty(&Diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + } + + const aiColor3D &Emissive = GetColorPropertyFromMaterial(props, "Emissive", ok); + if (ok) { + out_mat->AddProperty(&Emissive, 1, AI_MATKEY_COLOR_EMISSIVE); + } else { + const aiColor3D &emissiveColor = GetColorProperty(props, "Maya|emissive", ok); + if (ok) { + out_mat->AddProperty(&emissiveColor, 1, AI_MATKEY_COLOR_EMISSIVE); + } + } + + const aiColor3D &Ambient = GetColorPropertyFromMaterial(props, "Ambient", ok); + if (ok) { + out_mat->AddProperty(&Ambient, 1, AI_MATKEY_COLOR_AMBIENT); + } + + // we store specular factor as SHININESS_STRENGTH, so just get the color + const aiColor3D &Specular = GetColorProperty(props, "SpecularColor", ok, true); + if (ok) { + out_mat->AddProperty(&Specular, 1, AI_MATKEY_COLOR_SPECULAR); + } + + // and also try to get SHININESS_STRENGTH + const float SpecularFactor = PropertyGet<float>(props, "SpecularFactor", ok, true); + if (ok) { + out_mat->AddProperty(&SpecularFactor, 1, AI_MATKEY_SHININESS_STRENGTH); + } + + // and the specular exponent + const float ShininessExponent = PropertyGet<float>(props, "ShininessExponent", ok); + if (ok) { + out_mat->AddProperty(&ShininessExponent, 1, AI_MATKEY_SHININESS); + } + + // TransparentColor / TransparencyFactor... gee thanks FBX :rolleyes: + const aiColor3D &Transparent = GetColorPropertyFactored(props, "TransparentColor", "TransparencyFactor", ok); + float CalculatedOpacity = 1.0f; + if (ok) { + out_mat->AddProperty(&Transparent, 1, AI_MATKEY_COLOR_TRANSPARENT); + // as calculated by FBX SDK 2017: + CalculatedOpacity = 1.0f - ((Transparent.r + Transparent.g + Transparent.b) / 3.0f); + } + + // try to get the transparency factor + const float TransparencyFactor = PropertyGet<float>(props, "TransparencyFactor", ok); + if (ok) { + out_mat->AddProperty(&TransparencyFactor, 1, AI_MATKEY_TRANSPARENCYFACTOR); + } + + // use of TransparencyFactor is inconsistent. + // Maya always stores it as 1.0, + // so we can't use it to set AI_MATKEY_OPACITY. + // Blender is more sensible and stores it as the alpha value. + // However both the FBX SDK and Blender always write an additional + // legacy "Opacity" field, so we can try to use that. + // + // If we can't find it, + // we can fall back to the value which the FBX SDK calculates + // from transparency colour (RGB) and factor (F) as + // 1.0 - F*((R+G+B)/3). + // + // There's no consistent way to interpret this opacity value, + // so it's up to clients to do the correct thing. + const float Opacity = PropertyGet<float>(props, "Opacity", ok); + if (ok) { + out_mat->AddProperty(&Opacity, 1, AI_MATKEY_OPACITY); + } else if (CalculatedOpacity != 1.0) { + out_mat->AddProperty(&CalculatedOpacity, 1, AI_MATKEY_OPACITY); + } + + // reflection color and factor are stored separately + const aiColor3D &Reflection = GetColorProperty(props, "ReflectionColor", ok, true); + if (ok) { + out_mat->AddProperty(&Reflection, 1, AI_MATKEY_COLOR_REFLECTIVE); + } + + float ReflectionFactor = PropertyGet<float>(props, "ReflectionFactor", ok, true); + if (ok) { + out_mat->AddProperty(&ReflectionFactor, 1, AI_MATKEY_REFLECTIVITY); + } + + const float BumpFactor = PropertyGet<float>(props, "BumpFactor", ok); + if (ok) { + out_mat->AddProperty(&BumpFactor, 1, AI_MATKEY_BUMPSCALING); + } + + const float DispFactor = PropertyGet<float>(props, "DisplacementFactor", ok); + if (ok) { + out_mat->AddProperty(&DispFactor, 1, "$mat.displacementscaling", 0, 0); + } + + // PBR material information + const aiColor3D &baseColor = GetColorProperty(props, "Maya|base_color", ok); + if (ok) { + out_mat->AddProperty(&baseColor, 1, AI_MATKEY_BASE_COLOR); + } + + const float useColorMap = PropertyGet<float>(props, "Maya|use_color_map", ok); + if (ok) { + out_mat->AddProperty(&useColorMap, 1, AI_MATKEY_USE_COLOR_MAP); + } + + const float useMetallicMap = PropertyGet<float>(props, "Maya|use_metallic_map", ok); + if (ok) { + out_mat->AddProperty(&useMetallicMap, 1, AI_MATKEY_USE_METALLIC_MAP); + } + + const float metallicFactor = PropertyGet<float>(props, "Maya|metallic", ok); + if (ok) { + out_mat->AddProperty(&metallicFactor, 1, AI_MATKEY_METALLIC_FACTOR); + } + + const float useRoughnessMap = PropertyGet<float>(props, "Maya|use_roughness_map", ok); + if (ok) { + out_mat->AddProperty(&useRoughnessMap, 1, AI_MATKEY_USE_ROUGHNESS_MAP); + } + + const float roughnessFactor = PropertyGet<float>(props, "Maya|roughness", ok); + if (ok) { + out_mat->AddProperty(&roughnessFactor, 1, AI_MATKEY_ROUGHNESS_FACTOR); + } + + const float useEmissiveMap = PropertyGet<float>(props, "Maya|use_emissive_map", ok); + if (ok) { + out_mat->AddProperty(&useEmissiveMap, 1, AI_MATKEY_USE_EMISSIVE_MAP); + } + + const float emissiveIntensity = PropertyGet<float>(props, "Maya|emissive_intensity", ok); + if (ok) { + out_mat->AddProperty(&emissiveIntensity, 1, AI_MATKEY_EMISSIVE_INTENSITY); + } + + const float useAOMap = PropertyGet<float>(props, "Maya|use_ao_map", ok); + if (ok) { + out_mat->AddProperty(&useAOMap, 1, AI_MATKEY_USE_AO_MAP); + } +} + +void FBXConverter::SetShadingPropertiesRaw(aiMaterial *out_mat, const PropertyTable &props, const TextureMap &_textures, const MeshGeometry *const mesh) { + // Add all the unparsed properties with a "$raw." prefix + + const std::string prefix = "$raw."; + + for (const DirectPropertyMap::value_type &prop : props.GetUnparsedProperties()) { + + std::string name = prefix + prop.first; + + if (const TypedProperty<aiVector3D> *interpretedVec3 = prop.second->As<TypedProperty<aiVector3D>>()) { + out_mat->AddProperty(&interpretedVec3->Value(), 1, name.c_str(), 0, 0); + } else if (const TypedProperty<aiColor3D> *interpretedCol3 = prop.second->As<TypedProperty<aiColor3D>>()) { + out_mat->AddProperty(&interpretedCol3->Value(), 1, name.c_str(), 0, 0); + } else if (const TypedProperty<aiColor4D> *interpretedCol4 = prop.second->As<TypedProperty<aiColor4D>>()) { + out_mat->AddProperty(&interpretedCol4->Value(), 1, name.c_str(), 0, 0); + } else if (const TypedProperty<float> *interpretedFloat = prop.second->As<TypedProperty<float>>()) { + out_mat->AddProperty(&interpretedFloat->Value(), 1, name.c_str(), 0, 0); + } else if (const TypedProperty<int> *interpretedInt = prop.second->As<TypedProperty<int>>()) { + out_mat->AddProperty(&interpretedInt->Value(), 1, name.c_str(), 0, 0); + } else if (const TypedProperty<bool> *interpretedBool = prop.second->As<TypedProperty<bool>>()) { + int value = interpretedBool->Value() ? 1 : 0; + out_mat->AddProperty(&value, 1, name.c_str(), 0, 0); + } else if (const TypedProperty<std::string> *interpretedString = prop.second->As<TypedProperty<std::string>>()) { + const aiString value = aiString(interpretedString->Value()); + out_mat->AddProperty(&value, name.c_str(), 0, 0); + } + } + + // Add the textures' properties + + for (TextureMap::const_iterator it = _textures.begin(); it != _textures.end(); ++it) { + + std::string name = prefix + it->first; + + const Texture *const tex = it->second; + if (tex != nullptr) { + aiString path; + path.Set(tex->RelativeFilename()); + + const Video *media = tex->Media(); + if (media != nullptr && media->ContentLength() > 0) { + unsigned int index; + + VideoMap::const_iterator videoIt = textures_converted.find(media); + if (videoIt != textures_converted.end()) { + index = videoIt->second; + } else { + index = ConvertVideo(*media); + textures_converted[media] = index; + } + + // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) + path.data[0] = '*'; + path.length = 1 + ASSIMP_itoa10(path.data + 1, MAXLEN - 1, index); + } + + out_mat->AddProperty(&path, (name + "|file").c_str(), aiTextureType_UNKNOWN, 0); + + aiUVTransform uvTrafo; + // XXX handle all kinds of UV transformations + uvTrafo.mScaling = tex->UVScaling(); + uvTrafo.mTranslation = tex->UVTranslation(); + uvTrafo.mRotation = tex->UVRotation(); + out_mat->AddProperty(&uvTrafo, 1, (name + "|uvtrafo").c_str(), aiTextureType_UNKNOWN, 0); + + int uvIndex = 0; + + bool uvFound = false; + const std::string &uvSet = PropertyGet<std::string>(tex->Props(), "UVSet", uvFound); + if (uvFound) { + // "default" is the name which usually appears in the FbxFileTexture template + if (uvSet != "default" && uvSet.length()) { + // this is a bit awkward - we need to find a mesh that uses this + // material and scan its UV channels for the given UV name because + // assimp references UV channels by index, not by name. + + // XXX: the case that UV channels may appear in different orders + // in meshes is unhandled. A possible solution would be to sort + // the UV channels alphabetically, but this would have the side + // effect that the primary (first) UV channel would sometimes + // be moved, causing trouble when users read only the first + // UV channel and ignore UV channel assignments altogether. + + std::vector<aiMaterial *>::iterator materialIt = std::find(materials.begin(), materials.end(), out_mat); + const unsigned int matIndex = static_cast<unsigned int>(std::distance(materials.begin(), materialIt)); + + uvIndex = -1; + if (!mesh) { + for (const MeshMap::value_type &v : meshes_converted) { + const MeshGeometry *const meshGeom = dynamic_cast<const MeshGeometry *>(v.first); + if (!meshGeom) { + continue; + } + + const MatIndexArray &mats = meshGeom->GetMaterialIndices(); + if (std::find(mats.begin(), mats.end(), (int)matIndex) == mats.end()) { + continue; + } + + int index = -1; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (meshGeom->GetTextureCoords(i).empty()) { + break; + } + const std::string &curName = meshGeom->GetTextureCoordChannelName(i); + if (curName == uvSet) { + index = static_cast<int>(i); + break; + } + } + if (index == -1) { + FBXImporter::LogWarn("did not find UV channel named ", uvSet, " in a mesh using this material"); + continue; + } + + if (uvIndex == -1) { + uvIndex = index; + } else { + FBXImporter::LogWarn("the UV channel named ", uvSet, " appears at different positions in meshes, results will be wrong"); + } + } + } else { + int index = -1; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (mesh->GetTextureCoords(i).empty()) { + break; + } + const std::string &curName = mesh->GetTextureCoordChannelName(i); + if (curName == uvSet) { + index = static_cast<int>(i); + break; + } + } + if (index == -1) { + FBXImporter::LogWarn("did not find UV channel named ", uvSet, " in a mesh using this material"); + } + + if (uvIndex == -1) { + uvIndex = index; + } + } + + if (uvIndex == -1) { + FBXImporter::LogWarn("failed to resolve UV channel ", uvSet, ", using first UV channel"); + uvIndex = 0; + } + } + } + + out_mat->AddProperty(&uvIndex, 1, (name + "|uvwsrc").c_str(), aiTextureType_UNKNOWN, 0); + } + } +} + +double FBXConverter::FrameRateToDouble(FileGlobalSettings::FrameRate fp, double customFPSVal) { + switch (fp) { + case FileGlobalSettings::FrameRate_DEFAULT: + return 1.0; + + case FileGlobalSettings::FrameRate_120: + return 120.0; + + case FileGlobalSettings::FrameRate_100: + return 100.0; + + case FileGlobalSettings::FrameRate_60: + return 60.0; + + case FileGlobalSettings::FrameRate_50: + return 50.0; + + case FileGlobalSettings::FrameRate_48: + return 48.0; + + case FileGlobalSettings::FrameRate_30: + case FileGlobalSettings::FrameRate_30_DROP: + return 30.0; + + case FileGlobalSettings::FrameRate_NTSC_DROP_FRAME: + case FileGlobalSettings::FrameRate_NTSC_FULL_FRAME: + return 29.9700262; + + case FileGlobalSettings::FrameRate_PAL: + return 25.0; + + case FileGlobalSettings::FrameRate_CINEMA: + return 24.0; + + case FileGlobalSettings::FrameRate_1000: + return 1000.0; + + case FileGlobalSettings::FrameRate_CINEMA_ND: + return 23.976; + + case FileGlobalSettings::FrameRate_CUSTOM: + return customFPSVal; + + case FileGlobalSettings::FrameRate_MAX: // this is to silence compiler warnings + break; + } + + ai_assert(false); + + return -1.0f; +} + +void FBXConverter::ConvertAnimations() { + // first of all determine framerate + const FileGlobalSettings::FrameRate fps = doc.GlobalSettings().TimeMode(); + const float custom = doc.GlobalSettings().CustomFrameRate(); + anim_fps = FrameRateToDouble(fps, custom); + + const std::vector<const AnimationStack *> &curAnimations = doc.AnimationStacks(); + for (const AnimationStack *stack : curAnimations) { + ConvertAnimationStack(*stack); + } +} + +std::string FBXConverter::FixNodeName(const std::string &name) { + // strip Model:: prefix, avoiding ambiguities (i.e. don't strip if + // this causes ambiguities, well possible between empty identifiers, + // such as "Model::" and ""). Make sure the behaviour is consistent + // across multiple calls to FixNodeName(). + if (name.substr(0, 7) == "Model::") { + std::string temp = name.substr(7); + return temp; + } + + return name; +} + +std::string FBXConverter::FixAnimMeshName(const std::string &name) { + if (name.length()) { + size_t indexOf = name.find_first_of("::"); + if (indexOf != std::string::npos && indexOf < name.size() - 2) { + return name.substr(indexOf + 2); + } + } + return name.length() ? name : "AnimMesh"; +} + +void FBXConverter::ConvertAnimationStack(const AnimationStack &st) { + const AnimationLayerList &layers = st.Layers(); + if (layers.empty()) { + return; + } + + aiAnimation *const anim = new aiAnimation(); + animations.push_back(anim); + + // strip AnimationStack:: prefix + std::string name = st.Name(); + if (name.substr(0, 16) == "AnimationStack::") { + name = name.substr(16); + } else if (name.substr(0, 11) == "AnimStack::") { + name = name.substr(11); + } + + anim->mName.Set(name); + + // need to find all nodes for which we need to generate node animations - + // it may happen that we need to merge multiple layers, though. + NodeMap node_map; + + // reverse mapping from curves to layers, much faster than querying + // the FBX DOM for it. + LayerMap layer_map; + + const char *prop_whitelist[] = { + "Lcl Scaling", + "Lcl Rotation", + "Lcl Translation", + "DeformPercent" + }; + + std::map<std::string, morphAnimData *> morphAnimDatas; + + for (const AnimationLayer *layer : layers) { + ai_assert(layer); + const AnimationCurveNodeList &nodes = layer->Nodes(prop_whitelist, 4); + for (const AnimationCurveNode *node : nodes) { + ai_assert(node); + const Model *const model = dynamic_cast<const Model *>(node->Target()); + if (model) { + const std::string &curName = FixNodeName(model->Name()); + node_map[curName].push_back(node); + layer_map[node] = layer; + continue; + } + const BlendShapeChannel *const bsc = dynamic_cast<const BlendShapeChannel *>(node->Target()); + if (bsc) { + ProcessMorphAnimDatas(&morphAnimDatas, bsc, node); + } + } + } + + // generate node animations + std::vector<aiNodeAnim *> node_anims; + + double min_time = 1e10; + double max_time = -1e10; + + int64_t start_time = st.LocalStart(); + int64_t stop_time = st.LocalStop(); + bool has_local_startstop = start_time != 0 || stop_time != 0; + if (!has_local_startstop) { + // no time range given, so accept every keyframe and use the actual min/max time + // the numbers are INT64_MIN/MAX, the 20000 is for safety because GenerateNodeAnimations uses an epsilon of 10000 + start_time = -9223372036854775807ll + 20000; + stop_time = 9223372036854775807ll - 20000; + } + + try { + for (const NodeMap::value_type &kv : node_map) { + GenerateNodeAnimations(node_anims, + kv.first, + kv.second, + layer_map, + start_time, stop_time, + max_time, + min_time); + } + } catch (std::exception &) { + std::for_each(node_anims.begin(), node_anims.end(), Util::delete_fun<aiNodeAnim>()); + throw; + } + + if (node_anims.size() || morphAnimDatas.size()) { + if (node_anims.size()) { + anim->mChannels = new aiNodeAnim *[node_anims.size()](); + anim->mNumChannels = static_cast<unsigned int>(node_anims.size()); + std::swap_ranges(node_anims.begin(), node_anims.end(), anim->mChannels); + } + if (morphAnimDatas.size()) { + unsigned int numMorphMeshChannels = static_cast<unsigned int>(morphAnimDatas.size()); + anim->mMorphMeshChannels = new aiMeshMorphAnim *[numMorphMeshChannels]; + anim->mNumMorphMeshChannels = numMorphMeshChannels; + unsigned int i = 0; + for (const auto &morphAnimIt : morphAnimDatas) { + morphAnimData *animData = morphAnimIt.second; + unsigned int numKeys = static_cast<unsigned int>(animData->size()); + aiMeshMorphAnim *meshMorphAnim = new aiMeshMorphAnim(); + meshMorphAnim->mName.Set(morphAnimIt.first); + meshMorphAnim->mNumKeys = numKeys; + meshMorphAnim->mKeys = new aiMeshMorphKey[numKeys]; + unsigned int j = 0; + for (auto animIt : *animData) { + morphKeyData *keyData = animIt.second; + unsigned int numValuesAndWeights = static_cast<unsigned int>(keyData->values.size()); + meshMorphAnim->mKeys[j].mNumValuesAndWeights = numValuesAndWeights; + meshMorphAnim->mKeys[j].mValues = new unsigned int[numValuesAndWeights]; + meshMorphAnim->mKeys[j].mWeights = new double[numValuesAndWeights]; + meshMorphAnim->mKeys[j].mTime = CONVERT_FBX_TIME(animIt.first) * anim_fps; + for (unsigned int k = 0; k < numValuesAndWeights; k++) { + meshMorphAnim->mKeys[j].mValues[k] = keyData->values.at(k); + meshMorphAnim->mKeys[j].mWeights[k] = keyData->weights.at(k); + } + j++; + } + anim->mMorphMeshChannels[i++] = meshMorphAnim; + } + } + } else { + // empty animations would fail validation, so drop them + delete anim; + animations.pop_back(); + FBXImporter::LogInfo("ignoring empty AnimationStack (using IK?): ", name); + return; + } + + double start_time_fps = has_local_startstop ? (CONVERT_FBX_TIME(start_time) * anim_fps) : min_time; + double stop_time_fps = has_local_startstop ? (CONVERT_FBX_TIME(stop_time) * anim_fps) : max_time; + + // adjust relative timing for animation + for (unsigned int c = 0; c < anim->mNumChannels; c++) { + aiNodeAnim *channel = anim->mChannels[c]; + for (uint32_t i = 0; i < channel->mNumPositionKeys; i++) { + channel->mPositionKeys[i].mTime -= start_time_fps; + } + for (uint32_t i = 0; i < channel->mNumRotationKeys; i++) { + channel->mRotationKeys[i].mTime -= start_time_fps; + } + for (uint32_t i = 0; i < channel->mNumScalingKeys; i++) { + channel->mScalingKeys[i].mTime -= start_time_fps; + } + } + for (unsigned int c = 0; c < anim->mNumMorphMeshChannels; c++) { + aiMeshMorphAnim *channel = anim->mMorphMeshChannels[c]; + for (uint32_t i = 0; i < channel->mNumKeys; i++) { + channel->mKeys[i].mTime -= start_time_fps; + } + } + + // for some mysterious reason, mDuration is simply the maximum key -- the + // validator always assumes animations to start at zero. + anim->mDuration = stop_time_fps - start_time_fps; + anim->mTicksPerSecond = anim_fps; +} + +// ------------------------------------------------------------------------------------------------ +void FBXConverter::ProcessMorphAnimDatas(std::map<std::string, morphAnimData *> *morphAnimDatas, const BlendShapeChannel *bsc, const AnimationCurveNode *node) { + std::vector<const Connection *> bscConnections = doc.GetConnectionsBySourceSequenced(bsc->ID(), "Deformer"); + for (const Connection *bscConnection : bscConnections) { + auto bs = dynamic_cast<const BlendShape *>(bscConnection->DestinationObject()); + if (bs) { + auto channelIt = std::find(bs->BlendShapeChannels().begin(), bs->BlendShapeChannels().end(), bsc); + if (channelIt != bs->BlendShapeChannels().end()) { + auto channelIndex = static_cast<unsigned int>(std::distance(bs->BlendShapeChannels().begin(), channelIt)); + std::vector<const Connection *> bsConnections = doc.GetConnectionsBySourceSequenced(bs->ID(), "Geometry"); + for (const Connection *bsConnection : bsConnections) { + auto geo = dynamic_cast<const Geometry *>(bsConnection->DestinationObject()); + if (geo) { + std::vector<const Connection *> geoConnections = doc.GetConnectionsBySourceSequenced(geo->ID(), "Model"); + for (const Connection *geoConnection : geoConnections) { + auto model = dynamic_cast<const Model *>(geoConnection->DestinationObject()); + if (model) { + auto geoIt = std::find(model->GetGeometry().begin(), model->GetGeometry().end(), geo); + auto geoIndex = static_cast<unsigned int>(std::distance(model->GetGeometry().begin(), geoIt)); + auto name = aiString(FixNodeName(model->Name() + "*")); + name.length = 1 + ASSIMP_itoa10(name.data + name.length, MAXLEN - 1, geoIndex); + morphAnimData *animData; + auto animIt = morphAnimDatas->find(name.C_Str()); + if (animIt == morphAnimDatas->end()) { + animData = new morphAnimData(); + morphAnimDatas->insert(std::make_pair(name.C_Str(), animData)); + } else { + animData = animIt->second; + } + for (std::pair<std::string, const AnimationCurve *> curvesIt : node->Curves()) { + if (curvesIt.first == "d|DeformPercent") { + const AnimationCurve *animationCurve = curvesIt.second; + const KeyTimeList &keys = animationCurve->GetKeys(); + const KeyValueList &values = animationCurve->GetValues(); + unsigned int k = 0; + for (auto key : keys) { + morphKeyData *keyData; + auto keyIt = animData->find(key); + if (keyIt == animData->end()) { + keyData = new morphKeyData(); + animData->insert(std::make_pair(key, keyData)); + } else { + keyData = keyIt->second; + } + keyData->values.push_back(channelIndex); + keyData->weights.push_back(values.at(k) / 100.0f); + k++; + } + } + } + } + } + } + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +#ifdef ASSIMP_BUILD_DEBUG +// ------------------------------------------------------------------------------------------------ +// sanity check whether the input is ok +static void validateAnimCurveNodes(const std::vector<const AnimationCurveNode *> &curves, + bool strictMode) { + const Object *target(nullptr); + for (const AnimationCurveNode *node : curves) { + if (!target) { + target = node->Target(); + } + if (node->Target() != target) { + FBXImporter::LogWarn("Node target is nullptr type."); + } + if (strictMode) { + ai_assert(node->Target() == target); + } + } +} +#endif // ASSIMP_BUILD_DEBUG + +// ------------------------------------------------------------------------------------------------ +void FBXConverter::GenerateNodeAnimations(std::vector<aiNodeAnim *> &node_anims, + const std::string &fixed_name, + const std::vector<const AnimationCurveNode *> &curves, + const LayerMap &layer_map, + int64_t start, int64_t stop, + double &max_time, + double &min_time) { + + NodeMap node_property_map; + ai_assert(curves.size()); + +#ifdef ASSIMP_BUILD_DEBUG + validateAnimCurveNodes(curves, doc.Settings().strictMode); +#endif + const AnimationCurveNode *curve_node = nullptr; + for (const AnimationCurveNode *node : curves) { + ai_assert(node); + + if (node->TargetProperty().empty()) { + FBXImporter::LogWarn("target property for animation curve not set: ", node->Name()); + continue; + } + + curve_node = node; + if (node->Curves().empty()) { + FBXImporter::LogWarn("no animation curves assigned to AnimationCurveNode: ", node->Name()); + continue; + } + + node_property_map[node->TargetProperty()].push_back(node); + } + + ai_assert(curve_node); + ai_assert(curve_node->TargetAsModel()); + + const Model &target = *curve_node->TargetAsModel(); + + // check for all possible transformation components + NodeMap::const_iterator chain[TransformationComp_MAXIMUM]; + + bool has_any = false; + bool has_complex = false; + + for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) { + const TransformationComp comp = static_cast<TransformationComp>(i); + + // inverse pivots don't exist in the input, we just generate them + if (comp == TransformationComp_RotationPivotInverse || comp == TransformationComp_ScalingPivotInverse) { + chain[i] = node_property_map.end(); + continue; + } + + chain[i] = node_property_map.find(NameTransformationCompProperty(comp)); + if (chain[i] != node_property_map.end()) { + + // check if this curves contains redundant information by looking + // up the corresponding node's transformation chain. + if (doc.Settings().optimizeEmptyAnimationCurves && + IsRedundantAnimationData(target, comp, (chain[i]->second))) { + + FBXImporter::LogVerboseDebug("dropping redundant animation channel for node ", target.Name()); + continue; + } + + has_any = true; + + if (comp != TransformationComp_Rotation && comp != TransformationComp_Scaling && comp != TransformationComp_Translation) { + has_complex = true; + } + } + } + + if (!has_any) { + FBXImporter::LogWarn("ignoring node animation, did not find any transformation key frames"); + return; + } + + // this needs to play nicely with GenerateTransformationNodeChain() which will + // be invoked _later_ (animations come first). If this node has only rotation, + // scaling and translation _and_ there are no animated other components either, + // we can use a single node and also a single node animation channel. + if( !has_complex && !NeedsComplexTransformationChain(target)) { + aiNodeAnim* const nd = GenerateSimpleNodeAnim(fixed_name, target, chain, + node_property_map.end(), + start, stop, + max_time, + min_time + ); + + ai_assert(nd); + if (nd->mNumPositionKeys == 0 && nd->mNumRotationKeys == 0 && nd->mNumScalingKeys == 0) { + delete nd; + } else { + node_anims.push_back(nd); + } + return; + } + + // otherwise, things get gruesome and we need separate animation channels + // for each part of the transformation chain. Remember which channels + // we generated and pass this information to the node conversion + // code to avoid nodes that have identity transform, but non-identity + // animations, being dropped. + unsigned int flags = 0, bit = 0x1; + for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) { + const TransformationComp comp = static_cast<TransformationComp>(i); + + if (chain[i] != node_property_map.end()) { + flags |= bit; + + ai_assert(comp != TransformationComp_RotationPivotInverse); + ai_assert(comp != TransformationComp_ScalingPivotInverse); + + const std::string &chain_name = NameTransformationChainNode(fixed_name, comp); + + aiNodeAnim *na = nullptr; + switch (comp) { + case TransformationComp_Rotation: + case TransformationComp_PreRotation: + case TransformationComp_PostRotation: + case TransformationComp_GeometricRotation: + na = GenerateRotationNodeAnim(chain_name, + target, + (*chain[i]).second, + layer_map, + start, stop, + max_time, + min_time); + + break; + + case TransformationComp_RotationOffset: + case TransformationComp_RotationPivot: + case TransformationComp_ScalingOffset: + case TransformationComp_ScalingPivot: + case TransformationComp_Translation: + case TransformationComp_GeometricTranslation: + na = GenerateTranslationNodeAnim(chain_name, + target, + (*chain[i]).second, + layer_map, + start, stop, + max_time, + min_time); + + // pivoting requires us to generate an implicit inverse channel to undo the pivot translation + if (comp == TransformationComp_RotationPivot) { + const std::string &invName = NameTransformationChainNode(fixed_name, + TransformationComp_RotationPivotInverse); + + aiNodeAnim *const inv = GenerateTranslationNodeAnim(invName, + target, + (*chain[i]).second, + layer_map, + start, stop, + max_time, + min_time, + true); + + ai_assert(inv); + if (inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0) { + delete inv; + } else { + node_anims.push_back(inv); + } + + ai_assert(TransformationComp_RotationPivotInverse > i); + flags |= bit << (TransformationComp_RotationPivotInverse - i); + } else if (comp == TransformationComp_ScalingPivot) { + const std::string &invName = NameTransformationChainNode(fixed_name, + TransformationComp_ScalingPivotInverse); + + aiNodeAnim *const inv = GenerateTranslationNodeAnim(invName, + target, + (*chain[i]).second, + layer_map, + start, stop, + max_time, + min_time, + true); + + ai_assert(inv); + if (inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0) { + delete inv; + } else { + node_anims.push_back(inv); + } + + ai_assert(TransformationComp_RotationPivotInverse > i); + flags |= bit << (TransformationComp_RotationPivotInverse - i); + } + + break; + + case TransformationComp_Scaling: + case TransformationComp_GeometricScaling: + na = GenerateScalingNodeAnim(chain_name, + target, + (*chain[i]).second, + layer_map, + start, stop, + max_time, + min_time); + + break; + + default: + ai_assert(false); + } + + ai_assert(na); + if (na->mNumPositionKeys == 0 && na->mNumRotationKeys == 0 && na->mNumScalingKeys == 0) { + delete na; + } else { + node_anims.push_back(na); + } + continue; + } + } + + node_anim_chain_bits[fixed_name] = flags; +} + +bool FBXConverter::IsRedundantAnimationData(const Model &target, + TransformationComp comp, + const std::vector<const AnimationCurveNode *> &curves) { + ai_assert(curves.size()); + + // look for animation nodes with + // * sub channels for all relevant components set + // * one key/value pair per component + // * combined values match up the corresponding value in the bind pose node transformation + // only such nodes are 'redundant' for this function. + + if (curves.size() > 1) { + return false; + } + + const AnimationCurveNode &nd = *curves.front(); + const AnimationCurveMap &sub_curves = nd.Curves(); + + const AnimationCurveMap::const_iterator dx = sub_curves.find("d|X"); + const AnimationCurveMap::const_iterator dy = sub_curves.find("d|Y"); + const AnimationCurveMap::const_iterator dz = sub_curves.find("d|Z"); + + if (dx == sub_curves.end() || dy == sub_curves.end() || dz == sub_curves.end()) { + return false; + } + + const KeyValueList &vx = (*dx).second->GetValues(); + const KeyValueList &vy = (*dy).second->GetValues(); + const KeyValueList &vz = (*dz).second->GetValues(); + + if (vx.size() != 1 || vy.size() != 1 || vz.size() != 1) { + return false; + } + + const aiVector3D dyn_val = aiVector3D(vx[0], vy[0], vz[0]); + const aiVector3D &static_val = PropertyGet<aiVector3D>(target.Props(), + NameTransformationCompProperty(comp), + TransformationCompDefaultValue(comp)); + + const float epsilon = Math::getEpsilon<float>(); + return (dyn_val - static_val).SquareLength() < epsilon; +} + +aiNodeAnim *FBXConverter::GenerateRotationNodeAnim(const std::string &name, + const Model &target, + const std::vector<const AnimationCurveNode *> &curves, + const LayerMap &layer_map, + int64_t start, int64_t stop, + double &max_time, + double &min_time) { + std::unique_ptr<aiNodeAnim> na(new aiNodeAnim()); + na->mNodeName.Set(name); + + ConvertRotationKeys(na.get(), curves, layer_map, start, stop, max_time, min_time, target.RotationOrder()); + + // dummy scaling key + na->mScalingKeys = new aiVectorKey[1]; + na->mNumScalingKeys = 1; + + na->mScalingKeys[0].mTime = 0.; + na->mScalingKeys[0].mValue = aiVector3D(1.0f, 1.0f, 1.0f); + + // dummy position key + na->mPositionKeys = new aiVectorKey[1]; + na->mNumPositionKeys = 1; + + na->mPositionKeys[0].mTime = 0.; + na->mPositionKeys[0].mValue = aiVector3D(); + + return na.release(); +} + +aiNodeAnim *FBXConverter::GenerateScalingNodeAnim(const std::string &name, + const Model & /*target*/, + const std::vector<const AnimationCurveNode *> &curves, + const LayerMap &layer_map, + int64_t start, int64_t stop, + double &max_time, + double &min_time) { + std::unique_ptr<aiNodeAnim> na(new aiNodeAnim()); + na->mNodeName.Set(name); + + ConvertScaleKeys(na.get(), curves, layer_map, start, stop, max_time, min_time); + + // dummy rotation key + na->mRotationKeys = new aiQuatKey[1]; + na->mNumRotationKeys = 1; + + na->mRotationKeys[0].mTime = 0.; + na->mRotationKeys[0].mValue = aiQuaternion(); + + // dummy position key + na->mPositionKeys = new aiVectorKey[1]; + na->mNumPositionKeys = 1; + + na->mPositionKeys[0].mTime = 0.; + na->mPositionKeys[0].mValue = aiVector3D(); + + return na.release(); +} + +aiNodeAnim *FBXConverter::GenerateTranslationNodeAnim(const std::string &name, + const Model & /*target*/, + const std::vector<const AnimationCurveNode *> &curves, + const LayerMap &layer_map, + int64_t start, int64_t stop, + double &max_time, + double &min_time, + bool inverse) { + std::unique_ptr<aiNodeAnim> na(new aiNodeAnim()); + na->mNodeName.Set(name); + + ConvertTranslationKeys(na.get(), curves, layer_map, start, stop, max_time, min_time); + + if (inverse) { + for (unsigned int i = 0; i < na->mNumPositionKeys; ++i) { + na->mPositionKeys[i].mValue *= -1.0f; + } + } + + // dummy scaling key + na->mScalingKeys = new aiVectorKey[1]; + na->mNumScalingKeys = 1; + + na->mScalingKeys[0].mTime = 0.; + na->mScalingKeys[0].mValue = aiVector3D(1.0f, 1.0f, 1.0f); + + // dummy rotation key + na->mRotationKeys = new aiQuatKey[1]; + na->mNumRotationKeys = 1; + + na->mRotationKeys[0].mTime = 0.; + na->mRotationKeys[0].mValue = aiQuaternion(); + + return na.release(); +} + +aiNodeAnim* FBXConverter::GenerateSimpleNodeAnim(const std::string& name, + const Model& target, + NodeMap::const_iterator chain[TransformationComp_MAXIMUM], + NodeMap::const_iterator iterEnd, + int64_t start, int64_t stop, + double& maxTime, + double& minTime) +{ + std::unique_ptr<aiNodeAnim> na(new aiNodeAnim()); + na->mNodeName.Set(name); + + const PropertyTable &props = target.Props(); + + // collect unique times and keyframe lists + KeyFrameListList keyframeLists[TransformationComp_MAXIMUM]; + KeyTimeList keytimes; + + for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) { + if (chain[i] == iterEnd) + continue; + + if (i == TransformationComp_Rotation || i == TransformationComp_PreRotation + || i == TransformationComp_PostRotation || i == TransformationComp_GeometricRotation) { + keyframeLists[i] = GetRotationKeyframeList((*chain[i]).second, start, stop); + } else { + keyframeLists[i] = GetKeyframeList((*chain[i]).second, start, stop); + } + + for (KeyFrameListList::const_iterator it = keyframeLists[i].begin(); it != keyframeLists[i].end(); ++it) { + const KeyTimeList& times = *std::get<0>(*it); + keytimes.insert(keytimes.end(), times.begin(), times.end()); + } + + // remove duplicates + std::sort(keytimes.begin(), keytimes.end()); + + auto last = std::unique(keytimes.begin(), keytimes.end()); + keytimes.erase(last, keytimes.end()); + } + + const Model::RotOrder rotOrder = target.RotationOrder(); + const size_t keyCount = keytimes.size(); + + aiVector3D defTranslate = PropertyGet(props, "Lcl Translation", aiVector3D(0.f, 0.f, 0.f)); + aiVector3D defRotation = PropertyGet(props, "Lcl Rotation", aiVector3D(0.f, 0.f, 0.f)); + aiVector3D defScale = PropertyGet(props, "Lcl Scaling", aiVector3D(1.f, 1.f, 1.f)); + aiQuaternion defQuat = EulerToQuaternion(defRotation, rotOrder); + + aiVectorKey* outTranslations = new aiVectorKey[keyCount]; + aiQuatKey* outRotations = new aiQuatKey[keyCount]; + aiVectorKey* outScales = new aiVectorKey[keyCount]; + + if (keyframeLists[TransformationComp_Translation].size() > 0) { + InterpolateKeys(outTranslations, keytimes, keyframeLists[TransformationComp_Translation], defTranslate, maxTime, minTime); + } else { + for (size_t i = 0; i < keyCount; ++i) { + outTranslations[i].mTime = CONVERT_FBX_TIME(keytimes[i]) * anim_fps; + outTranslations[i].mValue = defTranslate; + } + } + + if (keyframeLists[TransformationComp_Rotation].size() > 0) { + InterpolateKeys(outRotations, keytimes, keyframeLists[TransformationComp_Rotation], defRotation, maxTime, minTime, rotOrder); + } else { + for (size_t i = 0; i < keyCount; ++i) { + outRotations[i].mTime = CONVERT_FBX_TIME(keytimes[i]) * anim_fps; + outRotations[i].mValue = defQuat; + } + } + + if (keyframeLists[TransformationComp_Scaling].size() > 0) { + InterpolateKeys(outScales, keytimes, keyframeLists[TransformationComp_Scaling], defScale, maxTime, minTime); + } else { + for (size_t i = 0; i < keyCount; ++i) { + outScales[i].mTime = CONVERT_FBX_TIME(keytimes[i]) * anim_fps; + outScales[i].mValue = defScale; + } + } + + bool ok = false; + + const float zero_epsilon = ai_epsilon; + + const aiVector3D& preRotation = PropertyGet<aiVector3D>(props, "PreRotation", ok); + if (ok && preRotation.SquareLength() > zero_epsilon) { + const aiQuaternion preQuat = EulerToQuaternion(preRotation, Model::RotOrder_EulerXYZ); + for (size_t i = 0; i < keyCount; ++i) { + outRotations[i].mValue = preQuat * outRotations[i].mValue; + } + } + + const aiVector3D& postRotation = PropertyGet<aiVector3D>(props, "PostRotation", ok); + if (ok && postRotation.SquareLength() > zero_epsilon) { + const aiQuaternion postQuat = EulerToQuaternion(postRotation, Model::RotOrder_EulerXYZ); + for (size_t i = 0; i < keyCount; ++i) { + outRotations[i].mValue = outRotations[i].mValue * postQuat; + } + } + + // convert TRS to SRT + for (size_t i = 0; i < keyCount; ++i) { + aiQuaternion& r = outRotations[i].mValue; + aiVector3D& s = outScales[i].mValue; + aiVector3D& t = outTranslations[i].mValue; + + aiMatrix4x4 mat, temp; + aiMatrix4x4::Translation(t, mat); + mat *= aiMatrix4x4(r.GetMatrix()); + mat *= aiMatrix4x4::Scaling(s, temp); + + mat.Decompose(s, r, t); + } + + na->mNumScalingKeys = static_cast<unsigned int>(keyCount); + na->mNumRotationKeys = na->mNumScalingKeys; + na->mNumPositionKeys = na->mNumScalingKeys; + + na->mScalingKeys = outScales; + na->mRotationKeys = outRotations; + na->mPositionKeys = outTranslations; + + return na.release(); +} + +FBXConverter::KeyFrameListList FBXConverter::GetKeyframeList(const std::vector<const AnimationCurveNode *> &nodes, int64_t start, int64_t stop) { + KeyFrameListList inputs; + inputs.reserve(nodes.size() * 3); + + //give some breathing room for rounding errors + int64_t adj_start = start - 10000; + int64_t adj_stop = stop + 10000; + + for (const AnimationCurveNode *node : nodes) { + ai_assert(node); + + const AnimationCurveMap &curves = node->Curves(); + for (const AnimationCurveMap::value_type &kv : curves) { + + unsigned int mapto; + if (kv.first == "d|X") { + mapto = 0; + } else if (kv.first == "d|Y") { + mapto = 1; + } else if (kv.first == "d|Z") { + mapto = 2; + } else { + FBXImporter::LogWarn("ignoring scale animation curve, did not recognize target component"); + continue; + } + + const AnimationCurve *const curve = kv.second; + ai_assert(curve->GetKeys().size() == curve->GetValues().size()); + ai_assert(curve->GetKeys().size()); + + //get values within the start/stop time window + std::shared_ptr<KeyTimeList> Keys(new KeyTimeList()); + std::shared_ptr<KeyValueList> Values(new KeyValueList()); + const size_t count = curve->GetKeys().size(); + Keys->reserve(count); + Values->reserve(count); + for (size_t n = 0; n < count; n++) { + int64_t k = curve->GetKeys().at(n); + if (k >= adj_start && k <= adj_stop) { + Keys->push_back(k); + Values->push_back(curve->GetValues().at(n)); + } + } + + inputs.push_back(std::make_tuple(Keys, Values, mapto)); + } + } + return inputs; // pray for NRVO :-) +} + +FBXConverter::KeyFrameListList FBXConverter::GetRotationKeyframeList(const std::vector<const AnimationCurveNode *> &nodes, + int64_t start, int64_t stop) { + KeyFrameListList inputs; + inputs.reserve(nodes.size() * 3); + + //give some breathing room for rounding errors + const int64_t adj_start = start - 10000; + const int64_t adj_stop = stop + 10000; + + for (const AnimationCurveNode *node : nodes) { + ai_assert(node); + + const AnimationCurveMap &curves = node->Curves(); + for (const AnimationCurveMap::value_type &kv : curves) { + + unsigned int mapto; + if (kv.first == "d|X") { + mapto = 0; + } else if (kv.first == "d|Y") { + mapto = 1; + } else if (kv.first == "d|Z") { + mapto = 2; + } else { + FBXImporter::LogWarn("ignoring scale animation curve, did not recognize target component"); + continue; + } + + const AnimationCurve *const curve = kv.second; + ai_assert(curve->GetKeys().size() == curve->GetValues().size()); + ai_assert(curve->GetKeys().size()); + + //get values within the start/stop time window + std::shared_ptr<KeyTimeList> Keys(new KeyTimeList()); + std::shared_ptr<KeyValueList> Values(new KeyValueList()); + const size_t count = curve->GetKeys().size(); + + int64_t tp = curve->GetKeys().at(0); + float vp = curve->GetValues().at(0); + Keys->push_back(tp); + Values->push_back(vp); + if (count > 1) { + int64_t tc = curve->GetKeys().at(1); + float vc = curve->GetValues().at(1); + for (size_t n = 1; n < count; n++) { + while (std::abs(vc - vp) >= 180.0f) { + float step = std::floor(float(tc - tp) / (vc - vp) * 179.0f); + int64_t tnew = tp + int64_t(step); + float vnew = vp + (vc - vp) * step / float(tc - tp); + if (tnew >= adj_start && tnew <= adj_stop) { + Keys->push_back(tnew); + Values->push_back(vnew); + } + tp = tnew; + vp = vnew; + } + if (tc >= adj_start && tc <= adj_stop) { + Keys->push_back(tc); + Values->push_back(vc); + } + if (n + 1 < count) { + tp = tc; + vp = vc; + tc = curve->GetKeys().at(n + 1); + vc = curve->GetValues().at(n + 1); + } + } + } + inputs.push_back(std::make_tuple(Keys, Values, mapto)); + } + } + return inputs; +} + +KeyTimeList FBXConverter::GetKeyTimeList(const KeyFrameListList &inputs) { + ai_assert(!inputs.empty()); + + // reserve some space upfront - it is likely that the key-frame lists + // have matching time values, so max(of all key-frame lists) should + // be a good estimate. + KeyTimeList keys; + + size_t estimate = 0; + for (const KeyFrameList &kfl : inputs) { + estimate = std::max(estimate, std::get<0>(kfl)->size()); + } + + keys.reserve(estimate); + + std::vector<unsigned int> next_pos; + next_pos.resize(inputs.size(), 0); + + const size_t count = inputs.size(); + while (true) { + + int64_t min_tick = std::numeric_limits<int64_t>::max(); + for (size_t i = 0; i < count; ++i) { + const KeyFrameList &kfl = inputs[i]; + + if (std::get<0>(kfl)->size() > next_pos[i] && std::get<0>(kfl)->at(next_pos[i]) < min_tick) { + min_tick = std::get<0>(kfl)->at(next_pos[i]); + } + } + + if (min_tick == std::numeric_limits<int64_t>::max()) { + break; + } + keys.push_back(min_tick); + + for (size_t i = 0; i < count; ++i) { + const KeyFrameList &kfl = inputs[i]; + + while (std::get<0>(kfl)->size() > next_pos[i] && std::get<0>(kfl)->at(next_pos[i]) == min_tick) { + ++next_pos[i]; + } + } + } + + return keys; +} + +void FBXConverter::InterpolateKeys(aiVectorKey *valOut, const KeyTimeList &keys, const KeyFrameListList &inputs, + const aiVector3D &def_value, + double &max_time, + double &min_time) { + ai_assert(!keys.empty()); + ai_assert(nullptr != valOut); + + std::vector<unsigned int> next_pos; + const size_t count(inputs.size()); + + next_pos.resize(inputs.size(), 0); + + for (KeyTimeList::value_type time : keys) { + ai_real result[3] = { def_value.x, def_value.y, def_value.z }; + + for (size_t i = 0; i < count; ++i) { + const KeyFrameList &kfl = inputs[i]; + + const size_t ksize = std::get<0>(kfl)->size(); + if (ksize == 0) { + continue; + } + if (ksize > next_pos[i] && std::get<0>(kfl)->at(next_pos[i]) == time) { + ++next_pos[i]; + } + + const size_t id0 = next_pos[i] > 0 ? next_pos[i] - 1 : 0; + const size_t id1 = next_pos[i] == ksize ? ksize - 1 : next_pos[i]; + + // use lerp for interpolation + const KeyValueList::value_type valueA = std::get<1>(kfl)->at(id0); + const KeyValueList::value_type valueB = std::get<1>(kfl)->at(id1); + + const KeyTimeList::value_type timeA = std::get<0>(kfl)->at(id0); + const KeyTimeList::value_type timeB = std::get<0>(kfl)->at(id1); + + const ai_real factor = timeB == timeA ? ai_real(0.) : static_cast<ai_real>((time - timeA)) / (timeB - timeA); + const ai_real interpValue = static_cast<ai_real>(valueA + (valueB - valueA) * factor); + + result[std::get<2>(kfl)] = interpValue; + } + + // magic value to convert fbx times to seconds + valOut->mTime = CONVERT_FBX_TIME(time) * anim_fps; + + min_time = std::min(min_time, valOut->mTime); + max_time = std::max(max_time, valOut->mTime); + + valOut->mValue.x = result[0]; + valOut->mValue.y = result[1]; + valOut->mValue.z = result[2]; + + ++valOut; + } +} + +void FBXConverter::InterpolateKeys(aiQuatKey *valOut, const KeyTimeList &keys, const KeyFrameListList &inputs, + const aiVector3D &def_value, + double &maxTime, + double &minTime, + Model::RotOrder order) { + ai_assert(!keys.empty()); + ai_assert(nullptr != valOut); + + std::unique_ptr<aiVectorKey[]> temp(new aiVectorKey[keys.size()]); + InterpolateKeys(temp.get(), keys, inputs, def_value, maxTime, minTime); + + aiMatrix4x4 m; + + aiQuaternion lastq; + + for (size_t i = 0, c = keys.size(); i < c; ++i) { + + valOut[i].mTime = temp[i].mTime; + + GetRotationMatrix(order, temp[i].mValue, m); + aiQuaternion quat = aiQuaternion(aiMatrix3x3(m)); + + // take shortest path by checking the inner product + // http://www.3dkingdoms.com/weekly/weekly.php?a=36 + if (quat.x * lastq.x + quat.y * lastq.y + quat.z * lastq.z + quat.w * lastq.w < 0) { + quat.Conjugate(); + quat.w = -quat.w; + } + lastq = quat; + + valOut[i].mValue = quat; + } +} + +aiQuaternion FBXConverter::EulerToQuaternion(const aiVector3D &rot, Model::RotOrder order) { + aiMatrix4x4 m; + GetRotationMatrix(order, rot, m); + + return aiQuaternion(aiMatrix3x3(m)); +} + +void FBXConverter::ConvertScaleKeys(aiNodeAnim *na, const std::vector<const AnimationCurveNode *> &nodes, const LayerMap & /*layers*/, + int64_t start, int64_t stop, + double &maxTime, + double &minTime) { + ai_assert(nodes.size()); + + // XXX for now, assume scale should be blended geometrically (i.e. two + // layers should be multiplied with each other). There is a FBX + // property in the layer to specify the behaviour, though. + + const KeyFrameListList &inputs = GetKeyframeList(nodes, start, stop); + const KeyTimeList &keys = GetKeyTimeList(inputs); + + na->mNumScalingKeys = static_cast<unsigned int>(keys.size()); + na->mScalingKeys = new aiVectorKey[keys.size()]; + if (keys.size() > 0) { + InterpolateKeys(na->mScalingKeys, keys, inputs, aiVector3D(1.0f, 1.0f, 1.0f), maxTime, minTime); + } +} + +void FBXConverter::ConvertTranslationKeys(aiNodeAnim *na, const std::vector<const AnimationCurveNode *> &nodes, + const LayerMap & /*layers*/, + int64_t start, int64_t stop, + double &maxTime, + double &minTime) { + ai_assert(nodes.size()); + + // XXX see notes in ConvertScaleKeys() + const KeyFrameListList &inputs = GetKeyframeList(nodes, start, stop); + const KeyTimeList &keys = GetKeyTimeList(inputs); + + na->mNumPositionKeys = static_cast<unsigned int>(keys.size()); + na->mPositionKeys = new aiVectorKey[keys.size()]; + if (keys.size() > 0) + InterpolateKeys(na->mPositionKeys, keys, inputs, aiVector3D(0.0f, 0.0f, 0.0f), maxTime, minTime); +} + +void FBXConverter::ConvertRotationKeys(aiNodeAnim *na, const std::vector<const AnimationCurveNode *> &nodes, + const LayerMap & /*layers*/, + int64_t start, int64_t stop, + double &maxTime, + double &minTime, + Model::RotOrder order) { + ai_assert(nodes.size()); + + // XXX see notes in ConvertScaleKeys() + const std::vector<KeyFrameList> &inputs = GetRotationKeyframeList(nodes, start, stop); + const KeyTimeList &keys = GetKeyTimeList(inputs); + + na->mNumRotationKeys = static_cast<unsigned int>(keys.size()); + na->mRotationKeys = new aiQuatKey[keys.size()]; + if (!keys.empty()) { + InterpolateKeys(na->mRotationKeys, keys, inputs, aiVector3D(0.0f, 0.0f, 0.0f), maxTime, minTime, order); + } +} + +void FBXConverter::ConvertGlobalSettings() { + if (nullptr == mSceneOut) { + return; + } + + const bool hasGenerator = !doc.Creator().empty(); + + mSceneOut->mMetaData = aiMetadata::Alloc(16 + (hasGenerator ? 1 : 0)); + mSceneOut->mMetaData->Set(0, "UpAxis", doc.GlobalSettings().UpAxis()); + mSceneOut->mMetaData->Set(1, "UpAxisSign", doc.GlobalSettings().UpAxisSign()); + mSceneOut->mMetaData->Set(2, "FrontAxis", doc.GlobalSettings().FrontAxis()); + mSceneOut->mMetaData->Set(3, "FrontAxisSign", doc.GlobalSettings().FrontAxisSign()); + mSceneOut->mMetaData->Set(4, "CoordAxis", doc.GlobalSettings().CoordAxis()); + mSceneOut->mMetaData->Set(5, "CoordAxisSign", doc.GlobalSettings().CoordAxisSign()); + mSceneOut->mMetaData->Set(6, "OriginalUpAxis", doc.GlobalSettings().OriginalUpAxis()); + mSceneOut->mMetaData->Set(7, "OriginalUpAxisSign", doc.GlobalSettings().OriginalUpAxisSign()); + //const double unitScaleFactor = (double)doc.GlobalSettings().UnitScaleFactor(); + mSceneOut->mMetaData->Set(8, "UnitScaleFactor", doc.GlobalSettings().UnitScaleFactor()); + mSceneOut->mMetaData->Set(9, "OriginalUnitScaleFactor", doc.GlobalSettings().OriginalUnitScaleFactor()); + mSceneOut->mMetaData->Set(10, "AmbientColor", doc.GlobalSettings().AmbientColor()); + mSceneOut->mMetaData->Set(11, "FrameRate", (int)doc.GlobalSettings().TimeMode()); + mSceneOut->mMetaData->Set(12, "TimeSpanStart", doc.GlobalSettings().TimeSpanStart()); + mSceneOut->mMetaData->Set(13, "TimeSpanStop", doc.GlobalSettings().TimeSpanStop()); + mSceneOut->mMetaData->Set(14, "CustomFrameRate", doc.GlobalSettings().CustomFrameRate()); + mSceneOut->mMetaData->Set(15, AI_METADATA_SOURCE_FORMAT_VERSION, aiString(ai_to_string(doc.FBXVersion()))); + if (hasGenerator) { + mSceneOut->mMetaData->Set(16, AI_METADATA_SOURCE_GENERATOR, aiString(doc.Creator())); + } +} + +void FBXConverter::TransferDataToScene() { + ai_assert(!mSceneOut->mMeshes); + ai_assert(!mSceneOut->mNumMeshes); + + // note: the trailing () ensures initialization with nullptr - not + // many C++ users seem to know this, so pointing it out to avoid + // confusion why this code works. + + if (!mMeshes.empty()) { + mSceneOut->mMeshes = new aiMesh *[mMeshes.size()](); + mSceneOut->mNumMeshes = static_cast<unsigned int>(mMeshes.size()); + + std::swap_ranges(mMeshes.begin(), mMeshes.end(), mSceneOut->mMeshes); + } + + if (!materials.empty()) { + mSceneOut->mMaterials = new aiMaterial *[materials.size()](); + mSceneOut->mNumMaterials = static_cast<unsigned int>(materials.size()); + + std::swap_ranges(materials.begin(), materials.end(), mSceneOut->mMaterials); + } + + if (!animations.empty()) { + mSceneOut->mAnimations = new aiAnimation *[animations.size()](); + mSceneOut->mNumAnimations = static_cast<unsigned int>(animations.size()); + + std::swap_ranges(animations.begin(), animations.end(), mSceneOut->mAnimations); + } + + if (!lights.empty()) { + mSceneOut->mLights = new aiLight *[lights.size()](); + mSceneOut->mNumLights = static_cast<unsigned int>(lights.size()); + + std::swap_ranges(lights.begin(), lights.end(), mSceneOut->mLights); + } + + if (!cameras.empty()) { + mSceneOut->mCameras = new aiCamera *[cameras.size()](); + mSceneOut->mNumCameras = static_cast<unsigned int>(cameras.size()); + + std::swap_ranges(cameras.begin(), cameras.end(), mSceneOut->mCameras); + } + + if (!textures.empty()) { + mSceneOut->mTextures = new aiTexture *[textures.size()](); + mSceneOut->mNumTextures = static_cast<unsigned int>(textures.size()); + + std::swap_ranges(textures.begin(), textures.end(), mSceneOut->mTextures); + } +} + +void FBXConverter::ConvertOrphanedEmbeddedTextures() { + // in C++14 it could be: + // for (auto&& [id, object] : objects) + for (auto &&id_and_object : doc.Objects()) { + auto &&id = std::get<0>(id_and_object); + auto &&object = std::get<1>(id_and_object); + // If an object doesn't have parent + if (doc.ConnectionsBySource().count(id) == 0) { + const Texture *realTexture = nullptr; + try { + const auto &element = object->GetElement(); + const Token &key = element.KeyToken(); + const char *obtype = key.begin(); + const size_t length = static_cast<size_t>(key.end() - key.begin()); + if (strncmp(obtype, "Texture", length) == 0) { + if (const Texture *texture = static_cast<const Texture *>(object->Get())) { + if (texture->Media() && texture->Media()->ContentLength() > 0) { + realTexture = texture; + } + } + } + } catch (...) { + // do nothing + } + if (realTexture) { + const Video *media = realTexture->Media(); + unsigned int index = ConvertVideo(*media); + textures_converted[media] = index; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void ConvertToAssimpScene(aiScene *out, const Document &doc, bool removeEmptyBones) { + FBXConverter converter(out, doc, removeEmptyBones); +} + +} // namespace FBX +} // namespace Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/FBX/FBXConverter.h b/libs/assimp/code/AssetLib/FBX/FBXConverter.h new file mode 100644 index 0000000..becfdb3 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXConverter.h @@ -0,0 +1,476 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXDConverter.h + * @brief FBX DOM to aiScene conversion + */ +#ifndef INCLUDED_AI_FBX_CONVERTER_H +#define INCLUDED_AI_FBX_CONVERTER_H + +#include "FBXParser.h" +#include "FBXMeshGeometry.h" +#include "FBXDocument.h" +#include "FBXUtil.h" +#include "FBXProperties.h" +#include "FBXImporter.h" + +#include <assimp/anim.h> +#include <assimp/material.h> +#include <assimp/light.h> +#include <assimp/texture.h> +#include <assimp/camera.h> +#include <assimp/StringComparison.h> +#include <unordered_map> +#include <unordered_set> + +struct aiScene; +struct aiNode; +struct aiMaterial; + +struct morphKeyData { + std::vector<unsigned int> values; + std::vector<float> weights; +}; +typedef std::map<int64_t, morphKeyData*> morphAnimData; + +namespace Assimp { +namespace FBX { + +class Document; +/** + * Convert a FBX #Document to #aiScene + * @param out Empty scene to be populated + * @param doc Parsed FBX document + * @param removeEmptyBones Will remove bones, which do not have any references to vertices. + */ +void ConvertToAssimpScene(aiScene* out, const Document& doc, bool removeEmptyBones); + +/** Dummy class to encapsulate the conversion process */ +class FBXConverter { +public: + /** + * The different parts that make up the final local transformation of a fbx-node + */ + enum TransformationComp { + TransformationComp_GeometricScalingInverse = 0, + TransformationComp_GeometricRotationInverse, + TransformationComp_GeometricTranslationInverse, + TransformationComp_Translation, + TransformationComp_RotationOffset, + TransformationComp_RotationPivot, + TransformationComp_PreRotation, + TransformationComp_Rotation, + TransformationComp_PostRotation, + TransformationComp_RotationPivotInverse, + TransformationComp_ScalingOffset, + TransformationComp_ScalingPivot, + TransformationComp_Scaling, + TransformationComp_ScalingPivotInverse, + TransformationComp_GeometricTranslation, + TransformationComp_GeometricRotation, + TransformationComp_GeometricScaling, + + TransformationComp_MAXIMUM + }; + +public: + FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones); + ~FBXConverter(); + +private: + // ------------------------------------------------------------------------------------------------ + // find scene root and trigger recursive scene conversion + void ConvertRootNode(); + + // ------------------------------------------------------------------------------------------------ + // collect and assign child nodes + void ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node); + + // ------------------------------------------------------------------------------------------------ + void ConvertLights(const Model& model, const std::string &orig_name ); + + // ------------------------------------------------------------------------------------------------ + void ConvertCameras(const Model& model, const std::string &orig_name ); + + // ------------------------------------------------------------------------------------------------ + void ConvertLight( const Light& light, const std::string &orig_name ); + + // ------------------------------------------------------------------------------------------------ + void ConvertCamera( const Camera& cam, const std::string &orig_name ); + + // ------------------------------------------------------------------------------------------------ + void GetUniqueName( const std::string &name, std::string& uniqueName ); + + // ------------------------------------------------------------------------------------------------ + // this returns unified names usable within assimp identifiers (i.e. no space characters - + // while these would be allowed, they are a potential trouble spot so better not use them). + const char* NameTransformationComp(TransformationComp comp); + + // ------------------------------------------------------------------------------------------------ + // Returns an unique name for a node or traverses up a hierarchy until a non-empty name is found and + // then makes this name unique + std::string MakeUniqueNodeName(const Model* const model, const aiNode& parent); + + // ------------------------------------------------------------------------------------------------ + // note: this returns the REAL fbx property names + const char* NameTransformationCompProperty(TransformationComp comp); + + // ------------------------------------------------------------------------------------------------ + aiVector3D TransformationCompDefaultValue(TransformationComp comp); + + // ------------------------------------------------------------------------------------------------ + void GetRotationMatrix(Model::RotOrder mode, const aiVector3D& rotation, aiMatrix4x4& out); + // ------------------------------------------------------------------------------------------------ + /** + * checks if a node has more than just scaling, rotation and translation components + */ + bool NeedsComplexTransformationChain(const Model& model); + + // ------------------------------------------------------------------------------------------------ + // note: name must be a FixNodeName() result + std::string NameTransformationChainNode(const std::string& name, TransformationComp comp); + + // ------------------------------------------------------------------------------------------------ + /** + * note: memory for output_nodes is managed by the caller, via the PotentialNode struct. + */ + struct PotentialNode; + bool GenerateTransformationNodeChain(const Model& model, const std::string& name, std::vector<PotentialNode>& output_nodes, std::vector<PotentialNode>& post_output_nodes); + + // ------------------------------------------------------------------------------------------------ + void SetupNodeMetadata(const Model& model, aiNode& nd); + + // ------------------------------------------------------------------------------------------------ + void ConvertModel(const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform); + + // ------------------------------------------------------------------------------------------------ + // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed + std::vector<unsigned int> + ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform); + + // ------------------------------------------------------------------------------------------------ + std::vector<unsigned int> ConvertLine(const LineGeometry& line, aiNode *root_node); + + // ------------------------------------------------------------------------------------------------ + aiMesh* SetupEmptyMesh(const Geometry& mesh, aiNode *parent); + + // ------------------------------------------------------------------------------------------------ + unsigned int ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model, + const aiMatrix4x4 &absolute_transform, aiNode *parent, + aiNode *root_node); + + // ------------------------------------------------------------------------------------------------ + std::vector<unsigned int> + ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform); + + // ------------------------------------------------------------------------------------------------ + unsigned int ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, MatIndexArray::value_type index, + aiNode *parent, aiNode *root_node, const aiMatrix4x4 &absolute_transform); + + // ------------------------------------------------------------------------------------------------ + static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits<unsigned int>::max() */ + static_cast<unsigned int>(-1); + + // ------------------------------------------------------------------------------------------------ + /** + * - if materialIndex == NO_MATERIAL_SEPARATION, materials are not taken into + * account when determining which weights to include. + * - outputVertStartIndices is only used when a material index is specified, it gives for + * each output vertex the DOM index it maps to. + */ + void ConvertWeights(aiMesh *out, const MeshGeometry &geo, const aiMatrix4x4 &absolute_transform, + aiNode *parent = nullptr, unsigned int materialIndex = NO_MATERIAL_SEPARATION, + std::vector<unsigned int> *outputVertStartIndices = nullptr); + + // ------------------------------------------------------------------------------------------------ + void ConvertCluster(std::vector<aiBone *> &local_mesh_bones, const Cluster *cl, + std::vector<size_t> &out_indices, std::vector<size_t> &index_out_indices, + std::vector<size_t> &count_out_indices, const aiMatrix4x4 &absolute_transform, + aiNode *parent ); + + // ------------------------------------------------------------------------------------------------ + void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo, + MatIndexArray::value_type materialIndex); + + // ------------------------------------------------------------------------------------------------ + unsigned int GetDefaultMaterial(); + + // ------------------------------------------------------------------------------------------------ + // Material -> aiMaterial + unsigned int ConvertMaterial(const Material& material, const MeshGeometry* const mesh); + + // ------------------------------------------------------------------------------------------------ + // Video -> aiTexture + unsigned int ConvertVideo(const Video& video); + + // ------------------------------------------------------------------------------------------------ + // convert embedded texture if necessary and return actual texture path + aiString GetTexturePath(const Texture* tex); + + // ------------------------------------------------------------------------------------------------ + void TrySetTextureProperties(aiMaterial* out_mat, const TextureMap& textures, + const std::string& propName, + aiTextureType target, const MeshGeometry* const mesh); + + // ------------------------------------------------------------------------------------------------ + void TrySetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, + const std::string& propName, + aiTextureType target, const MeshGeometry* const mesh); + + // ------------------------------------------------------------------------------------------------ + void SetTextureProperties(aiMaterial* out_mat, const TextureMap& textures, const MeshGeometry* const mesh); + + // ------------------------------------------------------------------------------------------------ + void SetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh); + + // ------------------------------------------------------------------------------------------------ + aiColor3D GetColorPropertyFromMaterial(const PropertyTable& props, const std::string& baseName, + bool& result); + aiColor3D GetColorPropertyFactored(const PropertyTable& props, const std::string& colorName, + const std::string& factorName, bool& result, bool useTemplate = true); + aiColor3D GetColorProperty(const PropertyTable& props, const std::string& colorName, + bool& result, bool useTemplate = true); + + // ------------------------------------------------------------------------------------------------ + void SetShadingPropertiesCommon(aiMaterial* out_mat, const PropertyTable& props); + void SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTable& props, const TextureMap& textures, const MeshGeometry* const mesh); + + // ------------------------------------------------------------------------------------------------ + // get the number of fps for a FrameRate enumerated value + static double FrameRateToDouble(FileGlobalSettings::FrameRate fp, double customFPSVal = -1.0); + + // ------------------------------------------------------------------------------------------------ + // convert animation data to aiAnimation et al + void ConvertAnimations(); + + // ------------------------------------------------------------------------------------------------ + // takes a fbx node name and returns the identifier to be used in the assimp output scene. + // the function is guaranteed to provide consistent results over multiple invocations + // UNLESS RenameNode() is called for a particular node name. + std::string FixNodeName(const std::string& name); + std::string FixAnimMeshName(const std::string& name); + + typedef std::map<const AnimationCurveNode*, const AnimationLayer*> LayerMap; + + // XXX: better use multi_map .. + typedef std::map<std::string, std::vector<const AnimationCurveNode*> > NodeMap; + + // ------------------------------------------------------------------------------------------------ + void ConvertAnimationStack(const AnimationStack& st); + + // ------------------------------------------------------------------------------------------------ + void ProcessMorphAnimDatas(std::map<std::string, morphAnimData*>* morphAnimDatas, const BlendShapeChannel* bsc, const AnimationCurveNode* node); + + // ------------------------------------------------------------------------------------------------ + void GenerateNodeAnimations(std::vector<aiNodeAnim*>& node_anims, + const std::string& fixed_name, + const std::vector<const AnimationCurveNode*>& curves, + const LayerMap& layer_map, + int64_t start, int64_t stop, + double& max_time, + double& min_time); + + // ------------------------------------------------------------------------------------------------ + bool IsRedundantAnimationData(const Model& target, + TransformationComp comp, + const std::vector<const AnimationCurveNode*>& curves); + + // ------------------------------------------------------------------------------------------------ + aiNodeAnim* GenerateRotationNodeAnim(const std::string& name, + const Model& target, + const std::vector<const AnimationCurveNode*>& curves, + const LayerMap& layer_map, + int64_t start, int64_t stop, + double& max_time, + double& min_time); + + // ------------------------------------------------------------------------------------------------ + aiNodeAnim* GenerateScalingNodeAnim(const std::string& name, + const Model& /*target*/, + const std::vector<const AnimationCurveNode*>& curves, + const LayerMap& layer_map, + int64_t start, int64_t stop, + double& max_time, + double& min_time); + + // ------------------------------------------------------------------------------------------------ + aiNodeAnim* GenerateTranslationNodeAnim(const std::string& name, + const Model& /*target*/, + const std::vector<const AnimationCurveNode*>& curves, + const LayerMap& layer_map, + int64_t start, int64_t stop, + double& max_time, + double& min_time, + bool inverse = false); + + // ------------------------------------------------------------------------------------------------ + // generate node anim, extracting only Rotation, Scaling and Translation from the given chain + aiNodeAnim* GenerateSimpleNodeAnim(const std::string& name, + const Model& target, + NodeMap::const_iterator chain[TransformationComp_MAXIMUM], + NodeMap::const_iterator iterEnd, + int64_t start, int64_t stop, + double& maxTime, + double& minTime); + + // key (time), value, mapto (component index) + typedef std::tuple<std::shared_ptr<KeyTimeList>, std::shared_ptr<KeyValueList>, unsigned int > KeyFrameList; + typedef std::vector<KeyFrameList> KeyFrameListList; + + // ------------------------------------------------------------------------------------------------ + KeyFrameListList GetKeyframeList(const std::vector<const AnimationCurveNode*>& nodes, int64_t start, int64_t stop); + KeyFrameListList GetRotationKeyframeList(const std::vector<const AnimationCurveNode*>& nodes, int64_t start, int64_t stop); + + // ------------------------------------------------------------------------------------------------ + KeyTimeList GetKeyTimeList(const KeyFrameListList& inputs); + + // ------------------------------------------------------------------------------------------------ + void InterpolateKeys(aiVectorKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs, + const aiVector3D& def_value, + double& max_time, + double& min_time); + + // ------------------------------------------------------------------------------------------------ + void InterpolateKeys(aiQuatKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs, + const aiVector3D& def_value, + double& maxTime, + double& minTime, + Model::RotOrder order); + + // ------------------------------------------------------------------------------------------------ + // euler xyz -> quat + aiQuaternion EulerToQuaternion(const aiVector3D& rot, Model::RotOrder order); + + // ------------------------------------------------------------------------------------------------ + void ConvertScaleKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& /*layers*/, + int64_t start, int64_t stop, + double& maxTime, + double& minTime); + + // ------------------------------------------------------------------------------------------------ + void ConvertTranslationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, + const LayerMap& /*layers*/, + int64_t start, int64_t stop, + double& maxTime, + double& minTime); + + // ------------------------------------------------------------------------------------------------ + void ConvertRotationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, + const LayerMap& /*layers*/, + int64_t start, int64_t stop, + double& maxTime, + double& minTime, + Model::RotOrder order); + + // ------------------------------------------------------------------------------------------------ + // Copy global geometric data and some information about the source asset into scene metadata. + void ConvertGlobalSettings(); + + // ------------------------------------------------------------------------------------------------ + // copy generated meshes, animations, lights, cameras and textures to the output scene + void TransferDataToScene(); + + // ------------------------------------------------------------------------------------------------ + // FBX file could have embedded textures not connected to anything + void ConvertOrphanedEmbeddedTextures(); + +private: + // 0: not assigned yet, others: index is value - 1 + unsigned int defaultMaterialIndex; + + std::vector<aiMesh*> mMeshes; + std::vector<aiMaterial*> materials; + std::vector<aiAnimation*> animations; + std::vector<aiLight*> lights; + std::vector<aiCamera*> cameras; + std::vector<aiTexture*> textures; + + using MaterialMap = std::fbx_unordered_map<const Material*, unsigned int>; + MaterialMap materials_converted; + + using VideoMap = std::fbx_unordered_map<const Video*, unsigned int>; + VideoMap textures_converted; + + using MeshMap = std::fbx_unordered_map<const Geometry*, std::vector<unsigned int> >; + MeshMap meshes_converted; + + // fixed node name -> which trafo chain components have animations? + using NodeAnimBitMap = std::fbx_unordered_map<std::string, unsigned int> ; + NodeAnimBitMap node_anim_chain_bits; + + // number of nodes with the same name + using NodeNameCache = std::fbx_unordered_map<std::string, unsigned int>; + NodeNameCache mNodeNames; + + // Deformer name is not the same as a bone name - it does contain the bone name though :) + // Deformer names in FBX are always unique in an FBX file. + std::map<const std::string, aiBone *> bone_map; + + double anim_fps; + + aiScene* const mSceneOut; + const FBX::Document& doc; + bool mRemoveEmptyBones; + static void BuildBoneList(aiNode *current_node, const aiNode *root_node, const aiScene *scene, + std::vector<aiBone*>& bones); + + void BuildBoneStack(aiNode *current_node, const aiNode *root_node, const aiScene *scene, + const std::vector<aiBone *> &bones, + std::map<aiBone *, aiNode *> &bone_stack, + std::vector<aiNode*> &node_stack ); + + static void BuildNodeList(aiNode *current_node, std::vector<aiNode *> &nodes); + + static aiNode *GetNodeFromStack(const aiString &node_name, std::vector<aiNode *> &nodes); + + static aiNode *GetArmatureRoot(aiNode *bone_node, std::vector<aiBone*> &bone_list); + + static bool IsBoneNode(const aiString &bone_name, std::vector<aiBone *> &bones); +}; + +} +} + +#endif // INCLUDED_AI_FBX_CONVERTER_H diff --git a/libs/assimp/code/AssetLib/FBX/FBXDeformer.cpp b/libs/assimp/code/AssetLib/FBX/FBXDeformer.cpp new file mode 100644 index 0000000..ba245ed --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXDeformer.cpp @@ -0,0 +1,213 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXNoteAttribute.cpp + * @brief Assimp::FBX::NodeAttribute (and subclasses) implementation + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +#include "FBXParser.h" +#include "FBXDocument.h" +#include "FBXMeshGeometry.h" +#include "FBXImporter.h" +#include "FBXDocumentUtil.h" + +namespace Assimp { +namespace FBX { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +Deformer::Deformer(uint64_t id, const Element& element, const Document& doc, const std::string& name) + : Object(id,element,name) +{ + const Scope& sc = GetRequiredScope(element); + + const std::string& classname = ParseTokenAsString(GetRequiredToken(element,2)); + props = GetPropertyTable(doc,"Deformer.Fbx" + classname,element,sc,true); +} + + +// ------------------------------------------------------------------------------------------------ +Deformer::~Deformer() +{ + +} + + +// ------------------------------------------------------------------------------------------------ +Cluster::Cluster(uint64_t id, const Element& element, const Document& doc, const std::string& name) +: Deformer(id,element,doc,name) +, node() +{ + const Scope& sc = GetRequiredScope(element); + + const Element* const Indexes = sc["Indexes"]; + const Element* const Weights = sc["Weights"]; + + const Element& Transform = GetRequiredElement(sc,"Transform",&element); + const Element& TransformLink = GetRequiredElement(sc,"TransformLink",&element); + + transform = ReadMatrix(Transform); + transformLink = ReadMatrix(TransformLink); + + // it is actually possible that there be Deformer's with no weights + if (!!Indexes != !!Weights) { + DOMError("either Indexes or Weights are missing from Cluster",&element); + } + + if(Indexes) { + ParseVectorDataArray(indices,*Indexes); + ParseVectorDataArray(weights,*Weights); + } + + if(indices.size() != weights.size()) { + DOMError("sizes of index and weight array don't match up",&element); + } + + // read assigned node + const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID(),"Model"); + for(const Connection* con : conns) { + const Model* const mod = ProcessSimpleConnection<Model>(*con, false, "Model -> Cluster", element); + if(mod) { + node = mod; + break; + } + } + + if (!node) { + DOMError("failed to read target Node for Cluster",&element); + } +} + + +// ------------------------------------------------------------------------------------------------ +Cluster::~Cluster() +{ + +} + + +// ------------------------------------------------------------------------------------------------ +Skin::Skin(uint64_t id, const Element& element, const Document& doc, const std::string& name) +: Deformer(id,element,doc,name) +, accuracy( 0.0f ) { + const Scope& sc = GetRequiredScope(element); + + const Element* const Link_DeformAcuracy = sc["Link_DeformAcuracy"]; + if(Link_DeformAcuracy) { + accuracy = ParseTokenAsFloat(GetRequiredToken(*Link_DeformAcuracy,0)); + } + + // resolve assigned clusters + const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID(),"Deformer"); + + clusters.reserve(conns.size()); + for(const Connection* con : conns) { + + const Cluster* const cluster = ProcessSimpleConnection<Cluster>(*con, false, "Cluster -> Skin", element); + if(cluster) { + clusters.push_back(cluster); + continue; + } + } +} + + +// ------------------------------------------------------------------------------------------------ +Skin::~Skin() +{ + +} +// ------------------------------------------------------------------------------------------------ +BlendShape::BlendShape(uint64_t id, const Element& element, const Document& doc, const std::string& name) + : Deformer(id, element, doc, name) +{ + const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID(), "Deformer"); + blendShapeChannels.reserve(conns.size()); + for (const Connection* con : conns) { + const BlendShapeChannel* const bspc = ProcessSimpleConnection<BlendShapeChannel>(*con, false, "BlendShapeChannel -> BlendShape", element); + if (bspc) { + blendShapeChannels.push_back(bspc); + continue; + } + } +} +// ------------------------------------------------------------------------------------------------ +BlendShape::~BlendShape() +{ + +} +// ------------------------------------------------------------------------------------------------ +BlendShapeChannel::BlendShapeChannel(uint64_t id, const Element& element, const Document& doc, const std::string& name) + : Deformer(id, element, doc, name) +{ + const Scope& sc = GetRequiredScope(element); + const Element* const DeformPercent = sc["DeformPercent"]; + if (DeformPercent) { + percent = ParseTokenAsFloat(GetRequiredToken(*DeformPercent, 0)); + } + const Element* const FullWeights = sc["FullWeights"]; + if (FullWeights) { + ParseVectorDataArray(fullWeights, *FullWeights); + } + const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID(), "Geometry"); + shapeGeometries.reserve(conns.size()); + for (const Connection* con : conns) { + const ShapeGeometry* const sg = ProcessSimpleConnection<ShapeGeometry>(*con, false, "Shape -> BlendShapeChannel", element); + if (sg) { + shapeGeometries.push_back(sg); + continue; + } + } +} +// ------------------------------------------------------------------------------------------------ +BlendShapeChannel::~BlendShapeChannel() +{ + +} +// ------------------------------------------------------------------------------------------------ +} +} +#endif + diff --git a/libs/assimp/code/AssetLib/FBX/FBXDocument.cpp b/libs/assimp/code/AssetLib/FBX/FBXDocument.cpp new file mode 100644 index 0000000..f228b17 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXDocument.cpp @@ -0,0 +1,722 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXDocument.cpp + * @brief Implementation of the FBX DOM classes + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +#include "FBXDocument.h" +#include "FBXMeshGeometry.h" +#include "FBXParser.h" +#include "FBXUtil.h" +#include "FBXImporter.h" +#include "FBXImportSettings.h" +#include "FBXDocumentUtil.h" +#include "FBXProperties.h" + +#include <assimp/DefaultLogger.hpp> + +#include <functional> +#include <map> +#include <memory> +#include <utility> + +namespace Assimp { +namespace FBX { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +LazyObject::LazyObject(uint64_t id, const Element& element, const Document& doc) +: doc(doc) +, element(element) +, id(id) +, flags() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +LazyObject::~LazyObject() +{ + // empty +} + +// ------------------------------------------------------------------------------------------------ +const Object* LazyObject::Get(bool dieOnError) +{ + if(IsBeingConstructed() || FailedToConstruct()) { + return nullptr; + } + + if (object.get()) { + return object.get(); + } + + const Token& key = element.KeyToken(); + const TokenList& tokens = element.Tokens(); + + if(tokens.size() < 3) { + DOMError("expected at least 3 tokens: id, name and class tag",&element); + } + + const char* err; + std::string name = ParseTokenAsString(*tokens[1],err); + if (err) { + DOMError(err,&element); + } + + // small fix for binary reading: binary fbx files don't use + // prefixes such as Model:: in front of their names. The + // loading code expects this at many places, though! + // so convert the binary representation (a 0x0001) to the + // double colon notation. + if(tokens[1]->IsBinary()) { + for (size_t i = 0; i < name.length(); ++i) { + if (name[i] == 0x0 && name[i+1] == 0x1) { + name = name.substr(i+2) + "::" + name.substr(0,i); + } + } + } + + const std::string classtag = ParseTokenAsString(*tokens[2],err); + if (err) { + DOMError(err,&element); + } + + // prevent recursive calls + flags |= BEING_CONSTRUCTED; + + try { + // this needs to be relatively fast since it happens a lot, + // so avoid constructing strings all the time. + const char* obtype = key.begin(); + const size_t length = static_cast<size_t>(key.end()-key.begin()); + + // For debugging + //dumpObjectClassInfo( objtype, classtag ); + + if (!strncmp(obtype,"Geometry",length)) { + if (!strcmp(classtag.c_str(),"Mesh")) { + object.reset(new MeshGeometry(id,element,name,doc)); + } + if (!strcmp(classtag.c_str(), "Shape")) { + object.reset(new ShapeGeometry(id, element, name, doc)); + } + if (!strcmp(classtag.c_str(), "Line")) { + object.reset(new LineGeometry(id, element, name, doc)); + } + } + else if (!strncmp(obtype,"NodeAttribute",length)) { + if (!strcmp(classtag.c_str(),"Camera")) { + object.reset(new Camera(id,element,doc,name)); + } + else if (!strcmp(classtag.c_str(),"CameraSwitcher")) { + object.reset(new CameraSwitcher(id,element,doc,name)); + } + else if (!strcmp(classtag.c_str(),"Light")) { + object.reset(new Light(id,element,doc,name)); + } + else if (!strcmp(classtag.c_str(),"Null")) { + object.reset(new Null(id,element,doc,name)); + } + else if (!strcmp(classtag.c_str(),"LimbNode")) { + object.reset(new LimbNode(id,element,doc,name)); + } + } + else if (!strncmp(obtype,"Deformer",length)) { + if (!strcmp(classtag.c_str(),"Cluster")) { + object.reset(new Cluster(id,element,doc,name)); + } + else if (!strcmp(classtag.c_str(),"Skin")) { + object.reset(new Skin(id,element,doc,name)); + } + else if (!strcmp(classtag.c_str(), "BlendShape")) { + object.reset(new BlendShape(id, element, doc, name)); + } + else if (!strcmp(classtag.c_str(), "BlendShapeChannel")) { + object.reset(new BlendShapeChannel(id, element, doc, name)); + } + } + else if ( !strncmp( obtype, "Model", length ) ) { + // FK and IK effectors are not supported + if ( strcmp( classtag.c_str(), "IKEffector" ) && strcmp( classtag.c_str(), "FKEffector" ) ) { + object.reset( new Model( id, element, doc, name ) ); + } + } + else if (!strncmp(obtype,"Material",length)) { + object.reset(new Material(id,element,doc,name)); + } + else if (!strncmp(obtype,"Texture",length)) { + object.reset(new Texture(id,element,doc,name)); + } + else if (!strncmp(obtype,"LayeredTexture",length)) { + object.reset(new LayeredTexture(id,element,doc,name)); + } + else if (!strncmp(obtype,"Video",length)) { + object.reset(new Video(id,element,doc,name)); + } + else if (!strncmp(obtype,"AnimationStack",length)) { + object.reset(new AnimationStack(id,element,name,doc)); + } + else if (!strncmp(obtype,"AnimationLayer",length)) { + object.reset(new AnimationLayer(id,element,name,doc)); + } + // note: order matters for these two + else if (!strncmp(obtype,"AnimationCurve",length)) { + object.reset(new AnimationCurve(id,element,name,doc)); + } + else if (!strncmp(obtype,"AnimationCurveNode",length)) { + object.reset(new AnimationCurveNode(id,element,name,doc)); + } + } + catch(std::exception& ex) { + flags &= ~BEING_CONSTRUCTED; + flags |= FAILED_TO_CONSTRUCT; + + if(dieOnError || doc.Settings().strictMode) { + throw; + } + + // note: the error message is already formatted, so raw logging is ok + if(!DefaultLogger::isNullLogger()) { + ASSIMP_LOG_ERROR(ex.what()); + } + return nullptr; + } + + if (!object.get()) { + //DOMError("failed to convert element to DOM object, class: " + classtag + ", name: " + name,&element); + } + + flags &= ~BEING_CONSTRUCTED; + return object.get(); +} + +// ------------------------------------------------------------------------------------------------ +Object::Object(uint64_t id, const Element& element, const std::string& name) +: element(element) +, name(name) +, id(id) +{ + // empty +} + +// ------------------------------------------------------------------------------------------------ +Object::~Object() +{ + // empty +} + +// ------------------------------------------------------------------------------------------------ +FileGlobalSettings::FileGlobalSettings(const Document &doc, std::shared_ptr<const PropertyTable> props) : + props(std::move(props)), doc(doc) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +FileGlobalSettings::~FileGlobalSettings() +{ + // empty +} + +// ------------------------------------------------------------------------------------------------ +Document::Document(const Parser& parser, const ImportSettings& settings) +: settings(settings) +, parser(parser) +{ + ASSIMP_LOG_DEBUG("Creating FBX Document"); + + // Cannot use array default initialization syntax because vc8 fails on it + for (auto &timeStamp : creationTimeStamp) { + timeStamp = 0; + } + + ReadHeader(); + ReadPropertyTemplates(); + + ReadGlobalSettings(); + + // This order is important, connections need parsed objects to check + // whether connections are ok or not. Objects may not be evaluated yet, + // though, since this may require valid connections. + ReadObjects(); + ReadConnections(); +} + +// ------------------------------------------------------------------------------------------------ +Document::~Document() +{ + for(ObjectMap::value_type& v : objects) { + delete v.second; + } + + for(ConnectionMap::value_type& v : src_connections) { + delete v.second; + } + // |dest_connections| contain the same Connection objects as the |src_connections| +} + +// ------------------------------------------------------------------------------------------------ +static const unsigned int LowerSupportedVersion = 7100; +static const unsigned int UpperSupportedVersion = 7400; + +void Document::ReadHeader() { + // Read ID objects from "Objects" section + const Scope& sc = parser.GetRootScope(); + const Element* const ehead = sc["FBXHeaderExtension"]; + if(!ehead || !ehead->Compound()) { + DOMError("no FBXHeaderExtension dictionary found"); + } + + const Scope& shead = *ehead->Compound(); + fbxVersion = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(shead,"FBXVersion",ehead),0)); + ASSIMP_LOG_DEBUG("FBX Version: ", fbxVersion); + + // While we may have some success with newer files, we don't support + // the older 6.n fbx format + if(fbxVersion < LowerSupportedVersion ) { + DOMError("unsupported, old format version, supported are only FBX 2011, FBX 2012 and FBX 2013"); + } + if(fbxVersion > UpperSupportedVersion ) { + if(Settings().strictMode) { + DOMError("unsupported, newer format version, supported are only FBX 2011, FBX 2012 and FBX 2013" + " (turn off strict mode to try anyhow) "); + } + else { + DOMWarning("unsupported, newer format version, supported are only FBX 2011, FBX 2012 and FBX 2013," + " trying to read it nevertheless"); + } + } + + const Element* const ecreator = shead["Creator"]; + if(ecreator) { + creator = ParseTokenAsString(GetRequiredToken(*ecreator,0)); + } + + const Element* const etimestamp = shead["CreationTimeStamp"]; + if(etimestamp && etimestamp->Compound()) { + const Scope& stimestamp = *etimestamp->Compound(); + creationTimeStamp[0] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Year"),0)); + creationTimeStamp[1] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Month"),0)); + creationTimeStamp[2] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Day"),0)); + creationTimeStamp[3] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Hour"),0)); + creationTimeStamp[4] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Minute"),0)); + creationTimeStamp[5] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Second"),0)); + creationTimeStamp[6] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Millisecond"),0)); + } +} + +// ------------------------------------------------------------------------------------------------ +void Document::ReadGlobalSettings() +{ + const Scope& sc = parser.GetRootScope(); + const Element* const ehead = sc["GlobalSettings"]; + if ( nullptr == ehead || !ehead->Compound() ) { + DOMWarning( "no GlobalSettings dictionary found" ); + globals.reset(new FileGlobalSettings(*this, std::make_shared<const PropertyTable>())); + return; + } + + std::shared_ptr<const PropertyTable> props = GetPropertyTable( *this, "", *ehead, *ehead->Compound(), true ); + + //double v = PropertyGet<float>( *props.get(), std::string("UnitScaleFactor"), 1.0 ); + + if(!props) { + DOMError("GlobalSettings dictionary contains no property table"); + } + + globals.reset(new FileGlobalSettings(*this, props)); +} + +// ------------------------------------------------------------------------------------------------ +void Document::ReadObjects() +{ + // read ID objects from "Objects" section + const Scope& sc = parser.GetRootScope(); + const Element* const eobjects = sc["Objects"]; + if(!eobjects || !eobjects->Compound()) { + DOMError("no Objects dictionary found"); + } + + // add a dummy entry to represent the Model::RootNode object (id 0), + // which is only indirectly defined in the input file + objects[0] = new LazyObject(0L, *eobjects, *this); + + const Scope& sobjects = *eobjects->Compound(); + for(const ElementMap::value_type& el : sobjects.Elements()) { + + // extract ID + const TokenList& tok = el.second->Tokens(); + + if (tok.empty()) { + DOMError("expected ID after object key",el.second); + } + + const char* err; + const uint64_t id = ParseTokenAsID(*tok[0], err); + if(err) { + DOMError(err,el.second); + } + + // id=0 is normally implicit + if(id == 0L) { + DOMError("encountered object with implicitly defined id 0",el.second); + } + + if(objects.find(id) != objects.end()) { + DOMWarning("encountered duplicate object id, ignoring first occurrence",el.second); + } + + objects[id] = new LazyObject(id, *el.second, *this); + + // grab all animation stacks upfront since there is no listing of them + if(!strcmp(el.first.c_str(),"AnimationStack")) { + animationStacks.push_back(id); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void Document::ReadPropertyTemplates() +{ + const Scope& sc = parser.GetRootScope(); + // read property templates from "Definitions" section + const Element* const edefs = sc["Definitions"]; + if(!edefs || !edefs->Compound()) { + DOMWarning("no Definitions dictionary found"); + return; + } + + const Scope& sdefs = *edefs->Compound(); + const ElementCollection otypes = sdefs.GetCollection("ObjectType"); + for(ElementMap::const_iterator it = otypes.first; it != otypes.second; ++it) { + const Element& el = *(*it).second; + const Scope* curSc = el.Compound(); + if (!curSc) { + DOMWarning("expected nested scope in ObjectType, ignoring",&el); + continue; + } + + const TokenList& tok = el.Tokens(); + if(tok.empty()) { + DOMWarning("expected name for ObjectType element, ignoring",&el); + continue; + } + + const std::string& oname = ParseTokenAsString(*tok[0]); + + const ElementCollection templs = curSc->GetCollection("PropertyTemplate"); + for (ElementMap::const_iterator elemIt = templs.first; elemIt != templs.second; ++elemIt) { + const Element &innerEl = *(*elemIt).second; + const Scope *innerSc = innerEl.Compound(); + if (!innerSc) { + DOMWarning("expected nested scope in PropertyTemplate, ignoring",&el); + continue; + } + + const TokenList &curTok = innerEl.Tokens(); + if (curTok.empty()) { + DOMWarning("expected name for PropertyTemplate element, ignoring",&el); + continue; + } + + const std::string &pname = ParseTokenAsString(*curTok[0]); + + const Element *Properties70 = (*innerSc)["Properties70"]; + if(Properties70) { + std::shared_ptr<const PropertyTable> props = std::make_shared<const PropertyTable>( + *Properties70, std::shared_ptr<const PropertyTable>(static_cast<const PropertyTable *>(nullptr)) + ); + + templates[oname+"."+pname] = props; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void Document::ReadConnections() +{ + const Scope& sc = parser.GetRootScope(); + // read property templates from "Definitions" section + const Element* const econns = sc["Connections"]; + if(!econns || !econns->Compound()) { + DOMError("no Connections dictionary found"); + } + + uint64_t insertionOrder = 0l; + const Scope& sconns = *econns->Compound(); + const ElementCollection conns = sconns.GetCollection("C"); + for(ElementMap::const_iterator it = conns.first; it != conns.second; ++it) { + const Element& el = *(*it).second; + const std::string& type = ParseTokenAsString(GetRequiredToken(el,0)); + + // PP = property-property connection, ignored for now + // (tokens: "PP", ID1, "Property1", ID2, "Property2") + if ( type == "PP" ) { + continue; + } + + const uint64_t src = ParseTokenAsID(GetRequiredToken(el,1)); + const uint64_t dest = ParseTokenAsID(GetRequiredToken(el,2)); + + // OO = object-object connection + // OP = object-property connection, in which case the destination property follows the object ID + const std::string& prop = (type == "OP" ? ParseTokenAsString(GetRequiredToken(el,3)) : ""); + + if(objects.find(src) == objects.end()) { + DOMWarning("source object for connection does not exist",&el); + continue; + } + + // dest may be 0 (root node) but we added a dummy object before + if(objects.find(dest) == objects.end()) { + DOMWarning("destination object for connection does not exist",&el); + continue; + } + + // add new connection + const Connection* const c = new Connection(insertionOrder++,src,dest,prop,*this); + src_connections.insert(ConnectionMap::value_type(src,c)); + dest_connections.insert(ConnectionMap::value_type(dest,c)); + } +} + +// ------------------------------------------------------------------------------------------------ +const std::vector<const AnimationStack*>& Document::AnimationStacks() const +{ + if (!animationStacksResolved.empty() || animationStacks.empty()) { + return animationStacksResolved; + } + + animationStacksResolved.reserve(animationStacks.size()); + for(uint64_t id : animationStacks) { + LazyObject* const lazy = GetObject(id); + const AnimationStack *stack = lazy->Get<AnimationStack>(); + if(!lazy || nullptr == stack ) { + DOMWarning("failed to read AnimationStack object"); + continue; + } + animationStacksResolved.push_back(stack); + } + + return animationStacksResolved; +} + +// ------------------------------------------------------------------------------------------------ +LazyObject* Document::GetObject(uint64_t id) const +{ + ObjectMap::const_iterator it = objects.find(id); + return it == objects.end() ? nullptr : (*it).second; +} + +#define MAX_CLASSNAMES 6 + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection*> Document::GetConnectionsSequenced(uint64_t id, const ConnectionMap& conns) const +{ + std::vector<const Connection*> temp; + + const std::pair<ConnectionMap::const_iterator,ConnectionMap::const_iterator> range = + conns.equal_range(id); + + temp.reserve(std::distance(range.first,range.second)); + for (ConnectionMap::const_iterator it = range.first; it != range.second; ++it) { + temp.push_back((*it).second); + } + + std::sort(temp.begin(), temp.end(), std::mem_fn(&Connection::Compare)); + + return temp; // NRVO should handle this +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection*> Document::GetConnectionsSequenced(uint64_t id, bool is_src, + const ConnectionMap& conns, + const char* const* classnames, + size_t count) const + +{ + ai_assert(classnames); + ai_assert( count != 0 ); + ai_assert( count <= MAX_CLASSNAMES); + + size_t lengths[MAX_CLASSNAMES]; + + const size_t c = count; + for (size_t i = 0; i < c; ++i) { + lengths[ i ] = strlen(classnames[i]); + } + + std::vector<const Connection*> temp; + const std::pair<ConnectionMap::const_iterator,ConnectionMap::const_iterator> range = + conns.equal_range(id); + + temp.reserve(std::distance(range.first,range.second)); + for (ConnectionMap::const_iterator it = range.first; it != range.second; ++it) { + const Token& key = (is_src + ? (*it).second->LazyDestinationObject() + : (*it).second->LazySourceObject() + ).GetElement().KeyToken(); + + const char* obtype = key.begin(); + + for (size_t i = 0; i < c; ++i) { + ai_assert(classnames[i]); + if(static_cast<size_t>(std::distance(key.begin(),key.end())) == lengths[i] && !strncmp(classnames[i],obtype,lengths[i])) { + obtype = nullptr; + break; + } + } + + if(obtype) { + continue; + } + + temp.push_back((*it).second); + } + + std::sort(temp.begin(), temp.end(), std::mem_fn(&Connection::Compare)); + return temp; // NRVO should handle this +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection*> Document::GetConnectionsBySourceSequenced(uint64_t source) const +{ + return GetConnectionsSequenced(source, ConnectionsBySource()); +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection*> Document::GetConnectionsBySourceSequenced(uint64_t src, const char* classname) const +{ + const char* arr[] = {classname}; + return GetConnectionsBySourceSequenced(src, arr,1); +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection*> Document::GetConnectionsBySourceSequenced(uint64_t source, + const char* const* classnames, size_t count) const +{ + return GetConnectionsSequenced(source, true, ConnectionsBySource(),classnames, count); +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection*> Document::GetConnectionsByDestinationSequenced(uint64_t dest, + const char* classname) const +{ + const char* arr[] = {classname}; + return GetConnectionsByDestinationSequenced(dest, arr,1); +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection*> Document::GetConnectionsByDestinationSequenced(uint64_t dest) const +{ + return GetConnectionsSequenced(dest, ConnectionsByDestination()); +} + +// ------------------------------------------------------------------------------------------------ +std::vector<const Connection*> Document::GetConnectionsByDestinationSequenced(uint64_t dest, + const char* const* classnames, size_t count) const + +{ + return GetConnectionsSequenced(dest, false, ConnectionsByDestination(),classnames, count); +} + +// ------------------------------------------------------------------------------------------------ +Connection::Connection(uint64_t insertionOrder, uint64_t src, uint64_t dest, const std::string& prop, + const Document& doc) + +: insertionOrder(insertionOrder) +, prop(prop) +, src(src) +, dest(dest) +, doc(doc) +{ + ai_assert(doc.Objects().find(src) != doc.Objects().end()); + // dest may be 0 (root node) + ai_assert(!dest || doc.Objects().find(dest) != doc.Objects().end()); +} + +// ------------------------------------------------------------------------------------------------ +Connection::~Connection() +{ + // empty +} + +// ------------------------------------------------------------------------------------------------ +LazyObject& Connection::LazySourceObject() const +{ + LazyObject* const lazy = doc.GetObject(src); + ai_assert(lazy); + return *lazy; +} + +// ------------------------------------------------------------------------------------------------ +LazyObject& Connection::LazyDestinationObject() const +{ + LazyObject* const lazy = doc.GetObject(dest); + ai_assert(lazy); + return *lazy; +} + +// ------------------------------------------------------------------------------------------------ +const Object* Connection::SourceObject() const +{ + LazyObject* const lazy = doc.GetObject(src); + ai_assert(lazy); + return lazy->Get(); +} + +// ------------------------------------------------------------------------------------------------ +const Object* Connection::DestinationObject() const +{ + LazyObject* const lazy = doc.GetObject(dest); + ai_assert(lazy); + return lazy->Get(); +} + +} // !FBX +} // !Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/FBX/FBXDocument.h b/libs/assimp/code/AssetLib/FBX/FBXDocument.h new file mode 100644 index 0000000..bac7e77 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXDocument.h @@ -0,0 +1,1186 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXDocument.h + * @brief FBX DOM + */ +#ifndef INCLUDED_AI_FBX_DOCUMENT_H +#define INCLUDED_AI_FBX_DOCUMENT_H + +#include <numeric> +#include <stdint.h> +#include <assimp/mesh.h> +#include "FBXProperties.h" +#include "FBXParser.h" + +#define _AI_CONCAT(a,b) a ## b +#define AI_CONCAT(a,b) _AI_CONCAT(a,b) + +namespace Assimp { +namespace FBX { + +class Parser; +class Object; +struct ImportSettings; + +class PropertyTable; +class Document; +class Material; +class ShapeGeometry; +class LineGeometry; +class Geometry; + +class Video; + +class AnimationCurve; +class AnimationCurveNode; +class AnimationLayer; +class AnimationStack; + +class BlendShapeChannel; +class BlendShape; +class Skin; +class Cluster; + + +/** Represents a delay-parsed FBX objects. Many objects in the scene + * are not needed by assimp, so it makes no sense to parse them + * upfront. */ +class LazyObject { +public: + LazyObject(uint64_t id, const Element& element, const Document& doc); + + ~LazyObject(); + + const Object* Get(bool dieOnError = false); + + template <typename T> + const T* Get(bool dieOnError = false) { + const Object* const ob = Get(dieOnError); + return ob ? dynamic_cast<const T *>(ob) : nullptr; + } + + uint64_t ID() const { + return id; + } + + bool IsBeingConstructed() const { + return (flags & BEING_CONSTRUCTED) != 0; + } + + bool FailedToConstruct() const { + return (flags & FAILED_TO_CONSTRUCT) != 0; + } + + const Element& GetElement() const { + return element; + } + + const Document& GetDocument() const { + return doc; + } + +private: + const Document& doc; + const Element& element; + std::unique_ptr<const Object> object; + + const uint64_t id; + + enum Flags { + BEING_CONSTRUCTED = 0x1, + FAILED_TO_CONSTRUCT = 0x2 + }; + + unsigned int flags; +}; + +/** Base class for in-memory (DOM) representations of FBX objects */ +class Object { +public: + Object(uint64_t id, const Element& element, const std::string& name); + + virtual ~Object(); + + const Element& SourceElement() const { + return element; + } + + const std::string& Name() const { + return name; + } + + uint64_t ID() const { + return id; + } + +protected: + const Element& element; + const std::string name; + const uint64_t id; +}; + +/** DOM class for generic FBX NoteAttribute blocks. NoteAttribute's just hold a property table, + * fixed members are added by deriving classes. */ +class NodeAttribute : public Object { +public: + NodeAttribute(uint64_t id, const Element& element, const Document& doc, const std::string& name); + + virtual ~NodeAttribute(); + + const PropertyTable& Props() const { + ai_assert(props.get()); + return *props.get(); + } + +private: + std::shared_ptr<const PropertyTable> props; +}; + +/** DOM base class for FBX camera settings attached to a node */ +class CameraSwitcher : public NodeAttribute { +public: + CameraSwitcher(uint64_t id, const Element& element, const Document& doc, const std::string& name); + + virtual ~CameraSwitcher(); + + int CameraID() const { + return cameraId; + } + + const std::string& CameraName() const { + return cameraName; + } + + const std::string& CameraIndexName() const { + return cameraIndexName; + } + +private: + int cameraId; + std::string cameraName; + std::string cameraIndexName; +}; + +#define fbx_stringize(a) #a + +#define fbx_simple_property(name, type, default_value) \ + type name() const { \ + return PropertyGet<type>(Props(), fbx_stringize(name), (default_value)); \ + } + +// XXX improve logging +#define fbx_simple_enum_property(name, type, default_value) \ + type name() const { \ + const int ival = PropertyGet<int>(Props(), fbx_stringize(name), static_cast<int>(default_value)); \ + if (ival < 0 || ival >= AI_CONCAT(type, _MAX)) { \ + ai_assert(static_cast<int>(default_value) >= 0); \ + ai_assert(static_cast<int>(default_value) < AI_CONCAT(type, _MAX)); \ + return static_cast<type>(default_value); \ + } \ + return static_cast<type>(ival); \ +} + + +/** DOM base class for FBX cameras attached to a node */ +class Camera : public NodeAttribute { +public: + Camera(uint64_t id, const Element& element, const Document& doc, const std::string& name); + + virtual ~Camera(); + + fbx_simple_property(Position, aiVector3D, aiVector3D(0,0,0)) + fbx_simple_property(UpVector, aiVector3D, aiVector3D(0,1,0)) + fbx_simple_property(InterestPosition, aiVector3D, aiVector3D(0,0,0)) + + fbx_simple_property(AspectWidth, float, 1.0f) + fbx_simple_property(AspectHeight, float, 1.0f) + fbx_simple_property(FilmWidth, float, 1.0f) + fbx_simple_property(FilmHeight, float, 1.0f) + + fbx_simple_property(NearPlane, float, 0.1f) + fbx_simple_property(FarPlane, float, 100.0f) + + fbx_simple_property(FilmAspectRatio, float, 1.0f) + fbx_simple_property(ApertureMode, int, 0) + + fbx_simple_property(FieldOfView, float, 1.0f) + fbx_simple_property(FocalLength, float, 1.0f) +}; + +/** DOM base class for FBX null markers attached to a node */ +class Null : public NodeAttribute { +public: + Null(uint64_t id, const Element& element, const Document& doc, const std::string& name); + virtual ~Null(); +}; + +/** DOM base class for FBX limb node markers attached to a node */ +class LimbNode : public NodeAttribute { +public: + LimbNode(uint64_t id, const Element& element, const Document& doc, const std::string& name); + virtual ~LimbNode(); +}; + +/** DOM base class for FBX lights attached to a node */ +class Light : public NodeAttribute { +public: + Light(uint64_t id, const Element& element, const Document& doc, const std::string& name); + virtual ~Light(); + + enum Type + { + Type_Point, + Type_Directional, + Type_Spot, + Type_Area, + Type_Volume, + + Type_MAX // end-of-enum sentinel + }; + + enum Decay + { + Decay_None, + Decay_Linear, + Decay_Quadratic, + Decay_Cubic, + + Decay_MAX // end-of-enum sentinel + }; + + fbx_simple_property(Color, aiVector3D, aiVector3D(1,1,1)) + fbx_simple_enum_property(LightType, Type, 0) + fbx_simple_property(CastLightOnObject, bool, false) + fbx_simple_property(DrawVolumetricLight, bool, true) + fbx_simple_property(DrawGroundProjection, bool, true) + fbx_simple_property(DrawFrontFacingVolumetricLight, bool, false) + fbx_simple_property(Intensity, float, 100.0f) + fbx_simple_property(InnerAngle, float, 0.0f) + fbx_simple_property(OuterAngle, float, 45.0f) + fbx_simple_property(Fog, int, 50) + fbx_simple_enum_property(DecayType, Decay, 2) + fbx_simple_property(DecayStart, float, 1.0f) + fbx_simple_property(FileName, std::string, "") + + fbx_simple_property(EnableNearAttenuation, bool, false) + fbx_simple_property(NearAttenuationStart, float, 0.0f) + fbx_simple_property(NearAttenuationEnd, float, 0.0f) + fbx_simple_property(EnableFarAttenuation, bool, false) + fbx_simple_property(FarAttenuationStart, float, 0.0f) + fbx_simple_property(FarAttenuationEnd, float, 0.0f) + + fbx_simple_property(CastShadows, bool, true) + fbx_simple_property(ShadowColor, aiVector3D, aiVector3D(0,0,0)) + + fbx_simple_property(AreaLightShape, int, 0) + + fbx_simple_property(LeftBarnDoor, float, 20.0f) + fbx_simple_property(RightBarnDoor, float, 20.0f) + fbx_simple_property(TopBarnDoor, float, 20.0f) + fbx_simple_property(BottomBarnDoor, float, 20.0f) + fbx_simple_property(EnableBarnDoor, bool, true) +}; + +/** DOM base class for FBX models (even though its semantics are more "node" than "model" */ +class Model : public Object { +public: + enum RotOrder { + RotOrder_EulerXYZ = 0, + RotOrder_EulerXZY, + RotOrder_EulerYZX, + RotOrder_EulerYXZ, + RotOrder_EulerZXY, + RotOrder_EulerZYX, + + RotOrder_SphericXYZ, + + RotOrder_MAX // end-of-enum sentinel + }; + + enum TransformInheritance { + TransformInheritance_RrSs = 0, + TransformInheritance_RSrs, + TransformInheritance_Rrs, + + TransformInheritance_MAX // end-of-enum sentinel + }; + + Model(uint64_t id, const Element& element, const Document& doc, const std::string& name); + + virtual ~Model(); + + fbx_simple_property(QuaternionInterpolate, int, 0) + + fbx_simple_property(RotationOffset, aiVector3D, aiVector3D()) + fbx_simple_property(RotationPivot, aiVector3D, aiVector3D()) + fbx_simple_property(ScalingOffset, aiVector3D, aiVector3D()) + fbx_simple_property(ScalingPivot, aiVector3D, aiVector3D()) + fbx_simple_property(TranslationActive, bool, false) + + fbx_simple_property(TranslationMin, aiVector3D, aiVector3D()) + fbx_simple_property(TranslationMax, aiVector3D, aiVector3D()) + + fbx_simple_property(TranslationMinX, bool, false) + fbx_simple_property(TranslationMaxX, bool, false) + fbx_simple_property(TranslationMinY, bool, false) + fbx_simple_property(TranslationMaxY, bool, false) + fbx_simple_property(TranslationMinZ, bool, false) + fbx_simple_property(TranslationMaxZ, bool, false) + + fbx_simple_enum_property(RotationOrder, RotOrder, 0) + fbx_simple_property(RotationSpaceForLimitOnly, bool, false) + fbx_simple_property(RotationStiffnessX, float, 0.0f) + fbx_simple_property(RotationStiffnessY, float, 0.0f) + fbx_simple_property(RotationStiffnessZ, float, 0.0f) + fbx_simple_property(AxisLen, float, 0.0f) + + fbx_simple_property(PreRotation, aiVector3D, aiVector3D()) + fbx_simple_property(PostRotation, aiVector3D, aiVector3D()) + fbx_simple_property(RotationActive, bool, false) + + fbx_simple_property(RotationMin, aiVector3D, aiVector3D()) + fbx_simple_property(RotationMax, aiVector3D, aiVector3D()) + + fbx_simple_property(RotationMinX, bool, false) + fbx_simple_property(RotationMaxX, bool, false) + fbx_simple_property(RotationMinY, bool, false) + fbx_simple_property(RotationMaxY, bool, false) + fbx_simple_property(RotationMinZ, bool, false) + fbx_simple_property(RotationMaxZ, bool, false) + fbx_simple_enum_property(InheritType, TransformInheritance, 0) + + fbx_simple_property(ScalingActive, bool, false) + fbx_simple_property(ScalingMin, aiVector3D, aiVector3D()) + fbx_simple_property(ScalingMax, aiVector3D, aiVector3D(1.f,1.f,1.f)) + fbx_simple_property(ScalingMinX, bool, false) + fbx_simple_property(ScalingMaxX, bool, false) + fbx_simple_property(ScalingMinY, bool, false) + fbx_simple_property(ScalingMaxY, bool, false) + fbx_simple_property(ScalingMinZ, bool, false) + fbx_simple_property(ScalingMaxZ, bool, false) + + fbx_simple_property(GeometricTranslation, aiVector3D, aiVector3D()) + fbx_simple_property(GeometricRotation, aiVector3D, aiVector3D()) + fbx_simple_property(GeometricScaling, aiVector3D, aiVector3D(1.f, 1.f, 1.f)) + + fbx_simple_property(MinDampRangeX, float, 0.0f) + fbx_simple_property(MinDampRangeY, float, 0.0f) + fbx_simple_property(MinDampRangeZ, float, 0.0f) + fbx_simple_property(MaxDampRangeX, float, 0.0f) + fbx_simple_property(MaxDampRangeY, float, 0.0f) + fbx_simple_property(MaxDampRangeZ, float, 0.0f) + + fbx_simple_property(MinDampStrengthX, float, 0.0f) + fbx_simple_property(MinDampStrengthY, float, 0.0f) + fbx_simple_property(MinDampStrengthZ, float, 0.0f) + fbx_simple_property(MaxDampStrengthX, float, 0.0f) + fbx_simple_property(MaxDampStrengthY, float, 0.0f) + fbx_simple_property(MaxDampStrengthZ, float, 0.0f) + + fbx_simple_property(PreferredAngleX, float, 0.0f) + fbx_simple_property(PreferredAngleY, float, 0.0f) + fbx_simple_property(PreferredAngleZ, float, 0.0f) + + fbx_simple_property(Show, bool, true) + fbx_simple_property(LODBox, bool, false) + fbx_simple_property(Freeze, bool, false) + + const std::string& Shading() const { + return shading; + } + + const std::string& Culling() const { + return culling; + } + + const PropertyTable& Props() const { + ai_assert(props.get()); + return *props.get(); + } + + /** Get material links */ + const std::vector<const Material*>& GetMaterials() const { + return materials; + } + + /** Get geometry links */ + const std::vector<const Geometry*>& GetGeometry() const { + return geometry; + } + + /** Get node attachments */ + const std::vector<const NodeAttribute*>& GetAttributes() const { + return attributes; + } + + /** convenience method to check if the node has a Null node marker */ + bool IsNull() const; + +private: + void ResolveLinks(const Element& element, const Document& doc); + +private: + std::vector<const Material*> materials; + std::vector<const Geometry*> geometry; + std::vector<const NodeAttribute*> attributes; + + std::string shading; + std::string culling; + std::shared_ptr<const PropertyTable> props; +}; + +/** DOM class for generic FBX textures */ +class Texture : public Object { +public: + Texture(uint64_t id, const Element& element, const Document& doc, const std::string& name); + + virtual ~Texture(); + + const std::string& Type() const { + return type; + } + + const std::string& FileName() const { + return fileName; + } + + const std::string& RelativeFilename() const { + return relativeFileName; + } + + const std::string& AlphaSource() const { + return alphaSource; + } + + const aiVector2D& UVTranslation() const { + return uvTrans; + } + + const aiVector2D& UVScaling() const { + return uvScaling; + } + + const ai_real &UVRotation() const { + return uvRotation; + } + + const PropertyTable& Props() const { + ai_assert(props.get()); + return *props.get(); + } + + // return a 4-tuple + const unsigned int* Crop() const { + return crop; + } + + const Video* Media() const { + return media; + } + +private: + aiVector2D uvTrans; + aiVector2D uvScaling; + ai_real uvRotation; + + std::string type; + std::string relativeFileName; + std::string fileName; + std::string alphaSource; + std::shared_ptr<const PropertyTable> props; + + unsigned int crop[4]; + + const Video* media; +}; + +/** DOM class for layered FBX textures */ +class LayeredTexture : public Object { +public: + LayeredTexture(uint64_t id, const Element& element, const Document& doc, const std::string& name); + virtual ~LayeredTexture(); + + // Can only be called after construction of the layered texture object due to construction flag. + void fillTexture(const Document& doc); + + enum BlendMode { + BlendMode_Translucent, + BlendMode_Additive, + BlendMode_Modulate, + BlendMode_Modulate2, + BlendMode_Over, + BlendMode_Normal, + BlendMode_Dissolve, + BlendMode_Darken, + BlendMode_ColorBurn, + BlendMode_LinearBurn, + BlendMode_DarkerColor, + BlendMode_Lighten, + BlendMode_Screen, + BlendMode_ColorDodge, + BlendMode_LinearDodge, + BlendMode_LighterColor, + BlendMode_SoftLight, + BlendMode_HardLight, + BlendMode_VividLight, + BlendMode_LinearLight, + BlendMode_PinLight, + BlendMode_HardMix, + BlendMode_Difference, + BlendMode_Exclusion, + BlendMode_Subtract, + BlendMode_Divide, + BlendMode_Hue, + BlendMode_Saturation, + BlendMode_Color, + BlendMode_Luminosity, + BlendMode_Overlay, + BlendMode_BlendModeCount + }; + + const Texture* getTexture(int index=0) const + { + return textures[index]; + + } + int textureCount() const { + return static_cast<int>(textures.size()); + } + BlendMode GetBlendMode() const + { + return blendMode; + } + float Alpha() + { + return alpha; + } +private: + std::vector<const Texture*> textures; + BlendMode blendMode; + float alpha; +}; + +typedef std::fbx_unordered_map<std::string, const Texture*> TextureMap; +typedef std::fbx_unordered_map<std::string, const LayeredTexture*> LayeredTextureMap; + + +/** DOM class for generic FBX videos */ +class Video : public Object { +public: + Video(uint64_t id, const Element& element, const Document& doc, const std::string& name); + + virtual ~Video(); + + const std::string& Type() const { + return type; + } + + const std::string& FileName() const { + return fileName; + } + + const std::string& RelativeFilename() const { + return relativeFileName; + } + + const PropertyTable& Props() const { + ai_assert(props.get()); + return *props.get(); + } + + const uint8_t* Content() const { + ai_assert(content); + return content; + } + + uint64_t ContentLength() const { + return contentLength; + } + + uint8_t* RelinquishContent() { + uint8_t* ptr = content; + content = 0; + return ptr; + } + +private: + std::string type; + std::string relativeFileName; + std::string fileName; + std::shared_ptr<const PropertyTable> props; + + uint64_t contentLength; + uint8_t* content; +}; + +/** DOM class for generic FBX materials */ +class Material : public Object { +public: + Material(uint64_t id, const Element& element, const Document& doc, const std::string& name); + + virtual ~Material(); + + const std::string& GetShadingModel() const { + return shading; + } + + bool IsMultilayer() const { + return multilayer; + } + + const PropertyTable& Props() const { + ai_assert(props.get()); + return *props.get(); + } + + const TextureMap& Textures() const { + return textures; + } + + const LayeredTextureMap& LayeredTextures() const { + return layeredTextures; + } + +private: + std::string shading; + bool multilayer; + std::shared_ptr<const PropertyTable> props; + + TextureMap textures; + LayeredTextureMap layeredTextures; +}; + +typedef std::vector<int64_t> KeyTimeList; +typedef std::vector<float> KeyValueList; + +/** Represents a FBX animation curve (i.e. a 1-dimensional set of keyframes and values therefore) */ +class AnimationCurve : public Object { +public: + AnimationCurve(uint64_t id, const Element& element, const std::string& name, const Document& doc); + virtual ~AnimationCurve(); + + /** get list of keyframe positions (time). + * Invariant: |GetKeys()| > 0 */ + const KeyTimeList& GetKeys() const { + return keys; + } + + /** get list of keyframe values. + * Invariant: |GetKeys()| == |GetValues()| && |GetKeys()| > 0*/ + const KeyValueList& GetValues() const { + return values; + } + + const std::vector<float>& GetAttributes() const { + return attributes; + } + + const std::vector<unsigned int>& GetFlags() const { + return flags; + } + +private: + KeyTimeList keys; + KeyValueList values; + std::vector<float> attributes; + std::vector<unsigned int> flags; +}; + +// property-name -> animation curve +typedef std::map<std::string, const AnimationCurve*> AnimationCurveMap; + +/** Represents a FBX animation curve (i.e. a mapping from single animation curves to nodes) */ +class AnimationCurveNode : public Object { +public: + /* the optional white list specifies a list of property names for which the caller + wants animations for. If the curve node does not match one of these, std::range_error + will be thrown. */ + AnimationCurveNode(uint64_t id, const Element& element, const std::string& name, const Document& doc, + const char *const *target_prop_whitelist = nullptr, size_t whitelist_size = 0); + + virtual ~AnimationCurveNode(); + + const PropertyTable& Props() const { + ai_assert(props.get()); + return *props.get(); + } + + + const AnimationCurveMap& Curves() const; + + /** Object the curve is assigned to, this can be nullptr if the + * target object has no DOM representation or could not + * be read for other reasons.*/ + const Object* Target() const { + return target; + } + + const Model* TargetAsModel() const { + return dynamic_cast<const Model*>(target); + } + + const NodeAttribute* TargetAsNodeAttribute() const { + return dynamic_cast<const NodeAttribute*>(target); + } + + /** Property of Target() that is being animated*/ + const std::string& TargetProperty() const { + return prop; + } + +private: + const Object* target; + std::shared_ptr<const PropertyTable> props; + mutable AnimationCurveMap curves; + + std::string prop; + const Document& doc; +}; + +typedef std::vector<const AnimationCurveNode*> AnimationCurveNodeList; + +/** Represents a FBX animation layer (i.e. a list of node animations) */ +class AnimationLayer : public Object { +public: + AnimationLayer(uint64_t id, const Element& element, const std::string& name, const Document& doc); + virtual ~AnimationLayer(); + + const PropertyTable& Props() const { + ai_assert(props.get()); + return *props.get(); + } + + /* the optional white list specifies a list of property names for which the caller + wants animations for. Curves not matching this list will not be added to the + animation layer. */ + AnimationCurveNodeList Nodes(const char* const * target_prop_whitelist = nullptr, size_t whitelist_size = 0) const; + +private: + std::shared_ptr<const PropertyTable> props; + const Document& doc; +}; + +typedef std::vector<const AnimationLayer*> AnimationLayerList; + +/** Represents a FBX animation stack (i.e. a list of animation layers) */ +class AnimationStack : public Object { +public: + AnimationStack(uint64_t id, const Element& element, const std::string& name, const Document& doc); + virtual ~AnimationStack(); + + fbx_simple_property(LocalStart, int64_t, 0L) + fbx_simple_property(LocalStop, int64_t, 0L) + fbx_simple_property(ReferenceStart, int64_t, 0L) + fbx_simple_property(ReferenceStop, int64_t, 0L) + + const PropertyTable& Props() const { + ai_assert(props.get()); + return *props.get(); + } + + const AnimationLayerList& Layers() const { + return layers; + } + +private: + std::shared_ptr<const PropertyTable> props; + AnimationLayerList layers; +}; + + +/** DOM class for deformers */ +class Deformer : public Object { +public: + Deformer(uint64_t id, const Element& element, const Document& doc, const std::string& name); + virtual ~Deformer(); + + const PropertyTable& Props() const { + ai_assert(props.get()); + return *props.get(); + } + +private: + std::shared_ptr<const PropertyTable> props; +}; + +typedef std::vector<float> WeightArray; +typedef std::vector<unsigned int> WeightIndexArray; + + +/** DOM class for BlendShapeChannel deformers */ +class BlendShapeChannel : public Deformer { +public: + BlendShapeChannel(uint64_t id, const Element& element, const Document& doc, const std::string& name); + + virtual ~BlendShapeChannel(); + + float DeformPercent() const { + return percent; + } + + const WeightArray& GetFullWeights() const { + return fullWeights; + } + + const std::vector<const ShapeGeometry*>& GetShapeGeometries() const { + return shapeGeometries; + } + +private: + float percent; + WeightArray fullWeights; + std::vector<const ShapeGeometry*> shapeGeometries; +}; + +/** DOM class for BlendShape deformers */ +class BlendShape : public Deformer { +public: + BlendShape(uint64_t id, const Element& element, const Document& doc, const std::string& name); + + virtual ~BlendShape(); + + const std::vector<const BlendShapeChannel*>& BlendShapeChannels() const { + return blendShapeChannels; + } + +private: + std::vector<const BlendShapeChannel*> blendShapeChannels; +}; + +/** DOM class for skin deformer clusters (aka sub-deformers) */ +class Cluster : public Deformer { +public: + Cluster(uint64_t id, const Element& element, const Document& doc, const std::string& name); + + virtual ~Cluster(); + + /** get the list of deformer weights associated with this cluster. + * Use #GetIndices() to get the associated vertices. Both arrays + * have the same size (and may also be empty). */ + const WeightArray& GetWeights() const { + return weights; + } + + /** get indices into the vertex data of the geometry associated + * with this cluster. Use #GetWeights() to get the associated weights. + * Both arrays have the same size (and may also be empty). */ + const WeightIndexArray& GetIndices() const { + return indices; + } + + /** */ + const aiMatrix4x4& Transform() const { + return transform; + } + + const aiMatrix4x4& TransformLink() const { + return transformLink; + } + + const Model* TargetNode() const { + return node; + } + +private: + WeightArray weights; + WeightIndexArray indices; + + aiMatrix4x4 transform; + aiMatrix4x4 transformLink; + + const Model* node; +}; + +/** DOM class for skin deformers */ +class Skin : public Deformer { +public: + Skin(uint64_t id, const Element& element, const Document& doc, const std::string& name); + + virtual ~Skin(); + + float DeformAccuracy() const { + return accuracy; + } + + const std::vector<const Cluster*>& Clusters() const { + return clusters; + } + +private: + float accuracy; + std::vector<const Cluster*> clusters; +}; + +/** Represents a link between two FBX objects. */ +class Connection { +public: + Connection(uint64_t insertionOrder, uint64_t src, uint64_t dest, const std::string& prop, const Document& doc); + + ~Connection(); + + // note: a connection ensures that the source and dest objects exist, but + // not that they have DOM representations, so the return value of one of + // these functions can still be nullptr. + const Object* SourceObject() const; + const Object* DestinationObject() const; + + // these, however, are always guaranteed to be valid + LazyObject& LazySourceObject() const; + LazyObject& LazyDestinationObject() const; + + + /** return the name of the property the connection is attached to. + * this is an empty string for object to object (OO) connections. */ + const std::string& PropertyName() const { + return prop; + } + + uint64_t InsertionOrder() const { + return insertionOrder; + } + + int CompareTo(const Connection* c) const { + ai_assert( nullptr != c ); + + // note: can't subtract because this would overflow uint64_t + if(InsertionOrder() > c->InsertionOrder()) { + return 1; + } + else if(InsertionOrder() < c->InsertionOrder()) { + return -1; + } + return 0; + } + + bool Compare(const Connection* c) const { + ai_assert( nullptr != c ); + + return InsertionOrder() < c->InsertionOrder(); + } + +public: + uint64_t insertionOrder; + const std::string prop; + + uint64_t src, dest; + const Document& doc; +}; + +// XXX again, unique_ptr would be useful. shared_ptr is too +// bloated since the objects have a well-defined single owner +// during their entire lifetime (Document). FBX files have +// up to many thousands of objects (most of which we never use), +// so the memory overhead for them should be kept at a minimum. +typedef std::fbx_unordered_map<uint64_t, LazyObject*> ObjectMap; +typedef std::fbx_unordered_map<std::string, std::shared_ptr<const PropertyTable> > PropertyTemplateMap; + +typedef std::fbx_unordered_multimap<uint64_t, const Connection*> ConnectionMap; + +/** DOM class for global document settings, a single instance per document can + * be accessed via Document.Globals(). */ +class FileGlobalSettings { +public: + FileGlobalSettings(const Document& doc, std::shared_ptr<const PropertyTable> props); + + ~FileGlobalSettings(); + + const PropertyTable& Props() const { + ai_assert(props.get()); + return *props.get(); + } + + const Document& GetDocument() const { + return doc; + } + + fbx_simple_property(UpAxis, int, 1) + fbx_simple_property(UpAxisSign, int, 1) + fbx_simple_property(FrontAxis, int, 2) + fbx_simple_property(FrontAxisSign, int, 1) + fbx_simple_property(CoordAxis, int, 0) + fbx_simple_property(CoordAxisSign, int, 1) + fbx_simple_property(OriginalUpAxis, int, 0) + fbx_simple_property(OriginalUpAxisSign, int, 1) + fbx_simple_property(UnitScaleFactor, float, 1) + fbx_simple_property(OriginalUnitScaleFactor, float, 1) + fbx_simple_property(AmbientColor, aiVector3D, aiVector3D(0,0,0)) + fbx_simple_property(DefaultCamera, std::string, "") + + + enum FrameRate { + FrameRate_DEFAULT = 0, + FrameRate_120 = 1, + FrameRate_100 = 2, + FrameRate_60 = 3, + FrameRate_50 = 4, + FrameRate_48 = 5, + FrameRate_30 = 6, + FrameRate_30_DROP = 7, + FrameRate_NTSC_DROP_FRAME = 8, + FrameRate_NTSC_FULL_FRAME = 9, + FrameRate_PAL = 10, + FrameRate_CINEMA = 11, + FrameRate_1000 = 12, + FrameRate_CINEMA_ND = 13, + FrameRate_CUSTOM = 14, + + FrameRate_MAX// end-of-enum sentinel + }; + + fbx_simple_enum_property(TimeMode, FrameRate, FrameRate_DEFAULT) + fbx_simple_property(TimeSpanStart, uint64_t, 0L) + fbx_simple_property(TimeSpanStop, uint64_t, 0L) + fbx_simple_property(CustomFrameRate, float, -1.0f) + +private: + std::shared_ptr<const PropertyTable> props; + const Document& doc; +}; + +/** DOM root for a FBX file */ +class Document { +public: + Document(const Parser& parser, const ImportSettings& settings); + + ~Document(); + + LazyObject* GetObject(uint64_t id) const; + + bool IsBinary() const { + return parser.IsBinary(); + } + + unsigned int FBXVersion() const { + return fbxVersion; + } + + const std::string& Creator() const { + return creator; + } + + // elements (in this order): Year, Month, Day, Hour, Second, Millisecond + const unsigned int* CreationTimeStamp() const { + return creationTimeStamp; + } + + const FileGlobalSettings& GlobalSettings() const { + ai_assert(globals.get()); + return *globals.get(); + } + + const PropertyTemplateMap& Templates() const { + return templates; + } + + const ObjectMap& Objects() const { + return objects; + } + + const ImportSettings& Settings() const { + return settings; + } + + const ConnectionMap& ConnectionsBySource() const { + return src_connections; + } + + const ConnectionMap& ConnectionsByDestination() const { + return dest_connections; + } + + // note: the implicit rule in all DOM classes is to always resolve + // from destination to source (since the FBX object hierarchy is, + // with very few exceptions, a DAG, this avoids cycles). In all + // cases that may involve back-facing edges in the object graph, + // use LazyObject::IsBeingConstructed() to check. + + std::vector<const Connection*> GetConnectionsBySourceSequenced(uint64_t source) const; + std::vector<const Connection*> GetConnectionsByDestinationSequenced(uint64_t dest) const; + + std::vector<const Connection*> GetConnectionsBySourceSequenced(uint64_t source, const char* classname) const; + std::vector<const Connection*> GetConnectionsByDestinationSequenced(uint64_t dest, const char* classname) const; + + std::vector<const Connection*> GetConnectionsBySourceSequenced(uint64_t source, + const char* const* classnames, size_t count) const; + std::vector<const Connection*> GetConnectionsByDestinationSequenced(uint64_t dest, + const char* const* classnames, + size_t count) const; + + const std::vector<const AnimationStack*>& AnimationStacks() const; + +private: + std::vector<const Connection*> GetConnectionsSequenced(uint64_t id, const ConnectionMap&) const; + std::vector<const Connection*> GetConnectionsSequenced(uint64_t id, bool is_src, + const ConnectionMap&, + const char* const* classnames, + size_t count) const; + void ReadHeader(); + void ReadObjects(); + void ReadPropertyTemplates(); + void ReadConnections(); + void ReadGlobalSettings(); + +private: + const ImportSettings& settings; + + ObjectMap objects; + const Parser& parser; + + PropertyTemplateMap templates; + ConnectionMap src_connections; + ConnectionMap dest_connections; + + unsigned int fbxVersion; + std::string creator; + unsigned int creationTimeStamp[7]; + + std::vector<uint64_t> animationStacks; + mutable std::vector<const AnimationStack*> animationStacksResolved; + + std::unique_ptr<FileGlobalSettings> globals; +}; + +} // Namespace FBX +} // Namespace Assimp + +#endif // INCLUDED_AI_FBX_DOCUMENT_H diff --git a/libs/assimp/code/AssetLib/FBX/FBXDocumentUtil.cpp b/libs/assimp/code/AssetLib/FBX/FBXDocumentUtil.cpp new file mode 100644 index 0000000..68185e5 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXDocumentUtil.cpp @@ -0,0 +1,135 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXDocumentUtil.cpp + * @brief Implementation of the FBX DOM utility functions declared in FBXDocumentUtil.h + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +#include "FBXParser.h" +#include "FBXDocument.h" +#include "FBXUtil.h" +#include "FBXDocumentUtil.h" +#include "FBXProperties.h" + + +namespace Assimp { +namespace FBX { +namespace Util { + +// ------------------------------------------------------------------------------------------------ +// signal DOM construction error, this is always unrecoverable. Throws DeadlyImportError. +void DOMError(const std::string& message, const Token& token) +{ + throw DeadlyImportError("FBX-DOM", Util::GetTokenText(&token), message); +} + +// ------------------------------------------------------------------------------------------------ +void DOMError(const std::string& message, const Element* element /*= nullptr*/) +{ + if(element) { + DOMError(message,element->KeyToken()); + } + throw DeadlyImportError("FBX-DOM ", message); +} + + +// ------------------------------------------------------------------------------------------------ +// print warning, do return +void DOMWarning(const std::string& message, const Token& token) +{ + if(DefaultLogger::get()) { + ASSIMP_LOG_WARN("FBX-DOM", Util::GetTokenText(&token), message); + } +} + +// ------------------------------------------------------------------------------------------------ +void DOMWarning(const std::string& message, const Element* element /*= nullptr*/) +{ + if(element) { + DOMWarning(message,element->KeyToken()); + return; + } + if(DefaultLogger::get()) { + ASSIMP_LOG_WARN("FBX-DOM: ", message); + } +} + + +// ------------------------------------------------------------------------------------------------ +// fetch a property table and the corresponding property template +std::shared_ptr<const PropertyTable> GetPropertyTable(const Document& doc, + const std::string& templateName, + const Element &element, + const Scope& sc, + bool no_warn /*= false*/) +{ + const Element* const Properties70 = sc["Properties70"]; + std::shared_ptr<const PropertyTable> templateProps = std::shared_ptr<const PropertyTable>( + static_cast<const PropertyTable *>(nullptr)); + + if(templateName.length()) { + PropertyTemplateMap::const_iterator it = doc.Templates().find(templateName); + if(it != doc.Templates().end()) { + templateProps = (*it).second; + } + } + + if(!Properties70 || !Properties70->Compound()) { + if(!no_warn) { + DOMWarning("property table (Properties70) not found",&element); + } + if(templateProps) { + return templateProps; + } + else { + return std::make_shared<const PropertyTable>(); + } + } + return std::make_shared<const PropertyTable>(*Properties70,templateProps); +} +} // !Util +} // !FBX +} // !Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/FBX/FBXDocumentUtil.h b/libs/assimp/code/AssetLib/FBX/FBXDocumentUtil.h new file mode 100644 index 0000000..2d76ee0 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXDocumentUtil.h @@ -0,0 +1,120 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, 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 FBXDocumentUtil.h + * @brief FBX internal utilities used by the DOM reading code + */ +#ifndef INCLUDED_AI_FBX_DOCUMENT_UTIL_H +#define INCLUDED_AI_FBX_DOCUMENT_UTIL_H + +#include <assimp/defs.h> +#include <string> +#include <memory> +#include "FBXDocument.h" + +struct Token; +struct Element; + +namespace Assimp { +namespace FBX { +namespace Util { + +/* DOM/Parse error reporting - does not return */ +AI_WONT_RETURN void DOMError(const std::string& message, const Token& token) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN void DOMError(const std::string& message, const Element* element = NULL) AI_WONT_RETURN_SUFFIX; + +// does return +void DOMWarning(const std::string& message, const Token& token); +void DOMWarning(const std::string& message, const Element* element = NULL); + + +// fetch a property table and the corresponding property template +std::shared_ptr<const PropertyTable> GetPropertyTable(const Document& doc, + const std::string& templateName, + const Element &element, + const Scope& sc, + bool no_warn = false); + +// ------------------------------------------------------------------------------------------------ +template <typename T> +inline +const T* ProcessSimpleConnection(const Connection& con, + bool is_object_property_conn, + const char* name, + const Element& element, + const char** propNameOut = nullptr) +{ + if (is_object_property_conn && !con.PropertyName().length()) { + DOMWarning("expected incoming " + std::string(name) + + " link to be an object-object connection, ignoring", + &element + ); + return nullptr; + } + else if (!is_object_property_conn && con.PropertyName().length()) { + DOMWarning("expected incoming " + std::string(name) + + " link to be an object-property connection, ignoring", + &element + ); + return nullptr; + } + + if(is_object_property_conn && propNameOut) { + // note: this is ok, the return value of PropertyValue() is guaranteed to + // remain valid and unchanged as long as the document exists. + *propNameOut = con.PropertyName().c_str(); + } + + const Object* const ob = con.SourceObject(); + if(!ob) { + DOMWarning("failed to read source object for incoming " + std::string(name) + + " link, ignoring", + &element); + return nullptr; + } + + return dynamic_cast<const T*>(ob); +} + +} //!Util +} //!FBX +} //!Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/FBX/FBXExportNode.cpp b/libs/assimp/code/AssetLib/FBX/FBXExportNode.cpp new file mode 100644 index 0000000..21c61b2 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXExportNode.cpp @@ -0,0 +1,561 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER + +#include "FBXExportNode.h" +#include "FBXCommon.h" + +#include <assimp/StreamWriter.h> // StreamWriterLE +#include <assimp/Exceptional.h> // DeadlyExportError +#include <assimp/ai_assert.h> +#include <assimp/StringUtils.h> // ai_snprintf + +#include <string> +#include <ostream> +#include <sstream> // ostringstream +#include <memory> // shared_ptr + +namespace Assimp { +// AddP70<type> helpers... there's no usable pattern here, +// so all are defined as separate functions. +// Even "animatable" properties are often completely different +// from the standard (nonanimated) property definition, +// so they are specified with an 'A' suffix. + +void FBX::Node::AddP70int( + const std::string& cur_name, int32_t value +) { + FBX::Node n("P"); + n.AddProperties(cur_name, "int", "Integer", "", value); + AddChild(n); +} + +void FBX::Node::AddP70bool( + const std::string& cur_name, bool value +) { + FBX::Node n("P"); + n.AddProperties(cur_name, "bool", "", "", int32_t(value)); + AddChild(n); +} + +void FBX::Node::AddP70double( + const std::string &cur_name, double value) { + FBX::Node n("P"); + n.AddProperties(cur_name, "double", "Number", "", value); + AddChild(n); +} + +void FBX::Node::AddP70numberA( + const std::string &cur_name, double value) { + FBX::Node n("P"); + n.AddProperties(cur_name, "Number", "", "A", value); + AddChild(n); +} + +void FBX::Node::AddP70color( + const std::string &cur_name, double r, double g, double b) { + FBX::Node n("P"); + n.AddProperties(cur_name, "ColorRGB", "Color", "", r, g, b); + AddChild(n); +} + +void FBX::Node::AddP70colorA( + const std::string &cur_name, double r, double g, double b) { + FBX::Node n("P"); + n.AddProperties(cur_name, "Color", "", "A", r, g, b); + AddChild(n); +} + +void FBX::Node::AddP70vector( + const std::string &cur_name, double x, double y, double z) { + FBX::Node n("P"); + n.AddProperties(cur_name, "Vector3D", "Vector", "", x, y, z); + AddChild(n); +} + +void FBX::Node::AddP70vectorA( + const std::string &cur_name, double x, double y, double z) { + FBX::Node n("P"); + n.AddProperties(cur_name, "Vector", "", "A", x, y, z); + AddChild(n); +} + +void FBX::Node::AddP70string( + const std::string &cur_name, const std::string &value) { + FBX::Node n("P"); + n.AddProperties(cur_name, "KString", "", "", value); + AddChild(n); +} + +void FBX::Node::AddP70enum( + const std::string &cur_name, int32_t value) { + FBX::Node n("P"); + n.AddProperties(cur_name, "enum", "", "", value); + AddChild(n); +} + +void FBX::Node::AddP70time( + const std::string &cur_name, int64_t value) { + FBX::Node n("P"); + n.AddProperties(cur_name, "KTime", "Time", "", value); + AddChild(n); +} + + +// public member functions for writing nodes to stream + +void FBX::Node::Dump( + const std::shared_ptr<Assimp::IOStream> &outfile, + bool binary, int indent) { + if (binary) { + Assimp::StreamWriterLE outstream(outfile); + DumpBinary(outstream); + } else { + std::ostringstream ss; + DumpAscii(ss, indent); + std::string s = ss.str(); + outfile->Write(s.c_str(), s.size(), 1); + } +} + +void FBX::Node::Dump( + Assimp::StreamWriterLE &outstream, + bool binary, int indent +) { + if (binary) { + DumpBinary(outstream); + } else { + std::ostringstream ss; + DumpAscii(ss, indent); + outstream.PutString(ss.str()); + } +} + + +// public member functions for low-level writing + +void FBX::Node::Begin( + Assimp::StreamWriterLE &s, + bool binary, int indent +) { + if (binary) { + BeginBinary(s); + } else { + // assume we're at the correct place to start already + (void)indent; + std::ostringstream ss; + BeginAscii(ss, indent); + s.PutString(ss.str()); + } +} + +void FBX::Node::DumpProperties( + Assimp::StreamWriterLE& s, + bool binary, int indent +) { + if (binary) { + DumpPropertiesBinary(s); + } else { + std::ostringstream ss; + DumpPropertiesAscii(ss, indent); + s.PutString(ss.str()); + } +} + +void FBX::Node::EndProperties( + Assimp::StreamWriterLE &s, + bool binary, int indent +) { + EndProperties(s, binary, indent, properties.size()); +} + +void FBX::Node::EndProperties( + Assimp::StreamWriterLE &s, + bool binary, int indent, + size_t num_properties +) { + if (binary) { + EndPropertiesBinary(s, num_properties); + } else { + // nothing to do + (void)indent; + } +} + +void FBX::Node::BeginChildren( + Assimp::StreamWriterLE &s, + bool binary, int indent +) { + if (binary) { + // nothing to do + } else { + std::ostringstream ss; + BeginChildrenAscii(ss, indent); + s.PutString(ss.str()); + } +} + +void FBX::Node::DumpChildren( + Assimp::StreamWriterLE& s, + bool binary, int indent +) { + if (binary) { + DumpChildrenBinary(s); + } else { + std::ostringstream ss; + DumpChildrenAscii(ss, indent); + if (ss.tellp() > 0) + s.PutString(ss.str()); + } +} + +void FBX::Node::End( + Assimp::StreamWriterLE &s, + bool binary, int indent, + bool has_children +) { + if (binary) { + EndBinary(s, has_children); + } else { + std::ostringstream ss; + EndAscii(ss, indent, has_children); + if (ss.tellp() > 0) + s.PutString(ss.str()); + } +} + + +// public member functions for writing to binary fbx + +void FBX::Node::DumpBinary(Assimp::StreamWriterLE &s) +{ + // write header section (with placeholders for some things) + BeginBinary(s); + + // write properties + DumpPropertiesBinary(s); + + // go back and fill in property related placeholders + EndPropertiesBinary(s, properties.size()); + + // write children + DumpChildrenBinary(s); + + // finish, filling in end offset placeholder + EndBinary(s, force_has_children || !children.empty()); +} + + +// public member functions for writing to ascii fbx + +void FBX::Node::DumpAscii(std::ostream &s, int indent) +{ + // write name + BeginAscii(s, indent); + + // write properties + DumpPropertiesAscii(s, indent); + + if (force_has_children || !children.empty()) { + // begin children (with a '{') + BeginChildrenAscii(s, indent + 1); + // write children + DumpChildrenAscii(s, indent + 1); + } + + // finish (also closing the children bracket '}') + EndAscii(s, indent, force_has_children || !children.empty()); +} + + +// private member functions for low-level writing to fbx + +void FBX::Node::BeginBinary(Assimp::StreamWriterLE &s) +{ + // remember start pos so we can come back and write the end pos + this->start_pos = s.Tell(); + + // placeholders for end pos and property section info + s.PutU8(0); // end pos + s.PutU8(0); // number of properties + s.PutU8(0); // total property section length + + // node name + s.PutU1(uint8_t(name.size())); // length of node name + s.PutString(name); // node name as raw bytes + + // property data comes after here + this->property_start = s.Tell(); +} + +void FBX::Node::DumpPropertiesBinary(Assimp::StreamWriterLE& s) +{ + for (auto &p : properties) { + p.DumpBinary(s); + } +} + +void FBX::Node::EndPropertiesBinary( + Assimp::StreamWriterLE &s, + size_t num_properties +) { + if (num_properties == 0) { return; } + size_t pos = s.Tell(); + ai_assert(pos > property_start); + size_t property_section_size = pos - property_start; + s.Seek(start_pos + 8); // 8 bytes of uint64_t of end_pos + s.PutU8(num_properties); + s.PutU8(property_section_size); + s.Seek(pos); +} + +void FBX::Node::DumpChildrenBinary(Assimp::StreamWriterLE& s) +{ + for (FBX::Node& child : children) { + child.DumpBinary(s); + } +} + +void FBX::Node::EndBinary( + Assimp::StreamWriterLE &s, + bool has_children +) { + // if there were children, add a null record + if (has_children) { s.PutString(Assimp::FBX::NULL_RECORD); } + + // now go back and write initial pos + this->end_pos = s.Tell(); + s.Seek(start_pos); + s.PutU8(end_pos); + s.Seek(end_pos); +} + + +void FBX::Node::BeginAscii(std::ostream& s, int indent) +{ + s << '\n'; + for (int i = 0; i < indent; ++i) { s << '\t'; } + s << name << ": "; +} + +void FBX::Node::DumpPropertiesAscii(std::ostream &s, int indent) +{ + for (size_t i = 0; i < properties.size(); ++i) { + if (i > 0) { s << ", "; } + properties[i].DumpAscii(s, indent); + } +} + +void FBX::Node::BeginChildrenAscii(std::ostream& s, int indent) +{ + // only call this if there are actually children + s << " {"; + (void)indent; +} + +void FBX::Node::DumpChildrenAscii(std::ostream& s, int indent) +{ + // children will need a lot of padding and corralling + if (children.size() || force_has_children) { + for (size_t i = 0; i < children.size(); ++i) { + // no compression in ascii files, so skip this node if it exists + if (children[i].name == "EncryptionType") { continue; } + // the child can dump itself + children[i].DumpAscii(s, indent); + } + } +} + +void FBX::Node::EndAscii(std::ostream& s, int indent, bool has_children) +{ + if (!has_children) { return; } // nothing to do + s << '\n'; + for (int i = 0; i < indent; ++i) { s << '\t'; } + s << "}"; +} + +// private helpers for static member functions + +// ascii property node from vector of doubles +void FBX::Node::WritePropertyNodeAscii( + const std::string& name, + const std::vector<double>& v, + Assimp::StreamWriterLE& s, + int indent +){ + char buffer[32]; + FBX::Node node(name); + node.Begin(s, false, indent); + std::string vsize = ai_to_string(v.size()); + // *<size> { + s.PutChar('*'); s.PutString(vsize); s.PutString(" {\n"); + // indent + 1 + for (int i = 0; i < indent + 1; ++i) { s.PutChar('\t'); } + // a: value,value,value,... + s.PutString("a: "); + int count = 0; + for (size_t i = 0; i < v.size(); ++i) { + if (i > 0) { s.PutChar(','); } + int len = ai_snprintf(buffer, sizeof(buffer), "%f", v[i]); + count += len; + if (count > 2048) { s.PutChar('\n'); count = 0; } + if (len < 0 || len > 31) { + // this should never happen + throw DeadlyExportError("failed to convert double to string"); + } + for (int j = 0; j < len; ++j) { s.PutChar(buffer[j]); } + } + // } + s.PutChar('\n'); + for (int i = 0; i < indent; ++i) { s.PutChar('\t'); } + s.PutChar('}'); s.PutChar(' '); + node.End(s, false, indent, false); +} + +// ascii property node from vector of int32_t +void FBX::Node::WritePropertyNodeAscii( + const std::string& name, + const std::vector<int32_t>& v, + Assimp::StreamWriterLE& s, + int indent +){ + char buffer[32]; + FBX::Node node(name); + node.Begin(s, false, indent); + std::string vsize = ai_to_string(v.size()); + // *<size> { + s.PutChar('*'); s.PutString(vsize); s.PutString(" {\n"); + // indent + 1 + for (int i = 0; i < indent + 1; ++i) { s.PutChar('\t'); } + // a: value,value,value,... + s.PutString("a: "); + int count = 0; + for (size_t i = 0; i < v.size(); ++i) { + if (i > 0) { s.PutChar(','); } + int len = ai_snprintf(buffer, sizeof(buffer), "%d", v[i]); + count += len; + if (count > 2048) { s.PutChar('\n'); count = 0; } + if (len < 0 || len > 31) { + // this should never happen + throw DeadlyExportError("failed to convert double to string"); + } + for (int j = 0; j < len; ++j) { s.PutChar(buffer[j]); } + } + // } + s.PutChar('\n'); + for (int i = 0; i < indent; ++i) { s.PutChar('\t'); } + s.PutChar('}'); s.PutChar(' '); + node.End(s, false, indent, false); +} + +// binary property node from vector of doubles +// TODO: optional zip compression! +void FBX::Node::WritePropertyNodeBinary( + const std::string& name, + const std::vector<double>& v, + Assimp::StreamWriterLE& s +){ + FBX::Node node(name); + node.BeginBinary(s); + s.PutU1('d'); + s.PutU4(uint32_t(v.size())); // number of elements + s.PutU4(0); // no encoding (1 would be zip-compressed) + s.PutU4(uint32_t(v.size()) * 8); // data size + for (auto it = v.begin(); it != v.end(); ++it) { s.PutF8(*it); } + node.EndPropertiesBinary(s, 1); + node.EndBinary(s, false); +} + +// binary property node from vector of int32_t +// TODO: optional zip compression! +void FBX::Node::WritePropertyNodeBinary( + const std::string& name, + const std::vector<int32_t>& v, + Assimp::StreamWriterLE& s +){ + FBX::Node node(name); + node.BeginBinary(s); + s.PutU1('i'); + s.PutU4(uint32_t(v.size())); // number of elements + s.PutU4(0); // no encoding (1 would be zip-compressed) + s.PutU4(uint32_t(v.size()) * 4); // data size + for (auto it = v.begin(); it != v.end(); ++it) { s.PutI4(*it); } + node.EndPropertiesBinary(s, 1); + node.EndBinary(s, false); +} + +// public static member functions + +// convenience function to create and write a property node, +// holding a single property which is an array of values. +// does not copy the data, so is efficient for large arrays. +void FBX::Node::WritePropertyNode( + const std::string& name, + const std::vector<double>& v, + Assimp::StreamWriterLE& s, + bool binary, int indent +){ + if (binary) { + FBX::Node::WritePropertyNodeBinary(name, v, s); + } else { + FBX::Node::WritePropertyNodeAscii(name, v, s, indent); + } +} + +// convenience function to create and write a property node, +// holding a single property which is an array of values. +// does not copy the data, so is efficient for large arrays. +void FBX::Node::WritePropertyNode( + const std::string& name, + const std::vector<int32_t>& v, + Assimp::StreamWriterLE& s, + bool binary, int indent +){ + if (binary) { + FBX::Node::WritePropertyNodeBinary(name, v, s); + } else { + FBX::Node::WritePropertyNodeAscii(name, v, s, indent); + } +} +} +#endif // ASSIMP_BUILD_NO_FBX_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/FBX/FBXExportNode.h b/libs/assimp/code/AssetLib/FBX/FBXExportNode.h new file mode 100644 index 0000000..c6c4549 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXExportNode.h @@ -0,0 +1,270 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXExportNode.h +* Declares the FBX::Node helper class for fbx export. +*/ +#ifndef AI_FBXEXPORTNODE_H_INC +#define AI_FBXEXPORTNODE_H_INC + +#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER + +#include "FBXExportProperty.h" + +#include <assimp/StreamWriter.h> // StreamWriterLE + +#include <string> +#include <vector> + +namespace Assimp { +namespace FBX { + class Node; +} + +class FBX::Node { +public: + // TODO: accessors + std::string name; // node name + std::vector<FBX::FBXExportProperty> properties; // node properties + std::vector<FBX::Node> children; // child nodes + + // some nodes always pretend they have children... + bool force_has_children = false; + +public: // constructors + /// The default class constructor. + Node() = default; + + /// The class constructor with the name. + Node(const std::string& n) + : name(n) + , properties() + , children() + , force_has_children( false ) { + // empty + } + + // convenience template to construct with properties directly + template <typename... More> + Node(const std::string& n, const More... more) + : name(n) + , properties() + , children() + , force_has_children(false) { + AddProperties(more...); + } + +public: // functions to add properties or children + // add a single property to the node + template <typename T> + void AddProperty(T value) { + properties.emplace_back(value); + } + + // convenience function to add multiple properties at once + template <typename T, typename... More> + void AddProperties(T value, More... more) { + properties.emplace_back(value); + AddProperties(more...); + } + void AddProperties() {} + + // add a child node directly + void AddChild(const Node& node) { children.push_back(node); } + + // convenience function to add a child node with a single property + template <typename... More> + void AddChild( + const std::string& name, + More... more + ) { + FBX::Node c(name); + c.AddProperties(more...); + children.push_back(c); + } + +public: // support specifically for dealing with Properties70 nodes + + // it really is simpler to make these all separate functions. + // the versions with 'A' suffixes are for animatable properties. + // those often follow a completely different format internally in FBX. + void AddP70int(const std::string& name, int32_t value); + void AddP70bool(const std::string& name, bool value); + void AddP70double(const std::string& name, double value); + void AddP70numberA(const std::string& name, double value); + void AddP70color(const std::string& name, double r, double g, double b); + void AddP70colorA(const std::string& name, double r, double g, double b); + void AddP70vector(const std::string& name, double x, double y, double z); + void AddP70vectorA(const std::string& name, double x, double y, double z); + void AddP70string(const std::string& name, const std::string& value); + void AddP70enum(const std::string& name, int32_t value); + void AddP70time(const std::string& name, int64_t value); + + // template for custom P70 nodes. + // anything that doesn't fit in the above can be created manually. + template <typename... More> + void AddP70( + const std::string& name, + const std::string& type, + const std::string& type2, + const std::string& flags, + More... more + ) { + Node n("P"); + n.AddProperties(name, type, type2, flags, more...); + AddChild(n); + } + +public: // member functions for writing data to a file or stream + + // write the full node to the given file or stream + void Dump( + const std::shared_ptr<Assimp::IOStream> &outfile, + bool binary, int indent); + void Dump(Assimp::StreamWriterLE &s, bool binary, int indent); + + // these other functions are for writing data piece by piece. + // they must be used carefully. + // for usage examples see FBXExporter.cpp. + void Begin(Assimp::StreamWriterLE &s, bool binary, int indent); + void DumpProperties(Assimp::StreamWriterLE& s, bool binary, int indent); + void EndProperties(Assimp::StreamWriterLE &s, bool binary, int indent); + void EndProperties( + Assimp::StreamWriterLE &s, bool binary, int indent, + size_t num_properties + ); + void BeginChildren(Assimp::StreamWriterLE &s, bool binary, int indent); + void DumpChildren(Assimp::StreamWriterLE& s, bool binary, int indent); + void End( + Assimp::StreamWriterLE &s, bool binary, int indent, + bool has_children + ); + +private: // internal functions used for writing + + void DumpBinary(Assimp::StreamWriterLE &s); + void DumpAscii(Assimp::StreamWriterLE &s, int indent); + void DumpAscii(std::ostream &s, int indent); + + void BeginBinary(Assimp::StreamWriterLE &s); + void DumpPropertiesBinary(Assimp::StreamWriterLE& s); + void EndPropertiesBinary(Assimp::StreamWriterLE &s); + void EndPropertiesBinary(Assimp::StreamWriterLE &s, size_t num_properties); + void DumpChildrenBinary(Assimp::StreamWriterLE& s); + void EndBinary(Assimp::StreamWriterLE &s, bool has_children); + + void BeginAscii(std::ostream &s, int indent); + void DumpPropertiesAscii(std::ostream &s, int indent); + void BeginChildrenAscii(std::ostream &s, int indent); + void DumpChildrenAscii(std::ostream &s, int indent); + void EndAscii(std::ostream &s, int indent, bool has_children); + +private: // data used for binary dumps + size_t start_pos; // starting position in stream + size_t end_pos; // ending position in stream + size_t property_start; // starting position of property section + +public: // static member functions + + // convenience function to create a node with a single property, + // and write it to the stream. + template <typename T> + static void WritePropertyNode( + const std::string& name, + const T value, + Assimp::StreamWriterLE& s, + bool binary, int indent + ) { + FBX::FBXExportProperty p(value); + FBX::Node node(name, p); + node.Dump(s, binary, indent); + } + + // convenience function to create and write a property node, + // holding a single property which is an array of values. + // does not copy the data, so is efficient for large arrays. + static void WritePropertyNode( + const std::string& name, + const std::vector<double>& v, + Assimp::StreamWriterLE& s, + bool binary, int indent + ); + + // convenience function to create and write a property node, + // holding a single property which is an array of values. + // does not copy the data, so is efficient for large arrays. + static void WritePropertyNode( + const std::string& name, + const std::vector<int32_t>& v, + Assimp::StreamWriterLE& s, + bool binary, int indent + ); + +private: // static helper functions + static void WritePropertyNodeAscii( + const std::string& name, + const std::vector<double>& v, + Assimp::StreamWriterLE& s, + int indent + ); + static void WritePropertyNodeAscii( + const std::string& name, + const std::vector<int32_t>& v, + Assimp::StreamWriterLE& s, + int indent + ); + static void WritePropertyNodeBinary( + const std::string& name, + const std::vector<double>& v, + Assimp::StreamWriterLE& s + ); + static void WritePropertyNodeBinary( + const std::string& name, + const std::vector<int32_t>& v, + Assimp::StreamWriterLE& s + ); + +}; +} + +#endif // ASSIMP_BUILD_NO_FBX_EXPORTER + +#endif // AI_FBXEXPORTNODE_H_INC diff --git a/libs/assimp/code/AssetLib/FBX/FBXExportProperty.cpp b/libs/assimp/code/AssetLib/FBX/FBXExportProperty.cpp new file mode 100644 index 0000000..3216d7d --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXExportProperty.cpp @@ -0,0 +1,385 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER + +#include "FBXExportProperty.h" + +#include <assimp/StreamWriter.h> // StreamWriterLE +#include <assimp/Exceptional.h> // DeadlyExportError + +#include <string> +#include <vector> +#include <ostream> +#include <locale> +#include <sstream> // ostringstream + +namespace Assimp { +namespace FBX { + +// constructors for single element properties + +FBXExportProperty::FBXExportProperty(bool v) +: type('C') +, data(1, uint8_t(v)) {} + +FBXExportProperty::FBXExportProperty(int16_t v) +: type('Y') +, data(2) { + uint8_t* d = data.data(); + (reinterpret_cast<int16_t*>(d))[0] = v; +} + +FBXExportProperty::FBXExportProperty(int32_t v) +: type('I') +, data(4) { + uint8_t* d = data.data(); + (reinterpret_cast<int32_t*>(d))[0] = v; +} + +FBXExportProperty::FBXExportProperty(float v) +: type('F') +, data(4) { + uint8_t* d = data.data(); + (reinterpret_cast<float*>(d))[0] = v; +} + +FBXExportProperty::FBXExportProperty(double v) +: type('D') +, data(8) { + uint8_t* d = data.data(); + (reinterpret_cast<double*>(d))[0] = v; +} + +FBXExportProperty::FBXExportProperty(int64_t v) +: type('L') +, data(8) { + uint8_t* d = data.data(); + (reinterpret_cast<int64_t*>(d))[0] = v; +} + +// constructors for array-type properties + +FBXExportProperty::FBXExportProperty(const char* c, bool raw) +: FBXExportProperty(std::string(c), raw) { + // empty +} + +// strings can either be saved as "raw" (R) data, or "string" (S) data +FBXExportProperty::FBXExportProperty(const std::string& s, bool raw) +: type(raw ? 'R' : 'S') +, data(s.size()) { + for (size_t i = 0; i < s.size(); ++i) { + data[i] = uint8_t(s[i]); + } +} + +FBXExportProperty::FBXExportProperty(const std::vector<uint8_t>& r) +: type('R') +, data(r) { + // empty +} + +FBXExportProperty::FBXExportProperty(const std::vector<int32_t>& va) +: type('i') +, data(4 * va.size() ) { + int32_t* d = reinterpret_cast<int32_t*>(data.data()); + for (size_t i = 0; i < va.size(); ++i) { + d[i] = va[i]; + } +} + +FBXExportProperty::FBXExportProperty(const std::vector<int64_t>& va) +: type('l') +, data(8 * va.size()) { + int64_t* d = reinterpret_cast<int64_t*>(data.data()); + for (size_t i = 0; i < va.size(); ++i) { + d[i] = va[i]; + } +} + +FBXExportProperty::FBXExportProperty(const std::vector<float>& va) +: type('f') +, data(4 * va.size()) { + float* d = reinterpret_cast<float*>(data.data()); + for (size_t i = 0; i < va.size(); ++i) { + d[i] = va[i]; + } +} + +FBXExportProperty::FBXExportProperty(const std::vector<double>& va) +: type('d') +, data(8 * va.size()) { + double* d = reinterpret_cast<double*>(data.data()); + for (size_t i = 0; i < va.size(); ++i) { + d[i] = va[i]; + } +} + +FBXExportProperty::FBXExportProperty(const aiMatrix4x4& vm) +: type('d') +, data(8 * 16) { + double* d = reinterpret_cast<double*>(data.data()); + for (unsigned int c = 0; c < 4; ++c) { + for (unsigned int r = 0; r < 4; ++r) { + d[4 * c + r] = vm[r][c]; + } + } +} + +// public member functions + +size_t FBXExportProperty::size() { + switch (type) { + case 'C': + case 'Y': + case 'I': + case 'F': + case 'D': + case 'L': + return data.size() + 1; + case 'S': + case 'R': + return data.size() + 5; + case 'i': + case 'd': + return data.size() + 13; + default: + throw DeadlyExportError("Requested size on property of unknown type"); + } +} + +void FBXExportProperty::DumpBinary(Assimp::StreamWriterLE& s) { + s.PutU1(type); + uint8_t* d = data.data(); + size_t N; + switch (type) { + case 'C': s.PutU1(*(reinterpret_cast<uint8_t*>(d))); return; + case 'Y': s.PutI2(*(reinterpret_cast<int16_t*>(d))); return; + case 'I': s.PutI4(*(reinterpret_cast<int32_t*>(d))); return; + case 'F': s.PutF4(*(reinterpret_cast<float*>(d))); return; + case 'D': s.PutF8(*(reinterpret_cast<double*>(d))); return; + case 'L': s.PutI8(*(reinterpret_cast<int64_t*>(d))); return; + case 'S': + case 'R': + s.PutU4(uint32_t(data.size())); + for (size_t i = 0; i < data.size(); ++i) { s.PutU1(data[i]); } + return; + case 'i': + N = data.size() / 4; + s.PutU4(uint32_t(N)); // number of elements + s.PutU4(0); // no encoding (1 would be zip-compressed) + // TODO: compress if large? + s.PutU4(uint32_t(data.size())); // data size + for (size_t i = 0; i < N; ++i) { + s.PutI4((reinterpret_cast<int32_t*>(d))[i]); + } + return; + case 'l': + N = data.size() / 8; + s.PutU4(uint32_t(N)); // number of elements + s.PutU4(0); // no encoding (1 would be zip-compressed) + // TODO: compress if large? + s.PutU4(uint32_t(data.size())); // data size + for (size_t i = 0; i < N; ++i) { + s.PutI8((reinterpret_cast<int64_t*>(d))[i]); + } + return; + case 'f': + N = data.size() / 4; + s.PutU4(uint32_t(N)); // number of elements + s.PutU4(0); // no encoding (1 would be zip-compressed) + // TODO: compress if large? + s.PutU4(uint32_t(data.size())); // data size + for (size_t i = 0; i < N; ++i) { + s.PutF4((reinterpret_cast<float*>(d))[i]); + } + return; + case 'd': + N = data.size() / 8; + s.PutU4(uint32_t(N)); // number of elements + s.PutU4(0); // no encoding (1 would be zip-compressed) + // TODO: compress if large? + s.PutU4(uint32_t(data.size())); // data size + for (size_t i = 0; i < N; ++i) { + s.PutF8((reinterpret_cast<double*>(d))[i]); + } + return; + default: + std::ostringstream err; + err << "Tried to dump property with invalid type '"; + err << type << "'!"; + throw DeadlyExportError(err.str()); + } +} + +void FBXExportProperty::DumpAscii(Assimp::StreamWriterLE& outstream, int indent) { + std::ostringstream ss; + ss.imbue(std::locale::classic()); + ss.precision(15); // this seems to match official FBX SDK exports + DumpAscii(ss, indent); + outstream.PutString(ss.str()); +} + +void FBXExportProperty::DumpAscii(std::ostream& s, int indent) { + // no writing type... or anything. just shove it into the stream. + uint8_t* d = data.data(); + size_t N; + size_t swap = data.size(); + size_t count = 0; + switch (type) { + case 'C': + if (*(reinterpret_cast<uint8_t*>(d))) { s << 'T'; } + else { s << 'F'; } + return; + case 'Y': s << *(reinterpret_cast<int16_t*>(d)); return; + case 'I': s << *(reinterpret_cast<int32_t*>(d)); return; + case 'F': s << *(reinterpret_cast<float*>(d)); return; + case 'D': s << *(reinterpret_cast<double*>(d)); return; + case 'L': s << *(reinterpret_cast<int64_t*>(d)); return; + case 'S': + // first search to see if it has "\x00\x01" in it - + // which separates fields which are reversed in the ascii version. + // yeah. + // FBX, yeah. + for (size_t i = 0; i < data.size(); ++i) { + if (data[i] == '\0') { + swap = i; + break; + } + } + case 'R': + s << '"'; + // we might as well check this now, + // probably it will never happen + for (size_t i = 0; i < data.size(); ++i) { + char c = data[i]; + if (c == '"') { + throw runtime_error("can't handle quotes in property string"); + } + } + // first write the SWAPPED member (if any) + for (size_t i = swap + 2; i < data.size(); ++i) { + char c = data[i]; + s << c; + } + // then a separator + if (swap != data.size()) { + s << "::"; + } + // then the initial member + for (size_t i = 0; i < swap; ++i) { + char c = data[i]; + s << c; + } + s << '"'; + return; + case 'i': + N = data.size() / 4; // number of elements + s << '*' << N << " {\n"; + for (int i = 0; i < indent + 1; ++i) { s << '\t'; } + s << "a: "; + for (size_t i = 0; i < N; ++i) { + if (i > 0) { s << ','; } + if (count++ > 120) { s << '\n'; count = 0; } + s << (reinterpret_cast<int32_t*>(d))[i]; + } + s << '\n'; + for (int i = 0; i < indent; ++i) { s << '\t'; } + s << "} "; + return; + case 'l': + N = data.size() / 8; + s << '*' << N << " {\n"; + for (int i = 0; i < indent + 1; ++i) { s << '\t'; } + s << "a: "; + for (size_t i = 0; i < N; ++i) { + if (i > 0) { s << ','; } + if (count++ > 120) { s << '\n'; count = 0; } + s << (reinterpret_cast<int64_t*>(d))[i]; + } + s << '\n'; + for (int i = 0; i < indent; ++i) { s << '\t'; } + s << "} "; + return; + case 'f': + N = data.size() / 4; + s << '*' << N << " {\n"; + for (int i = 0; i < indent + 1; ++i) { s << '\t'; } + s << "a: "; + for (size_t i = 0; i < N; ++i) { + if (i > 0) { s << ','; } + if (count++ > 120) { s << '\n'; count = 0; } + s << (reinterpret_cast<float*>(d))[i]; + } + s << '\n'; + for (int i = 0; i < indent; ++i) { s << '\t'; } + s << "} "; + return; + case 'd': + N = data.size() / 8; + s << '*' << N << " {\n"; + for (int i = 0; i < indent + 1; ++i) { s << '\t'; } + s << "a: "; + // set precision to something that can handle doubles + s.precision(15); + for (size_t i = 0; i < N; ++i) { + if (i > 0) { s << ','; } + if (count++ > 120) { s << '\n'; count = 0; } + s << (reinterpret_cast<double*>(d))[i]; + } + s << '\n'; + for (int i = 0; i < indent; ++i) { s << '\t'; } + s << "} "; + return; + default: + std::ostringstream err; + err << "Tried to dump property with invalid type '"; + err << type << "'!"; + throw runtime_error(err.str()); + } +} + +} // Namespace FBX +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_FBX_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/FBX/FBXExportProperty.h b/libs/assimp/code/AssetLib/FBX/FBXExportProperty.h new file mode 100644 index 0000000..26d0cf2 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXExportProperty.h @@ -0,0 +1,129 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXExportProperty.h +* Declares the FBX::Property helper class for fbx export. +*/ +#ifndef AI_FBXEXPORTPROPERTY_H_INC +#define AI_FBXEXPORTPROPERTY_H_INC + +#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER + +#include <assimp/types.h> // aiMatrix4x4 +#include <assimp/StreamWriter.h> // StreamWriterLE + +#include <string> +#include <vector> +#include <ostream> +#include <type_traits> // is_void + +namespace Assimp { +namespace FBX { + +/** @brief FBX::Property + * + * Holds a value of any of FBX's recognized types, + * each represented by a particular one-character code. + * C : 1-byte uint8, usually 0x00 or 0x01 to represent boolean false and true + * Y : 2-byte int16 + * I : 4-byte int32 + * F : 4-byte float + * D : 8-byte double + * L : 8-byte int64 + * i : array of int32 + * f : array of float + * d : array of double + * l : array of int64 + * b : array of 1-byte booleans (0x00 or 0x01) + * S : string (array of 1-byte char) + * R : raw data (array of bytes) + */ +class FBXExportProperty { +public: + // constructors for basic types. + // all explicit to avoid accidental typecasting + explicit FBXExportProperty(bool v); + // TODO: determine if there is actually a byte type, + // or if this always means <bool>. 'C' seems to imply <char>, + // so possibly the above was intended to represent both. + explicit FBXExportProperty(int16_t v); + explicit FBXExportProperty(int32_t v); + explicit FBXExportProperty(float v); + explicit FBXExportProperty(double v); + explicit FBXExportProperty(int64_t v); + // strings can either be stored as 'R' (raw) or 'S' (string) type + explicit FBXExportProperty(const char* c, bool raw = false); + explicit FBXExportProperty(const std::string& s, bool raw = false); + explicit FBXExportProperty(const std::vector<uint8_t>& r); + explicit FBXExportProperty(const std::vector<int32_t>& va); + explicit FBXExportProperty(const std::vector<int64_t>& va); + explicit FBXExportProperty(const std::vector<double>& va); + explicit FBXExportProperty(const std::vector<float>& va); + explicit FBXExportProperty(const aiMatrix4x4& vm); + + // this will catch any type not defined above, + // so that we don't accidentally convert something we don't want. + // for example (const char*) --> (bool)... seriously wtf C++ + template <class T> + explicit FBXExportProperty(T v) : type('X') { + static_assert(std::is_void<T>::value, "TRIED TO CREATE FBX PROPERTY WITH UNSUPPORTED TYPE, CHECK YOUR PROPERTY INSTANTIATION"); + } // note: no line wrap so it appears verbatim on the compiler error + + // the size of this property node in a binary file, in bytes + size_t size(); + + // write this property node as binary data to the given stream + void DumpBinary(Assimp::StreamWriterLE& s); + void DumpAscii(Assimp::StreamWriterLE& s, int indent = 0); + void DumpAscii(std::ostream& s, int indent = 0); + // note: make sure the ostream is in classic "C" locale + +private: + char type; + std::vector<uint8_t> data; +}; + +} // Namespace FBX +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_FBX_EXPORTER + +#endif // AI_FBXEXPORTPROPERTY_H_INC diff --git a/libs/assimp/code/AssetLib/FBX/FBXExporter.cpp b/libs/assimp/code/AssetLib/FBX/FBXExporter.cpp new file mode 100644 index 0000000..6956728 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXExporter.cpp @@ -0,0 +1,2799 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER + +#include "FBXExporter.h" +#include "FBXExportNode.h" +#include "FBXExportProperty.h" +#include "FBXCommon.h" +#include "FBXUtil.h" + +#include <assimp/version.h> // aiGetVersion +#include <assimp/IOSystem.hpp> +#include <assimp/Exporter.hpp> +#include <assimp/DefaultLogger.hpp> +#include <assimp/StreamWriter.h> // StreamWriterLE +#include <assimp/Exceptional.h> // DeadlyExportError +#include <assimp/material.h> // aiTextureType +#include <assimp/scene.h> +#include <assimp/mesh.h> + +// Header files, standard library. +#include <memory> // shared_ptr +#include <string> +#include <sstream> // stringstream +#include <ctime> // localtime, tm_* +#include <map> +#include <set> +#include <vector> +#include <array> +#include <unordered_set> +#include <numeric> + +// RESOURCES: +// https://code.blender.org/2013/08/fbx-binary-file-format-specification/ +// https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure + +const ai_real DEG = ai_real( 57.29577951308232087679815481 ); // degrees per radian + +using namespace Assimp; +using namespace Assimp::FBX; + +// some constants that we'll use for writing metadata +namespace Assimp { +namespace FBX { + const std::string EXPORT_VERSION_STR = "7.5.0"; + const uint32_t EXPORT_VERSION_INT = 7500; // 7.5 == 2016+ + // FBX files have some hashed values that depend on the creation time field, + // but for now we don't actually know how to generate these. + // what we can do is set them to a known-working version. + // this is the data that Blender uses in their FBX export process. + const std::string GENERIC_CTIME = "1970-01-01 10:00:00:000"; + const std::string GENERIC_FILEID = + "\x28\xb3\x2a\xeb\xb6\x24\xcc\xc2\xbf\xc8\xb0\x2a\xa9\x2b\xfc\xf1"; + const std::string GENERIC_FOOTID = + "\xfa\xbc\xab\x09\xd0\xc8\xd4\x66\xb1\x76\xfb\x83\x1c\xf7\x26\x7e"; + const std::string FOOT_MAGIC = + "\xf8\x5a\x8c\x6a\xde\xf5\xd9\x7e\xec\xe9\x0c\xe3\x75\x8f\x29\x0b"; + const std::string COMMENT_UNDERLINE = + ";------------------------------------------------------------------"; +} + + // --------------------------------------------------------------------- + // Worker function for exporting a scene to binary FBX. + // Prototyped and registered in Exporter.cpp + void ExportSceneFBX ( + const char* pFile, + IOSystem* pIOSystem, + const aiScene* pScene, + const ExportProperties* pProperties + ){ + // initialize the exporter + FBXExporter exporter(pScene, pProperties); + + // perform binary export + exporter.ExportBinary(pFile, pIOSystem); + } + + // --------------------------------------------------------------------- + // Worker function for exporting a scene to ASCII FBX. + // Prototyped and registered in Exporter.cpp + void ExportSceneFBXA ( + const char* pFile, + IOSystem* pIOSystem, + const aiScene* pScene, + const ExportProperties* pProperties + + ){ + // initialize the exporter + FBXExporter exporter(pScene, pProperties); + + // perform ascii export + exporter.ExportAscii(pFile, pIOSystem); + } + +} // end of namespace Assimp + +FBXExporter::FBXExporter ( const aiScene* pScene, const ExportProperties* pProperties ) +: binary(false) +, mScene(pScene) +, mProperties(pProperties) +, outfile() +, connections() +, mesh_uids() +, material_uids() +, node_uids() { + // will probably need to determine UIDs, connections, etc here. + // basically anything that needs to be known + // before we start writing sections to the stream. +} + +void FBXExporter::ExportBinary ( + const char* pFile, + IOSystem* pIOSystem +){ + // remember that we're exporting in binary mode + binary = true; + + // we're not currently using these preferences, + // but clang will cry about it if we never touch it. + // TODO: some of these might be relevant to export + (void)mProperties; + + // open the indicated file for writing (in binary mode) + outfile.reset(pIOSystem->Open(pFile,"wb")); + if (!outfile) { + throw DeadlyExportError( + "could not open output .fbx file: " + std::string(pFile) + ); + } + + // first a binary-specific file header + WriteBinaryHeader(); + + // the rest of the file is in node entries. + // we have to serialize each entry before we write to the output, + // as the first thing we write is the byte offset of the _next_ entry. + // Either that or we can skip back to write the offset when we finish. + WriteAllNodes(); + + // finally we have a binary footer to the file + WriteBinaryFooter(); + + // explicitly release file pointer, + // so we don't have to rely on class destruction. + outfile.reset(); +} + +void FBXExporter::ExportAscii ( + const char* pFile, + IOSystem* pIOSystem +){ + // remember that we're exporting in ascii mode + binary = false; + + // open the indicated file for writing in text mode + outfile.reset(pIOSystem->Open(pFile,"wt")); + if (!outfile) { + throw DeadlyExportError( + "could not open output .fbx file: " + std::string(pFile) + ); + } + + // write the ascii header + WriteAsciiHeader(); + + // write all the sections + WriteAllNodes(); + + // make sure the file ends with a newline. + // note: if the file is opened in text mode, + // this should do the right cross-platform thing. + outfile->Write("\n", 1, 1); + + // explicitly release file pointer, + // so we don't have to rely on class destruction. + outfile.reset(); +} + +void FBXExporter::WriteAsciiHeader() +{ + // basically just a comment at the top of the file + std::stringstream head; + head << "; FBX " << EXPORT_VERSION_STR << " project file\n"; + head << "; Created by the Open Asset Import Library (Assimp)\n"; + head << "; http://assimp.org\n"; + head << "; -------------------------------------------------\n"; + const std::string ascii_header = head.str(); + outfile->Write(ascii_header.c_str(), ascii_header.size(), 1); +} + +void FBXExporter::WriteAsciiSectionHeader(const std::string& title) +{ + StreamWriterLE outstream(outfile); + std::stringstream s; + s << "\n\n; " << title << '\n'; + s << FBX::COMMENT_UNDERLINE << "\n"; + outstream.PutString(s.str()); +} + +void FBXExporter::WriteBinaryHeader() +{ + // first a specific sequence of 23 bytes, always the same + const char binary_header[24] = "Kaydara FBX Binary\x20\x20\x00\x1a\x00"; + outfile->Write(binary_header, 1, 23); + + // then FBX version number, "multiplied" by 1000, as little-endian uint32. + // so 7.3 becomes 7300 == 0x841C0000, 7.4 becomes 7400 == 0xE81C0000, etc + { + StreamWriterLE outstream(outfile); + outstream.PutU4(EXPORT_VERSION_INT); + } // StreamWriter destructor writes the data to the file + + // after this the node data starts immediately + // (probably with the FBXHEaderExtension node) +} + +void FBXExporter::WriteBinaryFooter() +{ + outfile->Write(NULL_RECORD.c_str(), NULL_RECORD.size(), 1); + + outfile->Write(GENERIC_FOOTID.c_str(), GENERIC_FOOTID.size(), 1); + + // here some padding is added for alignment to 16 bytes. + // if already aligned, the full 16 bytes is added. + size_t pos = outfile->Tell(); + size_t pad = 16 - (pos % 16); + for (size_t i = 0; i < pad; ++i) { + outfile->Write("\x00", 1, 1); + } + + // not sure what this is, but it seems to always be 0 in modern files + for (size_t i = 0; i < 4; ++i) { + outfile->Write("\x00", 1, 1); + } + + // now the file version again + { + StreamWriterLE outstream(outfile); + outstream.PutU4(EXPORT_VERSION_INT); + } // StreamWriter destructor writes the data to the file + + // and finally some binary footer added to all files + for (size_t i = 0; i < 120; ++i) { + outfile->Write("\x00", 1, 1); + } + outfile->Write(FOOT_MAGIC.c_str(), FOOT_MAGIC.size(), 1); +} + +void FBXExporter::WriteAllNodes () +{ + // header + // (and fileid, creation time, creator, if binary) + WriteHeaderExtension(); + + // global settings + WriteGlobalSettings(); + + // documents + WriteDocuments(); + + // references + WriteReferences(); + + // definitions + WriteDefinitions(); + + // objects + WriteObjects(); + + // connections + WriteConnections(); + + // WriteTakes? (deprecated since at least 2015 (fbx 7.4)) +} + +//FBXHeaderExtension top-level node +void FBXExporter::WriteHeaderExtension () +{ + if (!binary) { + // no title, follows directly from the top comment + } + FBX::Node n("FBXHeaderExtension"); + StreamWriterLE outstream(outfile); + int indent = 0; + + // begin node + n.Begin(outstream, binary, indent); + + // write properties + // (none) + + // finish properties + n.EndProperties(outstream, binary, indent, 0); + + // begin children + n.BeginChildren(outstream, binary, indent); + + indent = 1; + + // write child nodes + FBX::Node::WritePropertyNode( + "FBXHeaderVersion", int32_t(1003), outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "FBXVersion", int32_t(EXPORT_VERSION_INT), outstream, binary, indent + ); + if (binary) { + FBX::Node::WritePropertyNode( + "EncryptionType", int32_t(0), outstream, binary, indent + ); + } + + FBX::Node CreationTimeStamp("CreationTimeStamp"); + time_t rawtime; + time(&rawtime); + struct tm * now = localtime(&rawtime); + CreationTimeStamp.AddChild("Version", int32_t(1000)); + CreationTimeStamp.AddChild("Year", int32_t(now->tm_year + 1900)); + CreationTimeStamp.AddChild("Month", int32_t(now->tm_mon + 1)); + CreationTimeStamp.AddChild("Day", int32_t(now->tm_mday)); + CreationTimeStamp.AddChild("Hour", int32_t(now->tm_hour)); + CreationTimeStamp.AddChild("Minute", int32_t(now->tm_min)); + CreationTimeStamp.AddChild("Second", int32_t(now->tm_sec)); + CreationTimeStamp.AddChild("Millisecond", int32_t(0)); + CreationTimeStamp.Dump(outstream, binary, indent); + + std::stringstream creator; + creator << "Open Asset Import Library (Assimp) " << aiGetVersionMajor() + << "." << aiGetVersionMinor() << "." << aiGetVersionRevision(); + FBX::Node::WritePropertyNode( + "Creator", creator.str(), outstream, binary, indent + ); + + //FBX::Node sceneinfo("SceneInfo"); + //sceneinfo.AddProperty("GlobalInfo" + FBX::SEPARATOR + "SceneInfo"); + // not sure if any of this is actually needed, + // so just write an empty node for now. + //sceneinfo.Dump(outstream, binary, indent); + + indent = 0; + + // finish node + n.End(outstream, binary, indent, true); + + // that's it for FBXHeaderExtension... + if (!binary) { return; } + + // but binary files also need top-level FileID, CreationTime, Creator: + std::vector<uint8_t> raw(GENERIC_FILEID.size()); + for (size_t i = 0; i < GENERIC_FILEID.size(); ++i) { + raw[i] = uint8_t(GENERIC_FILEID[i]); + } + FBX::Node::WritePropertyNode( + "FileId", raw, outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "CreationTime", GENERIC_CTIME, outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "Creator", creator.str(), outstream, binary, indent + ); +} + +// WriteGlobalSettings helpers + +void WritePropInt(const aiScene* scene, FBX::Node& p, const std::string& key, int defaultValue) +{ + int value; + if (scene->mMetaData != nullptr && scene->mMetaData->Get(key, value)) { + p.AddP70int(key, value); + } else { + p.AddP70int(key, defaultValue); + } +} + +void WritePropDouble(const aiScene* scene, FBX::Node& p, const std::string& key, double defaultValue) +{ + double value; + if (scene->mMetaData != nullptr && scene->mMetaData->Get(key, value)) { + p.AddP70double(key, value); + } else { + // fallback lookup float instead + float floatValue; + if (scene->mMetaData != nullptr && scene->mMetaData->Get(key, floatValue)) { + p.AddP70double(key, (double)floatValue); + } else { + p.AddP70double(key, defaultValue); + } + } +} + +void WritePropEnum(const aiScene* scene, FBX::Node& p, const std::string& key, int defaultValue) +{ + int value; + if (scene->mMetaData != nullptr && scene->mMetaData->Get(key, value)) { + p.AddP70enum(key, value); + } else { + p.AddP70enum(key, defaultValue); + } +} + +void WritePropColor(const aiScene* scene, FBX::Node& p, const std::string& key, const aiVector3D& defaultValue) +{ + aiVector3D value; + if (scene->mMetaData != nullptr && scene->mMetaData->Get(key, value)) { + // ai_real can be float or double, cast to avoid warnings + p.AddP70color(key, (double)value.x, (double)value.y, (double)value.z); + } else { + p.AddP70color(key, (double)defaultValue.x, (double)defaultValue.y, (double)defaultValue.z); + } +} + +void WritePropString(const aiScene* scene, FBX::Node& p, const std::string& key, const std::string& defaultValue) +{ + aiString value; // MetaData doesn't hold std::string + if (scene->mMetaData != nullptr && scene->mMetaData->Get(key, value)) { + p.AddP70string(key, value.C_Str()); + } else { + p.AddP70string(key, defaultValue); + } +} + +void FBXExporter::WriteGlobalSettings () +{ + if (!binary) { + // no title, follows directly from the header extension + } + FBX::Node gs("GlobalSettings"); + gs.AddChild("Version", int32_t(1000)); + + FBX::Node p("Properties70"); + WritePropInt(mScene, p, "UpAxis", 1); + WritePropInt(mScene, p, "UpAxisSign", 1); + WritePropInt(mScene, p, "FrontAxis", 2); + WritePropInt(mScene, p, "FrontAxisSign", 1); + WritePropInt(mScene, p, "CoordAxis", 0); + WritePropInt(mScene, p, "CoordAxisSign", 1); + WritePropInt(mScene, p, "OriginalUpAxis", 1); + WritePropInt(mScene, p, "OriginalUpAxisSign", 1); + WritePropDouble(mScene, p, "UnitScaleFactor", 1.0); + WritePropDouble(mScene, p, "OriginalUnitScaleFactor", 1.0); + WritePropColor(mScene, p, "AmbientColor", aiVector3D((ai_real)0.0, (ai_real)0.0, (ai_real)0.0)); + WritePropString(mScene, p,"DefaultCamera", "Producer Perspective"); + WritePropEnum(mScene, p, "TimeMode", 11); + WritePropEnum(mScene, p, "TimeProtocol", 2); + WritePropEnum(mScene, p, "SnapOnFrameMode", 0); + p.AddP70time("TimeSpanStart", 0); // TODO: animation support + p.AddP70time("TimeSpanStop", FBX::SECOND); // TODO: animation support + WritePropDouble(mScene, p, "CustomFrameRate", -1.0); + p.AddP70("TimeMarker", "Compound", "", ""); // not sure what this is + WritePropInt(mScene, p, "CurrentTimeMarker", -1); + gs.AddChild(p); + + gs.Dump(outfile, binary, 0); +} + +void FBXExporter::WriteDocuments () +{ + if (!binary) { + WriteAsciiSectionHeader("Documents Description"); + } + + // not sure what the use of multiple documents would be, + // or whether any end-application supports it + FBX::Node docs("Documents"); + docs.AddChild("Count", int32_t(1)); + FBX::Node doc("Document"); + + // generate uid + int64_t uid = generate_uid(); + doc.AddProperties(uid, "", "Scene"); + FBX::Node p("Properties70"); + p.AddP70("SourceObject", "object", "", ""); // what is this even for? + p.AddP70string("ActiveAnimStackName", ""); // should do this properly? + doc.AddChild(p); + + // UID for root node in scene hierarchy. + // always set to 0 in the case of a single document. + // not sure what happens if more than one document exists, + // but that won't matter to us as we're exporting a single scene. + doc.AddChild("RootNode", int64_t(0)); + + docs.AddChild(doc); + docs.Dump(outfile, binary, 0); +} + +void FBXExporter::WriteReferences () +{ + if (!binary) { + WriteAsciiSectionHeader("Document References"); + } + // always empty for now. + // not really sure what this is for. + FBX::Node n("References"); + n.force_has_children = true; + n.Dump(outfile, binary, 0); +} + + +// --------------------------------------------------------------- +// some internal helper functions used for writing the definitions +// (before any actual data is written) +// --------------------------------------------------------------- + +size_t count_nodes(const aiNode* n, const aiNode* root) { + size_t count; + if (n == root) { + count = n->mNumMeshes; // (not counting root node) + } else if (n->mNumMeshes > 1) { + count = n->mNumMeshes + 1; + } else { + count = 1; + } + for (size_t i = 0; i < n->mNumChildren; ++i) { + count += count_nodes(n->mChildren[i], root); + } + return count; +} + +bool has_phong_mat(const aiScene* scene) +{ + // just search for any material with a shininess exponent + for (size_t i = 0; i < scene->mNumMaterials; ++i) { + aiMaterial* mat = scene->mMaterials[i]; + float shininess = 0; + mat->Get(AI_MATKEY_SHININESS, shininess); + if (shininess > 0) { + return true; + } + } + return false; +} + +size_t count_images(const aiScene* scene) { + std::unordered_set<std::string> images; + aiString texpath; + for (size_t i = 0; i < scene->mNumMaterials; ++i) { + aiMaterial* mat = scene->mMaterials[i]; + for ( + size_t tt = aiTextureType_DIFFUSE; + tt < aiTextureType_UNKNOWN; + ++tt + ){ + const aiTextureType textype = static_cast<aiTextureType>(tt); + const size_t texcount = mat->GetTextureCount(textype); + for (unsigned int j = 0; j < texcount; ++j) { + mat->GetTexture(textype, j, &texpath); + images.insert(std::string(texpath.C_Str())); + } + } + } + return images.size(); +} + +size_t count_textures(const aiScene* scene) { + size_t count = 0; + for (size_t i = 0; i < scene->mNumMaterials; ++i) { + aiMaterial* mat = scene->mMaterials[i]; + for ( + size_t tt = aiTextureType_DIFFUSE; + tt < aiTextureType_UNKNOWN; + ++tt + ){ + // TODO: handle layered textures + if (mat->GetTextureCount(static_cast<aiTextureType>(tt)) > 0) { + count += 1; + } + } + } + return count; +} + +size_t count_deformers(const aiScene* scene) { + size_t count = 0; + for (size_t i = 0; i < scene->mNumMeshes; ++i) { + const size_t n = scene->mMeshes[i]->mNumBones; + if (n) { + // 1 main deformer, 1 subdeformer per bone + count += n + 1; + } + } + return count; +} + +void FBXExporter::WriteDefinitions () +{ + // basically this is just bookkeeping: + // determining how many of each type of object there are + // and specifying the base properties to use when otherwise unspecified. + + // ascii section header + if (!binary) { + WriteAsciiSectionHeader("Object definitions"); + } + + // we need to count the objects + int32_t count; + int32_t total_count = 0; + + // and store them + std::vector<FBX::Node> object_nodes; + FBX::Node n, pt, p; + + // GlobalSettings + // this seems to always be here in Maya exports + n = FBX::Node("ObjectType", "GlobalSettings"); + count = 1; + n.AddChild("Count", count); + object_nodes.push_back(n); + total_count += count; + + // AnimationStack / FbxAnimStack + // this seems to always be here in Maya exports, + // but no harm seems to come of leaving it out. + count = mScene->mNumAnimations; + if (count) { + n = FBX::Node("ObjectType", "AnimationStack"); + n.AddChild("Count", count); + pt = FBX::Node("PropertyTemplate", "FbxAnimStack"); + p = FBX::Node("Properties70"); + p.AddP70string("Description", ""); + p.AddP70time("LocalStart", 0); + p.AddP70time("LocalStop", 0); + p.AddP70time("ReferenceStart", 0); + p.AddP70time("ReferenceStop", 0); + pt.AddChild(p); + n.AddChild(pt); + object_nodes.push_back(n); + total_count += count; + } + + // AnimationLayer / FbxAnimLayer + // this seems to always be here in Maya exports, + // but no harm seems to come of leaving it out. + // Assimp doesn't support animation layers, + // so there will be one per aiAnimation + count = mScene->mNumAnimations; + if (count) { + n = FBX::Node("ObjectType", "AnimationLayer"); + n.AddChild("Count", count); + pt = FBX::Node("PropertyTemplate", "FBXAnimLayer"); + p = FBX::Node("Properties70"); + p.AddP70("Weight", "Number", "", "A", double(100)); + p.AddP70bool("Mute", 0); + p.AddP70bool("Solo", 0); + p.AddP70bool("Lock", 0); + p.AddP70color("Color", 0.8, 0.8, 0.8); + p.AddP70("BlendMode", "enum", "", "", int32_t(0)); + p.AddP70("RotationAccumulationMode", "enum", "", "", int32_t(0)); + p.AddP70("ScaleAccumulationMode", "enum", "", "", int32_t(0)); + p.AddP70("BlendModeBypass", "ULongLong", "", "", int64_t(0)); + pt.AddChild(p); + n.AddChild(pt); + object_nodes.push_back(n); + total_count += count; + } + + // NodeAttribute + // this is completely absurd. + // there can only be one "NodeAttribute" template, + // but FbxSkeleton, FbxCamera, FbxLight all are "NodeAttributes". + // so if only one exists we should set the template for that, + // otherwise... we just pick one :/. + // the others have to set all their properties every instance, + // because there's no template. + count = 1; // TODO: select properly + if (count) { + // FbxSkeleton + n = FBX::Node("ObjectType", "NodeAttribute"); + n.AddChild("Count", count); + pt = FBX::Node("PropertyTemplate", "FbxSkeleton"); + p = FBX::Node("Properties70"); + p.AddP70color("Color", 0.8, 0.8, 0.8); + p.AddP70double("Size", 33.333333333333); + p.AddP70("LimbLength", "double", "Number", "H", double(1)); + // note: not sure what the "H" flag is for - hidden? + pt.AddChild(p); + n.AddChild(pt); + object_nodes.push_back(n); + total_count += count; + } + + // Model / FbxNode + // <~~ node hierarchy + count = int32_t(count_nodes(mScene->mRootNode, mScene->mRootNode)); + if (count) { + n = FBX::Node("ObjectType", "Model"); + n.AddChild("Count", count); + pt = FBX::Node("PropertyTemplate", "FbxNode"); + p = FBX::Node("Properties70"); + p.AddP70enum("QuaternionInterpolate", 0); + p.AddP70vector("RotationOffset", 0.0, 0.0, 0.0); + p.AddP70vector("RotationPivot", 0.0, 0.0, 0.0); + p.AddP70vector("ScalingOffset", 0.0, 0.0, 0.0); + p.AddP70vector("ScalingPivot", 0.0, 0.0, 0.0); + p.AddP70bool("TranslationActive", 0); + p.AddP70vector("TranslationMin", 0.0, 0.0, 0.0); + p.AddP70vector("TranslationMax", 0.0, 0.0, 0.0); + p.AddP70bool("TranslationMinX", 0); + p.AddP70bool("TranslationMinY", 0); + p.AddP70bool("TranslationMinZ", 0); + p.AddP70bool("TranslationMaxX", 0); + p.AddP70bool("TranslationMaxY", 0); + p.AddP70bool("TranslationMaxZ", 0); + p.AddP70enum("RotationOrder", 0); + p.AddP70bool("RotationSpaceForLimitOnly", 0); + p.AddP70double("RotationStiffnessX", 0.0); + p.AddP70double("RotationStiffnessY", 0.0); + p.AddP70double("RotationStiffnessZ", 0.0); + p.AddP70double("AxisLen", 10.0); + p.AddP70vector("PreRotation", 0.0, 0.0, 0.0); + p.AddP70vector("PostRotation", 0.0, 0.0, 0.0); + p.AddP70bool("RotationActive", 0); + p.AddP70vector("RotationMin", 0.0, 0.0, 0.0); + p.AddP70vector("RotationMax", 0.0, 0.0, 0.0); + p.AddP70bool("RotationMinX", 0); + p.AddP70bool("RotationMinY", 0); + p.AddP70bool("RotationMinZ", 0); + p.AddP70bool("RotationMaxX", 0); + p.AddP70bool("RotationMaxY", 0); + p.AddP70bool("RotationMaxZ", 0); + p.AddP70enum("InheritType", 0); + p.AddP70bool("ScalingActive", 0); + p.AddP70vector("ScalingMin", 0.0, 0.0, 0.0); + p.AddP70vector("ScalingMax", 1.0, 1.0, 1.0); + p.AddP70bool("ScalingMinX", 0); + p.AddP70bool("ScalingMinY", 0); + p.AddP70bool("ScalingMinZ", 0); + p.AddP70bool("ScalingMaxX", 0); + p.AddP70bool("ScalingMaxY", 0); + p.AddP70bool("ScalingMaxZ", 0); + p.AddP70vector("GeometricTranslation", 0.0, 0.0, 0.0); + p.AddP70vector("GeometricRotation", 0.0, 0.0, 0.0); + p.AddP70vector("GeometricScaling", 1.0, 1.0, 1.0); + p.AddP70double("MinDampRangeX", 0.0); + p.AddP70double("MinDampRangeY", 0.0); + p.AddP70double("MinDampRangeZ", 0.0); + p.AddP70double("MaxDampRangeX", 0.0); + p.AddP70double("MaxDampRangeY", 0.0); + p.AddP70double("MaxDampRangeZ", 0.0); + p.AddP70double("MinDampStrengthX", 0.0); + p.AddP70double("MinDampStrengthY", 0.0); + p.AddP70double("MinDampStrengthZ", 0.0); + p.AddP70double("MaxDampStrengthX", 0.0); + p.AddP70double("MaxDampStrengthY", 0.0); + p.AddP70double("MaxDampStrengthZ", 0.0); + p.AddP70double("PreferedAngleX", 0.0); + p.AddP70double("PreferedAngleY", 0.0); + p.AddP70double("PreferedAngleZ", 0.0); + p.AddP70("LookAtProperty", "object", "", ""); + p.AddP70("UpVectorProperty", "object", "", ""); + p.AddP70bool("Show", 1); + p.AddP70bool("NegativePercentShapeSupport", 1); + p.AddP70int("DefaultAttributeIndex", -1); + p.AddP70bool("Freeze", 0); + p.AddP70bool("LODBox", 0); + p.AddP70( + "Lcl Translation", "Lcl Translation", "", "A", + double(0), double(0), double(0) + ); + p.AddP70( + "Lcl Rotation", "Lcl Rotation", "", "A", + double(0), double(0), double(0) + ); + p.AddP70( + "Lcl Scaling", "Lcl Scaling", "", "A", + double(1), double(1), double(1) + ); + p.AddP70("Visibility", "Visibility", "", "A", double(1)); + p.AddP70( + "Visibility Inheritance", "Visibility Inheritance", "", "", + int32_t(1) + ); + pt.AddChild(p); + n.AddChild(pt); + object_nodes.push_back(n); + total_count += count; + } + + // Geometry / FbxMesh + // <~~ aiMesh + count = mScene->mNumMeshes; + + // Blendshapes are considered Geometry + int32_t bsDeformerCount=0; + for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) { + aiMesh* m = mScene->mMeshes[mi]; + if (m->mNumAnimMeshes > 0) { + count+=m->mNumAnimMeshes; + bsDeformerCount+=m->mNumAnimMeshes; // One deformer per blendshape + bsDeformerCount++; // Plus one master blendshape deformer + } + } + + if (count) { + n = FBX::Node("ObjectType", "Geometry"); + n.AddChild("Count", count); + pt = FBX::Node("PropertyTemplate", "FbxMesh"); + p = FBX::Node("Properties70"); + p.AddP70color("Color", 0, 0, 0); + p.AddP70vector("BBoxMin", 0, 0, 0); + p.AddP70vector("BBoxMax", 0, 0, 0); + p.AddP70bool("Primary Visibility", 1); + p.AddP70bool("Casts Shadows", 1); + p.AddP70bool("Receive Shadows", 1); + pt.AddChild(p); + n.AddChild(pt); + object_nodes.push_back(n); + total_count += count; + } + + // Material / FbxSurfacePhong, FbxSurfaceLambert, FbxSurfaceMaterial + // <~~ aiMaterial + // basically if there's any phong material this is defined as phong, + // and otherwise lambert. + // More complex materials cause a bare-bones FbxSurfaceMaterial definition + // and are treated specially, as they're not really supported by FBX. + // TODO: support Maya's Stingray PBS material + count = mScene->mNumMaterials; + if (count) { + bool has_phong = has_phong_mat(mScene); + n = FBX::Node("ObjectType", "Material"); + n.AddChild("Count", count); + pt = FBX::Node("PropertyTemplate"); + if (has_phong) { + pt.AddProperty("FbxSurfacePhong"); + } else { + pt.AddProperty("FbxSurfaceLambert"); + } + p = FBX::Node("Properties70"); + if (has_phong) { + p.AddP70string("ShadingModel", "Phong"); + } else { + p.AddP70string("ShadingModel", "Lambert"); + } + p.AddP70bool("MultiLayer", 0); + p.AddP70colorA("EmissiveColor", 0.0, 0.0, 0.0); + p.AddP70numberA("EmissiveFactor", 1.0); + p.AddP70colorA("AmbientColor", 0.2, 0.2, 0.2); + p.AddP70numberA("AmbientFactor", 1.0); + p.AddP70colorA("DiffuseColor", 0.8, 0.8, 0.8); + p.AddP70numberA("DiffuseFactor", 1.0); + p.AddP70vector("Bump", 0.0, 0.0, 0.0); + p.AddP70vector("NormalMap", 0.0, 0.0, 0.0); + p.AddP70double("BumpFactor", 1.0); + p.AddP70colorA("TransparentColor", 0.0, 0.0, 0.0); + p.AddP70numberA("TransparencyFactor", 0.0); + p.AddP70color("DisplacementColor", 0.0, 0.0, 0.0); + p.AddP70double("DisplacementFactor", 1.0); + p.AddP70color("VectorDisplacementColor", 0.0, 0.0, 0.0); + p.AddP70double("VectorDisplacementFactor", 1.0); + if (has_phong) { + p.AddP70colorA("SpecularColor", 0.2, 0.2, 0.2); + p.AddP70numberA("SpecularFactor", 1.0); + p.AddP70numberA("ShininessExponent", 20.0); + p.AddP70colorA("ReflectionColor", 0.0, 0.0, 0.0); + p.AddP70numberA("ReflectionFactor", 1.0); + } + pt.AddChild(p); + n.AddChild(pt); + object_nodes.push_back(n); + total_count += count; + } + + // Video / FbxVideo + // one for each image file. + count = int32_t(count_images(mScene)); + if (count) { + n = FBX::Node("ObjectType", "Video"); + n.AddChild("Count", count); + pt = FBX::Node("PropertyTemplate", "FbxVideo"); + p = FBX::Node("Properties70"); + p.AddP70bool("ImageSequence", 0); + p.AddP70int("ImageSequenceOffset", 0); + p.AddP70double("FrameRate", 0.0); + p.AddP70int("LastFrame", 0); + p.AddP70int("Width", 0); + p.AddP70int("Height", 0); + p.AddP70("Path", "KString", "XRefUrl", "", ""); + p.AddP70int("StartFrame", 0); + p.AddP70int("StopFrame", 0); + p.AddP70double("PlaySpeed", 0.0); + p.AddP70time("Offset", 0); + p.AddP70enum("InterlaceMode", 0); + p.AddP70bool("FreeRunning", 0); + p.AddP70bool("Loop", 0); + p.AddP70enum("AccessMode", 0); + pt.AddChild(p); + n.AddChild(pt); + object_nodes.push_back(n); + total_count += count; + } + + // Texture / FbxFileTexture + // <~~ aiTexture + count = int32_t(count_textures(mScene)); + if (count) { + n = FBX::Node("ObjectType", "Texture"); + n.AddChild("Count", count); + pt = FBX::Node("PropertyTemplate", "FbxFileTexture"); + p = FBX::Node("Properties70"); + p.AddP70enum("TextureTypeUse", 0); + p.AddP70numberA("Texture alpha", 1.0); + p.AddP70enum("CurrentMappingType", 0); + p.AddP70enum("WrapModeU", 0); + p.AddP70enum("WrapModeV", 0); + p.AddP70bool("UVSwap", 0); + p.AddP70bool("PremultiplyAlpha", 1); + p.AddP70vectorA("Translation", 0.0, 0.0, 0.0); + p.AddP70vectorA("Rotation", 0.0, 0.0, 0.0); + p.AddP70vectorA("Scaling", 1.0, 1.0, 1.0); + p.AddP70vector("TextureRotationPivot", 0.0, 0.0, 0.0); + p.AddP70vector("TextureScalingPivot", 0.0, 0.0, 0.0); + p.AddP70enum("CurrentTextureBlendMode", 1); + p.AddP70string("UVSet", "default"); + p.AddP70bool("UseMaterial", 0); + p.AddP70bool("UseMipMap", 0); + pt.AddChild(p); + n.AddChild(pt); + object_nodes.push_back(n); + total_count += count; + } + + // AnimationCurveNode / FbxAnimCurveNode + count = mScene->mNumAnimations * 3; + if (count) { + n = FBX::Node("ObjectType", "AnimationCurveNode"); + n.AddChild("Count", count); + pt = FBX::Node("PropertyTemplate", "FbxAnimCurveNode"); + p = FBX::Node("Properties70"); + p.AddP70("d", "Compound", "", ""); + pt.AddChild(p); + n.AddChild(pt); + object_nodes.push_back(n); + total_count += count; + } + + // AnimationCurve / FbxAnimCurve + count = mScene->mNumAnimations * 9; + if (count) { + n = FBX::Node("ObjectType", "AnimationCurve"); + n.AddChild("Count", count); + object_nodes.push_back(n); + total_count += count; + } + + // Pose + count = 0; + for (size_t i = 0; i < mScene->mNumMeshes; ++i) { + aiMesh* mesh = mScene->mMeshes[i]; + if (mesh->HasBones()) { ++count; } + } + if (count) { + n = FBX::Node("ObjectType", "Pose"); + n.AddChild("Count", count); + object_nodes.push_back(n); + total_count += count; + } + + // Deformer + count = int32_t(count_deformers(mScene))+bsDeformerCount; + if (count) { + n = FBX::Node("ObjectType", "Deformer"); + n.AddChild("Count", count); + object_nodes.push_back(n); + total_count += count; + } + + // (template) + count = 0; + if (count) { + n = FBX::Node("ObjectType", ""); + n.AddChild("Count", count); + pt = FBX::Node("PropertyTemplate", ""); + p = FBX::Node("Properties70"); + pt.AddChild(p); + n.AddChild(pt); + object_nodes.push_back(n); + total_count += count; + } + + // now write it all + FBX::Node defs("Definitions"); + defs.AddChild("Version", int32_t(100)); + defs.AddChild("Count", int32_t(total_count)); + for (auto &on : object_nodes) { + defs.AddChild(on); + } + defs.Dump(outfile, binary, 0); +} + + +// ------------------------------------------------------------------- +// some internal helper functions used for writing the objects section +// (which holds the actual data) +// ------------------------------------------------------------------- + +aiNode* get_node_for_mesh(unsigned int meshIndex, aiNode* node) +{ + for (size_t i = 0; i < node->mNumMeshes; ++i) { + if (node->mMeshes[i] == meshIndex) { + return node; + } + } + for (size_t i = 0; i < node->mNumChildren; ++i) { + aiNode* ret = get_node_for_mesh(meshIndex, node->mChildren[i]); + if (ret) { return ret; } + } + return nullptr; +} + +aiMatrix4x4 get_world_transform(const aiNode* node, const aiScene* scene) +{ + std::vector<const aiNode*> node_chain; + while (node != scene->mRootNode) { + node_chain.push_back(node); + node = node->mParent; + } + aiMatrix4x4 transform; + for (auto n = node_chain.rbegin(); n != node_chain.rend(); ++n) { + transform *= (*n)->mTransformation; + } + return transform; +} + +int64_t to_ktime(double ticks, const aiAnimation* anim) { + if (anim->mTicksPerSecond <= 0) { + return static_cast<int64_t>(ticks) * FBX::SECOND; + } + return (static_cast<int64_t>(ticks) / static_cast<int64_t>(anim->mTicksPerSecond)) * FBX::SECOND; +} + +int64_t to_ktime(double time) { + return (static_cast<int64_t>(time * FBX::SECOND)); +} + +void FBXExporter::WriteObjects () +{ + if (!binary) { + WriteAsciiSectionHeader("Object properties"); + } + // numbers should match those given in definitions! make sure to check + StreamWriterLE outstream(outfile); + FBX::Node object_node("Objects"); + int indent = 0; + object_node.Begin(outstream, binary, indent); + object_node.EndProperties(outstream, binary, indent); + object_node.BeginChildren(outstream, binary, indent); + + bool bJoinIdenticalVertices = mProperties->GetPropertyBool("bJoinIdenticalVertices", true); + std::vector<std::vector<int32_t>> vVertexIndice;//save vertex_indices as it is needed later + + // geometry (aiMesh) + mesh_uids.clear(); + indent = 1; + for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) { + // it's all about this mesh + aiMesh* m = mScene->mMeshes[mi]; + + // start the node record + FBX::Node n("Geometry"); + int64_t uid = generate_uid(); + mesh_uids.push_back(uid); + n.AddProperty(uid); + n.AddProperty(FBX::SEPARATOR + "Geometry"); + n.AddProperty("Mesh"); + n.Begin(outstream, binary, indent); + n.DumpProperties(outstream, binary, indent); + n.EndProperties(outstream, binary, indent); + n.BeginChildren(outstream, binary, indent); + indent = 2; + + // output vertex data - each vertex should be unique (probably) + std::vector<double> flattened_vertices; + // index of original vertex in vertex data vector + std::vector<int32_t> vertex_indices; + // map of vertex value to its index in the data vector + std::map<aiVector3D,size_t> index_by_vertex_value; + if(bJoinIdenticalVertices){ + int32_t index = 0; + for (size_t vi = 0; vi < m->mNumVertices; ++vi) { + aiVector3D vtx = m->mVertices[vi]; + auto elem = index_by_vertex_value.find(vtx); + if (elem == index_by_vertex_value.end()) { + vertex_indices.push_back(index); + index_by_vertex_value[vtx] = index; + flattened_vertices.push_back(vtx[0]); + flattened_vertices.push_back(vtx[1]); + flattened_vertices.push_back(vtx[2]); + ++index; + } else { + vertex_indices.push_back(int32_t(elem->second)); + } + } + } + else { // do not join vertex, respect the export flag + vertex_indices.resize(m->mNumVertices); + std::iota(vertex_indices.begin(), vertex_indices.end(), 0); + for(unsigned int v = 0; v < m->mNumVertices; ++ v) { + aiVector3D vtx = m->mVertices[v]; + flattened_vertices.push_back(vtx.x); + flattened_vertices.push_back(vtx.y); + flattened_vertices.push_back(vtx.z); + } + } + vVertexIndice.push_back(vertex_indices); + + FBX::Node::WritePropertyNode( + "Vertices", flattened_vertices, outstream, binary, indent + ); + + // output polygon data as a flattened array of vertex indices. + // the last vertex index of each polygon is negated and - 1 + std::vector<int32_t> polygon_data; + for (size_t fi = 0; fi < m->mNumFaces; ++fi) { + const aiFace &f = m->mFaces[fi]; + for (size_t pvi = 0; pvi < f.mNumIndices - 1; ++pvi) { + polygon_data.push_back(vertex_indices[f.mIndices[pvi]]); + } + polygon_data.push_back( + -1 - vertex_indices[f.mIndices[f.mNumIndices-1]] + ); + } + FBX::Node::WritePropertyNode( + "PolygonVertexIndex", polygon_data, outstream, binary, indent + ); + + // here could be edges but they're insane. + // it's optional anyway, so let's ignore it. + + FBX::Node::WritePropertyNode( + "GeometryVersion", int32_t(124), outstream, binary, indent + ); + + // normals, if any + if (m->HasNormals()) { + FBX::Node normals("LayerElementNormal", int32_t(0)); + normals.Begin(outstream, binary, indent); + normals.DumpProperties(outstream, binary, indent); + normals.EndProperties(outstream, binary, indent); + normals.BeginChildren(outstream, binary, indent); + indent = 3; + FBX::Node::WritePropertyNode( + "Version", int32_t(101), outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "Name", "", outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "MappingInformationType", "ByPolygonVertex", + outstream, binary, indent + ); + // TODO: vertex-normals or indexed normals when appropriate + FBX::Node::WritePropertyNode( + "ReferenceInformationType", "Direct", + outstream, binary, indent + ); + std::vector<double> normal_data; + normal_data.reserve(3 * polygon_data.size()); + for (size_t fi = 0; fi < m->mNumFaces; ++fi) { + const aiFace &f = m->mFaces[fi]; + for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) { + const aiVector3D &curN = m->mNormals[f.mIndices[pvi]]; + normal_data.push_back(curN.x); + normal_data.push_back(curN.y); + normal_data.push_back(curN.z); + } + } + FBX::Node::WritePropertyNode( + "Normals", normal_data, outstream, binary, indent + ); + // note: version 102 has a NormalsW also... not sure what it is, + // so we can stick with version 101 for now. + indent = 2; + normals.End(outstream, binary, indent, true); + } + + // colors, if any + // TODO only one color channel currently + const int32_t colorChannelIndex = 0; + if (m->HasVertexColors(colorChannelIndex)) { + FBX::Node vertexcolors("LayerElementColor", int32_t(colorChannelIndex)); + vertexcolors.Begin(outstream, binary, indent); + vertexcolors.DumpProperties(outstream, binary, indent); + vertexcolors.EndProperties(outstream, binary, indent); + vertexcolors.BeginChildren(outstream, binary, indent); + indent = 3; + FBX::Node::WritePropertyNode( + "Version", int32_t(101), outstream, binary, indent + ); + char layerName[8]; + sprintf(layerName, "COLOR_%d", colorChannelIndex); + FBX::Node::WritePropertyNode( + "Name", (const char*)layerName, outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "MappingInformationType", "ByPolygonVertex", + outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "ReferenceInformationType", "Direct", + outstream, binary, indent + ); + std::vector<double> color_data; + color_data.reserve(4 * polygon_data.size()); + for (size_t fi = 0; fi < m->mNumFaces; ++fi) { + const aiFace &f = m->mFaces[fi]; + for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) { + const aiColor4D &c = m->mColors[colorChannelIndex][f.mIndices[pvi]]; + color_data.push_back(c.r); + color_data.push_back(c.g); + color_data.push_back(c.b); + color_data.push_back(c.a); + } + } + FBX::Node::WritePropertyNode( + "Colors", color_data, outstream, binary, indent + ); + indent = 2; + vertexcolors.End(outstream, binary, indent, true); + } + + // uvs, if any + for (size_t uvi = 0; uvi < m->GetNumUVChannels(); ++uvi) { + if (m->mNumUVComponents[uvi] > 2) { + // FBX only supports 2-channel UV maps... + // or at least i'm not sure how to indicate a different number + std::stringstream err; + err << "Only 2-channel UV maps supported by FBX,"; + err << " but mesh " << mi; + if (m->mName.length) { + err << " (" << m->mName.C_Str() << ")"; + } + err << " UV map " << uvi; + err << " has " << m->mNumUVComponents[uvi]; + err << " components! Data will be preserved,"; + err << " but may be incorrectly interpreted on load."; + ASSIMP_LOG_WARN(err.str()); + } + FBX::Node uv("LayerElementUV", int32_t(uvi)); + uv.Begin(outstream, binary, indent); + uv.DumpProperties(outstream, binary, indent); + uv.EndProperties(outstream, binary, indent); + uv.BeginChildren(outstream, binary, indent); + indent = 3; + FBX::Node::WritePropertyNode( + "Version", int32_t(101), outstream, binary, indent + ); + // it doesn't seem like assimp keeps the uv map name, + // so just leave it blank. + FBX::Node::WritePropertyNode( + "Name", "", outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "MappingInformationType", "ByPolygonVertex", + outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "ReferenceInformationType", "IndexToDirect", + outstream, binary, indent + ); + + std::vector<double> uv_data; + std::vector<int32_t> uv_indices; + std::map<aiVector3D,int32_t> index_by_uv; + int32_t index = 0; + for (size_t fi = 0; fi < m->mNumFaces; ++fi) { + const aiFace &f = m->mFaces[fi]; + for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) { + const aiVector3D &curUv = + m->mTextureCoords[uvi][f.mIndices[pvi]]; + auto elem = index_by_uv.find(curUv); + if (elem == index_by_uv.end()) { + index_by_uv[curUv] = index; + uv_indices.push_back(index); + for (unsigned int x = 0; x < m->mNumUVComponents[uvi]; ++x) { + uv_data.push_back(curUv[x]); + } + ++index; + } else { + uv_indices.push_back(elem->second); + } + } + } + FBX::Node::WritePropertyNode( + "UV", uv_data, outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "UVIndex", uv_indices, outstream, binary, indent + ); + indent = 2; + uv.End(outstream, binary, indent, true); + } + + // i'm not really sure why this material section exists, + // as the material is linked via "Connections". + // it seems to always have the same "0" value. + FBX::Node mat("LayerElementMaterial", int32_t(0)); + mat.AddChild("Version", int32_t(101)); + mat.AddChild("Name", ""); + mat.AddChild("MappingInformationType", "AllSame"); + mat.AddChild("ReferenceInformationType", "IndexToDirect"); + std::vector<int32_t> mat_indices = {0}; + mat.AddChild("Materials", mat_indices); + mat.Dump(outstream, binary, indent); + + // finally we have the layer specifications, + // which select the normals / UV set / etc to use. + // TODO: handle multiple uv sets correctly? + FBX::Node layer("Layer", int32_t(0)); + layer.AddChild("Version", int32_t(100)); + FBX::Node le("LayerElement"); + le.AddChild("Type", "LayerElementNormal"); + le.AddChild("TypedIndex", int32_t(0)); + layer.AddChild(le); + // TODO only 1 color channel currently + le = FBX::Node("LayerElement"); + le.AddChild("Type", "LayerElementColor"); + le.AddChild("TypedIndex", int32_t(0)); + layer.AddChild(le); + le = FBX::Node("LayerElement"); + le.AddChild("Type", "LayerElementMaterial"); + le.AddChild("TypedIndex", int32_t(0)); + layer.AddChild(le); + le = FBX::Node("LayerElement"); + le.AddChild("Type", "LayerElementUV"); + le.AddChild("TypedIndex", int32_t(0)); + layer.AddChild(le); + layer.Dump(outstream, binary, indent); + + for(unsigned int lr = 1; lr < m->GetNumUVChannels(); ++ lr) + { + FBX::Node layerExtra("Layer", int32_t(lr)); + layerExtra.AddChild("Version", int32_t(100)); + FBX::Node leExtra("LayerElement"); + leExtra.AddChild("Type", "LayerElementUV"); + leExtra.AddChild("TypedIndex", int32_t(lr)); + layerExtra.AddChild(leExtra); + layerExtra.Dump(outstream, binary, indent); + } + // finish the node record + indent = 1; + n.End(outstream, binary, indent, true); + } + + + // aiMaterial + material_uids.clear(); + for (size_t i = 0; i < mScene->mNumMaterials; ++i) { + // it's all about this material + aiMaterial* m = mScene->mMaterials[i]; + + // these are used to receive material data + float f; aiColor3D c; + + // start the node record + FBX::Node n("Material"); + + int64_t uid = generate_uid(); + material_uids.push_back(uid); + n.AddProperty(uid); + + aiString name; + m->Get(AI_MATKEY_NAME, name); + n.AddProperty(name.C_Str() + FBX::SEPARATOR + "Material"); + + n.AddProperty(""); + + n.AddChild("Version", int32_t(102)); + f = 0; + m->Get(AI_MATKEY_SHININESS, f); + bool phong = (f > 0); + if (phong) { + n.AddChild("ShadingModel", "phong"); + } else { + n.AddChild("ShadingModel", "lambert"); + } + n.AddChild("MultiLayer", int32_t(0)); + + FBX::Node p("Properties70"); + + // materials exported using the FBX SDK have two sets of fields. + // there are the properties specified in the PropertyTemplate, + // which are those supported by the modernFBX SDK, + // and an extra set of properties with simpler names. + // The extra properties are a legacy material system from pre-2009. + // + // In the modern system, each property has "color" and "factor". + // Generally the interpretation of these seems to be + // that the colour is multiplied by the factor before use, + // but this is not always clear-cut. + // + // Usually assimp only stores the colour, + // so we can just leave the factors at the default "1.0". + + // first we can export the "standard" properties + if (m->Get(AI_MATKEY_COLOR_AMBIENT, c) == aiReturn_SUCCESS) { + p.AddP70colorA("AmbientColor", c.r, c.g, c.b); + //p.AddP70numberA("AmbientFactor", 1.0); + } + if (m->Get(AI_MATKEY_COLOR_DIFFUSE, c) == aiReturn_SUCCESS) { + p.AddP70colorA("DiffuseColor", c.r, c.g, c.b); + //p.AddP70numberA("DiffuseFactor", 1.0); + } + if (m->Get(AI_MATKEY_COLOR_TRANSPARENT, c) == aiReturn_SUCCESS) { + // "TransparentColor" / "TransparencyFactor"... + // thanks FBX, for your insightful interpretation of consistency + p.AddP70colorA("TransparentColor", c.r, c.g, c.b); + // TransparencyFactor defaults to 0.0, so set it to 1.0. + // note: Maya always sets this to 1.0, + // so we can't use it sensibly as "Opacity". + // In stead we rely on the legacy "Opacity" value, below. + // Blender also relies on "Opacity" not "TransparencyFactor", + // probably for a similar reason. + p.AddP70numberA("TransparencyFactor", 1.0); + } + if (m->Get(AI_MATKEY_COLOR_REFLECTIVE, c) == aiReturn_SUCCESS) { + p.AddP70colorA("ReflectionColor", c.r, c.g, c.b); + } + if (m->Get(AI_MATKEY_REFLECTIVITY, f) == aiReturn_SUCCESS) { + p.AddP70numberA("ReflectionFactor", f); + } + if (phong) { + if (m->Get(AI_MATKEY_COLOR_SPECULAR, c) == aiReturn_SUCCESS) { + p.AddP70colorA("SpecularColor", c.r, c.g, c.b); + } + if (m->Get(AI_MATKEY_SHININESS_STRENGTH, f) == aiReturn_SUCCESS) { + p.AddP70numberA("ShininessFactor", f); + } + if (m->Get(AI_MATKEY_SHININESS, f) == aiReturn_SUCCESS) { + p.AddP70numberA("ShininessExponent", f); + } + if (m->Get(AI_MATKEY_REFLECTIVITY, f) == aiReturn_SUCCESS) { + p.AddP70numberA("ReflectionFactor", f); + } + } + + // Now the legacy system. + // For safety let's include it. + // thrse values don't exist in the property template, + // and usually are completely ignored when loading. + // One notable exception is the "Opacity" property, + // which Blender uses as (1.0 - alpha). + c.r = 0.0f; c.g = 0.0f; c.b = 0.0f; + m->Get(AI_MATKEY_COLOR_EMISSIVE, c); + p.AddP70vector("Emissive", c.r, c.g, c.b); + c.r = 0.2f; c.g = 0.2f; c.b = 0.2f; + m->Get(AI_MATKEY_COLOR_AMBIENT, c); + p.AddP70vector("Ambient", c.r, c.g, c.b); + c.r = 0.8f; c.g = 0.8f; c.b = 0.8f; + m->Get(AI_MATKEY_COLOR_DIFFUSE, c); + p.AddP70vector("Diffuse", c.r, c.g, c.b); + // The FBX SDK determines "Opacity" from transparency colour (RGB) + // and factor (F) as: O = (1.0 - F * ((R + G + B) / 3)). + // However we actually have an opacity value, + // so we should take it from AI_MATKEY_OPACITY if possible. + // It might make more sense to use TransparencyFactor, + // but Blender actually loads "Opacity" correctly, so let's use it. + f = 1.0f; + if (m->Get(AI_MATKEY_COLOR_TRANSPARENT, c) == aiReturn_SUCCESS) { + f = 1.0f - ((c.r + c.g + c.b) / 3.0f); + } + m->Get(AI_MATKEY_OPACITY, f); + p.AddP70double("Opacity", f); + if (phong) { + // specular color is multiplied by shininess_strength + c.r = 0.2f; c.g = 0.2f; c.b = 0.2f; + m->Get(AI_MATKEY_COLOR_SPECULAR, c); + f = 1.0f; + m->Get(AI_MATKEY_SHININESS_STRENGTH, f); + p.AddP70vector("Specular", f*c.r, f*c.g, f*c.b); + f = 20.0f; + m->Get(AI_MATKEY_SHININESS, f); + p.AddP70double("Shininess", f); + // Legacy "Reflectivity" is F*F*((R+G+B)/3), + // where F is the proportion of light reflected (AKA reflectivity), + // and RGB is the reflective colour of the material. + // No idea why, but we might as well set it the same way. + f = 0.0f; + m->Get(AI_MATKEY_REFLECTIVITY, f); + c.r = 1.0f, c.g = 1.0f, c.b = 1.0f; + m->Get(AI_MATKEY_COLOR_REFLECTIVE, c); + p.AddP70double("Reflectivity", f*f*((c.r+c.g+c.b)/3.0)); + } + + n.AddChild(p); + + n.Dump(outstream, binary, indent); + } + + // we need to look up all the images we're using, + // so we can generate uids, and eliminate duplicates. + std::map<std::string, int64_t> uid_by_image; + for (size_t i = 0; i < mScene->mNumMaterials; ++i) { + aiString texpath; + aiMaterial* mat = mScene->mMaterials[i]; + for ( + size_t tt = aiTextureType_DIFFUSE; + tt < aiTextureType_UNKNOWN; + ++tt + ){ + const aiTextureType textype = static_cast<aiTextureType>(tt); + const size_t texcount = mat->GetTextureCount(textype); + for (size_t j = 0; j < texcount; ++j) { + mat->GetTexture(textype, (unsigned int)j, &texpath); + const std::string texstring = texpath.C_Str(); + auto elem = uid_by_image.find(texstring); + if (elem == uid_by_image.end()) { + uid_by_image[texstring] = generate_uid(); + } + } + } + } + + // FbxVideo - stores images used by textures. + for (const auto &it : uid_by_image) { + FBX::Node n("Video"); + const int64_t& uid = it.second; + const std::string name = ""; // TODO: ... name??? + n.AddProperties(uid, name + FBX::SEPARATOR + "Video", "Clip"); + n.AddChild("Type", "Clip"); + FBX::Node p("Properties70"); + // TODO: get full path... relative path... etc... ugh... + // for now just use the same path for everything, + // and hopefully one of them will work out. + std::string path = it.first; + // try get embedded texture + const aiTexture* embedded_texture = mScene->GetEmbeddedTexture(it.first.c_str()); + if (embedded_texture != nullptr) { + // change the path (use original filename, if available. If name is empty, concatenate texture index with file extension) + std::stringstream newPath; + if (embedded_texture->mFilename.length > 0) { + newPath << embedded_texture->mFilename.C_Str(); + } else if (embedded_texture->achFormatHint[0]) { + int texture_index = std::stoi(path.substr(1, path.size() - 1)); + newPath << texture_index << "." << embedded_texture->achFormatHint; + } + path = newPath.str(); + // embed the texture + size_t texture_size = static_cast<size_t>(embedded_texture->mWidth * std::max(embedded_texture->mHeight, 1u)); + if (binary) { + // embed texture as binary data + std::vector<uint8_t> tex_data; + tex_data.resize(texture_size); + memcpy(&tex_data[0], (char*)embedded_texture->pcData, texture_size); + n.AddChild("Content", tex_data); + } else { + // embed texture in base64 encoding + std::string encoded_texture = FBX::Util::EncodeBase64((char*)embedded_texture->pcData, texture_size); + n.AddChild("Content", encoded_texture); + } + } + p.AddP70("Path", "KString", "XRefUrl", "", path); + n.AddChild(p); + n.AddChild("UseMipMap", int32_t(0)); + n.AddChild("Filename", path); + n.AddChild("RelativeFilename", path); + n.Dump(outstream, binary, indent); + } + + // Textures + // referenced by material_index/texture_type pairs. + std::map<std::pair<size_t,size_t>,int64_t> texture_uids; + const std::map<aiTextureType,std::string> prop_name_by_tt = { + {aiTextureType_DIFFUSE, "DiffuseColor"}, + {aiTextureType_SPECULAR, "SpecularColor"}, + {aiTextureType_AMBIENT, "AmbientColor"}, + {aiTextureType_EMISSIVE, "EmissiveColor"}, + {aiTextureType_HEIGHT, "Bump"}, + {aiTextureType_NORMALS, "NormalMap"}, + {aiTextureType_SHININESS, "ShininessExponent"}, + {aiTextureType_OPACITY, "TransparentColor"}, + {aiTextureType_DISPLACEMENT, "DisplacementColor"}, + //{aiTextureType_LIGHTMAP, "???"}, + {aiTextureType_REFLECTION, "ReflectionColor"} + //{aiTextureType_UNKNOWN, ""} + }; + for (size_t i = 0; i < mScene->mNumMaterials; ++i) { + // textures are attached to materials + aiMaterial* mat = mScene->mMaterials[i]; + int64_t material_uid = material_uids[i]; + + for ( + size_t j = aiTextureType_DIFFUSE; + j < aiTextureType_UNKNOWN; + ++j + ) { + const aiTextureType tt = static_cast<aiTextureType>(j); + size_t n = mat->GetTextureCount(tt); + + if (n < 1) { // no texture of this type + continue; + } + + if (n > 1) { + // TODO: multilayer textures + std::stringstream err; + err << "Multilayer textures not supported (for now),"; + err << " skipping texture type " << j; + err << " of material " << i; + ASSIMP_LOG_WARN(err.str()); + } + + // get image path for this (single-image) texture + aiString tpath; + if (mat->GetTexture(tt, 0, &tpath) != aiReturn_SUCCESS) { + std::stringstream err; + err << "Failed to get texture 0 for texture of type " << tt; + err << " on material " << i; + err << ", however GetTextureCount returned 1."; + throw DeadlyExportError(err.str()); + } + const std::string texture_path(tpath.C_Str()); + + // get connected image uid + auto elem = uid_by_image.find(texture_path); + if (elem == uid_by_image.end()) { + // this should never happen + std::stringstream err; + err << "Failed to find video element for texture with path"; + err << " \"" << texture_path << "\""; + err << ", type " << j << ", material " << i; + throw DeadlyExportError(err.str()); + } + const int64_t image_uid = elem->second; + + // get the name of the material property to connect to + auto elem2 = prop_name_by_tt.find(tt); + if (elem2 == prop_name_by_tt.end()) { + // don't know how to handle this type of texture, + // so skip it. + std::stringstream err; + err << "Not sure how to handle texture of type " << j; + err << " on material " << i; + err << ", skipping..."; + ASSIMP_LOG_WARN(err.str()); + continue; + } + const std::string& prop_name = elem2->second; + + // generate a uid for this texture + const int64_t texture_uid = generate_uid(); + + // link the texture to the material + connections.emplace_back( + "C", "OP", texture_uid, material_uid, prop_name + ); + + // link the image data to the texture + connections.emplace_back("C", "OO", image_uid, texture_uid); + + aiUVTransform trafo; + unsigned int max = sizeof(aiUVTransform); + aiGetMaterialFloatArray(mat, AI_MATKEY_UVTRANSFORM(aiTextureType_DIFFUSE, 0), (ai_real *)&trafo, &max); + + // now write the actual texture node + FBX::Node tnode("Texture"); + // TODO: some way to determine texture name? + const std::string texture_name = "" + FBX::SEPARATOR + "Texture"; + tnode.AddProperties(texture_uid, texture_name, ""); + // there really doesn't seem to be a better type than this: + tnode.AddChild("Type", "TextureVideoClip"); + tnode.AddChild("Version", int32_t(202)); + tnode.AddChild("TextureName", texture_name); + FBX::Node p("Properties70"); + p.AddP70vectorA("Translation", trafo.mTranslation[0], trafo.mTranslation[1], 0.0); + p.AddP70vectorA("Rotation", 0, 0, trafo.mRotation); + p.AddP70vectorA("Scaling", trafo.mScaling[0], trafo.mScaling[1], 0.0); + p.AddP70enum("CurrentTextureBlendMode", 0); // TODO: verify + //p.AddP70string("UVSet", ""); // TODO: how should this work? + p.AddP70bool("UseMaterial", 1); + tnode.AddChild(p); + // can't easily determine which texture path will be correct, + // so just store what we have in every field. + // these being incorrect is a common problem with FBX anyway. + tnode.AddChild("FileName", texture_path); + tnode.AddChild("RelativeFilename", texture_path); + tnode.AddChild("ModelUVTranslation", double(0.0), double(0.0)); + tnode.AddChild("ModelUVScaling", double(1.0), double(1.0)); + tnode.AddChild("Texture_Alpha_Source", "None"); + tnode.AddChild( + "Cropping", int32_t(0), int32_t(0), int32_t(0), int32_t(0) + ); + tnode.Dump(outstream, binary, indent); + } + } + + // Blendshapes, if any + for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) { + const aiMesh* m = mScene->mMeshes[mi]; + if (m->mNumAnimMeshes == 0) { + continue; + } + // make a deformer for this mesh + int64_t deformer_uid = generate_uid(); + FBX::Node dnode("Deformer"); + dnode.AddProperties(deformer_uid, m->mName.data + FBX::SEPARATOR + "Blendshapes", "BlendShape"); + dnode.AddChild("Version", int32_t(101)); + dnode.Dump(outstream, binary, indent); + // connect it + connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]); + std::vector<int32_t> vertex_indices = vVertexIndice[mi]; + + for (unsigned int am = 0; am < m->mNumAnimMeshes; ++am) { + aiAnimMesh *pAnimMesh = m->mAnimMeshes[am]; + std::string blendshape_name = pAnimMesh->mName.data; + + // start the node record + FBX::Node bsnode("Geometry"); + int64_t blendshape_uid = generate_uid(); + mesh_uids.push_back(blendshape_uid); + bsnode.AddProperty(blendshape_uid); + bsnode.AddProperty(blendshape_name + FBX::SEPARATOR + "Blendshape"); + bsnode.AddProperty("Shape"); + bsnode.AddChild("Version", int32_t(100)); + bsnode.Begin(outstream, binary, indent); + bsnode.DumpProperties(outstream, binary, indent); + bsnode.EndProperties(outstream, binary, indent); + bsnode.BeginChildren(outstream, binary, indent); + indent++; + if (pAnimMesh->HasPositions()) { + std::vector<int32_t>shape_indices; + std::vector<double>pPositionDiff; + std::vector<double>pNormalDiff; + + for (unsigned int vt = 0; vt < vertex_indices.size(); ++vt) { + aiVector3D pDiff = (pAnimMesh->mVertices[vertex_indices[vt]] - m->mVertices[vertex_indices[vt]]); + if(pDiff.Length()>1e-8){ + shape_indices.push_back(vertex_indices[vt]); + pPositionDiff.push_back(pDiff[0]); + pPositionDiff.push_back(pDiff[1]); + pPositionDiff.push_back(pDiff[2]); + + if (pAnimMesh->HasNormals()) { + aiVector3D nDiff = (pAnimMesh->mNormals[vertex_indices[vt]] - m->mNormals[vertex_indices[vt]]); + pNormalDiff.push_back(nDiff[0]); + pNormalDiff.push_back(nDiff[1]); + pNormalDiff.push_back(nDiff[2]); + } + } + } + + FBX::Node::WritePropertyNode( + "Indexes", shape_indices, outstream, binary, indent + ); + + FBX::Node::WritePropertyNode( + "Vertices", pPositionDiff, outstream, binary, indent + ); + + if (pNormalDiff.size()>0) { + FBX::Node::WritePropertyNode( + "Normals", pNormalDiff, outstream, binary, indent + ); + } + } + indent--; + bsnode.End(outstream, binary, indent, true); + + // Add blendshape Channel Deformer + FBX::Node sdnode("Deformer"); + const int64_t blendchannel_uid = generate_uid(); + sdnode.AddProperties( + blendchannel_uid, blendshape_name + FBX::SEPARATOR + "SubDeformer", "BlendShapeChannel" + ); + sdnode.AddChild("Version", int32_t(100)); + sdnode.AddChild("DeformPercent", float(0.0)); + FBX::Node p("Properties70"); + p.AddP70numberA("DeformPercent", 0.0); + sdnode.AddChild(p); + // TODO: Normally just one weight per channel, adding stub for later development + std::vector<float>fFullWeights; + fFullWeights.push_back(100.); + sdnode.AddChild("FullWeights", fFullWeights); + sdnode.Dump(outstream, binary, indent); + + connections.emplace_back("C", "OO", blendchannel_uid, deformer_uid); + connections.emplace_back("C", "OO", blendshape_uid, blendchannel_uid); + } + } + + // bones. + // + // output structure: + // subset of node hierarchy that are "skeleton", + // i.e. do not have meshes but only bones. + // but.. i'm not sure how anyone could guarantee that... + // + // input... + // well, for each mesh it has "bones", + // and the bone names correspond to nodes. + // of course we also need the parent nodes, + // as they give some of the transform........ + // + // well. we can assume a sane input, i suppose. + // + // so input is the bone node hierarchy, + // with an extra thing for the transformation of the MESH in BONE space. + // + // output is a set of bone nodes, + // a "bindpose" which indicates the default local transform of all bones, + // and a set of "deformers". + // each deformer is parented to a mesh geometry, + // and has one or more "subdeformer"s as children. + // each subdeformer has one bone node as a child, + // and represents the influence of that bone on the grandparent mesh. + // the subdeformer has a list of indices, and weights, + // with indices specifying vertex indices, + // and weights specifying the corresponding influence of this bone. + // it also has Transform and TransformLink elements, + // specifying the transform of the MESH in BONE space, + // and the transformation of the BONE in WORLD space, + // likely in the bindpose. + // + // the input bone structure is different but similar, + // storing the number of weights for this bone, + // and an array of (vertex index, weight) pairs. + // + // one sticky point is that the number of vertices may not match, + // because assimp splits vertices by normal, uv, etc. + + // functor for aiNode sorting + struct SortNodeByName + { + bool operator()(const aiNode *lhs, const aiNode *rhs) const + { + return strcmp(lhs->mName.C_Str(), rhs->mName.C_Str()) < 0; + } + }; + + // first we should mark the skeleton for each mesh. + // the skeleton must include not only the aiBones, + // but also all their parent nodes. + // anything that affects the position of any bone node must be included. + // Use SorNodeByName to make sure the exported result will be the same across all systems + // Otherwise the aiNodes of the skeleton would be sorted based on the pointer address, which isn't consistent + std::vector<std::set<const aiNode*, SortNodeByName>> skeleton_by_mesh(mScene->mNumMeshes); + // at the same time we can build a list of all the skeleton nodes, + // which will be used later to mark them as type "limbNode". + std::unordered_set<const aiNode*> limbnodes; + + //actual bone nodes in fbx, without parenting-up + std::unordered_set<std::string> setAllBoneNamesInScene; + for(unsigned int m = 0; m < mScene->mNumMeshes; ++ m) + { + aiMesh* pMesh = mScene->mMeshes[m]; + for(unsigned int b = 0; b < pMesh->mNumBones; ++ b) + setAllBoneNamesInScene.insert(pMesh->mBones[b]->mName.data); + } + aiMatrix4x4 mxTransIdentity; + + // and a map of nodes by bone name, as finding them is annoying. + std::map<std::string,aiNode*> node_by_bone; + for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) { + const aiMesh* m = mScene->mMeshes[mi]; + std::set<const aiNode*, SortNodeByName> skeleton; + for (size_t bi =0; bi < m->mNumBones; ++bi) { + const aiBone* b = m->mBones[bi]; + const std::string name(b->mName.C_Str()); + auto elem = node_by_bone.find(name); + aiNode* n; + if (elem != node_by_bone.end()) { + n = elem->second; + } else { + n = mScene->mRootNode->FindNode(b->mName); + if (!n) { + // this should never happen + std::stringstream err; + err << "Failed to find node for bone: \"" << name << "\""; + throw DeadlyExportError(err.str()); + } + node_by_bone[name] = n; + limbnodes.insert(n); + } + skeleton.insert(n); + // mark all parent nodes as skeleton as well, + // up until we find the root node, + // or else the node containing the mesh, + // or else the parent of a node containing the mesh. + for ( + const aiNode* parent = n->mParent; + parent && parent != mScene->mRootNode; + parent = parent->mParent + ) { + // if we've already done this node we can skip it all + if (skeleton.count(parent)) { + break; + } + // ignore fbx transform nodes as these will be collapsed later + // TODO: cache this by aiNode* + const std::string node_name(parent->mName.C_Str()); + if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) { + continue; + } + //not a bone in scene && no effect in transform + if(setAllBoneNamesInScene.find(node_name)==setAllBoneNamesInScene.end() + && parent->mTransformation == mxTransIdentity) { + continue; + } + // otherwise check if this is the root of the skeleton + bool end = false; + // is the mesh part of this node? + for (size_t i = 0; i < parent->mNumMeshes; ++i) { + if (parent->mMeshes[i] == mi) { + end = true; + break; + } + } + // is the mesh in one of the children of this node? + for (size_t j = 0; j < parent->mNumChildren; ++j) { + aiNode* child = parent->mChildren[j]; + for (size_t i = 0; i < child->mNumMeshes; ++i) { + if (child->mMeshes[i] == mi) { + end = true; + break; + } + } + if (end) { break; } + } + + // if it was the skeleton root we can finish here + if (end) { break; } + } + } + skeleton_by_mesh[mi] = skeleton; + } + + // we'll need the uids for the bone nodes, so generate them now + for (size_t i = 0; i < mScene->mNumMeshes; ++i) { + auto &s = skeleton_by_mesh[i]; + for (const aiNode* n : s) { + auto elem = node_uids.find(n); + if (elem == node_uids.end()) { + node_uids[n] = generate_uid(); + } + } + } + + // now, for each aiMesh, we need to export a deformer, + // and for each aiBone a subdeformer, + // which should have all the skinning info. + // these will need to be connected properly to the mesh, + // and we can do that all now. + for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) { + const aiMesh* m = mScene->mMeshes[mi]; + if (!m->HasBones()) { + continue; + } + // make a deformer for this mesh + int64_t deformer_uid = generate_uid(); + FBX::Node dnode("Deformer"); + dnode.AddProperties(deformer_uid, FBX::SEPARATOR + "Deformer", "Skin"); + dnode.AddChild("Version", int32_t(101)); + // "acuracy"... this is not a typo.... + dnode.AddChild("Link_DeformAcuracy", double(50)); + dnode.AddChild("SkinningType", "Linear"); // TODO: other modes? + dnode.Dump(outstream, binary, indent); + + // connect it + connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]); + + //computed before + std::vector<int32_t>& vertex_indices = vVertexIndice[mi]; + + // TODO, FIXME: this won't work if anything is not in the bind pose. + // for now if such a situation is detected, we throw an exception. + std::set<const aiBone*> not_in_bind_pose; + std::set<const aiNode*> no_offset_matrix; + + // first get this mesh's position in world space, + // as we'll need it for each subdeformer. + // + // ...of course taking the position of the MESH doesn't make sense, + // as it can be instanced to many nodes. + // All we can do is assume no instancing, + // and take the first node we find that contains the mesh. + aiNode* mesh_node = get_node_for_mesh((unsigned int)mi, mScene->mRootNode); + aiMatrix4x4 mesh_xform = get_world_transform(mesh_node, mScene); + + // now make a subdeformer for each bone in the skeleton + const std::set<const aiNode*, SortNodeByName> skeleton= skeleton_by_mesh[mi]; + for (const aiNode* bone_node : skeleton) { + // if there's a bone for this node, find it + const aiBone* b = nullptr; + for (size_t bi = 0; bi < m->mNumBones; ++bi) { + // TODO: this probably should index by something else + const std::string name(m->mBones[bi]->mName.C_Str()); + if (node_by_bone[name] == bone_node) { + b = m->mBones[bi]; + break; + } + } + if (!b) { + no_offset_matrix.insert(bone_node); + } + + // start the subdeformer node + const int64_t subdeformer_uid = generate_uid(); + FBX::Node sdnode("Deformer"); + sdnode.AddProperties( + subdeformer_uid, FBX::SEPARATOR + "SubDeformer", "Cluster" + ); + sdnode.AddChild("Version", int32_t(100)); + sdnode.AddChild("UserData", "", ""); + + std::set<int32_t> setWeightedVertex; + // add indices and weights, if any + if (b) { + std::vector<int32_t> subdef_indices; + std::vector<double> subdef_weights; + int32_t last_index = -1; + for (size_t wi = 0; wi < b->mNumWeights; ++wi) { + int32_t vi = vertex_indices[b->mWeights[wi].mVertexId]; + bool bIsWeightedAlready = (setWeightedVertex.find(vi) != setWeightedVertex.end()); + if (vi == last_index || bIsWeightedAlready) { + // only for vertices we exported to fbx + // TODO, FIXME: this assumes identically-located vertices + // will always deform in the same way. + // as assimp doesn't store a separate list of "positions", + // there's not much that can be done about this + // other than assuming that identical position means + // identical vertex. + continue; + } + setWeightedVertex.insert(vi); + subdef_indices.push_back(vi); + subdef_weights.push_back(b->mWeights[wi].mWeight); + last_index = vi; + } + // yes, "indexes" + sdnode.AddChild("Indexes", subdef_indices); + sdnode.AddChild("Weights", subdef_weights); + } + + // transform is the transform of the mesh, but in bone space. + // if the skeleton is in the bind pose, + // we can take the inverse of the world-space bone transform + // and multiply by the world-space transform of the mesh. + aiMatrix4x4 bone_xform = get_world_transform(bone_node, mScene); + aiMatrix4x4 inverse_bone_xform = bone_xform; + inverse_bone_xform.Inverse(); + aiMatrix4x4 tr = inverse_bone_xform * mesh_xform; + + sdnode.AddChild("Transform", tr); + + + sdnode.AddChild("TransformLink", bone_xform); + // note: this means we ALWAYS rely on the mesh node transform + // being unchanged from the time the skeleton was bound. + // there's not really any way around this at the moment. + + // done + sdnode.Dump(outstream, binary, indent); + + // lastly, connect to the parent deformer + connections.emplace_back( + "C", "OO", subdeformer_uid, deformer_uid + ); + + // we also need to connect the limb node to the subdeformer. + connections.emplace_back( + "C", "OO", node_uids[bone_node], subdeformer_uid + ); + } + + // if we cannot create a valid FBX file, simply die. + // this will both prevent unnecessary bug reports, + // and tell the user what they can do to fix the situation + // (i.e. export their model in the bind pose). + if (no_offset_matrix.size() && not_in_bind_pose.size()) { + std::stringstream err; + err << "Not enough information to construct bind pose"; + err << " for mesh " << mi << "!"; + err << " Transform matrix for bone \""; + err << (*not_in_bind_pose.begin())->mName.C_Str() << "\""; + if (not_in_bind_pose.size() > 1) { + err << " (and " << not_in_bind_pose.size() - 1 << " more)"; + } + err << " does not match mOffsetMatrix,"; + err << " and node \""; + err << (*no_offset_matrix.begin())->mName.C_Str() << "\""; + if (no_offset_matrix.size() > 1) { + err << " (and " << no_offset_matrix.size() - 1 << " more)"; + } + err << " has no offset matrix to rely on."; + err << " Please ensure bones are in the bind pose to export."; + throw DeadlyExportError(err.str()); + } + + } + + // BindPose + // + // This is a legacy system, which should be unnecessary. + // + // Somehow including it slows file loading by the official FBX SDK, + // and as it can reconstruct it from the deformers anyway, + // this is not currently included. + // + // The code is kept here in case it's useful in the future, + // but it's pretty much a hack anyway, + // as assimp doesn't store bindpose information for full skeletons. + // + /*for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) { + aiMesh* mesh = mScene->mMeshes[mi]; + if (! mesh->HasBones()) { continue; } + int64_t bindpose_uid = generate_uid(); + FBX::Node bpnode("Pose"); + bpnode.AddProperty(bindpose_uid); + // note: this uid is never linked or connected to anything. + bpnode.AddProperty(FBX::SEPARATOR + "Pose"); // blank name + bpnode.AddProperty("BindPose"); + + bpnode.AddChild("Type", "BindPose"); + bpnode.AddChild("Version", int32_t(100)); + + aiNode* mesh_node = get_node_for_mesh(mi, mScene->mRootNode); + + // next get the whole skeleton for this mesh. + // we need it all to define the bindpose section. + // the FBX SDK will complain if it's missing, + // and also if parents of used bones don't have a subdeformer. + // order shouldn't matter. + std::set<aiNode*> skeleton; + for (size_t bi = 0; bi < mesh->mNumBones; ++bi) { + // bone node should have already been indexed + const aiBone* b = mesh->mBones[bi]; + const std::string bone_name(b->mName.C_Str()); + aiNode* parent = node_by_bone[bone_name]; + // insert all nodes down to the root or mesh node + while ( + parent + && parent != mScene->mRootNode + && parent != mesh_node + ) { + skeleton.insert(parent); + parent = parent->mParent; + } + } + + // number of pose nodes. includes one for the mesh itself. + bpnode.AddChild("NbPoseNodes", int32_t(1 + skeleton.size())); + + // the first pose node is always the mesh itself + FBX::Node pose("PoseNode"); + pose.AddChild("Node", mesh_uids[mi]); + aiMatrix4x4 mesh_node_xform = get_world_transform(mesh_node, mScene); + pose.AddChild("Matrix", mesh_node_xform); + bpnode.AddChild(pose); + + for (aiNode* bonenode : skeleton) { + // does this node have a uid yet? + int64_t node_uid; + auto node_uid_iter = node_uids.find(bonenode); + if (node_uid_iter != node_uids.end()) { + node_uid = node_uid_iter->second; + } else { + node_uid = generate_uid(); + node_uids[bonenode] = node_uid; + } + + // make a pose thingy + pose = FBX::Node("PoseNode"); + pose.AddChild("Node", node_uid); + aiMatrix4x4 node_xform = get_world_transform(bonenode, mScene); + pose.AddChild("Matrix", node_xform); + bpnode.AddChild(pose); + } + + // now write it + bpnode.Dump(outstream, binary, indent); + }*/ + + // lights + indent = 1; + lights_uids.clear(); + for (size_t li = 0; li < mScene->mNumLights; ++li) { + aiLight* l = mScene->mLights[li]; + + int64_t uid = generate_uid(); + const std::string lightNodeAttributeName = l->mName.C_Str() + FBX::SEPARATOR + "NodeAttribute"; + + FBX::Node lna("NodeAttribute"); + lna.AddProperties(uid, lightNodeAttributeName, "Light"); + FBX::Node lnap("Properties70"); + + // Light color. + lnap.AddP70colorA("Color", l->mColorDiffuse.r, l->mColorDiffuse.g, l->mColorDiffuse.b); + + // TODO Assimp light description is quite concise and do not handle light intensity. + // Default value to 1000W. + lnap.AddP70numberA("Intensity", 1000); + + // FBXLight::EType conversion + switch (l->mType) { + case aiLightSource_POINT: + lnap.AddP70enum("LightType", 0); + break; + case aiLightSource_DIRECTIONAL: + lnap.AddP70enum("LightType", 1); + break; + case aiLightSource_SPOT: + lnap.AddP70enum("LightType", 2); + lnap.AddP70numberA("InnerAngle", AI_RAD_TO_DEG(l->mAngleInnerCone)); + lnap.AddP70numberA("OuterAngle", AI_RAD_TO_DEG(l->mAngleOuterCone)); + break; + // TODO Assimp do not handle 'area' nor 'volume' lights, but FBX does. + /*case aiLightSource_AREA: + lnap.AddP70enum("LightType", 3); + lnap.AddP70enum("AreaLightShape", 0); // 0=Rectangle, 1=Sphere + break; + case aiLightSource_VOLUME: + lnap.AddP70enum("LightType", 4); + break;*/ + default: + break; + } + + // Did not understood how to configure the decay so disabling attenuation. + lnap.AddP70enum("DecayType", 0); + + // Dump to FBX stream + lna.AddChild(lnap); + lna.AddChild("TypeFlags", FBX::FBXExportProperty("Light")); + lna.AddChild("GeometryVersion", FBX::FBXExportProperty(int32_t(124))); + lna.Dump(outstream, binary, indent); + + // Store name and uid (will be used later when parsing scene nodes) + lights_uids[l->mName.C_Str()] = uid; + } + + // TODO: cameras + + // write nodes (i.e. model hierarchy) + // start at root node + WriteModelNodes( + outstream, mScene->mRootNode, 0, limbnodes + ); + + // animations + // + // in FBX there are: + // * AnimationStack - corresponds to an aiAnimation + // * AnimationLayer - a combinable animation component + // * AnimationCurveNode - links the property to be animated + // * AnimationCurve - defines animation data for a single property value + // + // the CurveNode also provides the default value for a property, + // such as the X, Y, Z coordinates for animatable translation. + // + // the Curve only specifies values for one component of the property, + // so there will be a separate AnimationCurve for X, Y, and Z. + // + // Assimp has: + // * aiAnimation - basically corresponds to an AnimationStack + // * aiNodeAnim - defines all animation for one aiNode + // * aiVectorKey/aiQuatKey - define the keyframe data for T/R/S + // + // assimp has no equivalent for AnimationLayer, + // and these are flattened on FBX import. + // we can assume there will be one per AnimationStack. + // + // the aiNodeAnim contains all animation data for a single aiNode, + // which will correspond to three AnimationCurveNode's: + // one each for translation, rotation and scale. + // The data for each of these will be put in 9 AnimationCurve's, + // T.X, T.Y, T.Z, R.X, R.Y, R.Z, etc. + + // AnimationStack / aiAnimation + std::vector<int64_t> animation_stack_uids(mScene->mNumAnimations); + for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) { + int64_t animstack_uid = generate_uid(); + animation_stack_uids[ai] = animstack_uid; + const aiAnimation* anim = mScene->mAnimations[ai]; + + FBX::Node asnode("AnimationStack"); + std::string name = anim->mName.C_Str() + FBX::SEPARATOR + "AnimStack"; + asnode.AddProperties(animstack_uid, name, ""); + FBX::Node p("Properties70"); + p.AddP70time("LocalStart", 0); // assimp doesn't store this + p.AddP70time("LocalStop", to_ktime(anim->mDuration, anim)); + p.AddP70time("ReferenceStart", 0); + p.AddP70time("ReferenceStop", to_ktime(anim->mDuration, anim)); + asnode.AddChild(p); + + // this node absurdly always pretends it has children + // (in this case it does, but just in case...) + asnode.force_has_children = true; + asnode.Dump(outstream, binary, indent); + + // note: animation stacks are not connected to anything + } + + // AnimationLayer - one per aiAnimation + std::vector<int64_t> animation_layer_uids(mScene->mNumAnimations); + for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) { + int64_t animlayer_uid = generate_uid(); + animation_layer_uids[ai] = animlayer_uid; + FBX::Node alnode("AnimationLayer"); + alnode.AddProperties(animlayer_uid, FBX::SEPARATOR + "AnimLayer", ""); + + // this node absurdly always pretends it has children + alnode.force_has_children = true; + alnode.Dump(outstream, binary, indent); + + // connect to the relevant animstack + connections.emplace_back( + "C", "OO", animlayer_uid, animation_stack_uids[ai] + ); + } + + // AnimCurveNode - three per aiNodeAnim + std::vector<std::vector<std::array<int64_t,3>>> curve_node_uids; + for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) { + const aiAnimation* anim = mScene->mAnimations[ai]; + const int64_t layer_uid = animation_layer_uids[ai]; + std::vector<std::array<int64_t,3>> nodeanim_uids; + for (size_t nai = 0; nai < anim->mNumChannels; ++nai) { + const aiNodeAnim* na = anim->mChannels[nai]; + // get the corresponding aiNode + const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName); + // and its transform + const aiMatrix4x4 node_xfm = get_world_transform(node, mScene); + aiVector3D T, R, S; + node_xfm.Decompose(S, R, T); + + // AnimationCurveNode uids + std::array<int64_t,3> ids; + ids[0] = generate_uid(); // T + ids[1] = generate_uid(); // R + ids[2] = generate_uid(); // S + + // translation + WriteAnimationCurveNode(outstream, + ids[0], "T", T, "Lcl Translation", + layer_uid, node_uids[node] + ); + + // rotation + WriteAnimationCurveNode(outstream, + ids[1], "R", R, "Lcl Rotation", + layer_uid, node_uids[node] + ); + + // scale + WriteAnimationCurveNode(outstream, + ids[2], "S", S, "Lcl Scale", + layer_uid, node_uids[node] + ); + + // store the uids for later use + nodeanim_uids.push_back(ids); + } + curve_node_uids.push_back(nodeanim_uids); + } + + // AnimCurve - defines actual keyframe data. + // there's a separate curve for every component of every vector, + // for example a transform curvenode will have separate X/Y/Z AnimCurve's + for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) { + const aiAnimation* anim = mScene->mAnimations[ai]; + for (size_t nai = 0; nai < anim->mNumChannels; ++nai) { + const aiNodeAnim* na = anim->mChannels[nai]; + // get the corresponding aiNode + const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName); + // and its transform + const aiMatrix4x4 node_xfm = get_world_transform(node, mScene); + aiVector3D T, R, S; + node_xfm.Decompose(S, R, T); + const std::array<int64_t,3>& ids = curve_node_uids[ai][nai]; + + std::vector<int64_t> times; + std::vector<float> xval, yval, zval; + + // position/translation + for (size_t ki = 0; ki < na->mNumPositionKeys; ++ki) { + const aiVectorKey& k = na->mPositionKeys[ki]; + times.push_back(to_ktime(k.mTime)); + xval.push_back(k.mValue.x); + yval.push_back(k.mValue.y); + zval.push_back(k.mValue.z); + } + // one curve each for X, Y, Z + WriteAnimationCurve(outstream, T.x, times, xval, ids[0], "d|X"); + WriteAnimationCurve(outstream, T.y, times, yval, ids[0], "d|Y"); + WriteAnimationCurve(outstream, T.z, times, zval, ids[0], "d|Z"); + + // rotation + times.clear(); xval.clear(); yval.clear(); zval.clear(); + for (size_t ki = 0; ki < na->mNumRotationKeys; ++ki) { + const aiQuatKey& k = na->mRotationKeys[ki]; + times.push_back(to_ktime(k.mTime)); + // TODO: aiQuaternion method to convert to Euler... + aiMatrix4x4 m(k.mValue.GetMatrix()); + aiVector3D qs, qr, qt; + m.Decompose(qs, qr, qt); + qr *= DEG; + xval.push_back(qr.x); + yval.push_back(qr.y); + zval.push_back(qr.z); + } + WriteAnimationCurve(outstream, R.x, times, xval, ids[1], "d|X"); + WriteAnimationCurve(outstream, R.y, times, yval, ids[1], "d|Y"); + WriteAnimationCurve(outstream, R.z, times, zval, ids[1], "d|Z"); + + // scaling/scale + times.clear(); xval.clear(); yval.clear(); zval.clear(); + for (size_t ki = 0; ki < na->mNumScalingKeys; ++ki) { + const aiVectorKey& k = na->mScalingKeys[ki]; + times.push_back(to_ktime(k.mTime)); + xval.push_back(k.mValue.x); + yval.push_back(k.mValue.y); + zval.push_back(k.mValue.z); + } + WriteAnimationCurve(outstream, S.x, times, xval, ids[2], "d|X"); + WriteAnimationCurve(outstream, S.y, times, yval, ids[2], "d|Y"); + WriteAnimationCurve(outstream, S.z, times, zval, ids[2], "d|Z"); + } + } + + indent = 0; + object_node.End(outstream, binary, indent, true); +} + +// convenience map of magic node name strings to FBX properties, +// including the expected type of transform. +const std::map<std::string,std::pair<std::string,char>> transform_types = { + {"Translation", {"Lcl Translation", 't'}}, + {"RotationOffset", {"RotationOffset", 't'}}, + {"RotationPivot", {"RotationPivot", 't'}}, + {"PreRotation", {"PreRotation", 'r'}}, + {"Rotation", {"Lcl Rotation", 'r'}}, + {"PostRotation", {"PostRotation", 'r'}}, + {"RotationPivotInverse", {"RotationPivotInverse", 'i'}}, + {"ScalingOffset", {"ScalingOffset", 't'}}, + {"ScalingPivot", {"ScalingPivot", 't'}}, + {"Scaling", {"Lcl Scaling", 's'}}, + {"ScalingPivotInverse", {"ScalingPivotInverse", 'i'}}, + {"GeometricScaling", {"GeometricScaling", 's'}}, + {"GeometricRotation", {"GeometricRotation", 'r'}}, + {"GeometricTranslation", {"GeometricTranslation", 't'}}, + {"GeometricTranslationInverse", {"GeometricTranslationInverse", 'i'}}, + {"GeometricRotationInverse", {"GeometricRotationInverse", 'i'}}, + {"GeometricScalingInverse", {"GeometricScalingInverse", 'i'}} +}; + +// write a single model node to the stream +void FBXExporter::WriteModelNode( + StreamWriterLE& outstream, + bool, + const aiNode* node, + int64_t node_uid, + const std::string& type, + const std::vector<std::pair<std::string,aiVector3D>>& transform_chain, + TransformInheritance inherit_type +){ + const aiVector3D zero = {0, 0, 0}; + const aiVector3D one = {1, 1, 1}; + FBX::Node m("Model"); + std::string name = node->mName.C_Str() + FBX::SEPARATOR + "Model"; + m.AddProperties(node_uid, name, type); + m.AddChild("Version", int32_t(232)); + FBX::Node p("Properties70"); + p.AddP70bool("RotationActive", 1); + p.AddP70int("DefaultAttributeIndex", 0); + p.AddP70enum("InheritType", inherit_type); + if (transform_chain.empty()) { + // decompose 4x4 transform matrix into TRS + aiVector3D t, r, s; + node->mTransformation.Decompose(s, r, t); + if (t != zero) { + p.AddP70( + "Lcl Translation", "Lcl Translation", "", "A", + double(t.x), double(t.y), double(t.z) + ); + } + if (r != zero) { + p.AddP70( + "Lcl Rotation", "Lcl Rotation", "", "A", + double(DEG*r.x), double(DEG*r.y), double(DEG*r.z) + ); + } + if (s != one) { + p.AddP70( + "Lcl Scaling", "Lcl Scaling", "", "A", + double(s.x), double(s.y), double(s.z) + ); + } + } else { + // apply the transformation chain. + // these transformation elements are created when importing FBX, + // which has a complex transformation hierarchy for each node. + // as such we can bake the hierarchy back into the node on export. + for (auto &item : transform_chain) { + auto elem = transform_types.find(item.first); + if (elem == transform_types.end()) { + // then this is a bug + std::stringstream err; + err << "unrecognized FBX transformation type: "; + err << item.first; + throw DeadlyExportError(err.str()); + } + const std::string &cur_name = elem->second.first; + const aiVector3D &v = item.second; + if (cur_name.compare(0, 4, "Lcl ") == 0) { + // special handling for animatable properties + p.AddP70( cur_name, cur_name, "", "A", double(v.x), double(v.y), double(v.z) ); + } else { + p.AddP70vector(cur_name, v.x, v.y, v.z); + } + } + } + m.AddChild(p); + + // not sure what these are for, + // but they seem to be omnipresent + m.AddChild("Shading", FBXExportProperty(true)); + m.AddChild("Culling", FBXExportProperty("CullingOff")); + + m.Dump(outstream, binary, 1); +} + +// wrapper for WriteModelNodes to create and pass a blank transform chain +void FBXExporter::WriteModelNodes( + StreamWriterLE& s, + const aiNode* node, + int64_t parent_uid, + const std::unordered_set<const aiNode*>& limbnodes +) { + std::vector<std::pair<std::string,aiVector3D>> chain; + WriteModelNodes(s, node, parent_uid, limbnodes, chain); +} + +void FBXExporter::WriteModelNodes( + StreamWriterLE& outstream, + const aiNode* node, + int64_t parent_uid, + const std::unordered_set<const aiNode*>& limbnodes, + std::vector<std::pair<std::string,aiVector3D>>& transform_chain +) { + // first collapse any expanded transformation chains created by FBX import. + std::string node_name(node->mName.C_Str()); + if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) { + auto pos = node_name.find(MAGIC_NODE_TAG) + MAGIC_NODE_TAG.size() + 1; + std::string type_name = node_name.substr(pos); + auto elem = transform_types.find(type_name); + if (elem == transform_types.end()) { + // then this is a bug and should be fixed + std::stringstream err; + err << "unrecognized FBX transformation node"; + err << " of type " << type_name << " in node " << node_name; + throw DeadlyExportError(err.str()); + } + aiVector3D t, r, s; + node->mTransformation.Decompose(s, r, t); + switch (elem->second.second) { + case 'i': // inverse + // we don't need to worry about the inverse matrices + break; + case 't': // translation + transform_chain.emplace_back(elem->first, t); + break; + case 'r': // rotation + r *= float(DEG); + transform_chain.emplace_back(elem->first, r); + break; + case 's': // scale + transform_chain.emplace_back(elem->first, s); + break; + default: + // this should never happen + std::stringstream err; + err << "unrecognized FBX transformation type code: "; + err << elem->second.second; + throw DeadlyExportError(err.str()); + } + // now continue on to any child nodes + for (unsigned i = 0; i < node->mNumChildren; ++i) { + WriteModelNodes( + outstream, + node->mChildren[i], + parent_uid, + limbnodes, + transform_chain + ); + } + return; + } + + int64_t node_uid = 0; + // generate uid and connect to parent, if not the root node, + if (node != mScene->mRootNode) { + auto elem = node_uids.find(node); + if (elem != node_uids.end()) { + node_uid = elem->second; + } else { + node_uid = generate_uid(); + node_uids[node] = node_uid; + } + connections.emplace_back("C", "OO", node_uid, parent_uid); + } + + // what type of node is this? + if (node == mScene->mRootNode) { + // handled later + } else if (node->mNumMeshes == 1) { + // connect to child mesh, which should have been written previously + connections.emplace_back( + "C", "OO", mesh_uids[node->mMeshes[0]], node_uid + ); + // also connect to the material for the child mesh + connections.emplace_back( + "C", "OO", + material_uids[mScene->mMeshes[node->mMeshes[0]]->mMaterialIndex], + node_uid + ); + // write model node + WriteModelNode( + outstream, binary, node, node_uid, "Mesh", transform_chain + ); + } else if (limbnodes.count(node)) { + WriteModelNode( + outstream, binary, node, node_uid, "LimbNode", transform_chain + ); + // we also need to write a nodeattribute to mark it as a skeleton + int64_t node_attribute_uid = generate_uid(); + FBX::Node na("NodeAttribute"); + na.AddProperties( + node_attribute_uid, FBX::SEPARATOR + "NodeAttribute", "LimbNode" + ); + na.AddChild("TypeFlags", FBXExportProperty("Skeleton")); + na.Dump(outstream, binary, 1); + // and connect them + connections.emplace_back("C", "OO", node_attribute_uid, node_uid); + } else { + const auto& lightIt = lights_uids.find(node->mName.C_Str()); + if(lightIt != lights_uids.end()) { + // Node has a light connected to it. + WriteModelNode( + outstream, binary, node, node_uid, "Light", transform_chain + ); + connections.emplace_back("C", "OO", lightIt->second, node_uid); + } else { + // generate a null node so we can add children to it + WriteModelNode( + outstream, binary, node, node_uid, "Null", transform_chain + ); + } + } + + // if more than one child mesh, make nodes for each mesh + if (node->mNumMeshes > 1 || node == mScene->mRootNode) { + for (size_t i = 0; i < node->mNumMeshes; ++i) { + // make a new model node + int64_t new_node_uid = generate_uid(); + // connect to parent node + connections.emplace_back("C", "OO", new_node_uid, node_uid); + // connect to child mesh, which should have been written previously + connections.emplace_back( + "C", "OO", mesh_uids[node->mMeshes[i]], new_node_uid + ); + // also connect to the material for the child mesh + connections.emplace_back( + "C", "OO", + material_uids[ + mScene->mMeshes[node->mMeshes[i]]->mMaterialIndex + ], + new_node_uid + ); + + aiNode new_node; + // take name from mesh name, if it exists + new_node.mName = mScene->mMeshes[node->mMeshes[i]]->mName; + // write model node + WriteModelNode( + outstream, binary, &new_node, new_node_uid, "Mesh", std::vector<std::pair<std::string,aiVector3D>>() + ); + } + } + + // now recurse into children + for (size_t i = 0; i < node->mNumChildren; ++i) { + WriteModelNodes( + outstream, node->mChildren[i], node_uid, limbnodes + ); + } +} + +void FBXExporter::WriteAnimationCurveNode( + StreamWriterLE &outstream, + int64_t uid, + const std::string &name, // "T", "R", or "S" + aiVector3D default_value, + const std::string &property_name, // "Lcl Translation" etc + int64_t layer_uid, + int64_t node_uid) { + FBX::Node n("AnimationCurveNode"); + n.AddProperties(uid, name + FBX::SEPARATOR + "AnimCurveNode", ""); + FBX::Node p("Properties70"); + p.AddP70numberA("d|X", default_value.x); + p.AddP70numberA("d|Y", default_value.y); + p.AddP70numberA("d|Z", default_value.z); + n.AddChild(p); + n.Dump(outstream, binary, 1); + // connect to layer + this->connections.emplace_back("C", "OO", uid, layer_uid); + // connect to bone + this->connections.emplace_back("C", "OP", uid, node_uid, property_name); +} + +void FBXExporter::WriteAnimationCurve( + StreamWriterLE& outstream, + double default_value, + const std::vector<int64_t>& times, + const std::vector<float>& values, + int64_t curvenode_uid, + const std::string& property_link // "d|X", "d|Y", etc +) { + FBX::Node n("AnimationCurve"); + int64_t curve_uid = generate_uid(); + n.AddProperties(curve_uid, FBX::SEPARATOR + "AnimCurve", ""); + n.AddChild("Default", default_value); + n.AddChild("KeyVer", int32_t(4009)); + n.AddChild("KeyTime", times); + n.AddChild("KeyValueFloat", values); + // TODO: keyattr flags and data (STUB for now) + n.AddChild("KeyAttrFlags", std::vector<int32_t>{0}); + n.AddChild("KeyAttrDataFloat", std::vector<float>{0,0,0,0}); + n.AddChild( + "KeyAttrRefCount", + std::vector<int32_t>{static_cast<int32_t>(times.size())} + ); + n.Dump(outstream, binary, 1); + this->connections.emplace_back( + "C", "OP", curve_uid, curvenode_uid, property_link + ); +} + + +void FBXExporter::WriteConnections () +{ + // we should have completed the connection graph already, + // so basically just dump it here + if (!binary) { + WriteAsciiSectionHeader("Object connections"); + } + // TODO: comments with names in the ascii version + FBX::Node conn("Connections"); + StreamWriterLE outstream(outfile); + conn.Begin(outstream, binary, 0); + conn.BeginChildren(outstream, binary, 0); + for (auto &n : connections) { + n.Dump(outstream, binary, 1); + } + conn.End(outstream, binary, 0, !connections.empty()); + connections.clear(); +} + +#endif // ASSIMP_BUILD_NO_FBX_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/FBX/FBXExporter.h b/libs/assimp/code/AssetLib/FBX/FBXExporter.h new file mode 100644 index 0000000..659f936 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXExporter.h @@ -0,0 +1,177 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXExporter.h +* Declares the exporter class to write a scene to an fbx file +*/ +#ifndef AI_FBXEXPORTER_H_INC +#define AI_FBXEXPORTER_H_INC + +#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER + +#include "FBXExportNode.h" // FBX::Node +#include "FBXCommon.h" // FBX::TransformInheritance + +#include <assimp/types.h> +//#include <assimp/material.h> +#include <assimp/StreamWriter.h> // StreamWriterLE +#include <assimp/Exceptional.h> // DeadlyExportError + +#include <vector> +#include <map> +#include <unordered_set> +#include <memory> // shared_ptr +#include <sstream> // stringstream + +struct aiScene; +struct aiNode; +struct aiLight; + +namespace Assimp { + class IOSystem; + class IOStream; + class ExportProperties; + + // --------------------------------------------------------------------- + /** Helper class to export a given scene to an FBX file. */ + // --------------------------------------------------------------------- + class FBXExporter + { + public: + /// Constructor for a specific scene to export + FBXExporter(const aiScene* pScene, const ExportProperties* pProperties); + + // call one of these methods to export + void ExportBinary(const char* pFile, IOSystem* pIOSystem); + void ExportAscii(const char* pFile, IOSystem* pIOSystem); + + private: + bool binary; // whether current export is in binary or ascii format + const aiScene* mScene; // the scene to export + const ExportProperties* mProperties; // currently unused + std::shared_ptr<IOStream> outfile; // file to write to + + std::vector<FBX::Node> connections; // connection storage + + std::vector<int64_t> mesh_uids; + std::vector<int64_t> material_uids; + std::map<const aiNode*,int64_t> node_uids; + std::map<std::string,int64_t> lights_uids; + + // this crude unique-ID system is actually fine + int64_t last_uid = 999999; + int64_t generate_uid() { return ++last_uid; } + + // binary files have a specific header and footer, + // in addition to the actual data + void WriteBinaryHeader(); + void WriteBinaryFooter(); + + // ascii files have a comment at the top + void WriteAsciiHeader(); + + // WriteAllNodes does the actual export. + // It just calls all the Write<Section> methods below in order. + void WriteAllNodes(); + + // Methods to write individual sections. + // The order here matches the order inside an FBX file. + // Each method corresponds to a top-level FBX section, + // except WriteHeader which also includes some binary-only sections + // and WriteFooter which is binary data only. + void WriteHeaderExtension(); + // WriteFileId(); // binary-only, included in WriteHeader + // WriteCreationTime(); // binary-only, included in WriteHeader + // WriteCreator(); // binary-only, included in WriteHeader + void WriteGlobalSettings(); + void WriteDocuments(); + void WriteReferences(); + void WriteDefinitions(); + void WriteObjects(); + void WriteConnections(); + // WriteTakes(); // deprecated since at least 2015 (fbx 7.4) + + // helpers + void WriteAsciiSectionHeader(const std::string& title); + void WriteModelNodes( + Assimp::StreamWriterLE& s, + const aiNode* node, + int64_t parent_uid, + const std::unordered_set<const aiNode*>& limbnodes + ); + void WriteModelNodes( // usually don't call this directly + StreamWriterLE& s, + const aiNode* node, + int64_t parent_uid, + const std::unordered_set<const aiNode*>& limbnodes, + std::vector<std::pair<std::string,aiVector3D>>& transform_chain + ); + void WriteModelNode( // nor this + StreamWriterLE& s, + bool binary, + const aiNode* node, + int64_t node_uid, + const std::string& type, + const std::vector<std::pair<std::string,aiVector3D>>& xfm_chain, + FBX::TransformInheritance ti_type=FBX::TransformInheritance_RSrs + ); + void WriteAnimationCurveNode( + StreamWriterLE &outstream, + int64_t uid, + const std::string &name, // "T", "R", or "S" + aiVector3D default_value, + const std::string &property_name, // "Lcl Translation" etc + int64_t animation_layer_uid, + int64_t node_uid); + void WriteAnimationCurve( + StreamWriterLE& outstream, + double default_value, + const std::vector<int64_t>& times, + const std::vector<float>& values, + int64_t curvenode_id, + const std::string& property_link // "d|X", "d|Y", etc + ); + }; +} + +#endif // ASSIMP_BUILD_NO_FBX_EXPORTER + +#endif // AI_FBXEXPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/FBX/FBXImportSettings.h b/libs/assimp/code/AssetLib/FBX/FBXImportSettings.h new file mode 100644 index 0000000..90e64bf --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXImportSettings.h @@ -0,0 +1,158 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXImportSettings.h + * @brief FBX importer runtime configuration + */ +#ifndef INCLUDED_AI_FBX_IMPORTSETTINGS_H +#define INCLUDED_AI_FBX_IMPORTSETTINGS_H + +namespace Assimp { +namespace FBX { + +/** FBX import settings, parts of which are publicly accessible via their corresponding AI_CONFIG constants */ +struct ImportSettings { + ImportSettings() : + strictMode(true), + readAllLayers(true), + readAllMaterials(false), + readMaterials(true), + readTextures(true), + readCameras(true), + readLights(true), + readAnimations(true), + readWeights(true), + preservePivots(true), + optimizeEmptyAnimationCurves(true), + useLegacyEmbeddedTextureNaming(false), + removeEmptyBones(true), + convertToMeters(false) { + // empty + } + + /** enable strict mode: + * - only accept fbx 2012, 2013 files + * - on the slightest error, give up. + * + * Basically, strict mode means that the fbx file will actually + * be validated. Strict mode is off by default. */ + bool strictMode; + + /** specifies whether all geometry layers are read and scanned for + * usable data channels. The FBX spec indicates that many readers + * will only read the first channel and that this is in some way + * the recommended way- in reality, however, it happens a lot that + * vertex data is spread among multiple layers. The default + * value for this option is true.*/ + bool readAllLayers; + + /** specifies whether all materials are read, or only those that + * are referenced by at least one mesh. Reading all materials + * may make FBX reading a lot slower since all objects + * need to be processed . + * This bit is ignored unless readMaterials=true*/ + bool readAllMaterials; + + /** import materials (true) or skip them and assign a default + * material. The default value is true.*/ + bool readMaterials; + + /** import embedded textures? Default value is true.*/ + bool readTextures; + + /** import cameras? Default value is true.*/ + bool readCameras; + + /** import light sources? Default value is true.*/ + bool readLights; + + /** import animations (i.e. animation curves, the node + * skeleton is always imported). Default value is true. */ + bool readAnimations; + + /** read bones (vertex weights and deform info). + * Default value is true. */ + bool readWeights; + + /** preserve transformation pivots and offsets. Since these can + * not directly be represented in assimp, additional dummy + * nodes will be generated. Note that settings this to false + * can make animation import a lot slower. The default value + * is true. + * + * The naming scheme for the generated nodes is: + * <OriginalName>_$AssimpFbx$_<TransformName> + * + * where <TransformName> is one of + * RotationPivot + * RotationOffset + * PreRotation + * PostRotation + * ScalingPivot + * ScalingOffset + * Translation + * Scaling + * Rotation + **/ + bool preservePivots; + + /** do not import animation curves that specify a constant + * values matching the corresponding node transformation. + * The default value is true. */ + bool optimizeEmptyAnimationCurves; + + /** use legacy naming for embedded textures eg: (*0, *1, *2) + */ + bool useLegacyEmbeddedTextureNaming; + + /** Empty bones shall be removed + */ + bool removeEmptyBones; + + /** Set to true to perform a conversion from cm to meter after the import + */ + bool convertToMeters; +}; + +} // namespace FBX +} // namespace Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/FBX/FBXImporter.cpp b/libs/assimp/code/AssetLib/FBX/FBXImporter.cpp new file mode 100644 index 0000000..0f63acc --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXImporter.cpp @@ -0,0 +1,200 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. +r +* 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 FBXImporter.cpp + * @brief Implementation of the FBX importer. + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +#include "FBXImporter.h" + +#include "FBXConverter.h" +#include "FBXDocument.h" +#include "FBXParser.h" +#include "FBXTokenizer.h" +#include "FBXUtil.h" + +#include <assimp/MemoryIOWrapper.h> +#include <assimp/StreamReader.h> +#include <assimp/importerdesc.h> +#include <assimp/Importer.hpp> + +namespace Assimp { + +template <> +const char *LogFunctions<FBXImporter>::Prefix() { + static auto prefix = "FBX: "; + return prefix; +} + +} // namespace Assimp + +using namespace Assimp; +using namespace Assimp::Formatter; +using namespace Assimp::FBX; + +namespace { + +static const aiImporterDesc desc = { + "Autodesk FBX Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "fbx" +}; +} + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by #Importer +FBXImporter::FBXImporter() { +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +FBXImporter::~FBXImporter() { +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool FBXImporter::CanRead(const std::string & pFile, IOSystem * pIOHandler, bool /*checkSig*/) const { + // at least ASCII-FBX files usually have a 'FBX' somewhere in their head + static const char *tokens[] = { "fbx" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// List all extensions handled by this loader +const aiImporterDesc *FBXImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties for the loader +void FBXImporter::SetupProperties(const Importer *pImp) { + settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true); + settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false); + settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true); + settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true); + settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true); + settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true); + settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true); + settings.readWeights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_WEIGHTS, true); + settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false); + settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true); + settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true); + settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false); + settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true); + settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false); +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + auto streamCloser = [&](IOStream *pStream) { + pIOHandler->Close(pStream); + }; + std::unique_ptr<IOStream, decltype(streamCloser)> stream(pIOHandler->Open(pFile, "rb"), streamCloser); + if (!stream) { + ThrowException("Could not open file for reading"); + } + + ASSIMP_LOG_DEBUG("Reading FBX file"); + + // read entire file into memory - no streaming for this, fbx + // files can grow large, but the assimp output data structure + // then becomes very large, too. Assimp doesn't support + // streaming for its output data structures so the net win with + // streaming input data would be very low. + std::vector<char> contents; + contents.resize(stream->FileSize() + 1); + stream->Read(&*contents.begin(), 1, contents.size() - 1); + contents[contents.size() - 1] = 0; + const char *const begin = &*contents.begin(); + + // broadphase tokenizing pass in which we identify the core + // syntax elements of FBX (brackets, commas, key:value mappings) + TokenList tokens; + try { + + bool is_binary = false; + if (!strncmp(begin, "Kaydara FBX Binary", 18)) { + is_binary = true; + TokenizeBinary(tokens, begin, contents.size()); + } else { + Tokenize(tokens, begin); + } + + // use this information to construct a very rudimentary + // parse-tree representing the FBX scope structure + Parser parser(tokens, is_binary); + + // take the raw parse-tree and convert it to a FBX DOM + Document doc(parser, settings); + + // convert the FBX DOM to aiScene + ConvertToAssimpScene(pScene, doc, settings.removeEmptyBones); + + // size relative to cm + float size_relative_to_cm = doc.GlobalSettings().UnitScaleFactor(); + if (size_relative_to_cm == 0.0) + { + // BaseImporter later asserts that fileScale is non-zero. + ThrowException("The UnitScaleFactor must be non-zero"); + } + + // Set FBX file scale is relative to CM must be converted to M for + // assimp universal format (M) + SetFileScale(size_relative_to_cm * 0.01f); + + std::for_each(tokens.begin(), tokens.end(), Util::delete_fun<Token>()); + } catch (std::exception &) { + std::for_each(tokens.begin(), tokens.end(), Util::delete_fun<Token>()); + throw; + } +} + +#endif // !ASSIMP_BUILD_NO_FBX_IMPORTER diff --git a/libs/assimp/code/AssetLib/FBX/FBXImporter.h b/libs/assimp/code/AssetLib/FBX/FBXImporter.h new file mode 100644 index 0000000..a212efe --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXImporter.h @@ -0,0 +1,98 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXImporter.h + * @brief Declaration of the FBX main importer class + */ +#ifndef INCLUDED_AI_FBX_IMPORTER_H +#define INCLUDED_AI_FBX_IMPORTER_H + +#include <assimp/BaseImporter.h> +#include <assimp/LogAux.h> + +#include "FBXImportSettings.h" + +namespace Assimp { + +// TinyFormatter.h +namespace Formatter { + +template <typename T, typename TR, typename A> +class basic_formatter; + +typedef class basic_formatter<char, std::char_traits<char>, std::allocator<char>> format; + +} // namespace Formatter + +// ------------------------------------------------------------------------------------------- +/// Loads the Autodesk FBX file format. +/// +/// See http://en.wikipedia.org/wiki/FBX +// ------------------------------------------------------------------------------------------- +class FBXImporter : public BaseImporter, public LogFunctions<FBXImporter> { +public: + FBXImporter(); + ~FBXImporter() override; + + // -------------------- + bool CanRead(const std::string &pFile, + IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // -------------------- + const aiImporterDesc *GetInfo() const override; + + // -------------------- + void SetupProperties(const Importer *pImp) override; + + // -------------------- + void InternReadFile(const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler) override; + +private: + FBX::ImportSettings settings; +}; // !class FBXImporter + +} // end of namespace Assimp + +#endif // !INCLUDED_AI_FBX_IMPORTER_H diff --git a/libs/assimp/code/AssetLib/FBX/FBXMaterial.cpp b/libs/assimp/code/AssetLib/FBX/FBXMaterial.cpp new file mode 100644 index 0000000..8849179 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXMaterial.cpp @@ -0,0 +1,376 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXMaterial.cpp + * @brief Assimp::FBX::Material and Assimp::FBX::Texture implementation + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +#include "FBXParser.h" +#include "FBXDocument.h" +#include "FBXImporter.h" +#include "FBXImportSettings.h" +#include "FBXDocumentUtil.h" +#include "FBXProperties.h" +#include <assimp/ByteSwapper.h> +#include <assimp/ParsingUtils.h> + +#include "FBXUtil.h" + +namespace Assimp { +namespace FBX { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +Material::Material(uint64_t id, const Element& element, const Document& doc, const std::string& name) : + Object(id,element,name) { + const Scope& sc = GetRequiredScope(element); + + const Element* const ShadingModel = sc["ShadingModel"]; + const Element* const MultiLayer = sc["MultiLayer"]; + + if(MultiLayer) { + multilayer = !!ParseTokenAsInt(GetRequiredToken(*MultiLayer,0)); + } + + if(ShadingModel) { + shading = ParseTokenAsString(GetRequiredToken(*ShadingModel,0)); + } else { + DOMWarning("shading mode not specified, assuming phong",&element); + shading = "phong"; + } + + // lower-case shading because Blender (for example) writes "Phong" + for (size_t i = 0; i < shading.length(); ++i) { + shading[i] = static_cast<char>(tolower(static_cast<unsigned char>(shading[i]))); + } + std::string templateName; + if(shading == "phong") { + templateName = "Material.FbxSurfacePhong"; + } else if(shading == "lambert") { + templateName = "Material.FbxSurfaceLambert"; + } else { + DOMWarning("shading mode not recognized: " + shading,&element); + } + + props = GetPropertyTable(doc,templateName,element,sc); + + // resolve texture links + const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID()); + for(const Connection* con : conns) { + // texture link to properties, not objects + if ( 0 == con->PropertyName().length()) { + continue; + } + + const Object* const ob = con->SourceObject(); + if(nullptr == ob) { + DOMWarning("failed to read source object for texture link, ignoring",&element); + continue; + } + + const Texture* const tex = dynamic_cast<const Texture*>(ob); + if(nullptr == tex) { + const LayeredTexture* const layeredTexture = dynamic_cast<const LayeredTexture*>(ob); + if(!layeredTexture) { + DOMWarning("source object for texture link is not a texture or layered texture, ignoring",&element); + continue; + } + const std::string& prop = con->PropertyName(); + if (layeredTextures.find(prop) != layeredTextures.end()) { + DOMWarning("duplicate layered texture link: " + prop,&element); + } + + layeredTextures[prop] = layeredTexture; + ((LayeredTexture*)layeredTexture)->fillTexture(doc); + } else { + const std::string& prop = con->PropertyName(); + if (textures.find(prop) != textures.end()) { + DOMWarning("duplicate texture link: " + prop,&element); + } + + textures[prop] = tex; + } + } +} + + +// ------------------------------------------------------------------------------------------------ +Material::~Material() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +Texture::Texture(uint64_t id, const Element& element, const Document& doc, const std::string& name) : + Object(id,element,name), + uvScaling(1.0f,1.0f), + media(0) { + const Scope& sc = GetRequiredScope(element); + + const Element* const Type = sc["Type"]; + const Element* const FileName = sc["FileName"]; + const Element* const RelativeFilename = sc["RelativeFilename"]; + const Element* const ModelUVTranslation = sc["ModelUVTranslation"]; + const Element* const ModelUVScaling = sc["ModelUVScaling"]; + const Element* const Texture_Alpha_Source = sc["Texture_Alpha_Source"]; + const Element* const Cropping = sc["Cropping"]; + + if(Type) { + type = ParseTokenAsString(GetRequiredToken(*Type,0)); + } + + if(FileName) { + fileName = ParseTokenAsString(GetRequiredToken(*FileName,0)); + } + + if(RelativeFilename) { + relativeFileName = ParseTokenAsString(GetRequiredToken(*RelativeFilename,0)); + } + + if(ModelUVTranslation) { + uvTrans = aiVector2D(ParseTokenAsFloat(GetRequiredToken(*ModelUVTranslation,0)), + ParseTokenAsFloat(GetRequiredToken(*ModelUVTranslation,1)) + ); + } + + if(ModelUVScaling) { + uvScaling = aiVector2D(ParseTokenAsFloat(GetRequiredToken(*ModelUVScaling,0)), + ParseTokenAsFloat(GetRequiredToken(*ModelUVScaling,1)) + ); + } + + if(Cropping) { + crop[0] = ParseTokenAsInt(GetRequiredToken(*Cropping,0)); + crop[1] = ParseTokenAsInt(GetRequiredToken(*Cropping,1)); + crop[2] = ParseTokenAsInt(GetRequiredToken(*Cropping,2)); + crop[3] = ParseTokenAsInt(GetRequiredToken(*Cropping,3)); + } else { + // vc8 doesn't support the crop() syntax in initialization lists + // (and vc9 WARNS about the new (i.e. compliant) behaviour). + crop[0] = crop[1] = crop[2] = crop[3] = 0; + } + + if(Texture_Alpha_Source) { + alphaSource = ParseTokenAsString(GetRequiredToken(*Texture_Alpha_Source,0)); + } + + props = GetPropertyTable(doc,"Texture.FbxFileTexture",element,sc); + + // 3DS Max and FBX SDK use "Scaling" and "Translation" instead of "ModelUVScaling" and "ModelUVTranslation". Use these properties if available. + bool ok; + const aiVector3D& scaling = PropertyGet<aiVector3D>(*props, "Scaling", ok); + if (ok) { + uvScaling.x = scaling.x; + uvScaling.y = scaling.y; + } + + const aiVector3D& trans = PropertyGet<aiVector3D>(*props, "Translation", ok); + if (ok) { + uvTrans.x = trans.x; + uvTrans.y = trans.y; + } + + const aiVector3D &rotation = PropertyGet<aiVector3D>(*props, "Rotation", ok); + if (ok) { + uvRotation = rotation.z; + } + + // resolve video links + if(doc.Settings().readTextures) { + const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID()); + for(const Connection* con : conns) { + const Object* const ob = con->SourceObject(); + if (nullptr == ob) { + DOMWarning("failed to read source object for texture link, ignoring",&element); + continue; + } + + const Video* const video = dynamic_cast<const Video*>(ob); + if(video) { + media = video; + } + } + } +} + + +Texture::~Texture() { + // empty +} + +LayeredTexture::LayeredTexture(uint64_t id, const Element& element, const Document& /*doc*/, const std::string& name) : + Object(id,element,name), + blendMode(BlendMode_Modulate), + alpha(1) { + const Scope& sc = GetRequiredScope(element); + + const Element* const BlendModes = sc["BlendModes"]; + const Element* const Alphas = sc["Alphas"]; + + if (nullptr != BlendModes) { + blendMode = (BlendMode)ParseTokenAsInt(GetRequiredToken(*BlendModes,0)); + } + if (nullptr != Alphas) { + alpha = ParseTokenAsFloat(GetRequiredToken(*Alphas,0)); + } +} + +LayeredTexture::~LayeredTexture() { + // empty +} + +void LayeredTexture::fillTexture(const Document& doc) { + const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID()); + for(size_t i = 0; i < conns.size();++i) { + const Connection* con = conns.at(i); + + const Object* const ob = con->SourceObject(); + if (nullptr == ob) { + DOMWarning("failed to read source object for texture link, ignoring",&element); + continue; + } + + const Texture* const tex = dynamic_cast<const Texture*>(ob); + + textures.push_back(tex); + } +} + +// ------------------------------------------------------------------------------------------------ +Video::Video(uint64_t id, const Element& element, const Document& doc, const std::string& name) : + Object(id,element,name), + contentLength(0), + content(0) { + const Scope& sc = GetRequiredScope(element); + + const Element* const Type = sc["Type"]; + const Element* const FileName = sc.FindElementCaseInsensitive("FileName"); //some files retain the information as "Filename", others "FileName", who knows + const Element* const RelativeFilename = sc["RelativeFilename"]; + const Element* const Content = sc["Content"]; + + if(Type) { + type = ParseTokenAsString(GetRequiredToken(*Type,0)); + } + + if(FileName) { + fileName = ParseTokenAsString(GetRequiredToken(*FileName,0)); + } + + if(RelativeFilename) { + relativeFileName = ParseTokenAsString(GetRequiredToken(*RelativeFilename,0)); + } + + if(Content && !Content->Tokens().empty()) { + //this field is omitted when the embedded texture is already loaded, let's ignore if it's not found + try { + const Token& token = GetRequiredToken(*Content, 0); + const char* data = token.begin(); + if (!token.IsBinary()) { + if (*data != '"') { + DOMError("embedded content is not surrounded by quotation marks", &element); + } else { + size_t targetLength = 0; + auto numTokens = Content->Tokens().size(); + // First time compute size (it could be large like 64Gb and it is good to allocate it once) + for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx) { + const Token& dataToken = GetRequiredToken(*Content, tokenIdx); + size_t tokenLength = dataToken.end() - dataToken.begin() - 2; // ignore double quotes + const char* base64data = dataToken.begin() + 1; + const size_t outLength = Util::ComputeDecodedSizeBase64(base64data, tokenLength); + if (outLength == 0) { + DOMError("Corrupted embedded content found", &element); + } + targetLength += outLength; + } + if (targetLength == 0) { + DOMError("Corrupted embedded content found", &element); + } + content = new uint8_t[targetLength]; + contentLength = static_cast<uint64_t>(targetLength); + size_t dst_offset = 0; + for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx) { + const Token& dataToken = GetRequiredToken(*Content, tokenIdx); + size_t tokenLength = dataToken.end() - dataToken.begin() - 2; // ignore double quotes + const char* base64data = dataToken.begin() + 1; + dst_offset += Util::DecodeBase64(base64data, tokenLength, content + dst_offset, targetLength - dst_offset); + } + if (targetLength != dst_offset) { + delete[] content; + contentLength = 0; + DOMError("Corrupted embedded content found", &element); + } + } + } else if (static_cast<size_t>(token.end() - data) < 5) { + DOMError("binary data array is too short, need five (5) bytes for type signature and element count", &element); + } else if (*data != 'R') { + DOMWarning("video content is not raw binary data, ignoring", &element); + } else { + // read number of elements + uint32_t len = 0; + ::memcpy(&len, data + 1, sizeof(len)); + AI_SWAP4(len); + + contentLength = len; + + content = new uint8_t[len]; + ::memcpy(content, data + 5, len); + } + } catch (const runtime_error& runtimeError) { + //we don't need the content data for contents that has already been loaded + ASSIMP_LOG_VERBOSE_DEBUG("Caught exception in FBXMaterial (likely because content was already loaded): ", + runtimeError.what()); + } + } + + props = GetPropertyTable(doc,"Video.FbxVideo",element,sc); +} + + +Video::~Video() { + delete[] content; +} + +} //!FBX +} //!Assimp + +#endif // ASSIMP_BUILD_NO_FBX_IMPORTER diff --git a/libs/assimp/code/AssetLib/FBX/FBXMeshGeometry.cpp b/libs/assimp/code/AssetLib/FBX/FBXMeshGeometry.cpp new file mode 100644 index 0000000..1f92fa1 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXMeshGeometry.cpp @@ -0,0 +1,728 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXMeshGeometry.cpp + * @brief Assimp::FBX::MeshGeometry implementation + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +#include <functional> + +#include "FBXMeshGeometry.h" +#include "FBXDocument.h" +#include "FBXImporter.h" +#include "FBXImportSettings.h" +#include "FBXDocumentUtil.h" + + +namespace Assimp { +namespace FBX { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +Geometry::Geometry(uint64_t id, const Element& element, const std::string& name, const Document& doc) + : Object(id, element, name) + , skin() +{ + const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID(),"Deformer"); + for(const Connection* con : conns) { + const Skin* const sk = ProcessSimpleConnection<Skin>(*con, false, "Skin -> Geometry", element); + if(sk) { + skin = sk; + } + const BlendShape* const bsp = ProcessSimpleConnection<BlendShape>(*con, false, "BlendShape -> Geometry", element); + if (bsp) { + blendShapes.push_back(bsp); + } + } +} + +// ------------------------------------------------------------------------------------------------ +Geometry::~Geometry() +{ + // empty +} + +// ------------------------------------------------------------------------------------------------ +const std::vector<const BlendShape*>& Geometry::GetBlendShapes() const { + return blendShapes; +} + +// ------------------------------------------------------------------------------------------------ +const Skin* Geometry::DeformerSkin() const { + return skin; +} + +// ------------------------------------------------------------------------------------------------ +MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc) +: Geometry(id, element,name, doc) +{ + const Scope* sc = element.Compound(); + if (!sc) { + DOMError("failed to read Geometry object (class: Mesh), no data scope found"); + } + + // must have Mesh elements: + const Element& Vertices = GetRequiredElement(*sc,"Vertices",&element); + const Element& PolygonVertexIndex = GetRequiredElement(*sc,"PolygonVertexIndex",&element); + + // optional Mesh elements: + const ElementCollection& Layer = sc->GetCollection("Layer"); + + std::vector<aiVector3D> tempVerts; + ParseVectorDataArray(tempVerts,Vertices); + + if(tempVerts.empty()) { + FBXImporter::LogWarn("encountered mesh with no vertices"); + } + + std::vector<int> tempFaces; + ParseVectorDataArray(tempFaces,PolygonVertexIndex); + + if(tempFaces.empty()) { + FBXImporter::LogWarn("encountered mesh with no faces"); + } + + m_vertices.reserve(tempFaces.size()); + m_faces.reserve(tempFaces.size() / 3); + + m_mapping_offsets.resize(tempVerts.size()); + m_mapping_counts.resize(tempVerts.size(),0); + m_mappings.resize(tempFaces.size()); + + const size_t vertex_count = tempVerts.size(); + + // generate output vertices, computing an adjacency table to + // preserve the mapping from fbx indices to *this* indexing. + unsigned int count = 0; + for(int index : tempFaces) { + const int absi = index < 0 ? (-index - 1) : index; + if(static_cast<size_t>(absi) >= vertex_count) { + DOMError("polygon vertex index out of range",&PolygonVertexIndex); + } + + m_vertices.push_back(tempVerts[absi]); + ++count; + + ++m_mapping_counts[absi]; + + if (index < 0) { + m_faces.push_back(count); + count = 0; + } + } + + unsigned int cursor = 0; + for (size_t i = 0, e = tempVerts.size(); i < e; ++i) { + m_mapping_offsets[i] = cursor; + cursor += m_mapping_counts[i]; + + m_mapping_counts[i] = 0; + } + + cursor = 0; + for(int index : tempFaces) { + const int absi = index < 0 ? (-index - 1) : index; + m_mappings[m_mapping_offsets[absi] + m_mapping_counts[absi]++] = cursor++; + } + + // if settings.readAllLayers is true: + // * read all layers, try to load as many vertex channels as possible + // if settings.readAllLayers is false: + // * read only the layer with index 0, but warn about any further layers + for (ElementMap::const_iterator it = Layer.first; it != Layer.second; ++it) { + const TokenList& tokens = (*it).second->Tokens(); + + const char* err; + const int index = ParseTokenAsInt(*tokens[0], err); + if(err) { + DOMError(err,&element); + } + + if(doc.Settings().readAllLayers || index == 0) { + const Scope& layer = GetRequiredScope(*(*it).second); + ReadLayer(layer); + } + else { + FBXImporter::LogWarn("ignoring additional geometry layers"); + } + } +} + +// ------------------------------------------------------------------------------------------------ +MeshGeometry::~MeshGeometry() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +const std::vector<aiVector3D>& MeshGeometry::GetVertices() const { + return m_vertices; +} + +// ------------------------------------------------------------------------------------------------ +const std::vector<aiVector3D>& MeshGeometry::GetNormals() const { + return m_normals; +} + +// ------------------------------------------------------------------------------------------------ +const std::vector<aiVector3D>& MeshGeometry::GetTangents() const { + return m_tangents; +} + +// ------------------------------------------------------------------------------------------------ +const std::vector<aiVector3D>& MeshGeometry::GetBinormals() const { + return m_binormals; +} + +// ------------------------------------------------------------------------------------------------ +const std::vector<unsigned int>& MeshGeometry::GetFaceIndexCounts() const { + return m_faces; +} + +// ------------------------------------------------------------------------------------------------ +const std::vector<aiVector2D>& MeshGeometry::GetTextureCoords( unsigned int index ) const { + static const std::vector<aiVector2D> empty; + return index >= AI_MAX_NUMBER_OF_TEXTURECOORDS ? empty : m_uvs[ index ]; +} + +std::string MeshGeometry::GetTextureCoordChannelName( unsigned int index ) const { + return index >= AI_MAX_NUMBER_OF_TEXTURECOORDS ? "" : m_uvNames[ index ]; +} + +const std::vector<aiColor4D>& MeshGeometry::GetVertexColors( unsigned int index ) const { + static const std::vector<aiColor4D> empty; + return index >= AI_MAX_NUMBER_OF_COLOR_SETS ? empty : m_colors[ index ]; +} + +const MatIndexArray& MeshGeometry::GetMaterialIndices() const { + return m_materials; +} +// ------------------------------------------------------------------------------------------------ +const unsigned int* MeshGeometry::ToOutputVertexIndex( unsigned int in_index, unsigned int& count ) const { + if ( in_index >= m_mapping_counts.size() ) { + return nullptr; + } + + ai_assert( m_mapping_counts.size() == m_mapping_offsets.size() ); + count = m_mapping_counts[ in_index ]; + + ai_assert( m_mapping_offsets[ in_index ] + count <= m_mappings.size() ); + + return &m_mappings[ m_mapping_offsets[ in_index ] ]; +} + +// ------------------------------------------------------------------------------------------------ +unsigned int MeshGeometry::FaceForVertexIndex( unsigned int in_index ) const { + ai_assert( in_index < m_vertices.size() ); + + // in the current conversion pattern this will only be needed if + // weights are present, so no need to always pre-compute this table + if ( m_facesVertexStartIndices.empty() ) { + m_facesVertexStartIndices.resize( m_faces.size() + 1, 0 ); + + std::partial_sum( m_faces.begin(), m_faces.end(), m_facesVertexStartIndices.begin() + 1 ); + m_facesVertexStartIndices.pop_back(); + } + + ai_assert( m_facesVertexStartIndices.size() == m_faces.size() ); + const std::vector<unsigned int>::iterator it = std::upper_bound( + m_facesVertexStartIndices.begin(), + m_facesVertexStartIndices.end(), + in_index + ); + + return static_cast< unsigned int >( std::distance( m_facesVertexStartIndices.begin(), it - 1 ) ); +} + +// ------------------------------------------------------------------------------------------------ +void MeshGeometry::ReadLayer(const Scope& layer) +{ + const ElementCollection& LayerElement = layer.GetCollection("LayerElement"); + for (ElementMap::const_iterator eit = LayerElement.first; eit != LayerElement.second; ++eit) { + const Scope& elayer = GetRequiredScope(*(*eit).second); + + ReadLayerElement(elayer); + } +} + + +// ------------------------------------------------------------------------------------------------ +void MeshGeometry::ReadLayerElement(const Scope& layerElement) +{ + const Element& Type = GetRequiredElement(layerElement,"Type"); + const Element& TypedIndex = GetRequiredElement(layerElement,"TypedIndex"); + + const std::string& type = ParseTokenAsString(GetRequiredToken(Type,0)); + const int typedIndex = ParseTokenAsInt(GetRequiredToken(TypedIndex,0)); + + const Scope& top = GetRequiredScope(element); + const ElementCollection candidates = top.GetCollection(type); + + for (ElementMap::const_iterator it = candidates.first; it != candidates.second; ++it) { + const int index = ParseTokenAsInt(GetRequiredToken(*(*it).second,0)); + if(index == typedIndex) { + ReadVertexData(type,typedIndex,GetRequiredScope(*(*it).second)); + return; + } + } + + FBXImporter::LogError("failed to resolve vertex layer element: ", + type, ", index: ", typedIndex); +} + +// ------------------------------------------------------------------------------------------------ +void MeshGeometry::ReadVertexData(const std::string& type, int index, const Scope& source) +{ + const std::string& MappingInformationType = ParseTokenAsString(GetRequiredToken( + GetRequiredElement(source,"MappingInformationType"),0) + ); + + const std::string& ReferenceInformationType = ParseTokenAsString(GetRequiredToken( + GetRequiredElement(source,"ReferenceInformationType"),0) + ); + + if (type == "LayerElementUV") { + if(index >= AI_MAX_NUMBER_OF_TEXTURECOORDS) { + FBXImporter::LogError("ignoring UV layer, maximum number of UV channels exceeded: ", + index, " (limit is ", AI_MAX_NUMBER_OF_TEXTURECOORDS, ")" ); + return; + } + + const Element* Name = source["Name"]; + m_uvNames[index] = std::string(); + if(Name) { + m_uvNames[index] = ParseTokenAsString(GetRequiredToken(*Name,0)); + } + + ReadVertexDataUV(m_uvs[index],source, + MappingInformationType, + ReferenceInformationType + ); + } + else if (type == "LayerElementMaterial") { + if (m_materials.size() > 0) { + FBXImporter::LogError("ignoring additional material layer"); + return; + } + + std::vector<int> temp_materials; + + ReadVertexDataMaterials(temp_materials,source, + MappingInformationType, + ReferenceInformationType + ); + + // sometimes, there will be only negative entries. Drop the material + // layer in such a case (I guess it means a default material should + // be used). This is what the converter would do anyway, and it + // avoids losing the material if there are more material layers + // coming of which at least one contains actual data (did observe + // that with one test file). + const size_t count_neg = std::count_if(temp_materials.begin(),temp_materials.end(),[](int n) { return n < 0; }); + if(count_neg == temp_materials.size()) { + FBXImporter::LogWarn("ignoring dummy material layer (all entries -1)"); + return; + } + + std::swap(temp_materials, m_materials); + } + else if (type == "LayerElementNormal") { + if (m_normals.size() > 0) { + FBXImporter::LogError("ignoring additional normal layer"); + return; + } + + ReadVertexDataNormals(m_normals,source, + MappingInformationType, + ReferenceInformationType + ); + } + else if (type == "LayerElementTangent") { + if (m_tangents.size() > 0) { + FBXImporter::LogError("ignoring additional tangent layer"); + return; + } + + ReadVertexDataTangents(m_tangents,source, + MappingInformationType, + ReferenceInformationType + ); + } + else if (type == "LayerElementBinormal") { + if (m_binormals.size() > 0) { + FBXImporter::LogError("ignoring additional binormal layer"); + return; + } + + ReadVertexDataBinormals(m_binormals,source, + MappingInformationType, + ReferenceInformationType + ); + } + else if (type == "LayerElementColor") { + if(index >= AI_MAX_NUMBER_OF_COLOR_SETS) { + FBXImporter::LogError("ignoring vertex color layer, maximum number of color sets exceeded: ", + index, " (limit is ", AI_MAX_NUMBER_OF_COLOR_SETS, ")" ); + return; + } + + ReadVertexDataColors(m_colors[index],source, + MappingInformationType, + ReferenceInformationType + ); + } +} + +// ------------------------------------------------------------------------------------------------ +// Lengthy utility function to read and resolve a FBX vertex data array - that is, the +// output is in polygon vertex order. This logic is used for reading normals, UVs, colors, +// tangents .. +template <typename T> +void ResolveVertexDataArray(std::vector<T>& data_out, const Scope& source, + const std::string& MappingInformationType, + const std::string& ReferenceInformationType, + const char* dataElementName, + const char* indexDataElementName, + size_t vertex_count, + const std::vector<unsigned int>& mapping_counts, + const std::vector<unsigned int>& mapping_offsets, + const std::vector<unsigned int>& mappings) +{ + bool isDirect = ReferenceInformationType == "Direct"; + bool isIndexToDirect = ReferenceInformationType == "IndexToDirect"; + + // fall-back to direct data if there is no index data element + if ( isIndexToDirect && !HasElement( source, indexDataElementName ) ) { + isDirect = true; + isIndexToDirect = false; + } + + // handle permutations of Mapping and Reference type - it would be nice to + // deal with this more elegantly and with less redundancy, but right + // now it seems unavoidable. + if (MappingInformationType == "ByVertice" && isDirect) { + if (!HasElement(source, dataElementName)) { + return; + } + std::vector<T> tempData; + ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName)); + + if (tempData.size() != mapping_offsets.size()) { + FBXImporter::LogError("length of input data unexpected for ByVertice mapping: ", + tempData.size(), ", expected ", mapping_offsets.size()); + return; + } + + data_out.resize(vertex_count); + for (size_t i = 0, e = tempData.size(); i < e; ++i) { + const unsigned int istart = mapping_offsets[i], iend = istart + mapping_counts[i]; + for (unsigned int j = istart; j < iend; ++j) { + data_out[mappings[j]] = tempData[i]; + } + } + } + else if (MappingInformationType == "ByVertice" && isIndexToDirect) { + std::vector<T> tempData; + ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName)); + + std::vector<int> uvIndices; + ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName)); + + if (uvIndices.size() != vertex_count) { + FBXImporter::LogError("length of input data unexpected for ByVertice mapping: ", + uvIndices.size(), ", expected ", vertex_count); + return; + } + + data_out.resize(vertex_count); + + for (size_t i = 0, e = uvIndices.size(); i < e; ++i) { + + const unsigned int istart = mapping_offsets[i], iend = istart + mapping_counts[i]; + for (unsigned int j = istart; j < iend; ++j) { + if (static_cast<size_t>(uvIndices[i]) >= tempData.size()) { + DOMError("index out of range",&GetRequiredElement(source,indexDataElementName)); + } + data_out[mappings[j]] = tempData[uvIndices[i]]; + } + } + } + else if (MappingInformationType == "ByPolygonVertex" && isDirect) { + std::vector<T> tempData; + ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName)); + + if (tempData.size() != vertex_count) { + FBXImporter::LogError("length of input data unexpected for ByPolygon mapping: ", + tempData.size(), ", expected ", vertex_count + ); + return; + } + + data_out.swap(tempData); + } + else if (MappingInformationType == "ByPolygonVertex" && isIndexToDirect) { + std::vector<T> tempData; + ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName)); + + std::vector<int> uvIndices; + ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName)); + + if (uvIndices.size() > vertex_count) { + FBXImporter::LogWarn("trimming length of input array for ByPolygonVertex mapping: ", + uvIndices.size(), ", expected ", vertex_count); + uvIndices.resize(vertex_count); + } + + if (uvIndices.size() != vertex_count) { + FBXImporter::LogError("length of input data unexpected for ByPolygonVertex mapping: ", + uvIndices.size(), ", expected ", vertex_count); + return; + } + + data_out.resize(vertex_count); + + const T empty; + unsigned int next = 0; + for(int i : uvIndices) { + if ( -1 == i ) { + data_out[ next++ ] = empty; + continue; + } + if (static_cast<size_t>(i) >= tempData.size()) { + DOMError("index out of range",&GetRequiredElement(source,indexDataElementName)); + } + + data_out[next++] = tempData[i]; + } + } + else { + FBXImporter::LogError("ignoring vertex data channel, access type not implemented: ", + MappingInformationType, ",", ReferenceInformationType); + } +} + +// ------------------------------------------------------------------------------------------------ +void MeshGeometry::ReadVertexDataNormals(std::vector<aiVector3D>& normals_out, const Scope& source, + const std::string& MappingInformationType, + const std::string& ReferenceInformationType) +{ + ResolveVertexDataArray(normals_out,source,MappingInformationType,ReferenceInformationType, + "Normals", + "NormalsIndex", + m_vertices.size(), + m_mapping_counts, + m_mapping_offsets, + m_mappings); +} + +// ------------------------------------------------------------------------------------------------ +void MeshGeometry::ReadVertexDataUV(std::vector<aiVector2D>& uv_out, const Scope& source, + const std::string& MappingInformationType, + const std::string& ReferenceInformationType) +{ + ResolveVertexDataArray(uv_out,source,MappingInformationType,ReferenceInformationType, + "UV", + "UVIndex", + m_vertices.size(), + m_mapping_counts, + m_mapping_offsets, + m_mappings); +} + +// ------------------------------------------------------------------------------------------------ +void MeshGeometry::ReadVertexDataColors(std::vector<aiColor4D>& colors_out, const Scope& source, + const std::string& MappingInformationType, + const std::string& ReferenceInformationType) +{ + ResolveVertexDataArray(colors_out,source,MappingInformationType,ReferenceInformationType, + "Colors", + "ColorIndex", + m_vertices.size(), + m_mapping_counts, + m_mapping_offsets, + m_mappings); +} + +// ------------------------------------------------------------------------------------------------ +static const char *TangentIndexToken = "TangentIndex"; +static const char *TangentsIndexToken = "TangentsIndex"; + +void MeshGeometry::ReadVertexDataTangents(std::vector<aiVector3D>& tangents_out, const Scope& source, + const std::string& MappingInformationType, + const std::string& ReferenceInformationType) +{ + const char * str = source.Elements().count( "Tangents" ) > 0 ? "Tangents" : "Tangent"; + const char * strIdx = source.Elements().count( "Tangents" ) > 0 ? TangentsIndexToken : TangentIndexToken; + ResolveVertexDataArray(tangents_out,source,MappingInformationType,ReferenceInformationType, + str, + strIdx, + m_vertices.size(), + m_mapping_counts, + m_mapping_offsets, + m_mappings); +} + +// ------------------------------------------------------------------------------------------------ +static const char * BinormalIndexToken = "BinormalIndex"; +static const char * BinormalsIndexToken = "BinormalsIndex"; + +void MeshGeometry::ReadVertexDataBinormals(std::vector<aiVector3D>& binormals_out, const Scope& source, + const std::string& MappingInformationType, + const std::string& ReferenceInformationType) +{ + const char * str = source.Elements().count( "Binormals" ) > 0 ? "Binormals" : "Binormal"; + const char * strIdx = source.Elements().count( "Binormals" ) > 0 ? BinormalsIndexToken : BinormalIndexToken; + ResolveVertexDataArray(binormals_out,source,MappingInformationType,ReferenceInformationType, + str, + strIdx, + m_vertices.size(), + m_mapping_counts, + m_mapping_offsets, + m_mappings); +} + + +// ------------------------------------------------------------------------------------------------ +void MeshGeometry::ReadVertexDataMaterials(std::vector<int>& materials_out, const Scope& source, + const std::string& MappingInformationType, + const std::string& ReferenceInformationType) +{ + const size_t face_count = m_faces.size(); + if( 0 == face_count ) + { + return; + } + + // materials are handled separately. First of all, they are assigned per-face + // and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect + // has a slightly different meaning for materials. + ParseVectorDataArray(materials_out,GetRequiredElement(source,"Materials")); + + if (MappingInformationType == "AllSame") { + // easy - same material for all faces + if (materials_out.empty()) { + FBXImporter::LogError("expected material index, ignoring"); + return; + } else if (materials_out.size() > 1) { + FBXImporter::LogWarn("expected only a single material index, ignoring all except the first one"); + materials_out.clear(); + } + + materials_out.resize(m_vertices.size()); + std::fill(materials_out.begin(), materials_out.end(), materials_out.at(0)); + } else if (MappingInformationType == "ByPolygon" && ReferenceInformationType == "IndexToDirect") { + materials_out.resize(face_count); + + if(materials_out.size() != face_count) { + FBXImporter::LogError("length of input data unexpected for ByPolygon mapping: ", + materials_out.size(), ", expected ", face_count + ); + return; + } + } else { + FBXImporter::LogError("ignoring material assignments, access type not implemented: ", + MappingInformationType, ",", ReferenceInformationType); + } +} +// ------------------------------------------------------------------------------------------------ +ShapeGeometry::ShapeGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc) +: Geometry(id, element, name, doc) { + const Scope *sc = element.Compound(); + if (nullptr == sc) { + DOMError("failed to read Geometry object (class: Shape), no data scope found"); + } + const Element& Indexes = GetRequiredElement(*sc, "Indexes", &element); + const Element& Normals = GetRequiredElement(*sc, "Normals", &element); + const Element& Vertices = GetRequiredElement(*sc, "Vertices", &element); + ParseVectorDataArray(m_indices, Indexes); + ParseVectorDataArray(m_vertices, Vertices); + ParseVectorDataArray(m_normals, Normals); +} + +// ------------------------------------------------------------------------------------------------ +ShapeGeometry::~ShapeGeometry() { + // empty +} +// ------------------------------------------------------------------------------------------------ +const std::vector<aiVector3D>& ShapeGeometry::GetVertices() const { + return m_vertices; +} +// ------------------------------------------------------------------------------------------------ +const std::vector<aiVector3D>& ShapeGeometry::GetNormals() const { + return m_normals; +} +// ------------------------------------------------------------------------------------------------ +const std::vector<unsigned int>& ShapeGeometry::GetIndices() const { + return m_indices; +} +// ------------------------------------------------------------------------------------------------ +LineGeometry::LineGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc) + : Geometry(id, element, name, doc) +{ + const Scope* sc = element.Compound(); + if (!sc) { + DOMError("failed to read Geometry object (class: Line), no data scope found"); + } + const Element& Points = GetRequiredElement(*sc, "Points", &element); + const Element& PointsIndex = GetRequiredElement(*sc, "PointsIndex", &element); + ParseVectorDataArray(m_vertices, Points); + ParseVectorDataArray(m_indices, PointsIndex); +} + +// ------------------------------------------------------------------------------------------------ +LineGeometry::~LineGeometry() { + // empty +} +// ------------------------------------------------------------------------------------------------ +const std::vector<aiVector3D>& LineGeometry::GetVertices() const { + return m_vertices; +} +// ------------------------------------------------------------------------------------------------ +const std::vector<int>& LineGeometry::GetIndices() const { + return m_indices; +} +} // !FBX +} // !Assimp +#endif + diff --git a/libs/assimp/code/AssetLib/FBX/FBXMeshGeometry.h b/libs/assimp/code/AssetLib/FBX/FBXMeshGeometry.h new file mode 100644 index 0000000..3cca38a --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXMeshGeometry.h @@ -0,0 +1,235 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXImporter.h +* @brief Declaration of the FBX main importer class +*/ +#ifndef INCLUDED_AI_FBX_MESHGEOMETRY_H +#define INCLUDED_AI_FBX_MESHGEOMETRY_H + +#include "FBXParser.h" +#include "FBXDocument.h" + +namespace Assimp { +namespace FBX { + +/** + * DOM base class for all kinds of FBX geometry + */ +class Geometry : public Object +{ +public: + Geometry( uint64_t id, const Element& element, const std::string& name, const Document& doc ); + virtual ~Geometry(); + + /** Get the Skin attached to this geometry or nullptr */ + const Skin* DeformerSkin() const; + + /** Get the BlendShape attached to this geometry or nullptr */ + const std::vector<const BlendShape*>& GetBlendShapes() const; + +private: + const Skin* skin; + std::vector<const BlendShape*> blendShapes; + +}; + +typedef std::vector<int> MatIndexArray; + + +/** + * DOM class for FBX geometry of type "Mesh" + */ +class MeshGeometry : public Geometry +{ +public: + /** The class constructor */ + MeshGeometry( uint64_t id, const Element& element, const std::string& name, const Document& doc ); + + /** The class destructor */ + virtual ~MeshGeometry(); + + /** Get a list of all vertex points, non-unique*/ + const std::vector<aiVector3D>& GetVertices() const; + + /** Get a list of all vertex normals or an empty array if + * no normals are specified. */ + const std::vector<aiVector3D>& GetNormals() const; + + /** Get a list of all vertex tangents or an empty array + * if no tangents are specified */ + const std::vector<aiVector3D>& GetTangents() const; + + /** Get a list of all vertex bi-normals or an empty array + * if no bi-normals are specified */ + const std::vector<aiVector3D>& GetBinormals() const; + + /** Return list of faces - each entry denotes a face and specifies + * how many vertices it has. Vertices are taken from the + * vertex data arrays in sequential order. */ + const std::vector<unsigned int>& GetFaceIndexCounts() const; + + /** Get a UV coordinate slot, returns an empty array if + * the requested slot does not exist. */ + const std::vector<aiVector2D>& GetTextureCoords( unsigned int index ) const; + + /** Get a UV coordinate slot, returns an empty array if + * the requested slot does not exist. */ + std::string GetTextureCoordChannelName( unsigned int index ) const; + + /** Get a vertex color coordinate slot, returns an empty array if + * the requested slot does not exist. */ + const std::vector<aiColor4D>& GetVertexColors( unsigned int index ) const; + + /** Get per-face-vertex material assignments */ + const MatIndexArray& GetMaterialIndices() const; + + /** Convert from a fbx file vertex index (for example from a #Cluster weight) or nullptr + * if the vertex index is not valid. */ + const unsigned int* ToOutputVertexIndex( unsigned int in_index, unsigned int& count ) const; + + /** Determine the face to which a particular output vertex index belongs. + * This mapping is always unique. */ + unsigned int FaceForVertexIndex( unsigned int in_index ) const; +private: + void ReadLayer( const Scope& layer ); + void ReadLayerElement( const Scope& layerElement ); + void ReadVertexData( const std::string& type, int index, const Scope& source ); + + void ReadVertexDataUV( std::vector<aiVector2D>& uv_out, const Scope& source, + const std::string& MappingInformationType, + const std::string& ReferenceInformationType ); + + void ReadVertexDataNormals( std::vector<aiVector3D>& normals_out, const Scope& source, + const std::string& MappingInformationType, + const std::string& ReferenceInformationType ); + + void ReadVertexDataColors( std::vector<aiColor4D>& colors_out, const Scope& source, + const std::string& MappingInformationType, + const std::string& ReferenceInformationType ); + + void ReadVertexDataTangents( std::vector<aiVector3D>& tangents_out, const Scope& source, + const std::string& MappingInformationType, + const std::string& ReferenceInformationType ); + + void ReadVertexDataBinormals( std::vector<aiVector3D>& binormals_out, const Scope& source, + const std::string& MappingInformationType, + const std::string& ReferenceInformationType ); + + void ReadVertexDataMaterials( MatIndexArray& materials_out, const Scope& source, + const std::string& MappingInformationType, + const std::string& ReferenceInformationType ); + +private: + // cached data arrays + MatIndexArray m_materials; + std::vector<aiVector3D> m_vertices; + std::vector<unsigned int> m_faces; + mutable std::vector<unsigned int> m_facesVertexStartIndices; + std::vector<aiVector3D> m_tangents; + std::vector<aiVector3D> m_binormals; + std::vector<aiVector3D> m_normals; + + std::string m_uvNames[ AI_MAX_NUMBER_OF_TEXTURECOORDS ]; + std::vector<aiVector2D> m_uvs[ AI_MAX_NUMBER_OF_TEXTURECOORDS ]; + std::vector<aiColor4D> m_colors[ AI_MAX_NUMBER_OF_COLOR_SETS ]; + + std::vector<unsigned int> m_mapping_counts; + std::vector<unsigned int> m_mapping_offsets; + std::vector<unsigned int> m_mappings; +}; + +/** +* DOM class for FBX geometry of type "Shape" +*/ +class ShapeGeometry : public Geometry +{ +public: + /** The class constructor */ + ShapeGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc); + + /** The class destructor */ + virtual ~ShapeGeometry(); + + /** Get a list of all vertex points, non-unique*/ + const std::vector<aiVector3D>& GetVertices() const; + + /** Get a list of all vertex normals or an empty array if + * no normals are specified. */ + const std::vector<aiVector3D>& GetNormals() const; + + /** Return list of vertex indices. */ + const std::vector<unsigned int>& GetIndices() const; + +private: + std::vector<aiVector3D> m_vertices; + std::vector<aiVector3D> m_normals; + std::vector<unsigned int> m_indices; +}; +/** +* DOM class for FBX geometry of type "Line" +*/ +class LineGeometry : public Geometry +{ +public: + /** The class constructor */ + LineGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc); + + /** The class destructor */ + virtual ~LineGeometry(); + + /** Get a list of all vertex points, non-unique*/ + const std::vector<aiVector3D>& GetVertices() const; + + /** Return list of vertex indices. */ + const std::vector<int>& GetIndices() const; + +private: + std::vector<aiVector3D> m_vertices; + std::vector<int> m_indices; +}; + +} +} + +#endif // INCLUDED_AI_FBX_MESHGEOMETRY_H + diff --git a/libs/assimp/code/AssetLib/FBX/FBXModel.cpp b/libs/assimp/code/AssetLib/FBX/FBXModel.cpp new file mode 100644 index 0000000..9fe4cd5 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXModel.cpp @@ -0,0 +1,146 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXModel.cpp + * @brief Assimp::FBX::Model implementation + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +#include "FBXDocument.h" +#include "FBXDocumentUtil.h" +#include "FBXImporter.h" +#include "FBXMeshGeometry.h" +#include "FBXParser.h" + +namespace Assimp { +namespace FBX { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +Model::Model(uint64_t id, const Element &element, const Document &doc, const std::string &name) : + Object(id, element, name), shading("Y") { + const Scope &sc = GetRequiredScope(element); + const Element *const Shading = sc["Shading"]; + const Element *const Culling = sc["Culling"]; + + if (Shading) { + shading = GetRequiredToken(*Shading, 0).StringContents(); + } + + if (Culling) { + culling = ParseTokenAsString(GetRequiredToken(*Culling, 0)); + } + + props = GetPropertyTable(doc, "Model.FbxNode", element, sc); + ResolveLinks(element, doc); +} + +// ------------------------------------------------------------------------------------------------ +Model::~Model() { +} + +// ------------------------------------------------------------------------------------------------ +void Model::ResolveLinks(const Element&, const Document &doc) { + const char *const arr[] = { "Geometry", "Material", "NodeAttribute" }; + + // resolve material + const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), arr, 3); + + materials.reserve(conns.size()); + geometry.reserve(conns.size()); + attributes.reserve(conns.size()); + for (const Connection *con : conns) { + + // material and geometry links should be Object-Object connections + if (con->PropertyName().length()) { + continue; + } + + const Object *const ob = con->SourceObject(); + if (!ob) { + DOMWarning("failed to read source object for incoming Model link, ignoring", &element); + continue; + } + + const Material *const mat = dynamic_cast<const Material *>(ob); + if (mat) { + materials.push_back(mat); + continue; + } + + const Geometry *const geo = dynamic_cast<const Geometry *>(ob); + if (geo) { + geometry.push_back(geo); + continue; + } + + const NodeAttribute *const att = dynamic_cast<const NodeAttribute *>(ob); + if (att) { + attributes.push_back(att); + continue; + } + + DOMWarning("source object for model link is neither Material, NodeAttribute nor Geometry, ignoring", &element); + continue; + } +} + +// ------------------------------------------------------------------------------------------------ +bool Model::IsNull() const { + const std::vector<const NodeAttribute *> &attrs = GetAttributes(); + for (const NodeAttribute *att : attrs) { + + const Null *null_tag = dynamic_cast<const Null *>(att); + if (null_tag) { + return true; + } + } + + return false; +} + +} // namespace FBX +} // namespace Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/FBX/FBXNodeAttribute.cpp b/libs/assimp/code/AssetLib/FBX/FBXNodeAttribute.cpp new file mode 100644 index 0000000..a144f41 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXNodeAttribute.cpp @@ -0,0 +1,170 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXNoteAttribute.cpp + * @brief Assimp::FBX::NodeAttribute (and subclasses) implementation + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +#include "FBXParser.h" +#include "FBXDocument.h" +#include "FBXImporter.h" +#include "FBXDocumentUtil.h" + +namespace Assimp { +namespace FBX { + +using namespace Util; + +// ------------------------------------------------------------------------------------------------ +NodeAttribute::NodeAttribute(uint64_t id, const Element& element, const Document& doc, const std::string& name) +: Object(id,element,name) +, props() +{ + const Scope& sc = GetRequiredScope(element); + + const std::string& classname = ParseTokenAsString(GetRequiredToken(element,2)); + + // hack on the deriving type but Null/LimbNode attributes are the only case in which + // the property table is by design absent and no warning should be generated + // for it. + const bool is_null_or_limb = !strcmp(classname.c_str(), "Null") || !strcmp(classname.c_str(), "LimbNode"); + props = GetPropertyTable(doc,"NodeAttribute.Fbx" + classname,element,sc, is_null_or_limb); +} + + +// ------------------------------------------------------------------------------------------------ +NodeAttribute::~NodeAttribute() +{ + // empty +} + + +// ------------------------------------------------------------------------------------------------ +CameraSwitcher::CameraSwitcher(uint64_t id, const Element& element, const Document& doc, const std::string& name) + : NodeAttribute(id,element,doc,name) +{ + const Scope& sc = GetRequiredScope(element); + const Element* const CameraId = sc["CameraId"]; + const Element* const CameraName = sc["CameraName"]; + const Element* const CameraIndexName = sc["CameraIndexName"]; + + if(CameraId) { + cameraId = ParseTokenAsInt(GetRequiredToken(*CameraId,0)); + } + + if(CameraName) { + cameraName = GetRequiredToken(*CameraName,0).StringContents(); + } + + if(CameraIndexName && CameraIndexName->Tokens().size()) { + cameraIndexName = GetRequiredToken(*CameraIndexName,0).StringContents(); + } +} + +// ------------------------------------------------------------------------------------------------ +CameraSwitcher::~CameraSwitcher() +{ + // empty +} + +// ------------------------------------------------------------------------------------------------ +Camera::Camera(uint64_t id, const Element& element, const Document& doc, const std::string& name) +: NodeAttribute(id,element,doc,name) +{ + // empty +} + +// ------------------------------------------------------------------------------------------------ +Camera::~Camera() +{ + // empty +} + +// ------------------------------------------------------------------------------------------------ +Light::Light(uint64_t id, const Element& element, const Document& doc, const std::string& name) +: NodeAttribute(id,element,doc,name) +{ + // empty +} + + +// ------------------------------------------------------------------------------------------------ +Light::~Light() +{ +} + + +// ------------------------------------------------------------------------------------------------ +Null::Null(uint64_t id, const Element& element, const Document& doc, const std::string& name) +: NodeAttribute(id,element,doc,name) +{ + +} + + +// ------------------------------------------------------------------------------------------------ +Null::~Null() +{ + +} + + +// ------------------------------------------------------------------------------------------------ +LimbNode::LimbNode(uint64_t id, const Element& element, const Document& doc, const std::string& name) +: NodeAttribute(id,element,doc,name) +{ + +} + + +// ------------------------------------------------------------------------------------------------ +LimbNode::~LimbNode() +{ + +} + +} +} + +#endif diff --git a/libs/assimp/code/AssetLib/FBX/FBXParser.cpp b/libs/assimp/code/AssetLib/FBX/FBXParser.cpp new file mode 100644 index 0000000..e20377a --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXParser.cpp @@ -0,0 +1,1314 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXParser.cpp + * @brief Implementation of the FBX parser and the rudimentary DOM that we use + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +//#ifdef ASSIMP_BUILD_NO_OWN_ZLIB +#include "Common/Compression.h" +//# include <zlib.h> +//#else +//# include "../contrib/zlib/zlib.h" +//#endif + +#include "FBXTokenizer.h" +#include "FBXParser.h" +#include "FBXUtil.h" + +#include <assimp/ParsingUtils.h> +#include <assimp/fast_atof.h> +#include <assimp/ByteSwapper.h> +#include <assimp/DefaultLogger.hpp> + +#include <iostream> + +using namespace Assimp; +using namespace Assimp::FBX; + +namespace { + + // ------------------------------------------------------------------------------------------------ + // signal parse error, this is always unrecoverable. Throws DeadlyImportError. + AI_WONT_RETURN void ParseError(const std::string& message, const Token& token) AI_WONT_RETURN_SUFFIX; + AI_WONT_RETURN void ParseError(const std::string& message, const Token& token) + { + throw DeadlyImportError("FBX-Parser", Util::GetTokenText(&token), message); + } + + // ------------------------------------------------------------------------------------------------ + AI_WONT_RETURN void ParseError(const std::string &message, const Element *element = nullptr) AI_WONT_RETURN_SUFFIX; + AI_WONT_RETURN void ParseError(const std::string& message, const Element* element) + { + if(element) { + ParseError(message,element->KeyToken()); + } + throw DeadlyImportError("FBX-Parser ", message); + } + + + // ------------------------------------------------------------------------------------------------ + void ParseError(const std::string& message, TokenPtr token) + { + if(token) { + ParseError(message, *token); + } + ParseError(message); + } + + // Initially, we did reinterpret_cast, breaking strict aliasing rules. + // This actually caused trouble on Android, so let's be safe this time. + // https://github.com/assimp/assimp/issues/24 + template <typename T> + T SafeParse(const char* data, const char* end) { + // Actual size validation happens during Tokenization so + // this is valid as an assertion. + (void)(end); + ai_assert(static_cast<size_t>(end - data) >= sizeof(T)); + T result = static_cast<T>(0); + ::memcpy(&result, data, sizeof(T)); + return result; + } +} + +namespace Assimp { +namespace FBX { + +// ------------------------------------------------------------------------------------------------ +Element::Element(const Token& key_token, Parser& parser) : key_token(key_token) { + TokenPtr n = nullptr; + do { + n = parser.AdvanceToNextToken(); + if(!n) { + ParseError("unexpected end of file, expected closing bracket",parser.LastToken()); + } + + if (n->Type() == TokenType_DATA) { + tokens.push_back(n); + TokenPtr prev = n; + n = parser.AdvanceToNextToken(); + if(!n) { + ParseError("unexpected end of file, expected bracket, comma or key",parser.LastToken()); + } + + const TokenType ty = n->Type(); + + // some exporters are missing a comma on the next line + if (ty == TokenType_DATA && prev->Type() == TokenType_DATA && (n->Line() == prev->Line() + 1)) { + tokens.push_back(n); + continue; + } + + if (ty != TokenType_OPEN_BRACKET && ty != TokenType_CLOSE_BRACKET && ty != TokenType_COMMA && ty != TokenType_KEY) { + ParseError("unexpected token; expected bracket, comma or key",n); + } + } + + if (n->Type() == TokenType_OPEN_BRACKET) { + compound.reset(new Scope(parser)); + + // current token should be a TOK_CLOSE_BRACKET + n = parser.CurrentToken(); + ai_assert(n); + + if (n->Type() != TokenType_CLOSE_BRACKET) { + ParseError("expected closing bracket",n); + } + + parser.AdvanceToNextToken(); + return; + } + } + while(n->Type() != TokenType_KEY && n->Type() != TokenType_CLOSE_BRACKET); +} + +// ------------------------------------------------------------------------------------------------ +Element::~Element() +{ + // no need to delete tokens, they are owned by the parser +} + +// ------------------------------------------------------------------------------------------------ +Scope::Scope(Parser& parser,bool topLevel) +{ + if(!topLevel) { + TokenPtr t = parser.CurrentToken(); + if (t->Type() != TokenType_OPEN_BRACKET) { + ParseError("expected open bracket",t); + } + } + + TokenPtr n = parser.AdvanceToNextToken(); + if (n == nullptr) { + ParseError("unexpected end of file"); + } + + // note: empty scopes are allowed + while(n->Type() != TokenType_CLOSE_BRACKET) { + if (n->Type() != TokenType_KEY) { + ParseError("unexpected token, expected TOK_KEY",n); + } + + const std::string& str = n->StringContents(); + if (str.empty()) { + ParseError("unexpected content: empty string."); + } + + elements.insert(ElementMap::value_type(str,new_Element(*n,parser))); + + // Element() should stop at the next Key token (or right after a Close token) + n = parser.CurrentToken(); + if (n == nullptr) { + if (topLevel) { + return; + } + ParseError("unexpected end of file",parser.LastToken()); + } + } +} + +// ------------------------------------------------------------------------------------------------ +Scope::~Scope() { + for(ElementMap::value_type& v : elements) { + delete v.second; + } +} + +// ------------------------------------------------------------------------------------------------ +Parser::Parser (const TokenList& tokens, bool is_binary) +: tokens(tokens) +, last() +, current() +, cursor(tokens.begin()) +, is_binary(is_binary) +{ + ASSIMP_LOG_DEBUG("Parsing FBX tokens"); + root.reset(new Scope(*this,true)); +} + +// ------------------------------------------------------------------------------------------------ +Parser::~Parser() +{ + // empty +} + +// ------------------------------------------------------------------------------------------------ +TokenPtr Parser::AdvanceToNextToken() +{ + last = current; + if (cursor == tokens.end()) { + current = nullptr; + } else { + current = *cursor++; + } + return current; +} + +// ------------------------------------------------------------------------------------------------ +TokenPtr Parser::CurrentToken() const +{ + return current; +} + +// ------------------------------------------------------------------------------------------------ +TokenPtr Parser::LastToken() const +{ + return last; +} + +// ------------------------------------------------------------------------------------------------ +uint64_t ParseTokenAsID(const Token& t, const char*& err_out) +{ + err_out = nullptr; + + if (t.Type() != TokenType_DATA) { + err_out = "expected TOK_DATA token"; + return 0L; + } + + if(t.IsBinary()) + { + const char* data = t.begin(); + if (data[0] != 'L') { + err_out = "failed to parse ID, unexpected data type, expected L(ong) (binary)"; + return 0L; + } + + BE_NCONST uint64_t id = SafeParse<uint64_t>(data+1, t.end()); + AI_SWAP8(id); + return id; + } + + // XXX: should use size_t here + unsigned int length = static_cast<unsigned int>(t.end() - t.begin()); + ai_assert(length > 0); + + const char* out = nullptr; + const uint64_t id = strtoul10_64(t.begin(),&out,&length); + if (out > t.end()) { + err_out = "failed to parse ID (text)"; + return 0L; + } + + return id; +} + +// ------------------------------------------------------------------------------------------------ +size_t ParseTokenAsDim(const Token& t, const char*& err_out) +{ + // same as ID parsing, except there is a trailing asterisk + err_out = nullptr; + + if (t.Type() != TokenType_DATA) { + err_out = "expected TOK_DATA token"; + return 0; + } + + if(t.IsBinary()) + { + const char* data = t.begin(); + if (data[0] != 'L') { + err_out = "failed to parse ID, unexpected data type, expected L(ong) (binary)"; + return 0; + } + + BE_NCONST uint64_t id = SafeParse<uint64_t>(data+1, t.end()); + AI_SWAP8(id); + return static_cast<size_t>(id); + } + + if(*t.begin() != '*') { + err_out = "expected asterisk before array dimension"; + return 0; + } + + // XXX: should use size_t here + unsigned int length = static_cast<unsigned int>(t.end() - t.begin()); + if(length == 0) { + err_out = "expected valid integer number after asterisk"; + return 0; + } + + const char* out = nullptr; + const size_t id = static_cast<size_t>(strtoul10_64(t.begin() + 1,&out,&length)); + if (out > t.end()) { + err_out = "failed to parse ID"; + return 0; + } + + return id; +} + + +// ------------------------------------------------------------------------------------------------ +float ParseTokenAsFloat(const Token& t, const char*& err_out) +{ + err_out = nullptr; + + if (t.Type() != TokenType_DATA) { + err_out = "expected TOK_DATA token"; + return 0.0f; + } + + if(t.IsBinary()) + { + const char* data = t.begin(); + if (data[0] != 'F' && data[0] != 'D') { + err_out = "failed to parse F(loat) or D(ouble), unexpected data type (binary)"; + return 0.0f; + } + + if (data[0] == 'F') { + return SafeParse<float>(data+1, t.end()); + } + else { + return static_cast<float>( SafeParse<double>(data+1, t.end()) ); + } + } + + // need to copy the input string to a temporary buffer + // first - next in the fbx token stream comes ',', + // which fast_atof could interpret as decimal point. +#define MAX_FLOAT_LENGTH 31 + const size_t length = static_cast<size_t>(t.end()-t.begin()); + if (length > MAX_FLOAT_LENGTH) { + return 0.f; + } + + char temp[MAX_FLOAT_LENGTH + 1]; + std::copy(t.begin(), t.end(), temp); + temp[std::min(static_cast<size_t>(MAX_FLOAT_LENGTH),length)] = '\0'; + + return fast_atof(temp); +} + + +// ------------------------------------------------------------------------------------------------ +int ParseTokenAsInt(const Token& t, const char*& err_out) +{ + err_out = nullptr; + + if (t.Type() != TokenType_DATA) { + err_out = "expected TOK_DATA token"; + return 0; + } + + if(t.IsBinary()) + { + const char* data = t.begin(); + if (data[0] != 'I') { + err_out = "failed to parse I(nt), unexpected data type (binary)"; + return 0; + } + + BE_NCONST int32_t ival = SafeParse<int32_t>(data+1, t.end()); + AI_SWAP4(ival); + return static_cast<int>(ival); + } + + ai_assert(static_cast<size_t>(t.end() - t.begin()) > 0); + + const char* out; + const int intval = strtol10(t.begin(),&out); + if (out != t.end()) { + err_out = "failed to parse ID"; + return 0; + } + + return intval; +} + + +// ------------------------------------------------------------------------------------------------ +int64_t ParseTokenAsInt64(const Token& t, const char*& err_out) +{ + err_out = nullptr; + + if (t.Type() != TokenType_DATA) { + err_out = "expected TOK_DATA token"; + return 0L; + } + + if (t.IsBinary()) + { + const char* data = t.begin(); + if (data[0] != 'L') { + err_out = "failed to parse Int64, unexpected data type"; + return 0L; + } + + BE_NCONST int64_t id = SafeParse<int64_t>(data + 1, t.end()); + AI_SWAP8(id); + return id; + } + + // XXX: should use size_t here + unsigned int length = static_cast<unsigned int>(t.end() - t.begin()); + ai_assert(length > 0); + + const char* out = nullptr; + const int64_t id = strtol10_64(t.begin(), &out, &length); + if (out > t.end()) { + err_out = "failed to parse Int64 (text)"; + return 0L; + } + + return id; +} + +// ------------------------------------------------------------------------------------------------ +std::string ParseTokenAsString(const Token& t, const char*& err_out) +{ + err_out = nullptr; + + if (t.Type() != TokenType_DATA) { + err_out = "expected TOK_DATA token"; + return std::string(); + } + + if(t.IsBinary()) + { + const char* data = t.begin(); + if (data[0] != 'S') { + err_out = "failed to parse S(tring), unexpected data type (binary)"; + return std::string(); + } + + // read string length + BE_NCONST int32_t len = SafeParse<int32_t>(data+1, t.end()); + AI_SWAP4(len); + + ai_assert(t.end() - data == 5 + len); + return std::string(data + 5, len); + } + + const size_t length = static_cast<size_t>(t.end() - t.begin()); + if(length < 2) { + err_out = "token is too short to hold a string"; + return std::string(); + } + + const char* s = t.begin(), *e = t.end() - 1; + if (*s != '\"' || *e != '\"') { + err_out = "expected double quoted string"; + return std::string(); + } + + return std::string(s+1,length-2); +} + + +namespace { + +// ------------------------------------------------------------------------------------------------ +// read the type code and element count of a binary data array and stop there +void ReadBinaryDataArrayHead(const char*& data, const char* end, char& type, uint32_t& count, + const Element& el) +{ + if (static_cast<size_t>(end-data) < 5) { + ParseError("binary data array is too short, need five (5) bytes for type signature and element count",&el); + } + + // data type + type = *data; + + // read number of elements + BE_NCONST uint32_t len = SafeParse<uint32_t>(data+1, end); + AI_SWAP4(len); + + count = len; + data += 5; +} + + +// ------------------------------------------------------------------------------------------------ +// read binary data array, assume cursor points to the 'compression mode' field (i.e. behind the header) +void ReadBinaryDataArray(char type, uint32_t count, const char*& data, const char* end, + std::vector<char>& buff, const Element& /*el*/) { + BE_NCONST uint32_t encmode = SafeParse<uint32_t>(data, end); + AI_SWAP4(encmode); + data += 4; + + // next comes the compressed length + BE_NCONST uint32_t comp_len = SafeParse<uint32_t>(data, end); + AI_SWAP4(comp_len); + data += 4; + + ai_assert(data + comp_len == end); + + // determine the length of the uncompressed data by looking at the type signature + uint32_t stride = 0; + switch(type) + { + case 'f': + case 'i': + stride = 4; + break; + + case 'd': + case 'l': + stride = 8; + break; + + default: + ai_assert(false); + }; + + const uint32_t full_length = stride * count; + buff.resize(full_length); + + if(encmode == 0) { + ai_assert(full_length == comp_len); + + // plain data, no compression + std::copy(data, end, buff.begin()); + } + else if(encmode == 1) { + // zlib/deflate, next comes ZIP head (0x78 0x01) + // see http://www.ietf.org/rfc/rfc1950.txt + Compression compress; + if (compress.open(Compression::Format::Binary, Compression::FlushMode::Finish, 0)) { + compress.decompress(data, comp_len, buff); + compress.close(); + } + } +#ifdef ASSIMP_BUILD_DEBUG + else { + // runtime check for this happens at tokenization stage + ai_assert(false); + } +#endif + + data += comp_len; + ai_assert(data == end); +} + +} // !anon + + +// ------------------------------------------------------------------------------------------------ +// read an array of float3 tuples +void ParseVectorDataArray(std::vector<aiVector3D>& out, const Element& el) +{ + out.resize( 0 ); + + const TokenList& tok = el.Tokens(); + if(tok.empty()) { + ParseError("unexpected empty element",&el); + } + + if(tok[0]->IsBinary()) { + const char* data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if(count % 3 != 0) { + ParseError("number of floats is not a multiple of three (3) (binary)",&el); + } + + if(!count) { + return; + } + + if (type != 'd' && type != 'f') { + ParseError("expected float or double array (binary)",&el); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + ai_assert(data == end); + uint64_t dataToRead = static_cast<uint64_t>(count) * (type == 'd' ? 8 : 4); + if (dataToRead != buff.size()) { + ParseError("Invalid read size (binary)",&el); + } + + const uint32_t count3 = count / 3; + out.reserve(count3); + + if (type == 'd') { + const double* d = reinterpret_cast<const double*>(&buff[0]); + for (unsigned int i = 0; i < count3; ++i, d += 3) { + out.push_back(aiVector3D(static_cast<ai_real>(d[0]), + static_cast<ai_real>(d[1]), + static_cast<ai_real>(d[2]))); + } + // for debugging + /*for ( size_t i = 0; i < out.size(); i++ ) { + aiVector3D vec3( out[ i ] ); + std::stringstream stream; + stream << " vec3.x = " << vec3.x << " vec3.y = " << vec3.y << " vec3.z = " << vec3.z << std::endl; + DefaultLogger::get()->info( stream.str() ); + }*/ + } + else if (type == 'f') { + const float* f = reinterpret_cast<const float*>(&buff[0]); + for (unsigned int i = 0; i < count3; ++i, f += 3) { + out.push_back(aiVector3D(f[0],f[1],f[2])); + } + } + + return; + } + + const size_t dim = ParseTokenAsDim(*tok[0]); + + // may throw bad_alloc if the input is rubbish, but this need + // not to be prevented - importing would fail but we wouldn't + // crash since assimp handles this case properly. + out.reserve(dim); + + const Scope& scope = GetRequiredScope(el); + const Element& a = GetRequiredElement(scope,"a",&el); + + if (a.Tokens().size() % 3 != 0) { + ParseError("number of floats is not a multiple of three (3)",&el); + } + for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { + aiVector3D v; + v.x = ParseTokenAsFloat(**it++); + v.y = ParseTokenAsFloat(**it++); + v.z = ParseTokenAsFloat(**it++); + + out.push_back(v); + } +} + +// ------------------------------------------------------------------------------------------------ +// read an array of color4 tuples +void ParseVectorDataArray(std::vector<aiColor4D>& out, const Element& el) +{ + out.resize( 0 ); + const TokenList& tok = el.Tokens(); + if(tok.empty()) { + ParseError("unexpected empty element",&el); + } + + if(tok[0]->IsBinary()) { + const char* data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if(count % 4 != 0) { + ParseError("number of floats is not a multiple of four (4) (binary)",&el); + } + + if(!count) { + return; + } + + if (type != 'd' && type != 'f') { + ParseError("expected float or double array (binary)",&el); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + ai_assert(data == end); + uint64_t dataToRead = static_cast<uint64_t>(count) * (type == 'd' ? 8 : 4); + if (dataToRead != buff.size()) { + ParseError("Invalid read size (binary)",&el); + } + + const uint32_t count4 = count / 4; + out.reserve(count4); + + if (type == 'd') { + const double* d = reinterpret_cast<const double*>(&buff[0]); + for (unsigned int i = 0; i < count4; ++i, d += 4) { + out.push_back(aiColor4D(static_cast<float>(d[0]), + static_cast<float>(d[1]), + static_cast<float>(d[2]), + static_cast<float>(d[3]))); + } + } + else if (type == 'f') { + const float* f = reinterpret_cast<const float*>(&buff[0]); + for (unsigned int i = 0; i < count4; ++i, f += 4) { + out.push_back(aiColor4D(f[0],f[1],f[2],f[3])); + } + } + return; + } + + const size_t dim = ParseTokenAsDim(*tok[0]); + + // see notes in ParseVectorDataArray() above + out.reserve(dim); + + const Scope& scope = GetRequiredScope(el); + const Element& a = GetRequiredElement(scope,"a",&el); + + if (a.Tokens().size() % 4 != 0) { + ParseError("number of floats is not a multiple of four (4)",&el); + } + for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { + aiColor4D v; + v.r = ParseTokenAsFloat(**it++); + v.g = ParseTokenAsFloat(**it++); + v.b = ParseTokenAsFloat(**it++); + v.a = ParseTokenAsFloat(**it++); + + out.push_back(v); + } +} + + +// ------------------------------------------------------------------------------------------------ +// read an array of float2 tuples +void ParseVectorDataArray(std::vector<aiVector2D>& out, const Element& el) { + out.resize( 0 ); + const TokenList& tok = el.Tokens(); + if(tok.empty()) { + ParseError("unexpected empty element",&el); + } + + if(tok[0]->IsBinary()) { + const char* data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if(count % 2 != 0) { + ParseError("number of floats is not a multiple of two (2) (binary)",&el); + } + + if(!count) { + return; + } + + if (type != 'd' && type != 'f') { + ParseError("expected float or double array (binary)",&el); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + ai_assert(data == end); + uint64_t dataToRead = static_cast<uint64_t>(count) * (type == 'd' ? 8 : 4); + if (dataToRead != buff.size()) { + ParseError("Invalid read size (binary)",&el); + } + + const uint32_t count2 = count / 2; + out.reserve(count2); + + if (type == 'd') { + const double* d = reinterpret_cast<const double*>(&buff[0]); + for (unsigned int i = 0; i < count2; ++i, d += 2) { + out.push_back(aiVector2D(static_cast<float>(d[0]), + static_cast<float>(d[1]))); + } + } else if (type == 'f') { + const float* f = reinterpret_cast<const float*>(&buff[0]); + for (unsigned int i = 0; i < count2; ++i, f += 2) { + out.push_back(aiVector2D(f[0],f[1])); + } + } + + return; + } + + const size_t dim = ParseTokenAsDim(*tok[0]); + + // see notes in ParseVectorDataArray() above + out.reserve(dim); + + const Scope& scope = GetRequiredScope(el); + const Element& a = GetRequiredElement(scope,"a",&el); + + if (a.Tokens().size() % 2 != 0) { + ParseError("number of floats is not a multiple of two (2)",&el); + } + for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { + aiVector2D v; + v.x = ParseTokenAsFloat(**it++); + v.y = ParseTokenAsFloat(**it++); + + out.push_back(v); + } +} + + +// ------------------------------------------------------------------------------------------------ +// read an array of ints +void ParseVectorDataArray(std::vector<int>& out, const Element& el) { + out.resize( 0 ); + const TokenList& tok = el.Tokens(); + if(tok.empty()) { + ParseError("unexpected empty element",&el); + } + + if(tok[0]->IsBinary()) { + const char* data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if(!count) { + return; + } + + if (type != 'i') { + ParseError("expected int array (binary)",&el); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + ai_assert(data == end); + uint64_t dataToRead = static_cast<uint64_t>(count) * 4; + if (dataToRead != buff.size()) { + ParseError("Invalid read size (binary)",&el); + } + + out.reserve(count); + + const int32_t* ip = reinterpret_cast<const int32_t*>(&buff[0]); + for (unsigned int i = 0; i < count; ++i, ++ip) { + BE_NCONST int32_t val = *ip; + AI_SWAP4(val); + out.push_back(val); + } + + return; + } + + const size_t dim = ParseTokenAsDim(*tok[0]); + + // see notes in ParseVectorDataArray() + out.reserve(dim); + + const Scope& scope = GetRequiredScope(el); + const Element& a = GetRequiredElement(scope,"a",&el); + + for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { + const int ival = ParseTokenAsInt(**it++); + out.push_back(ival); + } +} + + +// ------------------------------------------------------------------------------------------------ +// read an array of floats +void ParseVectorDataArray(std::vector<float>& out, const Element& el) +{ + out.resize( 0 ); + const TokenList& tok = el.Tokens(); + if(tok.empty()) { + ParseError("unexpected empty element",&el); + } + + if(tok[0]->IsBinary()) { + const char* data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if(!count) { + return; + } + + if (type != 'd' && type != 'f') { + ParseError("expected float or double array (binary)",&el); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + ai_assert(data == end); + uint64_t dataToRead = static_cast<uint64_t>(count) * (type == 'd' ? 8 : 4); + if (dataToRead != buff.size()) { + ParseError("Invalid read size (binary)",&el); + } + + if (type == 'd') { + const double* d = reinterpret_cast<const double*>(&buff[0]); + for (unsigned int i = 0; i < count; ++i, ++d) { + out.push_back(static_cast<float>(*d)); + } + } + else if (type == 'f') { + const float* f = reinterpret_cast<const float*>(&buff[0]); + for (unsigned int i = 0; i < count; ++i, ++f) { + out.push_back(*f); + } + } + + return; + } + + const size_t dim = ParseTokenAsDim(*tok[0]); + + // see notes in ParseVectorDataArray() + out.reserve(dim); + + const Scope& scope = GetRequiredScope(el); + const Element& a = GetRequiredElement(scope,"a",&el); + + for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { + const float ival = ParseTokenAsFloat(**it++); + out.push_back(ival); + } +} + +// ------------------------------------------------------------------------------------------------ +// read an array of uints +void ParseVectorDataArray(std::vector<unsigned int>& out, const Element& el) +{ + out.resize( 0 ); + const TokenList& tok = el.Tokens(); + if(tok.empty()) { + ParseError("unexpected empty element",&el); + } + + if(tok[0]->IsBinary()) { + const char* data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if(!count) { + return; + } + + if (type != 'i') { + ParseError("expected (u)int array (binary)",&el); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + ai_assert(data == end); + uint64_t dataToRead = static_cast<uint64_t>(count) * 4; + if (dataToRead != buff.size()) { + ParseError("Invalid read size (binary)",&el); + } + + out.reserve(count); + + const int32_t* ip = reinterpret_cast<const int32_t*>(&buff[0]); + for (unsigned int i = 0; i < count; ++i, ++ip) { + BE_NCONST int32_t val = *ip; + if(val < 0) { + ParseError("encountered negative integer index (binary)"); + } + + AI_SWAP4(val); + out.push_back(val); + } + + return; + } + + const size_t dim = ParseTokenAsDim(*tok[0]); + + // see notes in ParseVectorDataArray() + out.reserve(dim); + + const Scope& scope = GetRequiredScope(el); + const Element& a = GetRequiredElement(scope,"a",&el); + + for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { + const int ival = ParseTokenAsInt(**it++); + if(ival < 0) { + ParseError("encountered negative integer index"); + } + out.push_back(static_cast<unsigned int>(ival)); + } +} + + +// ------------------------------------------------------------------------------------------------ +// read an array of uint64_ts +void ParseVectorDataArray(std::vector<uint64_t>& out, const Element& el) +{ + out.resize( 0 ); + const TokenList& tok = el.Tokens(); + if(tok.empty()) { + ParseError("unexpected empty element",&el); + } + + if(tok[0]->IsBinary()) { + const char* data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if(!count) { + return; + } + + if (type != 'l') { + ParseError("expected long array (binary)",&el); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + ai_assert(data == end); + uint64_t dataToRead = static_cast<uint64_t>(count) * 8; + if (dataToRead != buff.size()) { + ParseError("Invalid read size (binary)",&el); + } + + out.reserve(count); + + const uint64_t* ip = reinterpret_cast<const uint64_t*>(&buff[0]); + for (unsigned int i = 0; i < count; ++i, ++ip) { + BE_NCONST uint64_t val = *ip; + AI_SWAP8(val); + out.push_back(val); + } + + return; + } + + const size_t dim = ParseTokenAsDim(*tok[0]); + + // see notes in ParseVectorDataArray() + out.reserve(dim); + + const Scope& scope = GetRequiredScope(el); + const Element& a = GetRequiredElement(scope,"a",&el); + + for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { + const uint64_t ival = ParseTokenAsID(**it++); + + out.push_back(ival); + } +} + +// ------------------------------------------------------------------------------------------------ +// read an array of int64_ts +void ParseVectorDataArray(std::vector<int64_t>& out, const Element& el) +{ + out.resize( 0 ); + const TokenList& tok = el.Tokens(); + if (tok.empty()) { + ParseError("unexpected empty element", &el); + } + + if (tok[0]->IsBinary()) { + const char* data = tok[0]->begin(), *end = tok[0]->end(); + + char type; + uint32_t count; + ReadBinaryDataArrayHead(data, end, type, count, el); + + if (!count) { + return; + } + + if (type != 'l') { + ParseError("expected long array (binary)", &el); + } + + std::vector<char> buff; + ReadBinaryDataArray(type, count, data, end, buff, el); + + ai_assert(data == end); + uint64_t dataToRead = static_cast<uint64_t>(count) * 8; + if (dataToRead != buff.size()) { + ParseError("Invalid read size (binary)",&el); + } + + out.reserve(count); + + const int64_t* ip = reinterpret_cast<const int64_t*>(&buff[0]); + for (unsigned int i = 0; i < count; ++i, ++ip) { + BE_NCONST int64_t val = *ip; + AI_SWAP8(val); + out.push_back(val); + } + + return; + } + + const size_t dim = ParseTokenAsDim(*tok[0]); + + // see notes in ParseVectorDataArray() + out.reserve(dim); + + const Scope& scope = GetRequiredScope(el); + const Element& a = GetRequiredElement(scope, "a", &el); + + for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end;) { + const int64_t ival = ParseTokenAsInt64(**it++); + + out.push_back(ival); + } +} + +// ------------------------------------------------------------------------------------------------ +aiMatrix4x4 ReadMatrix(const Element& element) +{ + std::vector<float> values; + ParseVectorDataArray(values,element); + + if(values.size() != 16) { + ParseError("expected 16 matrix elements"); + } + + aiMatrix4x4 result; + + + result.a1 = values[0]; + result.a2 = values[1]; + result.a3 = values[2]; + result.a4 = values[3]; + + result.b1 = values[4]; + result.b2 = values[5]; + result.b3 = values[6]; + result.b4 = values[7]; + + result.c1 = values[8]; + result.c2 = values[9]; + result.c3 = values[10]; + result.c4 = values[11]; + + result.d1 = values[12]; + result.d2 = values[13]; + result.d3 = values[14]; + result.d4 = values[15]; + + result.Transpose(); + return result; +} + + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsString() with ParseError handling +std::string ParseTokenAsString(const Token& t) +{ + const char* err; + const std::string& i = ParseTokenAsString(t,err); + if(err) { + ParseError(err,t); + } + return i; +} + +bool HasElement( const Scope& sc, const std::string& index ) { + const Element* el = sc[ index ]; + if ( nullptr == el ) { + return false; + } + + return true; +} + +// ------------------------------------------------------------------------------------------------ +// extract a required element from a scope, abort if the element cannot be found +const Element& GetRequiredElement(const Scope& sc, const std::string& index, const Element* element /*= nullptr*/) +{ + const Element* el = sc[index]; + if(!el) { + ParseError("did not find required element \"" + index + "\"",element); + } + return *el; +} + + +// ------------------------------------------------------------------------------------------------ +// extract required compound scope +const Scope& GetRequiredScope(const Element& el) +{ + const Scope* const s = el.Compound(); + if(!s) { + ParseError("expected compound scope",&el); + } + + return *s; +} + + +// ------------------------------------------------------------------------------------------------ +// get token at a particular index +const Token& GetRequiredToken(const Element& el, unsigned int index) +{ + const TokenList& t = el.Tokens(); + if(index >= t.size()) { + ParseError(Formatter::format( "missing token at index " ) << index,&el); + } + + return *t[index]; +} + + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsID() with ParseError handling +uint64_t ParseTokenAsID(const Token& t) +{ + const char* err; + const uint64_t i = ParseTokenAsID(t,err); + if(err) { + ParseError(err,t); + } + return i; +} + + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsDim() with ParseError handling +size_t ParseTokenAsDim(const Token& t) +{ + const char* err; + const size_t i = ParseTokenAsDim(t,err); + if(err) { + ParseError(err,t); + } + return i; +} + + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsFloat() with ParseError handling +float ParseTokenAsFloat(const Token& t) +{ + const char* err; + const float i = ParseTokenAsFloat(t,err); + if(err) { + ParseError(err,t); + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsInt() with ParseError handling +int ParseTokenAsInt(const Token& t) +{ + const char* err; + const int i = ParseTokenAsInt(t,err); + if(err) { + ParseError(err,t); + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsInt64() with ParseError handling +int64_t ParseTokenAsInt64(const Token& t) +{ + const char* err; + const int64_t i = ParseTokenAsInt64(t, err); + if (err) { + ParseError(err, t); + } + return i; +} + +} // !FBX +} // !Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/FBX/FBXParser.h b/libs/assimp/code/AssetLib/FBX/FBXParser.h new file mode 100644 index 0000000..314481e --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXParser.h @@ -0,0 +1,235 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXParser.h + * @brief FBX parsing code + */ +#ifndef INCLUDED_AI_FBX_PARSER_H +#define INCLUDED_AI_FBX_PARSER_H + +#include <stdint.h> +#include <map> +#include <memory> +#include <vector> +#include <assimp/LogAux.h> +#include <assimp/fast_atof.h> + +#include "FBXCompileConfig.h" +#include "FBXTokenizer.h" + +namespace Assimp { +namespace FBX { + +class Scope; +class Parser; +class Element; + +// XXX should use C++11's unique_ptr - but assimp's need to keep working with 03 +typedef std::vector< Scope* > ScopeList; +typedef std::fbx_unordered_multimap< std::string, Element* > ElementMap; + +typedef std::pair<ElementMap::const_iterator,ElementMap::const_iterator> ElementCollection; + +# define new_Scope new Scope +# define new_Element new Element + + +/** FBX data entity that consists of a key:value tuple. + * + * Example: + * @verbatim + * AnimationCurve: 23, "AnimCurve::", "" { + * [..] + * } + * @endverbatim + * + * As can be seen in this sample, elements can contain nested #Scope + * as their trailing member. **/ +class Element +{ +public: + Element(const Token& key_token, Parser& parser); + ~Element(); + + const Scope* Compound() const { + return compound.get(); + } + + const Token& KeyToken() const { + return key_token; + } + + const TokenList& Tokens() const { + return tokens; + } + +private: + const Token& key_token; + TokenList tokens; + std::unique_ptr<Scope> compound; +}; + +/** FBX data entity that consists of a 'scope', a collection + * of not necessarily unique #Element instances. + * + * Example: + * @verbatim + * GlobalSettings: { + * Version: 1000 + * Properties70: + * [...] + * } + * @endverbatim */ +class Scope +{ +public: + Scope(Parser& parser, bool topLevel = false); + ~Scope(); + + const Element* operator[] (const std::string& index) const { + ElementMap::const_iterator it = elements.find(index); + return it == elements.end() ? nullptr : (*it).second; + } + + const Element* FindElementCaseInsensitive(const std::string& elementName) const { + const char* elementNameCStr = elementName.c_str(); + for (auto element = elements.begin(); element != elements.end(); ++element) + { + if (!ASSIMP_strincmp(element->first.c_str(), elementNameCStr, MAXLEN)) { + return element->second; + } + } + return nullptr; + } + + ElementCollection GetCollection(const std::string& index) const { + return elements.equal_range(index); + } + + const ElementMap& Elements() const { + return elements; + } + +private: + ElementMap elements; +}; + +/** FBX parsing class, takes a list of input tokens and generates a hierarchy + * of nested #Scope instances, representing the fbx DOM.*/ +class Parser +{ +public: + /** Parse given a token list. Does not take ownership of the tokens - + * the objects must persist during the entire parser lifetime */ + Parser (const TokenList& tokens,bool is_binary); + ~Parser(); + + const Scope& GetRootScope() const { + return *root.get(); + } + + bool IsBinary() const { + return is_binary; + } + +private: + friend class Scope; + friend class Element; + + TokenPtr AdvanceToNextToken(); + TokenPtr LastToken() const; + TokenPtr CurrentToken() const; + +private: + const TokenList& tokens; + + TokenPtr last, current; + TokenList::const_iterator cursor; + std::unique_ptr<Scope> root; + + const bool is_binary; +}; + + +/* token parsing - this happens when building the DOM out of the parse-tree*/ +uint64_t ParseTokenAsID(const Token& t, const char*& err_out); +size_t ParseTokenAsDim(const Token& t, const char*& err_out); + +float ParseTokenAsFloat(const Token& t, const char*& err_out); +int ParseTokenAsInt(const Token& t, const char*& err_out); +int64_t ParseTokenAsInt64(const Token& t, const char*& err_out); +std::string ParseTokenAsString(const Token& t, const char*& err_out); + +/* wrapper around ParseTokenAsXXX() with DOMError handling */ +uint64_t ParseTokenAsID(const Token& t); +size_t ParseTokenAsDim(const Token& t); +float ParseTokenAsFloat(const Token& t); +int ParseTokenAsInt(const Token& t); +int64_t ParseTokenAsInt64(const Token& t); +std::string ParseTokenAsString(const Token& t); + +/* read data arrays */ +void ParseVectorDataArray(std::vector<aiVector3D>& out, const Element& el); +void ParseVectorDataArray(std::vector<aiColor4D>& out, const Element& el); +void ParseVectorDataArray(std::vector<aiVector2D>& out, const Element& el); +void ParseVectorDataArray(std::vector<int>& out, const Element& el); +void ParseVectorDataArray(std::vector<float>& out, const Element& el); +void ParseVectorDataArray(std::vector<unsigned int>& out, const Element& el); +void ParseVectorDataArray(std::vector<uint64_t>& out, const Element& e); +void ParseVectorDataArray(std::vector<int64_t>& out, const Element& el); + +bool HasElement( const Scope& sc, const std::string& index ); + +// extract a required element from a scope, abort if the element cannot be found +const Element &GetRequiredElement(const Scope &sc, const std::string &index, const Element *element = nullptr); + +// extract required compound scope +const Scope& GetRequiredScope(const Element& el); +// get token at a particular index +const Token& GetRequiredToken(const Element& el, unsigned int index); + +// read a 4x4 matrix from an array of 16 floats +aiMatrix4x4 ReadMatrix(const Element& element); + +} // ! FBX +} // ! Assimp + +#endif // ! INCLUDED_AI_FBX_PARSER_H diff --git a/libs/assimp/code/AssetLib/FBX/FBXProperties.cpp b/libs/assimp/code/AssetLib/FBX/FBXProperties.cpp new file mode 100644 index 0000000..7803c27 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXProperties.cpp @@ -0,0 +1,270 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXProperties.cpp + * @brief Implementation of the FBX dynamic properties system + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +#include "FBXTokenizer.h" +#include "FBXParser.h" +#include "FBXDocument.h" +#include "FBXDocumentUtil.h" +#include "FBXProperties.h" + +#include <utility> + +namespace Assimp { +namespace FBX { + + using namespace Util; + +// ------------------------------------------------------------------------------------------------ +Property::Property() +{ +} + +// ------------------------------------------------------------------------------------------------ +Property::~Property() +{ +} + +namespace { + +void checkTokenCount(const TokenList& tok, unsigned int expectedCount) +{ + ai_assert(expectedCount >= 2); + if (tok.size() < expectedCount) { + const std::string& s = ParseTokenAsString(*tok[1]); + if (tok[1]->IsBinary()) { + throw DeadlyImportError("Not enough tokens for property of type ", s, " at offset ", tok[1]->Offset()); + } + else { + throw DeadlyImportError("Not enough tokens for property of type ", s, " at line ", tok[1]->Line()); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// read a typed property out of a FBX element. The return value is nullptr if the property cannot be read. +Property* ReadTypedProperty(const Element& element) +{ + ai_assert(element.KeyToken().StringContents() == "P"); + + const TokenList& tok = element.Tokens(); + if (tok.size() < 2) { + return nullptr; + } + + const std::string& s = ParseTokenAsString(*tok[1]); + const char* const cs = s.c_str(); + if (!strcmp(cs,"KString")) { + checkTokenCount(tok, 5); + return new TypedProperty<std::string>(ParseTokenAsString(*tok[4])); + } + else if (!strcmp(cs,"bool") || !strcmp(cs,"Bool")) { + checkTokenCount(tok, 5); + return new TypedProperty<bool>(ParseTokenAsInt(*tok[4]) != 0); + } + else if (!strcmp(cs, "int") || !strcmp(cs, "Int") || !strcmp(cs, "enum") || !strcmp(cs, "Enum") || !strcmp(cs, "Integer")) { + checkTokenCount(tok, 5); + return new TypedProperty<int>(ParseTokenAsInt(*tok[4])); + } + else if (!strcmp(cs, "ULongLong")) { + checkTokenCount(tok, 5); + return new TypedProperty<uint64_t>(ParseTokenAsID(*tok[4])); + } + else if (!strcmp(cs, "KTime")) { + checkTokenCount(tok, 5); + return new TypedProperty<int64_t>(ParseTokenAsInt64(*tok[4])); + } + else if (!strcmp(cs,"Vector3D") || + !strcmp(cs,"ColorRGB") || + !strcmp(cs,"Vector") || + !strcmp(cs,"Color") || + !strcmp(cs,"Lcl Translation") || + !strcmp(cs,"Lcl Rotation") || + !strcmp(cs,"Lcl Scaling") + ) { + checkTokenCount(tok, 7); + return new TypedProperty<aiVector3D>(aiVector3D( + ParseTokenAsFloat(*tok[4]), + ParseTokenAsFloat(*tok[5]), + ParseTokenAsFloat(*tok[6])) + ); + } + else if (!strcmp(cs,"double") || !strcmp(cs,"Number") || !strcmp(cs,"float") || !strcmp(cs,"Float") || !strcmp(cs,"FieldOfView") || !strcmp( cs, "UnitScaleFactor" ) ) { + checkTokenCount(tok, 5); + return new TypedProperty<float>(ParseTokenAsFloat(*tok[4])); + } + else if (!strcmp(cs, "ColorAndAlpha")) { + checkTokenCount(tok, 8); + return new TypedProperty<aiColor4D>(aiColor4D( + ParseTokenAsFloat(*tok[4]), + ParseTokenAsFloat(*tok[5]), + ParseTokenAsFloat(*tok[6]), + ParseTokenAsFloat(*tok[7])) + ); + } + return nullptr; +} + + +// ------------------------------------------------------------------------------------------------ +// peek into an element and check if it contains a FBX property, if so return its name. +std::string PeekPropertyName(const Element& element) +{ + ai_assert(element.KeyToken().StringContents() == "P"); + const TokenList& tok = element.Tokens(); + if(tok.size() < 4) { + return std::string(); + } + + return ParseTokenAsString(*tok[0]); +} + +} //! anon + + +// ------------------------------------------------------------------------------------------------ +PropertyTable::PropertyTable() +: templateProps() +, element() +{ +} + +// ------------------------------------------------------------------------------------------------ +PropertyTable::PropertyTable(const Element &element, std::shared_ptr<const PropertyTable> templateProps) : + templateProps(std::move(templateProps)), element(&element) { + const Scope& scope = GetRequiredScope(element); + for(const ElementMap::value_type& v : scope.Elements()) { + if(v.first != "P") { + DOMWarning("expected only P elements in property table",v.second); + continue; + } + + const std::string& name = PeekPropertyName(*v.second); + if(!name.length()) { + DOMWarning("could not read property name",v.second); + continue; + } + + LazyPropertyMap::const_iterator it = lazyProps.find(name); + if (it != lazyProps.end()) { + DOMWarning("duplicate property name, will hide previous value: " + name,v.second); + continue; + } + + lazyProps[name] = v.second; + } +} + +// ------------------------------------------------------------------------------------------------ +PropertyTable::~PropertyTable() +{ + for(PropertyMap::value_type& v : props) { + delete v.second; + } +} + + +// ------------------------------------------------------------------------------------------------ +const Property* PropertyTable::Get(const std::string& name) const +{ + PropertyMap::const_iterator it = props.find(name); + if (it == props.end()) { + // hasn't been parsed yet? + LazyPropertyMap::const_iterator lit = lazyProps.find(name); + if(lit != lazyProps.end()) { + props[name] = ReadTypedProperty(*(*lit).second); + it = props.find(name); + + ai_assert(it != props.end()); + } + + if (it == props.end()) { + // check property template + if(templateProps) { + return templateProps->Get(name); + } + + return nullptr; + } + } + + return (*it).second; +} + +DirectPropertyMap PropertyTable::GetUnparsedProperties() const +{ + DirectPropertyMap result; + + // Loop through all the lazy properties (which is all the properties) + for(const LazyPropertyMap::value_type& currentElement : lazyProps) { + + // Skip parsed properties + if (props.end() != props.find(currentElement.first)) { + continue; + } + + // Read the element's value. + // Wrap the naked pointer (since the call site is required to acquire ownership) + // std::unique_ptr from C++11 would be preferred both as a wrapper and a return value. + std::shared_ptr<Property> prop = std::shared_ptr<Property>(ReadTypedProperty(*currentElement.second)); + + // Element could not be read. Skip it. + if (!prop) { + continue; + } + + // Add to result + result[currentElement.first] = prop; + } + + return result; +} + +} //! FBX +} //! Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/FBX/FBXProperties.h b/libs/assimp/code/AssetLib/FBX/FBXProperties.h new file mode 100644 index 0000000..1881611 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXProperties.h @@ -0,0 +1,185 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXProperties.h + * @brief FBX dynamic properties + */ +#ifndef INCLUDED_AI_FBX_PROPERTIES_H +#define INCLUDED_AI_FBX_PROPERTIES_H + +#include "FBXCompileConfig.h" +#include <memory> +#include <string> + +namespace Assimp { +namespace FBX { + +// Forward declarations +class Element; + +/** Represents a dynamic property. Type info added by deriving classes, + * see #TypedProperty. + Example: + @verbatim + P: "ShininessExponent", "double", "Number", "",0.5 + @endvebatim +*/ +class Property { +protected: + Property(); + +public: + virtual ~Property(); + +public: + template <typename T> + const T* As() const { + return dynamic_cast<const T*>(this); + } +}; + +template<typename T> +class TypedProperty : public Property { +public: + explicit TypedProperty(const T& value) + : value(value) { + // empty + } + + const T& Value() const { + return value; + } + +private: + T value; +}; + + +typedef std::fbx_unordered_map<std::string,std::shared_ptr<Property> > DirectPropertyMap; +typedef std::fbx_unordered_map<std::string,const Property*> PropertyMap; +typedef std::fbx_unordered_map<std::string,const Element*> LazyPropertyMap; + +/** + * Represents a property table as can be found in the newer FBX files (Properties60, Properties70) + */ +class PropertyTable { +public: + // in-memory property table with no source element + PropertyTable(); + PropertyTable(const Element& element, std::shared_ptr<const PropertyTable> templateProps); + ~PropertyTable(); + + const Property* Get(const std::string& name) const; + + // PropertyTable's need not be coupled with FBX elements so this can be nullptr + const Element* GetElement() const { + return element; + } + + const PropertyTable* TemplateProps() const { + return templateProps.get(); + } + + DirectPropertyMap GetUnparsedProperties() const; + +private: + LazyPropertyMap lazyProps; + mutable PropertyMap props; + const std::shared_ptr<const PropertyTable> templateProps; + const Element* const element; +}; + +// ------------------------------------------------------------------------------------------------ +template <typename T> +inline +T PropertyGet(const PropertyTable& in, const std::string& name, const T& defaultValue) { + const Property* const prop = in.Get(name); + if( nullptr == prop) { + return defaultValue; + } + + // strong typing, no need to be lenient + const TypedProperty<T>* const tprop = prop->As< TypedProperty<T> >(); + if( nullptr == tprop) { + return defaultValue; + } + + return tprop->Value(); +} + +// ------------------------------------------------------------------------------------------------ +template <typename T> +inline +T PropertyGet(const PropertyTable& in, const std::string& name, bool& result, bool useTemplate=false ) { + const Property* prop = in.Get(name); + if( nullptr == prop) { + if ( ! useTemplate ) { + result = false; + return T(); + } + const PropertyTable* templ = in.TemplateProps(); + if ( nullptr == templ ) { + result = false; + return T(); + } + prop = templ->Get(name); + if ( nullptr == prop ) { + result = false; + return T(); + } + } + + // strong typing, no need to be lenient + const TypedProperty<T>* const tprop = prop->As< TypedProperty<T> >(); + if( nullptr == tprop) { + result = false; + return T(); + } + + result = true; + return tprop->Value(); +} + +} //! FBX +} //! Assimp + +#endif // INCLUDED_AI_FBX_PROPERTIES_H diff --git a/libs/assimp/code/AssetLib/FBX/FBXTokenizer.cpp b/libs/assimp/code/AssetLib/FBX/FBXTokenizer.cpp new file mode 100644 index 0000000..8698aba --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXTokenizer.cpp @@ -0,0 +1,250 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXTokenizer.cpp + * @brief Implementation of the FBX broadphase lexer + */ + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +// tab width for logging columns +#define ASSIMP_FBX_TAB_WIDTH 4 + +#include <assimp/ParsingUtils.h> + +#include "FBXTokenizer.h" +#include "FBXUtil.h" +#include <assimp/Exceptional.h> +#include <assimp/DefaultLogger.hpp> + +namespace Assimp { +namespace FBX { + +// ------------------------------------------------------------------------------------------------ +Token::Token(const char* sbegin, const char* send, TokenType type, unsigned int line, unsigned int column) + : +#ifdef DEBUG + contents(sbegin, static_cast<size_t>(send-sbegin)), +#endif + sbegin(sbegin) + , send(send) + , type(type) + , line(line) + , column(column) +{ + ai_assert(sbegin); + ai_assert(send); + + // tokens must be of non-zero length + ai_assert(static_cast<size_t>(send-sbegin) > 0); +} + +// ------------------------------------------------------------------------------------------------ +Token::~Token() +{ +} + +namespace { + +// ------------------------------------------------------------------------------------------------ +// signal tokenization error, this is always unrecoverable. Throws DeadlyImportError. +AI_WONT_RETURN void TokenizeError(const std::string& message, unsigned int line, unsigned int column) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN void TokenizeError(const std::string& message, unsigned int line, unsigned int column) +{ + throw DeadlyImportError("FBX-Tokenize", Util::GetLineAndColumnText(line,column), message); +} + + +// process a potential data token up to 'cur', adding it to 'output_tokens'. +// ------------------------------------------------------------------------------------------------ +void ProcessDataToken( TokenList& output_tokens, const char*& start, const char*& end, + unsigned int line, + unsigned int column, + TokenType type = TokenType_DATA, + bool must_have_token = false) +{ + if (start && end) { + // sanity check: + // tokens should have no whitespace outside quoted text and [start,end] should + // properly delimit the valid range. + bool in_double_quotes = false; + for (const char* c = start; c != end + 1; ++c) { + if (*c == '\"') { + in_double_quotes = !in_double_quotes; + } + + if (!in_double_quotes && IsSpaceOrNewLine(*c)) { + TokenizeError("unexpected whitespace in token", line, column); + } + } + + if (in_double_quotes) { + TokenizeError("non-terminated double quotes", line, column); + } + + output_tokens.push_back(new_Token(start,end + 1,type,line,column)); + } + else if (must_have_token) { + TokenizeError("unexpected character, expected data token", line, column); + } + + start = end = nullptr; +} + +} + +// ------------------------------------------------------------------------------------------------ +void Tokenize(TokenList& output_tokens, const char* input) +{ + ai_assert(input); + ASSIMP_LOG_DEBUG("Tokenizing ASCII FBX file"); + + // line and column numbers numbers are one-based + unsigned int line = 1; + unsigned int column = 1; + + bool comment = false; + bool in_double_quotes = false; + bool pending_data_token = false; + + const char *token_begin = nullptr, *token_end = nullptr; + for (const char* cur = input;*cur;column += (*cur == '\t' ? ASSIMP_FBX_TAB_WIDTH : 1), ++cur) { + const char c = *cur; + + if (IsLineEnd(c)) { + comment = false; + + column = 0; + ++line; + } + + if(comment) { + continue; + } + + if(in_double_quotes) { + if (c == '\"') { + in_double_quotes = false; + token_end = cur; + + ProcessDataToken(output_tokens,token_begin,token_end,line,column); + pending_data_token = false; + } + continue; + } + + switch(c) + { + case '\"': + if (token_begin) { + TokenizeError("unexpected double-quote", line, column); + } + token_begin = cur; + in_double_quotes = true; + continue; + + case ';': + ProcessDataToken(output_tokens,token_begin,token_end,line,column); + comment = true; + continue; + + case '{': + ProcessDataToken(output_tokens,token_begin,token_end, line, column); + output_tokens.push_back(new_Token(cur,cur+1,TokenType_OPEN_BRACKET,line,column)); + continue; + + case '}': + ProcessDataToken(output_tokens,token_begin,token_end,line,column); + output_tokens.push_back(new_Token(cur,cur+1,TokenType_CLOSE_BRACKET,line,column)); + continue; + + case ',': + if (pending_data_token) { + ProcessDataToken(output_tokens,token_begin,token_end,line,column,TokenType_DATA,true); + } + output_tokens.push_back(new_Token(cur,cur+1,TokenType_COMMA,line,column)); + continue; + + case ':': + if (pending_data_token) { + ProcessDataToken(output_tokens,token_begin,token_end,line,column,TokenType_KEY,true); + } + else { + TokenizeError("unexpected colon", line, column); + } + continue; + } + + if (IsSpaceOrNewLine(c)) { + + if (token_begin) { + // peek ahead and check if the next token is a colon in which + // case this counts as KEY token. + TokenType type = TokenType_DATA; + for (const char* peek = cur; *peek && IsSpaceOrNewLine(*peek); ++peek) { + if (*peek == ':') { + type = TokenType_KEY; + cur = peek; + break; + } + } + + ProcessDataToken(output_tokens,token_begin,token_end,line,column,type); + } + + pending_data_token = false; + } + else { + token_end = cur; + if (!token_begin) { + token_begin = cur; + } + + pending_data_token = true; + } + } +} + +} // !FBX +} // !Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/FBX/FBXTokenizer.h b/libs/assimp/code/AssetLib/FBX/FBXTokenizer.h new file mode 100644 index 0000000..8779509 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXTokenizer.h @@ -0,0 +1,188 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXTokenizer.h + * @brief FBX lexer + */ +#ifndef INCLUDED_AI_FBX_TOKENIZER_H +#define INCLUDED_AI_FBX_TOKENIZER_H + +#include "FBXCompileConfig.h" +#include <assimp/ai_assert.h> +#include <assimp/defs.h> +#include <vector> +#include <string> + +namespace Assimp { +namespace FBX { + +/** Rough classification for text FBX tokens used for constructing the + * basic scope hierarchy. */ +enum TokenType +{ + // { + TokenType_OPEN_BRACKET = 0, + + // } + TokenType_CLOSE_BRACKET, + + // '"blablubb"', '2', '*14' - very general token class, + // further processing happens at a later stage. + TokenType_DATA, + + // + TokenType_BINARY_DATA, + + // , + TokenType_COMMA, + + // blubb: + TokenType_KEY +}; + + +/** Represents a single token in a FBX file. Tokens are + * classified by the #TokenType enumerated types. + * + * Offers iterator protocol. Tokens are immutable. */ +class Token +{ +private: + static const unsigned int BINARY_MARKER = static_cast<unsigned int>(-1); + +public: + /** construct a textual token */ + Token(const char* sbegin, const char* send, TokenType type, unsigned int line, unsigned int column); + + /** construct a binary token */ + Token(const char* sbegin, const char* send, TokenType type, size_t offset); + + ~Token(); + +public: + std::string StringContents() const { + return std::string(begin(),end()); + } + + bool IsBinary() const { + return column == BINARY_MARKER; + } + + const char* begin() const { + return sbegin; + } + + const char* end() const { + return send; + } + + TokenType Type() const { + return type; + } + + size_t Offset() const { + ai_assert(IsBinary()); + return offset; + } + + unsigned int Line() const { + ai_assert(!IsBinary()); + return static_cast<unsigned int>(line); + } + + unsigned int Column() const { + ai_assert(!IsBinary()); + return column; + } + +private: + +#ifdef DEBUG + // full string copy for the sole purpose that it nicely appears + // in msvc's debugger window. + const std::string contents; +#endif + + + const char* const sbegin; + const char* const send; + const TokenType type; + + union { + size_t line; + size_t offset; + }; + const unsigned int column; +}; + +// XXX should use C++11's unique_ptr - but assimp's need to keep working with 03 +typedef const Token* TokenPtr; +typedef std::vector< TokenPtr > TokenList; + +#define new_Token new Token + + +/** Main FBX tokenizer function. Transform input buffer into a list of preprocessed tokens. + * + * Skips over comments and generates line and column numbers. + * + * @param output_tokens Receives a list of all tokens in the input data. + * @param input_buffer Textual input buffer to be processed, 0-terminated. + * @throw DeadlyImportError if something goes wrong */ +void Tokenize(TokenList& output_tokens, const char* input); + + +/** Tokenizer function for binary FBX files. + * + * Emits a token list suitable for direct parsing. + * + * @param output_tokens Receives a list of all tokens in the input data. + * @param input_buffer Binary input buffer to be processed. + * @param length Length of input buffer, in bytes. There is no 0-terminal. + * @throw DeadlyImportError if something goes wrong */ +void TokenizeBinary(TokenList& output_tokens, const char* input, size_t length); + + +} // ! FBX +} // ! Assimp + +#endif // ! INCLUDED_AI_FBX_PARSER_H diff --git a/libs/assimp/code/AssetLib/FBX/FBXUtil.cpp b/libs/assimp/code/AssetLib/FBX/FBXUtil.cpp new file mode 100644 index 0000000..ac465d6 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXUtil.cpp @@ -0,0 +1,241 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXUtil.cpp + * @brief Implementation of internal FBX utility functions + */ + +#include "FBXUtil.h" +#include "FBXTokenizer.h" + +#include <assimp/TinyFormatter.h> +#include <string> +#include <cstring> + +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER + +namespace Assimp { +namespace FBX { +namespace Util { + +// ------------------------------------------------------------------------------------------------ +const char* TokenTypeString(TokenType t) +{ + switch(t) { + case TokenType_OPEN_BRACKET: + return "TOK_OPEN_BRACKET"; + + case TokenType_CLOSE_BRACKET: + return "TOK_CLOSE_BRACKET"; + + case TokenType_DATA: + return "TOK_DATA"; + + case TokenType_COMMA: + return "TOK_COMMA"; + + case TokenType_KEY: + return "TOK_KEY"; + + case TokenType_BINARY_DATA: + return "TOK_BINARY_DATA"; + } + + ai_assert(false); + return ""; +} + + +// ------------------------------------------------------------------------------------------------ +std::string GetOffsetText(size_t offset) +{ + return static_cast<std::string>( Formatter::format() << " (offset 0x" << std::hex << offset << ") " ); +} + +// ------------------------------------------------------------------------------------------------ +std::string GetLineAndColumnText(unsigned int line, unsigned int column) +{ + return static_cast<std::string>( Formatter::format() << " (line " << line << " << col " << column << ") " ); +} + +// ------------------------------------------------------------------------------------------------ +std::string GetTokenText(const Token* tok) +{ + if(tok->IsBinary()) { + return static_cast<std::string>( Formatter::format() << + " (" << TokenTypeString(tok->Type()) << + ", offset 0x" << std::hex << tok->Offset() << ") " ); + } + + return static_cast<std::string>( Formatter::format() << + " (" << TokenTypeString(tok->Type()) << + ", line " << tok->Line() << + ", col " << tok->Column() << ") " ); +} + +// Generated by this formula: T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; +static const uint8_t base64DecodeTable[128] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 255, 255, 255, + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, + 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255 +}; + +uint8_t DecodeBase64(char ch) +{ + const auto idx = static_cast<uint8_t>(ch); + if (idx > 127) + return 255; + return base64DecodeTable[idx]; +} + +size_t ComputeDecodedSizeBase64(const char* in, size_t inLength) +{ + if (inLength < 2) + { + return 0; + } + const size_t equals = size_t(in[inLength - 1] == '=') + size_t(in[inLength - 2] == '='); + const size_t full_length = (inLength * 3) >> 2; // div by 4 + if (full_length < equals) + { + return 0; + } + return full_length - equals; +} + +size_t DecodeBase64(const char* in, size_t inLength, uint8_t* out, size_t maxOutLength) +{ + if (maxOutLength == 0 || inLength < 2) { + return 0; + } + const size_t realLength = inLength - size_t(in[inLength - 1] == '=') - size_t(in[inLength - 2] == '='); + size_t dst_offset = 0; + int val = 0, valb = -8; + for (size_t src_offset = 0; src_offset < realLength; ++src_offset) + { + const uint8_t table_value = Util::DecodeBase64(in[src_offset]); + if (table_value == 255) + { + return 0; + } + val = (val << 6) + table_value; + valb += 6; + if (valb >= 0) + { + out[dst_offset++] = static_cast<uint8_t>((val >> valb) & 0xFF); + valb -= 8; + val &= 0xFFF; + } + } + return dst_offset; +} + +static const char to_base64_string[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +char EncodeBase64(char byte) +{ + return to_base64_string[(size_t)byte]; +} + +/** Encodes a block of 4 bytes to base64 encoding +* +* @param bytes Bytes to encode. +* @param out_string String to write encoded values to. +* @param string_pos Position in out_string.*/ +void EncodeByteBlock(const char* bytes, std::string& out_string, size_t string_pos) +{ + char b0 = (bytes[0] & 0xFC) >> 2; + char b1 = (bytes[0] & 0x03) << 4 | ((bytes[1] & 0xF0) >> 4); + char b2 = (bytes[1] & 0x0F) << 2 | ((bytes[2] & 0xC0) >> 6); + char b3 = (bytes[2] & 0x3F); + + out_string[string_pos + 0] = EncodeBase64(b0); + out_string[string_pos + 1] = EncodeBase64(b1); + out_string[string_pos + 2] = EncodeBase64(b2); + out_string[string_pos + 3] = EncodeBase64(b3); +} + +std::string EncodeBase64(const char* data, size_t length) +{ + // calculate extra bytes needed to get a multiple of 3 + size_t extraBytes = 3 - length % 3; + + // number of base64 bytes + size_t encodedBytes = 4 * (length + extraBytes) / 3; + + std::string encoded_string(encodedBytes, '='); + + // read blocks of 3 bytes + for (size_t ib3 = 0; ib3 < length / 3; ib3++) + { + const size_t iByte = ib3 * 3; + const size_t iEncodedByte = ib3 * 4; + const char* currData = &data[iByte]; + + EncodeByteBlock(currData, encoded_string, iEncodedByte); + } + + // if size of data is not a multiple of 3, also encode the final bytes (and add zeros where needed) + if (extraBytes > 0) + { + char finalBytes[4] = { 0,0,0,0 }; + memcpy(&finalBytes[0], &data[length - length % 3], length % 3); + + const size_t iEncodedByte = encodedBytes - 4; + EncodeByteBlock(&finalBytes[0], encoded_string, iEncodedByte); + + // add '=' at the end + for (size_t i = 0; i < 4 * extraBytes / 3; i++) + encoded_string[encodedBytes - i - 1] = '='; + } + return encoded_string; +} + +} // !Util +} // !FBX +} // !Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/FBX/FBXUtil.h b/libs/assimp/code/AssetLib/FBX/FBXUtil.h new file mode 100644 index 0000000..0e0bb75 --- /dev/null +++ b/libs/assimp/code/AssetLib/FBX/FBXUtil.h @@ -0,0 +1,130 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FBXUtil.h + * @brief FBX utility functions for internal use + */ +#ifndef INCLUDED_AI_FBX_UTIL_H +#define INCLUDED_AI_FBX_UTIL_H + +#include "FBXCompileConfig.h" +#include "FBXTokenizer.h" +#include <stdint.h> + +namespace Assimp { +namespace FBX { + + +namespace Util { + + +/** helper for std::for_each to delete all heap-allocated items in a container */ +template<typename T> +struct delete_fun +{ + void operator()(const volatile T* del) { + delete del; + } +}; + +/** Get a string representation for a #TokenType. */ +const char* TokenTypeString(TokenType t); + + + +/** Format log/error messages using a given offset in the source binary file + * + * @param offset offset within the file + * @return A string of the following format: " (offset 0x{offset}) "*/ +std::string GetOffsetText(size_t offset); + + +/** Format log/error messages using a given line location in the source file. + * + * @param line Line index, 1-based + * @param column Column index, 1-based + * @return A string of the following format: " (line {line}, col {column}) "*/ +std::string GetLineAndColumnText(unsigned int line, unsigned int column); + + +/** Format log/error messages using a given cursor token. + * + * @param tok Token where parsing/processing stopped + * @return A string of the following format: " ({token-type}, line {line}, col {column}) "*/ +std::string GetTokenText(const Token* tok); + +/** Decode a single Base64-encoded character. +* +* @param ch Character to decode (from base64 to binary). +* @return decoded byte value*/ +uint8_t DecodeBase64(char ch); + +/** Compute decoded size of a Base64-encoded string +* +* @param in Characters to decode. +* @param inLength Number of characters to decode. +* @return size of the decoded data (number of bytes)*/ +size_t ComputeDecodedSizeBase64(const char* in, size_t inLength); + +/** Decode a Base64-encoded string +* +* @param in Characters to decode. +* @param inLength Number of characters to decode. +* @param out Pointer where we will store the decoded data. +* @param maxOutLength Size of output buffer. +* @return size of the decoded data (number of bytes)*/ +size_t DecodeBase64(const char* in, size_t inLength, uint8_t* out, size_t maxOutLength); + +char EncodeBase64(char byte); + +/** Encode bytes in base64-encoding +* +* @param data Binary data to encode. +* @param inLength Number of bytes to encode. +* @return base64-encoded string*/ +std::string EncodeBase64(const char* data, size_t length); + +} +} +} + +#endif // ! INCLUDED_AI_FBX_UTIL_H diff --git a/libs/assimp/code/AssetLib/HMP/HMPFileData.h b/libs/assimp/code/AssetLib/HMP/HMPFileData.h new file mode 100644 index 0000000..b297136 --- /dev/null +++ b/libs/assimp/code/AssetLib/HMP/HMPFileData.h @@ -0,0 +1,137 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Data structures for the 3D Game Studio Heightmap format (HMP) +//! + +namespace Assimp { +namespace HMP { + +#include <assimp/Compiler/pushpack1.h> +#include <stdint.h> + +// to make it easier for us, we test the magic word against both "endianesses" +#define AI_HMP_MAGIC_NUMBER_BE_4 AI_MAKE_MAGIC("HMP4") +#define AI_HMP_MAGIC_NUMBER_LE_4 AI_MAKE_MAGIC("4PMH") + +#define AI_HMP_MAGIC_NUMBER_BE_5 AI_MAKE_MAGIC("HMP5") +#define AI_HMP_MAGIC_NUMBER_LE_5 AI_MAKE_MAGIC("5PMH") + +#define AI_HMP_MAGIC_NUMBER_BE_7 AI_MAKE_MAGIC("HMP7") +#define AI_HMP_MAGIC_NUMBER_LE_7 AI_MAKE_MAGIC("7PMH") + +// --------------------------------------------------------------------------- +/** Data structure for the header of a HMP5 file. + * This is also used by HMP4 and HMP7, but with modifications +*/ +struct Header_HMP5 +{ + int8_t ident[4]; // "HMP5" + int32_t version; + + // ignored + float scale[3]; + float scale_origin[3]; + float boundingradius; + + //! Size of one triangle in x direction + float ftrisize_x; + //! Size of one triangle in y direction + float ftrisize_y; + //! Number of vertices in x direction + float fnumverts_x; + + //! Number of skins in the file + int32_t numskins; + + // can ignore this? + int32_t skinwidth; + int32_t skinheight; + + //!Number of vertices in the file + int32_t numverts; + + // ignored and zero + int32_t numtris; + + //! only one supported ... + int32_t numframes; + + //! Always 0 ... + int32_t num_stverts; + int32_t flags; + float size; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** Data structure for a terrain vertex in a HMP4 file +*/ +struct Vertex_HMP4 +{ + uint16_t p_pos[3]; + uint8_t normals162index; + uint8_t pad; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** Data structure for a terrain vertex in a HMP5 file +*/ +struct Vertex_HMP5 +{ + uint16_t z; + uint8_t normals162index; + uint8_t pad; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** Data structure for a terrain vertex in a HMP7 file +*/ +struct Vertex_HMP7 +{ + uint16_t z; + int8_t normal_x,normal_y; +} PACK_STRUCT; + +#include <assimp/Compiler/poppack1.h> + +} //! namespace HMP +} //! namespace Assimp diff --git a/libs/assimp/code/AssetLib/HMP/HMPLoader.cpp b/libs/assimp/code/AssetLib/HMP/HMPLoader.cpp new file mode 100644 index 0000000..1625cb3 --- /dev/null +++ b/libs/assimp/code/AssetLib/HMP/HMPLoader.cpp @@ -0,0 +1,508 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the MDL importer class */ + +#ifndef ASSIMP_BUILD_NO_HMP_IMPORTER + +// internal headers +#include "AssetLib/HMP/HMPLoader.h" +#include "AssetLib/MD2/MD2FileData.h" + +#include <assimp/StringUtils.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> + +#include <memory> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "3D GameStudio Heightmap (HMP) Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "hmp" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +HMPImporter::HMPImporter() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +HMPImporter::~HMPImporter() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool HMPImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const uint32_t tokens[] = { + AI_HMP_MAGIC_NUMBER_LE_4, + AI_HMP_MAGIC_NUMBER_LE_5, + AI_HMP_MAGIC_NUMBER_LE_7 + }; + return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Get list of all file extensions that are handled by this loader +const aiImporterDesc *HMPImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void HMPImporter::InternReadFile(const std::string &pFile, + aiScene *_pScene, IOSystem *_pIOHandler) { + pScene = _pScene; + mIOHandler = _pIOHandler; + std::unique_ptr<IOStream> file(mIOHandler->Open(pFile)); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open HMP file ", pFile, "."); + } + + // Check whether the HMP file is large enough to contain + // at least the file header + const size_t fileSize = file->FileSize(); + if (fileSize < 50) + throw DeadlyImportError("HMP File is too small."); + + // Allocate storage and copy the contents of the file to a memory buffer + mBuffer = new uint8_t[fileSize]; + file->Read((void *)mBuffer, 1, fileSize); + iFileSize = (unsigned int)fileSize; + + // Determine the file subtype and call the appropriate member function + const uint32_t iMagic = *((uint32_t *)this->mBuffer); + + // HMP4 format + if (AI_HMP_MAGIC_NUMBER_LE_4 == iMagic || + AI_HMP_MAGIC_NUMBER_BE_4 == iMagic) { + ASSIMP_LOG_DEBUG("HMP subtype: 3D GameStudio A4, magic word is HMP4"); + InternReadFile_HMP4(); + } + // HMP5 format + else if (AI_HMP_MAGIC_NUMBER_LE_5 == iMagic || + AI_HMP_MAGIC_NUMBER_BE_5 == iMagic) { + ASSIMP_LOG_DEBUG("HMP subtype: 3D GameStudio A5, magic word is HMP5"); + InternReadFile_HMP5(); + } + // HMP7 format + else if (AI_HMP_MAGIC_NUMBER_LE_7 == iMagic || + AI_HMP_MAGIC_NUMBER_BE_7 == iMagic) { + ASSIMP_LOG_DEBUG("HMP subtype: 3D GameStudio A7, magic word is HMP7"); + InternReadFile_HMP7(); + } else { + // Print the magic word to the logger + std::string szBuffer = ai_str_toprintable((const char *)&iMagic, sizeof(iMagic)); + + delete[] mBuffer; + mBuffer = nullptr; + + // We're definitely unable to load this file + throw DeadlyImportError("Unknown HMP subformat ", pFile, + ". Magic word (", szBuffer, ") is not known"); + } + + // Set the AI_SCENE_FLAGS_TERRAIN bit + pScene->mFlags |= AI_SCENE_FLAGS_TERRAIN; + + delete[] mBuffer; + mBuffer = nullptr; +} + +// ------------------------------------------------------------------------------------------------ +void HMPImporter::ValidateHeader_HMP457() { + const HMP::Header_HMP5 *const pcHeader = (const HMP::Header_HMP5 *)mBuffer; + + if (120 > iFileSize) { + throw DeadlyImportError("HMP file is too small (header size is " + "120 bytes, this file is smaller)"); + } + + if (!pcHeader->ftrisize_x || !pcHeader->ftrisize_y) + throw DeadlyImportError("Size of triangles in either x or y direction is zero"); + + if (pcHeader->fnumverts_x < 1.0f || (pcHeader->numverts / pcHeader->fnumverts_x) < 1.0f) + throw DeadlyImportError("Number of triangles in either x or y direction is zero"); + + if (!pcHeader->numframes) + throw DeadlyImportError("There are no frames. At least one should be there"); +} + +// ------------------------------------------------------------------------------------------------ +void HMPImporter::InternReadFile_HMP4() { + throw DeadlyImportError("HMP4 is currently not supported"); +} + +// ------------------------------------------------------------------------------------------------ +void HMPImporter::InternReadFile_HMP5() { + // read the file header and skip everything to byte 84 + const HMP::Header_HMP5 *pcHeader = (const HMP::Header_HMP5 *)mBuffer; + const unsigned char *szCurrent = (const unsigned char *)(mBuffer + 84); + ValidateHeader_HMP457(); + + // generate an output mesh + pScene->mNumMeshes = 1; + pScene->mMeshes = new aiMesh *[1]; + aiMesh *pcMesh = pScene->mMeshes[0] = new aiMesh(); + + pcMesh->mMaterialIndex = 0; + pcMesh->mVertices = new aiVector3D[pcHeader->numverts]; + pcMesh->mNormals = new aiVector3D[pcHeader->numverts]; + + const unsigned int height = (unsigned int)(pcHeader->numverts / pcHeader->fnumverts_x); + const unsigned int width = (unsigned int)pcHeader->fnumverts_x; + + // generate/load a material for the terrain + CreateMaterial(szCurrent, &szCurrent); + + // goto offset 120, I don't know why ... + // (fixme) is this the frame header? I assume yes since it starts with 2. + szCurrent += 36; + SizeCheck(szCurrent + sizeof(const HMP::Vertex_HMP7) * height * width); + + // now load all vertices from the file + aiVector3D *pcVertOut = pcMesh->mVertices; + aiVector3D *pcNorOut = pcMesh->mNormals; + const HMP::Vertex_HMP5 *src = (const HMP::Vertex_HMP5 *)szCurrent; + for (unsigned int y = 0; y < height; ++y) { + for (unsigned int x = 0; x < width; ++x) { + pcVertOut->x = x * pcHeader->ftrisize_x; + pcVertOut->y = y * pcHeader->ftrisize_y; + pcVertOut->z = (((float)src->z / 0xffff) - 0.5f) * pcHeader->ftrisize_x * 8.0f; + MD2::LookupNormalIndex(src->normals162index, *pcNorOut); + ++pcVertOut; + ++pcNorOut; + ++src; + } + } + + // generate texture coordinates if necessary + if (pcHeader->numskins) + GenerateTextureCoords(width, height); + + // now build a list of faces + CreateOutputFaceList(width, height); + + // there is no nodegraph in HMP files. Simply assign the one mesh + // (no, not the one ring) to the root node + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mName.Set("terrain_root"); + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mRootNode->mMeshes[0] = 0; +} + +// ------------------------------------------------------------------------------------------------ +void HMPImporter::InternReadFile_HMP7() { + // read the file header and skip everything to byte 84 + const HMP::Header_HMP5 *const pcHeader = (const HMP::Header_HMP5 *)mBuffer; + const unsigned char *szCurrent = (const unsigned char *)(mBuffer + 84); + ValidateHeader_HMP457(); + + // generate an output mesh + pScene->mNumMeshes = 1; + pScene->mMeshes = new aiMesh *[1]; + aiMesh *pcMesh = pScene->mMeshes[0] = new aiMesh(); + + pcMesh->mMaterialIndex = 0; + pcMesh->mVertices = new aiVector3D[pcHeader->numverts]; + pcMesh->mNormals = new aiVector3D[pcHeader->numverts]; + + const unsigned int height = (unsigned int)(pcHeader->numverts / pcHeader->fnumverts_x); + const unsigned int width = (unsigned int)pcHeader->fnumverts_x; + + // generate/load a material for the terrain + CreateMaterial(szCurrent, &szCurrent); + + // goto offset 120, I don't know why ... + // (fixme) is this the frame header? I assume yes since it starts with 2. + szCurrent += 36; + + SizeCheck(szCurrent + sizeof(const HMP::Vertex_HMP7) * height * width); + + // now load all vertices from the file + aiVector3D *pcVertOut = pcMesh->mVertices; + ai_assert(pcVertOut != nullptr); + aiVector3D *pcNorOut = pcMesh->mNormals; + ai_assert(pcNorOut != nullptr); + const HMP::Vertex_HMP7 *src = (const HMP::Vertex_HMP7 *)szCurrent; + for (unsigned int y = 0; y < height; ++y) { + for (unsigned int x = 0; x < width; ++x) { + pcVertOut->x = x * pcHeader->ftrisize_x; + pcVertOut->y = y * pcHeader->ftrisize_y; + + // FIXME: What exctly is the correct scaling factor to use? + // possibly pcHeader->scale_origin[2] in combination with a + // signed interpretation of src->z? + pcVertOut->z = (((float)src->z / 0xffff) - 0.5f) * pcHeader->ftrisize_x * 8.0f; + + pcNorOut->x = ((float)src->normal_x / 0x80); // * pcHeader->scale_origin[0]; + pcNorOut->y = ((float)src->normal_y / 0x80); // * pcHeader->scale_origin[1]; + pcNorOut->z = 1.0f; + pcNorOut->Normalize(); + + ++pcVertOut; + ++pcNorOut; + ++src; + } + } + + // generate texture coordinates if necessary + if (pcHeader->numskins) GenerateTextureCoords(width, height); + + // now build a list of faces + CreateOutputFaceList(width, height); + + // there is no nodegraph in HMP files. Simply assign the one mesh + // (no, not the One Ring) to the root node + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mName.Set("terrain_root"); + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mRootNode->mMeshes[0] = 0; +} + +// ------------------------------------------------------------------------------------------------ +void HMPImporter::CreateMaterial(const unsigned char *szCurrent, + const unsigned char **szCurrentOut) { + aiMesh *const pcMesh = pScene->mMeshes[0]; + const HMP::Header_HMP5 *const pcHeader = (const HMP::Header_HMP5 *)mBuffer; + + // we don't need to generate texture coordinates if + // we have no textures in the file ... + if (pcHeader->numskins) { + pcMesh->mTextureCoords[0] = new aiVector3D[pcHeader->numverts]; + pcMesh->mNumUVComponents[0] = 2; + + // now read the first skin and skip all others + ReadFirstSkin(pcHeader->numskins, szCurrent, &szCurrent); + *szCurrentOut = szCurrent; + return; + } + + // generate a default material + const int iMode = (int)aiShadingMode_Gouraud; + aiMaterial *pcHelper = new aiMaterial(); + pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + aiColor3D clr; + clr.b = clr.g = clr.r = 0.6f; + pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_SPECULAR); + + clr.b = clr.g = clr.r = 0.05f; + pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_AMBIENT); + + aiString szName; + szName.Set(AI_DEFAULT_MATERIAL_NAME); + pcHelper->AddProperty(&szName, AI_MATKEY_NAME); + + // add the material to the scene + pScene->mNumMaterials = 1; + pScene->mMaterials = new aiMaterial *[1]; + pScene->mMaterials[0] = pcHelper; + *szCurrentOut = szCurrent; +} + +// ------------------------------------------------------------------------------------------------ +void HMPImporter::CreateOutputFaceList(unsigned int width, unsigned int height) { + aiMesh *const pcMesh = this->pScene->mMeshes[0]; + + // Allocate enough storage + pcMesh->mNumFaces = (width - 1) * (height - 1); + pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; + + pcMesh->mNumVertices = pcMesh->mNumFaces * 4; + aiVector3D *pcVertices = new aiVector3D[pcMesh->mNumVertices]; + aiVector3D *pcNormals = new aiVector3D[pcMesh->mNumVertices]; + + aiFace *pcFaceOut(pcMesh->mFaces); + aiVector3D *pcVertOut = pcVertices; + aiVector3D *pcNorOut = pcNormals; + + aiVector3D *pcUVs = pcMesh->mTextureCoords[0] ? new aiVector3D[pcMesh->mNumVertices] : nullptr; + aiVector3D *pcUVOut(pcUVs); + + // Build the terrain square + const unsigned int upperBound = pcMesh->mNumVertices; + unsigned int iCurrent = 0; + for (unsigned int y = 0; y < height - 1; ++y) { + const size_t offset0 = y * width; + const size_t offset1 = (y + 1) * width; + for (unsigned int x = 0; x < width - 1; ++x, ++pcFaceOut) { + pcFaceOut->mNumIndices = 4; + pcFaceOut->mIndices = new unsigned int[4]; + if ((offset0 + x + 1) >= upperBound){ + continue; + } + if ((offset1 + x + 1) >= upperBound){ + continue; + } + + *pcVertOut++ = pcMesh->mVertices[offset0 + x]; + *pcVertOut++ = pcMesh->mVertices[offset1 + x]; + *pcVertOut++ = pcMesh->mVertices[offset1 + x + 1]; + *pcVertOut++ = pcMesh->mVertices[offset0 + x + 1]; + + *pcNorOut++ = pcMesh->mNormals[offset0 + x]; + *pcNorOut++ = pcMesh->mNormals[offset1 + x]; + *pcNorOut++ = pcMesh->mNormals[offset1 + x + 1]; + *pcNorOut++ = pcMesh->mNormals[offset0 + x + 1]; + + if (pcMesh->mTextureCoords[0]) { + *pcUVOut++ = pcMesh->mTextureCoords[0][offset0 + x]; + *pcUVOut++ = pcMesh->mTextureCoords[0][offset1 + x]; + *pcUVOut++ = pcMesh->mTextureCoords[0][offset1 + x + 1]; + *pcUVOut++ = pcMesh->mTextureCoords[0][offset0 + x + 1]; + } + + for (unsigned int i = 0; i < 4; ++i) + pcFaceOut->mIndices[i] = iCurrent++; + } + } + delete[] pcMesh->mVertices; + pcMesh->mVertices = pcVertices; + + delete[] pcMesh->mNormals; + pcMesh->mNormals = pcNormals; + + if (pcMesh->mTextureCoords[0]) { + delete[] pcMesh->mTextureCoords[0]; + pcMesh->mTextureCoords[0] = pcUVs; + } +} + +// ------------------------------------------------------------------------------------------------ +void HMPImporter::ReadFirstSkin(unsigned int iNumSkins, const unsigned char *szCursor, + const unsigned char **szCursorOut) { + ai_assert(0 != iNumSkins); + ai_assert(nullptr != szCursor); + + // read the type of the skin ... + // sometimes we need to skip 12 bytes here, I don't know why ... + uint32_t iType = *((uint32_t *)szCursor); + szCursor += sizeof(uint32_t); + if (0 == iType) { + szCursor += sizeof(uint32_t) * 2; + iType = *((uint32_t *)szCursor); + szCursor += sizeof(uint32_t); + if (!iType) + throw DeadlyImportError("Unable to read HMP7 skin chunk"); + } + // read width and height + uint32_t iWidth = *((uint32_t *)szCursor); + szCursor += sizeof(uint32_t); + uint32_t iHeight = *((uint32_t *)szCursor); + szCursor += sizeof(uint32_t); + + // allocate an output material + aiMaterial *pcMat = new aiMaterial(); + + // read the skin, this works exactly as for MDL7 + ParseSkinLump_3DGS_MDL7(szCursor, &szCursor, + pcMat, iType, iWidth, iHeight); + + // now we need to skip any other skins ... + for (unsigned int i = 1; i < iNumSkins; ++i) { + SizeCheck(szCursor + 3 * sizeof(uint32_t)); + iType = *((uint32_t *)szCursor); + szCursor += sizeof(uint32_t); + iWidth = *((uint32_t *)szCursor); + szCursor += sizeof(uint32_t); + iHeight = *((uint32_t *)szCursor); + szCursor += sizeof(uint32_t); + + SkipSkinLump_3DGS_MDL7(szCursor, &szCursor, iType, iWidth, iHeight); + SizeCheck(szCursor); + } + + // setup the material ... + pScene->mNumMaterials = 1; + pScene->mMaterials = new aiMaterial *[1]; + pScene->mMaterials[0] = pcMat; + + *szCursorOut = szCursor; +} + +// ------------------------------------------------------------------------------------------------ +// Generate proepr texture coords +void HMPImporter::GenerateTextureCoords(const unsigned int width, const unsigned int height) { + ai_assert(nullptr != pScene->mMeshes); + ai_assert(nullptr != pScene->mMeshes[0]); + ai_assert(nullptr != pScene->mMeshes[0]->mTextureCoords[0]); + + aiVector3D *uv = pScene->mMeshes[0]->mTextureCoords[0]; + if (uv == nullptr) { + return; + } + + if (height == 0.0f || width == 0.0) { + return; + } + + const float fY = (1.0f / height) + (1.0f / height) / height; + const float fX = (1.0f / width) + (1.0f / width) / width; + + for (unsigned int y = 0; y < height; ++y) { + for (unsigned int x = 0; x < width; ++x, ++uv) { + uv->y = fY * y; + uv->x = fX * x; + uv->z = 0.0f; + } + } +} + +#endif // !! ASSIMP_BUILD_NO_HMP_IMPORTER diff --git a/libs/assimp/code/AssetLib/HMP/HMPLoader.h b/libs/assimp/code/AssetLib/HMP/HMPLoader.h new file mode 100644 index 0000000..95ce0a9 --- /dev/null +++ b/libs/assimp/code/AssetLib/HMP/HMPLoader.h @@ -0,0 +1,140 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 HMPLoader.h + * @brief Declaration of the HMP importer class + */ +#pragma once +#ifndef AI_HMPLOADER_H_INCLUDED +#define AI_HMPLOADER_H_INCLUDED + +#include <assimp/BaseImporter.h> + +// internal headers +#include "AssetLib/HMP/HMPFileData.h" +#include "AssetLib/MDL/MDLLoader.h" + +namespace Assimp { +using namespace HMP; + +// --------------------------------------------------------------------------- +/** Used to load 3D GameStudio HMP files (terrains) +*/ +class HMPImporter : public MDLImporter { +public: + HMPImporter(); + ~HMPImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + + // ------------------------------------------------------------------- + /** Import a HMP4 file + */ + void InternReadFile_HMP4(); + + // ------------------------------------------------------------------- + /** Import a HMP5 file + */ + void InternReadFile_HMP5(); + + // ------------------------------------------------------------------- + /** Import a HMP7 file + */ + void InternReadFile_HMP7(); + + // ------------------------------------------------------------------- + /** Validate a HMP 5,4,7 file header + */ + void ValidateHeader_HMP457(); + + // ------------------------------------------------------------------- + /** Try to load one material from the file, if this fails create + * a default material + */ + void CreateMaterial(const unsigned char *szCurrent, + const unsigned char **szCurrentOut); + + // ------------------------------------------------------------------- + /** Build a list of output faces and vertices. The function + * triangulates the height map read from the file + * \param width Width of the height field + * \param width Height of the height field + */ + void CreateOutputFaceList(unsigned int width, unsigned int height); + + // ------------------------------------------------------------------- + /** Generate planar texture coordinates for a terrain + * \param width Width of the terrain, in vertices + * \param height Height of the terrain, in vertices + */ + void GenerateTextureCoords(const unsigned int width, + const unsigned int height); + + // ------------------------------------------------------------------- + /** Read the first skin from the file and skip all others ... + * \param iNumSkins Number of skins in the file + * \param szCursor Position of the first skin (offset 84) + */ + void ReadFirstSkin(unsigned int iNumSkins, const unsigned char *szCursor, + const unsigned char **szCursorOut); +}; + +} // end of namespace Assimp + +#endif // AI_HMPIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/HMP/HalfLifeFileData.h b/libs/assimp/code/AssetLib/HMP/HalfLifeFileData.h new file mode 100644 index 0000000..f47862b --- /dev/null +++ b/libs/assimp/code/AssetLib/HMP/HalfLifeFileData.h @@ -0,0 +1,149 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Definition of in-memory structures for the HL2 MDL file format +// and for the HalfLife text format (SMD) +// +// The specification has been taken from various sources on the internet. + + +#ifndef AI_MDLFILEHELPER2_H_INC +#define AI_MDLFILEHELPER2_H_INC + +#include <assimp/Compiler/pushpack1.h> + +namespace Assimp { +namespace MDL { + +// magic bytes used in Half Life 2 MDL models +#define AI_MDL_MAGIC_NUMBER_BE_HL2a AI_MAKE_MAGIC("IDST") +#define AI_MDL_MAGIC_NUMBER_LE_HL2a AI_MAKE_MAGIC("TSDI") +#define AI_MDL_MAGIC_NUMBER_BE_HL2b AI_MAKE_MAGIC("IDSQ") +#define AI_MDL_MAGIC_NUMBER_LE_HL2b AI_MAKE_MAGIC("QSDI") + +// --------------------------------------------------------------------------- +/** \struct Header_HL2 + * \brief Data structure for the HL2 main header + */ +// --------------------------------------------------------------------------- +struct Header_HL2 { + //! magic number: "IDST"/"IDSQ" + char ident[4]; + + //! Version number + int32_t version; + + //! Original file name in pak ? + char name[64]; + + //! Length of file name/length of file? + int32_t length; + + //! For viewer, ignored + aiVector3D eyeposition; + aiVector3D min; + aiVector3D max; + + //! AABB of the model + aiVector3D bbmin; + aiVector3D bbmax; + + // File flags + int32_t flags; + + //! NUmber of bones contained in the file + int32_t numbones; + int32_t boneindex; + + //! Number of bone controllers for bone animation + int32_t numbonecontrollers; + int32_t bonecontrollerindex; + + //! More bounding boxes ... + int32_t numhitboxes; + int32_t hitboxindex; + + //! Animation sequences in the file + int32_t numseq; + int32_t seqindex; + + //! Loaded sequences. Ignored + int32_t numseqgroups; + int32_t seqgroupindex; + + //! Raw texture data + int32_t numtextures; + int32_t textureindex; + int32_t texturedataindex; + + //! Number of skins (=textures?) + int32_t numskinref; + int32_t numskinfamilies; + int32_t skinindex; + + //! Number of parts + int32_t numbodyparts; + int32_t bodypartindex; + + //! attachable points for gameplay and physics + int32_t numattachments; + int32_t attachmentindex; + + //! Table of sound effects associated with the model + int32_t soundtable; + int32_t soundindex; + int32_t soundgroups; + int32_t soundgroupindex; + + //! Number of animation transitions + int32_t numtransitions; + int32_t transitionindex; +} /* PACK_STRUCT */; + +#include <assimp/Compiler/poppack1.h> + +} +} // end namespaces + +#endif // ! AI_MDLFILEHELPER2_H_INC diff --git a/libs/assimp/code/AssetLib/IFC/IFCBoolean.cpp b/libs/assimp/code/AssetLib/IFC/IFCBoolean.cpp new file mode 100644 index 0000000..36912a7 --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCBoolean.cpp @@ -0,0 +1,765 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IFCBoolean.cpp + * @brief Implements a subset of Ifc boolean operations + */ + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER + +#include "AssetLib/IFC/IFCUtil.h" +#include "Common/PolyTools.h" +#include "PostProcessing/ProcessHelper.h" + + +#include <iterator> +#include <tuple> + +namespace Assimp { +namespace IFC { + +// ------------------------------------------------------------------------------------------------ +// 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 IfcFloat dotOne = n * seg, dotTwo = -(n * pdelta); + + // 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) < ai_epsilon) + return false; + + // if segment starts on the plane, report a hit only if the end lies on the *other* side + if (std::abs(dotTwo) < ai_epsilon) { + if ((assumeStartOnWhiteSide && dotOne + dotTwo < ai_epsilon) || (!assumeStartOnWhiteSide && dotOne + dotTwo > -ai_epsilon)) { + out = e0; + return true; + } else { + return false; + } + } + + // 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) < ai_epsilon) + return false; + + // t must be in [0..1] if the intersection point is within the given segment + const IfcFloat t = dotTwo / dotOne; + if (t > 1.0 || t < 0.0) + return false; + + out = e0 + t * seg; + return true; +} + +// ------------------------------------------------------------------------------------------------ +void FilterPolygon(std::vector<IfcVector3> &resultpoly) { + if (resultpoly.size() < 3) { + resultpoly.clear(); + return; + } + + IfcVector3 vmin, vmax; + ArrayBounds(resultpoly.data(), static_cast<unsigned int>(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<IfcVector3>::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<IfcVector3> &resultpoly, TempMesh &result) { + FilterPolygon(resultpoly); + + if (resultpoly.size() > 2) { + result.mVerts.insert(result.mVerts.end(), resultpoly.begin(), resultpoly.end()); + result.mVertcnt.push_back(static_cast<unsigned int>(resultpoly.size())); + } +} + +// ------------------------------------------------------------------------------------------------ +void ProcessBooleanHalfSpaceDifference(const Schema_2x3::IfcHalfSpaceSolid *hs, TempMesh &result, + const TempMesh &first_operand, + ConversionData & /*conv*/) { + ai_assert(hs != nullptr); + + const Schema_2x3::IfcPlane *const plane = hs->BaseSurface->ToPtr<Schema_2x3::IfcPlane>(); + if (!plane) { + IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid"); + return; + } + + // extract plane base position vector and normal vector + IfcVector3 p, n(0.f, 0.f, 1.f); + if (plane->Position->Axis) { + ConvertDirection(n, plane->Position->Axis.Get()); + } + ConvertCartesianPoint(p, plane->Position->Location); + + if (!IsTrue(hs->AgreementFlag)) { + n *= -1.f; + } + + // clip the current contents of `meshout` against the plane we obtained from the second operand + const std::vector<IfcVector3> &in = first_operand.mVerts; + std::vector<IfcVector3> &outvert = result.mVerts; + + std::vector<unsigned int>::const_iterator begin = first_operand.mVertcnt.begin(), + end = first_operand.mVertcnt.end(), iit; + + outvert.reserve(in.size()); + result.mVertcnt.reserve(first_operand.mVertcnt.size()); + + unsigned int vidx = 0; + for (iit = begin; iit != end; vidx += *iit++) { + + unsigned int newcount = 0; + bool isAtWhiteSide = (in[vidx] - p) * n > -ai_epsilon; + 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; + 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); + newcount += 2; + } else { + // e0 is on the wrong side, so drop it and keep e1 instead + outvert.push_back(isectpos); + ++newcount; + } + isAtWhiteSide = !isAtWhiteSide; + } else { + if (isAtWhiteSide) { + outvert.push_back(e0); + ++newcount; + } + } + } + + 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<IfcVector3>::iterator e = std::unique(outvert.end() - newcount, outvert.end(), fz); + + if (e != outvert.end()) { + newcount -= static_cast<unsigned int>(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.mVertcnt.push_back(newcount); + } else + while (newcount-- > 0) { + result.mVerts.pop_back(); + } + } + IFCImporter::LogVerboseDebug("generating CSG geometry by plane clipping (IfcBooleanClippingResult)"); +} + +// ------------------------------------------------------------------------------------------------ +// 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. +// 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<IfcVector3> &boundary, + const bool isStartAssumedInside, std::vector<std::pair<size_t, IfcVector3>> &intersect_results, + const bool halfOpen = false) { + ai_assert(intersect_results.empty()); + + // 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; + + 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]; + IfcVector3 b = b1 - b0; + + // 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::abs(det) < ai_epsilon) { + // no solutions (parallel lines) + continue; + } + IfcFloat b_sqlen_inv = 1.0 / b.SquareLength(); + + const IfcFloat x = b0.x - e0.x; + const IfcFloat y = b0.y - e0.y; + 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 - p; + ai_assert((IfcVector2(check.x, check.y)).SquareLength() < 1e-5); +#endif + + // 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; + } + + // Line segment ends at boundary -> ignore any hit, it will be handled by possibly following segments + if (endsAtSegment && !halfOpen) + continue; + + // 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; + } + 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 >= -ai_epsilon * b_sqlen_inv && s <= 1.0 + ai_epsilon * 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 !intersect_results.empty(); +} + +// ------------------------------------------------------------------------------------------------ +// note: this functions works on 3D vectors, but performs its intersection checks solely in xy. +bool PointInPoly(const IfcVector3 &p, const std::vector<IfcVector3> &boundary) { + // even-odd algorithm: take a random vector that extends from p to infinite + // and counts how many times it intersects edges of the boundary. + // because checking for segment intersections is prone to numeric inaccuracies + // or double detections (i.e. when hitting multiple adjacent segments at their + // shared vertices) we do it thrice with different rays and vote on it. + + // the even-odd algorithm doesn't work for points which lie directly on + // the border of the polygon. If any of our attempts produces this result, + // we return false immediately. + + std::vector<std::pair<size_t, IfcVector3>> intersected_boundary; + size_t votes = 0; + + IntersectsBoundaryProfile(p, p + IfcVector3(1.0, 0, 0), boundary, true, intersected_boundary, true); + votes += intersected_boundary.size() % 2; + + intersected_boundary.clear(); + IntersectsBoundaryProfile(p, p + IfcVector3(0, 1.0, 0), boundary, true, intersected_boundary, true); + votes += intersected_boundary.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; + + return votes > 1; +} + +// ------------------------------------------------------------------------------------------------ +void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const Schema_2x3::IfcPolygonalBoundedHalfSpace *hs, TempMesh &result, + const TempMesh &first_operand, + ConversionData &conv) { + ai_assert(hs != nullptr); + + const Schema_2x3::IfcPlane *const plane = hs->BaseSurface->ToPtr<Schema_2x3::IfcPlane>(); + if (!plane) { + IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid"); + return; + } + + // extract plane base position vector and normal vector + IfcVector3 p, n(0.f, 0.f, 1.f); + if (plane->Position->Axis) { + ConvertDirection(n, plane->Position->Axis.Get()); + } + ConvertCartesianPoint(p, plane->Position->Location); + + if (!IsTrue(hs->AgreementFlag)) { + n *= -1.f; + } + + n.Normalize(); + + // obtain the polygonal bounding volume + std::shared_ptr<TempMesh> profile = std::shared_ptr<TempMesh>(new TempMesh()); + if (!ProcessCurve(hs->PolygonalBoundary, *profile.get(), conv)) { + IFCImporter::LogError("expected valid polyline for boundary of boolean halfspace"); + return; + } + + // determine winding order by calculating the normal. + IfcVector3 profileNormal = TempMesh::ComputePolygonNormal(profile->mVerts.data(), profile->mVerts.size()); + + IfcMatrix4 proj_inv; + ConvertAxisPlacement(proj_inv, hs->Position); + + // and map everything into a plane coordinate space so all intersection + // tests can be done in 2D space. + IfcMatrix4 proj = proj_inv; + proj.Inverse(); + + // clip the current contents of `meshout` against the plane we obtained from the second operand + const std::vector<IfcVector3> &in = first_operand.mVerts; + std::vector<IfcVector3> &outvert = result.mVerts; + std::vector<unsigned int> &outvertcnt = result.mVertcnt; + + outvert.reserve(in.size()); + outvertcnt.reserve(first_operand.mVertcnt.size()); + + unsigned int vidx = 0; + std::vector<unsigned int>::const_iterator begin = first_operand.mVertcnt.begin(); + std::vector<unsigned int>::const_iterator end = first_operand.mVertcnt.end(); + std::vector<unsigned int>::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<IfcVector3> whiteside, blackside; + + { + const IfcVector3 *srcVertices = &in[vidx]; + const size_t srcVtxCount = *iit; + if (srcVtxCount == 0) + continue; + + IfcVector3 polyNormal = TempMesh::ComputePolygonNormal(srcVertices, srcVtxCount, true); + + // 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 = (srcVertices[0] - p) * n > -ai_epsilon; + std::vector<IfcVector3> &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 = ((srcVertices[0]) - p) * n > -ai_epsilon; + for (size_t a = 0; a < srcVtxCount; ++a) { + IfcVector3 e0 = srcVertices[a]; + IfcVector3 e1 = srcVertices[(a + 1) % srcVtxCount]; + IfcVector3 ei; + + // put starting point to the current mesh + std::vector<IfcVector3> &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) { + if (trgt.empty() || (trgt.back() - ei).SquareLength() > 1e-12) + trgt.push_back(ei); + isCurrentlyOnWhiteSide = !isCurrentlyOnWhiteSide; + std::vector<IfcVector3> &newtrgt = isCurrentlyOnWhiteSide ? whiteside : blackside; + newtrgt.push_back(ei); + } + } + } + } + + // the part on the white side can be written into the target mesh right away + WritePolygon(whiteside, result); + + // 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); + + // 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<std::tuple<size_t, IfcVector3, size_t>> intersections; + bool startedInside = PointInPoly(proj * blackside.front(), profile->mVerts); + bool isCurrentlyInside = startedInside; + + std::vector<std::pair<size_t, IfcVector3>> intersected_boundary; + + for (size_t a = 0; a < blackside.size(); ++a) { + const IfcVector3 e0 = proj * blackside[a]; + const IfcVector3 e1 = proj * blackside[(a + 1) % blackside.size()]; + + intersected_boundary.clear(); + IntersectsBoundaryProfile(e0, e1, profile->mVerts, 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]); + } + } + } + } + // now add them to the list of intersections + for (size_t b = 0; b < intersected_boundary.size(); ++b) + intersections.push_back(std::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 are facing a non-recoverable error. + 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 ((std::get<1>(intersections[a]) - std::get<1>(intersections[(a + 1) % intersections.size()])).SquareLength() < 1e-10) + intersections.erase(intersections.begin() + a, intersections.begin() + a + 2); + else + a++; + } + if (intersections.size() > 1 && (std::get<1>(intersections.back()) - std::get<1>(intersections.front())).SquareLength() < 1e-10) { + intersections.pop_back(); + intersections.erase(intersections.begin()); + } + } + + // 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(static_cast<unsigned int>(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; + } + } + + // 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; + + // 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<IfcVector3> resultpoly; + size_t currentIntersecIdx = 0; + + while (true) { + ai_assert(intersections.size() > currentIntersecIdx + 1); + std::tuple<size_t, IfcVector3, size_t> currintsec = intersections[currentIntersecIdx + 0]; + std::tuple<size_t, IfcVector3, size_t> nextintsec = intersections[currentIntersecIdx + 1]; + intersections.erase(intersections.begin() + currentIntersecIdx, intersections.begin() + currentIntersecIdx + 2); + + // we start with an in->out intersection + resultpoly.push_back(std::get<1>(currintsec)); + // climb along the polygon to the next intersection, which should be an out->in + size_t numPolyPoints = (std::get<0>(currintsec) > std::get<0>(nextintsec) ? blackside.size() : 0) + std::get<0>(nextintsec) - std::get<0>(currintsec); + for (size_t a = 1; a <= numPolyPoints; ++a) + resultpoly.push_back(blackside[(std::get<0>(currintsec) + a) % blackside.size()]); + // put the out->in intersection + resultpoly.push_back(std::get<1>(nextintsec)); + + // generate segments along the boundary polygon that lie in the poly's plane until we hit another intersection + IfcVector3 startingPoint = proj * std::get<1>(nextintsec); + size_t currentBoundaryEdgeIdx = (std::get<2>(nextintsec) + (marchBackwardsOnBoundary ? 1 : 0)) % profile->mVerts.size(); + size_t nextIntsecIdx = SIZE_MAX; + while (nextIntsecIdx == SIZE_MAX) { + IfcFloat t = 1e10; + + size_t nextBoundaryEdgeIdx = marchBackwardsOnBoundary ? (currentBoundaryEdgeIdx + profile->mVerts.size() - 1) : currentBoundaryEdgeIdx + 1; + nextBoundaryEdgeIdx %= profile->mVerts.size(); + // vertices of the current boundary segments + IfcVector3 currBoundaryPoint = profile->mVerts[currentBoundaryEdgeIdx]; + IfcVector3 nextBoundaryPoint = profile->mVerts[nextBoundaryEdgeIdx]; + // project the two onto the polygon + if (std::abs(polyNormal.z) > 1e-5) { + currBoundaryPoint.z = startingPoint.z + (currBoundaryPoint.x - startingPoint.x) * polyNormal.x / polyNormal.z + (currBoundaryPoint.y - startingPoint.y) * polyNormal.y / polyNormal.z; + nextBoundaryPoint.z = startingPoint.z + (nextBoundaryPoint.x - startingPoint.x) * polyNormal.x / polyNormal.z + (nextBoundaryPoint.y - startingPoint.y) * polyNormal.y / polyNormal.z; + } + + // 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()); + } + + // 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 * std::get<1>(intersections[a]) - 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; + } + } + + // 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; + } + + // quick endless loop check + if (resultpoly.size() > blackside.size() + profile->mVerts.size()) { + IFCImporter::LogError("Encountered endless loop while clipping polygon against poly-bounded half space."); + break; + } + } + + // 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); + } + } + } + IFCImporter::LogVerboseDebug("generating CSG geometry by plane clipping with polygonal bounding (IfcBooleanClippingResult)"); +} + +// ------------------------------------------------------------------------------------------------ +void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedAreaSolid *as, TempMesh &result, + const TempMesh &first_operand, + ConversionData &conv) { + ai_assert(as != nullptr); + + // This case is handled by reduction to an instance of the quadrify() algorithm. + // Obviously, this won't work for arbitrarily complex cases. In fact, the first + // operand should be near-planar. Luckily, this is usually the case in Ifc + // buildings. + + std::shared_ptr<TempMesh> meshtmp = std::shared_ptr<TempMesh>(new TempMesh()); + ProcessExtrudedAreaSolid(*as, *meshtmp, conv, false); + + std::vector<TempOpening> openings(1, TempOpening(as, IfcVector3(0, 0, 0), meshtmp, std::shared_ptr<TempMesh>())); + + result = first_operand; + + TempMesh temp; + + std::vector<IfcVector3>::const_iterator vit = first_operand.mVerts.begin(); + for (unsigned int pcount : first_operand.mVertcnt) { + temp.Clear(); + + temp.mVerts.insert(temp.mVerts.end(), vit, vit + pcount); + temp.mVertcnt.push_back(pcount); + + // The algorithms used to generate mesh geometry sometimes + // spit out lines or other degenerates which must be + // filtered to avoid running into assertions later on. + + // ComputePolygonNormal returns the Newell normal, so the + // length of the normal is the area of the polygon. + const IfcVector3 &normal = temp.ComputeLastPolygonNormal(false); + if (normal.SquareLength() < static_cast<IfcFloat>(1e-5)) { + IFCImporter::LogWarn("skipping degenerate polygon (ProcessBooleanExtrudedAreaSolidDifference)"); + continue; + } + + GenerateOpenings(openings, temp, false, true); + result.Append(temp); + + vit += pcount; + } + + IFCImporter::LogVerboseDebug("generating CSG geometry by geometric difference to a solid (IfcExtrudedAreaSolid)"); +} + +// ------------------------------------------------------------------------------------------------ +void ProcessBoolean(const Schema_2x3::IfcBooleanResult &boolean, TempMesh &result, ConversionData &conv) { + // supported CSG operations: + // DIFFERENCE + if (const Schema_2x3::IfcBooleanResult *const clip = boolean.ToPtr<Schema_2x3::IfcBooleanResult>()) { + if (clip->Operator != "DIFFERENCE") { + IFCImporter::LogWarn("encountered unsupported boolean operator: ", (std::string)clip->Operator); + return; + } + + // supported cases (1st operand): + // IfcBooleanResult -- call ProcessBoolean recursively + // IfcSweptAreaSolid -- obtain polygonal geometry first + + // supported cases (2nd operand): + // IfcHalfSpaceSolid -- easy, clip against plane + // IfcExtrudedAreaSolid -- reduce to an instance of the quadrify() algorithm + + const Schema_2x3::IfcHalfSpaceSolid *const hs = clip->SecondOperand->ResolveSelectPtr<Schema_2x3::IfcHalfSpaceSolid>(conv.db); + const Schema_2x3::IfcExtrudedAreaSolid *const as = clip->SecondOperand->ResolveSelectPtr<Schema_2x3::IfcExtrudedAreaSolid>(conv.db); + if (!hs && !as) { + IFCImporter::LogError("expected IfcHalfSpaceSolid or IfcExtrudedAreaSolid as second clipping operand"); + return; + } + + TempMesh first_operand; + if (const Schema_2x3::IfcBooleanResult *const op0 = clip->FirstOperand->ResolveSelectPtr<Schema_2x3::IfcBooleanResult>(conv.db)) { + ProcessBoolean(*op0, first_operand, conv); + } else if (const Schema_2x3::IfcSweptAreaSolid *const swept = clip->FirstOperand->ResolveSelectPtr<Schema_2x3::IfcSweptAreaSolid>(conv.db)) { + ProcessSweptAreaSolid(*swept, first_operand, conv); + } else { + IFCImporter::LogError("expected IfcSweptAreaSolid or IfcBooleanResult as first clipping operand"); + return; + } + + if (hs) { + + const Schema_2x3::IfcPolygonalBoundedHalfSpace *const hs_bounded = clip->SecondOperand->ResolveSelectPtr<Schema_2x3::IfcPolygonalBoundedHalfSpace>(conv.db); + if (hs_bounded) { + ProcessPolygonalBoundedBooleanHalfSpaceDifference(hs_bounded, result, first_operand, conv); + } else { + ProcessBooleanHalfSpaceDifference(hs, result, first_operand, conv); + } + } else { + ProcessBooleanExtrudedAreaSolidDifference(as, result, first_operand, conv); + } + } else { + IFCImporter::LogWarn("skipping unknown IfcBooleanResult entity, type is ", boolean.GetClassName()); + } +} + +} // namespace IFC +} // namespace Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/IFC/IFCCurve.cpp b/libs/assimp/code/AssetLib/IFC/IFCCurve.cpp new file mode 100644 index 0000000..19732ef --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCCurve.cpp @@ -0,0 +1,618 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IFCProfile.cpp + * @brief Read profile and curves entities from IFC files + */ + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER +#include "IFCUtil.h" + +namespace Assimp { +namespace IFC { +namespace { + + +// -------------------------------------------------------------------------------- +// Conic is the base class for Circle and Ellipse +// -------------------------------------------------------------------------------- +class Conic : public Curve { +public: + // -------------------------------------------------- + Conic(const Schema_2x3::IfcConic& entity, ConversionData& conv) + : Curve(entity,conv) { + IfcMatrix4 trafo; + ConvertAxisPlacement(trafo,*entity.Position,conv); + + // for convenience, extract the matrix rows + location = IfcVector3(trafo.a4,trafo.b4,trafo.c4); + p[0] = IfcVector3(trafo.a1,trafo.b1,trafo.c1); + p[1] = IfcVector3(trafo.a2,trafo.b2,trafo.c2); + p[2] = IfcVector3(trafo.a3,trafo.b3,trafo.c3); + } + + // -------------------------------------------------- + bool IsClosed() const { + return true; + } + + // -------------------------------------------------- + size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const { + ai_assert( InRange( a ) ); + ai_assert( InRange( b ) ); + + a *= conv.angle_scale; + b *= conv.angle_scale; + + a = std::fmod(a,static_cast<IfcFloat>( AI_MATH_TWO_PI )); + b = std::fmod(b,static_cast<IfcFloat>( AI_MATH_TWO_PI )); + const IfcFloat setting = static_cast<IfcFloat>( AI_MATH_PI * conv.settings.conicSamplingAngle / 180.0 ); + return static_cast<size_t>( std::ceil(std::abs( b-a)) / setting); + } + + // -------------------------------------------------- + ParamRange GetParametricRange() const { + return std::make_pair(static_cast<IfcFloat>( 0. ), static_cast<IfcFloat>( AI_MATH_TWO_PI / conv.angle_scale )); + } + +protected: + IfcVector3 location, p[3]; +}; + +// -------------------------------------------------------------------------------- +// Circle +// -------------------------------------------------------------------------------- +class Circle : public Conic { +public: + // -------------------------------------------------- + Circle(const Schema_2x3::IfcCircle& entity, ConversionData& conv) + : Conic(entity,conv) + , entity(entity) + { + } + + // -------------------------------------------------- + IfcVector3 Eval(IfcFloat u) const { + u = -conv.angle_scale * u; + return location + static_cast<IfcFloat>(entity.Radius)*(static_cast<IfcFloat>(std::cos(u))*p[0] + + static_cast<IfcFloat>(std::sin(u))*p[1]); + } + +private: + const Schema_2x3::IfcCircle& entity; +}; + +// -------------------------------------------------------------------------------- +// Ellipse +// -------------------------------------------------------------------------------- +class Ellipse : public Conic { +public: + // -------------------------------------------------- + Ellipse(const Schema_2x3::IfcEllipse& entity, ConversionData& conv) + : Conic(entity,conv) + , entity(entity) { + // empty + } + + // -------------------------------------------------- + IfcVector3 Eval(IfcFloat u) const { + u = -conv.angle_scale * u; + return location + static_cast<IfcFloat>(entity.SemiAxis1)*static_cast<IfcFloat>(std::cos(u))*p[0] + + static_cast<IfcFloat>(entity.SemiAxis2)*static_cast<IfcFloat>(std::sin(u))*p[1]; + } + +private: + const Schema_2x3::IfcEllipse& entity; +}; + +// -------------------------------------------------------------------------------- +// Line +// -------------------------------------------------------------------------------- +class Line : public Curve { +public: + // -------------------------------------------------- + Line(const Schema_2x3::IfcLine& entity, ConversionData& conv) + : Curve(entity,conv) { + ConvertCartesianPoint(p,entity.Pnt); + ConvertVector(v,entity.Dir); + } + + // -------------------------------------------------- + bool IsClosed() const { + return false; + } + + // -------------------------------------------------- + IfcVector3 Eval(IfcFloat u) const { + return p + u*v; + } + + // -------------------------------------------------- + size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const { + ai_assert( InRange( a ) ); + ai_assert( InRange( b ) ); + // two points are always sufficient for a line segment + return a==b ? 1 : 2; + } + + + // -------------------------------------------------- + void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const { + ai_assert( InRange( a ) ); + ai_assert( InRange( b ) ); + + if (a == b) { + out.mVerts.push_back(Eval(a)); + return; + } + out.mVerts.reserve(out.mVerts.size()+2); + out.mVerts.push_back(Eval(a)); + out.mVerts.push_back(Eval(b)); + } + + // -------------------------------------------------- + ParamRange GetParametricRange() const { + const IfcFloat inf = std::numeric_limits<IfcFloat>::infinity(); + + return std::make_pair(-inf,+inf); + } + +private: + IfcVector3 p,v; +}; + +// -------------------------------------------------------------------------------- +// CompositeCurve joins multiple smaller, bounded curves +// -------------------------------------------------------------------------------- +class CompositeCurve : public BoundedCurve { + typedef std::pair< std::shared_ptr< BoundedCurve >, bool > CurveEntry; + +public: + // -------------------------------------------------- + CompositeCurve(const Schema_2x3::IfcCompositeCurve& entity, ConversionData& conv) + : BoundedCurve(entity,conv) + , total() { + curves.reserve(entity.Segments.size()); + for(const Schema_2x3::IfcCompositeCurveSegment& curveSegment :entity.Segments) { + // according to the specification, this must be a bounded curve + std::shared_ptr< Curve > cv(Curve::Convert(curveSegment.ParentCurve,conv)); + std::shared_ptr< BoundedCurve > bc = std::dynamic_pointer_cast<BoundedCurve>(cv); + + if (!bc) { + IFCImporter::LogError("expected segment of composite curve to be a bounded curve"); + continue; + } + + if ( (std::string)curveSegment.Transition != "CONTINUOUS" ) { + IFCImporter::LogVerboseDebug("ignoring transition code on composite curve segment, only continuous transitions are supported"); + } + + curves.push_back( CurveEntry(bc,IsTrue(curveSegment.SameSense)) ); + total += bc->GetParametricRangeDelta(); + } + + if (curves.empty()) { + throw CurveError("empty composite curve"); + } + } + + // -------------------------------------------------- + IfcVector3 Eval(IfcFloat u) const { + if (curves.empty()) { + return IfcVector3(); + } + + IfcFloat acc = 0; + for(const CurveEntry& entry : curves) { + const ParamRange& range = entry.first->GetParametricRange(); + const IfcFloat delta = std::abs(range.second-range.first); + if (u < acc+delta) { + return entry.first->Eval( entry.second ? (u-acc) + range.first : range.second-(u-acc)); + } + + acc += delta; + } + // clamp to end + return curves.back().first->Eval(curves.back().first->GetParametricRange().second); + } + + // -------------------------------------------------- + size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const { + ai_assert( InRange( a ) ); + ai_assert( InRange( b ) ); + size_t cnt = 0; + + IfcFloat acc = 0; + for(const CurveEntry& entry : curves) { + const ParamRange& range = entry.first->GetParametricRange(); + const IfcFloat delta = std::abs(range.second-range.first); + if (a <= acc+delta && b >= acc) { + const IfcFloat at = std::max(static_cast<IfcFloat>( 0. ),a-acc), bt = std::min(delta,b-acc); + cnt += entry.first->EstimateSampleCount( entry.second ? at + range.first : range.second - bt, entry.second ? bt + range.first : range.second - at ); + } + + acc += delta; + } + + return cnt; + } + + // -------------------------------------------------- + void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const { + ai_assert( InRange( a ) ); + ai_assert( InRange( b ) ); + + const size_t cnt = EstimateSampleCount(a,b); + out.mVerts.reserve(out.mVerts.size() + cnt); + + for(const CurveEntry& entry : curves) { + const size_t curCnt = out.mVerts.size(); + entry.first->SampleDiscrete(out); + + if (!entry.second && curCnt != out.mVerts.size()) { + std::reverse(out.mVerts.begin() + curCnt, out.mVerts.end()); + } + } + } + + // -------------------------------------------------- + ParamRange GetParametricRange() const { + return std::make_pair(static_cast<IfcFloat>( 0. ),total); + } + +private: + std::vector< CurveEntry > curves; + IfcFloat total; +}; + +// -------------------------------------------------------------------------------- +// TrimmedCurve can be used to trim an unbounded curve to a bounded range +// -------------------------------------------------------------------------------- +class TrimmedCurve : public BoundedCurve { +public: + // -------------------------------------------------- + TrimmedCurve(const Schema_2x3::IfcTrimmedCurve& entity, ConversionData& conv) + : BoundedCurve(entity,conv), + base(std::shared_ptr<const Curve>(Curve::Convert(entity.BasisCurve,conv))) + { + typedef std::shared_ptr<const STEP::EXPRESS::DataType> Entry; + + // for some reason, trimmed curves can either specify a parametric value + // or a point on the curve, or both. And they can even specify which of the + // two representations they prefer, even though an information invariant + // claims that they must be identical if both are present. + // oh well. + bool have_param = false, have_point = false; + IfcVector3 point; + for(const Entry& sel :entity.Trim1) { + if (const ::Assimp::STEP::EXPRESS::REAL* const r = sel->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) { + range.first = *r; + have_param = true; + break; + } + else if (const Schema_2x3::IfcCartesianPoint* const curR = sel->ResolveSelectPtr<Schema_2x3::IfcCartesianPoint>(conv.db)) { + ConvertCartesianPoint(point, *curR); + have_point = true; + } + } + if (!have_param) { + if (!have_point || !base->ReverseEval(point,range.first)) { + throw CurveError("IfcTrimmedCurve: failed to read first trim parameter, ignoring curve"); + } + } + have_param = false, have_point = false; + for(const Entry& sel :entity.Trim2) { + if (const ::Assimp::STEP::EXPRESS::REAL* const r = sel->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) { + range.second = *r; + have_param = true; + break; + } + else if (const Schema_2x3::IfcCartesianPoint* const curR = sel->ResolveSelectPtr<Schema_2x3::IfcCartesianPoint>(conv.db)) { + ConvertCartesianPoint(point, *curR); + have_point = true; + } + } + if (!have_param) { + if (!have_point || !base->ReverseEval(point,range.second)) { + throw CurveError("IfcTrimmedCurve: failed to read second trim parameter, ignoring curve"); + } + } + + agree_sense = IsTrue(entity.SenseAgreement); + if( !agree_sense ) { + std::swap(range.first,range.second); + } + + // "NOTE In case of a closed curve, it may be necessary to increment t1 or t2 + // by the parametric length for consistency with the sense flag." + if (base->IsClosed()) { + if( range.first > range.second ) { + range.second += base->GetParametricRangeDelta(); + } + } + + maxval = range.second-range.first; + ai_assert(maxval >= 0); + } + + // -------------------------------------------------- + IfcVector3 Eval(IfcFloat p) const { + ai_assert(InRange(p)); + return base->Eval( TrimParam(p) ); + } + + // -------------------------------------------------- + size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const { + ai_assert( InRange( a ) ); + ai_assert( InRange( b ) ); + return base->EstimateSampleCount(TrimParam(a),TrimParam(b)); + } + + // -------------------------------------------------- + void SampleDiscrete(TempMesh& out,IfcFloat a,IfcFloat b) const { + ai_assert(InRange(a)); + ai_assert(InRange(b)); + return base->SampleDiscrete(out,TrimParam(a),TrimParam(b)); + } + + // -------------------------------------------------- + ParamRange GetParametricRange() const { + return std::make_pair(static_cast<IfcFloat>( 0. ),maxval); + } + +private: + // -------------------------------------------------- + IfcFloat TrimParam(IfcFloat f) const { + return agree_sense ? f + range.first : range.second - f; + } + +private: + ParamRange range; + IfcFloat maxval; + bool agree_sense; + + std::shared_ptr<const Curve> base; +}; + + +// -------------------------------------------------------------------------------- +// PolyLine is a 'curve' defined by linear interpolation over a set of discrete points +// -------------------------------------------------------------------------------- +class PolyLine : public BoundedCurve { +public: + // -------------------------------------------------- + PolyLine(const Schema_2x3::IfcPolyline& entity, ConversionData& conv) + : BoundedCurve(entity,conv) + { + points.reserve(entity.Points.size()); + + IfcVector3 t; + for(const Schema_2x3::IfcCartesianPoint& cp : entity.Points) { + ConvertCartesianPoint(t,cp); + points.push_back(t); + } + } + + // -------------------------------------------------- + IfcVector3 Eval(IfcFloat p) const { + ai_assert(InRange(p)); + + const size_t b = static_cast<size_t>(std::floor(p)); + if (b == points.size()-1) { + return points.back(); + } + + const IfcFloat d = p-static_cast<IfcFloat>(b); + return points[b+1] * d + points[b] * (static_cast<IfcFloat>( 1. )-d); + } + + // -------------------------------------------------- + size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const { + ai_assert(InRange(a)); + ai_assert(InRange(b)); + return static_cast<size_t>( std::ceil(b) - std::floor(a) ); + } + + // -------------------------------------------------- + ParamRange GetParametricRange() const { + return std::make_pair(static_cast<IfcFloat>( 0. ),static_cast<IfcFloat>(points.size()-1)); + } + +private: + std::vector<IfcVector3> points; +}; + +} // anon + +// ------------------------------------------------------------------------------------------------ +Curve* Curve::Convert(const IFC::Schema_2x3::IfcCurve& curve,ConversionData& conv) { + if(curve.ToPtr<Schema_2x3::IfcBoundedCurve>()) { + if(const Schema_2x3::IfcPolyline* c = curve.ToPtr<Schema_2x3::IfcPolyline>()) { + return new PolyLine(*c,conv); + } + if(const Schema_2x3::IfcTrimmedCurve* c = curve.ToPtr<Schema_2x3::IfcTrimmedCurve>()) { + return new TrimmedCurve(*c,conv); + } + if(const Schema_2x3::IfcCompositeCurve* c = curve.ToPtr<Schema_2x3::IfcCompositeCurve>()) { + return new CompositeCurve(*c,conv); + } + } + + if(curve.ToPtr<Schema_2x3::IfcConic>()) { + if(const Schema_2x3::IfcCircle* c = curve.ToPtr<Schema_2x3::IfcCircle>()) { + return new Circle(*c,conv); + } + if(const Schema_2x3::IfcEllipse* c = curve.ToPtr<Schema_2x3::IfcEllipse>()) { + return new Ellipse(*c,conv); + } + } + + if(const Schema_2x3::IfcLine* c = curve.ToPtr<Schema_2x3::IfcLine>()) { + return new Line(*c,conv); + } + + // XXX OffsetCurve2D, OffsetCurve3D not currently supported + return nullptr; +} + +#ifdef ASSIMP_BUILD_DEBUG +// ------------------------------------------------------------------------------------------------ +bool Curve::InRange(IfcFloat u) const { + const ParamRange range = GetParametricRange(); + if (IsClosed()) { + return true; + } + const IfcFloat epsilon = Math::getEpsilon<float>(); + return u - range.first > -epsilon && range.second - u > -epsilon; +} +#endif + +// ------------------------------------------------------------------------------------------------ +IfcFloat Curve::GetParametricRangeDelta() const { + const ParamRange& range = GetParametricRange(); + return std::abs(range.second - range.first); +} + +// ------------------------------------------------------------------------------------------------ +size_t Curve::EstimateSampleCount(IfcFloat a, IfcFloat b) const { + (void)(a); (void)(b); + ai_assert( InRange( a ) ); + ai_assert( InRange( b ) ); + + // arbitrary default value, deriving classes should supply better suited values + return 16; +} + +// ------------------------------------------------------------------------------------------------ +IfcFloat RecursiveSearch(const Curve* cv, const IfcVector3& val, IfcFloat a, IfcFloat b, + unsigned int samples, IfcFloat threshold, unsigned int recurse = 0, unsigned int max_recurse = 15) { + ai_assert(samples>1); + + const IfcFloat delta = (b-a)/samples, inf = std::numeric_limits<IfcFloat>::infinity(); + IfcFloat min_point[2] = {a,b}, min_diff[2] = {inf,inf}; + IfcFloat runner = a; + + for (unsigned int i = 0; i < samples; ++i, runner += delta) { + const IfcFloat diff = (cv->Eval(runner)-val).SquareLength(); + if (diff < min_diff[0]) { + min_diff[1] = min_diff[0]; + min_point[1] = min_point[0]; + + min_diff[0] = diff; + min_point[0] = runner; + } + else if (diff < min_diff[1]) { + min_diff[1] = diff; + min_point[1] = runner; + } + } + + ai_assert( min_diff[ 0 ] != inf ); + ai_assert( min_diff[ 1 ] != inf ); + if ( std::fabs(a-min_point[0]) < threshold || recurse >= max_recurse) { + return min_point[0]; + } + + // fix for closed curves to take their wrap-over into account + if (cv->IsClosed() && std::fabs(min_point[0]-min_point[1]) > cv->GetParametricRangeDelta()*0.5 ) { + const Curve::ParamRange& range = cv->GetParametricRange(); + const IfcFloat wrapdiff = (cv->Eval(range.first)-val).SquareLength(); + + if (wrapdiff < min_diff[0]) { + const IfcFloat t = min_point[0]; + min_point[0] = min_point[1] > min_point[0] ? range.first : range.second; + min_point[1] = t; + } + } + + return RecursiveSearch(cv,val,min_point[0],min_point[1],samples,threshold,recurse+1,max_recurse); +} + +// ------------------------------------------------------------------------------------------------ +bool Curve::ReverseEval(const IfcVector3& val, IfcFloat& paramOut) const +{ + // note: the following algorithm is not guaranteed to find the 'right' parameter value + // in all possible cases, but it will always return at least some value so this function + // will never fail in the default implementation. + + // XXX derive threshold from curve topology + static const IfcFloat threshold = 1e-4f; + static const unsigned int samples = 16; + + const ParamRange& range = GetParametricRange(); + paramOut = RecursiveSearch(this,val,range.first,range.second,samples,threshold); + + return true; +} + +// ------------------------------------------------------------------------------------------------ +void Curve::SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const { + ai_assert( InRange( a ) ); + ai_assert( InRange( b ) ); + + const size_t cnt = std::max(static_cast<size_t>(0),EstimateSampleCount(a,b)); + out.mVerts.reserve( out.mVerts.size() + cnt + 1); + + IfcFloat p = a, delta = (b-a)/cnt; + for(size_t i = 0; i <= cnt; ++i, p += delta) { + out.mVerts.push_back(Eval(p)); + } +} + +// ------------------------------------------------------------------------------------------------ +bool BoundedCurve::IsClosed() const { + return false; +} + +// ------------------------------------------------------------------------------------------------ +void BoundedCurve::SampleDiscrete(TempMesh& out) const { + const ParamRange& range = GetParametricRange(); + ai_assert( range.first != std::numeric_limits<IfcFloat>::infinity() ); + ai_assert( range.second != std::numeric_limits<IfcFloat>::infinity() ); + + return SampleDiscrete(out,range.first,range.second); +} + +} // IFC +} // Assimp + +#endif // ASSIMP_BUILD_NO_IFC_IMPORTER diff --git a/libs/assimp/code/AssetLib/IFC/IFCGeometry.cpp b/libs/assimp/code/AssetLib/IFC/IFCGeometry.cpp new file mode 100644 index 0000000..ef59542 --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCGeometry.cpp @@ -0,0 +1,932 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IFCGeometry.cpp + * @brief Geometry conversion and synthesis for IFC + */ + + + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER +#include "IFCUtil.h" +#include "Common/PolyTools.h" +#include "PostProcessing/ProcessHelper.h" + +#ifdef ASSIMP_USE_HUNTER +# include <poly2tri/poly2tri.h> +# include <polyclipping/clipper.hpp> +#else +# include "../contrib/poly2tri/poly2tri/poly2tri.h" +# include "../contrib/clipper/clipper.hpp" +#endif + +#include <memory> +#include <iterator> + +namespace Assimp { +namespace IFC { + +// ------------------------------------------------------------------------------------------------ +bool ProcessPolyloop(const Schema_2x3::IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/) +{ + size_t cnt = 0; + for(const Schema_2x3::IfcCartesianPoint& c : loop.Polygon) { + IfcVector3 tmp; + ConvertCartesianPoint(tmp,c); + + meshout.mVerts.push_back(tmp); + ++cnt; + } + + meshout.mVertcnt.push_back(static_cast<unsigned int>(cnt)); + + // zero- or one- vertex polyloops simply ignored + if (meshout.mVertcnt.back() > 1) { + return true; + } + + if (meshout.mVertcnt.back()==1) { + meshout.mVertcnt.pop_back(); + meshout.mVerts.pop_back(); + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = (size_t)-1) +{ + // handle all trivial cases + if(inmesh.mVertcnt.empty()) { + return; + } + if(inmesh.mVertcnt.size() == 1) { + result.Append(inmesh); + return; + } + + ai_assert(std::count(inmesh.mVertcnt.begin(), inmesh.mVertcnt.end(), 0u) == 0); + + typedef std::vector<unsigned int>::const_iterator face_iter; + + face_iter begin = inmesh.mVertcnt.begin(), end = inmesh.mVertcnt.end(), iit; + std::vector<unsigned int>::const_iterator outer_polygon_it = end; + + // major task here: given a list of nested polygon boundaries (one of which + // is the outer contour), reduce the triangulation task arising here to + // one that can be solved using the "quadrulation" algorithm which we use + // for pouring windows out of walls. The algorithm does not handle all + // cases but at least it is numerically stable and gives "nice" triangles. + + // first compute normals for all polygons using Newell's algorithm + // do not normalize 'normals', we need the original length for computing the polygon area + std::vector<IfcVector3> normals; + inmesh.ComputePolygonNormals(normals,false); + + // One of the polygons might be a IfcFaceOuterBound (in which case `master_bounds` + // is its index). Sadly we can't rely on it, the docs say 'At most one of the bounds + // shall be of the type IfcFaceOuterBound' + IfcFloat area_outer_polygon = 1e-10f; + if (master_bounds != (size_t)-1) { + ai_assert(master_bounds < inmesh.mVertcnt.size()); + outer_polygon_it = begin + master_bounds; + } + else { + for(iit = begin; iit != end; ++iit) { + // find the polygon with the largest area and take it as the outer bound. + IfcVector3& n = normals[std::distance(begin,iit)]; + const IfcFloat area = n.SquareLength(); + if (area > area_outer_polygon) { + area_outer_polygon = area; + outer_polygon_it = iit; + } + } + } + if (outer_polygon_it == end) { + return; + } + + const size_t outer_polygon_size = *outer_polygon_it; + const IfcVector3& master_normal = normals[std::distance(begin, outer_polygon_it)]; + + // Generate fake openings to meet the interface for the quadrulate + // algorithm. It boils down to generating small boxes given the + // inner polygon and the surface normal of the outer contour. + // It is important that we use the outer contour's normal because + // this is the plane onto which the quadrulate algorithm will + // project the entire mesh. + std::vector<TempOpening> fake_openings; + fake_openings.reserve(inmesh.mVertcnt.size()-1); + + std::vector<IfcVector3>::const_iterator vit = inmesh.mVerts.begin(), outer_vit; + + for(iit = begin; iit != end; vit += *iit++) { + if (iit == outer_polygon_it) { + outer_vit = vit; + continue; + } + + // Filter degenerate polygons to keep them from causing trouble later on + IfcVector3& n = normals[std::distance(begin,iit)]; + const IfcFloat area = n.SquareLength(); + if (area < 1e-5f) { + IFCImporter::LogWarn("skipping degenerate polygon (ProcessPolygonBoundaries)"); + continue; + } + + fake_openings.push_back(TempOpening()); + TempOpening& opening = fake_openings.back(); + + opening.extrusionDir = master_normal; + opening.solid = nullptr; + + opening.profileMesh = std::make_shared<TempMesh>(); + opening.profileMesh->mVerts.reserve(*iit); + opening.profileMesh->mVertcnt.push_back(*iit); + + std::copy(vit, vit + *iit, std::back_inserter(opening.profileMesh->mVerts)); + } + + // fill a mesh with ONLY the main polygon + TempMesh temp; + temp.mVerts.reserve(outer_polygon_size); + temp.mVertcnt.push_back(static_cast<unsigned int>(outer_polygon_size)); + std::copy(outer_vit, outer_vit+outer_polygon_size, + std::back_inserter(temp.mVerts)); + + GenerateOpenings(fake_openings, temp, false, false); + result.Append(temp); +} + +// ------------------------------------------------------------------------------------------------ +void ProcessConnectedFaceSet(const Schema_2x3::IfcConnectedFaceSet& fset, TempMesh& result, ConversionData& conv) +{ + for(const Schema_2x3::IfcFace& face : fset.CfsFaces) { + // size_t ob = -1, cnt = 0; + TempMesh meshout; + for(const Schema_2x3::IfcFaceBound& bound : face.Bounds) { + + if(const Schema_2x3::IfcPolyLoop* const polyloop = bound.Bound->ToPtr<Schema_2x3::IfcPolyLoop>()) { + if(ProcessPolyloop(*polyloop, meshout,conv)) { + + // The outer boundary is better determined by checking which + // polygon covers the largest area. + + //if(bound.ToPtr<IfcFaceOuterBound>()) { + // ob = cnt; + //} + //++cnt; + + } + } + else { + IFCImporter::LogWarn("skipping unknown IfcFaceBound entity, type is ", bound.Bound->GetClassName()); + continue; + } + + // And this, even though it is sometimes TRUE and sometimes FALSE, + // does not really improve results. + + /*if(!IsTrue(bound.Orientation)) { + size_t c = 0; + for(unsigned int& c : meshout.vertcnt) { + std::reverse(result.verts.begin() + cnt,result.verts.begin() + cnt + c); + cnt += c; + } + }*/ + } + ProcessPolygonBoundaries(result, meshout); + } +} + +// ------------------------------------------------------------------------------------------------ +void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv) +{ + TempMesh meshout; + + // first read the profile description + if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.mVerts.size()<=1) { + return; + } + + IfcVector3 axis, pos; + ConvertAxisPlacement(axis,pos,solid.Axis); + + IfcMatrix4 tb0,tb1; + IfcMatrix4::Translation(pos,tb0); + IfcMatrix4::Translation(-pos,tb1); + + const std::vector<IfcVector3>& in = meshout.mVerts; + const size_t size=in.size(); + + bool has_area = solid.SweptArea->ProfileType == "AREA" && size>2; + const IfcFloat max_angle = solid.Angle*conv.angle_scale; + if(std::fabs(max_angle) < 1e-3) { + if(has_area) { + result = meshout; + } + return; + } + + const unsigned int cnt_segments = std::max(2u,static_cast<unsigned int>(conv.settings.cylindricalTessellation * std::fabs(max_angle)/AI_MATH_HALF_PI_F)); + const IfcFloat delta = max_angle/cnt_segments; + + has_area = has_area && std::fabs(max_angle) < AI_MATH_TWO_PI_F*0.99; + + result.mVerts.reserve(size*((cnt_segments+1)*4+(has_area?2:0))); + result.mVertcnt.reserve(size*cnt_segments+2); + + IfcMatrix4 rot; + rot = tb0 * IfcMatrix4::Rotation(delta,axis,rot) * tb1; + + size_t base = 0; + std::vector<IfcVector3>& out = result.mVerts; + + // dummy data to simplify later processing + for(size_t i = 0; i < size; ++i) { + out.insert(out.end(),4,in[i]); + } + + for(unsigned int seg = 0; seg < cnt_segments; ++seg) { + for(size_t i = 0; i < size; ++i) { + const size_t next = (i+1)%size; + + result.mVertcnt.push_back(4); + const IfcVector3 base_0 = out[base+i*4+3],base_1 = out[base+next*4+3]; + + out.push_back(base_0); + out.push_back(base_1); + out.push_back(rot*base_1); + out.push_back(rot*base_0); + } + base += size*4; + } + + out.erase(out.begin(),out.begin()+size*4); + + if(has_area) { + // leave the triangulation of the profile area to the ear cutting + // implementation in aiProcess_Triangulate - for now we just + // feed in two huge polygons. + base -= size*8; + for(size_t i = size; i--; ) { + out.push_back(out[base+i*4+3]); + } + for(size_t i = 0; i < size; ++i ) { + out.push_back(out[i*4]); + } + result.mVertcnt.push_back(static_cast<unsigned int>(size)); + result.mVertcnt.push_back(static_cast<unsigned int>(size)); + } + + IfcMatrix4 trafo; + ConvertAxisPlacement(trafo, solid.Position); + + result.Transform(trafo); + IFCImporter::LogVerboseDebug("generate mesh procedurally by radial extrusion (IfcRevolvedAreaSolid)"); +} + +// ------------------------------------------------------------------------------------------------ +void ProcessSweptDiskSolid(const Schema_2x3::IfcSweptDiskSolid &solid, TempMesh& result, ConversionData& conv) +{ + const Curve* const curve = Curve::Convert(*solid.Directrix, conv); + if(!curve) { + IFCImporter::LogError("failed to convert Directrix curve (IfcSweptDiskSolid)"); + return; + } + + const unsigned int cnt_segments = conv.settings.cylindricalTessellation; + const IfcFloat deltaAngle = AI_MATH_TWO_PI/cnt_segments; + + TempMesh temp; + curve->SampleDiscrete(temp, solid.StartParam, solid.EndParam); + const std::vector<IfcVector3>& curve_points = temp.mVerts; + + const size_t samples = curve_points.size(); + + result.mVerts.reserve(cnt_segments * samples * 4); + result.mVertcnt.reserve((cnt_segments - 1) * samples); + + std::vector<IfcVector3> points; + points.reserve(cnt_segments * samples); + + if(curve_points.empty()) { + IFCImporter::LogWarn("curve evaluation yielded no points (IfcSweptDiskSolid)"); + return; + } + + IfcVector3 current = curve_points[0]; + IfcVector3 previous = current; + IfcVector3 next; + + IfcVector3 startvec; + startvec.x = 1.0f; + startvec.y = 1.0f; + startvec.z = 1.0f; + + unsigned int last_dir = 0; + + // generate circles at the sweep positions + for(size_t i = 0; i < samples; ++i) { + + if(i != samples - 1) { + next = curve_points[i + 1]; + } + + // get a direction vector reflecting the approximate curvature (i.e. tangent) + IfcVector3 d = (current-previous) + (next-previous); + + d.Normalize(); + + // figure out an arbitrary point q so that (p-q) * d = 0, + // try to maximize ||(p-q)|| * ||(p_last-q_last)|| + IfcVector3 q; + bool take_any = false; + + for (unsigned int j = 0; j < 2; ++j, take_any = true) { + if ((last_dir == 0 || take_any) && std::abs(d.x) > ai_epsilon) { + q.y = startvec.y; + q.z = startvec.z; + q.x = -(d.y * q.y + d.z * q.z) / d.x; + last_dir = 0; + break; + } else if ((last_dir == 1 || take_any) && std::abs(d.y) > ai_epsilon) { + q.x = startvec.x; + q.z = startvec.z; + q.y = -(d.x * q.x + d.z * q.z) / d.y; + last_dir = 1; + break; + } else if ((last_dir == 2 && std::abs(d.z) > ai_epsilon) || take_any) { + q.y = startvec.y; + q.x = startvec.x; + q.z = -(d.y * q.y + d.x * q.x) / d.z; + last_dir = 2; + break; + } + } + + q *= solid.Radius / q.Length(); + startvec = q; + + // generate a rotation matrix to rotate q around d + IfcMatrix4 rot; + IfcMatrix4::Rotation(deltaAngle,d,rot); + + for (unsigned int seg = 0; seg < cnt_segments; ++seg, q *= rot ) { + points.push_back(q + current); + } + + previous = current; + current = next; + } + + // make quads + for(size_t i = 0; i < samples - 1; ++i) { + + const aiVector3D& this_start = points[ i * cnt_segments ]; + + // locate corresponding point on next sample ring + unsigned int best_pair_offset = 0; + float best_distance_squared = 1e10f; + for (unsigned int seg = 0; seg < cnt_segments; ++seg) { + const aiVector3D& p = points[ (i+1) * cnt_segments + seg]; + const float l = (p-this_start).SquareLength(); + + if(l < best_distance_squared) { + best_pair_offset = seg; + best_distance_squared = l; + } + } + + for (unsigned int seg = 0; seg < cnt_segments; ++seg) { + + result.mVerts.push_back(points[ i * cnt_segments + (seg % cnt_segments)]); + result.mVerts.push_back(points[ i * cnt_segments + (seg + 1) % cnt_segments]); + result.mVerts.push_back(points[ (i+1) * cnt_segments + ((seg + 1 + best_pair_offset) % cnt_segments)]); + result.mVerts.push_back(points[ (i+1) * cnt_segments + ((seg + best_pair_offset) % cnt_segments)]); + + IfcVector3& v1 = *(result.mVerts.end()-1); + IfcVector3& v2 = *(result.mVerts.end()-2); + IfcVector3& v3 = *(result.mVerts.end()-3); + IfcVector3& v4 = *(result.mVerts.end()-4); + + if (((v4-v3) ^ (v4-v1)) * (v4 - curve_points[i]) < 0.0f) { + std::swap(v4, v1); + std::swap(v3, v2); + } + + result.mVertcnt.push_back(4); + } + } + + IFCImporter::LogVerboseDebug("generate mesh procedurally by sweeping a disk along a curve (IfcSweptDiskSolid)"); +} + +// ------------------------------------------------------------------------------------------------ +IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut) +{ + const std::vector<IfcVector3>& out = curmesh.mVerts; + IfcMatrix3 m; + + ok = true; + + // The input "mesh" must be a single polygon + const size_t s = out.size(); + ai_assert( curmesh.mVertcnt.size() == 1 ); + ai_assert( curmesh.mVertcnt.back() == s); + + const IfcVector3 any_point = out[s-1]; + IfcVector3 nor; + + // The input polygon is arbitrarily shaped, therefore we might need some tries + // until we find a suitable normal. Note that Newell's algorithm would give + // a more robust result, but this variant also gives us a suitable first + // axis for the 2D coordinate space on the polygon plane, exploiting the + // fact that the input polygon is nearly always a quad. + bool done = false; + size_t idx( 0 ); + for (size_t i = 0; !done && i < s-2; done || ++i) { + idx = i; + for (size_t j = i+1; j < s-1; ++j) { + nor = -((out[i]-any_point)^(out[j]-any_point)); + if(std::fabs(nor.Length()) > 1e-8f) { + done = true; + break; + } + } + } + + if(!done) { + ok = false; + return m; + } + + nor.Normalize(); + norOut = nor; + + IfcVector3 r = (out[idx]-any_point); + r.Normalize(); + + //if(d) { + // *d = -any_point * nor; + //} + + // Reconstruct orthonormal basis + // XXX use Gram Schmidt for increased robustness + IfcVector3 u = r ^ nor; + u.Normalize(); + + m.a1 = r.x; + m.a2 = r.y; + m.a3 = r.z; + + m.b1 = u.x; + m.b2 = u.y; + m.b3 = u.z; + + m.c1 = -nor.x; + m.c2 = -nor.y; + m.c3 = -nor.z; + + return m; +} + +const auto closeDistance = ai_epsilon; + +bool areClose(Schema_2x3::IfcCartesianPoint pt1,Schema_2x3::IfcCartesianPoint pt2) { + if(pt1.Coordinates.size() != pt2.Coordinates.size()) + { + IFCImporter::LogWarn("unable to compare differently-dimensioned points"); + return false; + } + auto coord1 = pt1.Coordinates.begin(); + auto coord2 = pt2.Coordinates.begin(); + // we're just testing each dimension separately rather than doing euclidean distance, as we're + // looking for very close coordinates + for(; coord1 != pt1.Coordinates.end(); coord1++,coord2++) + { + if(std::fabs(*coord1 - *coord2) > closeDistance) + return false; + } + return true; +} + +bool areClose(IfcVector3 pt1,IfcVector3 pt2) { + return (std::fabs(pt1.x - pt2.x) < closeDistance && + std::fabs(pt1.y - pt2.y) < closeDistance && + std::fabs(pt1.z - pt2.z) < closeDistance); +} +// Extrudes the given polygon along the direction, converts it into an opening or applies all openings as necessary. +void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const TempMesh& curve, + const IfcVector3& extrusionDir, TempMesh& result, ConversionData &conv, bool collect_openings) +{ + // Outline: 'curve' is now a list of vertex points forming the underlying profile, extrude along the given axis, + // forming new triangles. + const bool has_area = solid.SweptArea->ProfileType == "AREA" && curve.mVerts.size() > 2; + if (solid.Depth < ai_epsilon) { + if( has_area ) { + result.Append(curve); + } + return; + } + + result.mVerts.reserve(curve.mVerts.size()*(has_area ? 4 : 2)); + result.mVertcnt.reserve(curve.mVerts.size() + 2); + std::vector<IfcVector3> in = curve.mVerts; + + // First step: transform all vertices into the target coordinate space + IfcMatrix4 trafo; + ConvertAxisPlacement(trafo, solid.Position); + + IfcVector3 vmin, vmax; + MinMaxChooser<IfcVector3>()(vmin, vmax); + for(IfcVector3& v : in) { + v *= trafo; + + vmin = std::min(vmin, v); + vmax = std::max(vmax, v); + } + + vmax -= vmin; + const IfcFloat diag = vmax.Length(); + IfcVector3 dir = IfcMatrix3(trafo) * extrusionDir; + + // 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<IfcVector3> nors; + const bool openings = !!conv.apply_openings && conv.apply_openings->size(); + + // Compute the normal vectors for all opening polygons as a prerequisite + // to TryAddOpenings_Poly2Tri() + // XXX this belongs into the aforementioned function + if( openings ) { + + if( !conv.settings.useCustomTriangulation ) { + // it is essential to apply the openings in the correct spatial order. The direction + // doesn't matter, but we would screw up if we started with e.g. a door in between + // two windows. + std::sort(conv.apply_openings->begin(), conv.apply_openings->end(), TempOpening::DistanceSorter(in[0])); + } + + nors.reserve(conv.apply_openings->size()); + for(TempOpening& t : *conv.apply_openings) { + TempMesh& bounds = *t.profileMesh.get(); + + if( bounds.mVerts.size() <= 2 ) { + nors.push_back(IfcVector3()); + continue; + } + auto nor = ((bounds.mVerts[2] - bounds.mVerts[0]) ^ (bounds.mVerts[1] - bounds.mVerts[0])).Normalize(); + auto vI0 = bounds.mVertcnt[0]; + for(size_t faceI = 0; faceI < bounds.mVertcnt.size(); faceI++) + { + if(bounds.mVertcnt[faceI] >= 3) { + // do a check that this is at least parallel to the base plane + auto nor2 = ((bounds.mVerts[vI0 + 2] - bounds.mVerts[vI0]) ^ (bounds.mVerts[vI0 + 1] - bounds.mVerts[vI0])).Normalize(); + if(!areClose(nor,nor2)) { + std::stringstream msg; + msg << "Face " << faceI << " is not parallel with face 0 - opening on entity " << solid.GetID(); + IFCImporter::LogWarn(msg.str().c_str()); + } + } + } + nors.push_back(nor); + } + } + + + TempMesh temp; + TempMesh& curmesh = openings ? temp : result; + std::vector<IfcVector3>& out = curmesh.mVerts; + + size_t sides_with_openings = 0; + for( size_t i = 0; i < in.size(); ++i ) { + const size_t next = (i + 1) % in.size(); + + curmesh.mVertcnt.push_back(4); + + out.push_back(in[i]); + 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, temp, true, true, dir) ) { + ++sides_with_openings; + } + + result.Append(temp); + temp.Clear(); + } + } + + if(openings) { + for(TempOpening& opening : *conv.apply_openings) { + if(!opening.wallPoints.empty()) { + std::stringstream msg; + msg << "failed to generate all window caps on ID " << (int)solid.GetID(); + IFCImporter::LogError(msg.str().c_str()); + } + opening.wallPoints.clear(); + } + } + + size_t sides_with_v_openings = 0; + if(has_area) { + + for(size_t n = 0; n < 2; ++n) { + if(n > 0) { + for(size_t i = 0; i < in.size(); ++i) + out.push_back(in[i] + dir); + } + else { + for(size_t i = in.size(); i--; ) + out.push_back(in[i]); + } + + curmesh.mVertcnt.push_back(static_cast<unsigned int>(in.size())); + if(openings && in.size() > 2) { + if(GenerateOpenings(*conv.apply_openings,temp,true,true,dir)) { + ++sides_with_v_openings; + } + + result.Append(temp); + temp.Clear(); + } + } + } + + if (openings && (sides_with_openings == 1 || sides_with_v_openings == 2)) { + std::stringstream msg; + msg << "failed to resolve all openings, presumably their topology is not supported by Assimp - ID " << solid.GetID() << " sides_with_openings " << sides_with_openings << " sides_with_v_openings " << sides_with_v_openings; + IFCImporter::LogWarn(msg.str().c_str()); + } + + IFCImporter::LogVerboseDebug("generate mesh procedurally by extrusion (IfcExtrudedAreaSolid)"); + + // If this is an opening element, store both the extruded mesh and the 2D profile mesh + // it was created from. Return an empty mesh to the caller. + if( collect_openings && !result.IsEmpty() ) { + ai_assert(conv.collect_openings); + std::shared_ptr<TempMesh> profile = std::shared_ptr<TempMesh>(new TempMesh()); + profile->Swap(result); + + std::shared_ptr<TempMesh> profile2D = std::shared_ptr<TempMesh>(new TempMesh()); + profile2D->mVerts.insert(profile2D->mVerts.end(), in.begin(), in.end()); + profile2D->mVertcnt.push_back(static_cast<unsigned int>(in.size())); + conv.collect_openings->push_back(TempOpening(&solid, dir, profile, profile2D)); + + ai_assert(result.IsEmpty()); + } +} + +// ------------------------------------------------------------------------------------------------ +void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, TempMesh& result, + ConversionData& conv, bool collect_openings) +{ + TempMesh meshout; + + // First read the profile description. + if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.mVerts.size()<=1) { + return; + } + + IfcVector3 dir; + ConvertDirection(dir,solid.ExtrudedDirection); + dir *= solid.Depth; + + // Some profiles bring their own holes, for which we need to provide a container. This all is somewhat backwards, + // and there's still so many corner cases uncovered - we really need a generic solution to all of this hole carving. + std::vector<TempOpening> fisherPriceMyFirstOpenings; + std::vector<TempOpening>* oldApplyOpenings = conv.apply_openings; + if( const Schema_2x3::IfcArbitraryProfileDefWithVoids* const cprofile = solid.SweptArea->ToPtr<Schema_2x3::IfcArbitraryProfileDefWithVoids>() ) { + if( !cprofile->InnerCurves.empty() ) { + // read all inner curves and extrude them to form proper openings. + std::vector<TempOpening>* oldCollectOpenings = conv.collect_openings; + conv.collect_openings = &fisherPriceMyFirstOpenings; + + for (const Schema_2x3::IfcCurve* curve : cprofile->InnerCurves) { + TempMesh curveMesh, tempMesh; + ProcessCurve(*curve, curveMesh, conv); + ProcessExtrudedArea(solid, curveMesh, dir, tempMesh, conv, true); + } + // and then apply those to the geometry we're about to generate + conv.apply_openings = conv.collect_openings; + conv.collect_openings = oldCollectOpenings; + } + } + + ProcessExtrudedArea(solid, meshout, dir, result, conv, collect_openings); + conv.apply_openings = oldApplyOpenings; +} + +// ------------------------------------------------------------------------------------------------ +void ProcessSweptAreaSolid(const Schema_2x3::IfcSweptAreaSolid& swept, TempMesh& meshout, + ConversionData& conv) +{ + if(const Schema_2x3::IfcExtrudedAreaSolid* const solid = swept.ToPtr<Schema_2x3::IfcExtrudedAreaSolid>()) { + ProcessExtrudedAreaSolid(*solid,meshout,conv, !!conv.collect_openings); + } + else if(const Schema_2x3::IfcRevolvedAreaSolid* const rev = swept.ToPtr<Schema_2x3::IfcRevolvedAreaSolid>()) { + ProcessRevolvedAreaSolid(*rev,meshout,conv); + } + else { + IFCImporter::LogWarn("skipping unknown IfcSweptAreaSolid entity, type is ", swept.GetClassName()); + } +} + +// ------------------------------------------------------------------------------------------------ +bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned int matid, std::set<unsigned int>& mesh_indices, + ConversionData& conv) +{ + bool fix_orientation = false; + std::shared_ptr< TempMesh > meshtmp = std::make_shared<TempMesh>(); + if(const Schema_2x3::IfcShellBasedSurfaceModel* shellmod = geo.ToPtr<Schema_2x3::IfcShellBasedSurfaceModel>()) { + for (const std::shared_ptr<const Schema_2x3::IfcShell> &shell : shellmod->SbsmBoundary) { + try { + const ::Assimp::STEP::EXPRESS::ENTITY& e = shell->To<::Assimp::STEP::EXPRESS::ENTITY>(); + const Schema_2x3::IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To<Schema_2x3::IfcConnectedFaceSet>(); + + ProcessConnectedFaceSet(fs,*meshtmp.get(),conv); + } + catch(std::bad_cast&) { + IFCImporter::LogWarn("unexpected type error, IfcShell ought to inherit from IfcConnectedFaceSet"); + } + } + fix_orientation = true; + } + else if(const Schema_2x3::IfcConnectedFaceSet* fset = geo.ToPtr<Schema_2x3::IfcConnectedFaceSet>()) { + ProcessConnectedFaceSet(*fset,*meshtmp.get(),conv); + fix_orientation = true; + } + else if(const Schema_2x3::IfcSweptAreaSolid* swept = geo.ToPtr<Schema_2x3::IfcSweptAreaSolid>()) { + ProcessSweptAreaSolid(*swept,*meshtmp.get(),conv); + } + else if(const Schema_2x3::IfcSweptDiskSolid* disk = geo.ToPtr<Schema_2x3::IfcSweptDiskSolid>()) { + ProcessSweptDiskSolid(*disk,*meshtmp.get(),conv); + } + else if(const Schema_2x3::IfcManifoldSolidBrep* brep = geo.ToPtr<Schema_2x3::IfcManifoldSolidBrep>()) { + ProcessConnectedFaceSet(brep->Outer,*meshtmp.get(),conv); + fix_orientation = true; + } + else if(const Schema_2x3::IfcFaceBasedSurfaceModel* surf = geo.ToPtr<Schema_2x3::IfcFaceBasedSurfaceModel>()) { + for(const Schema_2x3::IfcConnectedFaceSet& fc : surf->FbsmFaces) { + ProcessConnectedFaceSet(fc,*meshtmp.get(),conv); + } + fix_orientation = true; + } + else if(const Schema_2x3::IfcBooleanResult* boolean = geo.ToPtr<Schema_2x3::IfcBooleanResult>()) { + ProcessBoolean(*boolean,*meshtmp.get(),conv); + } + else if(geo.ToPtr<Schema_2x3::IfcBoundingBox>()) { + // silently skip over bounding boxes + return false; + } + else { + std::stringstream toLog; + toLog << "skipping unknown IfcGeometricRepresentationItem entity, type is " << geo.GetClassName() << " id is " << geo.GetID(); + IFCImporter::LogWarn(toLog.str().c_str()); + return false; + } + + // Do we just collect openings for a parent element (i.e. a wall)? + // In such a case, we generate the polygonal mesh as usual, + // but attach it to a TempOpening instance which will later be applied + // to the wall it pertains to. + + // Note: swep area solids are added in ProcessExtrudedAreaSolid(), + // which returns an empty mesh. + if(conv.collect_openings) { + if (!meshtmp->IsEmpty()) { + conv.collect_openings->push_back(TempOpening(geo.ToPtr<Schema_2x3::IfcSolidModel>(), + IfcVector3(0,0,0), + meshtmp, + std::shared_ptr<TempMesh>())); + } + return true; + } + + if (meshtmp->IsEmpty()) { + return false; + } + + meshtmp->RemoveAdjacentDuplicates(); + meshtmp->RemoveDegenerates(); + + if(fix_orientation) { +// meshtmp->FixupFaceOrientation(); + } + + aiMesh* const mesh = meshtmp->ToMesh(); + if(mesh) { + mesh->mMaterialIndex = matid; + mesh_indices.insert(static_cast<unsigned int>(conv.meshes.size())); + conv.meshes.push_back(mesh); + return true; + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +void AssignAddedMeshes(std::set<unsigned int>& mesh_indices,aiNode* nd, + ConversionData& /*conv*/) +{ + if (!mesh_indices.empty()) { + std::set<unsigned int>::const_iterator it = mesh_indices.cbegin(); + std::set<unsigned int>::const_iterator end = mesh_indices.cend(); + + nd->mNumMeshes = static_cast<unsigned int>(mesh_indices.size()); + + nd->mMeshes = new unsigned int[nd->mNumMeshes]; + for(unsigned int i = 0; it != end && i < nd->mNumMeshes; ++i, ++it) { + nd->mMeshes[i] = *it; + } + } +} + +// ------------------------------------------------------------------------------------------------ +bool TryQueryMeshCache(const Schema_2x3::IfcRepresentationItem& item, + std::set<unsigned int>& mesh_indices, unsigned int mat_index, + ConversionData& conv) +{ + 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::inserter(mesh_indices, mesh_indices.end())); + return true; + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +void PopulateMeshCache(const Schema_2x3::IfcRepresentationItem& item, + const std::set<unsigned int>& mesh_indices, unsigned int mat_index, + ConversionData& conv) +{ + ConversionData::MeshCacheIndex idx(&item, mat_index); + conv.cached_meshes[idx] = mesh_indices; +} + +// ------------------------------------------------------------------------------------------------ +bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, unsigned int matid, + std::set<unsigned int>& mesh_indices, + ConversionData& 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,localmatid,conv); + } + } + else return false; + } + return true; +} + + +} // ! IFC +} // ! Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/IFC/IFCLoader.cpp b/libs/assimp/code/AssetLib/IFC/IFCLoader.cpp new file mode 100644 index 0000000..0c20686 --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCLoader.cpp @@ -0,0 +1,931 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IFCLoad.cpp + * @brief Implementation of the Industry Foundation Classes loader. + */ + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER + +#include <iterator> +#include <limits> +#include <tuple> + +#ifndef ASSIMP_BUILD_NO_COMPRESSED_IFC +#ifdef ASSIMP_USE_HUNTER +#include <minizip/unzip.h> +#else +#include <unzip.h> +#endif +#endif + +#include "../STEPParser/STEPFileReader.h" +#include "IFCLoader.h" + +#include "IFCUtil.h" + +#include <assimp/MemoryIOWrapper.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/Importer.hpp> + +namespace Assimp { +template <> +const char *LogFunctions<IFCImporter>::Prefix() { + static auto prefix = "IFC: "; + return prefix; +} +} // namespace Assimp + +using namespace Assimp; +using namespace Assimp::Formatter; +using namespace Assimp::IFC; + +/* DO NOT REMOVE this comment block. The genentitylist.sh script + * just looks for names adhering to the IfcSomething naming scheme + * and includes all matches in the whitelist for code-generation. Thus, + * all entity classes that are only indirectly referenced need to be + * mentioned explicitly. + + IfcRepresentationMap + IfcProductRepresentation + IfcUnitAssignment + IfcClosedShell + IfcDoor + + */ + +namespace { + +// forward declarations +void SetUnits(ConversionData &conv); +void SetCoordinateSpace(ConversionData &conv); +void ProcessSpatialStructures(ConversionData &conv); +void MakeTreeRelative(ConversionData &conv); +void ConvertUnit(const ::Assimp::STEP::EXPRESS::DataType &dt, ConversionData &conv); + +} // namespace + +static const aiImporterDesc desc = { + "Industry Foundation Classes (IFC) Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "ifc ifczip step stp" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +IFCImporter::IFCImporter() {} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +IFCImporter::~IFCImporter() { +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool IFCImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + // note: this is the common identification for STEP-encoded files, so + // it is only unambiguous as long as we don't support any further + // file formats with STEP as their encoding. + static const char *tokens[] = { "ISO-10303-21" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// List all extensions handled by this loader +const aiImporterDesc *IFCImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties for the loader +void IFCImporter::SetupProperties(const Importer *pImp) { + settings.skipSpaceRepresentations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_SKIP_SPACE_REPRESENTATIONS, true); + settings.useCustomTriangulation = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_CUSTOM_TRIANGULATION, true); + settings.conicSamplingAngle = std::min(std::max((float)pImp->GetPropertyFloat(AI_CONFIG_IMPORT_IFC_SMOOTHING_ANGLE, AI_IMPORT_IFC_DEFAULT_SMOOTHING_ANGLE), 5.0f), 120.0f); + settings.cylindricalTessellation = std::min(std::max(pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IFC_CYLINDRICAL_TESSELLATION, AI_IMPORT_IFC_DEFAULT_CYLINDRICAL_TESSELLATION), 3), 180); + settings.skipAnnotations = true; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void IFCImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile)); + if (!stream) { + ThrowException("Could not open file for reading"); + } + + // if this is a ifczip file, decompress its contents first + if (GetExtension(pFile) == "ifczip") { +#ifndef ASSIMP_BUILD_NO_COMPRESSED_IFC + unzFile zip = unzOpen(pFile.c_str()); + if (zip == nullptr) { + ThrowException("Could not open ifczip file for reading, unzip failed"); + } + + // chop 'zip' postfix + std::string fileName = pFile.substr(0, pFile.length() - 3); + + std::string::size_type s = pFile.find_last_of('\\'); + if (s == std::string::npos) { + s = pFile.find_last_of('/'); + } + if (s != std::string::npos) { + fileName = fileName.substr(s + 1); + } + + // search file (same name as the IFCZIP except for the file extension) and place file pointer there + if (UNZ_OK == unzGoToFirstFile(zip)) { + do { + // get file size, etc. + unz_file_info fileInfo; + char filename[256]; + unzGetCurrentFileInfo(zip, &fileInfo, filename, sizeof(filename), 0, 0, 0, 0); + if (GetExtension(filename) != "ifc") { + continue; + } + uint8_t *buff = new uint8_t[fileInfo.uncompressed_size]; + LogInfo("Decompressing IFCZIP file"); + unzOpenCurrentFile(zip); + size_t total = 0; + int read = 0; + do { + int bufferSize = fileInfo.uncompressed_size < INT16_MAX ? fileInfo.uncompressed_size : INT16_MAX; + void *buffer = malloc(bufferSize); + read = unzReadCurrentFile(zip, buffer, bufferSize); + if (read > 0) { + memcpy((char *)buff + total, buffer, read); + total += read; + } + free(buffer); + } while (read > 0); + size_t filesize = fileInfo.uncompressed_size; + if (total == 0 || size_t(total) != filesize) { + delete[] buff; + ThrowException("Failed to decompress IFC ZIP file"); + } + unzCloseCurrentFile(zip); + stream.reset(new MemoryIOStream(buff, fileInfo.uncompressed_size, true)); + if (unzGoToNextFile(zip) == UNZ_END_OF_LIST_OF_FILE) { + ThrowException("Found no IFC file member in IFCZIP file (1)"); + } + break; + + } while (true); + } else { + ThrowException("Found no IFC file member in IFCZIP file (2)"); + } + + unzClose(zip); +#else + ThrowException("Could not open ifczip file for reading, assimp was built without ifczip support"); +#endif + } + + std::unique_ptr<STEP::DB> db(STEP::ReadFileHeader(stream)); + const STEP::HeaderInfo &head = static_cast<const STEP::DB &>(*db).GetHeader(); + + if (!head.fileSchema.size() || head.fileSchema.substr(0, 3) != "IFC") { + ThrowException("Unrecognized file schema: " + head.fileSchema); + } + + if (!DefaultLogger::isNullLogger()) { + LogDebug("File schema is \'", head.fileSchema, '\''); + if (head.timestamp.length()) { + LogDebug("Timestamp \'", head.timestamp, '\''); + } + if (head.app.length()) { + LogDebug("Application/Exporter identline is \'", head.app, '\''); + } + } + + // obtain a copy of the machine-generated IFC scheme + ::Assimp::STEP::EXPRESS::ConversionSchema schema; + Schema_2x3::GetSchema(schema); + + // tell the reader which entity types to track with special care + static const char *const types_to_track[] = { + "ifcsite", "ifcbuilding", "ifcproject" + }; + + // tell the reader for which types we need to simulate STEPs reverse indices + static const char *const inverse_indices_to_track[] = { + "ifcrelcontainedinspatialstructure", "ifcrelaggregates", "ifcrelvoidselement", "ifcreldefinesbyproperties", "ifcpropertyset", "ifcstyleditem" + }; + + // feed the IFC schema into the reader and pre-parse all lines + STEP::ReadFile(*db, schema, types_to_track, inverse_indices_to_track); + const STEP::LazyObject *proj = db->GetObject("ifcproject"); + if (!proj) { + ThrowException("missing IfcProject entity"); + } + + ConversionData conv(*db, proj->To<Schema_2x3::IfcProject>(), pScene, settings); + SetUnits(conv); + SetCoordinateSpace(conv); + ProcessSpatialStructures(conv); + MakeTreeRelative(conv); + +// NOTE - this is a stress test for the importer, but it works only +// in a build with no entities disabled. See +// scripts/IFCImporter/CPPGenerator.py +// for more information. +#ifdef ASSIMP_IFC_TEST + db->EvaluateAll(); +#endif + + // do final data copying + if (conv.meshes.size()) { + pScene->mNumMeshes = static_cast<unsigned int>(conv.meshes.size()); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes](); + std::copy(conv.meshes.begin(), conv.meshes.end(), pScene->mMeshes); + + // needed to keep the d'tor from burning us + conv.meshes.clear(); + } + + if (conv.materials.size()) { + pScene->mNumMaterials = static_cast<unsigned int>(conv.materials.size()); + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials](); + std::copy(conv.materials.begin(), conv.materials.end(), pScene->mMaterials); + + // needed to keep the d'tor from burning us + conv.materials.clear(); + } + + // apply world coordinate system (which includes the scaling to convert to meters and a -90 degrees rotation around x) + aiMatrix4x4 scale, rot; + aiMatrix4x4::Scaling(static_cast<aiVector3D>(IfcVector3(conv.len_scale)), scale); + aiMatrix4x4::RotationX(-AI_MATH_HALF_PI_F, rot); + + pScene->mRootNode->mTransformation = rot * scale * conv.wcs * pScene->mRootNode->mTransformation; + + // this must be last because objects are evaluated lazily as we process them + if (!DefaultLogger::isNullLogger()) { + LogDebug("STEP: evaluated ", db->GetEvaluatedObjectCount(), " object records"); + } +} + +namespace { + +// ------------------------------------------------------------------------------------------------ +void ConvertUnit(const Schema_2x3::IfcNamedUnit &unit, ConversionData &conv) { + if (const Schema_2x3::IfcSIUnit *const si = unit.ToPtr<Schema_2x3::IfcSIUnit>()) { + if (si->UnitType == "LENGTHUNIT") { + conv.len_scale = si->Prefix ? ConvertSIPrefix(si->Prefix) : 1.f; + IFCImporter::LogDebug("got units used for lengths"); + } + if (si->UnitType == "PLANEANGLEUNIT") { + if (si->Name != "RADIAN") { + IFCImporter::LogWarn("expected base unit for angles to be radian"); + } + } + } else if (const Schema_2x3::IfcConversionBasedUnit *const convu = unit.ToPtr<Schema_2x3::IfcConversionBasedUnit>()) { + if (convu->UnitType == "PLANEANGLEUNIT") { + try { + conv.angle_scale = convu->ConversionFactor->ValueComponent->To<::Assimp::STEP::EXPRESS::REAL>(); + ConvertUnit(*convu->ConversionFactor->UnitComponent, conv); + IFCImporter::LogDebug("got units used for angles"); + } catch (std::bad_cast &) { + IFCImporter::LogError("skipping unknown IfcConversionBasedUnit.ValueComponent entry - expected REAL"); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void ConvertUnit(const ::Assimp::STEP::EXPRESS::DataType &dt, ConversionData &conv) { + try { + const ::Assimp::STEP::EXPRESS::ENTITY &e = dt.To<::Assimp::STEP::EXPRESS::ENTITY>(); + + const Schema_2x3::IfcNamedUnit &unit = e.ResolveSelect<Schema_2x3::IfcNamedUnit>(conv.db); + if (unit.UnitType != "LENGTHUNIT" && unit.UnitType != "PLANEANGLEUNIT") { + return; + } + + ConvertUnit(unit, conv); + } catch (std::bad_cast &) { + // not entity, somehow + IFCImporter::LogError("skipping unknown IfcUnit entry - expected entity"); + } +} + +// ------------------------------------------------------------------------------------------------ +void SetUnits(ConversionData &conv) { + // see if we can determine the coordinate space used to express. + for (size_t i = 0; i < conv.proj.UnitsInContext->Units.size(); ++i) { + ConvertUnit(*conv.proj.UnitsInContext->Units[i], conv); + } +} + +// ------------------------------------------------------------------------------------------------ +void SetCoordinateSpace(ConversionData &conv) { + const Schema_2x3::IfcRepresentationContext *fav = nullptr; + for (const Schema_2x3::IfcRepresentationContext &v : conv.proj.RepresentationContexts) { + fav = &v; + // Model should be the most suitable type of context, hence ignore the others + if (v.ContextType && v.ContextType.Get() == "Model") { + break; + } + } + if (fav) { + if (const Schema_2x3::IfcGeometricRepresentationContext *const geo = fav->ToPtr<Schema_2x3::IfcGeometricRepresentationContext>()) { + ConvertAxisPlacement(conv.wcs, *geo->WorldCoordinateSystem, conv); + IFCImporter::LogDebug("got world coordinate system"); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void ResolveObjectPlacement(aiMatrix4x4 &m, const Schema_2x3::IfcObjectPlacement &place, ConversionData &conv) { + if (const Schema_2x3::IfcLocalPlacement *const local = place.ToPtr<Schema_2x3::IfcLocalPlacement>()) { + IfcMatrix4 tmp; + ConvertAxisPlacement(tmp, *local->RelativePlacement, conv); + + m = static_cast<aiMatrix4x4>(tmp); + + if (local->PlacementRelTo) { + aiMatrix4x4 tmpM; + ResolveObjectPlacement(tmpM, local->PlacementRelTo.Get(), conv); + m = tmpM * m; + } + } else { + IFCImporter::LogWarn("skipping unknown IfcObjectPlacement entity, type is ", place.GetClassName()); + } +} + +// ------------------------------------------------------------------------------------------------ +bool ProcessMappedItem(const Schema_2x3::IfcMappedItem &mapped, aiNode *nd_src, std::vector<aiNode *> &subnodes_src, unsigned int matid, ConversionData &conv) { + // insert a custom node here, the carthesian transform operator is simply a conventional transformation matrix + std::unique_ptr<aiNode> nd(new aiNode()); + nd->mName.Set("IfcMappedItem"); + + // handle the Cartesian operator + IfcMatrix4 m; + ConvertTransformOperator(m, *mapped.MappingTarget); + + IfcMatrix4 msrc; + ConvertAxisPlacement(msrc, *mapped.MappingSource->MappingOrigin, conv); + + msrc = m * msrc; + + std::set<unsigned int> meshes; + const size_t old_openings = conv.collect_openings ? conv.collect_openings->size() : 0; + if (conv.apply_openings) { + IfcMatrix4 minv = msrc; + minv.Inverse(); + for (TempOpening &open : *conv.apply_openings) { + open.Transform(minv); + } + } + + unsigned int localmatid = ProcessMaterials(mapped.GetID(), matid, conv, false); + const Schema_2x3::IfcRepresentation &repr = mapped.MappingSource->MappedRepresentation; + + bool got = false; + for (const Schema_2x3::IfcRepresentationItem &item : repr.Items) { + if (!ProcessRepresentationItem(item, localmatid, meshes, conv)) { + IFCImporter::LogWarn("skipping mapped entity of type ", item.GetClassName(), ", no representations could be generated"); + } else + got = true; + } + + if (!got) { + return false; + } + + AssignAddedMeshes(meshes, nd.get(), conv); + if (conv.collect_openings) { + + // if this pass serves us only to collect opening geometry, + // make sure we transform the TempMesh's which we need to + // preserve as well. + if (const size_t diff = conv.collect_openings->size() - old_openings) { + for (size_t i = 0; i < diff; ++i) { + (*conv.collect_openings)[old_openings + i].Transform(msrc); + } + } + } + + nd->mTransformation = nd_src->mTransformation * static_cast<aiMatrix4x4>(msrc); + subnodes_src.push_back(nd.release()); + + return true; +} + +// ------------------------------------------------------------------------------------------------ +struct RateRepresentationPredicate { + int Rate(const Schema_2x3::IfcRepresentation *r) const { + // the smaller, the better + + if (!r->RepresentationIdentifier) { + // neutral choice if no extra information is specified + return 0; + } + + const std::string &name = r->RepresentationIdentifier.Get(); + if (name == "MappedRepresentation") { + if (!r->Items.empty()) { + // take the first item and base our choice on it + const Schema_2x3::IfcMappedItem *const m = r->Items.front()->ToPtr<Schema_2x3::IfcMappedItem>(); + if (m) { + return Rate(m->MappingSource->MappedRepresentation); + } + } + return 100; + } + + return Rate(name); + } + + int Rate(const std::string &r) const { + if (r == "SolidModel") { + return -3; + } + + // give strong preference to extruded geometry. + if (r == "SweptSolid") { + return -10; + } + + if (r == "Clipping") { + return -5; + } + + // 'Brep' is difficult to get right due to possible voids in the + // polygon boundaries, so take it only if we are forced to (i.e. + // if the only alternative is (non-clipping) boolean operations, + // which are not supported at all). + if (r == "Brep") { + return -2; + } + + // Curves, bounding boxes - those will most likely not be loaded + // as we can't make any use out of this data. So consider them + // last. + if (r == "BoundingBox" || r == "Curve2D") { + return 100; + } + return 0; + } + + bool operator()(const Schema_2x3::IfcRepresentation *a, const Schema_2x3::IfcRepresentation *b) const { + return Rate(a) < Rate(b); + } +}; + +// ------------------------------------------------------------------------------------------------ +void ProcessProductRepresentation(const Schema_2x3::IfcProduct &el, aiNode *nd, std::vector<aiNode *> &subnodes, ConversionData &conv) { + if (!el.Representation) { + return; + } + + // extract Color from metadata, if present + unsigned int matid = ProcessMaterials(el.GetID(), std::numeric_limits<uint32_t>::max(), conv, false); + std::set<unsigned int> 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 + // for the different representation types to make some sensible choices what + // to load and what not to load. + const STEP::ListOf<STEP::Lazy<Schema_2x3::IfcRepresentation>, 1, 0> &src = el.Representation.Get()->Representations; + std::vector<const Schema_2x3::IfcRepresentation *> repr_ordered(src.size()); + std::copy(src.begin(), src.end(), repr_ordered.begin()); + std::sort(repr_ordered.begin(), repr_ordered.end(), RateRepresentationPredicate()); + for (const Schema_2x3::IfcRepresentation *repr : repr_ordered) { + bool res = false; + for (const Schema_2x3::IfcRepresentationItem &item : repr->Items) { + if (const Schema_2x3::IfcMappedItem *const geo = item.ToPtr<Schema_2x3::IfcMappedItem>()) { + res = ProcessMappedItem(*geo, nd, subnodes, matid, conv) || res; + } else { + res = ProcessRepresentationItem(item, matid, meshes, conv) || res; + } + } + // if we got something meaningful at this point, skip any further representations + if (res) { + break; + } + } + AssignAddedMeshes(meshes, nd, conv); +} + +typedef std::map<std::string, std::string> Metadata; + +// ------------------------------------------------------------------------------------------------ +void ProcessMetadata(const Schema_2x3::ListOf<Schema_2x3::Lazy<Schema_2x3::IfcProperty>, 1, 0> &set, ConversionData &conv, Metadata &properties, + const std::string &prefix = std::string(), + unsigned int nest = 0) { + for (const Schema_2x3::IfcProperty &property : set) { + const std::string &key = prefix.length() > 0 ? (prefix + "." + property.Name) : property.Name; + if (const Schema_2x3::IfcPropertySingleValue *const singleValue = property.ToPtr<Schema_2x3::IfcPropertySingleValue>()) { + if (singleValue->NominalValue) { + if (const ::Assimp::STEP::EXPRESS::STRING *str = singleValue->NominalValue.Get()->ToPtr<::Assimp::STEP::EXPRESS::STRING>()) { + std::string value = static_cast<std::string>(*str); + properties[key] = value; + } else if (const ::Assimp::STEP::EXPRESS::REAL *val1 = singleValue->NominalValue.Get()->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) { + float value = static_cast<float>(*val1); + std::stringstream s; + s << value; + properties[key] = s.str(); + } else if (const ::Assimp::STEP::EXPRESS::INTEGER *val2 = singleValue->NominalValue.Get()->ToPtr<::Assimp::STEP::EXPRESS::INTEGER>()) { + int64_t curValue = static_cast<int64_t>(*val2); + std::stringstream s; + s << curValue; + properties[key] = s.str(); + } + } + } else if (const Schema_2x3::IfcPropertyListValue *const listValue = property.ToPtr<Schema_2x3::IfcPropertyListValue>()) { + std::stringstream ss; + ss << "["; + unsigned index = 0; + for (const Schema_2x3::IfcValue::Out &v : listValue->ListValues) { + if (!v) continue; + if (const ::Assimp::STEP::EXPRESS::STRING *str = v->ToPtr<::Assimp::STEP::EXPRESS::STRING>()) { + std::string value = static_cast<std::string>(*str); + ss << "'" << value << "'"; + } else if (const ::Assimp::STEP::EXPRESS::REAL *val1 = v->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) { + float value = static_cast<float>(*val1); + ss << value; + } else if (const ::Assimp::STEP::EXPRESS::INTEGER *val2 = v->ToPtr<::Assimp::STEP::EXPRESS::INTEGER>()) { + int64_t value = static_cast<int64_t>(*val2); + ss << value; + } + if (index + 1 < listValue->ListValues.size()) { + ss << ","; + } + index++; + } + ss << "]"; + properties[key] = ss.str(); + } else if (const Schema_2x3::IfcComplexProperty *const complexProp = property.ToPtr<Schema_2x3::IfcComplexProperty>()) { + if (nest > 2) { // mostly arbitrary limit to prevent stack overflow vulnerabilities + IFCImporter::LogError("maximum nesting level for IfcComplexProperty reached, skipping this property."); + } else { + ProcessMetadata(complexProp->HasProperties, conv, properties, key, nest + 1); + } + } else { + properties[key] = std::string(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void ProcessMetadata(uint64_t relDefinesByPropertiesID, ConversionData &conv, Metadata &properties) { + if (const Schema_2x3::IfcRelDefinesByProperties *const pset = conv.db.GetObject(relDefinesByPropertiesID)->ToPtr<Schema_2x3::IfcRelDefinesByProperties>()) { + if (const Schema_2x3::IfcPropertySet *const set = conv.db.GetObject(pset->RelatingPropertyDefinition->GetID())->ToPtr<Schema_2x3::IfcPropertySet>()) { + ProcessMetadata(set->HasProperties, conv, properties); + } + } +} + +// ------------------------------------------------------------------------------------------------ +aiNode *ProcessSpatialStructure(aiNode *parent, const Schema_2x3::IfcProduct &el, ConversionData &conv, + std::vector<TempOpening> *collect_openings = nullptr) { + const STEP::DB::RefMap &refs = conv.db.GetRefs(); + + // skip over space and annotation nodes - usually, these have no meaning in Assimp's context + bool skipGeometry = false; + if (conv.settings.skipSpaceRepresentations) { + if (el.ToPtr<Schema_2x3::IfcSpace>()) { + IFCImporter::LogVerboseDebug("skipping IfcSpace entity due to importer settings"); + skipGeometry = true; + } + } + + if (conv.settings.skipAnnotations) { + if (el.ToPtr<Schema_2x3::IfcAnnotation>()) { + IFCImporter::LogVerboseDebug("skipping IfcAnnotation entity due to importer settings"); + return nullptr; + } + } + + // add an output node for this spatial structure + aiNode *nd(new aiNode); + nd->mName.Set(el.GetClassName() + "_" + (el.Name ? el.Name.Get() : "Unnamed") + "_" + el.GlobalId); + nd->mParent = parent; + + conv.already_processed.insert(el.GetID()); + + // check for node metadata + STEP::DB::RefMapRange children = refs.equal_range(el.GetID()); + if (children.first != refs.end()) { + Metadata properties; + if (children.first == children.second) { + // handles single property set + ProcessMetadata((*children.first).second, conv, properties); + } else { + // handles multiple property sets (currently all property sets are merged, + // which may not be the best solution in the long run) + for (STEP::DB::RefMap::const_iterator it = children.first; it != children.second; ++it) { + ProcessMetadata((*it).second, conv, properties); + } + } + + if (!properties.empty()) { + aiMetadata *data = aiMetadata::Alloc(static_cast<unsigned int>(properties.size())); + unsigned int index(0); + for (const Metadata::value_type &kv : properties) { + data->Set(index++, kv.first, aiString(kv.second)); + } + nd->mMetaData = data; + } + } + + if (el.ObjectPlacement) { + ResolveObjectPlacement(nd->mTransformation, el.ObjectPlacement.Get(), conv); + } + + std::vector<TempOpening> openings; + + IfcMatrix4 myInv; + bool didinv = false; + + // convert everything contained directly within this structure, + // this may result in more nodes. + std::vector<aiNode *> subnodes; + try { + // locate aggregates and 'contained-in-here'-elements of this spatial structure and add them in recursively + // on our way, collect openings in *this* element + STEP::DB::RefMapRange range = refs.equal_range(el.GetID()); + + for (STEP::DB::RefMapRange range2 = range; range2.first != range.second; ++range2.first) { + // skip over meshes that have already been processed before. This is strictly necessary + // because the reverse indices also include references contained in argument lists and + // therefore every element has a back-reference hold by its parent. + if (conv.already_processed.find((*range2.first).second) != conv.already_processed.end()) { + continue; + } + const STEP::LazyObject &obj = conv.db.MustGetObject((*range2.first).second); + + // handle regularly-contained elements + if (const Schema_2x3::IfcRelContainedInSpatialStructure *const cont = obj->ToPtr<Schema_2x3::IfcRelContainedInSpatialStructure>()) { + if (cont->RelatingStructure->GetID() != el.GetID()) { + continue; + } + for (const Schema_2x3::IfcProduct &pro : cont->RelatedElements) { + if (pro.ToPtr<Schema_2x3::IfcOpeningElement>()) { + // IfcOpeningElement is handled below. Sadly we can't use it here as is: + // The docs say that opening elements are USUALLY attached to building storey, + // but we want them for the building elements to which they belong. + continue; + } + + aiNode *const ndnew = ProcessSpatialStructure(nd, pro, conv, nullptr); + if (ndnew) { + subnodes.push_back(ndnew); + } + } + } + // handle openings, which we collect in a list rather than adding them to the node graph + else if (const Schema_2x3::IfcRelVoidsElement *const fills = obj->ToPtr<Schema_2x3::IfcRelVoidsElement>()) { + if (fills->RelatingBuildingElement->GetID() == el.GetID()) { + const Schema_2x3::IfcFeatureElementSubtraction &open = fills->RelatedOpeningElement; + + // move opening elements to a separate node since they are semantically different than elements that are just 'contained' + std::unique_ptr<aiNode> nd_aggr(new aiNode()); + nd_aggr->mName.Set("$RelVoidsElement"); + nd_aggr->mParent = nd; + + nd_aggr->mTransformation = nd->mTransformation; + + std::vector<TempOpening> openings_local; + aiNode *const ndnew = ProcessSpatialStructure(nd_aggr.get(), open, conv, &openings_local); + if (ndnew) { + + nd_aggr->mNumChildren = 1; + nd_aggr->mChildren = new aiNode *[1](); + + nd_aggr->mChildren[0] = ndnew; + + if (openings_local.size()) { + if (!didinv) { + myInv = aiMatrix4x4(nd->mTransformation).Inverse(); + didinv = true; + } + + // we need all openings to be in the local space of *this* node, so transform them + for (TempOpening &op : openings_local) { + op.Transform(myInv * nd_aggr->mChildren[0]->mTransformation); + openings.push_back(op); + } + } + subnodes.push_back(nd_aggr.release()); + } + } + } + } + + for (; range.first != range.second; ++range.first) { + // see note in loop above + if (conv.already_processed.find((*range.first).second) != conv.already_processed.end()) { + continue; + } + if (const Schema_2x3::IfcRelAggregates *const aggr = conv.db.GetObject((*range.first).second)->ToPtr<Schema_2x3::IfcRelAggregates>()) { + if (aggr->RelatingObject->GetID() != el.GetID()) { + continue; + } + + // move aggregate elements to a separate node since they are semantically different than elements that are just 'contained' + std::unique_ptr<aiNode> nd_aggr(new aiNode()); + nd_aggr->mName.Set("$RelAggregates"); + nd_aggr->mParent = nd; + + nd_aggr->mTransformation = nd->mTransformation; + + nd_aggr->mChildren = new aiNode *[aggr->RelatedObjects.size()](); + for (const Schema_2x3::IfcObjectDefinition &def : aggr->RelatedObjects) { + if (const Schema_2x3::IfcProduct *const prod = def.ToPtr<Schema_2x3::IfcProduct>()) { + + aiNode *const ndnew = ProcessSpatialStructure(nd_aggr.get(), *prod, conv, nullptr); + if (ndnew) { + nd_aggr->mChildren[nd_aggr->mNumChildren++] = ndnew; + } + } + } + + subnodes.push_back(nd_aggr.release()); + } + } + + conv.collect_openings = collect_openings; + if (!conv.collect_openings) { + conv.apply_openings = &openings; + } + + if (!skipGeometry) { + ProcessProductRepresentation(el, nd, subnodes, conv); + conv.apply_openings = conv.collect_openings = nullptr; + } + + if (subnodes.size()) { + nd->mChildren = new aiNode *[subnodes.size()](); + for (aiNode *nd2 : subnodes) { + nd->mChildren[nd->mNumChildren++] = nd2; + nd2->mParent = nd; + } + } + } catch (...) { + // it hurts, but I don't want to pull boost::ptr_vector into -noboost only for these few spots here + std::for_each(subnodes.begin(), subnodes.end(), delete_fun<aiNode>()); + throw; + } + + ai_assert(conv.already_processed.find(el.GetID()) != conv.already_processed.end()); + conv.already_processed.erase(conv.already_processed.find(el.GetID())); + return nd; +} + +// ------------------------------------------------------------------------------------------------ +void ProcessSpatialStructures(ConversionData &conv) { + // XXX add support for multiple sites (i.e. IfcSpatialStructureElements with composition == COMPLEX) + + // process all products in the file. it is reasonable to assume that a + // file that is relevant for us contains at least a site or a building. + const STEP::DB::ObjectMapByType &map = conv.db.GetObjectsByType(); + + ai_assert(map.find("ifcsite") != map.end()); + const STEP::DB::ObjectSet *range = &map.find("ifcsite")->second; + + if (range->empty()) { + ai_assert(map.find("ifcbuilding") != map.end()); + range = &map.find("ifcbuilding")->second; + if (range->empty()) { + // no site, no building - fail; + IFCImporter::ThrowException("no root element found (expected IfcBuilding or preferably IfcSite)"); + } + } + + std::vector<aiNode *> nodes; + + for (const STEP::LazyObject *lz : *range) { + const Schema_2x3::IfcSpatialStructureElement *const prod = lz->ToPtr<Schema_2x3::IfcSpatialStructureElement>(); + if (!prod) { + continue; + } + IFCImporter::LogVerboseDebug("looking at spatial structure `", (prod->Name ? prod->Name.Get() : "unnamed"), "`", (prod->ObjectType ? " which is of type " + prod->ObjectType.Get() : "")); + + // the primary sites are referenced by an IFCRELAGGREGATES element which assigns them to the IFCPRODUCT + const STEP::DB::RefMap &refs = conv.db.GetRefs(); + STEP::DB::RefMapRange ref_range = refs.equal_range(conv.proj.GetID()); + for (; ref_range.first != ref_range.second; ++ref_range.first) { + if (const Schema_2x3::IfcRelAggregates *const aggr = conv.db.GetObject((*ref_range.first).second)->ToPtr<Schema_2x3::IfcRelAggregates>()) { + + for (const Schema_2x3::IfcObjectDefinition &def : aggr->RelatedObjects) { + // comparing pointer values is not sufficient, we would need to cast them to the same type first + // as there is multiple inheritance in the game. + if (def.GetID() == prod->GetID()) { + IFCImporter::LogVerboseDebug("selecting this spatial structure as root structure"); + // got it, this is one primary site. + nodes.push_back(ProcessSpatialStructure(nullptr, *prod, conv, nullptr)); + } + } + } + } + } + + size_t nb_nodes = nodes.size(); + + if (nb_nodes == 0) { + IFCImporter::LogWarn("failed to determine primary site element, taking all the IfcSite"); + for (const STEP::LazyObject *lz : *range) { + const Schema_2x3::IfcSpatialStructureElement *const prod = lz->ToPtr<Schema_2x3::IfcSpatialStructureElement>(); + if (!prod) { + continue; + } + + nodes.push_back(ProcessSpatialStructure(nullptr, *prod, conv, nullptr)); + } + + nb_nodes = nodes.size(); + } + + if (nb_nodes == 1) { + conv.out->mRootNode = nodes[0]; + } else if (nb_nodes > 1) { + conv.out->mRootNode = new aiNode("Root"); + conv.out->mRootNode->mParent = nullptr; + conv.out->mRootNode->mNumChildren = static_cast<unsigned int>(nb_nodes); + conv.out->mRootNode->mChildren = new aiNode *[conv.out->mRootNode->mNumChildren]; + + for (size_t i = 0; i < nb_nodes; ++i) { + aiNode *node = nodes[i]; + + node->mParent = conv.out->mRootNode; + + conv.out->mRootNode->mChildren[i] = node; + } + } else { + IFCImporter::ThrowException("failed to determine primary site element"); + } +} + +// ------------------------------------------------------------------------------------------------ +void MakeTreeRelative(aiNode *start, const aiMatrix4x4 &combined) { + // combined is the parent's absolute transformation matrix + const aiMatrix4x4 old = start->mTransformation; + + if (!combined.IsIdentity()) { + start->mTransformation = aiMatrix4x4(combined).Inverse() * start->mTransformation; + } + + // All nodes store absolute transformations right now, so we need to make them relative + for (unsigned int i = 0; i < start->mNumChildren; ++i) { + MakeTreeRelative(start->mChildren[i], old); + } +} + +// ------------------------------------------------------------------------------------------------ +void MakeTreeRelative(ConversionData &conv) { + MakeTreeRelative(conv.out->mRootNode, IfcMatrix4()); +} + +} // namespace + +#endif diff --git a/libs/assimp/code/AssetLib/IFC/IFCLoader.h b/libs/assimp/code/AssetLib/IFC/IFCLoader.h new file mode 100644 index 0000000..7b2c3aa --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCLoader.h @@ -0,0 +1,116 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IFC.h + * @brief Declaration of the Industry Foundation Classes (IFC) loader main class + */ +#ifndef INCLUDED_AI_IFC_LOADER_H +#define INCLUDED_AI_IFC_LOADER_H + +#include <assimp/BaseImporter.h> +#include <assimp/LogAux.h> + +namespace Assimp { + +// TinyFormatter.h +namespace Formatter { + +template <typename T, typename TR, typename A> +class basic_formatter; +typedef class basic_formatter<char, std::char_traits<char>, std::allocator<char>> format; + +} // namespace Formatter + +namespace STEP { + +class DB; + +} + +// ------------------------------------------------------------------------------------------- +/** Load the IFC format, which is an open specification to describe building and construction + industry data. + + See http://en.wikipedia.org/wiki/Industry_Foundation_Classes +*/ +// ------------------------------------------------------------------------------------------- +class IFCImporter : public BaseImporter, public LogFunctions<IFCImporter> { +public: + // loader settings, publicly accessible via their corresponding AI_CONFIG constants + struct Settings { + Settings() : + skipSpaceRepresentations(), useCustomTriangulation(), skipAnnotations(), conicSamplingAngle(10.f), cylindricalTessellation(32) {} + + bool skipSpaceRepresentations; + bool useCustomTriangulation; + bool skipAnnotations; + float conicSamplingAngle; + int cylindricalTessellation; + }; + + IFCImporter(); + ~IFCImporter() override; + + // -------------------- + bool CanRead(const std::string &pFile, + IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // -------------------- + const aiImporterDesc *GetInfo() const override; + + // -------------------- + void SetupProperties(const Importer *pImp) override; + + // -------------------- + void InternReadFile(const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler) override; + +private: + Settings settings; + +}; // !class IFCImporter + +} // end of namespace Assimp +#endif // !INCLUDED_AI_IFC_LOADER_H diff --git a/libs/assimp/code/AssetLib/IFC/IFCMaterial.cpp b/libs/assimp/code/AssetLib/IFC/IFCMaterial.cpp new file mode 100644 index 0000000..6cd0e59 --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCMaterial.cpp @@ -0,0 +1,202 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IFCMaterial.cpp + * @brief Implementation of conversion routines to convert IFC materials to aiMaterial + */ + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER + +#include "IFCUtil.h" +#include <limits> +#include <assimp/material.h> + +namespace Assimp { +namespace IFC { + +// ------------------------------------------------------------------------------------------------ +static int ConvertShadingMode(const std::string& name) { + if (name == "BLINN") { + return aiShadingMode_Blinn; + } + else if (name == "FLAT" || name == "NOTDEFINED") { + return aiShadingMode_NoShading; + } + else if (name == "PHONG") { + return aiShadingMode_Phong; + } + IFCImporter::LogWarn("shading mode ", name, " not recognized by Assimp, using Phong instead"); + return aiShadingMode_Phong; +} + +// ------------------------------------------------------------------------------------------------ +static void FillMaterial(aiMaterial* mat,const IFC::Schema_2x3::IfcSurfaceStyle* surf,ConversionData& conv) { + aiString name; + name.Set((surf->Name? surf->Name.Get() : "IfcSurfaceStyle_Unnamed")); + mat->AddProperty(&name,AI_MATKEY_NAME); + + // now see which kinds of surface information are present + for (const std::shared_ptr<const IFC::Schema_2x3::IfcSurfaceStyleElementSelect> &sel2 : surf->Styles) { + if (const IFC::Schema_2x3::IfcSurfaceStyleShading* shade = sel2->ResolveSelectPtr<IFC::Schema_2x3::IfcSurfaceStyleShading>(conv.db)) { + aiColor4D col_base,col; + + ConvertColor(col_base, shade->SurfaceColour); + mat->AddProperty(&col_base,1, AI_MATKEY_COLOR_DIFFUSE); + + if (const IFC::Schema_2x3::IfcSurfaceStyleRendering* ren = shade->ToPtr<IFC::Schema_2x3::IfcSurfaceStyleRendering>()) { + + if (ren->Transparency) { + const float t = 1.f-static_cast<float>(ren->Transparency.Get()); + mat->AddProperty(&t,1, AI_MATKEY_OPACITY); + } + + if (ren->DiffuseColour) { + ConvertColor(col, *ren->DiffuseColour.Get(),conv,&col_base); + mat->AddProperty(&col,1, AI_MATKEY_COLOR_DIFFUSE); + } + + if (ren->SpecularColour) { + ConvertColor(col, *ren->SpecularColour.Get(),conv,&col_base); + mat->AddProperty(&col,1, AI_MATKEY_COLOR_SPECULAR); + } + + if (ren->TransmissionColour) { + ConvertColor(col, *ren->TransmissionColour.Get(),conv,&col_base); + mat->AddProperty(&col,1, AI_MATKEY_COLOR_TRANSPARENT); + } + + if (ren->ReflectionColour) { + ConvertColor(col, *ren->ReflectionColour.Get(),conv,&col_base); + mat->AddProperty(&col,1, AI_MATKEY_COLOR_REFLECTIVE); + } + + const int shading = (ren->SpecularHighlight && ren->SpecularColour)?ConvertShadingMode(ren->ReflectanceMethod):static_cast<int>(aiShadingMode_Gouraud); + mat->AddProperty(&shading,1, AI_MATKEY_SHADING_MODEL); + + if (ren->SpecularHighlight) { + if(const ::Assimp::STEP::EXPRESS::REAL* rt = ren->SpecularHighlight.Get()->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) { + // at this point we don't distinguish between the two distinct ways of + // specifying highlight intensities. leave this to the user. + const float e = static_cast<float>(*rt); + mat->AddProperty(&e,1,AI_MATKEY_SHININESS); + } + else { + IFCImporter::LogWarn("unexpected type error, SpecularHighlight should be a REAL"); + } + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +unsigned int ProcessMaterials(uint64_t id, unsigned int prevMatId, ConversionData& conv, bool forceDefaultMat) { + STEP::DB::RefMapRange range = conv.db.GetRefs().equal_range(id); + for(;range.first != range.second; ++range.first) { + if(const IFC::Schema_2x3::IfcStyledItem* const styled = conv.db.GetObject((*range.first).second)->ToPtr<IFC::Schema_2x3::IfcStyledItem>()) { + for(const IFC::Schema_2x3::IfcPresentationStyleAssignment& as : styled->Styles) { + for (const std::shared_ptr<const IFC::Schema_2x3::IfcPresentationStyleSelect> &sel : as.Styles) { + + if( const IFC::Schema_2x3::IfcSurfaceStyle* const surf = sel->ResolveSelectPtr<IFC::Schema_2x3::IfcSurfaceStyle>(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<std::string>(surf->Side); + if( side != "BOTH" ) { + IFCImporter::LogWarn("ignoring surface side marker on IFC::IfcSurfaceStyle: ", side); + } + + std::unique_ptr<aiMaterial> mat(new aiMaterial()); + + FillMaterial(mat.get(), surf, conv); + + conv.materials.push_back(mat.release()); + unsigned int matindex = static_cast<unsigned int>(conv.materials.size() - 1); + conv.cached_materials[surf] = matindex; + return matindex; + } + } + } + } + } + + // no local material defined. If there's global one, use that instead + if ( prevMatId != std::numeric_limits<uint32_t>::max() ) { + return prevMatId; + } + + // we're still here - create an default material if required, or simply fail otherwise + if ( !forceDefaultMat ) { + return std::numeric_limits<uint32_t>::max(); + } + + aiString name; + name.Set("<IFCDefault>"); + // 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::unique_ptr<aiMaterial> 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 +} // ! Assimp + +#endif // ASSIMP_BUILD_NO_IFC_IMPORTER diff --git a/libs/assimp/code/AssetLib/IFC/IFCOpenings.cpp b/libs/assimp/code/AssetLib/IFC/IFCOpenings.cpp new file mode 100644 index 0000000..7420019 --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCOpenings.cpp @@ -0,0 +1,1955 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IFCOpenings.cpp + * @brief Implements a subset of Ifc CSG operations for pouring + * holes for windows and doors into walls. + */ + + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER +#include "IFCUtil.h" +#include "Common/PolyTools.h" +#include "PostProcessing/ProcessHelper.h" + +#ifdef ASSIMP_USE_HUNTER +# include <poly2tri/poly2tri.h> +# include <polyclipping/clipper.hpp> +#else +# include "../contrib/poly2tri/poly2tri/poly2tri.h" +# include "../contrib/clipper/clipper.hpp" +#endif + +#include <iterator> +#include <forward_list> +#include <deque> + +namespace Assimp { + namespace IFC { + + using ClipperLib::ulong64; + // XXX use full -+ range ... + const ClipperLib::long64 max_ulong64 = 1518500249; // clipper.cpp / hiRange var + + //#define to_int64(p) (static_cast<ulong64>( std::max( 0., std::min( static_cast<IfcFloat>((p)), 1.) ) * max_ulong64 )) +#define to_int64(p) (static_cast<ulong64>(static_cast<IfcFloat>((p) ) * max_ulong64 )) +#define from_int64(p) (static_cast<IfcFloat>((p)) / max_ulong64) +#define one_vec (IfcVector2(static_cast<IfcFloat>(1.0),static_cast<IfcFloat>(1.0))) + + + // fallback method to generate wall openings + bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings, + TempMesh& curmesh); + + +typedef std::pair< IfcVector2, IfcVector2 > BoundingBox; +typedef std::map<IfcVector2,size_t,XYSorter> XYSortedField; + + +// ------------------------------------------------------------------------------------------------ +void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField& field, + const std::vector< BoundingBox >& bbs, + std::vector<IfcVector2>& out) +{ + if (!(pmin.x-pmax.x) || !(pmin.y-pmax.y)) { + return; + } + + IfcFloat xs = 1e10, xe = 1e10; + bool found = false; + + // Search along the x-axis until we find an opening + XYSortedField::iterator start = field.begin(); + for(; start != field.end(); ++start) { + const BoundingBox& bb = bbs[(*start).second]; + if(bb.first.x >= pmax.x) { + break; + } + + if (bb.second.x > pmin.x && bb.second.y > pmin.y && bb.first.y < pmax.y) { + xs = bb.first.x; + xe = bb.second.x; + found = true; + break; + } + } + + if (!found) { + // the rectangle [pmin,pend] is opaque, fill it + out.push_back(pmin); + out.push_back(IfcVector2(pmin.x,pmax.y)); + out.push_back(pmax); + out.push_back(IfcVector2(pmax.x,pmin.y)); + return; + } + + xs = std::max(pmin.x,xs); + xe = std::min(pmax.x,xe); + + // see if there's an offset to fill at the top of our quad + if (xs - pmin.x) { + out.push_back(pmin); + out.push_back(IfcVector2(pmin.x,pmax.y)); + out.push_back(IfcVector2(xs,pmax.y)); + out.push_back(IfcVector2(xs,pmin.y)); + } + + // search along the y-axis for all openings that overlap xs and our quad + IfcFloat ylast = pmin.y; + found = false; + for(; start != field.end(); ++start) { + const BoundingBox& bb = bbs[(*start).second]; + if (bb.first.x > xs || bb.first.y >= pmax.y) { + break; + } + + if (bb.second.y > ylast) { + + found = true; + const IfcFloat ys = std::max(bb.first.y,pmin.y), ye = std::min(bb.second.y,pmax.y); + if (ys - ylast > 0.0f) { + QuadrifyPart( IfcVector2(xs,ylast), IfcVector2(xe,ys) ,field,bbs,out); + } + + // the following are the window vertices + + /*wnd.push_back(IfcVector2(xs,ys)); + wnd.push_back(IfcVector2(xs,ye)); + wnd.push_back(IfcVector2(xe,ye)); + wnd.push_back(IfcVector2(xe,ys));*/ + ylast = ye; + } + } + if (!found) { + // the rectangle [pmin,pend] is opaque, fill it + out.push_back(IfcVector2(xs,pmin.y)); + out.push_back(IfcVector2(xs,pmax.y)); + out.push_back(IfcVector2(xe,pmax.y)); + out.push_back(IfcVector2(xe,pmin.y)); + return; + } + if (ylast < pmax.y) { + QuadrifyPart( IfcVector2(xs,ylast), IfcVector2(xe,pmax.y) ,field,bbs,out); + } + + // now for the whole rest + if (pmax.x-xe) { + QuadrifyPart(IfcVector2(xe,pmin.y), pmax ,field,bbs,out); + } +} + +typedef std::vector<IfcVector2> Contour; +typedef std::vector<bool> SkipList; // should probably use int for performance reasons + +struct ProjectedWindowContour +{ + Contour contour; + BoundingBox bb; + SkipList skiplist; + bool is_rectangular; + + + ProjectedWindowContour(const Contour& contour, const BoundingBox& bb, bool is_rectangular) + : contour(contour) + , bb(bb) + , is_rectangular(is_rectangular) + {} + + + bool IsInvalid() const { + return contour.empty(); + } + + void FlagInvalid() { + contour.clear(); + } + + void PrepareSkiplist() { + skiplist.resize(contour.size(),false); + } +}; + +typedef std::vector< ProjectedWindowContour > ContourVector; + +// ------------------------------------------------------------------------------------------------ +bool BoundingBoxesOverlapping( const BoundingBox &ibb, const BoundingBox &bb ) +{ + // count the '=' case as non-overlapping but as adjacent to each other + return ibb.first.x < bb.second.x && ibb.second.x > bb.first.x && + ibb.first.y < bb.second.y && ibb.second.y > bb.first.y; +} + +// ------------------------------------------------------------------------------------------------ +bool IsDuplicateVertex(const IfcVector2& vv, const std::vector<IfcVector2>& temp_contour) +{ + // sanity check for duplicate vertices + for(const IfcVector2& cp : temp_contour) { + if ((cp-vv).SquareLength() < 1e-5f) { + return true; + } + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +void ExtractVerticesFromClipper(const ClipperLib::Polygon& poly, std::vector<IfcVector2>& temp_contour, + bool filter_duplicates = false) +{ + temp_contour.clear(); + for(const ClipperLib::IntPoint& point : poly) { + IfcVector2 vv = IfcVector2( from_int64(point.X), from_int64(point.Y)); + vv = std::max(vv,IfcVector2()); + vv = std::min(vv,one_vec); + + if (!filter_duplicates || !IsDuplicateVertex(vv, temp_contour)) { + temp_contour.push_back(vv); + } + } +} + +// ------------------------------------------------------------------------------------------------ +BoundingBox GetBoundingBox(const ClipperLib::Polygon& poly) +{ + IfcVector2 newbb_min, newbb_max; + MinMaxChooser<IfcVector2>()(newbb_min, newbb_max); + + for(const ClipperLib::IntPoint& point : poly) { + IfcVector2 vv = IfcVector2( from_int64(point.X), from_int64(point.Y)); + + // sanity rounding + vv = std::max(vv,IfcVector2()); + vv = std::min(vv,one_vec); + + newbb_min = std::min(newbb_min,vv); + newbb_max = std::max(newbb_max,vv); + } + return BoundingBox(newbb_min, newbb_max); +} + +// ------------------------------------------------------------------------------------------------ +void InsertWindowContours(const ContourVector& contours, + const std::vector<TempOpening>& /*openings*/, + TempMesh& curmesh) +{ + // fix windows - we need to insert the real, polygonal shapes into the quadratic holes that we have now + for(size_t i = 0; i < contours.size();++i) { + const BoundingBox& bb = contours[i].bb; + const std::vector<IfcVector2>& contour = contours[i].contour; + if(contour.empty()) { + continue; + } + + // check if we need to do it at all - many windows just fit perfectly into their quadratic holes, + // i.e. their contours *are* already their bounding boxes. + if (contour.size() == 4) { + std::set<IfcVector2,XYSorter> verts; + for(size_t n = 0; n < 4; ++n) { + verts.insert(contour[n]); + } + const std::set<IfcVector2,XYSorter>::const_iterator end = verts.end(); + if (verts.find(bb.first)!=end && verts.find(bb.second)!=end + && verts.find(IfcVector2(bb.first.x,bb.second.y))!=end + && verts.find(IfcVector2(bb.second.x,bb.first.y))!=end + ) { + continue; + } + } + + const IfcFloat diag = (bb.first-bb.second).Length(); + const IfcFloat epsilon = diag/1000.f; + + // walk through all contour points and find those that lie on the BB corner + size_t last_hit = (size_t)-1, very_first_hit = (size_t)-1; + IfcVector2 edge; + for(size_t n = 0, e=0, size = contour.size();; n=(n+1)%size, ++e) { + + // sanity checking + if (e == size*2) { + IFCImporter::LogError("encountered unexpected topology while generating window contour"); + break; + } + + const IfcVector2& v = contour[n]; + + bool hit = false; + if (std::fabs(v.x-bb.first.x)<epsilon) { + edge.x = bb.first.x; + hit = true; + } + else if (std::fabs(v.x-bb.second.x)<epsilon) { + edge.x = bb.second.x; + hit = true; + } + + if (std::fabs(v.y-bb.first.y)<epsilon) { + edge.y = bb.first.y; + hit = true; + } + else if (std::fabs(v.y-bb.second.y)<epsilon) { + edge.y = bb.second.y; + hit = true; + } + + if (hit) { + if (last_hit != (size_t)-1) { + + const size_t old = curmesh.mVerts.size(); + size_t cnt = last_hit > n ? size-(last_hit-n) : n-last_hit; + for(size_t a = last_hit, ee = 0; ee <= cnt; a=(a+1)%size, ++ee) { + // hack: this is to fix cases where opening contours are self-intersecting. + // Clipper doesn't produce such polygons, but as soon as we're back in + // our brave new floating-point world, very small distances are consumed + // by the maximum available precision, leading to self-intersecting + // polygons. This fix makes concave windows fail even worse, but + // anyway, fail is fail. + if ((contour[a] - edge).SquareLength() > diag*diag*0.7) { + continue; + } + curmesh.mVerts.push_back(IfcVector3(contour[a].x, contour[a].y, 0.0f)); + } + + if (edge != contour[last_hit]) { + + IfcVector2 corner = edge; + + if (std::fabs(contour[last_hit].x-bb.first.x)<epsilon) { + corner.x = bb.first.x; + } + else if (std::fabs(contour[last_hit].x-bb.second.x)<epsilon) { + corner.x = bb.second.x; + } + + if (std::fabs(contour[last_hit].y-bb.first.y)<epsilon) { + corner.y = bb.first.y; + } + else if (std::fabs(contour[last_hit].y-bb.second.y)<epsilon) { + corner.y = bb.second.y; + } + + curmesh.mVerts.push_back(IfcVector3(corner.x, corner.y, 0.0f)); + } + else if (cnt == 1) { + // avoid degenerate polygons (also known as lines or points) + curmesh.mVerts.erase(curmesh.mVerts.begin()+old,curmesh.mVerts.end()); + } + + if (const size_t d = curmesh.mVerts.size()-old) { + curmesh.mVertcnt.push_back(static_cast<unsigned int>(d)); + std::reverse(curmesh.mVerts.rbegin(),curmesh.mVerts.rbegin()+d); + } + if (n == very_first_hit) { + break; + } + } + else { + very_first_hit = n; + } + + last_hit = n; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void MergeWindowContours (const std::vector<IfcVector2>& a, + const std::vector<IfcVector2>& b, + ClipperLib::ExPolygons& out) +{ + out.clear(); + + ClipperLib::Clipper clipper; + ClipperLib::Polygon clip; + + for(const IfcVector2& pip : a) { + clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } + + if (ClipperLib::Orientation(clip)) { + std::reverse(clip.begin(), clip.end()); + } + + clipper.AddPolygon(clip, ClipperLib::ptSubject); + clip.clear(); + + for(const IfcVector2& pip : b) { + clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } + + if (ClipperLib::Orientation(clip)) { + std::reverse(clip.begin(), clip.end()); + } + + clipper.AddPolygon(clip, ClipperLib::ptSubject); + clipper.Execute(ClipperLib::ctUnion, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero); +} + +// ------------------------------------------------------------------------------------------------ +// Subtract a from b +void MakeDisjunctWindowContours (const std::vector<IfcVector2>& a, + const std::vector<IfcVector2>& b, + ClipperLib::ExPolygons& out) +{ + out.clear(); + + ClipperLib::Clipper clipper; + ClipperLib::Polygon clip; + + for(const IfcVector2& pip : a) { + clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } + + if (ClipperLib::Orientation(clip)) { + std::reverse(clip.begin(), clip.end()); + } + + clipper.AddPolygon(clip, ClipperLib::ptClip); + clip.clear(); + + for(const IfcVector2& pip : b) { + clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } + + if (ClipperLib::Orientation(clip)) { + std::reverse(clip.begin(), clip.end()); + } + + clipper.AddPolygon(clip, ClipperLib::ptSubject); + clipper.Execute(ClipperLib::ctDifference, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero); +} + +// ------------------------------------------------------------------------------------------------ +void CleanupWindowContour(ProjectedWindowContour& window) +{ + std::vector<IfcVector2> scratch; + std::vector<IfcVector2>& contour = window.contour; + + ClipperLib::Polygon subject; + ClipperLib::Clipper clipper; + ClipperLib::ExPolygons clipped; + + for(const IfcVector2& pip : contour) { + subject.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } + + clipper.AddPolygon(subject,ClipperLib::ptSubject); + clipper.Execute(ClipperLib::ctUnion,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero); + + // This should yield only one polygon or something went wrong + if (clipped.size() != 1) { + + // Empty polygon? drop the contour altogether + if(clipped.empty()) { + IFCImporter::LogError("error during polygon clipping, window contour is degenerate"); + window.FlagInvalid(); + return; + } + + // Else: take the first only + IFCImporter::LogError("error during polygon clipping, window contour is not convex"); + } + + ExtractVerticesFromClipper(clipped[0].outer, scratch); + // Assume the bounding box doesn't change during this operation +} + +// ------------------------------------------------------------------------------------------------ +void CleanupWindowContours(ContourVector& contours) +{ + // Use PolyClipper to clean up window contours + try { + for(ProjectedWindowContour& window : contours) { + CleanupWindowContour(window); + } + } + catch (const char* sx) { + IFCImporter::LogError("error during polygon clipping, window shape may be wrong: (Clipper: " + + std::string(sx) + ")"); + } +} + +// ------------------------------------------------------------------------------------------------ +void CleanupOuterContour(const std::vector<IfcVector2>& contour_flat, TempMesh& curmesh) +{ + std::vector<IfcVector3> vold; + std::vector<unsigned int> iold; + + vold.reserve(curmesh.mVerts.size()); + iold.reserve(curmesh.mVertcnt.size()); + + // Fix the outer contour using polyclipper + try { + + ClipperLib::Polygon subject; + ClipperLib::Clipper clipper; + ClipperLib::ExPolygons clipped; + + ClipperLib::Polygon clip; + clip.reserve(contour_flat.size()); + for(const IfcVector2& pip : contour_flat) { + clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } + + if (!ClipperLib::Orientation(clip)) { + std::reverse(clip.begin(), clip.end()); + } + + // We need to run polyclipper on every single polygon -- we can't run it one all + // of them at once or it would merge them all together which would undo all + // previous steps + subject.reserve(4); + size_t index = 0; + size_t countdown = 0; + for(const IfcVector3& pip : curmesh.mVerts) { + if (!countdown) { + countdown = curmesh.mVertcnt[index++]; + if (!countdown) { + continue; + } + } + subject.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + if (--countdown == 0) { + if (!ClipperLib::Orientation(subject)) { + std::reverse(subject.begin(), subject.end()); + } + + clipper.AddPolygon(subject,ClipperLib::ptSubject); + clipper.AddPolygon(clip,ClipperLib::ptClip); + + clipper.Execute(ClipperLib::ctIntersection,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero); + + for(const ClipperLib::ExPolygon& ex : clipped) { + iold.push_back(static_cast<unsigned int>(ex.outer.size())); + for(const ClipperLib::IntPoint& point : ex.outer) { + vold.push_back(IfcVector3( + from_int64(point.X), + from_int64(point.Y), + 0.0f)); + } + } + + subject.clear(); + clipped.clear(); + clipper.Clear(); + } + } + } + catch (const char* sx) { + IFCImporter::LogError("Ifc: error during polygon clipping, wall contour line may be wrong: (Clipper: " + + std::string(sx) + ")"); + + return; + } + + // swap data arrays + std::swap(vold,curmesh.mVerts); + std::swap(iold,curmesh.mVertcnt); +} + +typedef std::vector<TempOpening*> OpeningRefs; +typedef std::vector<OpeningRefs > OpeningRefVector; + +typedef std::vector<std::pair< + ContourVector::const_iterator, + Contour::const_iterator> +> ContourRefVector; + +// ------------------------------------------------------------------------------------------------ +bool BoundingBoxesAdjacent(const BoundingBox& bb, const BoundingBox& ibb) +{ + // TODO: I'm pretty sure there is a much more compact way to check this + const IfcFloat epsilon = Math::getEpsilon<float>(); + return (std::fabs(bb.second.x - ibb.first.x) < epsilon && bb.first.y <= ibb.second.y && bb.second.y >= ibb.first.y) || + (std::fabs(bb.first.x - ibb.second.x) < epsilon && ibb.first.y <= bb.second.y && ibb.second.y >= bb.first.y) || + (std::fabs(bb.second.y - ibb.first.y) < epsilon && bb.first.x <= ibb.second.x && bb.second.x >= ibb.first.x) || + (std::fabs(bb.first.y - ibb.second.y) < epsilon && ibb.first.x <= bb.second.x && ibb.second.x >= bb.first.x); +} + +// ------------------------------------------------------------------------------------------------ +// Check if m0,m1 intersects n0,n1 assuming same ordering of the points in the line segments +// output the intersection points on n0,n1 +bool IntersectingLineSegments(const IfcVector2& n0, const IfcVector2& n1, + const IfcVector2& m0, const IfcVector2& m1, + IfcVector2& out0, IfcVector2& out1) +{ + const IfcVector2 n0_to_n1 = n1 - n0; + + const IfcVector2 n0_to_m0 = m0 - n0; + const IfcVector2 n1_to_m1 = m1 - n1; + + const IfcVector2 n0_to_m1 = m1 - n0; + + const IfcFloat e = 1e-5f; + const IfcFloat smalle = 1e-9f; + + static const IfcFloat inf = std::numeric_limits<IfcFloat>::infinity(); + + if (!(n0_to_m0.SquareLength() < e*e || std::fabs(n0_to_m0 * n0_to_n1) / (n0_to_m0.Length() * n0_to_n1.Length()) > 1-1e-5 )) { + return false; + } + + if (!(n1_to_m1.SquareLength() < e*e || std::fabs(n1_to_m1 * n0_to_n1) / (n1_to_m1.Length() * n0_to_n1.Length()) > 1-1e-5 )) { + return false; + } + + IfcFloat s0; + IfcFloat s1; + + // pick the axis with the higher absolute difference so the result + // is more accurate. Since we cannot guarantee that the axis with + // the higher absolute difference is big enough as to avoid + // divisions by zero, the case 0/0 ~ infinity is detected and + // handled separately. + if(std::fabs(n0_to_n1.x) > std::fabs(n0_to_n1.y)) { + s0 = n0_to_m0.x / n0_to_n1.x; + s1 = n0_to_m1.x / n0_to_n1.x; + + if (std::fabs(s0) == inf && std::fabs(n0_to_m0.x) < smalle) { + s0 = 0.; + } + if (std::fabs(s1) == inf && std::fabs(n0_to_m1.x) < smalle) { + s1 = 0.; + } + } + else { + s0 = n0_to_m0.y / n0_to_n1.y; + s1 = n0_to_m1.y / n0_to_n1.y; + + if (std::fabs(s0) == inf && std::fabs(n0_to_m0.y) < smalle) { + s0 = 0.; + } + if (std::fabs(s1) == inf && std::fabs(n0_to_m1.y) < smalle) { + s1 = 0.; + } + } + + if (s1 < s0) { + std::swap(s1,s0); + } + + s0 = std::max(0.0,s0); + s1 = std::max(0.0,s1); + + s0 = std::min(1.0,s0); + s1 = std::min(1.0,s1); + + if (std::fabs(s1-s0) < e) { + return false; + } + + out0 = n0 + s0 * n0_to_n1; + out1 = n0 + s1 * n0_to_n1; + + return true; +} + +// ------------------------------------------------------------------------------------------------ +void FindAdjacentContours(ContourVector::iterator current, const ContourVector& contours) +{ + const IfcFloat sqlen_epsilon = static_cast<IfcFloat>(Math::getEpsilon<float>()); + const BoundingBox& bb = (*current).bb; + + // What is to be done here is to populate the skip lists for the contour + // and to add necessary padding points when needed. + SkipList& skiplist = (*current).skiplist; + + // First step to find possible adjacent contours is to check for adjacent bounding + // boxes. If the bounding boxes are not adjacent, the contours lines cannot possibly be. + for (ContourVector::const_iterator it = contours.begin(), end = contours.end(); it != end; ++it) { + if ((*it).IsInvalid()) { + continue; + } + + // this left here to make clear we also run on the current contour + // to check for overlapping contour segments (which can happen due + // to projection artifacts). + //if(it == current) { + // continue; + //} + + const bool is_me = it == current; + + const BoundingBox& ibb = (*it).bb; + + // Assumption: the bounding boxes are pairwise disjoint or identical + ai_assert(is_me || !BoundingBoxesOverlapping(bb, ibb)); + + if (is_me || BoundingBoxesAdjacent(bb, ibb)) { + + // Now do a each-against-everyone check for intersecting contour + // lines. This obviously scales terribly, but in typical real + // world Ifc files it will not matter since most windows that + // are adjacent to each others are rectangular anyway. + + Contour& ncontour = (*current).contour; + const Contour& mcontour = (*it).contour; + + for (size_t n = 0; n < ncontour.size(); ++n) { + const IfcVector2 n0 = ncontour[n]; + const IfcVector2 n1 = ncontour[(n+1) % ncontour.size()]; + + for (size_t m = 0, mend = (is_me ? n : mcontour.size()); m < mend; ++m) { + ai_assert(&mcontour != &ncontour || m < n); + + const IfcVector2 m0 = mcontour[m]; + const IfcVector2 m1 = mcontour[(m+1) % mcontour.size()]; + + IfcVector2 isect0, isect1; + if (IntersectingLineSegments(n0,n1, m0, m1, isect0, isect1)) { + + if ((isect0 - n0).SquareLength() > sqlen_epsilon) { + ++n; + + ncontour.insert(ncontour.begin() + n, isect0); + skiplist.insert(skiplist.begin() + n, true); + } + else { + skiplist[n] = true; + } + + if ((isect1 - n1).SquareLength() > sqlen_epsilon) { + ++n; + + ncontour.insert(ncontour.begin() + n, isect1); + skiplist.insert(skiplist.begin() + n, false); + } + } + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +AI_FORCE_INLINE bool LikelyBorder(const IfcVector2& vdelta) +{ + const IfcFloat dot_point_epsilon = static_cast<IfcFloat>(Math::getEpsilon<float>()); + return std::fabs(vdelta.x * vdelta.y) < dot_point_epsilon; +} + +// ------------------------------------------------------------------------------------------------ +void FindBorderContours(ContourVector::iterator current) +{ + const IfcFloat border_epsilon_upper = static_cast<IfcFloat>(1-1e-4); + const IfcFloat border_epsilon_lower = static_cast<IfcFloat>(1e-4); + + bool outer_border = false; + bool start_on_outer_border = false; + + SkipList& skiplist = (*current).skiplist; + IfcVector2 last_proj_point; + + const Contour::const_iterator cbegin = (*current).contour.begin(), cend = (*current).contour.end(); + + for (Contour::const_iterator cit = cbegin; cit != cend; ++cit) { + const IfcVector2& proj_point = *cit; + + // Check if this connection is along the outer boundary of the projection + // plane. In such a case we better drop it because such 'edges' should + // not have any geometry to close them (think of door openings). + if (proj_point.x <= border_epsilon_lower || proj_point.x >= border_epsilon_upper || + proj_point.y <= border_epsilon_lower || proj_point.y >= border_epsilon_upper) { + + if (outer_border) { + ai_assert(cit != cbegin); + if (LikelyBorder(proj_point - last_proj_point)) { + skiplist[std::distance(cbegin, cit) - 1] = true; + } + } + else if (cit == cbegin) { + start_on_outer_border = true; + } + + outer_border = true; + } + else { + outer_border = false; + } + + last_proj_point = proj_point; + } + + // handle last segment + if (outer_border && start_on_outer_border) { + const IfcVector2& proj_point = *cbegin; + if (LikelyBorder(proj_point - last_proj_point)) { + skiplist[skiplist.size()-1] = true; + } + } +} + +// ------------------------------------------------------------------------------------------------ +AI_FORCE_INLINE bool LikelyDiagonal(IfcVector2 vdelta) +{ + vdelta.x = std::fabs(vdelta.x); + vdelta.y = std::fabs(vdelta.y); + return (std::fabs(vdelta.x-vdelta.y) < 0.8 * std::max(vdelta.x, vdelta.y)); +} + +// ------------------------------------------------------------------------------------------------ +void FindLikelyCrossingLines(ContourVector::iterator current) +{ + SkipList& skiplist = (*current).skiplist; + IfcVector2 last_proj_point; + + const Contour::const_iterator cbegin = (*current).contour.begin(), cend = (*current).contour.end(); + for (Contour::const_iterator cit = cbegin; cit != cend; ++cit) { + const IfcVector2& proj_point = *cit; + + if (cit != cbegin) { + IfcVector2 vdelta = proj_point - last_proj_point; + if (LikelyDiagonal(vdelta)) { + skiplist[std::distance(cbegin, cit) - 1] = true; + } + } + + last_proj_point = proj_point; + } + + // handle last segment + if (LikelyDiagonal(*cbegin - last_proj_point)) { + skiplist[skiplist.size()-1] = true; + } +} + +// ------------------------------------------------------------------------------------------------ +size_t CloseWindows(ContourVector& contours, + const IfcMatrix4& minv, + OpeningRefVector& contours_to_openings, + TempMesh& curmesh) +{ + size_t closed = 0; + // For all contour points, check if one of the assigned openings does + // already have points assigned to it. In this case, assume this is + // the other side of the wall and generate connections between + // the two holes in order to close the window. + + // All this gets complicated by the fact that contours may pertain to + // multiple openings(due to merging of adjacent or overlapping openings). + // The code is based on the assumption that this happens symmetrically + // on both sides of the wall. If it doesn't (which would be a bug anyway) + // wrong geometry may be generated. + for (ContourVector::iterator it = contours.begin(), end = contours.end(); it != end; ++it) { + if ((*it).IsInvalid()) { + continue; + } + OpeningRefs& refs = contours_to_openings[std::distance(contours.begin(), it)]; + + bool has_other_side = false; + for(const TempOpening* opening : refs) { + if(!opening->wallPoints.empty()) { + has_other_side = true; + break; + } + } + + if (has_other_side) { + + ContourRefVector adjacent_contours; + + // prepare a skiplist for this contour. The skiplist is used to + // eliminate unwanted contour lines for adjacent windows and + // those bordering the outer frame. + (*it).PrepareSkiplist(); + + FindAdjacentContours(it, contours); + FindBorderContours(it); + + // if the window is the result of a finite union or intersection of rectangles, + // there shouldn't be any crossing or diagonal lines in it. Such lines would + // be artifacts caused by numerical inaccuracies or other bugs in polyclipper + // and our own code. Since rectangular openings are by far the most frequent + // case, it is worth filtering for this corner case. + if((*it).is_rectangular) { + FindLikelyCrossingLines(it); + } + + ai_assert((*it).skiplist.size() == (*it).contour.size()); + + SkipList::const_iterator skipbegin = (*it).skiplist.begin(); + + curmesh.mVerts.reserve(curmesh.mVerts.size() + (*it).contour.size() * 4); + curmesh.mVertcnt.reserve(curmesh.mVertcnt.size() + (*it).contour.size()); + + bool reverseCountourFaces = false; + + // compare base poly normal and contour normal to detect if we need to reverse the face winding + if(curmesh.mVertcnt.size() > 0) { + IfcVector3 basePolyNormal = TempMesh::ComputePolygonNormal(curmesh.mVerts.data(), curmesh.mVertcnt.front()); + + std::vector<IfcVector3> 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()); + + 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<bool>::const_iterator skipit = skipbegin; + + IfcVector3 start0; + IfcVector3 start1; + + const Contour::const_iterator cbegin = (*it).contour.begin(), cend = (*it).contour.end(); + + bool drop_this_edge = false; + for (Contour::const_iterator cit = cbegin; cit != cend; ++cit, drop_this_edge = *skipit++) { + const IfcVector2& proj_point = *cit; + + // Locate the closest opposite point. This should be a good heuristic to + // connect only the points that are really intended to be connected. + IfcFloat best = static_cast<IfcFloat>(1e10); + IfcVector3 bestv; + + const IfcVector3 world_point = minv * IfcVector3(proj_point.x,proj_point.y,0.0f); + + for(const TempOpening* opening : refs) { + for(const IfcVector3& other : opening->wallPoints) { + const IfcFloat sqdist = (world_point - other).SquareLength(); + + if (sqdist < best) { + // avoid self-connections + if(sqdist < 1e-5) { + continue; + } + + bestv = other; + best = sqdist; + } + } + } + + if (drop_this_edge) { + curmesh.mVerts.pop_back(); + curmesh.mVerts.pop_back(); + } + else { + curmesh.mVerts.push_back(((cit == cbegin) != reverseCountourFaces) ? world_point : bestv); + curmesh.mVerts.push_back(((cit == cbegin) != reverseCountourFaces) ? bestv : world_point); + + curmesh.mVertcnt.push_back(4); + ++closed; + } + + if (cit == cbegin) { + start0 = world_point; + start1 = bestv; + continue; + } + + curmesh.mVerts.push_back(reverseCountourFaces ? bestv : world_point); + curmesh.mVerts.push_back(reverseCountourFaces ? world_point : bestv); + + if (cit == cend - 1) { + drop_this_edge = *skipit; + + // Check if the final connection (last to first element) is itself + // a border edge that needs to be dropped. + if (drop_this_edge) { + --closed; + curmesh.mVertcnt.pop_back(); + curmesh.mVerts.pop_back(); + curmesh.mVerts.pop_back(); + } + else { + curmesh.mVerts.push_back(reverseCountourFaces ? start0 : start1); + curmesh.mVerts.push_back(reverseCountourFaces ? start1 : start0); + } + } + } + } + else { + + const Contour::const_iterator cbegin = (*it).contour.begin(), cend = (*it).contour.end(); + for(TempOpening* opening : refs) { + ai_assert(opening->wallPoints.empty()); + opening->wallPoints.reserve(opening->wallPoints.capacity() + (*it).contour.size()); + for (Contour::const_iterator cit = cbegin; cit != cend; ++cit) { + + const IfcVector2& proj_point = *cit; + opening->wallPoints.push_back(minv * IfcVector3(proj_point.x,proj_point.y,0.0f)); + } + } + } + } + return closed; +} + +// ------------------------------------------------------------------------------------------------ +void Quadrify(const std::vector< BoundingBox >& bbs, TempMesh& curmesh) +{ + ai_assert(curmesh.IsEmpty()); + + std::vector<IfcVector2> quads; + quads.reserve(bbs.size()*4); + + // sort openings by x and y axis as a preliminiary to the QuadrifyPart() algorithm + XYSortedField field; + for (std::vector<BoundingBox>::const_iterator it = bbs.begin(); it != bbs.end(); ++it) { + if (field.find((*it).first) != field.end()) { + IFCImporter::LogWarn("constraint failure during generation of wall openings, results may be faulty"); + } + field[(*it).first] = std::distance(bbs.begin(),it); + } + + QuadrifyPart(IfcVector2(),one_vec,field,bbs,quads); + ai_assert(!(quads.size() % 4)); + + curmesh.mVertcnt.resize(quads.size()/4,4); + curmesh.mVerts.reserve(quads.size()); + for(const IfcVector2& v2 : quads) { + curmesh.mVerts.push_back(IfcVector3(v2.x, v2.y, static_cast<IfcFloat>(0.0))); + } +} + +// ------------------------------------------------------------------------------------------------ +void Quadrify(const ContourVector& contours, TempMesh& curmesh) +{ + std::vector<BoundingBox> bbs; + bbs.reserve(contours.size()); + + for(const ContourVector::value_type& val : contours) { + bbs.push_back(val.bb); + } + + Quadrify(bbs, curmesh); +} + +// ------------------------------------------------------------------------------------------------ +IfcMatrix4 ProjectOntoPlane(std::vector<IfcVector2>& out_contour, const TempMesh& in_mesh, + bool &ok, IfcVector3& nor_out) +{ + const std::vector<IfcVector3>& in_verts = in_mesh.mVerts; + ok = true; + + IfcMatrix4 m = IfcMatrix4(DerivePlaneCoordinateSpace(in_mesh, ok, nor_out)); + if(!ok) { + return IfcMatrix4(); + } +#ifdef ASSIMP_BUILD_DEBUG + const IfcFloat det = m.Determinant(); + ai_assert(std::fabs(det-1) < 1e-5); +#endif + + IfcFloat zcoord = 0; + out_contour.reserve(in_verts.size()); + + + IfcVector3 vmin, vmax; + MinMaxChooser<IfcVector3>()(vmin, vmax); + + // Project all points into the new coordinate system, collect min/max verts on the way + for(const IfcVector3& x : in_verts) { + const IfcVector3 vv = m * x; + // keep Z offset in the plane coordinate system. Ignoring precision issues + // (which are present, of course), this should be the same value for + // all polygon vertices (assuming the polygon is planar). + + // XXX this should be guarded, but we somehow need to pick a suitable + // epsilon + // if(coord != -1.0f) { + // assert(std::fabs(coord - vv.z) < 1e-3f); + // } + zcoord += vv.z; + vmin = std::min(vv, vmin); + vmax = std::max(vv, vmax); + + out_contour.push_back(IfcVector2(vv.x,vv.y)); + } + + zcoord /= in_verts.size(); + + // Further improve the projection by mapping the entire working set into + // [0,1] range. This gives us a consistent data range so all epsilons + // used below can be constants. + vmax -= vmin; + for(IfcVector2& vv : out_contour) { + vv.x = (vv.x - vmin.x) / vmax.x; + vv.y = (vv.y - vmin.y) / vmax.y; + + // sanity rounding + vv = std::max(vv,IfcVector2()); + vv = std::min(vv,one_vec); + } + + IfcMatrix4 mult; + mult.a1 = static_cast<IfcFloat>(1.0) / vmax.x; + mult.b2 = static_cast<IfcFloat>(1.0) / vmax.y; + + mult.a4 = -vmin.x * mult.a1; + mult.b4 = -vmin.y * mult.b2; + mult.c4 = -zcoord; + m = mult * m; + + // debug code to verify correctness +#ifdef ASSIMP_BUILD_DEBUG + std::vector<IfcVector2> out_contour2; + for(const IfcVector3& x : in_verts) { + const IfcVector3& vv = m * x; + + out_contour2.push_back(IfcVector2(vv.x,vv.y)); + ai_assert(std::fabs(vv.z) < vmax.z + 1e-8); + } + + for(size_t i = 0; i < out_contour.size(); ++i) { + ai_assert((out_contour[i] - out_contour2[i]).SquareLength() < ai_epsilon); + } +#endif + + return m; +} + +// ------------------------------------------------------------------------------------------------ +bool GenerateOpenings(std::vector<TempOpening>& openings, + TempMesh& curmesh, + bool check_intersection, + bool generate_connection_geometry, + const IfcVector3& wall_extrusion_axis) +{ + OpeningRefVector contours_to_openings; + + // Try to derive a solid base plane within the current surface for use as + // working coordinate system. Map all vertices onto this plane and + // rescale them to [0,1] range. This normalization means all further + // epsilons need not be scaled. + bool ok = true; + + std::vector<IfcVector2> contour_flat; + + IfcVector3 nor; + const IfcMatrix4 m = ProjectOntoPlane(contour_flat, curmesh, ok, nor); + if(!ok) { + return false; + } + + // Obtain inverse transform for getting back to world space later on + const IfcMatrix4 minv = IfcMatrix4(m).Inverse(); + + // Compute bounding boxes for all 2D openings in projection space + ContourVector contours; + + std::vector<IfcVector2> temp_contour; + std::vector<IfcVector2> temp_contour2; + + IfcVector3 wall_extrusion_axis_norm = wall_extrusion_axis; + wall_extrusion_axis_norm.Normalize(); + + for(TempOpening& opening :openings) { + + // extrusionDir may be 0,0,0 on case where the opening mesh is not an + // IfcExtrudedAreaSolid but something else (i.e. a brep) + IfcVector3 norm_extrusion_dir = opening.extrusionDir; + if (norm_extrusion_dir.SquareLength() > 1e-10) { + norm_extrusion_dir.Normalize(); + } + else { + norm_extrusion_dir = IfcVector3(); + } + + TempMesh* profile_data = opening.profileMesh.get(); + bool is_2d_source = false; + if (opening.profileMesh2D && norm_extrusion_dir.SquareLength() > 0) { + if (std::fabs(norm_extrusion_dir * nor) > 0.9) { + profile_data = opening.profileMesh2D.get(); + is_2d_source = true; + } + } + std::vector<IfcVector3> profile_verts = profile_data->mVerts; + std::vector<unsigned int> profile_vertcnts = profile_data->mVertcnt; + if(profile_verts.size() <= 2) { + continue; + } + + // The opening meshes are real 3D meshes so skip over all faces + // clearly facing into the wrong direction. Also, we need to check + // whether the meshes do actually intersect the base surface plane. + // This is done by recording minimum and maximum values for the + // d component of the plane equation for all polys and checking + // against surface d. + + // Use the sign of the dot product of the face normal to the plane + // normal to determine to which side of the difference mesh a + // triangle belongs. Get independent bounding boxes and vertex + // sets for both sides and take the better one (we can't just + // take both - this would likely cause major screwup of vertex + // winding, producing errors as late as in CloseWindows()). + IfcFloat dmin, dmax; + MinMaxChooser<IfcFloat>()(dmin,dmax); + + temp_contour.clear(); + temp_contour2.clear(); + + IfcVector2 vpmin,vpmax; + MinMaxChooser<IfcVector2>()(vpmin,vpmax); + + IfcVector2 vpmin2,vpmax2; + MinMaxChooser<IfcVector2>()(vpmin2,vpmax2); + + for (size_t f = 0, vi_total = 0, fend = profile_vertcnts.size(); f < fend; ++f) { + + bool side_flag = true; + if (!is_2d_source) { + const IfcVector3 face_nor = ((profile_verts[vi_total+2] - profile_verts[vi_total]) ^ + (profile_verts[vi_total+1] - profile_verts[vi_total])).Normalize(); + + const IfcFloat abs_dot_face_nor = std::abs(nor * face_nor); + if (abs_dot_face_nor < 0.9) { + vi_total += profile_vertcnts[f]; + continue; + } + + side_flag = nor * face_nor > 0; + } + + for (unsigned int vi = 0, vend = profile_vertcnts[f]; vi < vend; ++vi, ++vi_total) { + const IfcVector3& x = profile_verts[vi_total]; + + const IfcVector3 v = m * x; + IfcVector2 vv(v.x, v.y); + + //if(check_intersection) { + dmin = std::min(dmin, v.z); + dmax = std::max(dmax, v.z); + //} + + // sanity rounding + vv = std::max(vv,IfcVector2()); + vv = std::min(vv,one_vec); + + if(side_flag) { + vpmin = std::min(vpmin,vv); + vpmax = std::max(vpmax,vv); + } + else { + vpmin2 = std::min(vpmin2,vv); + vpmax2 = std::max(vpmax2,vv); + } + + std::vector<IfcVector2>& store = side_flag ? temp_contour : temp_contour2; + + if (!IsDuplicateVertex(vv, store)) { + store.push_back(vv); + } + } + } + + if (temp_contour2.size() > 2) { + ai_assert(!is_2d_source); + const IfcVector2 area = vpmax-vpmin; + const IfcVector2 area2 = vpmax2-vpmin2; + if (temp_contour.size() <= 2 || std::fabs(area2.x * area2.y) > std::fabs(area.x * area.y)) { + temp_contour.swap(temp_contour2); + + vpmax = vpmax2; + vpmin = vpmin2; + } + } + if(temp_contour.size() <= 2) { + continue; + } + + // TODO: This epsilon may be too large + const IfcFloat epsilon = std::fabs(dmax-dmin) * 0.0001; + if (!is_2d_source && check_intersection && (0 < dmin-epsilon || 0 > dmax+epsilon)) { + continue; + } + + BoundingBox bb = BoundingBox(vpmin,vpmax); + + // Skip over very small openings - these are likely projection errors + // (i.e. they don't belong to this side of the wall) + if(std::fabs(vpmax.x - vpmin.x) * std::fabs(vpmax.y - vpmin.y) < static_cast<IfcFloat>(1e-10)) { + continue; + } + std::vector<TempOpening*> joined_openings(1, &opening); + + bool is_rectangle = temp_contour.size() == 4; + + // See if this BB intersects or is in close adjacency to any other BB we have so far. + for (ContourVector::iterator it = contours.begin(); it != contours.end(); ) { + const BoundingBox& ibb = (*it).bb; + + if (BoundingBoxesOverlapping(ibb, bb)) { + + if (!(*it).is_rectangular) { + is_rectangle = false; + } + + const std::vector<IfcVector2>& other = (*it).contour; + ClipperLib::ExPolygons poly; + + // First check whether subtracting the old contour (to which ibb belongs) + // from the new contour (to which bb belongs) yields an updated bb which + // no longer overlaps ibb + MakeDisjunctWindowContours(other, temp_contour, poly); + if(poly.size() == 1) { + + const BoundingBox newbb = GetBoundingBox(poly[0].outer); + if (!BoundingBoxesOverlapping(ibb, newbb )) { + // Good guy bounding box + bb = newbb ; + + ExtractVerticesFromClipper(poly[0].outer, temp_contour, false); + continue; + } + } + + // Take these two overlapping contours and try to merge them. If they + // overlap (which should not happen, but in fact happens-in-the-real- + // world [tm] ), resume using a single contour and a single bounding box. + MergeWindowContours(temp_contour, other, poly); + + if (poly.size() > 1) { + return TryAddOpenings_Poly2Tri(openings, curmesh); + } + else if (poly.size() == 0) { + IFCImporter::LogWarn("ignoring duplicate opening"); + temp_contour.clear(); + break; + } + else { + IFCImporter::LogVerboseDebug("merging overlapping openings"); + ExtractVerticesFromClipper(poly[0].outer, temp_contour, false); + + // Generate the union of the bounding boxes + bb.first = std::min(bb.first, ibb.first); + bb.second = std::max(bb.second, ibb.second); + + // Update contour-to-opening tables accordingly + if (generate_connection_geometry) { + std::vector<TempOpening*>& t = contours_to_openings[std::distance(contours.begin(),it)]; + joined_openings.insert(joined_openings.end(), t.begin(), t.end()); + + contours_to_openings.erase(contours_to_openings.begin() + std::distance(contours.begin(),it)); + } + + contours.erase(it); + + // Restart from scratch because the newly formed BB might now + // overlap any other BB which its constituent BBs didn't + // previously overlap. + it = contours.begin(); + continue; + } + } + ++it; + } + + if(!temp_contour.empty()) { + if (generate_connection_geometry) { + contours_to_openings.push_back(std::vector<TempOpening*>( + joined_openings.begin(), + joined_openings.end())); + } + + contours.push_back(ProjectedWindowContour(temp_contour, bb, is_rectangle)); + } + } + + // Check if we still have any openings left - it may well be that this is + // not the cause, for example if all the opening candidates don't intersect + // this surface or point into a direction perpendicular to it. + if (contours.empty()) { + return false; + } + + curmesh.Clear(); + + // Generate a base subdivision into quads to accommodate the given list + // of window bounding boxes. + Quadrify(contours,curmesh); + + // Run a sanity cleanup pass on the window contours to avoid generating + // artifacts during the contour generation phase later on. + CleanupWindowContours(contours); + + // Previously we reduced all windows to rectangular AABBs in projection + // space, now it is time to fill the gaps between the BBs and the real + // window openings. + InsertWindowContours(contours,openings, curmesh); + + // Clip the entire outer contour of our current result against the real + // outer contour of the surface. This is necessary because the result + // of the Quadrify() algorithm is always a square area spanning + // over [0,1]^2 (i.e. entire projection space). + CleanupOuterContour(contour_flat, curmesh); + + // Undo the projection and get back to world (or local object) space + for(IfcVector3& v3 : curmesh.mVerts) { + v3 = minv * v3; + } + + // Generate window caps to connect the symmetric openings on both sides + // of the wall. + if (generate_connection_geometry) { + CloseWindows(contours, minv, contours_to_openings, curmesh); + } + return true; +} + +std::vector<IfcVector2> GetContourInPlane2D(std::shared_ptr<TempMesh> mesh,IfcMatrix3 planeSpace, + IfcVector3 planeNor,IfcFloat planeOffset, + IfcVector3 extrusionDir,IfcVector3& wall_extrusion,bool& first,bool& ok) { + std::vector<IfcVector2> contour; + + const auto outernor = ((mesh->mVerts[2] - mesh->mVerts[0]) ^ (mesh->mVerts[1] - mesh->mVerts[0])).Normalize(); + const IfcFloat dot = planeNor * outernor; + if (std::fabs(dot) < 1.f - ai_epsilon) { + std::stringstream msg; + msg << "Skipping: Unaligned opening (" << planeNor.x << ", " << planeNor.y << ", " << planeNor.z << ")"; + msg << " . ( " << outernor.x << ", " << outernor.y << ", " << outernor.z << ") = " << dot; + IFCImporter::LogDebug(msg.str().c_str()); + ok = false; + return contour; + } + + const std::vector<IfcVector3>& va = mesh->mVerts; + if(va.size() <= 2) { + std::stringstream msg; + msg << "Skipping: Only " << va.size() << " verticies in opening mesh."; + IFCImporter::LogDebug(msg.str().c_str()); + ok = false; + return contour; + } + + for(const IfcVector3& xx : mesh->mVerts) { + IfcVector3 vv = planeSpace * xx,vv_extr = planeSpace * (xx + extrusionDir); + + const bool is_extruded_side = std::fabs(vv.z - planeOffset) > std::fabs(vv_extr.z - planeOffset); + if(first) { + first = false; + if(dot > 0.f) { + wall_extrusion = extrusionDir; + if(is_extruded_side) { + wall_extrusion = -wall_extrusion; + } + } + } + + // XXX should not be necessary - but it is. Why? For precision reasons? + vv = is_extruded_side ? vv_extr : vv; + contour.push_back(IfcVector2(vv.x,vv.y)); + } + ok = true; + + return contour; +} + +const float close{ ai_epsilon }; + +static bool isClose(IfcVector2 first,IfcVector2 second) { + auto diff = (second - first); + return (std::fabs(diff.x) < close && std::fabs(diff.y) < close); +} + +static void logSegment(std::pair<IfcVector2,IfcVector2> segment) { + std::stringstream msg2; + msg2 << " Segment: \n"; + msg2 << " " << segment.first.x << " " << segment.first.y << " \n"; + msg2 << " " << segment.second.x << " " << segment.second.y << " \n"; + IFCImporter::LogInfo(msg2.str().c_str()); +} + +std::vector<std::vector<IfcVector2>> GetContoursInPlane3D(std::shared_ptr<TempMesh> mesh,IfcMatrix3 planeSpace, + IfcFloat planeOffset) { + + { + std::stringstream msg; + msg << "GetContoursInPlane3D: planeSpace is \n"; + msg << planeSpace.a1 << " " << planeSpace.a2 << " " << planeSpace.a3 << " " << "\n"; + msg << planeSpace.b1 << " " << planeSpace.b2 << " " << planeSpace.b3 << " " << "\n"; + msg << planeSpace.c1 << " " << planeSpace.c2 << " " << planeSpace.c3 << " " << "\n"; + msg << "\n planeOffset is " << planeOffset; + IFCImporter::LogInfo(msg.str().c_str()); + } + + // we'll put our line segments in here, and then merge them together into contours later + std::deque<std::pair<IfcVector2,IfcVector2>> lineSegments; + + // find the lines giving the intersection of the faces with the plane - we'll work in planeSpace throughout. + size_t vI0{ 0 }; // vertex index for first vertex in plane + for(auto nVertices : mesh->mVertcnt) { // iterate over faces + { + std::stringstream msg; + msg << "GetContoursInPlane3D: face (transformed) is \n"; + for(auto vI = vI0; vI < vI0 + nVertices; vI++) { + auto v = planeSpace * mesh->mVerts[vI]; + msg << " " << v.x << " " << v.y << " " << v.z << " " << "\n"; + } + IFCImporter::LogInfo(msg.str().c_str()); + } + + if(nVertices <= 2) // not a plane, a point or line + { + std::stringstream msg; + msg << "GetContoursInPlane3D: found point or line when expecting plane (only " << nVertices << " vertices)"; + IFCImporter::LogWarn(msg.str().c_str()); + vI0 += nVertices; + continue; + } + + auto v0 = planeSpace * mesh->mVerts[vI0]; + + // now calculate intersections between face and plane + IfcVector2 firstPoint; + bool gotFirstPoint(false); + + if(std::fabs(v0.z - planeOffset) < close) { + // first point is on the plane + firstPoint.x = v0.x; + firstPoint.y = v0.y; + gotFirstPoint = true; + } + + auto vn = v0; + for(auto vI = vI0 + 1; vI < vI0 + nVertices; vI++) { + auto vp = vn; + vn = planeSpace * mesh->mVerts[vI]; + IfcVector3 intersection; + + if(std::fabs(vn.z - planeOffset) < close) { + // on the plane + intersection = vn; + } + else if((vn.z > planeOffset) != (vp.z > planeOffset)) + { + // passes through the plane + auto vdir = vn - vp; + auto scale = (planeOffset - vp.z) / vdir.z; + intersection = vp + scale * vdir; + } + else { + // nowhere near - move on + continue; + } + + if(!gotFirstPoint) { + if(std::fabs(vp.z - planeOffset) < close) { + // just had a second line along the plane + firstPoint.x = vp.x; + firstPoint.y = vp.y; + IfcVector2 secondPoint(intersection.x,intersection.y); + auto s = std::pair<IfcVector2,IfcVector2>(firstPoint,secondPoint); + logSegment(s); + lineSegments.push_back(s); + // next firstpoint should be this one + } + else { + // store the first intersection point + firstPoint.x = intersection.x; + firstPoint.y = intersection.y; + gotFirstPoint = true; + } + } + else { + // now got the second point, so store the pair + IfcVector2 secondPoint(intersection.x,intersection.y); + auto s = std::pair<IfcVector2,IfcVector2>(firstPoint,secondPoint); + logSegment(s); + lineSegments.push_back(s); + + // - note that we don't move onto the next face as a non-convex face can create two or more intersections with a plane + gotFirstPoint = false; + } + } + if(gotFirstPoint) { + IFCImporter::LogWarn("GetContoursInPlane3D: odd number of intersections with plane"); + } + vI0 += nVertices; + } + + { + std::stringstream msg; + msg << "GetContoursInPlane3D: found " << lineSegments.size() << " line segments:\n"; + IFCImporter::LogInfo(msg.str().c_str()); + + for(auto& s : lineSegments) { + logSegment(s); + } + + } + + // now merge contours until we have the best-looking polygons we can + std::vector<Contour> contours; + while(!lineSegments.empty()) { + // start with a polygon and make the best closed contour we can + const auto& firstSeg = lineSegments.front(); + std::deque<IfcVector2> contour{ firstSeg.first, firstSeg.second }; + lineSegments.pop_front(); + bool foundNextPoint{ true }; + bool closedContour{ false }; + while(foundNextPoint) { + foundNextPoint = false; + for(auto nextSeg = lineSegments.begin(); nextSeg != lineSegments.end(); nextSeg++) { + // see if we can match up both ends - in which case we've closed the contour + if((isClose(contour.front(),nextSeg->first) && isClose(contour.back(),nextSeg->second)) || + (isClose(contour.back(),nextSeg->first) && isClose(contour.front(),nextSeg->second)) + ) { + lineSegments.erase(nextSeg); + closedContour = true; + break; + } + + // otherwise, see if we can match up either end + foundNextPoint = true; + if(isClose(contour.front(),nextSeg->first)) { + contour.push_front(nextSeg->second); + } + else if(isClose(contour.front(),nextSeg->second)) { + contour.push_front(nextSeg->first); + } + else if(isClose(contour.back(),nextSeg->first)) { + contour.push_back(nextSeg->second); + } + else if(isClose(contour.back(),nextSeg->second)) { + contour.push_back(nextSeg->first); + } + else { + foundNextPoint = false; + } + if(foundNextPoint) { + lineSegments.erase(nextSeg); + break; + } + } + } + + if(!closedContour) { + IFCImporter::LogWarn("GetContoursInPlane3D: did not close contour"); + } + + // now add the contour if we can + if(contour.size() <= 2) { + IFCImporter::LogWarn("GetContoursInPlane3D: discarding line/point contour"); + continue; + } + Contour c{}; + for(auto p : contour) + { + c.push_back(p); + } + contours.push_back(c); + } + + { + std::stringstream msg; + msg << "GetContoursInPlane3D: found " << contours.size() << " contours:\n"; + + for(auto c : contours) { + msg << " Contour: \n"; + for(auto p : c) { + msg << " " << p.x << " " << p.y << " \n"; + } + } + + IFCImporter::LogInfo(msg.str().c_str()); + } + + + return contours; +} + +std::vector<std::vector<IfcVector2>> GetContoursInPlane(std::shared_ptr<TempMesh> mesh,IfcMatrix3 planeSpace, + IfcVector3 planeNor,IfcFloat planeOffset, + IfcVector3 extrusionDir,IfcVector3& wall_extrusion,bool& first) { + + if(mesh->mVertcnt.size() == 1) + { + bool ok; + auto contour = GetContourInPlane2D(mesh,planeSpace,planeNor,planeOffset,extrusionDir,wall_extrusion,first,ok); + if(ok) + return std::vector<std::vector<IfcVector2>> {contour}; + else + return std::vector<std::vector<IfcVector2>> {}; + } + else + { + return GetContoursInPlane3D(mesh,planeSpace,planeOffset); + } +} + +// ------------------------------------------------------------------------------------------------ +bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings, + TempMesh& curmesh) +{ + IFCImporter::LogWarn("forced to use poly2tri fallback method to generate wall openings"); + std::vector<IfcVector3>& out = curmesh.mVerts; + + bool result = false; + + // Try to derive a solid base plane within the current surface for use as + // working coordinate system. + bool ok; + IfcVector3 nor; + const IfcMatrix3 m = DerivePlaneCoordinateSpace(curmesh, ok, nor); + if (!ok) { + return false; + } + + const IfcMatrix3 minv = IfcMatrix3(m).Inverse(); + + + IfcFloat coord = -1; + + std::vector<IfcVector2> contour_flat; + contour_flat.reserve(out.size()); + + IfcVector2 vmin, vmax; + MinMaxChooser<IfcVector2>()(vmin, vmax); + + // Move all points into the new coordinate system, collecting min/max verts on the way + for(IfcVector3& x : out) { + const IfcVector3 vv = m * x; + + // keep Z offset in the plane coordinate system. Ignoring precision issues + // (which are present, of course), this should be the same value for + // all polygon vertices (assuming the polygon is planar). + + + // XXX this should be guarded, but we somehow need to pick a suitable + // epsilon + // if(coord != -1.0f) { + // assert(std::fabs(coord - vv.z) < 1e-3f); + // } + + coord = vv.z; + + vmin = std::min(IfcVector2(vv.x, vv.y), vmin); + vmax = std::max(IfcVector2(vv.x, vv.y), vmax); + + contour_flat.push_back(IfcVector2(vv.x,vv.y)); + } + + // With the current code in DerivePlaneCoordinateSpace, + // vmin,vmax should always be the 0...1 rectangle (+- numeric inaccuracies) + // but here we won't rely on this. + + vmax -= vmin; + + // If this happens then the projection must have been wrong. + ai_assert(vmax.Length()); + + ClipperLib::ExPolygons clipped; + ClipperLib::Polygons holes_union; + + + IfcVector3 wall_extrusion; + bool first = true; + + try { + + ClipperLib::Clipper clipper_holes; + + for(const TempOpening& t : openings) { + auto contours = GetContoursInPlane(t.profileMesh,m,nor,coord,t.extrusionDir,wall_extrusion,first); + + for(auto& contour : contours) { + // scale to clipping space + ClipperLib::Polygon hole; + for(IfcVector2& pip : contour) { + pip.x = (pip.x - vmin.x) / vmax.x; + pip.y = (pip.y - vmin.y) / vmax.y; + + hole.push_back(ClipperLib::IntPoint(to_int64(pip.x),to_int64(pip.y))); + } + + if(!ClipperLib::Orientation(hole)) { + std::reverse(hole.begin(),hole.end()); + // assert(ClipperLib::Orientation(hole)); + } + + /*ClipperLib::Polygons pol_temp(1), pol_temp2(1); + pol_temp[0] = hole; + + ClipperLib::OffsetPolygons(pol_temp,pol_temp2,5.0); + hole = pol_temp2[0];*/ + + clipper_holes.AddPolygon(hole,ClipperLib::ptSubject); + { + std::stringstream msg; + msg << "- added polygon "; + for(auto elem : hole) { + msg << " (" << elem.X << ", " << elem.Y << ")"; + } + IFCImporter::LogDebug(msg.str().c_str()); + } + } + } + + clipper_holes.Execute(ClipperLib::ctUnion,holes_union, + ClipperLib::pftNonZero, + ClipperLib::pftNonZero); + + if (holes_union.empty()) { + return false; + } + + // Now that we have the big union of all holes, subtract it from the outer contour + // to obtain the final polygon to feed into the triangulator. + { + ClipperLib::Polygon poly; + for(IfcVector2& pip : contour_flat) { + pip.x = (pip.x - vmin.x) / vmax.x; + pip.y = (pip.y - vmin.y) / vmax.y; + + poly.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } + + if (ClipperLib::Orientation(poly)) { + std::reverse(poly.begin(), poly.end()); + } + clipper_holes.Clear(); + clipper_holes.AddPolygon(poly,ClipperLib::ptSubject); + + clipper_holes.AddPolygons(holes_union,ClipperLib::ptClip); + clipper_holes.Execute(ClipperLib::ctDifference,clipped, + ClipperLib::pftNonZero, + ClipperLib::pftNonZero); + } + + } + catch (const char* sx) { + IFCImporter::LogError("Ifc: error during polygon clipping, skipping openings for this face: (Clipper: " + + std::string(sx) + ")"); + + return false; + } + + std::vector<IfcVector3> old_verts; + std::vector<unsigned int> old_vertcnt; + + old_verts.swap(curmesh.mVerts); + old_vertcnt.swap(curmesh.mVertcnt); + + std::vector< std::vector<p2t::Point*> > contours; + for(ClipperLib::ExPolygon& clip : clipped) { + + contours.clear(); + + // Build the outer polygon contour line for feeding into poly2tri + std::vector<p2t::Point*> contour_points; + for(ClipperLib::IntPoint& point : clip.outer) { + contour_points.push_back( new p2t::Point(from_int64(point.X), from_int64(point.Y)) ); + } + + p2t::CDT* cdt ; + try { + // Note: this relies on custom modifications in poly2tri to raise runtime_error's + // instead if assertions. These failures are not debug only, they can actually + // happen in production use if the input data is broken. An assertion would be + // inappropriate. + cdt = new p2t::CDT(contour_points); + } + catch(const std::exception& e) { + IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: " + + std::string(e.what()) + ")"); + continue; + } + + + // Build the poly2tri inner contours for all holes we got from ClipperLib + for(ClipperLib::Polygon& opening : clip.holes) { + + contours.push_back(std::vector<p2t::Point*>()); + std::vector<p2t::Point*>& contour = contours.back(); + + for(ClipperLib::IntPoint& point : opening) { + contour.push_back( new p2t::Point(from_int64(point.X), from_int64(point.Y)) ); + } + + cdt->AddHole(contour); + } + + try { + // Note: See above + cdt->Triangulate(); + } + catch(const std::exception& e) { + IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: " + + std::string(e.what()) + ")"); + continue; + } + + const std::vector<p2t::Triangle*> tris = cdt->GetTriangles(); + + // Collect the triangles we just produced + for(p2t::Triangle* tri : tris) { + for(int i = 0; i < 3; ++i) { + + const IfcVector2 v = IfcVector2( + static_cast<IfcFloat>( tri->GetPoint(i)->x ), + static_cast<IfcFloat>( tri->GetPoint(i)->y ) + ); + + ai_assert(v.x <= 1.0 && v.x >= 0.0 && v.y <= 1.0 && v.y >= 0.0); + const IfcVector3 v3 = minv * IfcVector3(vmin.x + v.x * vmax.x, vmin.y + v.y * vmax.y,coord) ; + + curmesh.mVerts.push_back(v3); + } + curmesh.mVertcnt.push_back(3); + } + + result = true; + } + + if (!result) { + // revert -- it's a shame, but better than nothing + curmesh.mVerts.insert(curmesh.mVerts.end(),old_verts.begin(), old_verts.end()); + curmesh.mVertcnt.insert(curmesh.mVertcnt.end(),old_vertcnt.begin(), old_vertcnt.end()); + + IFCImporter::LogError("Ifc: revert, could not generate openings for this wall"); + } + + return result; +} + + + } // ! IFC +} // ! Assimp + +#undef to_int64 +#undef from_int64 +#undef one_vec + +#endif diff --git a/libs/assimp/code/AssetLib/IFC/IFCProfile.cpp b/libs/assimp/code/AssetLib/IFC/IFCProfile.cpp new file mode 100644 index 0000000..aa31124 --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCProfile.cpp @@ -0,0 +1,190 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IFCProfile.cpp + * @brief Read profile and curves entities from IFC files + */ + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER + +#include "IFCUtil.h" + +namespace Assimp { +namespace IFC { + +// ------------------------------------------------------------------------------------------------ +void ProcessPolyLine(const Schema_2x3::IfcPolyline& def, TempMesh& meshout, ConversionData& /*conv*/) +{ + // this won't produce a valid mesh, it just spits out a list of vertices + IfcVector3 t; + for(const Schema_2x3::IfcCartesianPoint& cp : def.Points) { + ConvertCartesianPoint(t,cp); + meshout.mVerts.push_back(t); + } + meshout.mVertcnt.push_back(static_cast<unsigned int>(meshout.mVerts.size())); +} + +// ------------------------------------------------------------------------------------------------ +bool ProcessCurve(const Schema_2x3::IfcCurve& curve, TempMesh& meshout, ConversionData& conv) +{ + std::unique_ptr<const Curve> cv(Curve::Convert(curve,conv)); + if (!cv) { + IFCImporter::LogWarn("skipping unknown IfcCurve entity, type is ", curve.GetClassName()); + return false; + } + + // we must have a bounded curve at this point + if (const BoundedCurve* bc = dynamic_cast<const BoundedCurve*>(cv.get())) { + try { + bc->SampleDiscrete(meshout); + } + catch(const CurveError& cv) { + IFCImporter::LogError(cv.mStr, " (error occurred while processing curve)"); + return false; + } + meshout.mVertcnt.push_back(static_cast<unsigned int>(meshout.mVerts.size())); + return true; + } + + IFCImporter::LogError("cannot use unbounded curve as profile"); + return false; +} + +// ------------------------------------------------------------------------------------------------ +void ProcessClosedProfile(const Schema_2x3::IfcArbitraryClosedProfileDef& def, TempMesh& meshout, ConversionData& conv) +{ + ProcessCurve(def.OuterCurve,meshout,conv); +} + +// ------------------------------------------------------------------------------------------------ +void ProcessOpenProfile(const Schema_2x3::IfcArbitraryOpenProfileDef& def, TempMesh& meshout, ConversionData& conv) +{ + ProcessCurve(def.Curve,meshout,conv); +} + +// ------------------------------------------------------------------------------------------------ +void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& def, TempMesh& meshout, ConversionData& conv) +{ + if(const Schema_2x3::IfcRectangleProfileDef* const cprofile = def.ToPtr<Schema_2x3::IfcRectangleProfileDef>()) { + const IfcFloat x = cprofile->XDim*0.5f, y = cprofile->YDim*0.5f; + + meshout.mVerts.reserve(meshout.mVerts.size()+4); + meshout.mVerts.push_back( IfcVector3( x, y, 0.f )); + meshout.mVerts.push_back( IfcVector3(-x, y, 0.f )); + meshout.mVerts.push_back( IfcVector3(-x,-y, 0.f )); + meshout.mVerts.push_back( IfcVector3( x,-y, 0.f )); + meshout.mVertcnt.push_back(4); + } + else if( const Schema_2x3::IfcCircleProfileDef* const circle = def.ToPtr<Schema_2x3::IfcCircleProfileDef>()) { + if(def.ToPtr<Schema_2x3::IfcCircleHollowProfileDef>()) { + // TODO + } + const size_t segments = conv.settings.cylindricalTessellation; + const IfcFloat delta = AI_MATH_TWO_PI_F/segments, radius = circle->Radius; + + meshout.mVerts.reserve(segments); + + IfcFloat angle = 0.f; + for(size_t i = 0; i < segments; ++i, angle += delta) { + meshout.mVerts.push_back( IfcVector3( std::cos(angle)*radius, std::sin(angle)*radius, 0.f )); + } + + meshout.mVertcnt.push_back(static_cast<unsigned int>(segments)); + } + else if( const Schema_2x3::IfcIShapeProfileDef* const ishape = def.ToPtr<Schema_2x3::IfcIShapeProfileDef>()) { + // construct simplified IBeam shape + const IfcFloat offset = (ishape->OverallWidth - ishape->WebThickness) / 2; + const IfcFloat inner_height = ishape->OverallDepth - ishape->FlangeThickness * 2; + + meshout.mVerts.reserve(12); + meshout.mVerts.push_back(IfcVector3(0,0,0)); + meshout.mVerts.push_back(IfcVector3(0,ishape->FlangeThickness,0)); + meshout.mVerts.push_back(IfcVector3(offset,ishape->FlangeThickness,0)); + meshout.mVerts.push_back(IfcVector3(offset,ishape->FlangeThickness + inner_height,0)); + meshout.mVerts.push_back(IfcVector3(0,ishape->FlangeThickness + inner_height,0)); + meshout.mVerts.push_back(IfcVector3(0,ishape->OverallDepth,0)); + meshout.mVerts.push_back(IfcVector3(ishape->OverallWidth,ishape->OverallDepth,0)); + meshout.mVerts.push_back(IfcVector3(ishape->OverallWidth,ishape->FlangeThickness + inner_height,0)); + meshout.mVerts.push_back(IfcVector3(offset+ishape->WebThickness,ishape->FlangeThickness + inner_height,0)); + meshout.mVerts.push_back(IfcVector3(offset+ishape->WebThickness,ishape->FlangeThickness,0)); + meshout.mVerts.push_back(IfcVector3(ishape->OverallWidth,ishape->FlangeThickness,0)); + meshout.mVerts.push_back(IfcVector3(ishape->OverallWidth,0,0)); + + meshout.mVertcnt.push_back(12); + } + else { + IFCImporter::LogWarn("skipping unknown IfcParameterizedProfileDef entity, type is ", def.GetClassName()); + return; + } + + IfcMatrix4 trafo; + ConvertAxisPlacement(trafo, *def.Position); + meshout.Transform(trafo); +} + +// ------------------------------------------------------------------------------------------------ +bool ProcessProfile(const Schema_2x3::IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv) +{ + if(const Schema_2x3::IfcArbitraryClosedProfileDef* const cprofile = prof.ToPtr<Schema_2x3::IfcArbitraryClosedProfileDef>()) { + ProcessClosedProfile(*cprofile,meshout,conv); + } + else if(const Schema_2x3::IfcArbitraryOpenProfileDef* const copen = prof.ToPtr<Schema_2x3::IfcArbitraryOpenProfileDef>()) { + ProcessOpenProfile(*copen,meshout,conv); + } + else if(const Schema_2x3::IfcParameterizedProfileDef* const cparam = prof.ToPtr<Schema_2x3::IfcParameterizedProfileDef>()) { + ProcessParametrizedProfile(*cparam,meshout,conv); + } + else { + IFCImporter::LogWarn("skipping unknown IfcProfileDef entity, type is ", prof.GetClassName()); + return false; + } + meshout.RemoveAdjacentDuplicates(); + if (!meshout.mVertcnt.size() || meshout.mVertcnt.front() <= 1) { + return false; + } + return true; +} + +} // ! IFC +} // ! Assimp + +#endif // ASSIMP_BUILD_NO_IFC_IMPORTER diff --git a/libs/assimp/code/AssetLib/IFC/IFCReaderGen1_2x3.cpp b/libs/assimp/code/AssetLib/IFC/IFCReaderGen1_2x3.cpp new file mode 100644 index 0000000..a6f7ae3 --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCReaderGen1_2x3.cpp @@ -0,0 +1,3177 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, ASSIMP Development 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 Development 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. + +---------------------------------------------------------------------- +*/ + +/** MACHINE-GENERATED by scripts/ICFImporter/CppGenerator.py */ + +//#include "AssimpPCH.h" +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER + +#include "IFCReaderGen_2x3.h" + +#if _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4702) +#endif // _MSC_VER + +namespace Assimp { + +using namespace ::Assimp::IFC; +using namespace ::Assimp::IFC::Schema_2x3; + +namespace { + typedef EXPRESS::ConversionSchema::SchemaEntry SchemaEntry; + + static const SchemaEntry schema_raw_2x3[] = { + SchemaEntry("ifcstairtypeenum",nullptr ) +, SchemaEntry("ifcspacetypeenum",nullptr ) +, SchemaEntry("ifcwalltypeenum",nullptr ) +, SchemaEntry("ifcmonthinyearnumber",nullptr ) +, SchemaEntry("ifcheatfluxdensitymeasure",nullptr ) +, SchemaEntry("ifckinematicviscositymeasure",nullptr ) +, SchemaEntry("ifcsequenceenum",nullptr ) +, SchemaEntry("ifcairtoairheatrecoverytypeenum",nullptr ) +, SchemaEntry("ifcactorselect",nullptr ) +, SchemaEntry("ifctransformertypeenum",nullptr ) +, SchemaEntry("ifcunitaryequipmenttypeenum",nullptr ) +, SchemaEntry("ifcelectricflowstoragedevicetypeenum",nullptr ) +, SchemaEntry("ifcenergysequenceenum",nullptr ) +, SchemaEntry("ifcworkcontroltypeenum",nullptr ) +, SchemaEntry("ifccurvaturemeasure",nullptr ) +, SchemaEntry("ifcparametervalue",nullptr ) +, SchemaEntry("ifcappliedvalueselect",nullptr ) +, SchemaEntry("ifcwarpingconstantmeasure",nullptr ) +, SchemaEntry("ifcarithmeticoperatorenum",nullptr ) +, SchemaEntry("ifclinearforcemeasure",nullptr ) +, SchemaEntry("ifcwindowpanelpositionenum",nullptr ) +, SchemaEntry("ifcflowmetertypeenum",nullptr ) +, SchemaEntry("ifcrampflighttypeenum",nullptr ) +, SchemaEntry("ifcspecularhighlightselect",nullptr ) +, SchemaEntry("ifcactiontypeenum",nullptr ) +, SchemaEntry("ifcgeometricprojectionenum",nullptr ) +, SchemaEntry("ifctimeseriesdatatypeenum",nullptr ) +, SchemaEntry("ifcmagneticfluxmeasure",nullptr ) +, SchemaEntry("ifcobjecttypeenum",nullptr ) +, SchemaEntry("ifcdataoriginenum",nullptr ) +, SchemaEntry("ifcmassdensitymeasure",nullptr ) +, SchemaEntry("ifclightfixturetypeenum",nullptr ) +, SchemaEntry("ifcservicelifetypeenum",nullptr ) +, SchemaEntry("ifcelectricvoltagemeasure",nullptr ) +, SchemaEntry("ifcheatingvaluemeasure",nullptr ) +, SchemaEntry("ifcpresentabletext",nullptr ) +, SchemaEntry("ifcaheadorbehind",nullptr ) +, SchemaEntry("ifcsimplevalue",nullptr ) +, SchemaEntry("ifcsensortypeenum",nullptr ) +, SchemaEntry("ifcderivedunitenum",nullptr ) +, SchemaEntry("ifcsizeselect",nullptr ) +, SchemaEntry("ifctransportelementtypeenum",nullptr ) +, SchemaEntry("ifcinventorytypeenum",nullptr ) +, SchemaEntry("ifctextdecoration",nullptr ) +, SchemaEntry("ifcdirectionsenseenum",nullptr ) +, SchemaEntry("ifcductfittingtypeenum",nullptr ) +, SchemaEntry("ifcdocumentstatusenum",nullptr ) +, SchemaEntry("ifcslabtypeenum",nullptr ) +, SchemaEntry("ifcdoorstyleconstructionenum",nullptr ) +, SchemaEntry("ifcvolumemeasure",nullptr ) +, SchemaEntry("ifcinductancemeasure",nullptr ) +, SchemaEntry("ifccurtainwalltypeenum",nullptr ) +, SchemaEntry("ifcsiunitname",nullptr ) +, SchemaEntry("ifcspecularexponent",nullptr ) +, SchemaEntry("ifcsoundpressuremeasure",nullptr ) +, SchemaEntry("ifcanalysistheorytypeenum",nullptr ) +, SchemaEntry("ifcgasterminaltypeenum",nullptr ) +, SchemaEntry("ifcyearnumber",nullptr ) +, SchemaEntry("ifcmodulusofelasticitymeasure",nullptr ) +, SchemaEntry("ifcchangeactionenum",nullptr ) +, SchemaEntry("ifcdampertypeenum",nullptr ) +, SchemaEntry("ifcevaporatortypeenum",nullptr ) +, SchemaEntry("ifcionconcentrationmeasure",nullptr ) +, SchemaEntry("ifcductsegmenttypeenum",nullptr ) +, SchemaEntry("ifcprotectivedevicetypeenum",nullptr ) +, SchemaEntry("ifcabsorbeddosemeasure",nullptr ) +, SchemaEntry("ifcmassperlengthmeasure",nullptr ) +, SchemaEntry("ifctextfontname",nullptr ) +, SchemaEntry("ifcorientationselect",nullptr ) +, SchemaEntry("ifcilluminancemeasure",nullptr ) +, SchemaEntry("ifcfiresuppressionterminaltypeenum",nullptr ) +, SchemaEntry("ifcfontstyle",nullptr ) +, SchemaEntry("ifcmomentofinertiameasure",nullptr ) +, SchemaEntry("ifcmodulusofsubgradereactionmeasure",nullptr ) +, SchemaEntry("ifccomplexnumber",nullptr ) +, SchemaEntry("ifchumidifiertypeenum",nullptr ) +, SchemaEntry("ifcpresentationstyleselect",nullptr ) +, SchemaEntry("ifcthermaltransmittancemeasure",nullptr ) +, SchemaEntry("ifcribplatedirectionenum",nullptr ) +, SchemaEntry("ifcclassificationnotationselect",nullptr ) +, SchemaEntry("ifcminuteinhour",nullptr ) +, SchemaEntry("ifcinternalorexternalenum",nullptr ) +, SchemaEntry("ifcrotationalfrequencymeasure",nullptr ) +, SchemaEntry("ifcsanitaryterminaltypeenum",nullptr ) +, SchemaEntry("ifcsymbolstyleselect",nullptr ) +, SchemaEntry("ifcelementcompositionenum",nullptr ) +, SchemaEntry("ifctextpath",nullptr ) +, SchemaEntry("ifcpowermeasure",nullptr ) +, SchemaEntry("ifcsurfacestyleelementselect",nullptr ) +, SchemaEntry("ifcresourceconsumptionenum",nullptr ) +, SchemaEntry("ifcelectriccapacitancemeasure",nullptr ) +, SchemaEntry("ifclayersetdirectionenum",nullptr ) +, SchemaEntry("ifcrailingtypeenum",nullptr ) +, SchemaEntry("ifcobjectiveenum",nullptr ) +, SchemaEntry("ifcdocumentselect",nullptr ) +, SchemaEntry("ifcmodulusoflinearsubgradereactionmeasure",nullptr ) +, SchemaEntry("ifcthermaladmittancemeasure",nullptr ) +, SchemaEntry("ifctransitioncode",nullptr ) +, SchemaEntry("ifcconnectiontypeenum",nullptr ) +, SchemaEntry("ifcmonetarymeasure",nullptr ) +, SchemaEntry("ifcstackterminaltypeenum",nullptr ) +, SchemaEntry("ifccolour",nullptr ) +, SchemaEntry("ifctext",nullptr ) +, SchemaEntry("ifccontextdependentmeasure",nullptr ) +, SchemaEntry("ifcthermalconductivitymeasure",nullptr ) +, SchemaEntry("ifcprojectedortruelengthenum",nullptr ) +, SchemaEntry("ifcpressuremeasure",nullptr ) +, SchemaEntry("ifcmoisturediffusivitymeasure",nullptr ) +, SchemaEntry("ifcbooleanoperator",nullptr ) +, SchemaEntry("ifcpropertysourceenum",nullptr ) +, SchemaEntry("ifctimestamp",nullptr ) +, SchemaEntry("ifcmaterialselect",nullptr ) +, SchemaEntry("ifcgloballyuniqueid",nullptr ) +, SchemaEntry("ifcreflectancemethodenum",nullptr ) +, SchemaEntry("ifcvaporpermeabilitymeasure",nullptr ) +, SchemaEntry("ifctimeseriesscheduletypeenum",nullptr ) +, SchemaEntry("ifclinearmomentmeasure",nullptr ) +, SchemaEntry("ifcgeometricsetselect",nullptr ) +, SchemaEntry("ifcsectionmodulusmeasure",nullptr ) +, SchemaEntry("ifcbsplinecurveform",nullptr ) +, SchemaEntry("ifcdimensionextentusage",nullptr ) +, SchemaEntry("ifcthermalexpansioncoefficientmeasure",nullptr ) +, SchemaEntry("ifchourinday",nullptr ) +, SchemaEntry("ifclinearvelocitymeasure",nullptr ) +, SchemaEntry("ifctorquemeasure",nullptr ) +, SchemaEntry("ifctemperaturegradientmeasure",nullptr ) +, SchemaEntry("ifcfillstyleselect",nullptr ) +, SchemaEntry("ifcelectricchargemeasure",nullptr ) +, SchemaEntry("ifcheatexchangertypeenum",nullptr ) +, SchemaEntry("ifcelectriccurrentenum",nullptr ) +, SchemaEntry("ifcdaylightsavinghour",nullptr ) +, SchemaEntry("ifcshell",nullptr ) +, SchemaEntry("ifcdoseequivalentmeasure",nullptr ) +, SchemaEntry("ifcprojectordertypeenum",nullptr ) +, SchemaEntry("ifcderivedmeasurevalue",nullptr ) +, SchemaEntry("ifclightdistributioncurveenum",nullptr ) +, SchemaEntry("ifcwarpingmomentmeasure",nullptr ) +, SchemaEntry("ifcmembertypeenum",nullptr ) +, SchemaEntry("ifcsoundpowermeasure",nullptr ) +, SchemaEntry("ifctextalignment",nullptr ) +, SchemaEntry("ifccurveoredgecurve",nullptr ) +, SchemaEntry("ifcmassflowratemeasure",nullptr ) +, SchemaEntry("ifcisothermalmoisturecapacitymeasure",nullptr ) +, SchemaEntry("ifccsgselect",nullptr ) +, SchemaEntry("ifccoolingtowertypeenum",nullptr ) +, SchemaEntry("ifcmassmeasure",nullptr ) +, SchemaEntry("ifcpileconstructionenum",nullptr ) +, SchemaEntry("ifcdoorstyleoperationenum",nullptr ) +, SchemaEntry("ifcflowdirectionenum",nullptr ) +, SchemaEntry("ifcthermalloadsourceenum",nullptr ) +, SchemaEntry("ifclengthmeasure",nullptr ) +, SchemaEntry("ifcconstraintenum",nullptr ) +, SchemaEntry("ifcaxis2placement",nullptr ) +, SchemaEntry("ifcloadgrouptypeenum",nullptr ) +, SchemaEntry("ifcvalue",nullptr ) +, SchemaEntry("ifcreinforcingbarsurfaceenum",nullptr ) +, SchemaEntry("ifcprojectorderrecordtypeenum",nullptr ) +, SchemaEntry("ifcdatetimeselect",nullptr ) +, SchemaEntry("ifcstructuralsurfacetypeenum",nullptr ) +, SchemaEntry("ifcpermeablecoveringoperationenum",nullptr ) +, SchemaEntry("ifcfontweight",nullptr ) +, SchemaEntry("ifcphmeasure",nullptr ) +, SchemaEntry("ifcdescriptivemeasure",nullptr ) +, SchemaEntry("ifccurvestylefontselect",nullptr ) +, SchemaEntry("ifcunit",nullptr ) +, SchemaEntry("ifchatchlinedistanceselect",nullptr ) +, SchemaEntry("ifctextstyleselect",nullptr ) +, SchemaEntry("ifcmetricvalueselect",nullptr ) +, SchemaEntry("ifcvectorordirection",nullptr ) +, SchemaEntry("ifcassemblyplaceenum",nullptr ) +, SchemaEntry("ifcairterminaltypeenum",nullptr ) +, SchemaEntry("ifccoveringtypeenum",nullptr ) +, SchemaEntry("ifcplanarforcemeasure",nullptr ) +, SchemaEntry("ifcvalvetypeenum",nullptr ) +, SchemaEntry("ifcalarmtypeenum",nullptr ) +, SchemaEntry("ifcdynamicviscositymeasure",nullptr ) +, SchemaEntry("ifccurrencyenum",nullptr ) +, SchemaEntry("ifcmodulusofrotationalsubgradereactionmeasure",nullptr ) +, SchemaEntry("ifccablecarrierfittingtypeenum",nullptr ) +, SchemaEntry("ifcboolean",nullptr ) +, SchemaEntry("ifcactionsourcetypeenum",nullptr ) +, SchemaEntry("ifcstructuralactivityassignmentselect",nullptr ) +, SchemaEntry("ifcdistributionchamberelementtypeenum",nullptr ) +, SchemaEntry("ifcevaporativecoolertypeenum",nullptr ) +, SchemaEntry("ifcmagneticfluxdensitymeasure",nullptr ) +, SchemaEntry("ifclightdistributiondatasourceselect",nullptr ) +, SchemaEntry("ifctubebundletypeenum",nullptr ) +, SchemaEntry("ifcaccelerationmeasure",nullptr ) +, SchemaEntry("ifcboilertypeenum",nullptr ) +, SchemaEntry("ifcramptypeenum",nullptr ) +, SchemaEntry("ifcluminousintensitydistributionmeasure",nullptr ) +, SchemaEntry("ifctrimmingpreference",nullptr ) +, SchemaEntry("ifcspecificheatcapacitymeasure",nullptr ) +, SchemaEntry("ifcamountofsubstancemeasure",nullptr ) +, SchemaEntry("ifcroleenum",nullptr ) +, SchemaEntry("ifcdocumentconfidentialityenum",nullptr ) +, SchemaEntry("ifcfrequencymeasure",nullptr ) +, SchemaEntry("ifcsectiontypeenum",nullptr ) +, SchemaEntry("ifcelementassemblytypeenum",nullptr ) +, SchemaEntry("ifcfootingtypeenum",nullptr ) +, SchemaEntry("ifclayereditem",nullptr ) +, SchemaEntry("ifccablesegmenttypeenum",nullptr ) +, SchemaEntry("ifcdefinedsymbolselect",nullptr ) +, SchemaEntry("ifcbuildingelementproxytypeenum",nullptr ) +, SchemaEntry("ifcelectricgeneratortypeenum",nullptr ) +, SchemaEntry("ifcrotationalstiffnessmeasure",nullptr ) +, SchemaEntry("ifcspaceheatertypeenum",nullptr ) +, SchemaEntry("ifcareameasure",nullptr ) +, SchemaEntry("ifclabel",nullptr ) +, SchemaEntry("ifccostscheduletypeenum",nullptr ) +, SchemaEntry("ifcswitchingdevicetypeenum",nullptr ) +, SchemaEntry("ifcelectrictimecontroltypeenum",nullptr ) +, SchemaEntry("ifcfiltertypeenum",nullptr ) +, SchemaEntry("ifcpositivelengthmeasure",nullptr ) +, SchemaEntry("ifcnullstyle",nullptr ) +, SchemaEntry("ifcconditioncriterionselect",nullptr ) +, SchemaEntry("ifcshearmodulusmeasure",nullptr ) +, SchemaEntry("ifcnormalisedratiomeasure",nullptr ) +, SchemaEntry("ifcdoorpaneloperationenum",nullptr ) +, SchemaEntry("ifcpointorvertexpoint",nullptr ) +, SchemaEntry("ifcrooftypeenum",nullptr ) +, SchemaEntry("ifccountmeasure",nullptr ) +, SchemaEntry("ifcelectricconductancemeasure",nullptr ) +, SchemaEntry("ifcproceduretypeenum",nullptr ) +, SchemaEntry("ifcflowinstrumenttypeenum",nullptr ) +, SchemaEntry("ifcelectricmotortypeenum",nullptr ) +, SchemaEntry("ifcsurfaceside",nullptr ) +, SchemaEntry("ifcstructuralcurvetypeenum",nullptr ) +, SchemaEntry("ifccondensertypeenum",nullptr ) +, SchemaEntry("ifclinearstiffnessmeasure",nullptr ) +, SchemaEntry("ifcunitenum",nullptr ) +, SchemaEntry("ifcoccupanttypeenum",nullptr ) +, SchemaEntry("ifcthermalloadtypeenum",nullptr ) +, SchemaEntry("ifcreinforcingbarroleenum",nullptr ) +, SchemaEntry("ifcbenchmarkenum",nullptr ) +, SchemaEntry("ifcpositiveplaneanglemeasure",nullptr ) +, SchemaEntry("ifctexttransformation",nullptr ) +, SchemaEntry("ifcdraughtingcalloutelement",nullptr ) +, SchemaEntry("ifcratiomeasure",nullptr ) +, SchemaEntry("ifcsolidanglemeasure",nullptr ) +, SchemaEntry("ifcpipesegmenttypeenum",nullptr ) +, SchemaEntry("ifccablecarriersegmenttypeenum",nullptr ) +, SchemaEntry("ifccolourorfactor",nullptr ) +, SchemaEntry("ifcidentifier",nullptr ) +, SchemaEntry("ifctendontypeenum",nullptr ) +, SchemaEntry("ifccontrollertypeenum",nullptr ) +, SchemaEntry("ifcradioactivitymeasure",nullptr ) +, SchemaEntry("ifctimemeasure",nullptr ) +, SchemaEntry("ifcpumptypeenum",nullptr ) +, SchemaEntry("ifcelectricheatertypeenum",nullptr ) +, SchemaEntry("ifcbeamtypeenum",nullptr ) +, SchemaEntry("ifcstateenum",nullptr ) +, SchemaEntry("ifcsiprefix",nullptr ) +, SchemaEntry("ifcnumericmeasure",nullptr ) +, SchemaEntry("ifcoutlettypeenum",nullptr ) +, SchemaEntry("ifccompoundplaneanglemeasure",nullptr ) +, SchemaEntry("ifcservicelifefactortypeenum",nullptr ) +, SchemaEntry("ifclogicaloperatorenum",nullptr ) +, SchemaEntry("ifcbooleanoperand",nullptr ) +, SchemaEntry("ifcobjectreferenceselect",nullptr ) +, SchemaEntry("ifccooledbeamtypeenum",nullptr ) +, SchemaEntry("ifcductsilencertypeenum",nullptr ) +, SchemaEntry("ifcsectionalareaintegralmeasure",nullptr ) +, SchemaEntry("ifcfontvariant",nullptr ) +, SchemaEntry("ifcvolumetricflowratemeasure",nullptr ) +, SchemaEntry("ifcplatetypeenum",nullptr ) +, SchemaEntry("ifcenvironmentalimpactcategoryenum",nullptr ) +, SchemaEntry("ifcvibrationisolatortypeenum",nullptr ) +, SchemaEntry("ifcthermodynamictemperaturemeasure",nullptr ) +, SchemaEntry("ifcrotationalmassmeasure",nullptr ) +, SchemaEntry("ifcsecondinminute",nullptr ) +, SchemaEntry("ifcdayinmonthnumber",nullptr ) +, SchemaEntry("ifcdimensioncount",nullptr ) +, SchemaEntry("ifcwindowstyleoperationenum",nullptr ) +, SchemaEntry("ifcthermalresistancemeasure",nullptr ) +, SchemaEntry("ifcmeasurevalue",nullptr ) +, SchemaEntry("ifcwindowpaneloperationenum",nullptr ) +, SchemaEntry("ifcchillertypeenum",nullptr ) +, SchemaEntry("ifcpositiveratiomeasure",nullptr ) +, SchemaEntry("ifcinteger",nullptr ) +, SchemaEntry("ifclogical",nullptr ) +, SchemaEntry("ifcjunctionboxtypeenum",nullptr ) +, SchemaEntry("ifcaddresstypeenum",nullptr ) +, SchemaEntry("ifcwasteterminaltypeenum",nullptr ) +, SchemaEntry("ifctrimmingselect",nullptr ) +, SchemaEntry("ifclightemissionsourceenum",nullptr ) +, SchemaEntry("ifcsoundscaleenum",nullptr ) +, SchemaEntry("ifcluminousfluxmeasure",nullptr ) +, SchemaEntry("ifcelectricresistancemeasure",nullptr ) +, SchemaEntry("ifcintegercountratemeasure",nullptr ) +, SchemaEntry("ifcphysicalorvirtualenum",nullptr ) +, SchemaEntry("ifcmolecularweightmeasure",nullptr ) +, SchemaEntry("ifcprofiletypeenum",nullptr ) +, SchemaEntry("ifcboxalignment",nullptr ) +, SchemaEntry("ifcglobalorlocalenum",nullptr ) +, SchemaEntry("ifcspecularroughness",nullptr ) +, SchemaEntry("ifclamptypeenum",nullptr ) +, SchemaEntry("ifcpiletypeenum",nullptr ) +, SchemaEntry("ifcelectriccurrentmeasure",nullptr ) +, SchemaEntry("ifcfantypeenum",nullptr ) +, SchemaEntry("ifcsurfaceorfacesurface",nullptr ) +, SchemaEntry("ifcpipefittingtypeenum",nullptr ) +, SchemaEntry("ifctanktypeenum",nullptr ) +, SchemaEntry("ifccurvefontorscaledcurvefontselect",nullptr ) +, SchemaEntry("ifcwindowstyleconstructionenum",nullptr ) +, SchemaEntry("ifcairterminalboxtypeenum",nullptr ) +, SchemaEntry("ifcstairflighttypeenum",nullptr ) +, SchemaEntry("ifcluminousintensitymeasure",nullptr ) +, SchemaEntry("ifcmotorconnectiontypeenum",nullptr ) +, SchemaEntry("ifcplaneanglemeasure",nullptr ) +, SchemaEntry("ifcactuatortypeenum",nullptr ) +, SchemaEntry("ifccolumntypeenum",nullptr ) +, SchemaEntry("ifctextfontselect",nullptr ) +, SchemaEntry("ifcdoorpanelpositionenum",nullptr ) +, SchemaEntry("ifccoiltypeenum",nullptr ) +, SchemaEntry("ifcangularvelocitymeasure",nullptr ) +, SchemaEntry("ifcanalysismodeltypeenum",nullptr ) +, SchemaEntry("ifclibraryselect",nullptr ) +, SchemaEntry("ifcforcemeasure",nullptr ) +, SchemaEntry("ifcfillareastyletileshapeselect",nullptr ) +, SchemaEntry("ifcelectricappliancetypeenum",nullptr ) +, SchemaEntry("ifcsurfacetextureenum",nullptr ) +, SchemaEntry("ifccharacterstyleselect",nullptr ) +, SchemaEntry("ifcenergymeasure",nullptr ) +, SchemaEntry("ifcreal",nullptr ) +, SchemaEntry("ifccompressortypeenum",nullptr ) +, SchemaEntry("ifcelectricdistributionpointfunctionenum",nullptr ) +, SchemaEntry("ifcroot",&STEP::ObjectHelper<IfcRoot,4>::Construct ) +, SchemaEntry("ifcobjectdefinition",&STEP::ObjectHelper<IfcObjectDefinition,0>::Construct ) +, SchemaEntry("ifctypeobject",&STEP::ObjectHelper<IfcTypeObject,2>::Construct ) +, SchemaEntry("ifctypeproduct",&STEP::ObjectHelper<IfcTypeProduct,2>::Construct ) +, SchemaEntry("ifcelementtype",&STEP::ObjectHelper<IfcElementType,1>::Construct ) +, SchemaEntry("ifcdistributionelementtype",&STEP::ObjectHelper<IfcDistributionElementType,0>::Construct ) +, SchemaEntry("ifcdistributionflowelementtype",&STEP::ObjectHelper<IfcDistributionFlowElementType,0>::Construct ) +, SchemaEntry("ifcflowcontrollertype",&STEP::ObjectHelper<IfcFlowControllerType,0>::Construct ) +, SchemaEntry("ifcelectrictimecontroltype",&STEP::ObjectHelper<IfcElectricTimeControlType,1>::Construct ) +, SchemaEntry("ifcrepresentation",&STEP::ObjectHelper<IfcRepresentation,4>::Construct ) +, SchemaEntry("ifcshapemodel",&STEP::ObjectHelper<IfcShapeModel,0>::Construct ) +, SchemaEntry("ifctopologyrepresentation",&STEP::ObjectHelper<IfcTopologyRepresentation,0>::Construct ) +, SchemaEntry("ifcrelationship",&STEP::ObjectHelper<IfcRelationship,0>::Construct ) +, SchemaEntry("ifcrelconnects",&STEP::ObjectHelper<IfcRelConnects,0>::Construct ) +, SchemaEntry("ifcrelcoversspaces",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcflowfittingtype",&STEP::ObjectHelper<IfcFlowFittingType,0>::Construct ) +, SchemaEntry("ifccablecarrierfittingtype",&STEP::ObjectHelper<IfcCableCarrierFittingType,1>::Construct ) +, SchemaEntry("ifcstructuralconnectioncondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcslippageconnectioncondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcenergyconversiondevicetype",&STEP::ObjectHelper<IfcEnergyConversionDeviceType,0>::Construct ) +, SchemaEntry("ifccoiltype",&STEP::ObjectHelper<IfcCoilType,1>::Construct ) +, SchemaEntry("ifcobject",&STEP::ObjectHelper<IfcObject,1>::Construct ) +, SchemaEntry("ifccontrol",&STEP::ObjectHelper<IfcControl,0>::Construct ) +, SchemaEntry("ifcperformancehistory",&STEP::ObjectHelper<IfcPerformanceHistory,1>::Construct ) +, SchemaEntry("ifcrepresentationitem",&STEP::ObjectHelper<IfcRepresentationItem,0>::Construct ) +, SchemaEntry("ifcgeometricrepresentationitem",&STEP::ObjectHelper<IfcGeometricRepresentationItem,0>::Construct ) +, SchemaEntry("ifctextliteral",&STEP::ObjectHelper<IfcTextLiteral,3>::Construct ) +, SchemaEntry("ifctextliteralwithextent",&STEP::ObjectHelper<IfcTextLiteralWithExtent,2>::Construct ) +, SchemaEntry("ifcproductrepresentation",&STEP::ObjectHelper<IfcProductRepresentation,3>::Construct ) +, SchemaEntry("ifcproduct",&STEP::ObjectHelper<IfcProduct,2>::Construct ) +, SchemaEntry("ifcelement",&STEP::ObjectHelper<IfcElement,1>::Construct ) +, SchemaEntry("ifcdistributionelement",&STEP::ObjectHelper<IfcDistributionElement,0>::Construct ) +, SchemaEntry("ifcdistributionflowelement",&STEP::ObjectHelper<IfcDistributionFlowElement,0>::Construct ) +, SchemaEntry("ifccurve",&STEP::ObjectHelper<IfcCurve,0>::Construct ) +, SchemaEntry("ifcboundedcurve",&STEP::ObjectHelper<IfcBoundedCurve,0>::Construct ) +, SchemaEntry("ifccompositecurve",&STEP::ObjectHelper<IfcCompositeCurve,2>::Construct ) +, SchemaEntry("ifc2dcompositecurve",&STEP::ObjectHelper<Ifc2DCompositeCurve,0>::Construct ) +, SchemaEntry("ifcboundarycondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcboundaryfacecondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccartesiantransformationoperator",&STEP::ObjectHelper<IfcCartesianTransformationOperator,4>::Construct ) +, SchemaEntry("ifccartesiantransformationoperator3d",&STEP::ObjectHelper<IfcCartesianTransformationOperator3D,1>::Construct ) +, SchemaEntry("ifcproperty",&STEP::ObjectHelper<IfcProperty,2>::Construct ) +, SchemaEntry("ifcsimpleproperty",&STEP::ObjectHelper<IfcSimpleProperty,0>::Construct ) +, SchemaEntry("ifcpropertyenumeratedvalue",&STEP::ObjectHelper<IfcPropertyEnumeratedValue,2>::Construct ) +, SchemaEntry("ifcpresentationlayerassignment",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpresentationlayerwithstyle",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcbuildingelementtype",&STEP::ObjectHelper<IfcBuildingElementType,0>::Construct ) +, SchemaEntry("ifcstairflighttype",&STEP::ObjectHelper<IfcStairFlightType,1>::Construct ) +, SchemaEntry("ifcsurface",&STEP::ObjectHelper<IfcSurface,0>::Construct ) +, SchemaEntry("ifcelementarysurface",&STEP::ObjectHelper<IfcElementarySurface,1>::Construct ) +, SchemaEntry("ifcplane",&STEP::ObjectHelper<IfcPlane,0>::Construct ) +, SchemaEntry("ifcbooleanresult",&STEP::ObjectHelper<IfcBooleanResult,3>::Construct ) +, SchemaEntry("ifcbooleanclippingresult",&STEP::ObjectHelper<IfcBooleanClippingResult,0>::Construct ) +, SchemaEntry("ifcsolidmodel",&STEP::ObjectHelper<IfcSolidModel,0>::Construct ) +, SchemaEntry("ifcmanifoldsolidbrep",&STEP::ObjectHelper<IfcManifoldSolidBrep,1>::Construct ) +, SchemaEntry("ifcprofileproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcgeneralprofileproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralprofileproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcflowterminaltype",&STEP::ObjectHelper<IfcFlowTerminalType,0>::Construct ) +, SchemaEntry("ifcstackterminaltype",&STEP::ObjectHelper<IfcStackTerminalType,1>::Construct ) +, SchemaEntry("ifcstructuralitem",&STEP::ObjectHelper<IfcStructuralItem,0>::Construct ) +, SchemaEntry("ifcstructuralconnection",&STEP::ObjectHelper<IfcStructuralConnection,1>::Construct ) +, SchemaEntry("ifcstructuralcurveconnection",&STEP::ObjectHelper<IfcStructuralCurveConnection,0>::Construct ) +, SchemaEntry("ifcjunctionboxtype",&STEP::ObjectHelper<IfcJunctionBoxType,1>::Construct ) +, SchemaEntry("ifcrelassociates",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassociatesconstraint",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpropertydefinition",&STEP::ObjectHelper<IfcPropertyDefinition,0>::Construct ) +, SchemaEntry("ifcpropertysetdefinition",&STEP::ObjectHelper<IfcPropertySetDefinition,0>::Construct ) +, SchemaEntry("ifcdoorpanelproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconstraintrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcspacethermalloadproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifclibraryinformation",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcprocess",&STEP::ObjectHelper<IfcProcess,0>::Construct ) +, SchemaEntry("ifctask",&STEP::ObjectHelper<IfcTask,5>::Construct ) +, SchemaEntry("ifcappliedvalue",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcenvironmentalimpactvalue",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelfillselement",&STEP::ObjectHelper<IfcRelFillsElement,2>::Construct ) +, SchemaEntry("ifcprocedure",&STEP::ObjectHelper<IfcProcedure,3>::Construct ) +, SchemaEntry("ifcstructuralload",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadstatic",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadsingledisplacement",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcproxy",&STEP::ObjectHelper<IfcProxy,2>::Construct ) +, SchemaEntry("ifccurvestylefont",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcresource",&STEP::ObjectHelper<IfcResource,0>::Construct ) +, SchemaEntry("ifcconstructionresource",&STEP::ObjectHelper<IfcConstructionResource,4>::Construct ) +, SchemaEntry("ifcsubcontractresource",&STEP::ObjectHelper<IfcSubContractResource,2>::Construct ) +, SchemaEntry("ifccalendardate",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdocumentelectronicformat",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelcontainedinspatialstructure",&STEP::ObjectHelper<IfcRelContainedInSpatialStructure,2>::Construct ) +, SchemaEntry("ifcmaterialproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcproductsofcombustionproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctopologicalrepresentationitem",&STEP::ObjectHelper<IfcTopologicalRepresentationItem,0>::Construct ) +, SchemaEntry("ifcedge",&STEP::ObjectHelper<IfcEdge,2>::Construct ) +, SchemaEntry("ifcedgecurve",&STEP::ObjectHelper<IfcEdgeCurve,2>::Construct ) +, SchemaEntry("ifcplatetype",&STEP::ObjectHelper<IfcPlateType,1>::Construct ) +, SchemaEntry("ifcobjectplacement",&STEP::ObjectHelper<IfcObjectPlacement,0>::Construct ) +, SchemaEntry("ifcgridplacement",&STEP::ObjectHelper<IfcGridPlacement,2>::Construct ) +, SchemaEntry("ifcfiresuppressionterminaltype",&STEP::ObjectHelper<IfcFireSuppressionTerminalType,1>::Construct ) +, SchemaEntry("ifcmechanicalmaterialproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcflowstoragedevice",&STEP::ObjectHelper<IfcFlowStorageDevice,0>::Construct ) +, SchemaEntry("ifcperson",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsweptsurface",&STEP::ObjectHelper<IfcSweptSurface,2>::Construct ) +, SchemaEntry("ifcsurfaceofrevolution",&STEP::ObjectHelper<IfcSurfaceOfRevolution,1>::Construct ) +, SchemaEntry("ifcorientededge",&STEP::ObjectHelper<IfcOrientedEdge,2>::Construct ) +, SchemaEntry("ifcownerhistory",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassigns",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassignstoactor",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdirection",&STEP::ObjectHelper<IfcDirection,1>::Construct ) +, SchemaEntry("ifcreinforcementbarproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcprofiledef",&STEP::ObjectHelper<IfcProfileDef,2>::Construct ) +, SchemaEntry("ifcparameterizedprofiledef",&STEP::ObjectHelper<IfcParameterizedProfileDef,1>::Construct ) +, SchemaEntry("ifccshapeprofiledef",&STEP::ObjectHelper<IfcCShapeProfileDef,6>::Construct ) +, SchemaEntry("ifcfeatureelement",&STEP::ObjectHelper<IfcFeatureElement,0>::Construct ) +, SchemaEntry("ifcfeatureelementsubtraction",&STEP::ObjectHelper<IfcFeatureElementSubtraction,0>::Construct ) +, SchemaEntry("ifcedgefeature",&STEP::ObjectHelper<IfcEdgeFeature,1>::Construct ) +, SchemaEntry("ifcchamferedgefeature",&STEP::ObjectHelper<IfcChamferEdgeFeature,2>::Construct ) +, SchemaEntry("ifcbuildingelement",&STEP::ObjectHelper<IfcBuildingElement,0>::Construct ) +, SchemaEntry("ifccolumn",&STEP::ObjectHelper<IfcColumn,0>::Construct ) +, SchemaEntry("ifcpropertyreferencevalue",&STEP::ObjectHelper<IfcPropertyReferenceValue,2>::Construct ) +, SchemaEntry("ifcmaterialclassificationrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcelectricmotortype",&STEP::ObjectHelper<IfcElectricMotorType,1>::Construct ) +, SchemaEntry("ifcspatialstructureelementtype",&STEP::ObjectHelper<IfcSpatialStructureElementType,0>::Construct ) +, SchemaEntry("ifcspacetype",&STEP::ObjectHelper<IfcSpaceType,1>::Construct ) +, SchemaEntry("ifcexternalreference",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcexternallydefinedhatchstyle",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccolumntype",&STEP::ObjectHelper<IfcColumnType,1>::Construct ) +, SchemaEntry("ifccranerailashapeprofiledef",&STEP::ObjectHelper<IfcCraneRailAShapeProfileDef,12>::Construct ) +, SchemaEntry("ifccondensertype",&STEP::ObjectHelper<IfcCondenserType,1>::Construct ) +, SchemaEntry("ifcrelconnectselements",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelconnectswithrealizingelements",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccircleprofiledef",&STEP::ObjectHelper<IfcCircleProfileDef,1>::Construct ) +, SchemaEntry("ifccirclehollowprofiledef",&STEP::ObjectHelper<IfcCircleHollowProfileDef,1>::Construct ) +, SchemaEntry("ifcorganizationrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcplacement",&STEP::ObjectHelper<IfcPlacement,1>::Construct ) +, SchemaEntry("ifcaxis2placement3d",&STEP::ObjectHelper<IfcAxis2Placement3D,2>::Construct ) +, SchemaEntry("ifcpresentationstyle",&STEP::ObjectHelper<IfcPresentationStyle,1>::Construct ) +, SchemaEntry("ifccurvestyle",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcequipmentelement",&STEP::ObjectHelper<IfcEquipmentElement,0>::Construct ) +, SchemaEntry("ifccompositecurvesegment",&STEP::ObjectHelper<IfcCompositeCurveSegment,3>::Construct ) +, SchemaEntry("ifcrectangleprofiledef",&STEP::ObjectHelper<IfcRectangleProfileDef,2>::Construct ) +, SchemaEntry("ifcphysicalquantity",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcphysicalcomplexquantity",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassociateslibrary",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelsequence",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcbuildingelementproxy",&STEP::ObjectHelper<IfcBuildingElementProxy,1>::Construct ) +, SchemaEntry("ifcdistributioncontrolelementtype",&STEP::ObjectHelper<IfcDistributionControlElementType,0>::Construct ) +, SchemaEntry("ifcflowinstrumenttype",&STEP::ObjectHelper<IfcFlowInstrumentType,1>::Construct ) +, SchemaEntry("ifcdraughtingcallout",&STEP::ObjectHelper<IfcDraughtingCallout,1>::Construct ) +, SchemaEntry("ifcdimensioncurvedirectedcallout",&STEP::ObjectHelper<IfcDimensionCurveDirectedCallout,0>::Construct ) +, SchemaEntry("ifclineardimension",&STEP::ObjectHelper<IfcLinearDimension,0>::Construct ) +, SchemaEntry("ifcelementassembly",&STEP::ObjectHelper<IfcElementAssembly,2>::Construct ) +, SchemaEntry("ifcdraughtingcalloutrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccsgprimitive3d",&STEP::ObjectHelper<IfcCsgPrimitive3D,1>::Construct ) +, SchemaEntry("ifcrightcircularcone",&STEP::ObjectHelper<IfcRightCircularCone,2>::Construct ) +, SchemaEntry("ifcexternallydefinedsurfacestyle",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcprojectorder",&STEP::ObjectHelper<IfcProjectOrder,3>::Construct ) +, SchemaEntry("ifcpropertyconstraintrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifclshapeprofiledef",&STEP::ObjectHelper<IfcLShapeProfileDef,8>::Construct ) +, SchemaEntry("ifcangulardimension",&STEP::ObjectHelper<IfcAngularDimension,0>::Construct ) +, SchemaEntry("ifctextstylefordefinedfont",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifclocalplacement",&STEP::ObjectHelper<IfcLocalPlacement,2>::Construct ) +, SchemaEntry("ifcsweptareasolid",&STEP::ObjectHelper<IfcSweptAreaSolid,2>::Construct ) +, SchemaEntry("ifcrevolvedareasolid",&STEP::ObjectHelper<IfcRevolvedAreaSolid,2>::Construct ) +, SchemaEntry("ifcstructuralsurfaceconnection",&STEP::ObjectHelper<IfcStructuralSurfaceConnection,0>::Construct ) +, SchemaEntry("ifcradiusdimension",&STEP::ObjectHelper<IfcRadiusDimension,0>::Construct ) +, SchemaEntry("ifcsweptdisksolid",&STEP::ObjectHelper<IfcSweptDiskSolid,5>::Construct ) +, SchemaEntry("ifchalfspacesolid",&STEP::ObjectHelper<IfcHalfSpaceSolid,2>::Construct ) +, SchemaEntry("ifcpolygonalboundedhalfspace",&STEP::ObjectHelper<IfcPolygonalBoundedHalfSpace,2>::Construct ) +, SchemaEntry("ifctimeseriesschedule",&STEP::ObjectHelper<IfcTimeSeriesSchedule,3>::Construct ) +, SchemaEntry("ifcdimensioncalloutrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccooledbeamtype",&STEP::ObjectHelper<IfcCooledBeamType,1>::Construct ) +, SchemaEntry("ifcproject",&STEP::ObjectHelper<IfcProject,4>::Construct ) +, SchemaEntry("ifcapprovalrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcevaporatortype",&STEP::ObjectHelper<IfcEvaporatorType,1>::Construct ) +, SchemaEntry("ifclaborresource",&STEP::ObjectHelper<IfcLaborResource,1>::Construct ) +, SchemaEntry("ifcstructuralloadsingledisplacementdistortion",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpropertyboundedvalue",&STEP::ObjectHelper<IfcPropertyBoundedValue,3>::Construct ) +, SchemaEntry("ifcrampflighttype",&STEP::ObjectHelper<IfcRampFlightType,1>::Construct ) +, SchemaEntry("ifcmember",&STEP::ObjectHelper<IfcMember,0>::Construct ) +, SchemaEntry("ifcstructuralloadplanarforce",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctubebundletype",&STEP::ObjectHelper<IfcTubeBundleType,1>::Construct ) +, SchemaEntry("ifcvalvetype",&STEP::ObjectHelper<IfcValveType,1>::Construct ) +, SchemaEntry("ifcexternallydefinedtextfont",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctrimmedcurve",&STEP::ObjectHelper<IfcTrimmedCurve,5>::Construct ) +, SchemaEntry("ifcreldefines",&STEP::ObjectHelper<IfcRelDefines,1>::Construct ) +, SchemaEntry("ifcreldefinesbyproperties",&STEP::ObjectHelper<IfcRelDefinesByProperties,1>::Construct ) +, SchemaEntry("ifcrelassignstocontrol",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcactor",&STEP::ObjectHelper<IfcActor,1>::Construct ) +, SchemaEntry("ifcoccupant",&STEP::ObjectHelper<IfcOccupant,1>::Construct ) +, SchemaEntry("ifchumidifiertype",&STEP::ObjectHelper<IfcHumidifierType,1>::Construct ) +, SchemaEntry("ifcarbitraryopenprofiledef",&STEP::ObjectHelper<IfcArbitraryOpenProfileDef,1>::Construct ) +, SchemaEntry("ifcrelassignstoprojectorder",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpermit",&STEP::ObjectHelper<IfcPermit,1>::Construct ) +, SchemaEntry("ifcoffsetcurve3d",&STEP::ObjectHelper<IfcOffsetCurve3D,4>::Construct ) +, SchemaEntry("ifclightsource",&STEP::ObjectHelper<IfcLightSource,4>::Construct ) +, SchemaEntry("ifclightsourcepositional",&STEP::ObjectHelper<IfcLightSourcePositional,5>::Construct ) +, SchemaEntry("ifcsurfacetexture",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcblobtexture",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccompositeprofiledef",&STEP::ObjectHelper<IfcCompositeProfileDef,2>::Construct ) +, SchemaEntry("ifcdocumentinformation",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsurfacestylelighting",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcphysicalsimplequantity",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcquantityarea",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctimeseries",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcclassificationnotation",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcramp",&STEP::ObjectHelper<IfcRamp,1>::Construct ) +, SchemaEntry("ifcpredefineditem",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpredefinedcurvefont",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpredefinedcolour",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccurrencyrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcflowmovingdevice",&STEP::ObjectHelper<IfcFlowMovingDevice,0>::Construct ) +, SchemaEntry("ifcspaceheatertype",&STEP::ObjectHelper<IfcSpaceHeaterType,1>::Construct ) +, SchemaEntry("ifclamptype",&STEP::ObjectHelper<IfcLampType,1>::Construct ) +, SchemaEntry("ifcbuildingelementcomponent",&STEP::ObjectHelper<IfcBuildingElementComponent,0>::Construct ) +, SchemaEntry("ifcreinforcingelement",&STEP::ObjectHelper<IfcReinforcingElement,1>::Construct ) +, SchemaEntry("ifcreinforcingbar",&STEP::ObjectHelper<IfcReinforcingBar,5>::Construct ) +, SchemaEntry("ifcelectricheatertype",&STEP::ObjectHelper<IfcElectricHeaterType,1>::Construct ) +, SchemaEntry("ifctshapeprofiledef",&STEP::ObjectHelper<IfcTShapeProfileDef,10>::Construct ) +, SchemaEntry("ifcconstraint",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcobjective",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralactivity",&STEP::ObjectHelper<IfcStructuralActivity,2>::Construct ) +, SchemaEntry("ifcstructuralaction",&STEP::ObjectHelper<IfcStructuralAction,2>::Construct ) +, SchemaEntry("ifctexturecoordinate",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctexturemap",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmonetaryunit",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcquantitytime",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctablerow",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifclightdistributiondata",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcductfittingtype",&STEP::ObjectHelper<IfcDuctFittingType,1>::Construct ) +, SchemaEntry("ifccartesiantransformationoperator2d",&STEP::ObjectHelper<IfcCartesianTransformationOperator2D,0>::Construct ) +, SchemaEntry("ifccartesiantransformationoperator2dnonuniform",&STEP::ObjectHelper<IfcCartesianTransformationOperator2DnonUniform,1>::Construct ) +, SchemaEntry("ifcclassificationnotationfacet",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassociatesapproval",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdraughtingpredefinedcurvefont",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadsingleforce",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadsingleforcewarping",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccurvestylefontandscaling",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcvirtualelement",&STEP::ObjectHelper<IfcVirtualElement,0>::Construct ) +, SchemaEntry("ifcrightcircularcylinder",&STEP::ObjectHelper<IfcRightCircularCylinder,2>::Construct ) +, SchemaEntry("ifcoutlettype",&STEP::ObjectHelper<IfcOutletType,1>::Construct ) +, SchemaEntry("ifcreldecomposes",&STEP::ObjectHelper<IfcRelDecomposes,2>::Construct ) +, SchemaEntry("ifcrelnests",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccovering",&STEP::ObjectHelper<IfcCovering,1>::Construct ) +, SchemaEntry("ifcexternallydefinedsymbol",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcirregulartimeseries",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpolyline",&STEP::ObjectHelper<IfcPolyline,1>::Construct ) +, SchemaEntry("ifcpath",&STEP::ObjectHelper<IfcPath,1>::Construct ) +, SchemaEntry("ifcelementcomponent",&STEP::ObjectHelper<IfcElementComponent,0>::Construct ) +, SchemaEntry("ifcfastener",&STEP::ObjectHelper<IfcFastener,0>::Construct ) +, SchemaEntry("ifcmappeditem",&STEP::ObjectHelper<IfcMappedItem,2>::Construct ) +, SchemaEntry("ifcmetric",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdocumentreference",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsectionproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrectangularpyramid",&STEP::ObjectHelper<IfcRectangularPyramid,3>::Construct ) +, SchemaEntry("ifcrelreferencedinspatialstructure",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccrewresource",&STEP::ObjectHelper<IfcCrewResource,0>::Construct ) +, SchemaEntry("ifcnamedunit",&STEP::ObjectHelper<IfcNamedUnit,2>::Construct ) +, SchemaEntry("ifccontextdependentunit",&STEP::ObjectHelper<IfcContextDependentUnit,1>::Construct ) +, SchemaEntry("ifcunitaryequipmenttype",&STEP::ObjectHelper<IfcUnitaryEquipmentType,1>::Construct ) +, SchemaEntry("ifcroof",&STEP::ObjectHelper<IfcRoof,1>::Construct ) +, SchemaEntry("ifcrelassignstasks",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralmember",&STEP::ObjectHelper<IfcStructuralMember,0>::Construct ) +, SchemaEntry("ifcrelconnectsports",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstylemodel",&STEP::ObjectHelper<IfcStyleModel,0>::Construct ) +, SchemaEntry("ifcstyledrepresentation",&STEP::ObjectHelper<IfcStyledRepresentation,0>::Construct ) +, SchemaEntry("ifcspatialstructureelement",&STEP::ObjectHelper<IfcSpatialStructureElement,2>::Construct ) +, SchemaEntry("ifcbuilding",&STEP::ObjectHelper<IfcBuilding,3>::Construct ) +, SchemaEntry("ifcconnectedfaceset",&STEP::ObjectHelper<IfcConnectedFaceSet,1>::Construct ) +, SchemaEntry("ifcopenshell",&STEP::ObjectHelper<IfcOpenShell,0>::Construct ) +, SchemaEntry("ifcfacetedbrep",&STEP::ObjectHelper<IfcFacetedBrep,0>::Construct ) +, SchemaEntry("ifclocaltime",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmechanicalconcretematerialproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconic",&STEP::ObjectHelper<IfcConic,1>::Construct ) +, SchemaEntry("ifccoveringtype",&STEP::ObjectHelper<IfcCoveringType,1>::Construct ) +, SchemaEntry("ifcroundedrectangleprofiledef",&STEP::ObjectHelper<IfcRoundedRectangleProfileDef,1>::Construct ) +, SchemaEntry("ifcairterminaltype",&STEP::ObjectHelper<IfcAirTerminalType,1>::Construct ) +, SchemaEntry("ifcflowmovingdevicetype",&STEP::ObjectHelper<IfcFlowMovingDeviceType,0>::Construct ) +, SchemaEntry("ifccompressortype",&STEP::ObjectHelper<IfcCompressorType,1>::Construct ) +, SchemaEntry("ifcwindowpanelproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpredefinedsymbol",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpredefinedterminatorsymbol",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcishapeprofiledef",&STEP::ObjectHelper<IfcIShapeProfileDef,5>::Construct ) +, SchemaEntry("ifcasymmetricishapeprofiledef",&STEP::ObjectHelper<IfcAsymmetricIShapeProfileDef,4>::Construct ) +, SchemaEntry("ifccontrollertype",&STEP::ObjectHelper<IfcControllerType,1>::Construct ) +, SchemaEntry("ifcrailing",&STEP::ObjectHelper<IfcRailing,1>::Construct ) +, SchemaEntry("ifcgroup",&STEP::ObjectHelper<IfcGroup,0>::Construct ) +, SchemaEntry("ifcasset",&STEP::ObjectHelper<IfcAsset,9>::Construct ) +, SchemaEntry("ifcmaterialdefinitionrepresentation",&STEP::ObjectHelper<IfcMaterialDefinitionRepresentation,1>::Construct ) +, SchemaEntry("ifccurvestylefontpattern",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcapprovalpropertyrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrailingtype",&STEP::ObjectHelper<IfcRailingType,1>::Construct ) +, SchemaEntry("ifcwall",&STEP::ObjectHelper<IfcWall,0>::Construct ) +, SchemaEntry("ifcclassificationitem",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralpointconnection",&STEP::ObjectHelper<IfcStructuralPointConnection,0>::Construct ) +, SchemaEntry("ifcconnectiongeometry",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconnectionpointgeometry",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctimeseriesvalue",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpropertylistvalue",&STEP::ObjectHelper<IfcPropertyListValue,2>::Construct ) +, SchemaEntry("ifcfurniturestandard",&STEP::ObjectHelper<IfcFurnitureStandard,0>::Construct ) +, SchemaEntry("ifcrelschedulescostitems",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcelectricgeneratortype",&STEP::ObjectHelper<IfcElectricGeneratorType,1>::Construct ) +, SchemaEntry("ifcdoor",&STEP::ObjectHelper<IfcDoor,2>::Construct ) +, SchemaEntry("ifcstyleditem",&STEP::ObjectHelper<IfcStyledItem,3>::Construct ) +, SchemaEntry("ifcannotationoccurrence",&STEP::ObjectHelper<IfcAnnotationOccurrence,0>::Construct ) +, SchemaEntry("ifcannotationsymboloccurrence",&STEP::ObjectHelper<IfcAnnotationSymbolOccurrence,0>::Construct ) +, SchemaEntry("ifcarbitraryclosedprofiledef",&STEP::ObjectHelper<IfcArbitraryClosedProfileDef,1>::Construct ) +, SchemaEntry("ifcarbitraryprofiledefwithvoids",&STEP::ObjectHelper<IfcArbitraryProfileDefWithVoids,1>::Construct ) +, SchemaEntry("ifcline",&STEP::ObjectHelper<IfcLine,2>::Construct ) +, SchemaEntry("ifcmateriallayerset",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcflowsegmenttype",&STEP::ObjectHelper<IfcFlowSegmentType,0>::Construct ) +, SchemaEntry("ifcairterminalboxtype",&STEP::ObjectHelper<IfcAirTerminalBoxType,1>::Construct ) +, SchemaEntry("ifcrelconnectsstructuralmember",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpropertysinglevalue",&STEP::ObjectHelper<IfcPropertySingleValue,2>::Construct ) +, SchemaEntry("ifcalarmtype",&STEP::ObjectHelper<IfcAlarmType,1>::Construct ) +, SchemaEntry("ifcellipseprofiledef",&STEP::ObjectHelper<IfcEllipseProfileDef,2>::Construct ) +, SchemaEntry("ifcstair",&STEP::ObjectHelper<IfcStair,1>::Construct ) +, SchemaEntry("ifcpredefinedtextfont",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctextstylefontmodel",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsurfacestyleshading",&STEP::ObjectHelper<IfcSurfaceStyleShading,1>::Construct ) +, SchemaEntry("ifcpumptype",&STEP::ObjectHelper<IfcPumpType,1>::Construct ) +, SchemaEntry("ifcdefinedsymbol",&STEP::ObjectHelper<IfcDefinedSymbol,2>::Construct ) +, SchemaEntry("ifcclassificationitemrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcgeneralmaterialproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcelementcomponenttype",&STEP::ObjectHelper<IfcElementComponentType,0>::Construct ) +, SchemaEntry("ifcfastenertype",&STEP::ObjectHelper<IfcFastenerType,0>::Construct ) +, SchemaEntry("ifcmechanicalfastenertype",&STEP::ObjectHelper<IfcMechanicalFastenerType,0>::Construct ) +, SchemaEntry("ifcpermeablecoveringproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcflowfitting",&STEP::ObjectHelper<IfcFlowFitting,0>::Construct ) +, SchemaEntry("ifcapproval",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcshapeaspect",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconstraintclassificationrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifclightsourcedirectional",&STEP::ObjectHelper<IfcLightSourceDirectional,1>::Construct ) +, SchemaEntry("ifcsurfacestyle",&STEP::ObjectHelper<IfcSurfaceStyle,2>::Construct ) +, SchemaEntry("ifcrelconnectsstructuralactivity",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassociatesprofileproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcannotationsurface",&STEP::ObjectHelper<IfcAnnotationSurface,2>::Construct ) +, SchemaEntry("ifcfuelproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcflowcontroller",&STEP::ObjectHelper<IfcFlowController,0>::Construct ) +, SchemaEntry("ifcfailureconnectioncondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcbuildingstorey",&STEP::ObjectHelper<IfcBuildingStorey,1>::Construct ) +, SchemaEntry("ifcworkcontrol",&STEP::ObjectHelper<IfcWorkControl,10>::Construct ) +, SchemaEntry("ifcworkschedule",&STEP::ObjectHelper<IfcWorkSchedule,0>::Construct ) +, SchemaEntry("ifctable",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcductsegmenttype",&STEP::ObjectHelper<IfcDuctSegmentType,1>::Construct ) +, SchemaEntry("ifcstructuralsteelprofileproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdraughtingpredefinedtextfont",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcface",&STEP::ObjectHelper<IfcFace,1>::Construct ) +, SchemaEntry("ifcstructuralsurfacemember",&STEP::ObjectHelper<IfcStructuralSurfaceMember,2>::Construct ) +, SchemaEntry("ifcstructuralsurfacemembervarying",&STEP::ObjectHelper<IfcStructuralSurfaceMemberVarying,2>::Construct ) +, SchemaEntry("ifcfacesurface",&STEP::ObjectHelper<IfcFaceSurface,2>::Construct ) +, SchemaEntry("ifcclassification",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmateriallist",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccostschedule",&STEP::ObjectHelper<IfcCostSchedule,8>::Construct ) +, SchemaEntry("ifccoordinateduniversaltimeoffset",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcplanarextent",&STEP::ObjectHelper<IfcPlanarExtent,2>::Construct ) +, SchemaEntry("ifcplanarbox",&STEP::ObjectHelper<IfcPlanarBox,1>::Construct ) +, SchemaEntry("ifcfillareastyle",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsectionreinforcementproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccolourspecification",&STEP::ObjectHelper<IfcColourSpecification,1>::Construct ) +, SchemaEntry("ifcvector",&STEP::ObjectHelper<IfcVector,2>::Construct ) +, SchemaEntry("ifcbeam",&STEP::ObjectHelper<IfcBeam,0>::Construct ) +, SchemaEntry("ifccolourrgb",&STEP::ObjectHelper<IfcColourRgb,3>::Construct ) +, SchemaEntry("ifcstructuralplanaraction",&STEP::ObjectHelper<IfcStructuralPlanarAction,1>::Construct ) +, SchemaEntry("ifcstructuralplanaractionvarying",&STEP::ObjectHelper<IfcStructuralPlanarActionVarying,2>::Construct ) +, SchemaEntry("ifcsite",&STEP::ObjectHelper<IfcSite,5>::Construct ) +, SchemaEntry("ifcdiscreteaccessorytype",&STEP::ObjectHelper<IfcDiscreteAccessoryType,0>::Construct ) +, SchemaEntry("ifcvibrationisolatortype",&STEP::ObjectHelper<IfcVibrationIsolatorType,1>::Construct ) +, SchemaEntry("ifcevaporativecoolertype",&STEP::ObjectHelper<IfcEvaporativeCoolerType,1>::Construct ) +, SchemaEntry("ifcdistributionchamberelementtype",&STEP::ObjectHelper<IfcDistributionChamberElementType,1>::Construct ) +, SchemaEntry("ifcfeatureelementaddition",&STEP::ObjectHelper<IfcFeatureElementAddition,0>::Construct ) +, SchemaEntry("ifcrelassignstoresource",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructureddimensioncallout",&STEP::ObjectHelper<IfcStructuredDimensionCallout,0>::Construct ) +, SchemaEntry("ifccoolingtowertype",&STEP::ObjectHelper<IfcCoolingTowerType,1>::Construct ) +, SchemaEntry("ifccenterlineprofiledef",&STEP::ObjectHelper<IfcCenterLineProfileDef,1>::Construct ) +, SchemaEntry("ifctexturevertex",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcorganization",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcwindowstyle",&STEP::ObjectHelper<IfcWindowStyle,4>::Construct ) +, SchemaEntry("ifclightsourcegoniometric",&STEP::ObjectHelper<IfcLightSourceGoniometric,6>::Construct ) +, SchemaEntry("ifcribplateprofileproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctransformertype",&STEP::ObjectHelper<IfcTransformerType,1>::Construct ) +, SchemaEntry("ifcmembertype",&STEP::ObjectHelper<IfcMemberType,1>::Construct ) +, SchemaEntry("ifcsurfaceoflinearextrusion",&STEP::ObjectHelper<IfcSurfaceOfLinearExtrusion,2>::Construct ) +, SchemaEntry("ifcmotorconnectiontype",&STEP::ObjectHelper<IfcMotorConnectionType,1>::Construct ) +, SchemaEntry("ifcflowtreatmentdevicetype",&STEP::ObjectHelper<IfcFlowTreatmentDeviceType,0>::Construct ) +, SchemaEntry("ifcductsilencertype",&STEP::ObjectHelper<IfcDuctSilencerType,1>::Construct ) +, SchemaEntry("ifcwindowliningproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcfurnishingelementtype",&STEP::ObjectHelper<IfcFurnishingElementType,0>::Construct ) +, SchemaEntry("ifcsystemfurnitureelementtype",&STEP::ObjectHelper<IfcSystemFurnitureElementType,0>::Construct ) +, SchemaEntry("ifcconnectionpointeccentricity",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcwasteterminaltype",&STEP::ObjectHelper<IfcWasteTerminalType,1>::Construct ) +, SchemaEntry("ifcbsplinecurve",&STEP::ObjectHelper<IfcBSplineCurve,5>::Construct ) +, SchemaEntry("ifcbeziercurve",&STEP::ObjectHelper<IfcBezierCurve,0>::Construct ) +, SchemaEntry("ifcdocumentinformationrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcactuatortype",&STEP::ObjectHelper<IfcActuatorType,1>::Construct ) +, SchemaEntry("ifcdistributioncontrolelement",&STEP::ObjectHelper<IfcDistributionControlElement,1>::Construct ) +, SchemaEntry("ifcannotation",&STEP::ObjectHelper<IfcAnnotation,0>::Construct ) +, SchemaEntry("ifcrelassociatesdocument",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdoorliningproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcshellbasedsurfacemodel",&STEP::ObjectHelper<IfcShellBasedSurfaceModel,1>::Construct ) +, SchemaEntry("ifcactionrequest",&STEP::ObjectHelper<IfcActionRequest,1>::Construct ) +, SchemaEntry("ifcextrudedareasolid",&STEP::ObjectHelper<IfcExtrudedAreaSolid,2>::Construct ) +, SchemaEntry("ifcsystem",&STEP::ObjectHelper<IfcSystem,0>::Construct ) +, SchemaEntry("ifcfillareastylehatching",&STEP::ObjectHelper<IfcFillAreaStyleHatching,5>::Construct ) +, SchemaEntry("ifcrelvoidselement",&STEP::ObjectHelper<IfcRelVoidsElement,2>::Construct ) +, SchemaEntry("ifcrelconnectspathelements",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelspaceboundary",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsurfacecurvesweptareasolid",&STEP::ObjectHelper<IfcSurfaceCurveSweptAreaSolid,4>::Construct ) +, SchemaEntry("ifccartesiantransformationoperator3dnonuniform",&STEP::ObjectHelper<IfcCartesianTransformationOperator3DnonUniform,2>::Construct ) +, SchemaEntry("ifcrelinteractionrequirements",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccurtainwalltype",&STEP::ObjectHelper<IfcCurtainWallType,1>::Construct ) +, SchemaEntry("ifcquantitylength",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcequipmentstandard",&STEP::ObjectHelper<IfcEquipmentStandard,0>::Construct ) +, SchemaEntry("ifcflowstoragedevicetype",&STEP::ObjectHelper<IfcFlowStorageDeviceType,0>::Construct ) +, SchemaEntry("ifcvirtualgridintersection",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdiameterdimension",&STEP::ObjectHelper<IfcDiameterDimension,0>::Construct ) +, SchemaEntry("ifcswitchingdevicetype",&STEP::ObjectHelper<IfcSwitchingDeviceType,1>::Construct ) +, SchemaEntry("ifcaddress",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctelecomaddress",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcwindow",&STEP::ObjectHelper<IfcWindow,2>::Construct ) +, SchemaEntry("ifcmechanicalsteelmaterialproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcflowtreatmentdevice",&STEP::ObjectHelper<IfcFlowTreatmentDevice,0>::Construct ) +, SchemaEntry("ifcrelservicesbuildings",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcchillertype",&STEP::ObjectHelper<IfcChillerType,1>::Construct ) +, SchemaEntry("ifcrelassignstoproduct",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrectanglehollowprofiledef",&STEP::ObjectHelper<IfcRectangleHollowProfileDef,3>::Construct ) +, SchemaEntry("ifcenergyproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcboxedhalfspace",&STEP::ObjectHelper<IfcBoxedHalfSpace,1>::Construct ) +, SchemaEntry("ifcaxis2placement2d",&STEP::ObjectHelper<IfcAxis2Placement2D,1>::Construct ) +, SchemaEntry("ifcspaceprogram",&STEP::ObjectHelper<IfcSpaceProgram,5>::Construct ) +, SchemaEntry("ifcpoint",&STEP::ObjectHelper<IfcPoint,0>::Construct ) +, SchemaEntry("ifccartesianpoint",&STEP::ObjectHelper<IfcCartesianPoint,1>::Construct ) +, SchemaEntry("ifcboundedsurface",&STEP::ObjectHelper<IfcBoundedSurface,0>::Construct ) +, SchemaEntry("ifcloop",&STEP::ObjectHelper<IfcLoop,0>::Construct ) +, SchemaEntry("ifcpolyloop",&STEP::ObjectHelper<IfcPolyLoop,1>::Construct ) +, SchemaEntry("ifcpredefinedpointmarkersymbol",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcterminatorsymbol",&STEP::ObjectHelper<IfcTerminatorSymbol,1>::Construct ) +, SchemaEntry("ifcdimensioncurveterminator",&STEP::ObjectHelper<IfcDimensionCurveTerminator,1>::Construct ) +, SchemaEntry("ifcrelprojectselement",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctrapeziumprofiledef",&STEP::ObjectHelper<IfcTrapeziumProfileDef,4>::Construct ) +, SchemaEntry("ifcrepresentationcontext",&STEP::ObjectHelper<IfcRepresentationContext,2>::Construct ) +, SchemaEntry("ifcgeometricrepresentationcontext",&STEP::ObjectHelper<IfcGeometricRepresentationContext,4>::Construct ) +, SchemaEntry("ifctextstylewithboxcharacteristics",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccurveboundedplane",&STEP::ObjectHelper<IfcCurveBoundedPlane,3>::Construct ) +, SchemaEntry("ifcquantitycount",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctimeseriesreferencerelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadtemperature",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsiunit",&STEP::ObjectHelper<IfcSIUnit,2>::Construct ) +, SchemaEntry("ifcstructuralreaction",&STEP::ObjectHelper<IfcStructuralReaction,0>::Construct ) +, SchemaEntry("ifcstructuralpointreaction",&STEP::ObjectHelper<IfcStructuralPointReaction,0>::Construct ) +, SchemaEntry("ifcaxis1placement",&STEP::ObjectHelper<IfcAxis1Placement,1>::Construct ) +, SchemaEntry("ifcreinforcementdefinitionproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcelectricappliancetype",&STEP::ObjectHelper<IfcElectricApplianceType,1>::Construct ) +, SchemaEntry("ifcsensortype",&STEP::ObjectHelper<IfcSensorType,1>::Construct ) +, SchemaEntry("ifcfurnishingelement",&STEP::ObjectHelper<IfcFurnishingElement,0>::Construct ) +, SchemaEntry("ifcprotectivedevicetype",&STEP::ObjectHelper<IfcProtectiveDeviceType,1>::Construct ) +, SchemaEntry("ifczshapeprofiledef",&STEP::ObjectHelper<IfcZShapeProfileDef,6>::Construct ) +, SchemaEntry("ifcscheduletimecontrol",&STEP::ObjectHelper<IfcScheduleTimeControl,18>::Construct ) +, SchemaEntry("ifcrepresentationmap",&STEP::ObjectHelper<IfcRepresentationMap,2>::Construct ) +, SchemaEntry("ifcclosedshell",&STEP::ObjectHelper<IfcClosedShell,0>::Construct ) +, SchemaEntry("ifcbuildingelementpart",&STEP::ObjectHelper<IfcBuildingElementPart,0>::Construct ) +, SchemaEntry("ifcdraughtingpredefinedcolour",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpostaladdress",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcblock",&STEP::ObjectHelper<IfcBlock,3>::Construct ) +, SchemaEntry("ifclightfixturetype",&STEP::ObjectHelper<IfcLightFixtureType,1>::Construct ) +, SchemaEntry("ifcopeningelement",&STEP::ObjectHelper<IfcOpeningElement,0>::Construct ) +, SchemaEntry("ifclightsourcespot",&STEP::ObjectHelper<IfcLightSourceSpot,4>::Construct ) +, SchemaEntry("ifctendonanchor",&STEP::ObjectHelper<IfcTendonAnchor,0>::Construct ) +, SchemaEntry("ifcsurfacestylerefraction",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcelectricflowstoragedevicetype",&STEP::ObjectHelper<IfcElectricFlowStorageDeviceType,1>::Construct ) +, SchemaEntry("ifcfluidflowproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsphere",&STEP::ObjectHelper<IfcSphere,1>::Construct ) +, SchemaEntry("ifcrelassociatesappliedvalue",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdampertype",&STEP::ObjectHelper<IfcDamperType,1>::Construct ) +, SchemaEntry("ifcprojectorderrecord",&STEP::ObjectHelper<IfcProjectOrderRecord,2>::Construct ) +, SchemaEntry("ifcdimensionalexponents",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcreldefinesbytype",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdistributionchamberelement",&STEP::ObjectHelper<IfcDistributionChamberElement,0>::Construct ) +, SchemaEntry("ifcmechanicalfastener",&STEP::ObjectHelper<IfcMechanicalFastener,2>::Construct ) +, SchemaEntry("ifcquantityvolume",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrectangulartrimmedsurface",&STEP::ObjectHelper<IfcRectangularTrimmedSurface,7>::Construct ) +, SchemaEntry("ifcdateandtime",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifczone",&STEP::ObjectHelper<IfcZone,0>::Construct ) +, SchemaEntry("ifcfantype",&STEP::ObjectHelper<IfcFanType,1>::Construct ) +, SchemaEntry("ifcgeometricset",&STEP::ObjectHelper<IfcGeometricSet,1>::Construct ) +, SchemaEntry("ifcfillareastyletiles",&STEP::ObjectHelper<IfcFillAreaStyleTiles,3>::Construct ) +, SchemaEntry("ifcpixeltexture",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccablesegmenttype",&STEP::ObjectHelper<IfcCableSegmentType,1>::Construct ) +, SchemaEntry("ifcreloverridesproperties",&STEP::ObjectHelper<IfcRelOverridesProperties,1>::Construct ) +, SchemaEntry("ifcmeasurewithunit",&STEP::ObjectHelper<IfcMeasureWithUnit,2>::Construct ) +, SchemaEntry("ifcslabtype",&STEP::ObjectHelper<IfcSlabType,1>::Construct ) +, SchemaEntry("ifcservicelife",&STEP::ObjectHelper<IfcServiceLife,2>::Construct ) +, SchemaEntry("ifcfurnituretype",&STEP::ObjectHelper<IfcFurnitureType,1>::Construct ) +, SchemaEntry("ifccostitem",&STEP::ObjectHelper<IfcCostItem,0>::Construct ) +, SchemaEntry("ifcreinforcingmesh",&STEP::ObjectHelper<IfcReinforcingMesh,8>::Construct ) +, SchemaEntry("ifcextendedmaterialproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcactorrole",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcfacetedbrepwithvoids",&STEP::ObjectHelper<IfcFacetedBrepWithVoids,1>::Construct ) +, SchemaEntry("ifcconstraintaggregationrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcgasterminaltype",&STEP::ObjectHelper<IfcGasTerminalType,1>::Construct ) +, SchemaEntry("ifcrelconnectswitheccentricity",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpile",&STEP::ObjectHelper<IfcPile,2>::Construct ) +, SchemaEntry("ifcfillareastyletilesymbolwithstyle",&STEP::ObjectHelper<IfcFillAreaStyleTileSymbolWithStyle,1>::Construct ) +, SchemaEntry("ifcelectricalbaseproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconstructionmaterialresource",&STEP::ObjectHelper<IfcConstructionMaterialResource,2>::Construct ) +, SchemaEntry("ifcannotationcurveoccurrence",&STEP::ObjectHelper<IfcAnnotationCurveOccurrence,0>::Construct ) +, SchemaEntry("ifcdimensioncurve",&STEP::ObjectHelper<IfcDimensionCurve,0>::Construct ) +, SchemaEntry("ifcgeometriccurveset",&STEP::ObjectHelper<IfcGeometricCurveSet,0>::Construct ) +, SchemaEntry("ifcrelaggregates",&STEP::ObjectHelper<IfcRelAggregates,0>::Construct ) +, SchemaEntry("ifcfacebasedsurfacemodel",&STEP::ObjectHelper<IfcFaceBasedSurfaceModel,1>::Construct ) +, SchemaEntry("ifcenergyconversiondevice",&STEP::ObjectHelper<IfcEnergyConversionDevice,0>::Construct ) +, SchemaEntry("ifcrampflight",&STEP::ObjectHelper<IfcRampFlight,0>::Construct ) +, SchemaEntry("ifcpropertyenumeration",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcvertexloop",&STEP::ObjectHelper<IfcVertexLoop,1>::Construct ) +, SchemaEntry("ifcplate",&STEP::ObjectHelper<IfcPlate,0>::Construct ) +, SchemaEntry("ifcushapeprofiledef",&STEP::ObjectHelper<IfcUShapeProfileDef,8>::Construct ) +, SchemaEntry("ifchygroscopicmaterialproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcfacebound",&STEP::ObjectHelper<IfcFaceBound,2>::Construct ) +, SchemaEntry("ifcfaceouterbound",&STEP::ObjectHelper<IfcFaceOuterBound,0>::Construct ) +, SchemaEntry("ifconedirectionrepeatfactor",&STEP::ObjectHelper<IfcOneDirectionRepeatFactor,1>::Construct ) +, SchemaEntry("ifcboilertype",&STEP::ObjectHelper<IfcBoilerType,1>::Construct ) +, SchemaEntry("ifcconstructionequipmentresource",&STEP::ObjectHelper<IfcConstructionEquipmentResource,0>::Construct ) +, SchemaEntry("ifccomplexproperty",&STEP::ObjectHelper<IfcComplexProperty,2>::Construct ) +, SchemaEntry("ifcfooting",&STEP::ObjectHelper<IfcFooting,1>::Construct ) +, SchemaEntry("ifcopticalmaterialproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconstructionproductresource",&STEP::ObjectHelper<IfcConstructionProductResource,0>::Construct ) +, SchemaEntry("ifcboundaryedgecondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcderivedprofiledef",&STEP::ObjectHelper<IfcDerivedProfileDef,3>::Construct ) +, SchemaEntry("ifcpropertytablevalue",&STEP::ObjectHelper<IfcPropertyTableValue,5>::Construct ) +, SchemaEntry("ifcrelassignstogroup",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcflowmetertype",&STEP::ObjectHelper<IfcFlowMeterType,1>::Construct ) +, SchemaEntry("ifcdoorstyle",&STEP::ObjectHelper<IfcDoorStyle,4>::Construct ) +, SchemaEntry("ifcrelconnectsporttoelement",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassociatesclassification",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcunitassignment",&STEP::ObjectHelper<IfcUnitAssignment,1>::Construct ) +, SchemaEntry("ifcflowterminal",&STEP::ObjectHelper<IfcFlowTerminal,0>::Construct ) +, SchemaEntry("ifccranerailfshapeprofiledef",&STEP::ObjectHelper<IfcCraneRailFShapeProfileDef,9>::Construct ) +, SchemaEntry("ifcflowsegment",&STEP::ObjectHelper<IfcFlowSegment,0>::Construct ) +, SchemaEntry("ifcelementquantity",&STEP::ObjectHelper<IfcElementQuantity,2>::Construct ) +, SchemaEntry("ifcboundarynodecondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcboundarynodeconditionwarping",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccurtainwall",&STEP::ObjectHelper<IfcCurtainWall,0>::Construct ) +, SchemaEntry("ifcdiscreteaccessory",&STEP::ObjectHelper<IfcDiscreteAccessory,0>::Construct ) +, SchemaEntry("ifcgrid",&STEP::ObjectHelper<IfcGrid,3>::Construct ) +, SchemaEntry("ifcsanitaryterminaltype",&STEP::ObjectHelper<IfcSanitaryTerminalType,1>::Construct ) +, SchemaEntry("ifcsoundproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsubedge",&STEP::ObjectHelper<IfcSubedge,1>::Construct ) +, SchemaEntry("ifctextstyletextmodel",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcfiltertype",&STEP::ObjectHelper<IfcFilterType,1>::Construct ) +, SchemaEntry("ifcsymbolstyle",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctendon",&STEP::ObjectHelper<IfcTendon,8>::Construct ) +, SchemaEntry("ifcdimensionpair",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadgroup",&STEP::ObjectHelper<IfcStructuralLoadGroup,5>::Construct ) +, SchemaEntry("ifcpresentationstyleassignment",&STEP::ObjectHelper<IfcPresentationStyleAssignment,1>::Construct ) +, SchemaEntry("ifcregulartimeseries",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralcurvemember",&STEP::ObjectHelper<IfcStructuralCurveMember,1>::Construct ) +, SchemaEntry("ifclightsourceambient",&STEP::ObjectHelper<IfcLightSourceAmbient,0>::Construct ) +, SchemaEntry("ifccondition",&STEP::ObjectHelper<IfcCondition,0>::Construct ) +, SchemaEntry("ifcport",&STEP::ObjectHelper<IfcPort,0>::Construct ) +, SchemaEntry("ifcspace",&STEP::ObjectHelper<IfcSpace,2>::Construct ) +, SchemaEntry("ifcheatexchangertype",&STEP::ObjectHelper<IfcHeatExchangerType,1>::Construct ) +, SchemaEntry("ifctanktype",&STEP::ObjectHelper<IfcTankType,1>::Construct ) +, SchemaEntry("ifcinventory",&STEP::ObjectHelper<IfcInventory,6>::Construct ) +, SchemaEntry("ifctextstyle",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcappliedvaluerelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsoundvalue",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctransportelementtype",&STEP::ObjectHelper<IfcTransportElementType,1>::Construct ) +, SchemaEntry("ifcairtoairheatrecoverytype",&STEP::ObjectHelper<IfcAirToAirHeatRecoveryType,1>::Construct ) +, SchemaEntry("ifcstairflight",&STEP::ObjectHelper<IfcStairFlight,4>::Construct ) +, SchemaEntry("ifcelectricalelement",&STEP::ObjectHelper<IfcElectricalElement,0>::Construct ) +, SchemaEntry("ifclightintensitydistribution",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcclassificationreference",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsurfacestylewithtextures",&STEP::ObjectHelper<IfcSurfaceStyleWithTextures,1>::Construct ) +, SchemaEntry("ifcboundingbox",&STEP::ObjectHelper<IfcBoundingBox,4>::Construct ) +, SchemaEntry("ifcapplication",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcwalltype",&STEP::ObjectHelper<IfcWallType,1>::Construct ) +, SchemaEntry("ifcmove",&STEP::ObjectHelper<IfcMove,3>::Construct ) +, SchemaEntry("ifccircle",&STEP::ObjectHelper<IfcCircle,1>::Construct ) +, SchemaEntry("ifcoffsetcurve2d",&STEP::ObjectHelper<IfcOffsetCurve2D,3>::Construct ) +, SchemaEntry("ifcmateriallayersetusage",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpointoncurve",&STEP::ObjectHelper<IfcPointOnCurve,2>::Construct ) +, SchemaEntry("ifcstructuralresultgroup",&STEP::ObjectHelper<IfcStructuralResultGroup,3>::Construct ) +, SchemaEntry("ifcsectionedspine",&STEP::ObjectHelper<IfcSectionedSpine,3>::Construct ) +, SchemaEntry("ifcslab",&STEP::ObjectHelper<IfcSlab,1>::Construct ) +, SchemaEntry("ifcconnectionportgeometry",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcquantityweight",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassociatesmaterial",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcvertex",&STEP::ObjectHelper<IfcVertex,0>::Construct ) +, SchemaEntry("ifcvertexpoint",&STEP::ObjectHelper<IfcVertexPoint,1>::Construct ) +, SchemaEntry("ifcreferencesvaluedocument",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpersonandorganization",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelflowcontrolelements",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassignstoprocess",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructurallinearaction",&STEP::ObjectHelper<IfcStructuralLinearAction,1>::Construct ) +, SchemaEntry("ifcstructurallinearactionvarying",&STEP::ObjectHelper<IfcStructuralLinearActionVarying,2>::Construct ) +, SchemaEntry("ifcbuildingelementproxytype",&STEP::ObjectHelper<IfcBuildingElementProxyType,1>::Construct ) +, SchemaEntry("ifcprojectionelement",&STEP::ObjectHelper<IfcProjectionElement,0>::Construct ) +, SchemaEntry("ifcderivedunit",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcapprovalactorrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconversionbasedunit",&STEP::ObjectHelper<IfcConversionBasedUnit,2>::Construct ) +, SchemaEntry("ifcmaterial",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcgeometricrepresentationsubcontext",&STEP::ObjectHelper<IfcGeometricRepresentationSubContext,4>::Construct ) +, SchemaEntry("ifcannotationsurfaceoccurrence",&STEP::ObjectHelper<IfcAnnotationSurfaceOccurrence,0>::Construct ) +, SchemaEntry("ifcpredefineddimensionsymbol",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcroundededgefeature",&STEP::ObjectHelper<IfcRoundedEdgeFeature,1>::Construct ) +, SchemaEntry("ifcrelcoversbldgelements",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcelectricdistributionpoint",&STEP::ObjectHelper<IfcElectricDistributionPoint,2>::Construct ) +, SchemaEntry("ifccablecarriersegmenttype",&STEP::ObjectHelper<IfcCableCarrierSegmentType,1>::Construct ) +, SchemaEntry("ifcstructuralloadlinearforce",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcgridaxis",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcirregulartimeseriesvalue",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcwallstandardcase",&STEP::ObjectHelper<IfcWallStandardCase,0>::Construct ) +, SchemaEntry("ifcreloccupiesspaces",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcderivedunitelement",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccsgsolid",&STEP::ObjectHelper<IfcCsgSolid,1>::Construct ) +, SchemaEntry("ifcbeamtype",&STEP::ObjectHelper<IfcBeamType,1>::Construct ) +, SchemaEntry("ifcannotationfillarea",&STEP::ObjectHelper<IfcAnnotationFillArea,2>::Construct ) +, SchemaEntry("ifcrelaxation",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralcurvemembervarying",&STEP::ObjectHelper<IfcStructuralCurveMemberVarying,0>::Construct ) +, SchemaEntry("ifcpointonsurface",&STEP::ObjectHelper<IfcPointOnSurface,3>::Construct ) +, SchemaEntry("ifcpropertydependencyrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcvertexbasedtexturemap",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcorderaction",&STEP::ObjectHelper<IfcOrderAction,1>::Construct ) +, SchemaEntry("ifclibraryreference",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcedgeloop",&STEP::ObjectHelper<IfcEdgeLoop,1>::Construct ) +, SchemaEntry("ifcannotationfillareaoccurrence",&STEP::ObjectHelper<IfcAnnotationFillAreaOccurrence,2>::Construct ) +, SchemaEntry("ifcrelconnectsstructuralelement",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcworkplan",&STEP::ObjectHelper<IfcWorkPlan,0>::Construct ) +, SchemaEntry("ifcellipse",&STEP::ObjectHelper<IfcEllipse,2>::Construct ) +, SchemaEntry("ifcproductdefinitionshape",&STEP::ObjectHelper<IfcProductDefinitionShape,0>::Construct ) +, SchemaEntry("ifcprojectioncurve",&STEP::ObjectHelper<IfcProjectionCurve,0>::Construct ) +, SchemaEntry("ifcelectricalcircuit",&STEP::ObjectHelper<IfcElectricalCircuit,0>::Construct ) +, SchemaEntry("ifcrationalbeziercurve",&STEP::ObjectHelper<IfcRationalBezierCurve,1>::Construct ) +, SchemaEntry("ifcstructuralpointaction",&STEP::ObjectHelper<IfcStructuralPointAction,0>::Construct ) +, SchemaEntry("ifcservicelifefactor",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcthermalmaterialproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctexturecoordinategenerator",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpipesegmenttype",&STEP::ObjectHelper<IfcPipeSegmentType,1>::Construct ) +, SchemaEntry("ifctwodirectionrepeatfactor",&STEP::ObjectHelper<IfcTwoDirectionRepeatFactor,1>::Construct ) +, SchemaEntry("ifcshaperepresentation",&STEP::ObjectHelper<IfcShapeRepresentation,0>::Construct ) +, SchemaEntry("ifcpropertyset",&STEP::ObjectHelper<IfcPropertySet,1>::Construct ) +, SchemaEntry("ifcsurfacestylerendering",&STEP::ObjectHelper<IfcSurfaceStyleRendering,8>::Construct ) +, SchemaEntry("ifcdistributionport",&STEP::ObjectHelper<IfcDistributionPort,1>::Construct ) +, SchemaEntry("ifcimagetexture",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpipefittingtype",&STEP::ObjectHelper<IfcPipeFittingType,1>::Construct ) +, SchemaEntry("ifctransportelement",&STEP::ObjectHelper<IfcTransportElement,3>::Construct ) +, SchemaEntry("ifcannotationtextoccurrence",&STEP::ObjectHelper<IfcAnnotationTextOccurrence,0>::Construct ) +, SchemaEntry("ifcconnectionsurfacegeometry",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralanalysismodel",&STEP::ObjectHelper<IfcStructuralAnalysisModel,4>::Construct ) +, SchemaEntry("ifcconnectioncurvegeometry",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconditioncriterion",&STEP::ObjectHelper<IfcConditionCriterion,2>::Construct ) +, SchemaEntry("ifcwaterproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmateriallayer",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccostvalue",&STEP::ObjectHelper<NotImplemented,0>::Construct ) + + }; +} + +// ----------------------------------------------------------------------------------------------------------- +void IFC::Schema_2x3::GetSchema(EXPRESS::ConversionSchema& out) { + out = EXPRESS::ConversionSchema(schema_raw_2x3); +} + +namespace STEP { + +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<NotImplemented>(const STEP::DB& /*db*/, const LIST& /*params*/, NotImplemented* /*in*/) +{ + return 0; +} + +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRoot>(const DB& db, const LIST& params, IfcRoot* in) +{ + size_t base = 0; + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRoot"); } do { // convert the 'GlobalId' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRoot,4>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->GlobalId, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRoot to be a `IfcGloballyUniqueId`")); } + } while(0); + do { // convert the 'OwnerHistory' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRoot,4>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->OwnerHistory, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRoot to be a `IfcOwnerHistory`")); } + } while(0); + do { // convert the 'Name' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRoot,4>::aux_is_derived[2]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRoot to be a `IfcLabel`")); } + } while(0); + do { // convert the 'Description' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRoot,4>::aux_is_derived[3]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Description, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRoot to be a `IfcText`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcObjectDefinition>(const DB& db, const LIST& params, IfcObjectDefinition* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRoot*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcObjectDefinition"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTypeObject>(const DB& db, const LIST& params, IfcTypeObject* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObjectDefinition*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTypeProduct>(const DB& db, const LIST& params, IfcTypeProduct* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElementType>(const DB& db, const LIST& params, IfcElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionElementType>(const DB& db, const LIST& params, IfcDistributionElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionFlowElementType>(const DB& db, const LIST& params, IfcDistributionFlowElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowControllerType>(const DB& db, const LIST& params, IfcFlowControllerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricTimeControlType>(const DB& db, const LIST& params, IfcElectricTimeControlType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRepresentation>(const DB& db, const LIST& params, IfcRepresentation* in) +{ + size_t base = 0; + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRepresentation"); } do { // convert the 'ContextOfItems' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRepresentation,4>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->ContextOfItems, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentation to be a `IfcRepresentationContext`")); } + } while(0); + do { // convert the 'RepresentationIdentifier' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRepresentation,4>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RepresentationIdentifier, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentation to be a `IfcLabel`")); } + } while(0); + do { // convert the 'RepresentationType' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRepresentation,4>::aux_is_derived[2]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RepresentationType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRepresentation to be a `IfcLabel`")); } + } while(0); + do { // convert the 'Items' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRepresentation,4>::aux_is_derived[3]=true; break; } + try { GenericConvert( in->Items, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRepresentation to be a `SET [1:?] OF IfcRepresentationItem`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcShapeModel>(const DB& db, const LIST& params, IfcShapeModel* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentation*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTopologyRepresentation>(const DB& db, const LIST& params, IfcTopologyRepresentation* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcShapeModel*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelationship>(const DB& db, const LIST& params, IfcRelationship* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRoot*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRelationship"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelConnects>(const DB& db, const LIST& params, IfcRelConnects* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelationship*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRelConnects"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowFittingType>(const DB& db, const LIST& params, IfcFlowFittingType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCableCarrierFittingType>(const DB& db, const LIST& params, IfcCableCarrierFittingType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFittingType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEnergyConversionDeviceType>(const DB& db, const LIST& params, IfcEnergyConversionDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCoilType>(const DB& db, const LIST& params, IfcCoilType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcObject>(const DB& db, const LIST& params, IfcObject* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObjectDefinition*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcObject"); } do { // convert the 'ObjectType' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcObject,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ObjectType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcObject to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcControl>(const DB& db, const LIST& params, IfcControl* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPerformanceHistory>(const DB& db, const LIST& params, IfcPerformanceHistory* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRepresentationItem>(const DB& /*db*/, const LIST& /*params*/, IfcRepresentationItem* /*in*/) +{ + size_t base = 0; + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGeometricRepresentationItem>(const DB& db, const LIST& params, IfcGeometricRepresentationItem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTextLiteral>(const DB& db, const LIST& params, IfcTextLiteral* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTextLiteralWithExtent>(const DB& db, const LIST& params, IfcTextLiteralWithExtent* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTextLiteral*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProductRepresentation>(const DB& db, const LIST& params, IfcProductRepresentation* in) +{ + size_t base = 0; + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcProductRepresentation"); } do { // convert the 'Name' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcProductRepresentation,3>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProductRepresentation to be a `IfcLabel`")); } + } while(0); + do { // convert the 'Description' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcProductRepresentation,3>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Description, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProductRepresentation to be a `IfcText`")); } + } while(0); + do { // convert the 'Representations' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcProductRepresentation,3>::aux_is_derived[2]=true; break; } + try { GenericConvert( in->Representations, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcProductRepresentation to be a `LIST [1:?] OF IfcRepresentation`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProduct>(const DB& db, const LIST& params, IfcProduct* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObject*>(in)); + if (params.GetSize() < 7) { throw STEP::TypeError("expected 7 arguments to IfcProduct"); } do { // convert the 'ObjectPlacement' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcProduct,2>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ObjectPlacement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcProduct to be a `IfcObjectPlacement`")); } + } while(0); + do { // convert the 'Representation' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcProduct,2>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Representation, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcProduct to be a `IfcProductRepresentation`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElement>(const DB& db, const LIST& params, IfcElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcElement"); } do { // convert the 'Tag' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcElement,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Tag, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcElement to be a `IfcIdentifier`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionElement>(const DB& db, const LIST& params, IfcDistributionElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionFlowElement>(const DB& db, const LIST& params, IfcDistributionFlowElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCurve>(const DB& db, const LIST& params, IfcCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBoundedCurve>(const DB& db, const LIST& params, IfcBoundedCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCurve*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCompositeCurve>(const DB& db, const LIST& params, IfcCompositeCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedCurve*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcCompositeCurve"); } do { // convert the 'Segments' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcCompositeCurve,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Segments, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCompositeCurve to be a `LIST [1:?] OF IfcCompositeCurveSegment`")); } + } while(0); + do { // convert the 'SelfIntersect' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcCompositeCurve,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->SelfIntersect, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCompositeCurve to be a `LOGICAL`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<Ifc2DCompositeCurve>(const DB& db, const LIST& params, Ifc2DCompositeCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCompositeCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianTransformationOperator>(const DB& db, const LIST& params, IfcCartesianTransformationOperator* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcCartesianTransformationOperator"); } do { // convert the 'Axis1' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcCartesianTransformationOperator,4>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Axis1, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCartesianTransformationOperator to be a `IfcDirection`")); } + } while(0); + do { // convert the 'Axis2' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcCartesianTransformationOperator,4>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Axis2, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCartesianTransformationOperator to be a `IfcDirection`")); } + } while(0); + do { // convert the 'LocalOrigin' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcCartesianTransformationOperator,4>::aux_is_derived[2]=true; break; } + try { GenericConvert( in->LocalOrigin, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcCartesianTransformationOperator to be a `IfcCartesianPoint`")); } + } while(0); + do { // convert the 'Scale' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcCartesianTransformationOperator,4>::aux_is_derived[3]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Scale, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcCartesianTransformationOperator to be a `REAL`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianTransformationOperator3D>(const DB& db, const LIST& params, IfcCartesianTransformationOperator3D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCartesianTransformationOperator*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcCartesianTransformationOperator3D"); } do { // convert the 'Axis3' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcCartesianTransformationOperator3D,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Axis3, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcCartesianTransformationOperator3D to be a `IfcDirection`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProperty>(const DB& db, const LIST& params, IfcProperty* in) +{ + size_t base = 0; + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcProperty"); } do { // convert the 'Name' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcProperty,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProperty to be a `IfcIdentifier`")); } + } while(0); + do { // convert the 'Description' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcProperty,2>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Description, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProperty to be a `IfcText`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSimpleProperty>(const DB& db, const LIST& params, IfcSimpleProperty* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProperty*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcSimpleProperty"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertyEnumeratedValue>(const DB& db, const LIST& params, IfcPropertyEnumeratedValue* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSimpleProperty*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingElementType>(const DB& db, const LIST& params, IfcBuildingElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStairFlightType>(const DB& db, const LIST& params, IfcStairFlightType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurface>(const DB& db, const LIST& params, IfcSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElementarySurface>(const DB& db, const LIST& params, IfcElementarySurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSurface*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcElementarySurface"); } do { // convert the 'Position' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcElementarySurface,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Position, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcElementarySurface to be a `IfcAxis2Placement3D`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPlane>(const DB& db, const LIST& params, IfcPlane* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementarySurface*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPlane"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBooleanResult>(const DB& db, const LIST& params, IfcBooleanResult* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcBooleanResult"); } do { // convert the 'Operator' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcBooleanResult,3>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Operator, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBooleanResult to be a `IfcBooleanOperator`")); } + } while(0); + do { // convert the 'FirstOperand' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcBooleanResult,3>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->FirstOperand, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBooleanResult to be a `IfcBooleanOperand`")); } + } while(0); + do { // convert the 'SecondOperand' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcBooleanResult,3>::aux_is_derived[2]=true; break; } + try { GenericConvert( in->SecondOperand, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBooleanResult to be a `IfcBooleanOperand`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBooleanClippingResult>(const DB& db, const LIST& params, IfcBooleanClippingResult* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBooleanResult*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcBooleanClippingResult"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSolidModel>(const DB& db, const LIST& params, IfcSolidModel* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcManifoldSolidBrep>(const DB& db, const LIST& params, IfcManifoldSolidBrep* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSolidModel*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcManifoldSolidBrep"); } do { // convert the 'Outer' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcManifoldSolidBrep,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Outer, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcManifoldSolidBrep to be a `IfcClosedShell`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowTerminalType>(const DB& db, const LIST& params, IfcFlowTerminalType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStackTerminalType>(const DB& db, const LIST& params, IfcStackTerminalType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralItem>(const DB& db, const LIST& params, IfcStructuralItem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralConnection>(const DB& db, const LIST& params, IfcStructuralConnection* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralCurveConnection>(const DB& db, const LIST& params, IfcStructuralCurveConnection* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralConnection*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcJunctionBoxType>(const DB& db, const LIST& params, IfcJunctionBoxType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFittingType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertyDefinition>(const DB& db, const LIST& params, IfcPropertyDefinition* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRoot*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPropertyDefinition"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertySetDefinition>(const DB& db, const LIST& params, IfcPropertySetDefinition* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPropertyDefinition*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPropertySetDefinition"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProcess>(const DB& db, const LIST& params, IfcProcess* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTask>(const DB& db, const LIST& params, IfcTask* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProcess*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelFillsElement>(const DB& db, const LIST& params, IfcRelFillsElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelConnects*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelFillsElement"); } do { // convert the 'RelatingOpeningElement' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatingOpeningElement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelFillsElement to be a `IfcOpeningElement`")); } + } while(0); + do { // convert the 'RelatedBuildingElement' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatedBuildingElement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelFillsElement to be a `IfcElement`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProcedure>(const DB& db, const LIST& params, IfcProcedure* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProcess*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProxy>(const DB& db, const LIST& params, IfcProxy* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcResource>(const DB& db, const LIST& params, IfcResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConstructionResource>(const DB& db, const LIST& params, IfcConstructionResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSubContractResource>(const DB& db, const LIST& params, IfcSubContractResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelContainedInSpatialStructure>(const DB& db, const LIST& params, IfcRelContainedInSpatialStructure* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelConnects*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelContainedInSpatialStructure"); } do { // convert the 'RelatedElements' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatedElements, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelContainedInSpatialStructure to be a `SET [1:?] OF IfcProduct`")); } + } while(0); + do { // convert the 'RelatingStructure' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatingStructure, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelContainedInSpatialStructure to be a `IfcSpatialStructureElement`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTopologicalRepresentationItem>(const DB& db, const LIST& params, IfcTopologicalRepresentationItem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEdge>(const DB& db, const LIST& params, IfcEdge* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEdgeCurve>(const DB& db, const LIST& params, IfcEdgeCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEdge*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPlateType>(const DB& db, const LIST& params, IfcPlateType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcObjectPlacement>(const DB& /*db*/, const LIST& /*params*/, IfcObjectPlacement* /*in*/) +{ + size_t base = 0; + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGridPlacement>(const DB& db, const LIST& params, IfcGridPlacement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObjectPlacement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFireSuppressionTerminalType>(const DB& db, const LIST& params, IfcFireSuppressionTerminalType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowStorageDevice>(const DB& db, const LIST& params, IfcFlowStorageDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSweptSurface>(const DB& db, const LIST& params, IfcSweptSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceOfRevolution>(const DB& db, const LIST& params, IfcSurfaceOfRevolution* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSweptSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOrientedEdge>(const DB& db, const LIST& params, IfcOrientedEdge* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEdge*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDirection>(const DB& db, const LIST& params, IfcDirection* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcDirection"); } do { // convert the 'DirectionRatios' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->DirectionRatios, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcDirection to be a `LIST [2:3] OF REAL`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProfileDef>(const DB& db, const LIST& params, IfcProfileDef* in) +{ + size_t base = 0; + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcProfileDef"); } do { // convert the 'ProfileType' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcProfileDef,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->ProfileType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProfileDef to be a `IfcProfileTypeEnum`")); } + } while(0); + do { // convert the 'ProfileName' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcProfileDef,2>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ProfileName, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProfileDef to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcParameterizedProfileDef>(const DB& db, const LIST& params, IfcParameterizedProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProfileDef*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcParameterizedProfileDef"); } do { // convert the 'Position' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcParameterizedProfileDef,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Position, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcParameterizedProfileDef to be a `IfcAxis2Placement2D`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCShapeProfileDef>(const DB& db, const LIST& params, IfcCShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFeatureElement>(const DB& db, const LIST& params, IfcFeatureElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcFeatureElement"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFeatureElementSubtraction>(const DB& db, const LIST& params, IfcFeatureElementSubtraction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFeatureElement*>(in)); + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcFeatureElementSubtraction"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEdgeFeature>(const DB& db, const LIST& params, IfcEdgeFeature* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFeatureElementSubtraction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcChamferEdgeFeature>(const DB& db, const LIST& params, IfcChamferEdgeFeature* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEdgeFeature*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingElement>(const DB& db, const LIST& params, IfcBuildingElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcBuildingElement"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcColumn>(const DB& db, const LIST& params, IfcColumn* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertyReferenceValue>(const DB& db, const LIST& params, IfcPropertyReferenceValue* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSimpleProperty*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricMotorType>(const DB& db, const LIST& params, IfcElectricMotorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpatialStructureElementType>(const DB& db, const LIST& params, IfcSpatialStructureElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpaceType>(const DB& db, const LIST& params, IfcSpaceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialStructureElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcColumnType>(const DB& db, const LIST& params, IfcColumnType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCraneRailAShapeProfileDef>(const DB& db, const LIST& params, IfcCraneRailAShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCondenserType>(const DB& db, const LIST& params, IfcCondenserType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCircleProfileDef>(const DB& db, const LIST& params, IfcCircleProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcCircleProfileDef"); } do { // convert the 'Radius' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcCircleProfileDef,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Radius, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcCircleProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCircleHollowProfileDef>(const DB& db, const LIST& params, IfcCircleHollowProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCircleProfileDef*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcCircleHollowProfileDef"); } do { // convert the 'WallThickness' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->WallThickness, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcCircleHollowProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPlacement>(const DB& db, const LIST& params, IfcPlacement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPlacement"); } do { // convert the 'Location' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcPlacement,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Location, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPlacement to be a `IfcCartesianPoint`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAxis2Placement3D>(const DB& db, const LIST& params, IfcAxis2Placement3D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPlacement*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcAxis2Placement3D"); } do { // convert the 'Axis' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Axis, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis2Placement3D to be a `IfcDirection`")); } + } while(0); + do { // convert the 'RefDirection' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RefDirection, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcAxis2Placement3D to be a `IfcDirection`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPresentationStyle>(const DB& db, const LIST& params, IfcPresentationStyle* in) +{ + size_t base = 0; + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPresentationStyle"); } do { // convert the 'Name' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcPresentationStyle,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPresentationStyle to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEquipmentElement>(const DB& db, const LIST& params, IfcEquipmentElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCompositeCurveSegment>(const DB& db, const LIST& params, IfcCompositeCurveSegment* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcCompositeCurveSegment"); } do { // convert the 'Transition' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Transition, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCompositeCurveSegment to be a `IfcTransitionCode`")); } + } while(0); + do { // convert the 'SameSense' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->SameSense, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCompositeCurveSegment to be a `BOOLEAN`")); } + } while(0); + do { // convert the 'ParentCurve' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->ParentCurve, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcCompositeCurveSegment to be a `IfcCurve`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRectangleProfileDef>(const DB& db, const LIST& params, IfcRectangleProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcRectangleProfileDef"); } do { // convert the 'XDim' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRectangleProfileDef,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->XDim, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRectangleProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'YDim' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRectangleProfileDef,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->YDim, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRectangleProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingElementProxy>(const DB& db, const LIST& params, IfcBuildingElementProxy* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionControlElementType>(const DB& db, const LIST& params, IfcDistributionControlElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowInstrumentType>(const DB& db, const LIST& params, IfcFlowInstrumentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDraughtingCallout>(const DB& db, const LIST& params, IfcDraughtingCallout* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDimensionCurveDirectedCallout>(const DB& db, const LIST& params, IfcDimensionCurveDirectedCallout* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDraughtingCallout*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLinearDimension>(const DB& db, const LIST& params, IfcLinearDimension* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDimensionCurveDirectedCallout*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElementAssembly>(const DB& db, const LIST& params, IfcElementAssembly* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCsgPrimitive3D>(const DB& db, const LIST& params, IfcCsgPrimitive3D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRightCircularCone>(const DB& db, const LIST& params, IfcRightCircularCone* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCsgPrimitive3D*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProjectOrder>(const DB& db, const LIST& params, IfcProjectOrder* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLShapeProfileDef>(const DB& db, const LIST& params, IfcLShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAngularDimension>(const DB& db, const LIST& params, IfcAngularDimension* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDimensionCurveDirectedCallout*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLocalPlacement>(const DB& db, const LIST& params, IfcLocalPlacement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObjectPlacement*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcLocalPlacement"); } do { // convert the 'PlacementRelTo' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->PlacementRelTo, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcLocalPlacement to be a `IfcObjectPlacement`")); } + } while(0); + do { // convert the 'RelativePlacement' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelativePlacement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcLocalPlacement to be a `IfcAxis2Placement`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSweptAreaSolid>(const DB& db, const LIST& params, IfcSweptAreaSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSolidModel*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcSweptAreaSolid"); } do { // convert the 'SweptArea' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcSweptAreaSolid,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->SweptArea, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSweptAreaSolid to be a `IfcProfileDef`")); } + } while(0); + do { // convert the 'Position' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcSweptAreaSolid,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->Position, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSweptAreaSolid to be a `IfcAxis2Placement3D`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRevolvedAreaSolid>(const DB& db, const LIST& params, IfcRevolvedAreaSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSweptAreaSolid*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRevolvedAreaSolid"); } do { // convert the 'Axis' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Axis, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRevolvedAreaSolid to be a `IfcAxis1Placement`")); } + } while(0); + do { // convert the 'Angle' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Angle, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRevolvedAreaSolid to be a `IfcPlaneAngleMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralSurfaceConnection>(const DB& db, const LIST& params, IfcStructuralSurfaceConnection* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralConnection*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRadiusDimension>(const DB& db, const LIST& params, IfcRadiusDimension* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDimensionCurveDirectedCallout*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSweptDiskSolid>(const DB& db, const LIST& params, IfcSweptDiskSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSolidModel*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcSweptDiskSolid"); } do { // convert the 'Directrix' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Directrix, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSweptDiskSolid to be a `IfcCurve`")); } + } while(0); + do { // convert the 'Radius' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Radius, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSweptDiskSolid to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'InnerRadius' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->InnerRadius, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSweptDiskSolid to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'StartParam' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->StartParam, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSweptDiskSolid to be a `IfcParameterValue`")); } + } while(0); + do { // convert the 'EndParam' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->EndParam, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcSweptDiskSolid to be a `IfcParameterValue`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcHalfSpaceSolid>(const DB& db, const LIST& params, IfcHalfSpaceSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcHalfSpaceSolid"); } do { // convert the 'BaseSurface' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcHalfSpaceSolid,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->BaseSurface, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcHalfSpaceSolid to be a `IfcSurface`")); } + } while(0); + do { // convert the 'AgreementFlag' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcHalfSpaceSolid,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->AgreementFlag, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcHalfSpaceSolid to be a `BOOLEAN`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPolygonalBoundedHalfSpace>(const DB& db, const LIST& params, IfcPolygonalBoundedHalfSpace* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcHalfSpaceSolid*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPolygonalBoundedHalfSpace"); } do { // convert the 'Position' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Position, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPolygonalBoundedHalfSpace to be a `IfcAxis2Placement3D`")); } + } while(0); + do { // convert the 'PolygonalBoundary' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->PolygonalBoundary, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPolygonalBoundedHalfSpace to be a `IfcBoundedCurve`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTimeSeriesSchedule>(const DB& db, const LIST& params, IfcTimeSeriesSchedule* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCooledBeamType>(const DB& db, const LIST& params, IfcCooledBeamType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProject>(const DB& db, const LIST& params, IfcProject* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObject*>(in)); + if (params.GetSize() < 9) { throw STEP::TypeError("expected 9 arguments to IfcProject"); } do { // convert the 'LongName' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->LongName, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcProject to be a `IfcLabel`")); } + } while(0); + do { // convert the 'Phase' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Phase, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcProject to be a `IfcLabel`")); } + } while(0); + do { // convert the 'RepresentationContexts' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RepresentationContexts, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcProject to be a `SET [1:?] OF IfcRepresentationContext`")); } + } while(0); + do { // convert the 'UnitsInContext' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->UnitsInContext, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcProject to be a `IfcUnitAssignment`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEvaporatorType>(const DB& db, const LIST& params, IfcEvaporatorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLaborResource>(const DB& db, const LIST& params, IfcLaborResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertyBoundedValue>(const DB& db, const LIST& params, IfcPropertyBoundedValue* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSimpleProperty*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRampFlightType>(const DB& db, const LIST& params, IfcRampFlightType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMember>(const DB& db, const LIST& params, IfcMember* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTubeBundleType>(const DB& db, const LIST& params, IfcTubeBundleType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcValveType>(const DB& db, const LIST& params, IfcValveType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTrimmedCurve>(const DB& db, const LIST& params, IfcTrimmedCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedCurve*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcTrimmedCurve"); } do { // convert the 'BasisCurve' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->BasisCurve, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcTrimmedCurve to be a `IfcCurve`")); } + } while(0); + do { // convert the 'Trim1' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Trim1, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcTrimmedCurve to be a `SET [1:2] OF IfcTrimmingSelect`")); } + } while(0); + do { // convert the 'Trim2' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Trim2, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcTrimmedCurve to be a `SET [1:2] OF IfcTrimmingSelect`")); } + } while(0); + do { // convert the 'SenseAgreement' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->SenseAgreement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcTrimmedCurve to be a `BOOLEAN`")); } + } while(0); + do { // convert the 'MasterRepresentation' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->MasterRepresentation, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcTrimmedCurve to be a `IfcTrimmingPreference`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelDefines>(const DB& db, const LIST& params, IfcRelDefines* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelationship*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcRelDefines"); } do { // convert the 'RelatedObjects' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRelDefines,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->RelatedObjects, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelDefines to be a `SET [1:?] OF IfcObject`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelDefinesByProperties>(const DB& db, const LIST& params, IfcRelDefinesByProperties* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelDefines*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelDefinesByProperties"); } do { // convert the 'RelatingPropertyDefinition' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRelDefinesByProperties,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->RelatingPropertyDefinition, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelDefinesByProperties to be a `IfcPropertySetDefinition`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcActor>(const DB& db, const LIST& params, IfcActor* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOccupant>(const DB& db, const LIST& params, IfcOccupant* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcActor*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcHumidifierType>(const DB& db, const LIST& params, IfcHumidifierType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcArbitraryOpenProfileDef>(const DB& db, const LIST& params, IfcArbitraryOpenProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProfileDef*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcArbitraryOpenProfileDef"); } do { // convert the 'Curve' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcArbitraryOpenProfileDef,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Curve, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcArbitraryOpenProfileDef to be a `IfcBoundedCurve`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPermit>(const DB& db, const LIST& params, IfcPermit* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOffsetCurve3D>(const DB& db, const LIST& params, IfcOffsetCurve3D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightSource>(const DB& db, const LIST& params, IfcLightSource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightSourcePositional>(const DB& db, const LIST& params, IfcLightSourcePositional* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLightSource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCompositeProfileDef>(const DB& db, const LIST& params, IfcCompositeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRamp>(const DB& db, const LIST& params, IfcRamp* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowMovingDevice>(const DB& db, const LIST& params, IfcFlowMovingDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpaceHeaterType>(const DB& db, const LIST& params, IfcSpaceHeaterType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLampType>(const DB& db, const LIST& params, IfcLampType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingElementComponent>(const DB& db, const LIST& params, IfcBuildingElementComponent* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcReinforcingElement>(const DB& db, const LIST& params, IfcReinforcingElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementComponent*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcReinforcingBar>(const DB& db, const LIST& params, IfcReinforcingBar* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcReinforcingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricHeaterType>(const DB& db, const LIST& params, IfcElectricHeaterType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTShapeProfileDef>(const DB& db, const LIST& params, IfcTShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralActivity>(const DB& db, const LIST& params, IfcStructuralActivity* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralAction>(const DB& db, const LIST& params, IfcStructuralAction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralActivity*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDuctFittingType>(const DB& db, const LIST& params, IfcDuctFittingType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFittingType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianTransformationOperator2D>(const DB& db, const LIST& params, IfcCartesianTransformationOperator2D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCartesianTransformationOperator*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianTransformationOperator2DnonUniform>(const DB& db, const LIST& params, IfcCartesianTransformationOperator2DnonUniform* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCartesianTransformationOperator2D*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVirtualElement>(const DB& db, const LIST& params, IfcVirtualElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRightCircularCylinder>(const DB& db, const LIST& params, IfcRightCircularCylinder* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCsgPrimitive3D*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOutletType>(const DB& db, const LIST& params, IfcOutletType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelDecomposes>(const DB& db, const LIST& params, IfcRelDecomposes* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelationship*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelDecomposes"); } do { // convert the 'RelatingObject' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRelDecomposes,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->RelatingObject, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelDecomposes to be a `IfcObjectDefinition`")); } + } while(0); + do { // convert the 'RelatedObjects' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRelDecomposes,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->RelatedObjects, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelDecomposes to be a `SET [1:?] OF IfcObjectDefinition`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCovering>(const DB& db, const LIST& params, IfcCovering* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPolyline>(const DB& db, const LIST& params, IfcPolyline* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedCurve*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPolyline"); } do { // convert the 'Points' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Points, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPolyline to be a `LIST [2:?] OF IfcCartesianPoint`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPath>(const DB& db, const LIST& params, IfcPath* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElementComponent>(const DB& db, const LIST& params, IfcElementComponent* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFastener>(const DB& db, const LIST& params, IfcFastener* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponent*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMappedItem>(const DB& db, const LIST& params, IfcMappedItem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentationItem*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcMappedItem"); } do { // convert the 'MappingSource' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->MappingSource, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcMappedItem to be a `IfcRepresentationMap`")); } + } while(0); + do { // convert the 'MappingTarget' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->MappingTarget, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcMappedItem to be a `IfcCartesianTransformationOperator`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRectangularPyramid>(const DB& db, const LIST& params, IfcRectangularPyramid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCsgPrimitive3D*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCrewResource>(const DB& db, const LIST& params, IfcCrewResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcNamedUnit>(const DB& db, const LIST& params, IfcNamedUnit* in) +{ + size_t base = 0; + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcNamedUnit"); } do { // convert the 'Dimensions' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcNamedUnit,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Dimensions, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcNamedUnit to be a `IfcDimensionalExponents`")); } + } while(0); + do { // convert the 'UnitType' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcNamedUnit,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->UnitType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcNamedUnit to be a `IfcUnitEnum`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcContextDependentUnit>(const DB& db, const LIST& params, IfcContextDependentUnit* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcNamedUnit*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcUnitaryEquipmentType>(const DB& db, const LIST& params, IfcUnitaryEquipmentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRoof>(const DB& db, const LIST& params, IfcRoof* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralMember>(const DB& db, const LIST& params, IfcStructuralMember* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStyleModel>(const DB& db, const LIST& params, IfcStyleModel* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentation*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStyledRepresentation>(const DB& db, const LIST& params, IfcStyledRepresentation* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStyleModel*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpatialStructureElement>(const DB& db, const LIST& params, IfcSpatialStructureElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); + if (params.GetSize() < 9) { throw STEP::TypeError("expected 9 arguments to IfcSpatialStructureElement"); } do { // convert the 'LongName' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcSpatialStructureElement,2>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->LongName, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcSpatialStructureElement to be a `IfcLabel`")); } + } while(0); + do { // convert the 'CompositionType' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcSpatialStructureElement,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->CompositionType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcSpatialStructureElement to be a `IfcElementCompositionEnum`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuilding>(const DB& db, const LIST& params, IfcBuilding* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialStructureElement*>(in)); + if (params.GetSize() < 12) { throw STEP::TypeError("expected 12 arguments to IfcBuilding"); } do { // convert the 'ElevationOfRefHeight' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ElevationOfRefHeight, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcBuilding to be a `IfcLengthMeasure`")); } + } while(0); + do { // convert the 'ElevationOfTerrain' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ElevationOfTerrain, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcBuilding to be a `IfcLengthMeasure`")); } + } while(0); + do { // convert the 'BuildingAddress' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->BuildingAddress, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 11 to IfcBuilding to be a `IfcPostalAddress`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConnectedFaceSet>(const DB& db, const LIST& params, IfcConnectedFaceSet* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcConnectedFaceSet"); } do { // convert the 'CfsFaces' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcConnectedFaceSet,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->CfsFaces, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcConnectedFaceSet to be a `SET [1:?] OF IfcFace`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOpenShell>(const DB& db, const LIST& params, IfcOpenShell* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConnectedFaceSet*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFacetedBrep>(const DB& db, const LIST& params, IfcFacetedBrep* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcManifoldSolidBrep*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConic>(const DB& db, const LIST& params, IfcConic* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCurve*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcConic"); } do { // convert the 'Position' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcConic,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Position, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcConic to be a `IfcAxis2Placement`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCoveringType>(const DB& db, const LIST& params, IfcCoveringType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRoundedRectangleProfileDef>(const DB& db, const LIST& params, IfcRoundedRectangleProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRectangleProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAirTerminalType>(const DB& db, const LIST& params, IfcAirTerminalType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowMovingDeviceType>(const DB& db, const LIST& params, IfcFlowMovingDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCompressorType>(const DB& db, const LIST& params, IfcCompressorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowMovingDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcIShapeProfileDef>(const DB& db, const LIST& params, IfcIShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcIShapeProfileDef"); } do { // convert the 'OverallWidth' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcIShapeProfileDef,5>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->OverallWidth, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'OverallDepth' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcIShapeProfileDef,5>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->OverallDepth, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'WebThickness' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcIShapeProfileDef,5>::aux_is_derived[2]=true; break; } + try { GenericConvert( in->WebThickness, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'FlangeThickness' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcIShapeProfileDef,5>::aux_is_derived[3]=true; break; } + try { GenericConvert( in->FlangeThickness, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'FilletRadius' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcIShapeProfileDef,5>::aux_is_derived[4]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->FilletRadius, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAsymmetricIShapeProfileDef>(const DB& db, const LIST& params, IfcAsymmetricIShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcIShapeProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcControllerType>(const DB& db, const LIST& params, IfcControllerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRailing>(const DB& db, const LIST& params, IfcRailing* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGroup>(const DB& db, const LIST& params, IfcGroup* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAsset>(const DB& db, const LIST& params, IfcAsset* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGroup*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMaterialDefinitionRepresentation>(const DB& db, const LIST& params, IfcMaterialDefinitionRepresentation* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProductRepresentation*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRailingType>(const DB& db, const LIST& params, IfcRailingType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWall>(const DB& db, const LIST& params, IfcWall* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralPointConnection>(const DB& db, const LIST& params, IfcStructuralPointConnection* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralConnection*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertyListValue>(const DB& db, const LIST& params, IfcPropertyListValue* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSimpleProperty*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPropertyListValue"); } do { // convert the 'ListValues' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->ListValues, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPropertyListValue to be a `LIST [1:?] OF IfcValue`")); } + } while(0); + do { // convert the 'Unit' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Unit, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPropertyListValue to be a `IfcUnit`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFurnitureStandard>(const DB& db, const LIST& params, IfcFurnitureStandard* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricGeneratorType>(const DB& db, const LIST& params, IfcElectricGeneratorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDoor>(const DB& db, const LIST& params, IfcDoor* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); + if (params.GetSize() < 10) { throw STEP::TypeError("expected 10 arguments to IfcDoor"); } do { // convert the 'OverallHeight' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->OverallHeight, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcDoor to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'OverallWidth' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->OverallWidth, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcDoor to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStyledItem>(const DB& db, const LIST& params, IfcStyledItem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentationItem*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcStyledItem"); } do { // convert the 'Item' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcStyledItem,3>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Item, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcStyledItem to be a `IfcRepresentationItem`")); } + } while(0); + do { // convert the 'Styles' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcStyledItem,3>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->Styles, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcStyledItem to be a `SET [1:?] OF IfcPresentationStyleAssignment`")); } + } while(0); + do { // convert the 'Name' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcStyledItem,3>::aux_is_derived[2]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcStyledItem to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAnnotationOccurrence>(const DB& db, const LIST& params, IfcAnnotationOccurrence* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStyledItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAnnotationSymbolOccurrence>(const DB& db, const LIST& params, IfcAnnotationSymbolOccurrence* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcAnnotationOccurrence*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcArbitraryClosedProfileDef>(const DB& db, const LIST& params, IfcArbitraryClosedProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProfileDef*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcArbitraryClosedProfileDef"); } do { // convert the 'OuterCurve' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcArbitraryClosedProfileDef,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->OuterCurve, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcArbitraryClosedProfileDef to be a `IfcCurve`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcArbitraryProfileDefWithVoids>(const DB& db, const LIST& params, IfcArbitraryProfileDefWithVoids* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcArbitraryClosedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLine>(const DB& db, const LIST& params, IfcLine* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCurve*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcLine"); } do { // convert the 'Pnt' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Pnt, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcLine to be a `IfcCartesianPoint`")); } + } while(0); + do { // convert the 'Dir' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Dir, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcLine to be a `IfcVector`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowSegmentType>(const DB& db, const LIST& params, IfcFlowSegmentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAirTerminalBoxType>(const DB& db, const LIST& params, IfcAirTerminalBoxType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertySingleValue>(const DB& db, const LIST& params, IfcPropertySingleValue* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSimpleProperty*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPropertySingleValue"); } do { // convert the 'NominalValue' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->NominalValue, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPropertySingleValue to be a `IfcValue`")); } + } while(0); + do { // convert the 'Unit' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Unit, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPropertySingleValue to be a `IfcUnit`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAlarmType>(const DB& db, const LIST& params, IfcAlarmType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEllipseProfileDef>(const DB& db, const LIST& params, IfcEllipseProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStair>(const DB& db, const LIST& params, IfcStair* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceStyleShading>(const DB& db, const LIST& params, IfcSurfaceStyleShading* in) +{ + size_t base = 0; + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcSurfaceStyleShading"); } do { // convert the 'SurfaceColour' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcSurfaceStyleShading,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->SurfaceColour, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSurfaceStyleShading to be a `IfcColourRgb`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPumpType>(const DB& db, const LIST& params, IfcPumpType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowMovingDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDefinedSymbol>(const DB& db, const LIST& params, IfcDefinedSymbol* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElementComponentType>(const DB& db, const LIST& params, IfcElementComponentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFastenerType>(const DB& db, const LIST& params, IfcFastenerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMechanicalFastenerType>(const DB& db, const LIST& params, IfcMechanicalFastenerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFastenerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowFitting>(const DB& db, const LIST& params, IfcFlowFitting* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightSourceDirectional>(const DB& db, const LIST& params, IfcLightSourceDirectional* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLightSource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- + +} // ! STEP +} // ! Assimp + +#if _MSC_VER +# pragma warning(pop) +#endif // _MSC_VER + +#endif diff --git a/libs/assimp/code/AssetLib/IFC/IFCReaderGen2_2x3.cpp b/libs/assimp/code/AssetLib/IFC/IFCReaderGen2_2x3.cpp new file mode 100644 index 0000000..0d70511 --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCReaderGen2_2x3.cpp @@ -0,0 +1,1927 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, ASSIMP Development 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 Development 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. + +---------------------------------------------------------------------- +*/ + +//#include "AssimpPCH.h" +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER + +#include "IFCReaderGen_2x3.h" + +#if _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4702) +#endif // _MSC_VER + +namespace Assimp { +using namespace IFC; +using namespace ::Assimp::IFC::Schema_2x3; + +namespace STEP { + +template <> size_t GenericFill<IfcSurfaceStyle>(const DB& db, const LIST& params, IfcSurfaceStyle* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPresentationStyle*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcSurfaceStyle"); } do { // convert the 'Side' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Side, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSurfaceStyle to be a `IfcSurfaceSide`")); } + } while(0); + do { // convert the 'Styles' argument + std::shared_ptr<const DataType> arg = params[ base++ ]; + try { GenericConvert( in->Styles, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSurfaceStyle to be a `SET [1:5] OF IfcSurfaceStyleElementSelect`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAnnotationSurface>(const DB& db, const LIST& params, IfcAnnotationSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowController>(const DB& db, const LIST& params, IfcFlowController* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingStorey>(const DB& db, const LIST& params, IfcBuildingStorey* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialStructureElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWorkControl>(const DB& db, const LIST& params, IfcWorkControl* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWorkSchedule>(const DB& db, const LIST& params, IfcWorkSchedule* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcWorkControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDuctSegmentType>(const DB& db, const LIST& params, IfcDuctSegmentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowSegmentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFace>(const DB& db, const LIST& params, IfcFace* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcFace"); } do { // convert the 'Bounds' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcFace,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Bounds, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFace to be a `SET [1:?] OF IfcFaceBound`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralSurfaceMember>(const DB& db, const LIST& params, IfcStructuralSurfaceMember* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralMember*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralSurfaceMemberVarying>(const DB& db, const LIST& params, IfcStructuralSurfaceMemberVarying* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralSurfaceMember*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFaceSurface>(const DB& db, const LIST& params, IfcFaceSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFace*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCostSchedule>(const DB& db, const LIST& params, IfcCostSchedule* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPlanarExtent>(const DB& db, const LIST& params, IfcPlanarExtent* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPlanarBox>(const DB& db, const LIST& params, IfcPlanarBox* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPlanarExtent*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcColourSpecification>(const DB& db, const LIST& params, IfcColourSpecification* in) +{ + size_t base = 0; + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcColourSpecification"); } do { // convert the 'Name' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcColourSpecification,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcColourSpecification to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVector>(const DB& db, const LIST& params, IfcVector* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcVector"); } do { // convert the 'Orientation' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Orientation, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcVector to be a `IfcDirection`")); } + } while(0); + do { // convert the 'Magnitude' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Magnitude, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcVector to be a `IfcLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBeam>(const DB& db, const LIST& params, IfcBeam* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcColourRgb>(const DB& db, const LIST& params, IfcColourRgb* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcColourSpecification*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcColourRgb"); } do { // convert the 'Red' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Red, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } + } while(0); + do { // convert the 'Green' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Green, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } + } while(0); + do { // convert the 'Blue' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Blue, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralPlanarAction>(const DB& db, const LIST& params, IfcStructuralPlanarAction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralAction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralPlanarActionVarying>(const DB& db, const LIST& params, IfcStructuralPlanarActionVarying* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralPlanarAction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSite>(const DB& db, const LIST& params, IfcSite* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialStructureElement*>(in)); + if (params.GetSize() < 14) { throw STEP::TypeError("expected 14 arguments to IfcSite"); } do { // convert the 'RefLatitude' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RefLatitude, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcSite to be a `IfcCompoundPlaneAngleMeasure`")); } + } while(0); + do { // convert the 'RefLongitude' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RefLongitude, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcSite to be a `IfcCompoundPlaneAngleMeasure`")); } + } while(0); + do { // convert the 'RefElevation' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RefElevation, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 11 to IfcSite to be a `IfcLengthMeasure`")); } + } while(0); + do { // convert the 'LandTitleNumber' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->LandTitleNumber, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 12 to IfcSite to be a `IfcLabel`")); } + } while(0); + do { // convert the 'SiteAddress' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->SiteAddress, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 13 to IfcSite to be a `IfcPostalAddress`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDiscreteAccessoryType>(const DB& db, const LIST& params, IfcDiscreteAccessoryType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVibrationIsolatorType>(const DB& db, const LIST& params, IfcVibrationIsolatorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDiscreteAccessoryType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEvaporativeCoolerType>(const DB& db, const LIST& params, IfcEvaporativeCoolerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionChamberElementType>(const DB& db, const LIST& params, IfcDistributionChamberElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFeatureElementAddition>(const DB& db, const LIST& params, IfcFeatureElementAddition* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFeatureElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuredDimensionCallout>(const DB& db, const LIST& params, IfcStructuredDimensionCallout* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDraughtingCallout*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCoolingTowerType>(const DB& db, const LIST& params, IfcCoolingTowerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCenterLineProfileDef>(const DB& db, const LIST& params, IfcCenterLineProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcArbitraryOpenProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWindowStyle>(const DB& db, const LIST& params, IfcWindowStyle* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightSourceGoniometric>(const DB& db, const LIST& params, IfcLightSourceGoniometric* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLightSource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTransformerType>(const DB& db, const LIST& params, IfcTransformerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMemberType>(const DB& db, const LIST& params, IfcMemberType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceOfLinearExtrusion>(const DB& db, const LIST& params, IfcSurfaceOfLinearExtrusion* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSweptSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMotorConnectionType>(const DB& db, const LIST& params, IfcMotorConnectionType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowTreatmentDeviceType>(const DB& db, const LIST& params, IfcFlowTreatmentDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDuctSilencerType>(const DB& db, const LIST& params, IfcDuctSilencerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTreatmentDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFurnishingElementType>(const DB& db, const LIST& params, IfcFurnishingElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSystemFurnitureElementType>(const DB& db, const LIST& params, IfcSystemFurnitureElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFurnishingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWasteTerminalType>(const DB& db, const LIST& params, IfcWasteTerminalType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBSplineCurve>(const DB& db, const LIST& params, IfcBSplineCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedCurve*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcBSplineCurve"); } do { // convert the 'Degree' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcBSplineCurve,5>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Degree, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBSplineCurve to be a `INTEGER`")); } + } while(0); + do { // convert the 'ControlPointsList' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcBSplineCurve,5>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->ControlPointsList, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBSplineCurve to be a `LIST [2:?] OF IfcCartesianPoint`")); } + } while(0); + do { // convert the 'CurveForm' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcBSplineCurve,5>::aux_is_derived[2]=true; break; } + try { GenericConvert( in->CurveForm, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBSplineCurve to be a `IfcBSplineCurveForm`")); } + } while(0); + do { // convert the 'ClosedCurve' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcBSplineCurve,5>::aux_is_derived[3]=true; break; } + try { GenericConvert( in->ClosedCurve, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcBSplineCurve to be a `LOGICAL`")); } + } while(0); + do { // convert the 'SelfIntersect' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcBSplineCurve,5>::aux_is_derived[4]=true; break; } + try { GenericConvert( in->SelfIntersect, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcBSplineCurve to be a `LOGICAL`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBezierCurve>(const DB& db, const LIST& params, IfcBezierCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBSplineCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcActuatorType>(const DB& db, const LIST& params, IfcActuatorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionControlElement>(const DB& db, const LIST& params, IfcDistributionControlElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAnnotation>(const DB& db, const LIST& params, IfcAnnotation* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); + if (params.GetSize() < 7) { throw STEP::TypeError("expected 7 arguments to IfcAnnotation"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcShellBasedSurfaceModel>(const DB& db, const LIST& params, IfcShellBasedSurfaceModel* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcShellBasedSurfaceModel"); } do { // convert the 'SbsmBoundary' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->SbsmBoundary, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcShellBasedSurfaceModel to be a `SET [1:?] OF IfcShell`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcActionRequest>(const DB& db, const LIST& params, IfcActionRequest* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcExtrudedAreaSolid>(const DB& db, const LIST& params, IfcExtrudedAreaSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSweptAreaSolid*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcExtrudedAreaSolid"); } do { // convert the 'ExtrudedDirection' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->ExtrudedDirection, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcExtrudedAreaSolid to be a `IfcDirection`")); } + } while(0); + do { // convert the 'Depth' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Depth, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcExtrudedAreaSolid to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSystem>(const DB& db, const LIST& params, IfcSystem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGroup*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFillAreaStyleHatching>(const DB& db, const LIST& params, IfcFillAreaStyleHatching* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelVoidsElement>(const DB& db, const LIST& params, IfcRelVoidsElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelConnects*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelVoidsElement"); } do { // convert the 'RelatingBuildingElement' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatingBuildingElement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelVoidsElement to be a `IfcElement`")); } + } while(0); + do { // convert the 'RelatedOpeningElement' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatedOpeningElement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelVoidsElement to be a `IfcFeatureElementSubtraction`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceCurveSweptAreaSolid>(const DB& db, const LIST& params, IfcSurfaceCurveSweptAreaSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSweptAreaSolid*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianTransformationOperator3DnonUniform>(const DB& db, const LIST& params, IfcCartesianTransformationOperator3DnonUniform* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCartesianTransformationOperator3D*>(in)); + if (params.GetSize() < 7) { throw STEP::TypeError("expected 7 arguments to IfcCartesianTransformationOperator3DnonUniform"); } do { // convert the 'Scale2' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Scale2, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcCartesianTransformationOperator3DnonUniform to be a `REAL`")); } + } while(0); + do { // convert the 'Scale3' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Scale3, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcCartesianTransformationOperator3DnonUniform to be a `REAL`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCurtainWallType>(const DB& db, const LIST& params, IfcCurtainWallType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEquipmentStandard>(const DB& db, const LIST& params, IfcEquipmentStandard* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowStorageDeviceType>(const DB& db, const LIST& params, IfcFlowStorageDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDiameterDimension>(const DB& db, const LIST& params, IfcDiameterDimension* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDimensionCurveDirectedCallout*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSwitchingDeviceType>(const DB& db, const LIST& params, IfcSwitchingDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWindow>(const DB& db, const LIST& params, IfcWindow* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowTreatmentDevice>(const DB& db, const LIST& params, IfcFlowTreatmentDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcChillerType>(const DB& db, const LIST& params, IfcChillerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRectangleHollowProfileDef>(const DB& db, const LIST& params, IfcRectangleHollowProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRectangleProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBoxedHalfSpace>(const DB& db, const LIST& params, IfcBoxedHalfSpace* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcHalfSpaceSolid*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAxis2Placement2D>(const DB& db, const LIST& params, IfcAxis2Placement2D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPlacement*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcAxis2Placement2D"); } do { // convert the 'RefDirection' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RefDirection, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis2Placement2D to be a `IfcDirection`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpaceProgram>(const DB& db, const LIST& params, IfcSpaceProgram* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPoint>(const DB& db, const LIST& params, IfcPoint* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianPoint>(const DB& db, const LIST& params, IfcCartesianPoint* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPoint*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcCartesianPoint"); } do { // convert the 'Coordinates' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Coordinates, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCartesianPoint to be a `LIST [1:3] OF IfcLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBoundedSurface>(const DB& db, const LIST& params, IfcBoundedSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLoop>(const DB& db, const LIST& params, IfcLoop* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPolyLoop>(const DB& db, const LIST& params, IfcPolyLoop* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLoop*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPolyLoop"); } do { // convert the 'Polygon' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Polygon, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPolyLoop to be a `LIST [3:?] OF IfcCartesianPoint`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTerminatorSymbol>(const DB& db, const LIST& params, IfcTerminatorSymbol* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcAnnotationSymbolOccurrence*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDimensionCurveTerminator>(const DB& db, const LIST& params, IfcDimensionCurveTerminator* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTerminatorSymbol*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTrapeziumProfileDef>(const DB& db, const LIST& params, IfcTrapeziumProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRepresentationContext>(const DB& db, const LIST& params, IfcRepresentationContext* in) +{ + size_t base = 0; + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcRepresentationContext"); } do { // convert the 'ContextIdentifier' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRepresentationContext,2>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ContextIdentifier, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentationContext to be a `IfcLabel`")); } + } while(0); + do { // convert the 'ContextType' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcRepresentationContext,2>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ContextType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentationContext to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGeometricRepresentationContext>(const DB& db, const LIST& params, IfcGeometricRepresentationContext* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentationContext*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcGeometricRepresentationContext"); } do { // convert the 'CoordinateSpaceDimension' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcGeometricRepresentationContext,4>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->CoordinateSpaceDimension, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcGeometricRepresentationContext to be a `IfcDimensionCount`")); } + } while(0); + do { // convert the 'Precision' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcGeometricRepresentationContext,4>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Precision, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcGeometricRepresentationContext to be a `REAL`")); } + } while(0); + do { // convert the 'WorldCoordinateSystem' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcGeometricRepresentationContext,4>::aux_is_derived[2]=true; break; } + try { GenericConvert( in->WorldCoordinateSystem, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcGeometricRepresentationContext to be a `IfcAxis2Placement`")); } + } while(0); + do { // convert the 'TrueNorth' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcGeometricRepresentationContext,4>::aux_is_derived[3]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->TrueNorth, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcGeometricRepresentationContext to be a `IfcDirection`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCurveBoundedPlane>(const DB& db, const LIST& params, IfcCurveBoundedPlane* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSIUnit>(const DB& db, const LIST& params, IfcSIUnit* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcNamedUnit*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcSIUnit"); } do { // convert the 'Prefix' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Prefix, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSIUnit to be a `IfcSIPrefix`")); } + } while(0); + do { // convert the 'Name' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSIUnit to be a `IfcSIUnitName`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralReaction>(const DB& db, const LIST& params, IfcStructuralReaction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralActivity*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralPointReaction>(const DB& db, const LIST& params, IfcStructuralPointReaction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralReaction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAxis1Placement>(const DB& db, const LIST& params, IfcAxis1Placement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPlacement*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcAxis1Placement"); } do { // convert the 'Axis' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Axis, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis1Placement to be a `IfcDirection`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricApplianceType>(const DB& db, const LIST& params, IfcElectricApplianceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSensorType>(const DB& db, const LIST& params, IfcSensorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFurnishingElement>(const DB& db, const LIST& params, IfcFurnishingElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProtectiveDeviceType>(const DB& db, const LIST& params, IfcProtectiveDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcZShapeProfileDef>(const DB& db, const LIST& params, IfcZShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcScheduleTimeControl>(const DB& db, const LIST& params, IfcScheduleTimeControl* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRepresentationMap>(const DB& db, const LIST& params, IfcRepresentationMap* in) +{ + size_t base = 0; + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcRepresentationMap"); } do { // convert the 'MappingOrigin' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->MappingOrigin, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentationMap to be a `IfcAxis2Placement`")); } + } while(0); + do { // convert the 'MappedRepresentation' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->MappedRepresentation, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentationMap to be a `IfcRepresentation`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcClosedShell>(const DB& db, const LIST& params, IfcClosedShell* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConnectedFaceSet*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcClosedShell"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingElementPart>(const DB& db, const LIST& params, IfcBuildingElementPart* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementComponent*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBlock>(const DB& db, const LIST& params, IfcBlock* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCsgPrimitive3D*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightFixtureType>(const DB& db, const LIST& params, IfcLightFixtureType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOpeningElement>(const DB& db, const LIST& params, IfcOpeningElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFeatureElementSubtraction*>(in)); + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcOpeningElement"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightSourceSpot>(const DB& db, const LIST& params, IfcLightSourceSpot* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLightSourcePositional*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTendonAnchor>(const DB& db, const LIST& params, IfcTendonAnchor* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcReinforcingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricFlowStorageDeviceType>(const DB& db, const LIST& params, IfcElectricFlowStorageDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowStorageDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSphere>(const DB& db, const LIST& params, IfcSphere* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCsgPrimitive3D*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDamperType>(const DB& db, const LIST& params, IfcDamperType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProjectOrderRecord>(const DB& db, const LIST& params, IfcProjectOrderRecord* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionChamberElement>(const DB& db, const LIST& params, IfcDistributionChamberElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMechanicalFastener>(const DB& db, const LIST& params, IfcMechanicalFastener* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFastener*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRectangularTrimmedSurface>(const DB& db, const LIST& params, IfcRectangularTrimmedSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcZone>(const DB& db, const LIST& params, IfcZone* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGroup*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFanType>(const DB& db, const LIST& params, IfcFanType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowMovingDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGeometricSet>(const DB& db, const LIST& params, IfcGeometricSet* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFillAreaStyleTiles>(const DB& db, const LIST& params, IfcFillAreaStyleTiles* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCableSegmentType>(const DB& db, const LIST& params, IfcCableSegmentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowSegmentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelOverridesProperties>(const DB& db, const LIST& params, IfcRelOverridesProperties* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelDefinesByProperties*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMeasureWithUnit>(const DB& db, const LIST& params, IfcMeasureWithUnit* in) +{ + size_t base = 0; + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcMeasureWithUnit"); } do { // convert the 'ValueComponent' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->ValueComponent, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcMeasureWithUnit to be a `IfcValue`")); } + } while(0); + do { // convert the 'UnitComponent' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->UnitComponent, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcMeasureWithUnit to be a `IfcUnit`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSlabType>(const DB& db, const LIST& params, IfcSlabType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcServiceLife>(const DB& db, const LIST& params, IfcServiceLife* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFurnitureType>(const DB& db, const LIST& params, IfcFurnitureType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFurnishingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCostItem>(const DB& db, const LIST& params, IfcCostItem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcReinforcingMesh>(const DB& db, const LIST& params, IfcReinforcingMesh* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcReinforcingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFacetedBrepWithVoids>(const DB& db, const LIST& params, IfcFacetedBrepWithVoids* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcManifoldSolidBrep*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGasTerminalType>(const DB& db, const LIST& params, IfcGasTerminalType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPile>(const DB& db, const LIST& params, IfcPile* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFillAreaStyleTileSymbolWithStyle>(const DB& db, const LIST& params, IfcFillAreaStyleTileSymbolWithStyle* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConstructionMaterialResource>(const DB& db, const LIST& params, IfcConstructionMaterialResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAnnotationCurveOccurrence>(const DB& db, const LIST& params, IfcAnnotationCurveOccurrence* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcAnnotationOccurrence*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDimensionCurve>(const DB& db, const LIST& params, IfcDimensionCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcAnnotationCurveOccurrence*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGeometricCurveSet>(const DB& db, const LIST& params, IfcGeometricCurveSet* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricSet*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelAggregates>(const DB& db, const LIST& params, IfcRelAggregates* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelDecomposes*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelAggregates"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFaceBasedSurfaceModel>(const DB& db, const LIST& params, IfcFaceBasedSurfaceModel* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcFaceBasedSurfaceModel"); } do { // convert the 'FbsmFaces' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->FbsmFaces, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFaceBasedSurfaceModel to be a `SET [1:?] OF IfcConnectedFaceSet`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEnergyConversionDevice>(const DB& db, const LIST& params, IfcEnergyConversionDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRampFlight>(const DB& db, const LIST& params, IfcRampFlight* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVertexLoop>(const DB& db, const LIST& params, IfcVertexLoop* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLoop*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPlate>(const DB& db, const LIST& params, IfcPlate* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcUShapeProfileDef>(const DB& db, const LIST& params, IfcUShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFaceBound>(const DB& db, const LIST& params, IfcFaceBound* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcFaceBound"); } do { // convert the 'Bound' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcFaceBound,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Bound, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFaceBound to be a `IfcLoop`")); } + } while(0); + do { // convert the 'Orientation' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::Schema_2x3::IfcFaceBound,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->Orientation, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcFaceBound to be a `BOOLEAN`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFaceOuterBound>(const DB& db, const LIST& params, IfcFaceOuterBound* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFaceBound*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcFaceOuterBound"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOneDirectionRepeatFactor>(const DB& db, const LIST& params, IfcOneDirectionRepeatFactor* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBoilerType>(const DB& db, const LIST& params, IfcBoilerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConstructionEquipmentResource>(const DB& db, const LIST& params, IfcConstructionEquipmentResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcComplexProperty>(const DB& db, const LIST& params, IfcComplexProperty* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProperty*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcComplexProperty"); } do { // convert the 'UsageName' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->UsageName, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcComplexProperty to be a `IfcIdentifier`")); } + } while(0); + do { // convert the 'HasProperties' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->HasProperties, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcComplexProperty to be a `SET [1:?] OF IfcProperty`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFooting>(const DB& db, const LIST& params, IfcFooting* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConstructionProductResource>(const DB& db, const LIST& params, IfcConstructionProductResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDerivedProfileDef>(const DB& db, const LIST& params, IfcDerivedProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertyTableValue>(const DB& db, const LIST& params, IfcPropertyTableValue* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSimpleProperty*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowMeterType>(const DB& db, const LIST& params, IfcFlowMeterType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDoorStyle>(const DB& db, const LIST& params, IfcDoorStyle* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcUnitAssignment>(const DB& db, const LIST& params, IfcUnitAssignment* in) +{ + size_t base = 0; + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcUnitAssignment"); } do { // convert the 'Units' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Units, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcUnitAssignment to be a `SET [1:?] OF IfcUnit`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowTerminal>(const DB& db, const LIST& params, IfcFlowTerminal* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCraneRailFShapeProfileDef>(const DB& db, const LIST& params, IfcCraneRailFShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowSegment>(const DB& db, const LIST& params, IfcFlowSegment* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElementQuantity>(const DB& db, const LIST& params, IfcElementQuantity* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPropertySetDefinition*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcElementQuantity"); } do { // convert the 'MethodOfMeasurement' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->MethodOfMeasurement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcElementQuantity to be a `IfcLabel`")); } + } while(0); + do { // convert the 'Quantities' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Quantities, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcElementQuantity to be a `SET [1:?] OF IfcPhysicalQuantity`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCurtainWall>(const DB& db, const LIST& params, IfcCurtainWall* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDiscreteAccessory>(const DB& db, const LIST& params, IfcDiscreteAccessory* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponent*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGrid>(const DB& db, const LIST& params, IfcGrid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSanitaryTerminalType>(const DB& db, const LIST& params, IfcSanitaryTerminalType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSubedge>(const DB& db, const LIST& params, IfcSubedge* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEdge*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFilterType>(const DB& db, const LIST& params, IfcFilterType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTreatmentDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTendon>(const DB& db, const LIST& params, IfcTendon* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcReinforcingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralLoadGroup>(const DB& db, const LIST& params, IfcStructuralLoadGroup* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGroup*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPresentationStyleAssignment>(const DB& db, const LIST& params, IfcPresentationStyleAssignment* in) +{ + size_t base = 0; + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPresentationStyleAssignment"); } do { // convert the 'Styles' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Styles, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPresentationStyleAssignment to be a `SET [1:?] OF IfcPresentationStyleSelect`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralCurveMember>(const DB& db, const LIST& params, IfcStructuralCurveMember* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralMember*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightSourceAmbient>(const DB& db, const LIST& params, IfcLightSourceAmbient* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLightSource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCondition>(const DB& db, const LIST& params, IfcCondition* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGroup*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPort>(const DB& db, const LIST& params, IfcPort* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpace>(const DB& db, const LIST& params, IfcSpace* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialStructureElement*>(in)); + if (params.GetSize() < 11) { throw STEP::TypeError("expected 11 arguments to IfcSpace"); } do { // convert the 'InteriorOrExteriorSpace' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->InteriorOrExteriorSpace, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcSpace to be a `IfcInternalOrExternalEnum`")); } + } while(0); + do { // convert the 'ElevationWithFlooring' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ElevationWithFlooring, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcSpace to be a `IfcLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcHeatExchangerType>(const DB& db, const LIST& params, IfcHeatExchangerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTankType>(const DB& db, const LIST& params, IfcTankType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowStorageDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcInventory>(const DB& db, const LIST& params, IfcInventory* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGroup*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTransportElementType>(const DB& db, const LIST& params, IfcTransportElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAirToAirHeatRecoveryType>(const DB& db, const LIST& params, IfcAirToAirHeatRecoveryType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStairFlight>(const DB& db, const LIST& params, IfcStairFlight* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricalElement>(const DB& db, const LIST& params, IfcElectricalElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceStyleWithTextures>(const DB& db, const LIST& params, IfcSurfaceStyleWithTextures* in) +{ + size_t base = 0; + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcSurfaceStyleWithTextures"); } do { // convert the 'Textures' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Textures, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSurfaceStyleWithTextures to be a `LIST [1:?] OF IfcSurfaceTexture`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBoundingBox>(const DB& db, const LIST& params, IfcBoundingBox* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcBoundingBox"); } do { // convert the 'Corner' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Corner, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBoundingBox to be a `IfcCartesianPoint`")); } + } while(0); + do { // convert the 'XDim' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->XDim, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'YDim' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->YDim, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'ZDim' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->ZDim, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWallType>(const DB& db, const LIST& params, IfcWallType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMove>(const DB& db, const LIST& params, IfcMove* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTask*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCircle>(const DB& db, const LIST& params, IfcCircle* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConic*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcCircle"); } do { // convert the 'Radius' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Radius, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCircle to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOffsetCurve2D>(const DB& db, const LIST& params, IfcOffsetCurve2D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPointOnCurve>(const DB& db, const LIST& params, IfcPointOnCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPoint*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralResultGroup>(const DB& db, const LIST& params, IfcStructuralResultGroup* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGroup*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSectionedSpine>(const DB& db, const LIST& params, IfcSectionedSpine* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSlab>(const DB& db, const LIST& params, IfcSlab* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVertex>(const DB& db, const LIST& params, IfcVertex* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVertexPoint>(const DB& db, const LIST& params, IfcVertexPoint* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcVertex*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralLinearAction>(const DB& db, const LIST& params, IfcStructuralLinearAction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralAction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralLinearActionVarying>(const DB& db, const LIST& params, IfcStructuralLinearActionVarying* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralLinearAction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingElementProxyType>(const DB& db, const LIST& params, IfcBuildingElementProxyType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProjectionElement>(const DB& db, const LIST& params, IfcProjectionElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFeatureElementAddition*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConversionBasedUnit>(const DB& db, const LIST& params, IfcConversionBasedUnit* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcNamedUnit*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcConversionBasedUnit"); } do { // convert the 'Name' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcConversionBasedUnit to be a `IfcLabel`")); } + } while(0); + do { // convert the 'ConversionFactor' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->ConversionFactor, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcConversionBasedUnit to be a `IfcMeasureWithUnit`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGeometricRepresentationSubContext>(const DB& db, const LIST& params, IfcGeometricRepresentationSubContext* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationContext*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAnnotationSurfaceOccurrence>(const DB& db, const LIST& params, IfcAnnotationSurfaceOccurrence* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcAnnotationOccurrence*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRoundedEdgeFeature>(const DB& db, const LIST& params, IfcRoundedEdgeFeature* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEdgeFeature*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricDistributionPoint>(const DB& db, const LIST& params, IfcElectricDistributionPoint* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowController*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCableCarrierSegmentType>(const DB& db, const LIST& params, IfcCableCarrierSegmentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowSegmentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWallStandardCase>(const DB& db, const LIST& params, IfcWallStandardCase* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcWall*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCsgSolid>(const DB& db, const LIST& params, IfcCsgSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSolidModel*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBeamType>(const DB& db, const LIST& params, IfcBeamType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAnnotationFillArea>(const DB& db, const LIST& params, IfcAnnotationFillArea* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralCurveMemberVarying>(const DB& db, const LIST& params, IfcStructuralCurveMemberVarying* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralCurveMember*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPointOnSurface>(const DB& db, const LIST& params, IfcPointOnSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPoint*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOrderAction>(const DB& db, const LIST& params, IfcOrderAction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTask*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEdgeLoop>(const DB& db, const LIST& params, IfcEdgeLoop* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLoop*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAnnotationFillAreaOccurrence>(const DB& db, const LIST& params, IfcAnnotationFillAreaOccurrence* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcAnnotationOccurrence*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWorkPlan>(const DB& db, const LIST& params, IfcWorkPlan* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcWorkControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEllipse>(const DB& db, const LIST& params, IfcEllipse* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConic*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcEllipse"); } do { // convert the 'SemiAxis1' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->SemiAxis1, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcEllipse to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'SemiAxis2' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->SemiAxis2, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcEllipse to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProductDefinitionShape>(const DB& db, const LIST& params, IfcProductDefinitionShape* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProductRepresentation*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProjectionCurve>(const DB& db, const LIST& params, IfcProjectionCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcAnnotationCurveOccurrence*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricalCircuit>(const DB& db, const LIST& params, IfcElectricalCircuit* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSystem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRationalBezierCurve>(const DB& db, const LIST& params, IfcRationalBezierCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBezierCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralPointAction>(const DB& db, const LIST& params, IfcStructuralPointAction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralAction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPipeSegmentType>(const DB& db, const LIST& params, IfcPipeSegmentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowSegmentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTwoDirectionRepeatFactor>(const DB& db, const LIST& params, IfcTwoDirectionRepeatFactor* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcOneDirectionRepeatFactor*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcShapeRepresentation>(const DB& db, const LIST& params, IfcShapeRepresentation* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcShapeModel*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertySet>(const DB& db, const LIST& params, IfcPropertySet* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPropertySetDefinition*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcPropertySet"); } do { // convert the 'HasProperties' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->HasProperties, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcPropertySet to be a `SET [1:?] OF IfcProperty`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceStyleRendering>(const DB& db, const LIST& params, IfcSurfaceStyleRendering* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSurfaceStyleShading*>(in)); + if (params.GetSize() < 9) { throw STEP::TypeError("expected 9 arguments to IfcSurfaceStyleRendering"); } do { // convert the 'Transparency' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Transparency, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSurfaceStyleRendering to be a `IfcNormalisedRatioMeasure`")); } + } while(0); + do { // convert the 'DiffuseColour' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->DiffuseColour, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } + } while(0); + do { // convert the 'TransmissionColour' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->TransmissionColour, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } + } while(0); + do { // convert the 'DiffuseTransmissionColour' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->DiffuseTransmissionColour, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } + } while(0); + do { // convert the 'ReflectionColour' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ReflectionColour, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } + } while(0); + do { // convert the 'SpecularColour' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->SpecularColour, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } + } while(0); + do { // convert the 'SpecularHighlight' argument + std::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->SpecularHighlight, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcSurfaceStyleRendering to be a `IfcSpecularHighlightSelect`")); } + } while(0); + do { // convert the 'ReflectanceMethod' argument + std::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->ReflectanceMethod, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcSurfaceStyleRendering to be a `IfcReflectanceMethodEnum`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionPort>(const DB& db, const LIST& params, IfcDistributionPort* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPort*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPipeFittingType>(const DB& db, const LIST& params, IfcPipeFittingType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFittingType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTransportElement>(const DB& db, const LIST& params, IfcTransportElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAnnotationTextOccurrence>(const DB& db, const LIST& params, IfcAnnotationTextOccurrence* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcAnnotationOccurrence*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralAnalysisModel>(const DB& db, const LIST& params, IfcStructuralAnalysisModel* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSystem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConditionCriterion>(const DB& db, const LIST& params, IfcConditionCriterion* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} + +} // ! STEP +} // ! Assimp + +#if _MSC_VER +# pragma warning(pop) +#endif // _MSC_VER + +#endif diff --git a/libs/assimp/code/AssetLib/IFC/IFCReaderGen_2x3.h b/libs/assimp/code/AssetLib/IFC/IFCReaderGen_2x3.h new file mode 100644 index 0000000..f87f121 --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCReaderGen_2x3.h @@ -0,0 +1,4380 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, ASSIMP Development 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 Development 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. + +---------------------------------------------------------------------- +*/ + +/** MACHINE-GENERATED by scripts/ICFImporter/CppGenerator.py */ + +#ifndef INCLUDED_IFC_READER_GEN_H +#define INCLUDED_IFC_READER_GEN_H + +#include "AssetLib/Step/STEPFile.h" + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning( disable : 4512 ) +#endif // _MSC_VER + +namespace Assimp { +namespace IFC { + namespace Schema_2x3 { + + using namespace STEP; + using namespace STEP::EXPRESS; + + + struct NotImplemented : public ObjectHelper<NotImplemented,0> { + + }; + + + // ****************************************************************************** + // IFC Custom data types + // ****************************************************************************** + + + // C++ wrapper type for IfcAbsorbedDoseMeasure + typedef REAL IfcAbsorbedDoseMeasure; + // C++ wrapper type for IfcAccelerationMeasure + typedef REAL IfcAccelerationMeasure; + // C++ wrapper type for IfcAmountOfSubstanceMeasure + typedef REAL IfcAmountOfSubstanceMeasure; + // C++ wrapper type for IfcAngularVelocityMeasure + typedef REAL IfcAngularVelocityMeasure; + // C++ wrapper type for IfcAreaMeasure + typedef REAL IfcAreaMeasure; + // C++ wrapper type for IfcBoolean + typedef BOOLEAN IfcBoolean; + // C++ wrapper type for IfcBoxAlignment + typedef STRING IfcBoxAlignment; + // C++ wrapper type for IfcCompoundPlaneAngleMeasure + typedef ListOf< INTEGER, 3, 3 > IfcCompoundPlaneAngleMeasure; + // C++ wrapper type for IfcContextDependentMeasure + typedef REAL IfcContextDependentMeasure; + // C++ wrapper type for IfcCountMeasure + typedef NUMBER IfcCountMeasure; + // C++ wrapper type for IfcCurvatureMeasure + typedef REAL IfcCurvatureMeasure; + // C++ wrapper type for IfcDayInMonthNumber + typedef INTEGER IfcDayInMonthNumber; + // C++ wrapper type for IfcDaylightSavingHour + typedef INTEGER IfcDaylightSavingHour; + // C++ wrapper type for IfcDescriptiveMeasure + typedef STRING IfcDescriptiveMeasure; + // C++ wrapper type for IfcDimensionCount + typedef INTEGER IfcDimensionCount; + // C++ wrapper type for IfcDoseEquivalentMeasure + typedef REAL IfcDoseEquivalentMeasure; + // C++ wrapper type for IfcDynamicViscosityMeasure + typedef REAL IfcDynamicViscosityMeasure; + // C++ wrapper type for IfcElectricCapacitanceMeasure + typedef REAL IfcElectricCapacitanceMeasure; + // C++ wrapper type for IfcElectricChargeMeasure + typedef REAL IfcElectricChargeMeasure; + // C++ wrapper type for IfcElectricConductanceMeasure + typedef REAL IfcElectricConductanceMeasure; + // C++ wrapper type for IfcElectricCurrentMeasure + typedef REAL IfcElectricCurrentMeasure; + // C++ wrapper type for IfcElectricResistanceMeasure + typedef REAL IfcElectricResistanceMeasure; + // C++ wrapper type for IfcElectricVoltageMeasure + typedef REAL IfcElectricVoltageMeasure; + // C++ wrapper type for IfcEnergyMeasure + typedef REAL IfcEnergyMeasure; + // C++ wrapper type for IfcFontStyle + typedef STRING IfcFontStyle; + // C++ wrapper type for IfcFontVariant + typedef STRING IfcFontVariant; + // C++ wrapper type for IfcFontWeight + typedef STRING IfcFontWeight; + // C++ wrapper type for IfcForceMeasure + typedef REAL IfcForceMeasure; + // C++ wrapper type for IfcFrequencyMeasure + typedef REAL IfcFrequencyMeasure; + // C++ wrapper type for IfcGloballyUniqueId + typedef STRING IfcGloballyUniqueId; + // C++ wrapper type for IfcHeatFluxDensityMeasure + typedef REAL IfcHeatFluxDensityMeasure; + // C++ wrapper type for IfcHeatingValueMeasure + typedef REAL IfcHeatingValueMeasure; + // C++ wrapper type for IfcHourInDay + typedef INTEGER IfcHourInDay; + // C++ wrapper type for IfcIdentifier + typedef STRING IfcIdentifier; + // C++ wrapper type for IfcIlluminanceMeasure + typedef REAL IfcIlluminanceMeasure; + // C++ wrapper type for IfcInductanceMeasure + typedef REAL IfcInductanceMeasure; + // C++ wrapper type for IfcInteger + typedef INTEGER IfcInteger; + // C++ wrapper type for IfcIntegerCountRateMeasure + typedef INTEGER IfcIntegerCountRateMeasure; + // C++ wrapper type for IfcIonConcentrationMeasure + typedef REAL IfcIonConcentrationMeasure; + // C++ wrapper type for IfcIsothermalMoistureCapacityMeasure + typedef REAL IfcIsothermalMoistureCapacityMeasure; + // C++ wrapper type for IfcKinematicViscosityMeasure + typedef REAL IfcKinematicViscosityMeasure; + // C++ wrapper type for IfcLabel + typedef STRING IfcLabel; + // C++ wrapper type for IfcLengthMeasure + typedef REAL IfcLengthMeasure; + // C++ wrapper type for IfcLinearForceMeasure + typedef REAL IfcLinearForceMeasure; + // C++ wrapper type for IfcLinearMomentMeasure + typedef REAL IfcLinearMomentMeasure; + // C++ wrapper type for IfcLinearStiffnessMeasure + typedef REAL IfcLinearStiffnessMeasure; + // C++ wrapper type for IfcLinearVelocityMeasure + typedef REAL IfcLinearVelocityMeasure; + // C++ wrapper type for IfcLogical + typedef LOGICAL IfcLogical; + // C++ wrapper type for IfcLuminousFluxMeasure + typedef REAL IfcLuminousFluxMeasure; + // C++ wrapper type for IfcLuminousIntensityDistributionMeasure + typedef REAL IfcLuminousIntensityDistributionMeasure; + // C++ wrapper type for IfcLuminousIntensityMeasure + typedef REAL IfcLuminousIntensityMeasure; + // C++ wrapper type for IfcMagneticFluxDensityMeasure + typedef REAL IfcMagneticFluxDensityMeasure; + // C++ wrapper type for IfcMagneticFluxMeasure + typedef REAL IfcMagneticFluxMeasure; + // C++ wrapper type for IfcMassDensityMeasure + typedef REAL IfcMassDensityMeasure; + // C++ wrapper type for IfcMassFlowRateMeasure + typedef REAL IfcMassFlowRateMeasure; + // C++ wrapper type for IfcMassMeasure + typedef REAL IfcMassMeasure; + // C++ wrapper type for IfcMassPerLengthMeasure + typedef REAL IfcMassPerLengthMeasure; + // C++ wrapper type for IfcMinuteInHour + typedef INTEGER IfcMinuteInHour; + // C++ wrapper type for IfcModulusOfElasticityMeasure + typedef REAL IfcModulusOfElasticityMeasure; + // C++ wrapper type for IfcModulusOfLinearSubgradeReactionMeasure + typedef REAL IfcModulusOfLinearSubgradeReactionMeasure; + // C++ wrapper type for IfcModulusOfRotationalSubgradeReactionMeasure + typedef REAL IfcModulusOfRotationalSubgradeReactionMeasure; + // C++ wrapper type for IfcModulusOfSubgradeReactionMeasure + typedef REAL IfcModulusOfSubgradeReactionMeasure; + // C++ wrapper type for IfcMoistureDiffusivityMeasure + typedef REAL IfcMoistureDiffusivityMeasure; + // C++ wrapper type for IfcMolecularWeightMeasure + typedef REAL IfcMolecularWeightMeasure; + // C++ wrapper type for IfcMomentOfInertiaMeasure + typedef REAL IfcMomentOfInertiaMeasure; + // C++ wrapper type for IfcMonetaryMeasure + typedef REAL IfcMonetaryMeasure; + // C++ wrapper type for IfcMonthInYearNumber + typedef INTEGER IfcMonthInYearNumber; + // C++ wrapper type for IfcNormalisedRatioMeasure + typedef REAL IfcNormalisedRatioMeasure; + // C++ wrapper type for IfcNumericMeasure + typedef NUMBER IfcNumericMeasure; + // C++ wrapper type for IfcPHMeasure + typedef REAL IfcPHMeasure; + // C++ wrapper type for IfcParameterValue + typedef REAL IfcParameterValue; + // C++ wrapper type for IfcPlanarForceMeasure + typedef REAL IfcPlanarForceMeasure; + // C++ wrapper type for IfcPlaneAngleMeasure + typedef REAL IfcPlaneAngleMeasure; + // C++ wrapper type for IfcPositiveLengthMeasure + typedef REAL IfcPositiveLengthMeasure; + // C++ wrapper type for IfcPositivePlaneAngleMeasure + typedef REAL IfcPositivePlaneAngleMeasure; + // C++ wrapper type for IfcPositiveRatioMeasure + typedef REAL IfcPositiveRatioMeasure; + // C++ wrapper type for IfcPowerMeasure + typedef REAL IfcPowerMeasure; + // C++ wrapper type for IfcPresentableText + typedef STRING IfcPresentableText; + // C++ wrapper type for IfcPressureMeasure + typedef REAL IfcPressureMeasure; + // C++ wrapper type for IfcRadioActivityMeasure + typedef REAL IfcRadioActivityMeasure; + // C++ wrapper type for IfcRatioMeasure + typedef REAL IfcRatioMeasure; + // C++ wrapper type for IfcReal + typedef REAL IfcReal; + // C++ wrapper type for IfcRotationalFrequencyMeasure + typedef REAL IfcRotationalFrequencyMeasure; + // C++ wrapper type for IfcRotationalMassMeasure + typedef REAL IfcRotationalMassMeasure; + // C++ wrapper type for IfcRotationalStiffnessMeasure + typedef REAL IfcRotationalStiffnessMeasure; + // C++ wrapper type for IfcSecondInMinute + typedef REAL IfcSecondInMinute; + // C++ wrapper type for IfcSectionModulusMeasure + typedef REAL IfcSectionModulusMeasure; + // C++ wrapper type for IfcSectionalAreaIntegralMeasure + typedef REAL IfcSectionalAreaIntegralMeasure; + // C++ wrapper type for IfcShearModulusMeasure + typedef REAL IfcShearModulusMeasure; + // C++ wrapper type for IfcSolidAngleMeasure + typedef REAL IfcSolidAngleMeasure; + // C++ wrapper type for IfcSoundPowerMeasure + typedef REAL IfcSoundPowerMeasure; + // C++ wrapper type for IfcSoundPressureMeasure + typedef REAL IfcSoundPressureMeasure; + // C++ wrapper type for IfcSpecificHeatCapacityMeasure + typedef REAL IfcSpecificHeatCapacityMeasure; + // C++ wrapper type for IfcSpecularExponent + typedef REAL IfcSpecularExponent; + // C++ wrapper type for IfcSpecularRoughness + typedef REAL IfcSpecularRoughness; + // C++ wrapper type for IfcTemperatureGradientMeasure + typedef REAL IfcTemperatureGradientMeasure; + // C++ wrapper type for IfcText + typedef STRING IfcText; + // C++ wrapper type for IfcTextAlignment + typedef STRING IfcTextAlignment; + // C++ wrapper type for IfcTextDecoration + typedef STRING IfcTextDecoration; + // C++ wrapper type for IfcTextFontName + typedef STRING IfcTextFontName; + // C++ wrapper type for IfcTextTransformation + typedef STRING IfcTextTransformation; + // C++ wrapper type for IfcThermalAdmittanceMeasure + typedef REAL IfcThermalAdmittanceMeasure; + // C++ wrapper type for IfcThermalConductivityMeasure + typedef REAL IfcThermalConductivityMeasure; + // C++ wrapper type for IfcThermalExpansionCoefficientMeasure + typedef REAL IfcThermalExpansionCoefficientMeasure; + // C++ wrapper type for IfcThermalResistanceMeasure + typedef REAL IfcThermalResistanceMeasure; + // C++ wrapper type for IfcThermalTransmittanceMeasure + typedef REAL IfcThermalTransmittanceMeasure; + // C++ wrapper type for IfcThermodynamicTemperatureMeasure + typedef REAL IfcThermodynamicTemperatureMeasure; + // C++ wrapper type for IfcTimeMeasure + typedef REAL IfcTimeMeasure; + // C++ wrapper type for IfcTimeStamp + typedef INTEGER IfcTimeStamp; + // C++ wrapper type for IfcTorqueMeasure + typedef REAL IfcTorqueMeasure; + // C++ wrapper type for IfcVaporPermeabilityMeasure + typedef REAL IfcVaporPermeabilityMeasure; + // C++ wrapper type for IfcVolumeMeasure + typedef REAL IfcVolumeMeasure; + // C++ wrapper type for IfcVolumetricFlowRateMeasure + typedef REAL IfcVolumetricFlowRateMeasure; + // C++ wrapper type for IfcWarpingConstantMeasure + typedef REAL IfcWarpingConstantMeasure; + // C++ wrapper type for IfcWarpingMomentMeasure + typedef REAL IfcWarpingMomentMeasure; + // C++ wrapper type for IfcYearNumber + typedef INTEGER IfcYearNumber; + // C++ wrapper type for IfcActionSourceTypeEnum + typedef ENUMERATION IfcActionSourceTypeEnum; + // C++ wrapper type for IfcActionTypeEnum + typedef ENUMERATION IfcActionTypeEnum; + // C++ wrapper type for IfcActuatorTypeEnum + typedef ENUMERATION IfcActuatorTypeEnum; + // C++ wrapper type for IfcAddressTypeEnum + typedef ENUMERATION IfcAddressTypeEnum; + // C++ wrapper type for IfcAheadOrBehind + typedef ENUMERATION IfcAheadOrBehind; + // C++ wrapper type for IfcAirTerminalBoxTypeEnum + typedef ENUMERATION IfcAirTerminalBoxTypeEnum; + // C++ wrapper type for IfcAirTerminalTypeEnum + typedef ENUMERATION IfcAirTerminalTypeEnum; + // C++ wrapper type for IfcAirToAirHeatRecoveryTypeEnum + typedef ENUMERATION IfcAirToAirHeatRecoveryTypeEnum; + // C++ wrapper type for IfcAlarmTypeEnum + typedef ENUMERATION IfcAlarmTypeEnum; + // C++ wrapper type for IfcAnalysisModelTypeEnum + typedef ENUMERATION IfcAnalysisModelTypeEnum; + // C++ wrapper type for IfcAnalysisTheoryTypeEnum + typedef ENUMERATION IfcAnalysisTheoryTypeEnum; + // C++ wrapper type for IfcArithmeticOperatorEnum + typedef ENUMERATION IfcArithmeticOperatorEnum; + // C++ wrapper type for IfcAssemblyPlaceEnum + typedef ENUMERATION IfcAssemblyPlaceEnum; + // C++ wrapper type for IfcBSplineCurveForm + typedef ENUMERATION IfcBSplineCurveForm; + // C++ wrapper type for IfcBeamTypeEnum + typedef ENUMERATION IfcBeamTypeEnum; + // C++ wrapper type for IfcBenchmarkEnum + typedef ENUMERATION IfcBenchmarkEnum; + // C++ wrapper type for IfcBoilerTypeEnum + typedef ENUMERATION IfcBoilerTypeEnum; + // C++ wrapper type for IfcBooleanOperator + typedef ENUMERATION IfcBooleanOperator; + // C++ wrapper type for IfcBuildingElementProxyTypeEnum + typedef ENUMERATION IfcBuildingElementProxyTypeEnum; + // C++ wrapper type for IfcCableCarrierFittingTypeEnum + typedef ENUMERATION IfcCableCarrierFittingTypeEnum; + // C++ wrapper type for IfcCableCarrierSegmentTypeEnum + typedef ENUMERATION IfcCableCarrierSegmentTypeEnum; + // C++ wrapper type for IfcCableSegmentTypeEnum + typedef ENUMERATION IfcCableSegmentTypeEnum; + // C++ wrapper type for IfcChangeActionEnum + typedef ENUMERATION IfcChangeActionEnum; + // C++ wrapper type for IfcChillerTypeEnum + typedef ENUMERATION IfcChillerTypeEnum; + // C++ wrapper type for IfcCoilTypeEnum + typedef ENUMERATION IfcCoilTypeEnum; + // C++ wrapper type for IfcColumnTypeEnum + typedef ENUMERATION IfcColumnTypeEnum; + // C++ wrapper type for IfcCompressorTypeEnum + typedef ENUMERATION IfcCompressorTypeEnum; + // C++ wrapper type for IfcCondenserTypeEnum + typedef ENUMERATION IfcCondenserTypeEnum; + // C++ wrapper type for IfcConnectionTypeEnum + typedef ENUMERATION IfcConnectionTypeEnum; + // C++ wrapper type for IfcConstraintEnum + typedef ENUMERATION IfcConstraintEnum; + // C++ wrapper type for IfcControllerTypeEnum + typedef ENUMERATION IfcControllerTypeEnum; + // C++ wrapper type for IfcCooledBeamTypeEnum + typedef ENUMERATION IfcCooledBeamTypeEnum; + // C++ wrapper type for IfcCoolingTowerTypeEnum + typedef ENUMERATION IfcCoolingTowerTypeEnum; + // C++ wrapper type for IfcCostScheduleTypeEnum + typedef ENUMERATION IfcCostScheduleTypeEnum; + // C++ wrapper type for IfcCoveringTypeEnum + typedef ENUMERATION IfcCoveringTypeEnum; + // C++ wrapper type for IfcCurrencyEnum + typedef ENUMERATION IfcCurrencyEnum; + // C++ wrapper type for IfcCurtainWallTypeEnum + typedef ENUMERATION IfcCurtainWallTypeEnum; + // C++ wrapper type for IfcDamperTypeEnum + typedef ENUMERATION IfcDamperTypeEnum; + // C++ wrapper type for IfcDataOriginEnum + typedef ENUMERATION IfcDataOriginEnum; + // C++ wrapper type for IfcDerivedUnitEnum + typedef ENUMERATION IfcDerivedUnitEnum; + // C++ wrapper type for IfcDimensionExtentUsage + typedef ENUMERATION IfcDimensionExtentUsage; + // C++ wrapper type for IfcDirectionSenseEnum + typedef ENUMERATION IfcDirectionSenseEnum; + // C++ wrapper type for IfcDistributionChamberElementTypeEnum + typedef ENUMERATION IfcDistributionChamberElementTypeEnum; + // C++ wrapper type for IfcDocumentConfidentialityEnum + typedef ENUMERATION IfcDocumentConfidentialityEnum; + // C++ wrapper type for IfcDocumentStatusEnum + typedef ENUMERATION IfcDocumentStatusEnum; + // C++ wrapper type for IfcDoorPanelOperationEnum + typedef ENUMERATION IfcDoorPanelOperationEnum; + // C++ wrapper type for IfcDoorPanelPositionEnum + typedef ENUMERATION IfcDoorPanelPositionEnum; + // C++ wrapper type for IfcDoorStyleConstructionEnum + typedef ENUMERATION IfcDoorStyleConstructionEnum; + // C++ wrapper type for IfcDoorStyleOperationEnum + typedef ENUMERATION IfcDoorStyleOperationEnum; + // C++ wrapper type for IfcDuctFittingTypeEnum + typedef ENUMERATION IfcDuctFittingTypeEnum; + // C++ wrapper type for IfcDuctSegmentTypeEnum + typedef ENUMERATION IfcDuctSegmentTypeEnum; + // C++ wrapper type for IfcDuctSilencerTypeEnum + typedef ENUMERATION IfcDuctSilencerTypeEnum; + // C++ wrapper type for IfcElectricApplianceTypeEnum + typedef ENUMERATION IfcElectricApplianceTypeEnum; + // C++ wrapper type for IfcElectricCurrentEnum + typedef ENUMERATION IfcElectricCurrentEnum; + // C++ wrapper type for IfcElectricDistributionPointFunctionEnum + typedef ENUMERATION IfcElectricDistributionPointFunctionEnum; + // C++ wrapper type for IfcElectricFlowStorageDeviceTypeEnum + typedef ENUMERATION IfcElectricFlowStorageDeviceTypeEnum; + // C++ wrapper type for IfcElectricGeneratorTypeEnum + typedef ENUMERATION IfcElectricGeneratorTypeEnum; + // C++ wrapper type for IfcElectricHeaterTypeEnum + typedef ENUMERATION IfcElectricHeaterTypeEnum; + // C++ wrapper type for IfcElectricMotorTypeEnum + typedef ENUMERATION IfcElectricMotorTypeEnum; + // C++ wrapper type for IfcElectricTimeControlTypeEnum + typedef ENUMERATION IfcElectricTimeControlTypeEnum; + // C++ wrapper type for IfcElementAssemblyTypeEnum + typedef ENUMERATION IfcElementAssemblyTypeEnum; + // C++ wrapper type for IfcElementCompositionEnum + typedef ENUMERATION IfcElementCompositionEnum; + // C++ wrapper type for IfcEnergySequenceEnum + typedef ENUMERATION IfcEnergySequenceEnum; + // C++ wrapper type for IfcEnvironmentalImpactCategoryEnum + typedef ENUMERATION IfcEnvironmentalImpactCategoryEnum; + // C++ wrapper type for IfcEvaporativeCoolerTypeEnum + typedef ENUMERATION IfcEvaporativeCoolerTypeEnum; + // C++ wrapper type for IfcEvaporatorTypeEnum + typedef ENUMERATION IfcEvaporatorTypeEnum; + // C++ wrapper type for IfcFanTypeEnum + typedef ENUMERATION IfcFanTypeEnum; + // C++ wrapper type for IfcFilterTypeEnum + typedef ENUMERATION IfcFilterTypeEnum; + // C++ wrapper type for IfcFireSuppressionTerminalTypeEnum + typedef ENUMERATION IfcFireSuppressionTerminalTypeEnum; + // C++ wrapper type for IfcFlowDirectionEnum + typedef ENUMERATION IfcFlowDirectionEnum; + // C++ wrapper type for IfcFlowInstrumentTypeEnum + typedef ENUMERATION IfcFlowInstrumentTypeEnum; + // C++ wrapper type for IfcFlowMeterTypeEnum + typedef ENUMERATION IfcFlowMeterTypeEnum; + // C++ wrapper type for IfcFootingTypeEnum + typedef ENUMERATION IfcFootingTypeEnum; + // C++ wrapper type for IfcGasTerminalTypeEnum + typedef ENUMERATION IfcGasTerminalTypeEnum; + // C++ wrapper type for IfcGeometricProjectionEnum + typedef ENUMERATION IfcGeometricProjectionEnum; + // C++ wrapper type for IfcGlobalOrLocalEnum + typedef ENUMERATION IfcGlobalOrLocalEnum; + // C++ wrapper type for IfcHeatExchangerTypeEnum + typedef ENUMERATION IfcHeatExchangerTypeEnum; + // C++ wrapper type for IfcHumidifierTypeEnum + typedef ENUMERATION IfcHumidifierTypeEnum; + // C++ wrapper type for IfcInternalOrExternalEnum + typedef ENUMERATION IfcInternalOrExternalEnum; + // C++ wrapper type for IfcInventoryTypeEnum + typedef ENUMERATION IfcInventoryTypeEnum; + // C++ wrapper type for IfcJunctionBoxTypeEnum + typedef ENUMERATION IfcJunctionBoxTypeEnum; + // C++ wrapper type for IfcLampTypeEnum + typedef ENUMERATION IfcLampTypeEnum; + // C++ wrapper type for IfcLayerSetDirectionEnum + typedef ENUMERATION IfcLayerSetDirectionEnum; + // C++ wrapper type for IfcLightDistributionCurveEnum + typedef ENUMERATION IfcLightDistributionCurveEnum; + // C++ wrapper type for IfcLightEmissionSourceEnum + typedef ENUMERATION IfcLightEmissionSourceEnum; + // C++ wrapper type for IfcLightFixtureTypeEnum + typedef ENUMERATION IfcLightFixtureTypeEnum; + // C++ wrapper type for IfcLoadGroupTypeEnum + typedef ENUMERATION IfcLoadGroupTypeEnum; + // C++ wrapper type for IfcLogicalOperatorEnum + typedef ENUMERATION IfcLogicalOperatorEnum; + // C++ wrapper type for IfcMemberTypeEnum + typedef ENUMERATION IfcMemberTypeEnum; + // C++ wrapper type for IfcMotorConnectionTypeEnum + typedef ENUMERATION IfcMotorConnectionTypeEnum; + // C++ wrapper type for IfcNullStyle + typedef ENUMERATION IfcNullStyle; + // C++ wrapper type for IfcObjectTypeEnum + typedef ENUMERATION IfcObjectTypeEnum; + // C++ wrapper type for IfcObjectiveEnum + typedef ENUMERATION IfcObjectiveEnum; + // C++ wrapper type for IfcOccupantTypeEnum + typedef ENUMERATION IfcOccupantTypeEnum; + // C++ wrapper type for IfcOutletTypeEnum + typedef ENUMERATION IfcOutletTypeEnum; + // C++ wrapper type for IfcPermeableCoveringOperationEnum + typedef ENUMERATION IfcPermeableCoveringOperationEnum; + // C++ wrapper type for IfcPhysicalOrVirtualEnum + typedef ENUMERATION IfcPhysicalOrVirtualEnum; + // C++ wrapper type for IfcPileConstructionEnum + typedef ENUMERATION IfcPileConstructionEnum; + // C++ wrapper type for IfcPileTypeEnum + typedef ENUMERATION IfcPileTypeEnum; + // C++ wrapper type for IfcPipeFittingTypeEnum + typedef ENUMERATION IfcPipeFittingTypeEnum; + // C++ wrapper type for IfcPipeSegmentTypeEnum + typedef ENUMERATION IfcPipeSegmentTypeEnum; + // C++ wrapper type for IfcPlateTypeEnum + typedef ENUMERATION IfcPlateTypeEnum; + // C++ wrapper type for IfcProcedureTypeEnum + typedef ENUMERATION IfcProcedureTypeEnum; + // C++ wrapper type for IfcProfileTypeEnum + typedef ENUMERATION IfcProfileTypeEnum; + // C++ wrapper type for IfcProjectOrderRecordTypeEnum + typedef ENUMERATION IfcProjectOrderRecordTypeEnum; + // C++ wrapper type for IfcProjectOrderTypeEnum + typedef ENUMERATION IfcProjectOrderTypeEnum; + // C++ wrapper type for IfcProjectedOrTrueLengthEnum + typedef ENUMERATION IfcProjectedOrTrueLengthEnum; + // C++ wrapper type for IfcPropertySourceEnum + typedef ENUMERATION IfcPropertySourceEnum; + // C++ wrapper type for IfcProtectiveDeviceTypeEnum + typedef ENUMERATION IfcProtectiveDeviceTypeEnum; + // C++ wrapper type for IfcPumpTypeEnum + typedef ENUMERATION IfcPumpTypeEnum; + // C++ wrapper type for IfcRailingTypeEnum + typedef ENUMERATION IfcRailingTypeEnum; + // C++ wrapper type for IfcRampFlightTypeEnum + typedef ENUMERATION IfcRampFlightTypeEnum; + // C++ wrapper type for IfcRampTypeEnum + typedef ENUMERATION IfcRampTypeEnum; + // C++ wrapper type for IfcReflectanceMethodEnum + typedef ENUMERATION IfcReflectanceMethodEnum; + // C++ wrapper type for IfcReinforcingBarRoleEnum + typedef ENUMERATION IfcReinforcingBarRoleEnum; + // C++ wrapper type for IfcReinforcingBarSurfaceEnum + typedef ENUMERATION IfcReinforcingBarSurfaceEnum; + // C++ wrapper type for IfcResourceConsumptionEnum + typedef ENUMERATION IfcResourceConsumptionEnum; + // C++ wrapper type for IfcRibPlateDirectionEnum + typedef ENUMERATION IfcRibPlateDirectionEnum; + // C++ wrapper type for IfcRoleEnum + typedef ENUMERATION IfcRoleEnum; + // C++ wrapper type for IfcRoofTypeEnum + typedef ENUMERATION IfcRoofTypeEnum; + // C++ wrapper type for IfcSIPrefix + typedef ENUMERATION IfcSIPrefix; + // C++ wrapper type for IfcSIUnitName + typedef ENUMERATION IfcSIUnitName; + // C++ wrapper type for IfcSanitaryTerminalTypeEnum + typedef ENUMERATION IfcSanitaryTerminalTypeEnum; + // C++ wrapper type for IfcSectionTypeEnum + typedef ENUMERATION IfcSectionTypeEnum; + // C++ wrapper type for IfcSensorTypeEnum + typedef ENUMERATION IfcSensorTypeEnum; + // C++ wrapper type for IfcSequenceEnum + typedef ENUMERATION IfcSequenceEnum; + // C++ wrapper type for IfcServiceLifeFactorTypeEnum + typedef ENUMERATION IfcServiceLifeFactorTypeEnum; + // C++ wrapper type for IfcServiceLifeTypeEnum + typedef ENUMERATION IfcServiceLifeTypeEnum; + // C++ wrapper type for IfcSlabTypeEnum + typedef ENUMERATION IfcSlabTypeEnum; + // C++ wrapper type for IfcSoundScaleEnum + typedef ENUMERATION IfcSoundScaleEnum; + // C++ wrapper type for IfcSpaceHeaterTypeEnum + typedef ENUMERATION IfcSpaceHeaterTypeEnum; + // C++ wrapper type for IfcSpaceTypeEnum + typedef ENUMERATION IfcSpaceTypeEnum; + // C++ wrapper type for IfcStackTerminalTypeEnum + typedef ENUMERATION IfcStackTerminalTypeEnum; + // C++ wrapper type for IfcStairFlightTypeEnum + typedef ENUMERATION IfcStairFlightTypeEnum; + // C++ wrapper type for IfcStairTypeEnum + typedef ENUMERATION IfcStairTypeEnum; + // C++ wrapper type for IfcStateEnum + typedef ENUMERATION IfcStateEnum; + // C++ wrapper type for IfcStructuralCurveTypeEnum + typedef ENUMERATION IfcStructuralCurveTypeEnum; + // C++ wrapper type for IfcStructuralSurfaceTypeEnum + typedef ENUMERATION IfcStructuralSurfaceTypeEnum; + // C++ wrapper type for IfcSurfaceSide + typedef ENUMERATION IfcSurfaceSide; + // C++ wrapper type for IfcSurfaceTextureEnum + typedef ENUMERATION IfcSurfaceTextureEnum; + // C++ wrapper type for IfcSwitchingDeviceTypeEnum + typedef ENUMERATION IfcSwitchingDeviceTypeEnum; + // C++ wrapper type for IfcTankTypeEnum + typedef ENUMERATION IfcTankTypeEnum; + // C++ wrapper type for IfcTendonTypeEnum + typedef ENUMERATION IfcTendonTypeEnum; + // C++ wrapper type for IfcTextPath + typedef ENUMERATION IfcTextPath; + // C++ wrapper type for IfcThermalLoadSourceEnum + typedef ENUMERATION IfcThermalLoadSourceEnum; + // C++ wrapper type for IfcThermalLoadTypeEnum + typedef ENUMERATION IfcThermalLoadTypeEnum; + // C++ wrapper type for IfcTimeSeriesDataTypeEnum + typedef ENUMERATION IfcTimeSeriesDataTypeEnum; + // C++ wrapper type for IfcTimeSeriesScheduleTypeEnum + typedef ENUMERATION IfcTimeSeriesScheduleTypeEnum; + // C++ wrapper type for IfcTransformerTypeEnum + typedef ENUMERATION IfcTransformerTypeEnum; + // C++ wrapper type for IfcTransitionCode + typedef ENUMERATION IfcTransitionCode; + // C++ wrapper type for IfcTransportElementTypeEnum + typedef ENUMERATION IfcTransportElementTypeEnum; + // C++ wrapper type for IfcTrimmingPreference + typedef ENUMERATION IfcTrimmingPreference; + // C++ wrapper type for IfcTubeBundleTypeEnum + typedef ENUMERATION IfcTubeBundleTypeEnum; + // C++ wrapper type for IfcUnitEnum + typedef ENUMERATION IfcUnitEnum; + // C++ wrapper type for IfcUnitaryEquipmentTypeEnum + typedef ENUMERATION IfcUnitaryEquipmentTypeEnum; + // C++ wrapper type for IfcValveTypeEnum + typedef ENUMERATION IfcValveTypeEnum; + // C++ wrapper type for IfcVibrationIsolatorTypeEnum + typedef ENUMERATION IfcVibrationIsolatorTypeEnum; + // C++ wrapper type for IfcWallTypeEnum + typedef ENUMERATION IfcWallTypeEnum; + // C++ wrapper type for IfcWasteTerminalTypeEnum + typedef ENUMERATION IfcWasteTerminalTypeEnum; + // C++ wrapper type for IfcWindowPanelOperationEnum + typedef ENUMERATION IfcWindowPanelOperationEnum; + // C++ wrapper type for IfcWindowPanelPositionEnum + typedef ENUMERATION IfcWindowPanelPositionEnum; + // C++ wrapper type for IfcWindowStyleConstructionEnum + typedef ENUMERATION IfcWindowStyleConstructionEnum; + // C++ wrapper type for IfcWindowStyleOperationEnum + typedef ENUMERATION IfcWindowStyleOperationEnum; + // C++ wrapper type for IfcWorkControlTypeEnum + typedef ENUMERATION IfcWorkControlTypeEnum; + // C++ wrapper type for IfcActorSelect + typedef SELECT IfcActorSelect; + // C++ wrapper type for IfcAppliedValueSelect + typedef SELECT IfcAppliedValueSelect; + // C++ wrapper type for IfcAxis2Placement + typedef SELECT IfcAxis2Placement; + // C++ wrapper type for IfcBooleanOperand + typedef SELECT IfcBooleanOperand; + // C++ wrapper type for IfcCharacterStyleSelect + typedef SELECT IfcCharacterStyleSelect; + // C++ wrapper type for IfcClassificationNotationSelect + typedef SELECT IfcClassificationNotationSelect; + // C++ wrapper type for IfcColour + typedef SELECT IfcColour; + // C++ wrapper type for IfcColourOrFactor + typedef SELECT IfcColourOrFactor; + // C++ wrapper type for IfcConditionCriterionSelect + typedef SELECT IfcConditionCriterionSelect; + // C++ wrapper type for IfcCsgSelect + typedef SELECT IfcCsgSelect; + // C++ wrapper type for IfcCurveFontOrScaledCurveFontSelect + typedef SELECT IfcCurveFontOrScaledCurveFontSelect; + // C++ wrapper type for IfcCurveOrEdgeCurve + typedef SELECT IfcCurveOrEdgeCurve; + // C++ wrapper type for IfcCurveStyleFontSelect + typedef SELECT IfcCurveStyleFontSelect; + // C++ wrapper type for IfcDateTimeSelect + typedef SELECT IfcDateTimeSelect; + // C++ wrapper type for IfcDefinedSymbolSelect + typedef SELECT IfcDefinedSymbolSelect; + // C++ wrapper type for IfcDerivedMeasureValue + typedef SELECT IfcDerivedMeasureValue; + // C++ wrapper type for IfcDocumentSelect + typedef SELECT IfcDocumentSelect; + // C++ wrapper type for IfcDraughtingCalloutElement + typedef SELECT IfcDraughtingCalloutElement; + // C++ wrapper type for IfcFillAreaStyleTileShapeSelect + typedef SELECT IfcFillAreaStyleTileShapeSelect; + // C++ wrapper type for IfcFillStyleSelect + typedef SELECT IfcFillStyleSelect; + // C++ wrapper type for IfcGeometricSetSelect + typedef SELECT IfcGeometricSetSelect; + // C++ wrapper type for IfcHatchLineDistanceSelect + typedef SELECT IfcHatchLineDistanceSelect; + // C++ wrapper type for IfcLayeredItem + typedef SELECT IfcLayeredItem; + // C++ wrapper type for IfcLibrarySelect + typedef SELECT IfcLibrarySelect; + // C++ wrapper type for IfcLightDistributionDataSourceSelect + typedef SELECT IfcLightDistributionDataSourceSelect; + // C++ wrapper type for IfcMaterialSelect + typedef SELECT IfcMaterialSelect; + // C++ wrapper type for IfcMeasureValue + typedef SELECT IfcMeasureValue; + // C++ wrapper type for IfcMetricValueSelect + typedef SELECT IfcMetricValueSelect; + // C++ wrapper type for IfcObjectReferenceSelect + typedef SELECT IfcObjectReferenceSelect; + // C++ wrapper type for IfcOrientationSelect + typedef SELECT IfcOrientationSelect; + // C++ wrapper type for IfcPointOrVertexPoint + typedef SELECT IfcPointOrVertexPoint; + // C++ wrapper type for IfcPresentationStyleSelect + typedef SELECT IfcPresentationStyleSelect; + // C++ wrapper type for IfcShell + typedef SELECT IfcShell; + // C++ wrapper type for IfcSimpleValue + typedef SELECT IfcSimpleValue; + // C++ wrapper type for IfcSizeSelect + typedef SELECT IfcSizeSelect; + // C++ wrapper type for IfcSpecularHighlightSelect + typedef SELECT IfcSpecularHighlightSelect; + // C++ wrapper type for IfcStructuralActivityAssignmentSelect + typedef SELECT IfcStructuralActivityAssignmentSelect; + // C++ wrapper type for IfcSurfaceOrFaceSurface + typedef SELECT IfcSurfaceOrFaceSurface; + // C++ wrapper type for IfcSurfaceStyleElementSelect + typedef SELECT IfcSurfaceStyleElementSelect; + // C++ wrapper type for IfcSymbolStyleSelect + typedef SELECT IfcSymbolStyleSelect; + // C++ wrapper type for IfcTextFontSelect + typedef SELECT IfcTextFontSelect; + // C++ wrapper type for IfcTextStyleSelect + typedef SELECT IfcTextStyleSelect; + // C++ wrapper type for IfcTrimmingSelect + typedef SELECT IfcTrimmingSelect; + // C++ wrapper type for IfcUnit + typedef SELECT IfcUnit; + // C++ wrapper type for IfcValue + typedef SELECT IfcValue; + // C++ wrapper type for IfcVectorOrDirection + typedef SELECT IfcVectorOrDirection; + + + // ****************************************************************************** + // IFC Entities + // ****************************************************************************** + + struct IfcRepresentationItem; + struct IfcGeometricRepresentationItem; + struct IfcCurve; + struct IfcBoundedCurve; + struct IfcCompositeCurve; + struct Ifc2DCompositeCurve; + struct IfcRoot; + struct IfcObjectDefinition; + struct IfcObject; + struct IfcControl; + struct IfcActionRequest; + struct IfcActor; + typedef NotImplemented IfcActorRole; // (not currently used by Assimp) + struct IfcTypeObject; + struct IfcTypeProduct; + struct IfcElementType; + struct IfcDistributionElementType; + struct IfcDistributionControlElementType; + struct IfcActuatorType; + typedef NotImplemented IfcAddress; // (not currently used by Assimp) + struct IfcDistributionFlowElementType; + struct IfcFlowControllerType; + struct IfcAirTerminalBoxType; + struct IfcFlowTerminalType; + struct IfcAirTerminalType; + struct IfcEnergyConversionDeviceType; + struct IfcAirToAirHeatRecoveryType; + struct IfcAlarmType; + struct IfcDraughtingCallout; + struct IfcDimensionCurveDirectedCallout; + struct IfcAngularDimension; + struct IfcProduct; + struct IfcAnnotation; + struct IfcStyledItem; + struct IfcAnnotationOccurrence; + struct IfcAnnotationCurveOccurrence; + struct IfcAnnotationFillArea; + struct IfcAnnotationFillAreaOccurrence; + struct IfcAnnotationSurface; + struct IfcAnnotationSurfaceOccurrence; + struct IfcAnnotationSymbolOccurrence; + struct IfcAnnotationTextOccurrence; + typedef NotImplemented IfcApplication; // (not currently used by Assimp) + typedef NotImplemented IfcAppliedValue; // (not currently used by Assimp) + typedef NotImplemented IfcAppliedValueRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcApproval; // (not currently used by Assimp) + typedef NotImplemented IfcApprovalActorRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcApprovalPropertyRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcApprovalRelationship; // (not currently used by Assimp) + struct IfcProfileDef; + struct IfcArbitraryClosedProfileDef; + struct IfcArbitraryOpenProfileDef; + struct IfcArbitraryProfileDefWithVoids; + struct IfcGroup; + struct IfcAsset; + struct IfcParameterizedProfileDef; + struct IfcIShapeProfileDef; + struct IfcAsymmetricIShapeProfileDef; + struct IfcPlacement; + struct IfcAxis1Placement; + struct IfcAxis2Placement2D; + struct IfcAxis2Placement3D; + struct IfcBSplineCurve; + struct IfcElement; + struct IfcBuildingElement; + struct IfcBeam; + struct IfcBuildingElementType; + struct IfcBeamType; + struct IfcBezierCurve; + typedef NotImplemented IfcSurfaceTexture; // (not currently used by Assimp) + typedef NotImplemented IfcBlobTexture; // (not currently used by Assimp) + struct IfcCsgPrimitive3D; + struct IfcBlock; + struct IfcBoilerType; + struct IfcBooleanResult; + struct IfcBooleanClippingResult; + typedef NotImplemented IfcBoundaryCondition; // (not currently used by Assimp) + typedef NotImplemented IfcBoundaryEdgeCondition; // (not currently used by Assimp) + typedef NotImplemented IfcBoundaryFaceCondition; // (not currently used by Assimp) + typedef NotImplemented IfcBoundaryNodeCondition; // (not currently used by Assimp) + typedef NotImplemented IfcBoundaryNodeConditionWarping; // (not currently used by Assimp) + struct IfcSurface; + struct IfcBoundedSurface; + struct IfcBoundingBox; + struct IfcHalfSpaceSolid; + struct IfcBoxedHalfSpace; + struct IfcSpatialStructureElement; + struct IfcBuilding; + struct IfcBuildingElementComponent; + struct IfcBuildingElementPart; + struct IfcBuildingElementProxy; + struct IfcBuildingElementProxyType; + struct IfcBuildingStorey; + struct IfcCShapeProfileDef; + struct IfcFlowFittingType; + struct IfcCableCarrierFittingType; + struct IfcFlowSegmentType; + struct IfcCableCarrierSegmentType; + struct IfcCableSegmentType; + typedef NotImplemented IfcCalendarDate; // (not currently used by Assimp) + struct IfcPoint; + struct IfcCartesianPoint; + struct IfcCartesianTransformationOperator; + struct IfcCartesianTransformationOperator2D; + struct IfcCartesianTransformationOperator2DnonUniform; + struct IfcCartesianTransformationOperator3D; + struct IfcCartesianTransformationOperator3DnonUniform; + struct IfcCenterLineProfileDef; + struct IfcFeatureElement; + struct IfcFeatureElementSubtraction; + struct IfcEdgeFeature; + struct IfcChamferEdgeFeature; + struct IfcChillerType; + struct IfcConic; + struct IfcCircle; + struct IfcCircleProfileDef; + struct IfcCircleHollowProfileDef; + typedef NotImplemented IfcClassification; // (not currently used by Assimp) + typedef NotImplemented IfcClassificationItem; // (not currently used by Assimp) + typedef NotImplemented IfcClassificationItemRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcClassificationNotation; // (not currently used by Assimp) + typedef NotImplemented IfcClassificationNotationFacet; // (not currently used by Assimp) + typedef NotImplemented IfcExternalReference; // (not currently used by Assimp) + typedef NotImplemented IfcClassificationReference; // (not currently used by Assimp) + struct IfcTopologicalRepresentationItem; + struct IfcConnectedFaceSet; + struct IfcClosedShell; + struct IfcCoilType; + struct IfcColourSpecification; + struct IfcColourRgb; + struct IfcColumn; + struct IfcColumnType; + struct IfcProperty; + struct IfcComplexProperty; + struct IfcCompositeCurveSegment; + struct IfcCompositeProfileDef; + struct IfcFlowMovingDeviceType; + struct IfcCompressorType; + struct IfcCondenserType; + struct IfcCondition; + struct IfcConditionCriterion; + typedef NotImplemented IfcConnectionGeometry; // (not currently used by Assimp) + typedef NotImplemented IfcConnectionCurveGeometry; // (not currently used by Assimp) + typedef NotImplemented IfcConnectionPointGeometry; // (not currently used by Assimp) + typedef NotImplemented IfcConnectionPointEccentricity; // (not currently used by Assimp) + typedef NotImplemented IfcConnectionPortGeometry; // (not currently used by Assimp) + typedef NotImplemented IfcConnectionSurfaceGeometry; // (not currently used by Assimp) + typedef NotImplemented IfcConstraint; // (not currently used by Assimp) + typedef NotImplemented IfcConstraintAggregationRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcConstraintClassificationRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcConstraintRelationship; // (not currently used by Assimp) + struct IfcResource; + struct IfcConstructionResource; + struct IfcConstructionEquipmentResource; + struct IfcConstructionMaterialResource; + struct IfcConstructionProductResource; + struct IfcNamedUnit; + struct IfcContextDependentUnit; + struct IfcControllerType; + struct IfcConversionBasedUnit; + struct IfcCooledBeamType; + struct IfcCoolingTowerType; + typedef NotImplemented IfcCoordinatedUniversalTimeOffset; // (not currently used by Assimp) + struct IfcCostItem; + struct IfcCostSchedule; + typedef NotImplemented IfcCostValue; // (not currently used by Assimp) + struct IfcCovering; + struct IfcCoveringType; + struct IfcCraneRailAShapeProfileDef; + struct IfcCraneRailFShapeProfileDef; + struct IfcCrewResource; + struct IfcSolidModel; + struct IfcCsgSolid; + typedef NotImplemented IfcCurrencyRelationship; // (not currently used by Assimp) + struct IfcCurtainWall; + struct IfcCurtainWallType; + struct IfcCurveBoundedPlane; + struct IfcPresentationStyle; + typedef NotImplemented IfcCurveStyle; // (not currently used by Assimp) + typedef NotImplemented IfcCurveStyleFont; // (not currently used by Assimp) + typedef NotImplemented IfcCurveStyleFontAndScaling; // (not currently used by Assimp) + typedef NotImplemented IfcCurveStyleFontPattern; // (not currently used by Assimp) + struct IfcDamperType; + typedef NotImplemented IfcDateAndTime; // (not currently used by Assimp) + struct IfcDefinedSymbol; + struct IfcDerivedProfileDef; + typedef NotImplemented IfcDerivedUnit; // (not currently used by Assimp) + typedef NotImplemented IfcDerivedUnitElement; // (not currently used by Assimp) + struct IfcDiameterDimension; + typedef NotImplemented IfcDraughtingCalloutRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcDimensionCalloutRelationship; // (not currently used by Assimp) + struct IfcDimensionCurve; + struct IfcTerminatorSymbol; + struct IfcDimensionCurveTerminator; + typedef NotImplemented IfcDimensionPair; // (not currently used by Assimp) + typedef NotImplemented IfcDimensionalExponents; // (not currently used by Assimp) + struct IfcDirection; + struct IfcElementComponent; + struct IfcDiscreteAccessory; + struct IfcElementComponentType; + struct IfcDiscreteAccessoryType; + struct IfcDistributionElement; + struct IfcDistributionFlowElement; + struct IfcDistributionChamberElement; + struct IfcDistributionChamberElementType; + struct IfcDistributionControlElement; + struct IfcPort; + struct IfcDistributionPort; + typedef NotImplemented IfcDocumentElectronicFormat; // (not currently used by Assimp) + typedef NotImplemented IfcDocumentInformation; // (not currently used by Assimp) + typedef NotImplemented IfcDocumentInformationRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcDocumentReference; // (not currently used by Assimp) + struct IfcDoor; + struct IfcPropertyDefinition; + struct IfcPropertySetDefinition; + typedef NotImplemented IfcDoorLiningProperties; // (not currently used by Assimp) + typedef NotImplemented IfcDoorPanelProperties; // (not currently used by Assimp) + struct IfcDoorStyle; + typedef NotImplemented IfcPreDefinedItem; // (not currently used by Assimp) + typedef NotImplemented IfcPreDefinedColour; // (not currently used by Assimp) + typedef NotImplemented IfcDraughtingPreDefinedColour; // (not currently used by Assimp) + typedef NotImplemented IfcPreDefinedCurveFont; // (not currently used by Assimp) + typedef NotImplemented IfcDraughtingPreDefinedCurveFont; // (not currently used by Assimp) + typedef NotImplemented IfcPreDefinedTextFont; // (not currently used by Assimp) + typedef NotImplemented IfcDraughtingPreDefinedTextFont; // (not currently used by Assimp) + struct IfcDuctFittingType; + struct IfcDuctSegmentType; + struct IfcFlowTreatmentDeviceType; + struct IfcDuctSilencerType; + struct IfcEdge; + struct IfcEdgeCurve; + struct IfcLoop; + struct IfcEdgeLoop; + struct IfcElectricApplianceType; + struct IfcFlowController; + struct IfcElectricDistributionPoint; + struct IfcFlowStorageDeviceType; + struct IfcElectricFlowStorageDeviceType; + struct IfcElectricGeneratorType; + struct IfcElectricHeaterType; + struct IfcElectricMotorType; + struct IfcElectricTimeControlType; + typedef NotImplemented IfcEnergyProperties; // (not currently used by Assimp) + typedef NotImplemented IfcElectricalBaseProperties; // (not currently used by Assimp) + struct IfcSystem; + struct IfcElectricalCircuit; + struct IfcElectricalElement; + struct IfcElementAssembly; + struct IfcElementQuantity; + struct IfcElementarySurface; + struct IfcEllipse; + struct IfcEllipseProfileDef; + struct IfcEnergyConversionDevice; + typedef NotImplemented IfcEnvironmentalImpactValue; // (not currently used by Assimp) + struct IfcEquipmentElement; + struct IfcEquipmentStandard; + struct IfcEvaporativeCoolerType; + struct IfcEvaporatorType; + typedef NotImplemented IfcMaterialProperties; // (not currently used by Assimp) + typedef NotImplemented IfcExtendedMaterialProperties; // (not currently used by Assimp) + typedef NotImplemented IfcExternallyDefinedHatchStyle; // (not currently used by Assimp) + typedef NotImplemented IfcExternallyDefinedSurfaceStyle; // (not currently used by Assimp) + typedef NotImplemented IfcExternallyDefinedSymbol; // (not currently used by Assimp) + typedef NotImplemented IfcExternallyDefinedTextFont; // (not currently used by Assimp) + struct IfcSweptAreaSolid; + struct IfcExtrudedAreaSolid; + struct IfcFace; + struct IfcFaceBasedSurfaceModel; + struct IfcFaceBound; + struct IfcFaceOuterBound; + struct IfcFaceSurface; + struct IfcManifoldSolidBrep; + struct IfcFacetedBrep; + struct IfcFacetedBrepWithVoids; + typedef NotImplemented IfcStructuralConnectionCondition; // (not currently used by Assimp) + typedef NotImplemented IfcFailureConnectionCondition; // (not currently used by Assimp) + struct IfcFanType; + struct IfcFastener; + struct IfcFastenerType; + struct IfcFeatureElementAddition; + typedef NotImplemented IfcFillAreaStyle; // (not currently used by Assimp) + struct IfcFillAreaStyleHatching; + struct IfcFillAreaStyleTileSymbolWithStyle; + struct IfcFillAreaStyleTiles; + struct IfcFilterType; + struct IfcFireSuppressionTerminalType; + struct IfcFlowFitting; + struct IfcFlowInstrumentType; + struct IfcFlowMeterType; + struct IfcFlowMovingDevice; + struct IfcFlowSegment; + struct IfcFlowStorageDevice; + struct IfcFlowTerminal; + struct IfcFlowTreatmentDevice; + typedef NotImplemented IfcFluidFlowProperties; // (not currently used by Assimp) + struct IfcFooting; + typedef NotImplemented IfcFuelProperties; // (not currently used by Assimp) + struct IfcFurnishingElement; + struct IfcFurnishingElementType; + struct IfcFurnitureStandard; + struct IfcFurnitureType; + struct IfcGasTerminalType; + typedef NotImplemented IfcGeneralMaterialProperties; // (not currently used by Assimp) + typedef NotImplemented IfcProfileProperties; // (not currently used by Assimp) + typedef NotImplemented IfcGeneralProfileProperties; // (not currently used by Assimp) + struct IfcGeometricSet; + struct IfcGeometricCurveSet; + struct IfcRepresentationContext; + struct IfcGeometricRepresentationContext; + struct IfcGeometricRepresentationSubContext; + struct IfcGrid; + typedef NotImplemented IfcGridAxis; // (not currently used by Assimp) + struct IfcObjectPlacement; + struct IfcGridPlacement; + struct IfcHeatExchangerType; + struct IfcHumidifierType; + typedef NotImplemented IfcHygroscopicMaterialProperties; // (not currently used by Assimp) + typedef NotImplemented IfcImageTexture; // (not currently used by Assimp) + struct IfcInventory; + typedef NotImplemented IfcTimeSeries; // (not currently used by Assimp) + typedef NotImplemented IfcIrregularTimeSeries; // (not currently used by Assimp) + typedef NotImplemented IfcIrregularTimeSeriesValue; // (not currently used by Assimp) + struct IfcJunctionBoxType; + struct IfcLShapeProfileDef; + struct IfcLaborResource; + struct IfcLampType; + typedef NotImplemented IfcLibraryInformation; // (not currently used by Assimp) + typedef NotImplemented IfcLibraryReference; // (not currently used by Assimp) + typedef NotImplemented IfcLightDistributionData; // (not currently used by Assimp) + struct IfcLightFixtureType; + typedef NotImplemented IfcLightIntensityDistribution; // (not currently used by Assimp) + struct IfcLightSource; + struct IfcLightSourceAmbient; + struct IfcLightSourceDirectional; + struct IfcLightSourceGoniometric; + struct IfcLightSourcePositional; + struct IfcLightSourceSpot; + struct IfcLine; + struct IfcLinearDimension; + struct IfcLocalPlacement; + typedef NotImplemented IfcLocalTime; // (not currently used by Assimp) + struct IfcMappedItem; + typedef NotImplemented IfcMaterial; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialClassificationRelationship; // (not currently used by Assimp) + struct IfcProductRepresentation; + struct IfcMaterialDefinitionRepresentation; + typedef NotImplemented IfcMaterialLayer; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialLayerSet; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialLayerSetUsage; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialList; // (not currently used by Assimp) + struct IfcMeasureWithUnit; + typedef NotImplemented IfcMechanicalMaterialProperties; // (not currently used by Assimp) + typedef NotImplemented IfcMechanicalConcreteMaterialProperties; // (not currently used by Assimp) + struct IfcMechanicalFastener; + struct IfcMechanicalFastenerType; + typedef NotImplemented IfcMechanicalSteelMaterialProperties; // (not currently used by Assimp) + struct IfcMember; + struct IfcMemberType; + typedef NotImplemented IfcMetric; // (not currently used by Assimp) + typedef NotImplemented IfcMonetaryUnit; // (not currently used by Assimp) + struct IfcMotorConnectionType; + struct IfcProcess; + struct IfcTask; + struct IfcMove; + typedef NotImplemented IfcObjective; // (not currently used by Assimp) + struct IfcOccupant; + struct IfcOffsetCurve2D; + struct IfcOffsetCurve3D; + struct IfcOneDirectionRepeatFactor; + struct IfcOpenShell; + struct IfcOpeningElement; + typedef NotImplemented IfcOpticalMaterialProperties; // (not currently used by Assimp) + struct IfcOrderAction; + typedef NotImplemented IfcOrganization; // (not currently used by Assimp) + typedef NotImplemented IfcOrganizationRelationship; // (not currently used by Assimp) + struct IfcOrientedEdge; + struct IfcOutletType; + typedef NotImplemented IfcOwnerHistory; // (not currently used by Assimp) + struct IfcPath; + struct IfcPerformanceHistory; + typedef NotImplemented IfcPermeableCoveringProperties; // (not currently used by Assimp) + struct IfcPermit; + typedef NotImplemented IfcPerson; // (not currently used by Assimp) + typedef NotImplemented IfcPersonAndOrganization; // (not currently used by Assimp) + typedef NotImplemented IfcPhysicalQuantity; // (not currently used by Assimp) + typedef NotImplemented IfcPhysicalComplexQuantity; // (not currently used by Assimp) + typedef NotImplemented IfcPhysicalSimpleQuantity; // (not currently used by Assimp) + struct IfcPile; + struct IfcPipeFittingType; + struct IfcPipeSegmentType; + typedef NotImplemented IfcPixelTexture; // (not currently used by Assimp) + struct IfcPlanarExtent; + struct IfcPlanarBox; + struct IfcPlane; + struct IfcPlate; + struct IfcPlateType; + struct IfcPointOnCurve; + struct IfcPointOnSurface; + struct IfcPolyLoop; + struct IfcPolygonalBoundedHalfSpace; + struct IfcPolyline; + typedef NotImplemented IfcPostalAddress; // (not currently used by Assimp) + typedef NotImplemented IfcPreDefinedSymbol; // (not currently used by Assimp) + typedef NotImplemented IfcPreDefinedDimensionSymbol; // (not currently used by Assimp) + typedef NotImplemented IfcPreDefinedPointMarkerSymbol; // (not currently used by Assimp) + typedef NotImplemented IfcPreDefinedTerminatorSymbol; // (not currently used by Assimp) + typedef NotImplemented IfcPresentationLayerAssignment; // (not currently used by Assimp) + typedef NotImplemented IfcPresentationLayerWithStyle; // (not currently used by Assimp) + struct IfcPresentationStyleAssignment; + struct IfcProcedure; + struct IfcProductDefinitionShape; + typedef NotImplemented IfcProductsOfCombustionProperties; // (not currently used by Assimp) + struct IfcProject; + struct IfcProjectOrder; + struct IfcProjectOrderRecord; + struct IfcProjectionCurve; + struct IfcProjectionElement; + struct IfcSimpleProperty; + struct IfcPropertyBoundedValue; + typedef NotImplemented IfcPropertyConstraintRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcPropertyDependencyRelationship; // (not currently used by Assimp) + struct IfcPropertyEnumeratedValue; + typedef NotImplemented IfcPropertyEnumeration; // (not currently used by Assimp) + struct IfcPropertyListValue; + struct IfcPropertyReferenceValue; + struct IfcPropertySet; + struct IfcPropertySingleValue; + struct IfcPropertyTableValue; + struct IfcProtectiveDeviceType; + struct IfcProxy; + struct IfcPumpType; + typedef NotImplemented IfcQuantityArea; // (not currently used by Assimp) + typedef NotImplemented IfcQuantityCount; // (not currently used by Assimp) + typedef NotImplemented IfcQuantityLength; // (not currently used by Assimp) + typedef NotImplemented IfcQuantityTime; // (not currently used by Assimp) + typedef NotImplemented IfcQuantityVolume; // (not currently used by Assimp) + typedef NotImplemented IfcQuantityWeight; // (not currently used by Assimp) + struct IfcRadiusDimension; + struct IfcRailing; + struct IfcRailingType; + struct IfcRamp; + struct IfcRampFlight; + struct IfcRampFlightType; + struct IfcRationalBezierCurve; + struct IfcRectangleProfileDef; + struct IfcRectangleHollowProfileDef; + struct IfcRectangularPyramid; + struct IfcRectangularTrimmedSurface; + typedef NotImplemented IfcReferencesValueDocument; // (not currently used by Assimp) + typedef NotImplemented IfcRegularTimeSeries; // (not currently used by Assimp) + typedef NotImplemented IfcReinforcementBarProperties; // (not currently used by Assimp) + typedef NotImplemented IfcReinforcementDefinitionProperties; // (not currently used by Assimp) + struct IfcReinforcingElement; + struct IfcReinforcingBar; + struct IfcReinforcingMesh; + struct IfcRelationship; + struct IfcRelDecomposes; + struct IfcRelAggregates; + typedef NotImplemented IfcRelAssigns; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToControl; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsTasks; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToActor; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToGroup; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToProcess; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToProduct; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToProjectOrder; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToResource; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociates; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesAppliedValue; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesApproval; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesClassification; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesConstraint; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesDocument; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesLibrary; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesMaterial; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesProfileProperties; // (not currently used by Assimp) + struct IfcRelConnects; + typedef NotImplemented IfcRelConnectsElements; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsPathElements; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsPortToElement; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsPorts; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsStructuralActivity; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsStructuralElement; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsStructuralMember; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsWithEccentricity; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsWithRealizingElements; // (not currently used by Assimp) + struct IfcRelContainedInSpatialStructure; + typedef NotImplemented IfcRelCoversBldgElements; // (not currently used by Assimp) + typedef NotImplemented IfcRelCoversSpaces; // (not currently used by Assimp) + struct IfcRelDefines; + struct IfcRelDefinesByProperties; + typedef NotImplemented IfcRelDefinesByType; // (not currently used by Assimp) + struct IfcRelFillsElement; + typedef NotImplemented IfcRelFlowControlElements; // (not currently used by Assimp) + typedef NotImplemented IfcRelInteractionRequirements; // (not currently used by Assimp) + typedef NotImplemented IfcRelNests; // (not currently used by Assimp) + typedef NotImplemented IfcRelOccupiesSpaces; // (not currently used by Assimp) + struct IfcRelOverridesProperties; + typedef NotImplemented IfcRelProjectsElement; // (not currently used by Assimp) + typedef NotImplemented IfcRelReferencedInSpatialStructure; // (not currently used by Assimp) + typedef NotImplemented IfcRelSchedulesCostItems; // (not currently used by Assimp) + typedef NotImplemented IfcRelSequence; // (not currently used by Assimp) + typedef NotImplemented IfcRelServicesBuildings; // (not currently used by Assimp) + typedef NotImplemented IfcRelSpaceBoundary; // (not currently used by Assimp) + struct IfcRelVoidsElement; + typedef NotImplemented IfcRelaxation; // (not currently used by Assimp) + struct IfcRepresentation; + struct IfcRepresentationMap; + struct IfcRevolvedAreaSolid; + typedef NotImplemented IfcRibPlateProfileProperties; // (not currently used by Assimp) + struct IfcRightCircularCone; + struct IfcRightCircularCylinder; + struct IfcRoof; + struct IfcRoundedEdgeFeature; + struct IfcRoundedRectangleProfileDef; + struct IfcSIUnit; + struct IfcSanitaryTerminalType; + struct IfcScheduleTimeControl; + typedef NotImplemented IfcSectionProperties; // (not currently used by Assimp) + typedef NotImplemented IfcSectionReinforcementProperties; // (not currently used by Assimp) + struct IfcSectionedSpine; + struct IfcSensorType; + struct IfcServiceLife; + typedef NotImplemented IfcServiceLifeFactor; // (not currently used by Assimp) + typedef NotImplemented IfcShapeAspect; // (not currently used by Assimp) + struct IfcShapeModel; + struct IfcShapeRepresentation; + struct IfcShellBasedSurfaceModel; + struct IfcSite; + struct IfcSlab; + struct IfcSlabType; + typedef NotImplemented IfcSlippageConnectionCondition; // (not currently used by Assimp) + typedef NotImplemented IfcSoundProperties; // (not currently used by Assimp) + typedef NotImplemented IfcSoundValue; // (not currently used by Assimp) + struct IfcSpace; + struct IfcSpaceHeaterType; + struct IfcSpaceProgram; + typedef NotImplemented IfcSpaceThermalLoadProperties; // (not currently used by Assimp) + struct IfcSpatialStructureElementType; + struct IfcSpaceType; + struct IfcSphere; + struct IfcStackTerminalType; + struct IfcStair; + struct IfcStairFlight; + struct IfcStairFlightType; + struct IfcStructuralActivity; + struct IfcStructuralAction; + struct IfcStructuralAnalysisModel; + struct IfcStructuralItem; + struct IfcStructuralConnection; + struct IfcStructuralCurveConnection; + struct IfcStructuralMember; + struct IfcStructuralCurveMember; + struct IfcStructuralCurveMemberVarying; + struct IfcStructuralLinearAction; + struct IfcStructuralLinearActionVarying; + typedef NotImplemented IfcStructuralLoad; // (not currently used by Assimp) + struct IfcStructuralLoadGroup; + typedef NotImplemented IfcStructuralLoadStatic; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadLinearForce; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadPlanarForce; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadSingleDisplacement; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadSingleDisplacementDistortion; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadSingleForce; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadSingleForceWarping; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadTemperature; // (not currently used by Assimp) + struct IfcStructuralPlanarAction; + struct IfcStructuralPlanarActionVarying; + struct IfcStructuralPointAction; + struct IfcStructuralPointConnection; + struct IfcStructuralReaction; + struct IfcStructuralPointReaction; + typedef NotImplemented IfcStructuralProfileProperties; // (not currently used by Assimp) + struct IfcStructuralResultGroup; + typedef NotImplemented IfcStructuralSteelProfileProperties; // (not currently used by Assimp) + struct IfcStructuralSurfaceConnection; + struct IfcStructuralSurfaceMember; + struct IfcStructuralSurfaceMemberVarying; + struct IfcStructuredDimensionCallout; + struct IfcStyleModel; + struct IfcStyledRepresentation; + struct IfcSubContractResource; + struct IfcSubedge; + struct IfcSurfaceCurveSweptAreaSolid; + struct IfcSweptSurface; + struct IfcSurfaceOfLinearExtrusion; + struct IfcSurfaceOfRevolution; + struct IfcSurfaceStyle; + typedef NotImplemented IfcSurfaceStyleLighting; // (not currently used by Assimp) + typedef NotImplemented IfcSurfaceStyleRefraction; // (not currently used by Assimp) + struct IfcSurfaceStyleShading; + struct IfcSurfaceStyleRendering; + struct IfcSurfaceStyleWithTextures; + struct IfcSweptDiskSolid; + struct IfcSwitchingDeviceType; + typedef NotImplemented IfcSymbolStyle; // (not currently used by Assimp) + struct IfcSystemFurnitureElementType; + struct IfcTShapeProfileDef; + typedef NotImplemented IfcTable; // (not currently used by Assimp) + typedef NotImplemented IfcTableRow; // (not currently used by Assimp) + struct IfcTankType; + typedef NotImplemented IfcTelecomAddress; // (not currently used by Assimp) + struct IfcTendon; + struct IfcTendonAnchor; + struct IfcTextLiteral; + struct IfcTextLiteralWithExtent; + typedef NotImplemented IfcTextStyle; // (not currently used by Assimp) + typedef NotImplemented IfcTextStyleFontModel; // (not currently used by Assimp) + typedef NotImplemented IfcTextStyleForDefinedFont; // (not currently used by Assimp) + typedef NotImplemented IfcTextStyleTextModel; // (not currently used by Assimp) + typedef NotImplemented IfcTextStyleWithBoxCharacteristics; // (not currently used by Assimp) + typedef NotImplemented IfcTextureCoordinate; // (not currently used by Assimp) + typedef NotImplemented IfcTextureCoordinateGenerator; // (not currently used by Assimp) + typedef NotImplemented IfcTextureMap; // (not currently used by Assimp) + typedef NotImplemented IfcTextureVertex; // (not currently used by Assimp) + typedef NotImplemented IfcThermalMaterialProperties; // (not currently used by Assimp) + typedef NotImplemented IfcTimeSeriesReferenceRelationship; // (not currently used by Assimp) + struct IfcTimeSeriesSchedule; + typedef NotImplemented IfcTimeSeriesValue; // (not currently used by Assimp) + struct IfcTopologyRepresentation; + struct IfcTransformerType; + struct IfcTransportElement; + struct IfcTransportElementType; + struct IfcTrapeziumProfileDef; + struct IfcTrimmedCurve; + struct IfcTubeBundleType; + struct IfcTwoDirectionRepeatFactor; + struct IfcUShapeProfileDef; + struct IfcUnitAssignment; + struct IfcUnitaryEquipmentType; + struct IfcValveType; + struct IfcVector; + struct IfcVertex; + typedef NotImplemented IfcVertexBasedTextureMap; // (not currently used by Assimp) + struct IfcVertexLoop; + struct IfcVertexPoint; + struct IfcVibrationIsolatorType; + struct IfcVirtualElement; + typedef NotImplemented IfcVirtualGridIntersection; // (not currently used by Assimp) + struct IfcWall; + struct IfcWallStandardCase; + struct IfcWallType; + struct IfcWasteTerminalType; + typedef NotImplemented IfcWaterProperties; // (not currently used by Assimp) + struct IfcWindow; + typedef NotImplemented IfcWindowLiningProperties; // (not currently used by Assimp) + typedef NotImplemented IfcWindowPanelProperties; // (not currently used by Assimp) + struct IfcWindowStyle; + struct IfcWorkControl; + struct IfcWorkPlan; + struct IfcWorkSchedule; + struct IfcZShapeProfileDef; + struct IfcZone; + + + + // C++ wrapper for IfcRepresentationItem + struct IfcRepresentationItem : ObjectHelper<IfcRepresentationItem,0> { IfcRepresentationItem() : Object("IfcRepresentationItem") {} + + }; + + // C++ wrapper for IfcGeometricRepresentationItem + struct IfcGeometricRepresentationItem : IfcRepresentationItem, ObjectHelper<IfcGeometricRepresentationItem,0> { IfcGeometricRepresentationItem() : Object("IfcGeometricRepresentationItem") {} + + }; + + // C++ wrapper for IfcCurve + struct IfcCurve : IfcGeometricRepresentationItem, ObjectHelper<IfcCurve,0> { IfcCurve() : Object("IfcCurve") {} + + }; + + // C++ wrapper for IfcBoundedCurve + struct IfcBoundedCurve : IfcCurve, ObjectHelper<IfcBoundedCurve,0> { IfcBoundedCurve() : Object("IfcBoundedCurve") {} + + }; + + // C++ wrapper for IfcCompositeCurve + struct IfcCompositeCurve : IfcBoundedCurve, ObjectHelper<IfcCompositeCurve,2> { IfcCompositeCurve() : Object("IfcCompositeCurve") {} + ListOf< Lazy< IfcCompositeCurveSegment >, 1, 0 > Segments; + LOGICAL::Out SelfIntersect; + }; + + // C++ wrapper for Ifc2DCompositeCurve + struct Ifc2DCompositeCurve : IfcCompositeCurve, ObjectHelper<Ifc2DCompositeCurve,0> { Ifc2DCompositeCurve() : Object("Ifc2DCompositeCurve") {} + + }; + + // C++ wrapper for IfcRoot + struct IfcRoot : ObjectHelper<IfcRoot,4> { IfcRoot() : Object("IfcRoot") {} + IfcGloballyUniqueId::Out GlobalId; + Lazy< NotImplemented > OwnerHistory; + Maybe< IfcLabel::Out > Name; + Maybe< IfcText::Out > Description; + }; + + // C++ wrapper for IfcObjectDefinition + struct IfcObjectDefinition : IfcRoot, ObjectHelper<IfcObjectDefinition,0> { IfcObjectDefinition() : Object("IfcObjectDefinition") {} + + }; + + // C++ wrapper for IfcObject + struct IfcObject : IfcObjectDefinition, ObjectHelper<IfcObject,1> { IfcObject() : Object("IfcObject") {} + Maybe< IfcLabel::Out > ObjectType; + }; + + // C++ wrapper for IfcControl + struct IfcControl : IfcObject, ObjectHelper<IfcControl,0> { IfcControl() : Object("IfcControl") {} + + }; + + // C++ wrapper for IfcActionRequest + struct IfcActionRequest : IfcControl, ObjectHelper<IfcActionRequest,1> { IfcActionRequest() : Object("IfcActionRequest") {} + IfcIdentifier::Out RequestID; + }; + + // C++ wrapper for IfcActor + struct IfcActor : IfcObject, ObjectHelper<IfcActor,1> { IfcActor() : Object("IfcActor") {} + IfcActorSelect::Out TheActor; + }; + + // C++ wrapper for IfcTypeObject + struct IfcTypeObject : IfcObjectDefinition, ObjectHelper<IfcTypeObject,2> { IfcTypeObject() : Object("IfcTypeObject") {} + Maybe< IfcLabel::Out > ApplicableOccurrence; + Maybe< ListOf< Lazy< IfcPropertySetDefinition >, 1, 0 > > HasPropertySets; + }; + + // C++ wrapper for IfcTypeProduct + struct IfcTypeProduct : IfcTypeObject, ObjectHelper<IfcTypeProduct,2> { IfcTypeProduct() : Object("IfcTypeProduct") {} + Maybe< ListOf< Lazy< IfcRepresentationMap >, 1, 0 > > RepresentationMaps; + Maybe< IfcLabel::Out > Tag; + }; + + // C++ wrapper for IfcElementType + struct IfcElementType : IfcTypeProduct, ObjectHelper<IfcElementType,1> { IfcElementType() : Object("IfcElementType") {} + Maybe< IfcLabel::Out > ElementType; + }; + + // C++ wrapper for IfcDistributionElementType + struct IfcDistributionElementType : IfcElementType, ObjectHelper<IfcDistributionElementType,0> { IfcDistributionElementType() : Object("IfcDistributionElementType") {} + + }; + + // C++ wrapper for IfcDistributionControlElementType + struct IfcDistributionControlElementType : IfcDistributionElementType, ObjectHelper<IfcDistributionControlElementType,0> { IfcDistributionControlElementType() : Object("IfcDistributionControlElementType") {} + + }; + + // C++ wrapper for IfcActuatorType + struct IfcActuatorType : IfcDistributionControlElementType, ObjectHelper<IfcActuatorType,1> { IfcActuatorType() : Object("IfcActuatorType") {} + IfcActuatorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcDistributionFlowElementType + struct IfcDistributionFlowElementType : IfcDistributionElementType, ObjectHelper<IfcDistributionFlowElementType,0> { IfcDistributionFlowElementType() : Object("IfcDistributionFlowElementType") {} + + }; + + // C++ wrapper for IfcFlowControllerType + struct IfcFlowControllerType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowControllerType,0> { IfcFlowControllerType() : Object("IfcFlowControllerType") {} + + }; + + // C++ wrapper for IfcAirTerminalBoxType + struct IfcAirTerminalBoxType : IfcFlowControllerType, ObjectHelper<IfcAirTerminalBoxType,1> { IfcAirTerminalBoxType() : Object("IfcAirTerminalBoxType") {} + IfcAirTerminalBoxTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFlowTerminalType + struct IfcFlowTerminalType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowTerminalType,0> { IfcFlowTerminalType() : Object("IfcFlowTerminalType") {} + + }; + + // C++ wrapper for IfcAirTerminalType + struct IfcAirTerminalType : IfcFlowTerminalType, ObjectHelper<IfcAirTerminalType,1> { IfcAirTerminalType() : Object("IfcAirTerminalType") {} + IfcAirTerminalTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcEnergyConversionDeviceType + struct IfcEnergyConversionDeviceType : IfcDistributionFlowElementType, ObjectHelper<IfcEnergyConversionDeviceType,0> { IfcEnergyConversionDeviceType() : Object("IfcEnergyConversionDeviceType") {} + + }; + + // C++ wrapper for IfcAirToAirHeatRecoveryType + struct IfcAirToAirHeatRecoveryType : IfcEnergyConversionDeviceType, ObjectHelper<IfcAirToAirHeatRecoveryType,1> { IfcAirToAirHeatRecoveryType() : Object("IfcAirToAirHeatRecoveryType") {} + IfcAirToAirHeatRecoveryTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcAlarmType + struct IfcAlarmType : IfcDistributionControlElementType, ObjectHelper<IfcAlarmType,1> { IfcAlarmType() : Object("IfcAlarmType") {} + IfcAlarmTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcDraughtingCallout + struct IfcDraughtingCallout : IfcGeometricRepresentationItem, ObjectHelper<IfcDraughtingCallout,1> { IfcDraughtingCallout() : Object("IfcDraughtingCallout") {} + ListOf< IfcDraughtingCalloutElement, 1, 0 >::Out Contents; + }; + + // C++ wrapper for IfcDimensionCurveDirectedCallout + struct IfcDimensionCurveDirectedCallout : IfcDraughtingCallout, ObjectHelper<IfcDimensionCurveDirectedCallout,0> { IfcDimensionCurveDirectedCallout() : Object("IfcDimensionCurveDirectedCallout") {} + + }; + + // C++ wrapper for IfcAngularDimension + struct IfcAngularDimension : IfcDimensionCurveDirectedCallout, ObjectHelper<IfcAngularDimension,0> { IfcAngularDimension() : Object("IfcAngularDimension") {} + + }; + + // C++ wrapper for IfcProduct + struct IfcProduct : IfcObject, ObjectHelper<IfcProduct,2> { IfcProduct() : Object("IfcProduct") {} + Maybe< Lazy< IfcObjectPlacement > > ObjectPlacement; + Maybe< Lazy< IfcProductRepresentation > > Representation; + }; + + // C++ wrapper for IfcAnnotation + struct IfcAnnotation : IfcProduct, ObjectHelper<IfcAnnotation,0> { IfcAnnotation() : Object("IfcAnnotation") {} + + }; + + // C++ wrapper for IfcStyledItem + struct IfcStyledItem : IfcRepresentationItem, ObjectHelper<IfcStyledItem,3> { IfcStyledItem() : Object("IfcStyledItem") {} + Maybe< Lazy< IfcRepresentationItem > > Item; + ListOf< Lazy< IfcPresentationStyleAssignment >, 1, 0 > Styles; + Maybe< IfcLabel::Out > Name; + }; + + // C++ wrapper for IfcAnnotationOccurrence + struct IfcAnnotationOccurrence : IfcStyledItem, ObjectHelper<IfcAnnotationOccurrence,0> { IfcAnnotationOccurrence() : Object("IfcAnnotationOccurrence") {} + + }; + + // C++ wrapper for IfcAnnotationCurveOccurrence + struct IfcAnnotationCurveOccurrence : IfcAnnotationOccurrence, ObjectHelper<IfcAnnotationCurveOccurrence,0> { IfcAnnotationCurveOccurrence() : Object("IfcAnnotationCurveOccurrence") {} + + }; + + // C++ wrapper for IfcAnnotationFillArea + struct IfcAnnotationFillArea : IfcGeometricRepresentationItem, ObjectHelper<IfcAnnotationFillArea,2> { IfcAnnotationFillArea() : Object("IfcAnnotationFillArea") {} + Lazy< IfcCurve > OuterBoundary; + Maybe< ListOf< Lazy< IfcCurve >, 1, 0 > > InnerBoundaries; + }; + + // C++ wrapper for IfcAnnotationFillAreaOccurrence + struct IfcAnnotationFillAreaOccurrence : IfcAnnotationOccurrence, ObjectHelper<IfcAnnotationFillAreaOccurrence,2> { IfcAnnotationFillAreaOccurrence() : Object("IfcAnnotationFillAreaOccurrence") {} + Maybe< Lazy< IfcPoint > > FillStyleTarget; + Maybe< IfcGlobalOrLocalEnum::Out > GlobalOrLocal; + }; + + // C++ wrapper for IfcAnnotationSurface + struct IfcAnnotationSurface : IfcGeometricRepresentationItem, ObjectHelper<IfcAnnotationSurface,2> { IfcAnnotationSurface() : Object("IfcAnnotationSurface") {} + Lazy< IfcGeometricRepresentationItem > Item; + Maybe< Lazy< NotImplemented > > TextureCoordinates; + }; + + // C++ wrapper for IfcAnnotationSurfaceOccurrence + struct IfcAnnotationSurfaceOccurrence : IfcAnnotationOccurrence, ObjectHelper<IfcAnnotationSurfaceOccurrence,0> { IfcAnnotationSurfaceOccurrence() : Object("IfcAnnotationSurfaceOccurrence") {} + + }; + + // C++ wrapper for IfcAnnotationSymbolOccurrence + struct IfcAnnotationSymbolOccurrence : IfcAnnotationOccurrence, ObjectHelper<IfcAnnotationSymbolOccurrence,0> { IfcAnnotationSymbolOccurrence() : Object("IfcAnnotationSymbolOccurrence") {} + + }; + + // C++ wrapper for IfcAnnotationTextOccurrence + struct IfcAnnotationTextOccurrence : IfcAnnotationOccurrence, ObjectHelper<IfcAnnotationTextOccurrence,0> { IfcAnnotationTextOccurrence() : Object("IfcAnnotationTextOccurrence") {} + + }; + + // C++ wrapper for IfcProfileDef + struct IfcProfileDef : ObjectHelper<IfcProfileDef,2> { IfcProfileDef() : Object("IfcProfileDef") {} + IfcProfileTypeEnum::Out ProfileType; + Maybe< IfcLabel::Out > ProfileName; + }; + + // C++ wrapper for IfcArbitraryClosedProfileDef + struct IfcArbitraryClosedProfileDef : IfcProfileDef, ObjectHelper<IfcArbitraryClosedProfileDef,1> { IfcArbitraryClosedProfileDef() : Object("IfcArbitraryClosedProfileDef") {} + Lazy< IfcCurve > OuterCurve; + }; + + // C++ wrapper for IfcArbitraryOpenProfileDef + struct IfcArbitraryOpenProfileDef : IfcProfileDef, ObjectHelper<IfcArbitraryOpenProfileDef,1> { IfcArbitraryOpenProfileDef() : Object("IfcArbitraryOpenProfileDef") {} + Lazy< IfcBoundedCurve > Curve; + }; + + // C++ wrapper for IfcArbitraryProfileDefWithVoids + struct IfcArbitraryProfileDefWithVoids : IfcArbitraryClosedProfileDef, ObjectHelper<IfcArbitraryProfileDefWithVoids,1> { IfcArbitraryProfileDefWithVoids() : Object("IfcArbitraryProfileDefWithVoids") {} + ListOf< Lazy< IfcCurve >, 1, 0 > InnerCurves; + }; + + // C++ wrapper for IfcGroup + struct IfcGroup : IfcObject, ObjectHelper<IfcGroup,0> { IfcGroup() : Object("IfcGroup") {} + + }; + + // C++ wrapper for IfcAsset + struct IfcAsset : IfcGroup, ObjectHelper<IfcAsset,9> { IfcAsset() : Object("IfcAsset") {} + IfcIdentifier::Out AssetID; + Lazy< NotImplemented > OriginalValue; + Lazy< NotImplemented > CurrentValue; + Lazy< NotImplemented > TotalReplacementCost; + IfcActorSelect::Out Owner; + IfcActorSelect::Out User; + Lazy< NotImplemented > ResponsiblePerson; + Lazy< NotImplemented > IncorporationDate; + Lazy< NotImplemented > DepreciatedValue; + }; + + // C++ wrapper for IfcParameterizedProfileDef + struct IfcParameterizedProfileDef : IfcProfileDef, ObjectHelper<IfcParameterizedProfileDef,1> { IfcParameterizedProfileDef() : Object("IfcParameterizedProfileDef") {} + Lazy< IfcAxis2Placement2D > Position; + }; + + // C++ wrapper for IfcIShapeProfileDef + struct IfcIShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcIShapeProfileDef,5> { IfcIShapeProfileDef() : Object("IfcIShapeProfileDef") {} + IfcPositiveLengthMeasure::Out OverallWidth; + IfcPositiveLengthMeasure::Out OverallDepth; + IfcPositiveLengthMeasure::Out WebThickness; + IfcPositiveLengthMeasure::Out FlangeThickness; + Maybe< IfcPositiveLengthMeasure::Out > FilletRadius; + }; + + // C++ wrapper for IfcAsymmetricIShapeProfileDef + struct IfcAsymmetricIShapeProfileDef : IfcIShapeProfileDef, ObjectHelper<IfcAsymmetricIShapeProfileDef,4> { IfcAsymmetricIShapeProfileDef() : Object("IfcAsymmetricIShapeProfileDef") {} + IfcPositiveLengthMeasure::Out TopFlangeWidth; + Maybe< IfcPositiveLengthMeasure::Out > TopFlangeThickness; + Maybe< IfcPositiveLengthMeasure::Out > TopFlangeFilletRadius; + Maybe< IfcPositiveLengthMeasure::Out > CentreOfGravityInY; + }; + + // C++ wrapper for IfcPlacement + struct IfcPlacement : IfcGeometricRepresentationItem, ObjectHelper<IfcPlacement,1> { IfcPlacement() : Object("IfcPlacement") {} + Lazy< IfcCartesianPoint > Location; + }; + + // C++ wrapper for IfcAxis1Placement + struct IfcAxis1Placement : IfcPlacement, ObjectHelper<IfcAxis1Placement,1> { IfcAxis1Placement() : Object("IfcAxis1Placement") {} + Maybe< Lazy< IfcDirection > > Axis; + }; + + // C++ wrapper for IfcAxis2Placement2D + struct IfcAxis2Placement2D : IfcPlacement, ObjectHelper<IfcAxis2Placement2D,1> { IfcAxis2Placement2D() : Object("IfcAxis2Placement2D") {} + Maybe< Lazy< IfcDirection > > RefDirection; + }; + + // C++ wrapper for IfcAxis2Placement3D + struct IfcAxis2Placement3D : IfcPlacement, ObjectHelper<IfcAxis2Placement3D,2> { IfcAxis2Placement3D() : Object("IfcAxis2Placement3D") {} + Maybe< Lazy< IfcDirection > > Axis; + Maybe< Lazy< IfcDirection > > RefDirection; + }; + + // C++ wrapper for IfcBSplineCurve + struct IfcBSplineCurve : IfcBoundedCurve, ObjectHelper<IfcBSplineCurve,5> { IfcBSplineCurve() : Object("IfcBSplineCurve") {} + INTEGER::Out Degree; + ListOf< Lazy< IfcCartesianPoint >, 2, 0 > ControlPointsList; + IfcBSplineCurveForm::Out CurveForm; + LOGICAL::Out ClosedCurve; + LOGICAL::Out SelfIntersect; + }; + + // C++ wrapper for IfcElement + struct IfcElement : IfcProduct, ObjectHelper<IfcElement,1> { IfcElement() : Object("IfcElement") {} + Maybe< IfcIdentifier::Out > Tag; + }; + + // C++ wrapper for IfcBuildingElement + struct IfcBuildingElement : IfcElement, ObjectHelper<IfcBuildingElement,0> { IfcBuildingElement() : Object("IfcBuildingElement") {} + + }; + + // C++ wrapper for IfcBeam + struct IfcBeam : IfcBuildingElement, ObjectHelper<IfcBeam,0> { IfcBeam() : Object("IfcBeam") {} + + }; + + // C++ wrapper for IfcBuildingElementType + struct IfcBuildingElementType : IfcElementType, ObjectHelper<IfcBuildingElementType,0> { IfcBuildingElementType() : Object("IfcBuildingElementType") {} + + }; + + // C++ wrapper for IfcBeamType + struct IfcBeamType : IfcBuildingElementType, ObjectHelper<IfcBeamType,1> { IfcBeamType() : Object("IfcBeamType") {} + IfcBeamTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcBezierCurve + struct IfcBezierCurve : IfcBSplineCurve, ObjectHelper<IfcBezierCurve,0> { IfcBezierCurve() : Object("IfcBezierCurve") {} + + }; + + // C++ wrapper for IfcCsgPrimitive3D + struct IfcCsgPrimitive3D : IfcGeometricRepresentationItem, ObjectHelper<IfcCsgPrimitive3D,1> { IfcCsgPrimitive3D() : Object("IfcCsgPrimitive3D") {} + Lazy< IfcAxis2Placement3D > Position; + }; + + // C++ wrapper for IfcBlock + struct IfcBlock : IfcCsgPrimitive3D, ObjectHelper<IfcBlock,3> { IfcBlock() : Object("IfcBlock") {} + IfcPositiveLengthMeasure::Out XLength; + IfcPositiveLengthMeasure::Out YLength; + IfcPositiveLengthMeasure::Out ZLength; + }; + + // C++ wrapper for IfcBoilerType + struct IfcBoilerType : IfcEnergyConversionDeviceType, ObjectHelper<IfcBoilerType,1> { IfcBoilerType() : Object("IfcBoilerType") {} + IfcBoilerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcBooleanResult + struct IfcBooleanResult : IfcGeometricRepresentationItem, ObjectHelper<IfcBooleanResult,3> { IfcBooleanResult() : Object("IfcBooleanResult") {} + IfcBooleanOperator::Out Operator; + IfcBooleanOperand::Out FirstOperand; + IfcBooleanOperand::Out SecondOperand; + }; + + // C++ wrapper for IfcBooleanClippingResult + struct IfcBooleanClippingResult : IfcBooleanResult, ObjectHelper<IfcBooleanClippingResult,0> { IfcBooleanClippingResult() : Object("IfcBooleanClippingResult") {} + + }; + + // C++ wrapper for IfcSurface + struct IfcSurface : IfcGeometricRepresentationItem, ObjectHelper<IfcSurface,0> { IfcSurface() : Object("IfcSurface") {} + + }; + + // C++ wrapper for IfcBoundedSurface + struct IfcBoundedSurface : IfcSurface, ObjectHelper<IfcBoundedSurface,0> { IfcBoundedSurface() : Object("IfcBoundedSurface") {} + + }; + + // C++ wrapper for IfcBoundingBox + struct IfcBoundingBox : IfcGeometricRepresentationItem, ObjectHelper<IfcBoundingBox,4> { IfcBoundingBox() : Object("IfcBoundingBox") {} + Lazy< IfcCartesianPoint > Corner; + IfcPositiveLengthMeasure::Out XDim; + IfcPositiveLengthMeasure::Out YDim; + IfcPositiveLengthMeasure::Out ZDim; + }; + + // C++ wrapper for IfcHalfSpaceSolid + struct IfcHalfSpaceSolid : IfcGeometricRepresentationItem, ObjectHelper<IfcHalfSpaceSolid,2> { IfcHalfSpaceSolid() : Object("IfcHalfSpaceSolid") {} + Lazy< IfcSurface > BaseSurface; + BOOLEAN::Out AgreementFlag; + }; + + // C++ wrapper for IfcBoxedHalfSpace + struct IfcBoxedHalfSpace : IfcHalfSpaceSolid, ObjectHelper<IfcBoxedHalfSpace,1> { IfcBoxedHalfSpace() : Object("IfcBoxedHalfSpace") {} + Lazy< IfcBoundingBox > Enclosure; + }; + + // C++ wrapper for IfcSpatialStructureElement + struct IfcSpatialStructureElement : IfcProduct, ObjectHelper<IfcSpatialStructureElement,2> { IfcSpatialStructureElement() : Object("IfcSpatialStructureElement") {} + Maybe< IfcLabel::Out > LongName; + IfcElementCompositionEnum::Out CompositionType; + }; + + // C++ wrapper for IfcBuilding + struct IfcBuilding : IfcSpatialStructureElement, ObjectHelper<IfcBuilding,3> { IfcBuilding() : Object("IfcBuilding") {} + Maybe< IfcLengthMeasure::Out > ElevationOfRefHeight; + Maybe< IfcLengthMeasure::Out > ElevationOfTerrain; + Maybe< Lazy< NotImplemented > > BuildingAddress; + }; + + // C++ wrapper for IfcBuildingElementComponent + struct IfcBuildingElementComponent : IfcBuildingElement, ObjectHelper<IfcBuildingElementComponent,0> { IfcBuildingElementComponent() : Object("IfcBuildingElementComponent") {} + + }; + + // C++ wrapper for IfcBuildingElementPart + struct IfcBuildingElementPart : IfcBuildingElementComponent, ObjectHelper<IfcBuildingElementPart,0> { IfcBuildingElementPart() : Object("IfcBuildingElementPart") {} + + }; + + // C++ wrapper for IfcBuildingElementProxy + struct IfcBuildingElementProxy : IfcBuildingElement, ObjectHelper<IfcBuildingElementProxy,1> { IfcBuildingElementProxy() : Object("IfcBuildingElementProxy") {} + Maybe< IfcElementCompositionEnum::Out > CompositionType; + }; + + // C++ wrapper for IfcBuildingElementProxyType + struct IfcBuildingElementProxyType : IfcBuildingElementType, ObjectHelper<IfcBuildingElementProxyType,1> { IfcBuildingElementProxyType() : Object("IfcBuildingElementProxyType") {} + IfcBuildingElementProxyTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcBuildingStorey + struct IfcBuildingStorey : IfcSpatialStructureElement, ObjectHelper<IfcBuildingStorey,1> { IfcBuildingStorey() : Object("IfcBuildingStorey") {} + Maybe< IfcLengthMeasure::Out > Elevation; + }; + + // C++ wrapper for IfcCShapeProfileDef + struct IfcCShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcCShapeProfileDef,6> { IfcCShapeProfileDef() : Object("IfcCShapeProfileDef") {} + IfcPositiveLengthMeasure::Out Depth; + IfcPositiveLengthMeasure::Out Width; + IfcPositiveLengthMeasure::Out WallThickness; + IfcPositiveLengthMeasure::Out Girth; + Maybe< IfcPositiveLengthMeasure::Out > InternalFilletRadius; + Maybe< IfcPositiveLengthMeasure::Out > CentreOfGravityInX; + }; + + // C++ wrapper for IfcFlowFittingType + struct IfcFlowFittingType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowFittingType,0> { IfcFlowFittingType() : Object("IfcFlowFittingType") {} + + }; + + // C++ wrapper for IfcCableCarrierFittingType + struct IfcCableCarrierFittingType : IfcFlowFittingType, ObjectHelper<IfcCableCarrierFittingType,1> { IfcCableCarrierFittingType() : Object("IfcCableCarrierFittingType") {} + IfcCableCarrierFittingTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFlowSegmentType + struct IfcFlowSegmentType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowSegmentType,0> { IfcFlowSegmentType() : Object("IfcFlowSegmentType") {} + + }; + + // C++ wrapper for IfcCableCarrierSegmentType + struct IfcCableCarrierSegmentType : IfcFlowSegmentType, ObjectHelper<IfcCableCarrierSegmentType,1> { IfcCableCarrierSegmentType() : Object("IfcCableCarrierSegmentType") {} + IfcCableCarrierSegmentTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCableSegmentType + struct IfcCableSegmentType : IfcFlowSegmentType, ObjectHelper<IfcCableSegmentType,1> { IfcCableSegmentType() : Object("IfcCableSegmentType") {} + IfcCableSegmentTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPoint + struct IfcPoint : IfcGeometricRepresentationItem, ObjectHelper<IfcPoint,0> { IfcPoint() : Object("IfcPoint") {} + + }; + + // C++ wrapper for IfcCartesianPoint + struct IfcCartesianPoint : IfcPoint, ObjectHelper<IfcCartesianPoint,1> { IfcCartesianPoint() : Object("IfcCartesianPoint") {} + ListOf< IfcLengthMeasure, 1, 3 >::Out Coordinates; + }; + + // C++ wrapper for IfcCartesianTransformationOperator + struct IfcCartesianTransformationOperator : IfcGeometricRepresentationItem, ObjectHelper<IfcCartesianTransformationOperator,4> { IfcCartesianTransformationOperator() : Object("IfcCartesianTransformationOperator") {} + Maybe< Lazy< IfcDirection > > Axis1; + Maybe< Lazy< IfcDirection > > Axis2; + Lazy< IfcCartesianPoint > LocalOrigin; + Maybe< REAL::Out > Scale; + }; + + // C++ wrapper for IfcCartesianTransformationOperator2D + struct IfcCartesianTransformationOperator2D : IfcCartesianTransformationOperator, ObjectHelper<IfcCartesianTransformationOperator2D,0> { IfcCartesianTransformationOperator2D() : Object("IfcCartesianTransformationOperator2D") {} + + }; + + // C++ wrapper for IfcCartesianTransformationOperator2DnonUniform + struct IfcCartesianTransformationOperator2DnonUniform : IfcCartesianTransformationOperator2D, ObjectHelper<IfcCartesianTransformationOperator2DnonUniform,1> { IfcCartesianTransformationOperator2DnonUniform() : Object("IfcCartesianTransformationOperator2DnonUniform") {} + Maybe< REAL::Out > Scale2; + }; + + // C++ wrapper for IfcCartesianTransformationOperator3D + struct IfcCartesianTransformationOperator3D : IfcCartesianTransformationOperator, ObjectHelper<IfcCartesianTransformationOperator3D,1> { IfcCartesianTransformationOperator3D() : Object("IfcCartesianTransformationOperator3D") {} + Maybe< Lazy< IfcDirection > > Axis3; + }; + + // C++ wrapper for IfcCartesianTransformationOperator3DnonUniform + struct IfcCartesianTransformationOperator3DnonUniform : IfcCartesianTransformationOperator3D, ObjectHelper<IfcCartesianTransformationOperator3DnonUniform,2> { IfcCartesianTransformationOperator3DnonUniform() : Object("IfcCartesianTransformationOperator3DnonUniform") {} + Maybe< REAL::Out > Scale2; + Maybe< REAL::Out > Scale3; + }; + + // C++ wrapper for IfcCenterLineProfileDef + struct IfcCenterLineProfileDef : IfcArbitraryOpenProfileDef, ObjectHelper<IfcCenterLineProfileDef,1> { IfcCenterLineProfileDef() : Object("IfcCenterLineProfileDef") {} + IfcPositiveLengthMeasure::Out Thickness; + }; + + // C++ wrapper for IfcFeatureElement + struct IfcFeatureElement : IfcElement, ObjectHelper<IfcFeatureElement,0> { IfcFeatureElement() : Object("IfcFeatureElement") {} + + }; + + // C++ wrapper for IfcFeatureElementSubtraction + struct IfcFeatureElementSubtraction : IfcFeatureElement, ObjectHelper<IfcFeatureElementSubtraction,0> { IfcFeatureElementSubtraction() : Object("IfcFeatureElementSubtraction") {} + + }; + + // C++ wrapper for IfcEdgeFeature + struct IfcEdgeFeature : IfcFeatureElementSubtraction, ObjectHelper<IfcEdgeFeature,1> { IfcEdgeFeature() : Object("IfcEdgeFeature") {} + Maybe< IfcPositiveLengthMeasure::Out > FeatureLength; + }; + + // C++ wrapper for IfcChamferEdgeFeature + struct IfcChamferEdgeFeature : IfcEdgeFeature, ObjectHelper<IfcChamferEdgeFeature,2> { IfcChamferEdgeFeature() : Object("IfcChamferEdgeFeature") {} + Maybe< IfcPositiveLengthMeasure::Out > Width; + Maybe< IfcPositiveLengthMeasure::Out > Height; + }; + + // C++ wrapper for IfcChillerType + struct IfcChillerType : IfcEnergyConversionDeviceType, ObjectHelper<IfcChillerType,1> { IfcChillerType() : Object("IfcChillerType") {} + IfcChillerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcConic + struct IfcConic : IfcCurve, ObjectHelper<IfcConic,1> { IfcConic() : Object("IfcConic") {} + IfcAxis2Placement::Out Position; + }; + + // C++ wrapper for IfcCircle + struct IfcCircle : IfcConic, ObjectHelper<IfcCircle,1> { IfcCircle() : Object("IfcCircle") {} + IfcPositiveLengthMeasure::Out Radius; + }; + + // C++ wrapper for IfcCircleProfileDef + struct IfcCircleProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcCircleProfileDef,1> { IfcCircleProfileDef() : Object("IfcCircleProfileDef") {} + IfcPositiveLengthMeasure::Out Radius; + }; + + // C++ wrapper for IfcCircleHollowProfileDef + struct IfcCircleHollowProfileDef : IfcCircleProfileDef, ObjectHelper<IfcCircleHollowProfileDef,1> { IfcCircleHollowProfileDef() : Object("IfcCircleHollowProfileDef") {} + IfcPositiveLengthMeasure::Out WallThickness; + }; + + // C++ wrapper for IfcTopologicalRepresentationItem + struct IfcTopologicalRepresentationItem : IfcRepresentationItem, ObjectHelper<IfcTopologicalRepresentationItem,0> { IfcTopologicalRepresentationItem() : Object("IfcTopologicalRepresentationItem") {} + + }; + + // C++ wrapper for IfcConnectedFaceSet + struct IfcConnectedFaceSet : IfcTopologicalRepresentationItem, ObjectHelper<IfcConnectedFaceSet,1> { IfcConnectedFaceSet() : Object("IfcConnectedFaceSet") {} + ListOf< Lazy< IfcFace >, 1, 0 > CfsFaces; + }; + + // C++ wrapper for IfcClosedShell + struct IfcClosedShell : IfcConnectedFaceSet, ObjectHelper<IfcClosedShell,0> { IfcClosedShell() : Object("IfcClosedShell") {} + + }; + + // C++ wrapper for IfcCoilType + struct IfcCoilType : IfcEnergyConversionDeviceType, ObjectHelper<IfcCoilType,1> { IfcCoilType() : Object("IfcCoilType") {} + IfcCoilTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcColourSpecification + struct IfcColourSpecification : ObjectHelper<IfcColourSpecification,1> { IfcColourSpecification() : Object("IfcColourSpecification") {} + Maybe< IfcLabel::Out > Name; + }; + + // C++ wrapper for IfcColourRgb + struct IfcColourRgb : IfcColourSpecification, ObjectHelper<IfcColourRgb,3> { IfcColourRgb() : Object("IfcColourRgb") {} + IfcNormalisedRatioMeasure::Out Red; + IfcNormalisedRatioMeasure::Out Green; + IfcNormalisedRatioMeasure::Out Blue; + }; + + // C++ wrapper for IfcColumn + struct IfcColumn : IfcBuildingElement, ObjectHelper<IfcColumn,0> { IfcColumn() : Object("IfcColumn") {} + + }; + + // C++ wrapper for IfcColumnType + struct IfcColumnType : IfcBuildingElementType, ObjectHelper<IfcColumnType,1> { IfcColumnType() : Object("IfcColumnType") {} + IfcColumnTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcProperty + struct IfcProperty : ObjectHelper<IfcProperty,2> { IfcProperty() : Object("IfcProperty") {} + IfcIdentifier::Out Name; + Maybe< IfcText::Out > Description; + }; + + // C++ wrapper for IfcComplexProperty + struct IfcComplexProperty : IfcProperty, ObjectHelper<IfcComplexProperty,2> { IfcComplexProperty() : Object("IfcComplexProperty") {} + IfcIdentifier::Out UsageName; + ListOf< Lazy< IfcProperty >, 1, 0 > HasProperties; + }; + + // C++ wrapper for IfcCompositeCurveSegment + struct IfcCompositeCurveSegment : IfcGeometricRepresentationItem, ObjectHelper<IfcCompositeCurveSegment,3> { IfcCompositeCurveSegment() : Object("IfcCompositeCurveSegment") {} + IfcTransitionCode::Out Transition; + BOOLEAN::Out SameSense; + Lazy< IfcCurve > ParentCurve; + }; + + // C++ wrapper for IfcCompositeProfileDef + struct IfcCompositeProfileDef : IfcProfileDef, ObjectHelper<IfcCompositeProfileDef,2> { IfcCompositeProfileDef() : Object("IfcCompositeProfileDef") {} + ListOf< Lazy< IfcProfileDef >, 2, 0 > Profiles; + Maybe< IfcLabel::Out > Label; + }; + + // C++ wrapper for IfcFlowMovingDeviceType + struct IfcFlowMovingDeviceType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowMovingDeviceType,0> { IfcFlowMovingDeviceType() : Object("IfcFlowMovingDeviceType") {} + + }; + + // C++ wrapper for IfcCompressorType + struct IfcCompressorType : IfcFlowMovingDeviceType, ObjectHelper<IfcCompressorType,1> { IfcCompressorType() : Object("IfcCompressorType") {} + IfcCompressorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCondenserType + struct IfcCondenserType : IfcEnergyConversionDeviceType, ObjectHelper<IfcCondenserType,1> { IfcCondenserType() : Object("IfcCondenserType") {} + IfcCondenserTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCondition + struct IfcCondition : IfcGroup, ObjectHelper<IfcCondition,0> { IfcCondition() : Object("IfcCondition") {} + + }; + + // C++ wrapper for IfcConditionCriterion + struct IfcConditionCriterion : IfcControl, ObjectHelper<IfcConditionCriterion,2> { IfcConditionCriterion() : Object("IfcConditionCriterion") {} + IfcConditionCriterionSelect::Out Criterion; + IfcDateTimeSelect::Out CriterionDateTime; + }; + + // C++ wrapper for IfcResource + struct IfcResource : IfcObject, ObjectHelper<IfcResource,0> { IfcResource() : Object("IfcResource") {} + + }; + + // C++ wrapper for IfcConstructionResource + struct IfcConstructionResource : IfcResource, ObjectHelper<IfcConstructionResource,4> { IfcConstructionResource() : Object("IfcConstructionResource") {} + Maybe< IfcIdentifier::Out > ResourceIdentifier; + Maybe< IfcLabel::Out > ResourceGroup; + Maybe< IfcResourceConsumptionEnum::Out > ResourceConsumption; + Maybe< Lazy< IfcMeasureWithUnit > > BaseQuantity; + }; + + // C++ wrapper for IfcConstructionEquipmentResource + struct IfcConstructionEquipmentResource : IfcConstructionResource, ObjectHelper<IfcConstructionEquipmentResource,0> { IfcConstructionEquipmentResource() : Object("IfcConstructionEquipmentResource") {} + + }; + + // C++ wrapper for IfcConstructionMaterialResource + struct IfcConstructionMaterialResource : IfcConstructionResource, ObjectHelper<IfcConstructionMaterialResource,2> { IfcConstructionMaterialResource() : Object("IfcConstructionMaterialResource") {} + Maybe< ListOf< IfcActorSelect, 1, 0 >::Out > Suppliers; + Maybe< IfcRatioMeasure::Out > UsageRatio; + }; + + // C++ wrapper for IfcConstructionProductResource + struct IfcConstructionProductResource : IfcConstructionResource, ObjectHelper<IfcConstructionProductResource,0> { IfcConstructionProductResource() : Object("IfcConstructionProductResource") {} + + }; + + // C++ wrapper for IfcNamedUnit + struct IfcNamedUnit : ObjectHelper<IfcNamedUnit,2> { IfcNamedUnit() : Object("IfcNamedUnit") {} + Lazy< NotImplemented > Dimensions; + IfcUnitEnum::Out UnitType; + }; + + // C++ wrapper for IfcContextDependentUnit + struct IfcContextDependentUnit : IfcNamedUnit, ObjectHelper<IfcContextDependentUnit,1> { IfcContextDependentUnit() : Object("IfcContextDependentUnit") {} + IfcLabel::Out Name; + }; + + // C++ wrapper for IfcControllerType + struct IfcControllerType : IfcDistributionControlElementType, ObjectHelper<IfcControllerType,1> { IfcControllerType() : Object("IfcControllerType") {} + IfcControllerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcConversionBasedUnit + struct IfcConversionBasedUnit : IfcNamedUnit, ObjectHelper<IfcConversionBasedUnit,2> { IfcConversionBasedUnit() : Object("IfcConversionBasedUnit") {} + IfcLabel::Out Name; + Lazy< IfcMeasureWithUnit > ConversionFactor; + }; + + // C++ wrapper for IfcCooledBeamType + struct IfcCooledBeamType : IfcEnergyConversionDeviceType, ObjectHelper<IfcCooledBeamType,1> { IfcCooledBeamType() : Object("IfcCooledBeamType") {} + IfcCooledBeamTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCoolingTowerType + struct IfcCoolingTowerType : IfcEnergyConversionDeviceType, ObjectHelper<IfcCoolingTowerType,1> { IfcCoolingTowerType() : Object("IfcCoolingTowerType") {} + IfcCoolingTowerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCostItem + struct IfcCostItem : IfcControl, ObjectHelper<IfcCostItem,0> { IfcCostItem() : Object("IfcCostItem") {} + + }; + + // C++ wrapper for IfcCostSchedule + struct IfcCostSchedule : IfcControl, ObjectHelper<IfcCostSchedule,8> { IfcCostSchedule() : Object("IfcCostSchedule") {} + Maybe< IfcActorSelect::Out > SubmittedBy; + Maybe< IfcActorSelect::Out > PreparedBy; + Maybe< IfcDateTimeSelect::Out > SubmittedOn; + Maybe< IfcLabel::Out > Status; + Maybe< ListOf< IfcActorSelect, 1, 0 >::Out > TargetUsers; + Maybe< IfcDateTimeSelect::Out > UpdateDate; + IfcIdentifier::Out ID; + IfcCostScheduleTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCovering + struct IfcCovering : IfcBuildingElement, ObjectHelper<IfcCovering,1> { IfcCovering() : Object("IfcCovering") {} + Maybe< IfcCoveringTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcCoveringType + struct IfcCoveringType : IfcBuildingElementType, ObjectHelper<IfcCoveringType,1> { IfcCoveringType() : Object("IfcCoveringType") {} + IfcCoveringTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCraneRailAShapeProfileDef + struct IfcCraneRailAShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcCraneRailAShapeProfileDef,12> { IfcCraneRailAShapeProfileDef() : Object("IfcCraneRailAShapeProfileDef") {} + IfcPositiveLengthMeasure::Out OverallHeight; + IfcPositiveLengthMeasure::Out BaseWidth2; + Maybe< IfcPositiveLengthMeasure::Out > Radius; + IfcPositiveLengthMeasure::Out HeadWidth; + IfcPositiveLengthMeasure::Out HeadDepth2; + IfcPositiveLengthMeasure::Out HeadDepth3; + IfcPositiveLengthMeasure::Out WebThickness; + IfcPositiveLengthMeasure::Out BaseWidth4; + IfcPositiveLengthMeasure::Out BaseDepth1; + IfcPositiveLengthMeasure::Out BaseDepth2; + IfcPositiveLengthMeasure::Out BaseDepth3; + Maybe< IfcPositiveLengthMeasure::Out > CentreOfGravityInY; + }; + + // C++ wrapper for IfcCraneRailFShapeProfileDef + struct IfcCraneRailFShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcCraneRailFShapeProfileDef,9> { IfcCraneRailFShapeProfileDef() : Object("IfcCraneRailFShapeProfileDef") {} + IfcPositiveLengthMeasure::Out OverallHeight; + IfcPositiveLengthMeasure::Out HeadWidth; + Maybe< IfcPositiveLengthMeasure::Out > Radius; + IfcPositiveLengthMeasure::Out HeadDepth2; + IfcPositiveLengthMeasure::Out HeadDepth3; + IfcPositiveLengthMeasure::Out WebThickness; + IfcPositiveLengthMeasure::Out BaseDepth1; + IfcPositiveLengthMeasure::Out BaseDepth2; + Maybe< IfcPositiveLengthMeasure::Out > CentreOfGravityInY; + }; + + // C++ wrapper for IfcCrewResource + struct IfcCrewResource : IfcConstructionResource, ObjectHelper<IfcCrewResource,0> { IfcCrewResource() : Object("IfcCrewResource") {} + + }; + + // C++ wrapper for IfcSolidModel + struct IfcSolidModel : IfcGeometricRepresentationItem, ObjectHelper<IfcSolidModel,0> { IfcSolidModel() : Object("IfcSolidModel") {} + + }; + + // C++ wrapper for IfcCsgSolid + struct IfcCsgSolid : IfcSolidModel, ObjectHelper<IfcCsgSolid,1> { IfcCsgSolid() : Object("IfcCsgSolid") {} + IfcCsgSelect::Out TreeRootExpression; + }; + + // C++ wrapper for IfcCurtainWall + struct IfcCurtainWall : IfcBuildingElement, ObjectHelper<IfcCurtainWall,0> { IfcCurtainWall() : Object("IfcCurtainWall") {} + + }; + + // C++ wrapper for IfcCurtainWallType + struct IfcCurtainWallType : IfcBuildingElementType, ObjectHelper<IfcCurtainWallType,1> { IfcCurtainWallType() : Object("IfcCurtainWallType") {} + IfcCurtainWallTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCurveBoundedPlane + struct IfcCurveBoundedPlane : IfcBoundedSurface, ObjectHelper<IfcCurveBoundedPlane,3> { IfcCurveBoundedPlane() : Object("IfcCurveBoundedPlane") {} + Lazy< IfcPlane > BasisSurface; + Lazy< IfcCurve > OuterBoundary; + ListOf< Lazy< IfcCurve >, 0, 0 > InnerBoundaries; + }; + + // C++ wrapper for IfcPresentationStyle + struct IfcPresentationStyle : ObjectHelper<IfcPresentationStyle,1> { IfcPresentationStyle() : Object("IfcPresentationStyle") {} + Maybe< IfcLabel::Out > Name; + }; + + // C++ wrapper for IfcDamperType + struct IfcDamperType : IfcFlowControllerType, ObjectHelper<IfcDamperType,1> { IfcDamperType() : Object("IfcDamperType") {} + IfcDamperTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcDefinedSymbol + struct IfcDefinedSymbol : IfcGeometricRepresentationItem, ObjectHelper<IfcDefinedSymbol,2> { IfcDefinedSymbol() : Object("IfcDefinedSymbol") {} + IfcDefinedSymbolSelect::Out Definition; + Lazy< IfcCartesianTransformationOperator2D > Target; + }; + + // C++ wrapper for IfcDerivedProfileDef + struct IfcDerivedProfileDef : IfcProfileDef, ObjectHelper<IfcDerivedProfileDef,3> { IfcDerivedProfileDef() : Object("IfcDerivedProfileDef") {} + Lazy< IfcProfileDef > ParentProfile; + Lazy< IfcCartesianTransformationOperator2D > Operator; + Maybe< IfcLabel::Out > Label; + }; + + // C++ wrapper for IfcDiameterDimension + struct IfcDiameterDimension : IfcDimensionCurveDirectedCallout, ObjectHelper<IfcDiameterDimension,0> { IfcDiameterDimension() : Object("IfcDiameterDimension") {} + + }; + + // C++ wrapper for IfcDimensionCurve + struct IfcDimensionCurve : IfcAnnotationCurveOccurrence, ObjectHelper<IfcDimensionCurve,0> { IfcDimensionCurve() : Object("IfcDimensionCurve") {} + + }; + + // C++ wrapper for IfcTerminatorSymbol + struct IfcTerminatorSymbol : IfcAnnotationSymbolOccurrence, ObjectHelper<IfcTerminatorSymbol,1> { IfcTerminatorSymbol() : Object("IfcTerminatorSymbol") {} + Lazy< IfcAnnotationCurveOccurrence > AnnotatedCurve; + }; + + // C++ wrapper for IfcDimensionCurveTerminator + struct IfcDimensionCurveTerminator : IfcTerminatorSymbol, ObjectHelper<IfcDimensionCurveTerminator,1> { IfcDimensionCurveTerminator() : Object("IfcDimensionCurveTerminator") {} + IfcDimensionExtentUsage::Out Role; + }; + + // C++ wrapper for IfcDirection + struct IfcDirection : IfcGeometricRepresentationItem, ObjectHelper<IfcDirection,1> { IfcDirection() : Object("IfcDirection") {} + ListOf< REAL, 2, 3 >::Out DirectionRatios; + }; + + // C++ wrapper for IfcElementComponent + struct IfcElementComponent : IfcElement, ObjectHelper<IfcElementComponent,0> { IfcElementComponent() : Object("IfcElementComponent") {} + + }; + + // C++ wrapper for IfcDiscreteAccessory + struct IfcDiscreteAccessory : IfcElementComponent, ObjectHelper<IfcDiscreteAccessory,0> { IfcDiscreteAccessory() : Object("IfcDiscreteAccessory") {} + + }; + + // C++ wrapper for IfcElementComponentType + struct IfcElementComponentType : IfcElementType, ObjectHelper<IfcElementComponentType,0> { IfcElementComponentType() : Object("IfcElementComponentType") {} + + }; + + // C++ wrapper for IfcDiscreteAccessoryType + struct IfcDiscreteAccessoryType : IfcElementComponentType, ObjectHelper<IfcDiscreteAccessoryType,0> { IfcDiscreteAccessoryType() : Object("IfcDiscreteAccessoryType") {} + + }; + + // C++ wrapper for IfcDistributionElement + struct IfcDistributionElement : IfcElement, ObjectHelper<IfcDistributionElement,0> { IfcDistributionElement() : Object("IfcDistributionElement") {} + + }; + + // C++ wrapper for IfcDistributionFlowElement + struct IfcDistributionFlowElement : IfcDistributionElement, ObjectHelper<IfcDistributionFlowElement,0> { IfcDistributionFlowElement() : Object("IfcDistributionFlowElement") {} + + }; + + // C++ wrapper for IfcDistributionChamberElement + struct IfcDistributionChamberElement : IfcDistributionFlowElement, ObjectHelper<IfcDistributionChamberElement,0> { IfcDistributionChamberElement() : Object("IfcDistributionChamberElement") {} + + }; + + // C++ wrapper for IfcDistributionChamberElementType + struct IfcDistributionChamberElementType : IfcDistributionFlowElementType, ObjectHelper<IfcDistributionChamberElementType,1> { IfcDistributionChamberElementType() : Object("IfcDistributionChamberElementType") {} + IfcDistributionChamberElementTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcDistributionControlElement + struct IfcDistributionControlElement : IfcDistributionElement, ObjectHelper<IfcDistributionControlElement,1> { IfcDistributionControlElement() : Object("IfcDistributionControlElement") {} + Maybe< IfcIdentifier::Out > ControlElementId; + }; + + // C++ wrapper for IfcPort + struct IfcPort : IfcProduct, ObjectHelper<IfcPort,0> { IfcPort() : Object("IfcPort") {} + + }; + + // C++ wrapper for IfcDistributionPort + struct IfcDistributionPort : IfcPort, ObjectHelper<IfcDistributionPort,1> { IfcDistributionPort() : Object("IfcDistributionPort") {} + Maybe< IfcFlowDirectionEnum::Out > FlowDirection; + }; + + // C++ wrapper for IfcDoor + struct IfcDoor : IfcBuildingElement, ObjectHelper<IfcDoor,2> { IfcDoor() : Object("IfcDoor") {} + Maybe< IfcPositiveLengthMeasure::Out > OverallHeight; + Maybe< IfcPositiveLengthMeasure::Out > OverallWidth; + }; + + // C++ wrapper for IfcPropertyDefinition + struct IfcPropertyDefinition : IfcRoot, ObjectHelper<IfcPropertyDefinition,0> { IfcPropertyDefinition() : Object("IfcPropertyDefinition") {} + + }; + + // C++ wrapper for IfcPropertySetDefinition + struct IfcPropertySetDefinition : IfcPropertyDefinition, ObjectHelper<IfcPropertySetDefinition,0> { IfcPropertySetDefinition() : Object("IfcPropertySetDefinition") {} + + }; + + // C++ wrapper for IfcDoorStyle + struct IfcDoorStyle : IfcTypeProduct, ObjectHelper<IfcDoorStyle,4> { IfcDoorStyle() : Object("IfcDoorStyle") {} + IfcDoorStyleOperationEnum::Out OperationType; + IfcDoorStyleConstructionEnum::Out ConstructionType; + BOOLEAN::Out ParameterTakesPrecedence; + BOOLEAN::Out Sizeable; + }; + + // C++ wrapper for IfcDuctFittingType + struct IfcDuctFittingType : IfcFlowFittingType, ObjectHelper<IfcDuctFittingType,1> { IfcDuctFittingType() : Object("IfcDuctFittingType") {} + IfcDuctFittingTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcDuctSegmentType + struct IfcDuctSegmentType : IfcFlowSegmentType, ObjectHelper<IfcDuctSegmentType,1> { IfcDuctSegmentType() : Object("IfcDuctSegmentType") {} + IfcDuctSegmentTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFlowTreatmentDeviceType + struct IfcFlowTreatmentDeviceType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowTreatmentDeviceType,0> { IfcFlowTreatmentDeviceType() : Object("IfcFlowTreatmentDeviceType") {} + + }; + + // C++ wrapper for IfcDuctSilencerType + struct IfcDuctSilencerType : IfcFlowTreatmentDeviceType, ObjectHelper<IfcDuctSilencerType,1> { IfcDuctSilencerType() : Object("IfcDuctSilencerType") {} + IfcDuctSilencerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcEdge + struct IfcEdge : IfcTopologicalRepresentationItem, ObjectHelper<IfcEdge,2> { IfcEdge() : Object("IfcEdge") {} + Lazy< IfcVertex > EdgeStart; + Lazy< IfcVertex > EdgeEnd; + }; + + // C++ wrapper for IfcEdgeCurve + struct IfcEdgeCurve : IfcEdge, ObjectHelper<IfcEdgeCurve,2> { IfcEdgeCurve() : Object("IfcEdgeCurve") {} + Lazy< IfcCurve > EdgeGeometry; + BOOLEAN::Out SameSense; + }; + + // C++ wrapper for IfcLoop + struct IfcLoop : IfcTopologicalRepresentationItem, ObjectHelper<IfcLoop,0> { IfcLoop() : Object("IfcLoop") {} + + }; + + // C++ wrapper for IfcEdgeLoop + struct IfcEdgeLoop : IfcLoop, ObjectHelper<IfcEdgeLoop,1> { IfcEdgeLoop() : Object("IfcEdgeLoop") {} + ListOf< Lazy< IfcOrientedEdge >, 1, 0 > EdgeList; + }; + + // C++ wrapper for IfcElectricApplianceType + struct IfcElectricApplianceType : IfcFlowTerminalType, ObjectHelper<IfcElectricApplianceType,1> { IfcElectricApplianceType() : Object("IfcElectricApplianceType") {} + IfcElectricApplianceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFlowController + struct IfcFlowController : IfcDistributionFlowElement, ObjectHelper<IfcFlowController,0> { IfcFlowController() : Object("IfcFlowController") {} + + }; + + // C++ wrapper for IfcElectricDistributionPoint + struct IfcElectricDistributionPoint : IfcFlowController, ObjectHelper<IfcElectricDistributionPoint,2> { IfcElectricDistributionPoint() : Object("IfcElectricDistributionPoint") {} + IfcElectricDistributionPointFunctionEnum::Out DistributionPointFunction; + Maybe< IfcLabel::Out > UserDefinedFunction; + }; + + // C++ wrapper for IfcFlowStorageDeviceType + struct IfcFlowStorageDeviceType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowStorageDeviceType,0> { IfcFlowStorageDeviceType() : Object("IfcFlowStorageDeviceType") {} + + }; + + // C++ wrapper for IfcElectricFlowStorageDeviceType + struct IfcElectricFlowStorageDeviceType : IfcFlowStorageDeviceType, ObjectHelper<IfcElectricFlowStorageDeviceType,1> { IfcElectricFlowStorageDeviceType() : Object("IfcElectricFlowStorageDeviceType") {} + IfcElectricFlowStorageDeviceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcElectricGeneratorType + struct IfcElectricGeneratorType : IfcEnergyConversionDeviceType, ObjectHelper<IfcElectricGeneratorType,1> { IfcElectricGeneratorType() : Object("IfcElectricGeneratorType") {} + IfcElectricGeneratorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcElectricHeaterType + struct IfcElectricHeaterType : IfcFlowTerminalType, ObjectHelper<IfcElectricHeaterType,1> { IfcElectricHeaterType() : Object("IfcElectricHeaterType") {} + IfcElectricHeaterTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcElectricMotorType + struct IfcElectricMotorType : IfcEnergyConversionDeviceType, ObjectHelper<IfcElectricMotorType,1> { IfcElectricMotorType() : Object("IfcElectricMotorType") {} + IfcElectricMotorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcElectricTimeControlType + struct IfcElectricTimeControlType : IfcFlowControllerType, ObjectHelper<IfcElectricTimeControlType,1> { IfcElectricTimeControlType() : Object("IfcElectricTimeControlType") {} + IfcElectricTimeControlTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcSystem + struct IfcSystem : IfcGroup, ObjectHelper<IfcSystem,0> { IfcSystem() : Object("IfcSystem") {} + + }; + + // C++ wrapper for IfcElectricalCircuit + struct IfcElectricalCircuit : IfcSystem, ObjectHelper<IfcElectricalCircuit,0> { IfcElectricalCircuit() : Object("IfcElectricalCircuit") {} + + }; + + // C++ wrapper for IfcElectricalElement + struct IfcElectricalElement : IfcElement, ObjectHelper<IfcElectricalElement,0> { IfcElectricalElement() : Object("IfcElectricalElement") {} + + }; + + // C++ wrapper for IfcElementAssembly + struct IfcElementAssembly : IfcElement, ObjectHelper<IfcElementAssembly,2> { IfcElementAssembly() : Object("IfcElementAssembly") {} + Maybe< IfcAssemblyPlaceEnum::Out > AssemblyPlace; + IfcElementAssemblyTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcElementQuantity + struct IfcElementQuantity : IfcPropertySetDefinition, ObjectHelper<IfcElementQuantity,2> { IfcElementQuantity() : Object("IfcElementQuantity") {} + Maybe< IfcLabel::Out > MethodOfMeasurement; + ListOf< Lazy< NotImplemented >, 1, 0 > Quantities; + }; + + // C++ wrapper for IfcElementarySurface + struct IfcElementarySurface : IfcSurface, ObjectHelper<IfcElementarySurface,1> { IfcElementarySurface() : Object("IfcElementarySurface") {} + Lazy< IfcAxis2Placement3D > Position; + }; + + // C++ wrapper for IfcEllipse + struct IfcEllipse : IfcConic, ObjectHelper<IfcEllipse,2> { IfcEllipse() : Object("IfcEllipse") {} + IfcPositiveLengthMeasure::Out SemiAxis1; + IfcPositiveLengthMeasure::Out SemiAxis2; + }; + + // C++ wrapper for IfcEllipseProfileDef + struct IfcEllipseProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcEllipseProfileDef,2> { IfcEllipseProfileDef() : Object("IfcEllipseProfileDef") {} + IfcPositiveLengthMeasure::Out SemiAxis1; + IfcPositiveLengthMeasure::Out SemiAxis2; + }; + + // C++ wrapper for IfcEnergyConversionDevice + struct IfcEnergyConversionDevice : IfcDistributionFlowElement, ObjectHelper<IfcEnergyConversionDevice,0> { IfcEnergyConversionDevice() : Object("IfcEnergyConversionDevice") {} + + }; + + // C++ wrapper for IfcEquipmentElement + struct IfcEquipmentElement : IfcElement, ObjectHelper<IfcEquipmentElement,0> { IfcEquipmentElement() : Object("IfcEquipmentElement") {} + + }; + + // C++ wrapper for IfcEquipmentStandard + struct IfcEquipmentStandard : IfcControl, ObjectHelper<IfcEquipmentStandard,0> { IfcEquipmentStandard() : Object("IfcEquipmentStandard") {} + + }; + + // C++ wrapper for IfcEvaporativeCoolerType + struct IfcEvaporativeCoolerType : IfcEnergyConversionDeviceType, ObjectHelper<IfcEvaporativeCoolerType,1> { IfcEvaporativeCoolerType() : Object("IfcEvaporativeCoolerType") {} + IfcEvaporativeCoolerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcEvaporatorType + struct IfcEvaporatorType : IfcEnergyConversionDeviceType, ObjectHelper<IfcEvaporatorType,1> { IfcEvaporatorType() : Object("IfcEvaporatorType") {} + IfcEvaporatorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcSweptAreaSolid + struct IfcSweptAreaSolid : IfcSolidModel, ObjectHelper<IfcSweptAreaSolid,2> { IfcSweptAreaSolid() : Object("IfcSweptAreaSolid") {} + Lazy< IfcProfileDef > SweptArea; + Lazy< IfcAxis2Placement3D > Position; + }; + + // C++ wrapper for IfcExtrudedAreaSolid + struct IfcExtrudedAreaSolid : IfcSweptAreaSolid, ObjectHelper<IfcExtrudedAreaSolid,2> { IfcExtrudedAreaSolid() : Object("IfcExtrudedAreaSolid") {} + Lazy< IfcDirection > ExtrudedDirection; + IfcPositiveLengthMeasure::Out Depth; + }; + + // C++ wrapper for IfcFace + struct IfcFace : IfcTopologicalRepresentationItem, ObjectHelper<IfcFace,1> { IfcFace() : Object("IfcFace") {} + ListOf< Lazy< IfcFaceBound >, 1, 0 > Bounds; + }; + + // C++ wrapper for IfcFaceBasedSurfaceModel + struct IfcFaceBasedSurfaceModel : IfcGeometricRepresentationItem, ObjectHelper<IfcFaceBasedSurfaceModel,1> { IfcFaceBasedSurfaceModel() : Object("IfcFaceBasedSurfaceModel") {} + ListOf< Lazy< IfcConnectedFaceSet >, 1, 0 > FbsmFaces; + }; + + // C++ wrapper for IfcFaceBound + struct IfcFaceBound : IfcTopologicalRepresentationItem, ObjectHelper<IfcFaceBound,2> { IfcFaceBound() : Object("IfcFaceBound") {} + Lazy< IfcLoop > Bound; + BOOLEAN::Out Orientation; + }; + + // C++ wrapper for IfcFaceOuterBound + struct IfcFaceOuterBound : IfcFaceBound, ObjectHelper<IfcFaceOuterBound,0> { IfcFaceOuterBound() : Object("IfcFaceOuterBound") {} + + }; + + // C++ wrapper for IfcFaceSurface + struct IfcFaceSurface : IfcFace, ObjectHelper<IfcFaceSurface,2> { IfcFaceSurface() : Object("IfcFaceSurface") {} + Lazy< IfcSurface > FaceSurface; + BOOLEAN::Out SameSense; + }; + + // C++ wrapper for IfcManifoldSolidBrep + struct IfcManifoldSolidBrep : IfcSolidModel, ObjectHelper<IfcManifoldSolidBrep,1> { IfcManifoldSolidBrep() : Object("IfcManifoldSolidBrep") {} + Lazy< IfcClosedShell > Outer; + }; + + // C++ wrapper for IfcFacetedBrep + struct IfcFacetedBrep : IfcManifoldSolidBrep, ObjectHelper<IfcFacetedBrep,0> { IfcFacetedBrep() : Object("IfcFacetedBrep") {} + + }; + + // C++ wrapper for IfcFacetedBrepWithVoids + struct IfcFacetedBrepWithVoids : IfcManifoldSolidBrep, ObjectHelper<IfcFacetedBrepWithVoids,1> { IfcFacetedBrepWithVoids() : Object("IfcFacetedBrepWithVoids") {} + ListOf< Lazy< IfcClosedShell >, 1, 0 > Voids; + }; + + // C++ wrapper for IfcFanType + struct IfcFanType : IfcFlowMovingDeviceType, ObjectHelper<IfcFanType,1> { IfcFanType() : Object("IfcFanType") {} + IfcFanTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFastener + struct IfcFastener : IfcElementComponent, ObjectHelper<IfcFastener,0> { IfcFastener() : Object("IfcFastener") {} + + }; + + // C++ wrapper for IfcFastenerType + struct IfcFastenerType : IfcElementComponentType, ObjectHelper<IfcFastenerType,0> { IfcFastenerType() : Object("IfcFastenerType") {} + + }; + + // C++ wrapper for IfcFeatureElementAddition + struct IfcFeatureElementAddition : IfcFeatureElement, ObjectHelper<IfcFeatureElementAddition,0> { IfcFeatureElementAddition() : Object("IfcFeatureElementAddition") {} + + }; + + // C++ wrapper for IfcFillAreaStyleHatching + struct IfcFillAreaStyleHatching : IfcGeometricRepresentationItem, ObjectHelper<IfcFillAreaStyleHatching,5> { IfcFillAreaStyleHatching() : Object("IfcFillAreaStyleHatching") {} + Lazy< NotImplemented > HatchLineAppearance; + IfcHatchLineDistanceSelect::Out StartOfNextHatchLine; + Maybe< Lazy< IfcCartesianPoint > > PointOfReferenceHatchLine; + Maybe< Lazy< IfcCartesianPoint > > PatternStart; + IfcPlaneAngleMeasure::Out HatchLineAngle; + }; + + // C++ wrapper for IfcFillAreaStyleTileSymbolWithStyle + struct IfcFillAreaStyleTileSymbolWithStyle : IfcGeometricRepresentationItem, ObjectHelper<IfcFillAreaStyleTileSymbolWithStyle,1> { IfcFillAreaStyleTileSymbolWithStyle() : Object("IfcFillAreaStyleTileSymbolWithStyle") {} + Lazy< IfcAnnotationSymbolOccurrence > Symbol; + }; + + // C++ wrapper for IfcFillAreaStyleTiles + struct IfcFillAreaStyleTiles : IfcGeometricRepresentationItem, ObjectHelper<IfcFillAreaStyleTiles,3> { IfcFillAreaStyleTiles() : Object("IfcFillAreaStyleTiles") {} + Lazy< IfcOneDirectionRepeatFactor > TilingPattern; + ListOf< IfcFillAreaStyleTileShapeSelect, 1, 0 >::Out Tiles; + IfcPositiveRatioMeasure::Out TilingScale; + }; + + // C++ wrapper for IfcFilterType + struct IfcFilterType : IfcFlowTreatmentDeviceType, ObjectHelper<IfcFilterType,1> { IfcFilterType() : Object("IfcFilterType") {} + IfcFilterTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFireSuppressionTerminalType + struct IfcFireSuppressionTerminalType : IfcFlowTerminalType, ObjectHelper<IfcFireSuppressionTerminalType,1> { IfcFireSuppressionTerminalType() : Object("IfcFireSuppressionTerminalType") {} + IfcFireSuppressionTerminalTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFlowFitting + struct IfcFlowFitting : IfcDistributionFlowElement, ObjectHelper<IfcFlowFitting,0> { IfcFlowFitting() : Object("IfcFlowFitting") {} + + }; + + // C++ wrapper for IfcFlowInstrumentType + struct IfcFlowInstrumentType : IfcDistributionControlElementType, ObjectHelper<IfcFlowInstrumentType,1> { IfcFlowInstrumentType() : Object("IfcFlowInstrumentType") {} + IfcFlowInstrumentTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFlowMeterType + struct IfcFlowMeterType : IfcFlowControllerType, ObjectHelper<IfcFlowMeterType,1> { IfcFlowMeterType() : Object("IfcFlowMeterType") {} + IfcFlowMeterTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFlowMovingDevice + struct IfcFlowMovingDevice : IfcDistributionFlowElement, ObjectHelper<IfcFlowMovingDevice,0> { IfcFlowMovingDevice() : Object("IfcFlowMovingDevice") {} + + }; + + // C++ wrapper for IfcFlowSegment + struct IfcFlowSegment : IfcDistributionFlowElement, ObjectHelper<IfcFlowSegment,0> { IfcFlowSegment() : Object("IfcFlowSegment") {} + + }; + + // C++ wrapper for IfcFlowStorageDevice + struct IfcFlowStorageDevice : IfcDistributionFlowElement, ObjectHelper<IfcFlowStorageDevice,0> { IfcFlowStorageDevice() : Object("IfcFlowStorageDevice") {} + + }; + + // C++ wrapper for IfcFlowTerminal + struct IfcFlowTerminal : IfcDistributionFlowElement, ObjectHelper<IfcFlowTerminal,0> { IfcFlowTerminal() : Object("IfcFlowTerminal") {} + + }; + + // C++ wrapper for IfcFlowTreatmentDevice + struct IfcFlowTreatmentDevice : IfcDistributionFlowElement, ObjectHelper<IfcFlowTreatmentDevice,0> { IfcFlowTreatmentDevice() : Object("IfcFlowTreatmentDevice") {} + + }; + + // C++ wrapper for IfcFooting + struct IfcFooting : IfcBuildingElement, ObjectHelper<IfcFooting,1> { IfcFooting() : Object("IfcFooting") {} + IfcFootingTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFurnishingElement + struct IfcFurnishingElement : IfcElement, ObjectHelper<IfcFurnishingElement,0> { IfcFurnishingElement() : Object("IfcFurnishingElement") {} + + }; + + // C++ wrapper for IfcFurnishingElementType + struct IfcFurnishingElementType : IfcElementType, ObjectHelper<IfcFurnishingElementType,0> { IfcFurnishingElementType() : Object("IfcFurnishingElementType") {} + + }; + + // C++ wrapper for IfcFurnitureStandard + struct IfcFurnitureStandard : IfcControl, ObjectHelper<IfcFurnitureStandard,0> { IfcFurnitureStandard() : Object("IfcFurnitureStandard") {} + + }; + + // C++ wrapper for IfcFurnitureType + struct IfcFurnitureType : IfcFurnishingElementType, ObjectHelper<IfcFurnitureType,1> { IfcFurnitureType() : Object("IfcFurnitureType") {} + IfcAssemblyPlaceEnum::Out AssemblyPlace; + }; + + // C++ wrapper for IfcGasTerminalType + struct IfcGasTerminalType : IfcFlowTerminalType, ObjectHelper<IfcGasTerminalType,1> { IfcGasTerminalType() : Object("IfcGasTerminalType") {} + IfcGasTerminalTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcGeometricSet + struct IfcGeometricSet : IfcGeometricRepresentationItem, ObjectHelper<IfcGeometricSet,1> { IfcGeometricSet() : Object("IfcGeometricSet") {} + ListOf< IfcGeometricSetSelect, 1, 0 >::Out Elements; + }; + + // C++ wrapper for IfcGeometricCurveSet + struct IfcGeometricCurveSet : IfcGeometricSet, ObjectHelper<IfcGeometricCurveSet,0> { IfcGeometricCurveSet() : Object("IfcGeometricCurveSet") {} + + }; + + // C++ wrapper for IfcRepresentationContext + struct IfcRepresentationContext : ObjectHelper<IfcRepresentationContext,2> { IfcRepresentationContext() : Object("IfcRepresentationContext") {} + Maybe< IfcLabel::Out > ContextIdentifier; + Maybe< IfcLabel::Out > ContextType; + }; + + // C++ wrapper for IfcGeometricRepresentationContext + struct IfcGeometricRepresentationContext : IfcRepresentationContext, ObjectHelper<IfcGeometricRepresentationContext,4> { IfcGeometricRepresentationContext() : Object("IfcGeometricRepresentationContext") {} + IfcDimensionCount::Out CoordinateSpaceDimension; + Maybe< REAL::Out > Precision; + IfcAxis2Placement::Out WorldCoordinateSystem; + Maybe< Lazy< IfcDirection > > TrueNorth; + }; + + // C++ wrapper for IfcGeometricRepresentationSubContext + struct IfcGeometricRepresentationSubContext : IfcGeometricRepresentationContext, ObjectHelper<IfcGeometricRepresentationSubContext,4> { IfcGeometricRepresentationSubContext() : Object("IfcGeometricRepresentationSubContext") {} + Lazy< IfcGeometricRepresentationContext > ParentContext; + Maybe< IfcPositiveRatioMeasure::Out > TargetScale; + IfcGeometricProjectionEnum::Out TargetView; + Maybe< IfcLabel::Out > UserDefinedTargetView; + }; + + // C++ wrapper for IfcGrid + struct IfcGrid : IfcProduct, ObjectHelper<IfcGrid,3> { IfcGrid() : Object("IfcGrid") {} + ListOf< Lazy< NotImplemented >, 1, 0 > UAxes; + ListOf< Lazy< NotImplemented >, 1, 0 > VAxes; + Maybe< ListOf< Lazy< NotImplemented >, 1, 0 > > WAxes; + }; + + // C++ wrapper for IfcObjectPlacement + struct IfcObjectPlacement : ObjectHelper<IfcObjectPlacement,0> { IfcObjectPlacement() : Object("IfcObjectPlacement") {} + + }; + + // C++ wrapper for IfcGridPlacement + struct IfcGridPlacement : IfcObjectPlacement, ObjectHelper<IfcGridPlacement,2> { IfcGridPlacement() : Object("IfcGridPlacement") {} + Lazy< NotImplemented > PlacementLocation; + Maybe< Lazy< NotImplemented > > PlacementRefDirection; + }; + + // C++ wrapper for IfcHeatExchangerType + struct IfcHeatExchangerType : IfcEnergyConversionDeviceType, ObjectHelper<IfcHeatExchangerType,1> { IfcHeatExchangerType() : Object("IfcHeatExchangerType") {} + IfcHeatExchangerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcHumidifierType + struct IfcHumidifierType : IfcEnergyConversionDeviceType, ObjectHelper<IfcHumidifierType,1> { IfcHumidifierType() : Object("IfcHumidifierType") {} + IfcHumidifierTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcInventory + struct IfcInventory : IfcGroup, ObjectHelper<IfcInventory,6> { IfcInventory() : Object("IfcInventory") {} + IfcInventoryTypeEnum::Out InventoryType; + IfcActorSelect::Out Jurisdiction; + ListOf< Lazy< NotImplemented >, 1, 0 > ResponsiblePersons; + Lazy< NotImplemented > LastUpdateDate; + Maybe< Lazy< NotImplemented > > CurrentValue; + Maybe< Lazy< NotImplemented > > OriginalValue; + }; + + // C++ wrapper for IfcJunctionBoxType + struct IfcJunctionBoxType : IfcFlowFittingType, ObjectHelper<IfcJunctionBoxType,1> { IfcJunctionBoxType() : Object("IfcJunctionBoxType") {} + IfcJunctionBoxTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcLShapeProfileDef + struct IfcLShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcLShapeProfileDef,8> { IfcLShapeProfileDef() : Object("IfcLShapeProfileDef") {} + IfcPositiveLengthMeasure::Out Depth; + Maybe< IfcPositiveLengthMeasure::Out > Width; + IfcPositiveLengthMeasure::Out Thickness; + Maybe< IfcPositiveLengthMeasure::Out > FilletRadius; + Maybe< IfcPositiveLengthMeasure::Out > EdgeRadius; + Maybe< IfcPlaneAngleMeasure::Out > LegSlope; + Maybe< IfcPositiveLengthMeasure::Out > CentreOfGravityInX; + Maybe< IfcPositiveLengthMeasure::Out > CentreOfGravityInY; + }; + + // C++ wrapper for IfcLaborResource + struct IfcLaborResource : IfcConstructionResource, ObjectHelper<IfcLaborResource,1> { IfcLaborResource() : Object("IfcLaborResource") {} + Maybe< IfcText::Out > SkillSet; + }; + + // C++ wrapper for IfcLampType + struct IfcLampType : IfcFlowTerminalType, ObjectHelper<IfcLampType,1> { IfcLampType() : Object("IfcLampType") {} + IfcLampTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcLightFixtureType + struct IfcLightFixtureType : IfcFlowTerminalType, ObjectHelper<IfcLightFixtureType,1> { IfcLightFixtureType() : Object("IfcLightFixtureType") {} + IfcLightFixtureTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcLightSource + struct IfcLightSource : IfcGeometricRepresentationItem, ObjectHelper<IfcLightSource,4> { IfcLightSource() : Object("IfcLightSource") {} + Maybe< IfcLabel::Out > Name; + Lazy< IfcColourRgb > LightColour; + Maybe< IfcNormalisedRatioMeasure::Out > AmbientIntensity; + Maybe< IfcNormalisedRatioMeasure::Out > Intensity; + }; + + // C++ wrapper for IfcLightSourceAmbient + struct IfcLightSourceAmbient : IfcLightSource, ObjectHelper<IfcLightSourceAmbient,0> { IfcLightSourceAmbient() : Object("IfcLightSourceAmbient") {} + + }; + + // C++ wrapper for IfcLightSourceDirectional + struct IfcLightSourceDirectional : IfcLightSource, ObjectHelper<IfcLightSourceDirectional,1> { IfcLightSourceDirectional() : Object("IfcLightSourceDirectional") {} + Lazy< IfcDirection > Orientation; + }; + + // C++ wrapper for IfcLightSourceGoniometric + struct IfcLightSourceGoniometric : IfcLightSource, ObjectHelper<IfcLightSourceGoniometric,6> { IfcLightSourceGoniometric() : Object("IfcLightSourceGoniometric") {} + Lazy< IfcAxis2Placement3D > Position; + Maybe< Lazy< IfcColourRgb > > ColourAppearance; + IfcThermodynamicTemperatureMeasure::Out ColourTemperature; + IfcLuminousFluxMeasure::Out LuminousFlux; + IfcLightEmissionSourceEnum::Out LightEmissionSource; + IfcLightDistributionDataSourceSelect::Out LightDistributionDataSource; + }; + + // C++ wrapper for IfcLightSourcePositional + struct IfcLightSourcePositional : IfcLightSource, ObjectHelper<IfcLightSourcePositional,5> { IfcLightSourcePositional() : Object("IfcLightSourcePositional") {} + Lazy< IfcCartesianPoint > Position; + IfcPositiveLengthMeasure::Out Radius; + IfcReal::Out ConstantAttenuation; + IfcReal::Out DistanceAttenuation; + IfcReal::Out QuadricAttenuation; + }; + + // C++ wrapper for IfcLightSourceSpot + struct IfcLightSourceSpot : IfcLightSourcePositional, ObjectHelper<IfcLightSourceSpot,4> { IfcLightSourceSpot() : Object("IfcLightSourceSpot") {} + Lazy< IfcDirection > Orientation; + Maybe< IfcReal::Out > ConcentrationExponent; + IfcPositivePlaneAngleMeasure::Out SpreadAngle; + IfcPositivePlaneAngleMeasure::Out BeamWidthAngle; + }; + + // C++ wrapper for IfcLine + struct IfcLine : IfcCurve, ObjectHelper<IfcLine,2> { IfcLine() : Object("IfcLine") {} + Lazy< IfcCartesianPoint > Pnt; + Lazy< IfcVector > Dir; + }; + + // C++ wrapper for IfcLinearDimension + struct IfcLinearDimension : IfcDimensionCurveDirectedCallout, ObjectHelper<IfcLinearDimension,0> { IfcLinearDimension() : Object("IfcLinearDimension") {} + + }; + + // C++ wrapper for IfcLocalPlacement + struct IfcLocalPlacement : IfcObjectPlacement, ObjectHelper<IfcLocalPlacement,2> { IfcLocalPlacement() : Object("IfcLocalPlacement") {} + Maybe< Lazy< IfcObjectPlacement > > PlacementRelTo; + IfcAxis2Placement::Out RelativePlacement; + }; + + // C++ wrapper for IfcMappedItem + struct IfcMappedItem : IfcRepresentationItem, ObjectHelper<IfcMappedItem,2> { IfcMappedItem() : Object("IfcMappedItem") {} + Lazy< IfcRepresentationMap > MappingSource; + Lazy< IfcCartesianTransformationOperator > MappingTarget; + }; + + // C++ wrapper for IfcProductRepresentation + struct IfcProductRepresentation : ObjectHelper<IfcProductRepresentation,3> { IfcProductRepresentation() : Object("IfcProductRepresentation") {} + Maybe< IfcLabel::Out > Name; + Maybe< IfcText::Out > Description; + ListOf< Lazy< IfcRepresentation >, 1, 0 > Representations; + }; + + // C++ wrapper for IfcMaterialDefinitionRepresentation + struct IfcMaterialDefinitionRepresentation : IfcProductRepresentation, ObjectHelper<IfcMaterialDefinitionRepresentation,1> { IfcMaterialDefinitionRepresentation() : Object("IfcMaterialDefinitionRepresentation") {} + Lazy< NotImplemented > RepresentedMaterial; + }; + + // C++ wrapper for IfcMeasureWithUnit + struct IfcMeasureWithUnit : ObjectHelper<IfcMeasureWithUnit,2> { IfcMeasureWithUnit() : Object("IfcMeasureWithUnit") {} + IfcValue::Out ValueComponent; + IfcUnit::Out UnitComponent; + }; + + // C++ wrapper for IfcMechanicalFastener + struct IfcMechanicalFastener : IfcFastener, ObjectHelper<IfcMechanicalFastener,2> { IfcMechanicalFastener() : Object("IfcMechanicalFastener") {} + Maybe< IfcPositiveLengthMeasure::Out > NominalDiameter; + Maybe< IfcPositiveLengthMeasure::Out > NominalLength; + }; + + // C++ wrapper for IfcMechanicalFastenerType + struct IfcMechanicalFastenerType : IfcFastenerType, ObjectHelper<IfcMechanicalFastenerType,0> { IfcMechanicalFastenerType() : Object("IfcMechanicalFastenerType") {} + + }; + + // C++ wrapper for IfcMember + struct IfcMember : IfcBuildingElement, ObjectHelper<IfcMember,0> { IfcMember() : Object("IfcMember") {} + + }; + + // C++ wrapper for IfcMemberType + struct IfcMemberType : IfcBuildingElementType, ObjectHelper<IfcMemberType,1> { IfcMemberType() : Object("IfcMemberType") {} + IfcMemberTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcMotorConnectionType + struct IfcMotorConnectionType : IfcEnergyConversionDeviceType, ObjectHelper<IfcMotorConnectionType,1> { IfcMotorConnectionType() : Object("IfcMotorConnectionType") {} + IfcMotorConnectionTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcProcess + struct IfcProcess : IfcObject, ObjectHelper<IfcProcess,0> { IfcProcess() : Object("IfcProcess") {} + + }; + + // C++ wrapper for IfcTask + struct IfcTask : IfcProcess, ObjectHelper<IfcTask,5> { IfcTask() : Object("IfcTask") {} + IfcIdentifier::Out TaskId; + Maybe< IfcLabel::Out > Status; + Maybe< IfcLabel::Out > WorkMethod; + BOOLEAN::Out IsMilestone; + Maybe< INTEGER::Out > Priority; + }; + + // C++ wrapper for IfcMove + struct IfcMove : IfcTask, ObjectHelper<IfcMove,3> { IfcMove() : Object("IfcMove") {} + Lazy< IfcSpatialStructureElement > MoveFrom; + Lazy< IfcSpatialStructureElement > MoveTo; + Maybe< ListOf< IfcText, 1, 0 >::Out > PunchList; + }; + + // C++ wrapper for IfcOccupant + struct IfcOccupant : IfcActor, ObjectHelper<IfcOccupant,1> { IfcOccupant() : Object("IfcOccupant") {} + IfcOccupantTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcOffsetCurve2D + struct IfcOffsetCurve2D : IfcCurve, ObjectHelper<IfcOffsetCurve2D,3> { IfcOffsetCurve2D() : Object("IfcOffsetCurve2D") {} + Lazy< IfcCurve > BasisCurve; + IfcLengthMeasure::Out Distance; + LOGICAL::Out SelfIntersect; + }; + + // C++ wrapper for IfcOffsetCurve3D + struct IfcOffsetCurve3D : IfcCurve, ObjectHelper<IfcOffsetCurve3D,4> { IfcOffsetCurve3D() : Object("IfcOffsetCurve3D") {} + Lazy< IfcCurve > BasisCurve; + IfcLengthMeasure::Out Distance; + LOGICAL::Out SelfIntersect; + Lazy< IfcDirection > RefDirection; + }; + + // C++ wrapper for IfcOneDirectionRepeatFactor + struct IfcOneDirectionRepeatFactor : IfcGeometricRepresentationItem, ObjectHelper<IfcOneDirectionRepeatFactor,1> { IfcOneDirectionRepeatFactor() : Object("IfcOneDirectionRepeatFactor") {} + Lazy< IfcVector > RepeatFactor; + }; + + // C++ wrapper for IfcOpenShell + struct IfcOpenShell : IfcConnectedFaceSet, ObjectHelper<IfcOpenShell,0> { IfcOpenShell() : Object("IfcOpenShell") {} + + }; + + // C++ wrapper for IfcOpeningElement + struct IfcOpeningElement : IfcFeatureElementSubtraction, ObjectHelper<IfcOpeningElement,0> { IfcOpeningElement() : Object("IfcOpeningElement") {} + + }; + + // C++ wrapper for IfcOrderAction + struct IfcOrderAction : IfcTask, ObjectHelper<IfcOrderAction,1> { IfcOrderAction() : Object("IfcOrderAction") {} + IfcIdentifier::Out ActionID; + }; + + // C++ wrapper for IfcOrientedEdge + struct IfcOrientedEdge : IfcEdge, ObjectHelper<IfcOrientedEdge,2> { IfcOrientedEdge() : Object("IfcOrientedEdge") {} + Lazy< IfcEdge > EdgeElement; + BOOLEAN::Out Orientation; + }; + + // C++ wrapper for IfcOutletType + struct IfcOutletType : IfcFlowTerminalType, ObjectHelper<IfcOutletType,1> { IfcOutletType() : Object("IfcOutletType") {} + IfcOutletTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPath + struct IfcPath : IfcTopologicalRepresentationItem, ObjectHelper<IfcPath,1> { IfcPath() : Object("IfcPath") {} + ListOf< Lazy< IfcOrientedEdge >, 1, 0 > EdgeList; + }; + + // C++ wrapper for IfcPerformanceHistory + struct IfcPerformanceHistory : IfcControl, ObjectHelper<IfcPerformanceHistory,1> { IfcPerformanceHistory() : Object("IfcPerformanceHistory") {} + IfcLabel::Out LifeCyclePhase; + }; + + // C++ wrapper for IfcPermit + struct IfcPermit : IfcControl, ObjectHelper<IfcPermit,1> { IfcPermit() : Object("IfcPermit") {} + IfcIdentifier::Out PermitID; + }; + + // C++ wrapper for IfcPile + struct IfcPile : IfcBuildingElement, ObjectHelper<IfcPile,2> { IfcPile() : Object("IfcPile") {} + IfcPileTypeEnum::Out PredefinedType; + Maybe< IfcPileConstructionEnum::Out > ConstructionType; + }; + + // C++ wrapper for IfcPipeFittingType + struct IfcPipeFittingType : IfcFlowFittingType, ObjectHelper<IfcPipeFittingType,1> { IfcPipeFittingType() : Object("IfcPipeFittingType") {} + IfcPipeFittingTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPipeSegmentType + struct IfcPipeSegmentType : IfcFlowSegmentType, ObjectHelper<IfcPipeSegmentType,1> { IfcPipeSegmentType() : Object("IfcPipeSegmentType") {} + IfcPipeSegmentTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPlanarExtent + struct IfcPlanarExtent : IfcGeometricRepresentationItem, ObjectHelper<IfcPlanarExtent,2> { IfcPlanarExtent() : Object("IfcPlanarExtent") {} + IfcLengthMeasure::Out SizeInX; + IfcLengthMeasure::Out SizeInY; + }; + + // C++ wrapper for IfcPlanarBox + struct IfcPlanarBox : IfcPlanarExtent, ObjectHelper<IfcPlanarBox,1> { IfcPlanarBox() : Object("IfcPlanarBox") {} + IfcAxis2Placement::Out Placement; + }; + + // C++ wrapper for IfcPlane + struct IfcPlane : IfcElementarySurface, ObjectHelper<IfcPlane,0> { IfcPlane() : Object("IfcPlane") {} + + }; + + // C++ wrapper for IfcPlate + struct IfcPlate : IfcBuildingElement, ObjectHelper<IfcPlate,0> { IfcPlate() : Object("IfcPlate") {} + + }; + + // C++ wrapper for IfcPlateType + struct IfcPlateType : IfcBuildingElementType, ObjectHelper<IfcPlateType,1> { IfcPlateType() : Object("IfcPlateType") {} + IfcPlateTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPointOnCurve + struct IfcPointOnCurve : IfcPoint, ObjectHelper<IfcPointOnCurve,2> { IfcPointOnCurve() : Object("IfcPointOnCurve") {} + Lazy< IfcCurve > BasisCurve; + IfcParameterValue::Out PointParameter; + }; + + // C++ wrapper for IfcPointOnSurface + struct IfcPointOnSurface : IfcPoint, ObjectHelper<IfcPointOnSurface,3> { IfcPointOnSurface() : Object("IfcPointOnSurface") {} + Lazy< IfcSurface > BasisSurface; + IfcParameterValue::Out PointParameterU; + IfcParameterValue::Out PointParameterV; + }; + + // C++ wrapper for IfcPolyLoop + struct IfcPolyLoop : IfcLoop, ObjectHelper<IfcPolyLoop,1> { IfcPolyLoop() : Object("IfcPolyLoop") {} + ListOf< Lazy< IfcCartesianPoint >, 3, 0 > Polygon; + }; + + // C++ wrapper for IfcPolygonalBoundedHalfSpace + struct IfcPolygonalBoundedHalfSpace : IfcHalfSpaceSolid, ObjectHelper<IfcPolygonalBoundedHalfSpace,2> { IfcPolygonalBoundedHalfSpace() : Object("IfcPolygonalBoundedHalfSpace") {} + Lazy< IfcAxis2Placement3D > Position; + Lazy< IfcBoundedCurve > PolygonalBoundary; + }; + + // C++ wrapper for IfcPolyline + struct IfcPolyline : IfcBoundedCurve, ObjectHelper<IfcPolyline,1> { IfcPolyline() : Object("IfcPolyline") {} + ListOf< Lazy< IfcCartesianPoint >, 2, 0 > Points; + }; + + // C++ wrapper for IfcPresentationStyleAssignment + struct IfcPresentationStyleAssignment : ObjectHelper<IfcPresentationStyleAssignment,1> { IfcPresentationStyleAssignment() : Object("IfcPresentationStyleAssignment") {} + ListOf< IfcPresentationStyleSelect, 1, 0 >::Out Styles; + }; + + // C++ wrapper for IfcProcedure + struct IfcProcedure : IfcProcess, ObjectHelper<IfcProcedure,3> { IfcProcedure() : Object("IfcProcedure") {} + IfcIdentifier::Out ProcedureID; + IfcProcedureTypeEnum::Out ProcedureType; + Maybe< IfcLabel::Out > UserDefinedProcedureType; + }; + + // C++ wrapper for IfcProductDefinitionShape + struct IfcProductDefinitionShape : IfcProductRepresentation, ObjectHelper<IfcProductDefinitionShape,0> { IfcProductDefinitionShape() : Object("IfcProductDefinitionShape") {} + + }; + + // C++ wrapper for IfcProject + struct IfcProject : IfcObject, ObjectHelper<IfcProject,4> { IfcProject() : Object("IfcProject") {} + Maybe< IfcLabel::Out > LongName; + Maybe< IfcLabel::Out > Phase; + ListOf< Lazy< IfcRepresentationContext >, 1, 0 > RepresentationContexts; + Lazy< IfcUnitAssignment > UnitsInContext; + }; + + // C++ wrapper for IfcProjectOrder + struct IfcProjectOrder : IfcControl, ObjectHelper<IfcProjectOrder,3> { IfcProjectOrder() : Object("IfcProjectOrder") {} + IfcIdentifier::Out ID; + IfcProjectOrderTypeEnum::Out PredefinedType; + Maybe< IfcLabel::Out > Status; + }; + + // C++ wrapper for IfcProjectOrderRecord + struct IfcProjectOrderRecord : IfcControl, ObjectHelper<IfcProjectOrderRecord,2> { IfcProjectOrderRecord() : Object("IfcProjectOrderRecord") {} + ListOf< Lazy< NotImplemented >, 1, 0 > Records; + IfcProjectOrderRecordTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcProjectionCurve + struct IfcProjectionCurve : IfcAnnotationCurveOccurrence, ObjectHelper<IfcProjectionCurve,0> { IfcProjectionCurve() : Object("IfcProjectionCurve") {} + + }; + + // C++ wrapper for IfcProjectionElement + struct IfcProjectionElement : IfcFeatureElementAddition, ObjectHelper<IfcProjectionElement,0> { IfcProjectionElement() : Object("IfcProjectionElement") {} + + }; + + // C++ wrapper for IfcSimpleProperty + struct IfcSimpleProperty : IfcProperty, ObjectHelper<IfcSimpleProperty,0> { IfcSimpleProperty() : Object("IfcSimpleProperty") {} + + }; + + // C++ wrapper for IfcPropertyBoundedValue + struct IfcPropertyBoundedValue : IfcSimpleProperty, ObjectHelper<IfcPropertyBoundedValue,3> { IfcPropertyBoundedValue() : Object("IfcPropertyBoundedValue") {} + Maybe< IfcValue::Out > UpperBoundValue; + Maybe< IfcValue::Out > LowerBoundValue; + Maybe< IfcUnit::Out > Unit; + }; + + // C++ wrapper for IfcPropertyEnumeratedValue + struct IfcPropertyEnumeratedValue : IfcSimpleProperty, ObjectHelper<IfcPropertyEnumeratedValue,2> { IfcPropertyEnumeratedValue() : Object("IfcPropertyEnumeratedValue") {} + ListOf< IfcValue, 1, 0 >::Out EnumerationValues; + Maybe< Lazy< NotImplemented > > EnumerationReference; + }; + + // C++ wrapper for IfcPropertyListValue + struct IfcPropertyListValue : IfcSimpleProperty, ObjectHelper<IfcPropertyListValue,2> { IfcPropertyListValue() : Object("IfcPropertyListValue") {} + ListOf< IfcValue, 1, 0 >::Out ListValues; + Maybe< IfcUnit::Out > Unit; + }; + + // C++ wrapper for IfcPropertyReferenceValue + struct IfcPropertyReferenceValue : IfcSimpleProperty, ObjectHelper<IfcPropertyReferenceValue,2> { IfcPropertyReferenceValue() : Object("IfcPropertyReferenceValue") {} + Maybe< IfcLabel::Out > UsageName; + IfcObjectReferenceSelect::Out PropertyReference; + }; + + // C++ wrapper for IfcPropertySet + struct IfcPropertySet : IfcPropertySetDefinition, ObjectHelper<IfcPropertySet,1> { IfcPropertySet() : Object("IfcPropertySet") {} + ListOf< Lazy< IfcProperty >, 1, 0 > HasProperties; + }; + + // C++ wrapper for IfcPropertySingleValue + struct IfcPropertySingleValue : IfcSimpleProperty, ObjectHelper<IfcPropertySingleValue,2> { IfcPropertySingleValue() : Object("IfcPropertySingleValue") {} + Maybe< IfcValue::Out > NominalValue; + Maybe< IfcUnit::Out > Unit; + }; + + // C++ wrapper for IfcPropertyTableValue + struct IfcPropertyTableValue : IfcSimpleProperty, ObjectHelper<IfcPropertyTableValue,5> { IfcPropertyTableValue() : Object("IfcPropertyTableValue") {} + ListOf< IfcValue, 1, 0 >::Out DefiningValues; + ListOf< IfcValue, 1, 0 >::Out DefinedValues; + Maybe< IfcText::Out > Expression; + Maybe< IfcUnit::Out > DefiningUnit; + Maybe< IfcUnit::Out > DefinedUnit; + }; + + // C++ wrapper for IfcProtectiveDeviceType + struct IfcProtectiveDeviceType : IfcFlowControllerType, ObjectHelper<IfcProtectiveDeviceType,1> { IfcProtectiveDeviceType() : Object("IfcProtectiveDeviceType") {} + IfcProtectiveDeviceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcProxy + struct IfcProxy : IfcProduct, ObjectHelper<IfcProxy,2> { IfcProxy() : Object("IfcProxy") {} + IfcObjectTypeEnum::Out ProxyType; + Maybe< IfcLabel::Out > Tag; + }; + + // C++ wrapper for IfcPumpType + struct IfcPumpType : IfcFlowMovingDeviceType, ObjectHelper<IfcPumpType,1> { IfcPumpType() : Object("IfcPumpType") {} + IfcPumpTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcRadiusDimension + struct IfcRadiusDimension : IfcDimensionCurveDirectedCallout, ObjectHelper<IfcRadiusDimension,0> { IfcRadiusDimension() : Object("IfcRadiusDimension") {} + + }; + + // C++ wrapper for IfcRailing + struct IfcRailing : IfcBuildingElement, ObjectHelper<IfcRailing,1> { IfcRailing() : Object("IfcRailing") {} + Maybe< IfcRailingTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcRailingType + struct IfcRailingType : IfcBuildingElementType, ObjectHelper<IfcRailingType,1> { IfcRailingType() : Object("IfcRailingType") {} + IfcRailingTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcRamp + struct IfcRamp : IfcBuildingElement, ObjectHelper<IfcRamp,1> { IfcRamp() : Object("IfcRamp") {} + IfcRampTypeEnum::Out ShapeType; + }; + + // C++ wrapper for IfcRampFlight + struct IfcRampFlight : IfcBuildingElement, ObjectHelper<IfcRampFlight,0> { IfcRampFlight() : Object("IfcRampFlight") {} + + }; + + // C++ wrapper for IfcRampFlightType + struct IfcRampFlightType : IfcBuildingElementType, ObjectHelper<IfcRampFlightType,1> { IfcRampFlightType() : Object("IfcRampFlightType") {} + IfcRampFlightTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcRationalBezierCurve + struct IfcRationalBezierCurve : IfcBezierCurve, ObjectHelper<IfcRationalBezierCurve,1> { IfcRationalBezierCurve() : Object("IfcRationalBezierCurve") {} + ListOf< REAL, 2, 0 >::Out WeightsData; + }; + + // C++ wrapper for IfcRectangleProfileDef + struct IfcRectangleProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcRectangleProfileDef,2> { IfcRectangleProfileDef() : Object("IfcRectangleProfileDef") {} + IfcPositiveLengthMeasure::Out XDim; + IfcPositiveLengthMeasure::Out YDim; + }; + + // C++ wrapper for IfcRectangleHollowProfileDef + struct IfcRectangleHollowProfileDef : IfcRectangleProfileDef, ObjectHelper<IfcRectangleHollowProfileDef,3> { IfcRectangleHollowProfileDef() : Object("IfcRectangleHollowProfileDef") {} + IfcPositiveLengthMeasure::Out WallThickness; + Maybe< IfcPositiveLengthMeasure::Out > InnerFilletRadius; + Maybe< IfcPositiveLengthMeasure::Out > OuterFilletRadius; + }; + + // C++ wrapper for IfcRectangularPyramid + struct IfcRectangularPyramid : IfcCsgPrimitive3D, ObjectHelper<IfcRectangularPyramid,3> { IfcRectangularPyramid() : Object("IfcRectangularPyramid") {} + IfcPositiveLengthMeasure::Out XLength; + IfcPositiveLengthMeasure::Out YLength; + IfcPositiveLengthMeasure::Out Height; + }; + + // C++ wrapper for IfcRectangularTrimmedSurface + struct IfcRectangularTrimmedSurface : IfcBoundedSurface, ObjectHelper<IfcRectangularTrimmedSurface,7> { IfcRectangularTrimmedSurface() : Object("IfcRectangularTrimmedSurface") {} + Lazy< IfcSurface > BasisSurface; + IfcParameterValue::Out U1; + IfcParameterValue::Out V1; + IfcParameterValue::Out U2; + IfcParameterValue::Out V2; + BOOLEAN::Out Usense; + BOOLEAN::Out Vsense; + }; + + // C++ wrapper for IfcReinforcingElement + struct IfcReinforcingElement : IfcBuildingElementComponent, ObjectHelper<IfcReinforcingElement,1> { IfcReinforcingElement() : Object("IfcReinforcingElement") {} + Maybe< IfcLabel::Out > SteelGrade; + }; + + // C++ wrapper for IfcReinforcingBar + struct IfcReinforcingBar : IfcReinforcingElement, ObjectHelper<IfcReinforcingBar,5> { IfcReinforcingBar() : Object("IfcReinforcingBar") {} + IfcPositiveLengthMeasure::Out NominalDiameter; + IfcAreaMeasure::Out CrossSectionArea; + Maybe< IfcPositiveLengthMeasure::Out > BarLength; + IfcReinforcingBarRoleEnum::Out BarRole; + Maybe< IfcReinforcingBarSurfaceEnum::Out > BarSurface; + }; + + // C++ wrapper for IfcReinforcingMesh + struct IfcReinforcingMesh : IfcReinforcingElement, ObjectHelper<IfcReinforcingMesh,8> { IfcReinforcingMesh() : Object("IfcReinforcingMesh") {} + Maybe< IfcPositiveLengthMeasure::Out > MeshLength; + Maybe< IfcPositiveLengthMeasure::Out > MeshWidth; + IfcPositiveLengthMeasure::Out LongitudinalBarNominalDiameter; + IfcPositiveLengthMeasure::Out TransverseBarNominalDiameter; + IfcAreaMeasure::Out LongitudinalBarCrossSectionArea; + IfcAreaMeasure::Out TransverseBarCrossSectionArea; + IfcPositiveLengthMeasure::Out LongitudinalBarSpacing; + IfcPositiveLengthMeasure::Out TransverseBarSpacing; + }; + + // C++ wrapper for IfcRelationship + struct IfcRelationship : IfcRoot, ObjectHelper<IfcRelationship,0> { IfcRelationship() : Object("IfcRelationship") {} + + }; + + // C++ wrapper for IfcRelDecomposes + struct IfcRelDecomposes : IfcRelationship, ObjectHelper<IfcRelDecomposes,2> { IfcRelDecomposes() : Object("IfcRelDecomposes") {} + Lazy< IfcObjectDefinition > RelatingObject; + ListOf< Lazy< IfcObjectDefinition >, 1, 0 > RelatedObjects; + }; + + // C++ wrapper for IfcRelAggregates + struct IfcRelAggregates : IfcRelDecomposes, ObjectHelper<IfcRelAggregates,0> { IfcRelAggregates() : Object("IfcRelAggregates") {} + + }; + + // C++ wrapper for IfcRelConnects + struct IfcRelConnects : IfcRelationship, ObjectHelper<IfcRelConnects,0> { IfcRelConnects() : Object("IfcRelConnects") {} + + }; + + // C++ wrapper for IfcRelContainedInSpatialStructure + struct IfcRelContainedInSpatialStructure : IfcRelConnects, ObjectHelper<IfcRelContainedInSpatialStructure,2> { IfcRelContainedInSpatialStructure() : Object("IfcRelContainedInSpatialStructure") {} + ListOf< Lazy< IfcProduct >, 1, 0 > RelatedElements; + Lazy< IfcSpatialStructureElement > RelatingStructure; + }; + + // C++ wrapper for IfcRelDefines + struct IfcRelDefines : IfcRelationship, ObjectHelper<IfcRelDefines,1> { IfcRelDefines() : Object("IfcRelDefines") {} + ListOf< Lazy< IfcObject >, 1, 0 > RelatedObjects; + }; + + // C++ wrapper for IfcRelDefinesByProperties + struct IfcRelDefinesByProperties : IfcRelDefines, ObjectHelper<IfcRelDefinesByProperties,1> { IfcRelDefinesByProperties() : Object("IfcRelDefinesByProperties") {} + Lazy< IfcPropertySetDefinition > RelatingPropertyDefinition; + }; + + // C++ wrapper for IfcRelFillsElement + struct IfcRelFillsElement : IfcRelConnects, ObjectHelper<IfcRelFillsElement,2> { IfcRelFillsElement() : Object("IfcRelFillsElement") {} + Lazy< IfcOpeningElement > RelatingOpeningElement; + Lazy< IfcElement > RelatedBuildingElement; + }; + + // C++ wrapper for IfcRelOverridesProperties + struct IfcRelOverridesProperties : IfcRelDefinesByProperties, ObjectHelper<IfcRelOverridesProperties,1> { IfcRelOverridesProperties() : Object("IfcRelOverridesProperties") {} + ListOf< Lazy< IfcProperty >, 1, 0 > OverridingProperties; + }; + + // C++ wrapper for IfcRelVoidsElement + struct IfcRelVoidsElement : IfcRelConnects, ObjectHelper<IfcRelVoidsElement,2> { IfcRelVoidsElement() : Object("IfcRelVoidsElement") {} + Lazy< IfcElement > RelatingBuildingElement; + Lazy< IfcFeatureElementSubtraction > RelatedOpeningElement; + }; + + // C++ wrapper for IfcRepresentation + struct IfcRepresentation : ObjectHelper<IfcRepresentation,4> { IfcRepresentation() : Object("IfcRepresentation") {} + Lazy< IfcRepresentationContext > ContextOfItems; + Maybe< IfcLabel::Out > RepresentationIdentifier; + Maybe< IfcLabel::Out > RepresentationType; + ListOf< Lazy< IfcRepresentationItem >, 1, 0 > Items; + }; + + // C++ wrapper for IfcRepresentationMap + struct IfcRepresentationMap : ObjectHelper<IfcRepresentationMap,2> { IfcRepresentationMap() : Object("IfcRepresentationMap") {} + IfcAxis2Placement::Out MappingOrigin; + Lazy< IfcRepresentation > MappedRepresentation; + }; + + // C++ wrapper for IfcRevolvedAreaSolid + struct IfcRevolvedAreaSolid : IfcSweptAreaSolid, ObjectHelper<IfcRevolvedAreaSolid,2> { IfcRevolvedAreaSolid() : Object("IfcRevolvedAreaSolid") {} + Lazy< IfcAxis1Placement > Axis; + IfcPlaneAngleMeasure::Out Angle; + }; + + // C++ wrapper for IfcRightCircularCone + struct IfcRightCircularCone : IfcCsgPrimitive3D, ObjectHelper<IfcRightCircularCone,2> { IfcRightCircularCone() : Object("IfcRightCircularCone") {} + IfcPositiveLengthMeasure::Out Height; + IfcPositiveLengthMeasure::Out BottomRadius; + }; + + // C++ wrapper for IfcRightCircularCylinder + struct IfcRightCircularCylinder : IfcCsgPrimitive3D, ObjectHelper<IfcRightCircularCylinder,2> { IfcRightCircularCylinder() : Object("IfcRightCircularCylinder") {} + IfcPositiveLengthMeasure::Out Height; + IfcPositiveLengthMeasure::Out Radius; + }; + + // C++ wrapper for IfcRoof + struct IfcRoof : IfcBuildingElement, ObjectHelper<IfcRoof,1> { IfcRoof() : Object("IfcRoof") {} + IfcRoofTypeEnum::Out ShapeType; + }; + + // C++ wrapper for IfcRoundedEdgeFeature + struct IfcRoundedEdgeFeature : IfcEdgeFeature, ObjectHelper<IfcRoundedEdgeFeature,1> { IfcRoundedEdgeFeature() : Object("IfcRoundedEdgeFeature") {} + Maybe< IfcPositiveLengthMeasure::Out > Radius; + }; + + // C++ wrapper for IfcRoundedRectangleProfileDef + struct IfcRoundedRectangleProfileDef : IfcRectangleProfileDef, ObjectHelper<IfcRoundedRectangleProfileDef,1> { IfcRoundedRectangleProfileDef() : Object("IfcRoundedRectangleProfileDef") {} + IfcPositiveLengthMeasure::Out RoundingRadius; + }; + + // C++ wrapper for IfcSIUnit + struct IfcSIUnit : IfcNamedUnit, ObjectHelper<IfcSIUnit,2> { IfcSIUnit() : Object("IfcSIUnit") {} + Maybe< IfcSIPrefix::Out > Prefix; + IfcSIUnitName::Out Name; + }; + + // C++ wrapper for IfcSanitaryTerminalType + struct IfcSanitaryTerminalType : IfcFlowTerminalType, ObjectHelper<IfcSanitaryTerminalType,1> { IfcSanitaryTerminalType() : Object("IfcSanitaryTerminalType") {} + IfcSanitaryTerminalTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcScheduleTimeControl + struct IfcScheduleTimeControl : IfcControl, ObjectHelper<IfcScheduleTimeControl,18> { IfcScheduleTimeControl() : Object("IfcScheduleTimeControl") {} + Maybe< IfcDateTimeSelect::Out > ActualStart; + Maybe< IfcDateTimeSelect::Out > EarlyStart; + Maybe< IfcDateTimeSelect::Out > LateStart; + Maybe< IfcDateTimeSelect::Out > ScheduleStart; + Maybe< IfcDateTimeSelect::Out > ActualFinish; + Maybe< IfcDateTimeSelect::Out > EarlyFinish; + Maybe< IfcDateTimeSelect::Out > LateFinish; + Maybe< IfcDateTimeSelect::Out > ScheduleFinish; + Maybe< IfcTimeMeasure::Out > ScheduleDuration; + Maybe< IfcTimeMeasure::Out > ActualDuration; + Maybe< IfcTimeMeasure::Out > RemainingTime; + Maybe< IfcTimeMeasure::Out > FreeFloat; + Maybe< IfcTimeMeasure::Out > TotalFloat; + Maybe< BOOLEAN::Out > IsCritical; + Maybe< IfcDateTimeSelect::Out > StatusTime; + Maybe< IfcTimeMeasure::Out > StartFloat; + Maybe< IfcTimeMeasure::Out > FinishFloat; + Maybe< IfcPositiveRatioMeasure::Out > Completion; + }; + + // C++ wrapper for IfcSectionedSpine + struct IfcSectionedSpine : IfcGeometricRepresentationItem, ObjectHelper<IfcSectionedSpine,3> { IfcSectionedSpine() : Object("IfcSectionedSpine") {} + Lazy< IfcCompositeCurve > SpineCurve; + ListOf< Lazy< IfcProfileDef >, 2, 0 > CrossSections; + ListOf< Lazy< IfcAxis2Placement3D >, 2, 0 > CrossSectionPositions; + }; + + // C++ wrapper for IfcSensorType + struct IfcSensorType : IfcDistributionControlElementType, ObjectHelper<IfcSensorType,1> { IfcSensorType() : Object("IfcSensorType") {} + IfcSensorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcServiceLife + struct IfcServiceLife : IfcControl, ObjectHelper<IfcServiceLife,2> { IfcServiceLife() : Object("IfcServiceLife") {} + IfcServiceLifeTypeEnum::Out ServiceLifeType; + IfcTimeMeasure::Out ServiceLifeDuration; + }; + + // C++ wrapper for IfcShapeModel + struct IfcShapeModel : IfcRepresentation, ObjectHelper<IfcShapeModel,0> { IfcShapeModel() : Object("IfcShapeModel") {} + + }; + + // C++ wrapper for IfcShapeRepresentation + struct IfcShapeRepresentation : IfcShapeModel, ObjectHelper<IfcShapeRepresentation,0> { IfcShapeRepresentation() : Object("IfcShapeRepresentation") {} + + }; + + // C++ wrapper for IfcShellBasedSurfaceModel + struct IfcShellBasedSurfaceModel : IfcGeometricRepresentationItem, ObjectHelper<IfcShellBasedSurfaceModel,1> { IfcShellBasedSurfaceModel() : Object("IfcShellBasedSurfaceModel") {} + ListOf< IfcShell, 1, 0 >::Out SbsmBoundary; + }; + + // C++ wrapper for IfcSite + struct IfcSite : IfcSpatialStructureElement, ObjectHelper<IfcSite,5> { IfcSite() : Object("IfcSite") {} + Maybe< IfcCompoundPlaneAngleMeasure::Out > RefLatitude; + Maybe< IfcCompoundPlaneAngleMeasure::Out > RefLongitude; + Maybe< IfcLengthMeasure::Out > RefElevation; + Maybe< IfcLabel::Out > LandTitleNumber; + Maybe< Lazy< NotImplemented > > SiteAddress; + }; + + // C++ wrapper for IfcSlab + struct IfcSlab : IfcBuildingElement, ObjectHelper<IfcSlab,1> { IfcSlab() : Object("IfcSlab") {} + Maybe< IfcSlabTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcSlabType + struct IfcSlabType : IfcBuildingElementType, ObjectHelper<IfcSlabType,1> { IfcSlabType() : Object("IfcSlabType") {} + IfcSlabTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcSpace + struct IfcSpace : IfcSpatialStructureElement, ObjectHelper<IfcSpace,2> { IfcSpace() : Object("IfcSpace") {} + IfcInternalOrExternalEnum::Out InteriorOrExteriorSpace; + Maybe< IfcLengthMeasure::Out > ElevationWithFlooring; + }; + + // C++ wrapper for IfcSpaceHeaterType + struct IfcSpaceHeaterType : IfcEnergyConversionDeviceType, ObjectHelper<IfcSpaceHeaterType,1> { IfcSpaceHeaterType() : Object("IfcSpaceHeaterType") {} + IfcSpaceHeaterTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcSpaceProgram + struct IfcSpaceProgram : IfcControl, ObjectHelper<IfcSpaceProgram,5> { IfcSpaceProgram() : Object("IfcSpaceProgram") {} + IfcIdentifier::Out SpaceProgramIdentifier; + Maybe< IfcAreaMeasure::Out > MaxRequiredArea; + Maybe< IfcAreaMeasure::Out > MinRequiredArea; + Maybe< Lazy< IfcSpatialStructureElement > > RequestedLocation; + IfcAreaMeasure::Out StandardRequiredArea; + }; + + // C++ wrapper for IfcSpatialStructureElementType + struct IfcSpatialStructureElementType : IfcElementType, ObjectHelper<IfcSpatialStructureElementType,0> { IfcSpatialStructureElementType() : Object("IfcSpatialStructureElementType") {} + + }; + + // C++ wrapper for IfcSpaceType + struct IfcSpaceType : IfcSpatialStructureElementType, ObjectHelper<IfcSpaceType,1> { IfcSpaceType() : Object("IfcSpaceType") {} + IfcSpaceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcSphere + struct IfcSphere : IfcCsgPrimitive3D, ObjectHelper<IfcSphere,1> { IfcSphere() : Object("IfcSphere") {} + IfcPositiveLengthMeasure::Out Radius; + }; + + // C++ wrapper for IfcStackTerminalType + struct IfcStackTerminalType : IfcFlowTerminalType, ObjectHelper<IfcStackTerminalType,1> { IfcStackTerminalType() : Object("IfcStackTerminalType") {} + IfcStackTerminalTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcStair + struct IfcStair : IfcBuildingElement, ObjectHelper<IfcStair,1> { IfcStair() : Object("IfcStair") {} + IfcStairTypeEnum::Out ShapeType; + }; + + // C++ wrapper for IfcStairFlight + struct IfcStairFlight : IfcBuildingElement, ObjectHelper<IfcStairFlight,4> { IfcStairFlight() : Object("IfcStairFlight") {} + Maybe< INTEGER::Out > NumberOfRiser; + Maybe< INTEGER::Out > NumberOfTreads; + Maybe< IfcPositiveLengthMeasure::Out > RiserHeight; + Maybe< IfcPositiveLengthMeasure::Out > TreadLength; + }; + + // C++ wrapper for IfcStairFlightType + struct IfcStairFlightType : IfcBuildingElementType, ObjectHelper<IfcStairFlightType,1> { IfcStairFlightType() : Object("IfcStairFlightType") {} + IfcStairFlightTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcStructuralActivity + struct IfcStructuralActivity : IfcProduct, ObjectHelper<IfcStructuralActivity,2> { IfcStructuralActivity() : Object("IfcStructuralActivity") {} + Lazy< NotImplemented > AppliedLoad; + IfcGlobalOrLocalEnum::Out GlobalOrLocal; + }; + + // C++ wrapper for IfcStructuralAction + struct IfcStructuralAction : IfcStructuralActivity, ObjectHelper<IfcStructuralAction,2> { IfcStructuralAction() : Object("IfcStructuralAction") {} + BOOLEAN::Out DestabilizingLoad; + Maybe< Lazy< IfcStructuralReaction > > CausedBy; + }; + + // C++ wrapper for IfcStructuralAnalysisModel + struct IfcStructuralAnalysisModel : IfcSystem, ObjectHelper<IfcStructuralAnalysisModel,4> { IfcStructuralAnalysisModel() : Object("IfcStructuralAnalysisModel") {} + IfcAnalysisModelTypeEnum::Out PredefinedType; + Maybe< Lazy< IfcAxis2Placement3D > > OrientationOf2DPlane; + Maybe< ListOf< Lazy< IfcStructuralLoadGroup >, 1, 0 > > LoadedBy; + Maybe< ListOf< Lazy< IfcStructuralResultGroup >, 1, 0 > > HasResults; + }; + + // C++ wrapper for IfcStructuralItem + struct IfcStructuralItem : IfcProduct, ObjectHelper<IfcStructuralItem,0> { IfcStructuralItem() : Object("IfcStructuralItem") {} + + }; + + // C++ wrapper for IfcStructuralConnection + struct IfcStructuralConnection : IfcStructuralItem, ObjectHelper<IfcStructuralConnection,1> { IfcStructuralConnection() : Object("IfcStructuralConnection") {} + Maybe< Lazy< NotImplemented > > AppliedCondition; + }; + + // C++ wrapper for IfcStructuralCurveConnection + struct IfcStructuralCurveConnection : IfcStructuralConnection, ObjectHelper<IfcStructuralCurveConnection,0> { IfcStructuralCurveConnection() : Object("IfcStructuralCurveConnection") {} + + }; + + // C++ wrapper for IfcStructuralMember + struct IfcStructuralMember : IfcStructuralItem, ObjectHelper<IfcStructuralMember,0> { IfcStructuralMember() : Object("IfcStructuralMember") {} + + }; + + // C++ wrapper for IfcStructuralCurveMember + struct IfcStructuralCurveMember : IfcStructuralMember, ObjectHelper<IfcStructuralCurveMember,1> { IfcStructuralCurveMember() : Object("IfcStructuralCurveMember") {} + IfcStructuralCurveTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcStructuralCurveMemberVarying + struct IfcStructuralCurveMemberVarying : IfcStructuralCurveMember, ObjectHelper<IfcStructuralCurveMemberVarying,0> { IfcStructuralCurveMemberVarying() : Object("IfcStructuralCurveMemberVarying") {} + + }; + + // C++ wrapper for IfcStructuralLinearAction + struct IfcStructuralLinearAction : IfcStructuralAction, ObjectHelper<IfcStructuralLinearAction,1> { IfcStructuralLinearAction() : Object("IfcStructuralLinearAction") {} + IfcProjectedOrTrueLengthEnum::Out ProjectedOrTrue; + }; + + // C++ wrapper for IfcStructuralLinearActionVarying + struct IfcStructuralLinearActionVarying : IfcStructuralLinearAction, ObjectHelper<IfcStructuralLinearActionVarying,2> { IfcStructuralLinearActionVarying() : Object("IfcStructuralLinearActionVarying") {} + Lazy< NotImplemented > VaryingAppliedLoadLocation; + ListOf< Lazy< NotImplemented >, 1, 0 > SubsequentAppliedLoads; + }; + + // C++ wrapper for IfcStructuralLoadGroup + struct IfcStructuralLoadGroup : IfcGroup, ObjectHelper<IfcStructuralLoadGroup,5> { IfcStructuralLoadGroup() : Object("IfcStructuralLoadGroup") {} + IfcLoadGroupTypeEnum::Out PredefinedType; + IfcActionTypeEnum::Out ActionType; + IfcActionSourceTypeEnum::Out ActionSource; + Maybe< IfcPositiveRatioMeasure::Out > Coefficient; + Maybe< IfcLabel::Out > Purpose; + }; + + // C++ wrapper for IfcStructuralPlanarAction + struct IfcStructuralPlanarAction : IfcStructuralAction, ObjectHelper<IfcStructuralPlanarAction,1> { IfcStructuralPlanarAction() : Object("IfcStructuralPlanarAction") {} + IfcProjectedOrTrueLengthEnum::Out ProjectedOrTrue; + }; + + // C++ wrapper for IfcStructuralPlanarActionVarying + struct IfcStructuralPlanarActionVarying : IfcStructuralPlanarAction, ObjectHelper<IfcStructuralPlanarActionVarying,2> { IfcStructuralPlanarActionVarying() : Object("IfcStructuralPlanarActionVarying") {} + Lazy< NotImplemented > VaryingAppliedLoadLocation; + ListOf< Lazy< NotImplemented >, 2, 0 > SubsequentAppliedLoads; + }; + + // C++ wrapper for IfcStructuralPointAction + struct IfcStructuralPointAction : IfcStructuralAction, ObjectHelper<IfcStructuralPointAction,0> { IfcStructuralPointAction() : Object("IfcStructuralPointAction") {} + + }; + + // C++ wrapper for IfcStructuralPointConnection + struct IfcStructuralPointConnection : IfcStructuralConnection, ObjectHelper<IfcStructuralPointConnection,0> { IfcStructuralPointConnection() : Object("IfcStructuralPointConnection") {} + + }; + + // C++ wrapper for IfcStructuralReaction + struct IfcStructuralReaction : IfcStructuralActivity, ObjectHelper<IfcStructuralReaction,0> { IfcStructuralReaction() : Object("IfcStructuralReaction") {} + + }; + + // C++ wrapper for IfcStructuralPointReaction + struct IfcStructuralPointReaction : IfcStructuralReaction, ObjectHelper<IfcStructuralPointReaction,0> { IfcStructuralPointReaction() : Object("IfcStructuralPointReaction") {} + + }; + + // C++ wrapper for IfcStructuralResultGroup + struct IfcStructuralResultGroup : IfcGroup, ObjectHelper<IfcStructuralResultGroup,3> { IfcStructuralResultGroup() : Object("IfcStructuralResultGroup") {} + IfcAnalysisTheoryTypeEnum::Out TheoryType; + Maybe< Lazy< IfcStructuralLoadGroup > > ResultForLoadGroup; + BOOLEAN::Out IsLinear; + }; + + // C++ wrapper for IfcStructuralSurfaceConnection + struct IfcStructuralSurfaceConnection : IfcStructuralConnection, ObjectHelper<IfcStructuralSurfaceConnection,0> { IfcStructuralSurfaceConnection() : Object("IfcStructuralSurfaceConnection") {} + + }; + + // C++ wrapper for IfcStructuralSurfaceMember + struct IfcStructuralSurfaceMember : IfcStructuralMember, ObjectHelper<IfcStructuralSurfaceMember,2> { IfcStructuralSurfaceMember() : Object("IfcStructuralSurfaceMember") {} + IfcStructuralSurfaceTypeEnum::Out PredefinedType; + Maybe< IfcPositiveLengthMeasure::Out > Thickness; + }; + + // C++ wrapper for IfcStructuralSurfaceMemberVarying + struct IfcStructuralSurfaceMemberVarying : IfcStructuralSurfaceMember, ObjectHelper<IfcStructuralSurfaceMemberVarying,2> { IfcStructuralSurfaceMemberVarying() : Object("IfcStructuralSurfaceMemberVarying") {} + ListOf< IfcPositiveLengthMeasure, 2, 0 >::Out SubsequentThickness; + Lazy< NotImplemented > VaryingThicknessLocation; + }; + + // C++ wrapper for IfcStructuredDimensionCallout + struct IfcStructuredDimensionCallout : IfcDraughtingCallout, ObjectHelper<IfcStructuredDimensionCallout,0> { IfcStructuredDimensionCallout() : Object("IfcStructuredDimensionCallout") {} + + }; + + // C++ wrapper for IfcStyleModel + struct IfcStyleModel : IfcRepresentation, ObjectHelper<IfcStyleModel,0> { IfcStyleModel() : Object("IfcStyleModel") {} + + }; + + // C++ wrapper for IfcStyledRepresentation + struct IfcStyledRepresentation : IfcStyleModel, ObjectHelper<IfcStyledRepresentation,0> { IfcStyledRepresentation() : Object("IfcStyledRepresentation") {} + + }; + + // C++ wrapper for IfcSubContractResource + struct IfcSubContractResource : IfcConstructionResource, ObjectHelper<IfcSubContractResource,2> { IfcSubContractResource() : Object("IfcSubContractResource") {} + Maybe< IfcActorSelect::Out > SubContractor; + Maybe< IfcText::Out > JobDescription; + }; + + // C++ wrapper for IfcSubedge + struct IfcSubedge : IfcEdge, ObjectHelper<IfcSubedge,1> { IfcSubedge() : Object("IfcSubedge") {} + Lazy< IfcEdge > ParentEdge; + }; + + // C++ wrapper for IfcSurfaceCurveSweptAreaSolid + struct IfcSurfaceCurveSweptAreaSolid : IfcSweptAreaSolid, ObjectHelper<IfcSurfaceCurveSweptAreaSolid,4> { IfcSurfaceCurveSweptAreaSolid() : Object("IfcSurfaceCurveSweptAreaSolid") {} + Lazy< IfcCurve > Directrix; + IfcParameterValue::Out StartParam; + IfcParameterValue::Out EndParam; + Lazy< IfcSurface > ReferenceSurface; + }; + + // C++ wrapper for IfcSweptSurface + struct IfcSweptSurface : IfcSurface, ObjectHelper<IfcSweptSurface,2> { IfcSweptSurface() : Object("IfcSweptSurface") {} + Lazy< IfcProfileDef > SweptCurve; + Lazy< IfcAxis2Placement3D > Position; + }; + + // C++ wrapper for IfcSurfaceOfLinearExtrusion + struct IfcSurfaceOfLinearExtrusion : IfcSweptSurface, ObjectHelper<IfcSurfaceOfLinearExtrusion,2> { IfcSurfaceOfLinearExtrusion() : Object("IfcSurfaceOfLinearExtrusion") {} + Lazy< IfcDirection > ExtrudedDirection; + IfcLengthMeasure::Out Depth; + }; + + // C++ wrapper for IfcSurfaceOfRevolution + struct IfcSurfaceOfRevolution : IfcSweptSurface, ObjectHelper<IfcSurfaceOfRevolution,1> { IfcSurfaceOfRevolution() : Object("IfcSurfaceOfRevolution") {} + Lazy< IfcAxis1Placement > AxisPosition; + }; + + // C++ wrapper for IfcSurfaceStyle + struct IfcSurfaceStyle : IfcPresentationStyle, ObjectHelper<IfcSurfaceStyle,2> { IfcSurfaceStyle() : Object("IfcSurfaceStyle") {} + IfcSurfaceSide::Out Side; + ListOf< IfcSurfaceStyleElementSelect, 1, 5 >::Out Styles; + }; + + // C++ wrapper for IfcSurfaceStyleShading + struct IfcSurfaceStyleShading : ObjectHelper<IfcSurfaceStyleShading,1> { IfcSurfaceStyleShading() : Object("IfcSurfaceStyleShading") {} + Lazy< IfcColourRgb > SurfaceColour; + }; + + // C++ wrapper for IfcSurfaceStyleRendering + struct IfcSurfaceStyleRendering : IfcSurfaceStyleShading, ObjectHelper<IfcSurfaceStyleRendering,8> { IfcSurfaceStyleRendering() : Object("IfcSurfaceStyleRendering") {} + Maybe< IfcNormalisedRatioMeasure::Out > Transparency; + Maybe< IfcColourOrFactor::Out > DiffuseColour; + Maybe< IfcColourOrFactor::Out > TransmissionColour; + Maybe< IfcColourOrFactor::Out > DiffuseTransmissionColour; + Maybe< IfcColourOrFactor::Out > ReflectionColour; + Maybe< IfcColourOrFactor::Out > SpecularColour; + Maybe< IfcSpecularHighlightSelect::Out > SpecularHighlight; + IfcReflectanceMethodEnum::Out ReflectanceMethod; + }; + + // C++ wrapper for IfcSurfaceStyleWithTextures + struct IfcSurfaceStyleWithTextures : ObjectHelper<IfcSurfaceStyleWithTextures,1> { IfcSurfaceStyleWithTextures() : Object("IfcSurfaceStyleWithTextures") {} + ListOf< Lazy< NotImplemented >, 1, 0 > Textures; + }; + + // C++ wrapper for IfcSweptDiskSolid + struct IfcSweptDiskSolid : IfcSolidModel, ObjectHelper<IfcSweptDiskSolid,5> { IfcSweptDiskSolid() : Object("IfcSweptDiskSolid") {} + Lazy< IfcCurve > Directrix; + IfcPositiveLengthMeasure::Out Radius; + Maybe< IfcPositiveLengthMeasure::Out > InnerRadius; + IfcParameterValue::Out StartParam; + IfcParameterValue::Out EndParam; + }; + + // C++ wrapper for IfcSwitchingDeviceType + struct IfcSwitchingDeviceType : IfcFlowControllerType, ObjectHelper<IfcSwitchingDeviceType,1> { IfcSwitchingDeviceType() : Object("IfcSwitchingDeviceType") {} + IfcSwitchingDeviceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcSystemFurnitureElementType + struct IfcSystemFurnitureElementType : IfcFurnishingElementType, ObjectHelper<IfcSystemFurnitureElementType,0> { IfcSystemFurnitureElementType() : Object("IfcSystemFurnitureElementType") {} + + }; + + // C++ wrapper for IfcTShapeProfileDef + struct IfcTShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcTShapeProfileDef,10> { IfcTShapeProfileDef() : Object("IfcTShapeProfileDef") {} + IfcPositiveLengthMeasure::Out Depth; + IfcPositiveLengthMeasure::Out FlangeWidth; + IfcPositiveLengthMeasure::Out WebThickness; + IfcPositiveLengthMeasure::Out FlangeThickness; + Maybe< IfcPositiveLengthMeasure::Out > FilletRadius; + Maybe< IfcPositiveLengthMeasure::Out > FlangeEdgeRadius; + Maybe< IfcPositiveLengthMeasure::Out > WebEdgeRadius; + Maybe< IfcPlaneAngleMeasure::Out > WebSlope; + Maybe< IfcPlaneAngleMeasure::Out > FlangeSlope; + Maybe< IfcPositiveLengthMeasure::Out > CentreOfGravityInY; + }; + + // C++ wrapper for IfcTankType + struct IfcTankType : IfcFlowStorageDeviceType, ObjectHelper<IfcTankType,1> { IfcTankType() : Object("IfcTankType") {} + IfcTankTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcTendon + struct IfcTendon : IfcReinforcingElement, ObjectHelper<IfcTendon,8> { IfcTendon() : Object("IfcTendon") {} + IfcTendonTypeEnum::Out PredefinedType; + IfcPositiveLengthMeasure::Out NominalDiameter; + IfcAreaMeasure::Out CrossSectionArea; + Maybe< IfcForceMeasure::Out > TensionForce; + Maybe< IfcPressureMeasure::Out > PreStress; + Maybe< IfcNormalisedRatioMeasure::Out > FrictionCoefficient; + Maybe< IfcPositiveLengthMeasure::Out > AnchorageSlip; + Maybe< IfcPositiveLengthMeasure::Out > MinCurvatureRadius; + }; + + // C++ wrapper for IfcTendonAnchor + struct IfcTendonAnchor : IfcReinforcingElement, ObjectHelper<IfcTendonAnchor,0> { IfcTendonAnchor() : Object("IfcTendonAnchor") {} + + }; + + // C++ wrapper for IfcTextLiteral + struct IfcTextLiteral : IfcGeometricRepresentationItem, ObjectHelper<IfcTextLiteral,3> { IfcTextLiteral() : Object("IfcTextLiteral") {} + IfcPresentableText::Out Literal; + IfcAxis2Placement::Out Placement; + IfcTextPath::Out Path; + }; + + // C++ wrapper for IfcTextLiteralWithExtent + struct IfcTextLiteralWithExtent : IfcTextLiteral, ObjectHelper<IfcTextLiteralWithExtent,2> { IfcTextLiteralWithExtent() : Object("IfcTextLiteralWithExtent") {} + Lazy< IfcPlanarExtent > Extent; + IfcBoxAlignment::Out BoxAlignment; + }; + + // C++ wrapper for IfcTimeSeriesSchedule + struct IfcTimeSeriesSchedule : IfcControl, ObjectHelper<IfcTimeSeriesSchedule,3> { IfcTimeSeriesSchedule() : Object("IfcTimeSeriesSchedule") {} + Maybe< ListOf< IfcDateTimeSelect, 1, 0 >::Out > ApplicableDates; + IfcTimeSeriesScheduleTypeEnum::Out TimeSeriesScheduleType; + Lazy< NotImplemented > TimeSeries; + }; + + // C++ wrapper for IfcTopologyRepresentation + struct IfcTopologyRepresentation : IfcShapeModel, ObjectHelper<IfcTopologyRepresentation,0> { IfcTopologyRepresentation() : Object("IfcTopologyRepresentation") {} + + }; + + // C++ wrapper for IfcTransformerType + struct IfcTransformerType : IfcEnergyConversionDeviceType, ObjectHelper<IfcTransformerType,1> { IfcTransformerType() : Object("IfcTransformerType") {} + IfcTransformerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcTransportElement + struct IfcTransportElement : IfcElement, ObjectHelper<IfcTransportElement,3> { IfcTransportElement() : Object("IfcTransportElement") {} + Maybe< IfcTransportElementTypeEnum::Out > OperationType; + Maybe< IfcMassMeasure::Out > CapacityByWeight; + Maybe< IfcCountMeasure::Out > CapacityByNumber; + }; + + // C++ wrapper for IfcTransportElementType + struct IfcTransportElementType : IfcElementType, ObjectHelper<IfcTransportElementType,1> { IfcTransportElementType() : Object("IfcTransportElementType") {} + IfcTransportElementTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcTrapeziumProfileDef + struct IfcTrapeziumProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcTrapeziumProfileDef,4> { IfcTrapeziumProfileDef() : Object("IfcTrapeziumProfileDef") {} + IfcPositiveLengthMeasure::Out BottomXDim; + IfcPositiveLengthMeasure::Out TopXDim; + IfcPositiveLengthMeasure::Out YDim; + IfcLengthMeasure::Out TopXOffset; + }; + + // C++ wrapper for IfcTrimmedCurve + struct IfcTrimmedCurve : IfcBoundedCurve, ObjectHelper<IfcTrimmedCurve,5> { IfcTrimmedCurve() : Object("IfcTrimmedCurve") {} + Lazy< IfcCurve > BasisCurve; + ListOf< IfcTrimmingSelect, 1, 2 >::Out Trim1; + ListOf< IfcTrimmingSelect, 1, 2 >::Out Trim2; + BOOLEAN::Out SenseAgreement; + IfcTrimmingPreference::Out MasterRepresentation; + }; + + // C++ wrapper for IfcTubeBundleType + struct IfcTubeBundleType : IfcEnergyConversionDeviceType, ObjectHelper<IfcTubeBundleType,1> { IfcTubeBundleType() : Object("IfcTubeBundleType") {} + IfcTubeBundleTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcTwoDirectionRepeatFactor + struct IfcTwoDirectionRepeatFactor : IfcOneDirectionRepeatFactor, ObjectHelper<IfcTwoDirectionRepeatFactor,1> { IfcTwoDirectionRepeatFactor() : Object("IfcTwoDirectionRepeatFactor") {} + Lazy< IfcVector > SecondRepeatFactor; + }; + + // C++ wrapper for IfcUShapeProfileDef + struct IfcUShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcUShapeProfileDef,8> { IfcUShapeProfileDef() : Object("IfcUShapeProfileDef") {} + IfcPositiveLengthMeasure::Out Depth; + IfcPositiveLengthMeasure::Out FlangeWidth; + IfcPositiveLengthMeasure::Out WebThickness; + IfcPositiveLengthMeasure::Out FlangeThickness; + Maybe< IfcPositiveLengthMeasure::Out > FilletRadius; + Maybe< IfcPositiveLengthMeasure::Out > EdgeRadius; + Maybe< IfcPlaneAngleMeasure::Out > FlangeSlope; + Maybe< IfcPositiveLengthMeasure::Out > CentreOfGravityInX; + }; + + // C++ wrapper for IfcUnitAssignment + struct IfcUnitAssignment : ObjectHelper<IfcUnitAssignment,1> { IfcUnitAssignment() : Object("IfcUnitAssignment") {} + ListOf< IfcUnit, 1, 0 >::Out Units; + }; + + // C++ wrapper for IfcUnitaryEquipmentType + struct IfcUnitaryEquipmentType : IfcEnergyConversionDeviceType, ObjectHelper<IfcUnitaryEquipmentType,1> { IfcUnitaryEquipmentType() : Object("IfcUnitaryEquipmentType") {} + IfcUnitaryEquipmentTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcValveType + struct IfcValveType : IfcFlowControllerType, ObjectHelper<IfcValveType,1> { IfcValveType() : Object("IfcValveType") {} + IfcValveTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcVector + struct IfcVector : IfcGeometricRepresentationItem, ObjectHelper<IfcVector,2> { IfcVector() : Object("IfcVector") {} + Lazy< IfcDirection > Orientation; + IfcLengthMeasure::Out Magnitude; + }; + + // C++ wrapper for IfcVertex + struct IfcVertex : IfcTopologicalRepresentationItem, ObjectHelper<IfcVertex,0> { IfcVertex() : Object("IfcVertex") {} + + }; + + // C++ wrapper for IfcVertexLoop + struct IfcVertexLoop : IfcLoop, ObjectHelper<IfcVertexLoop,1> { IfcVertexLoop() : Object("IfcVertexLoop") {} + Lazy< IfcVertex > LoopVertex; + }; + + // C++ wrapper for IfcVertexPoint + struct IfcVertexPoint : IfcVertex, ObjectHelper<IfcVertexPoint,1> { IfcVertexPoint() : Object("IfcVertexPoint") {} + Lazy< IfcPoint > VertexGeometry; + }; + + // C++ wrapper for IfcVibrationIsolatorType + struct IfcVibrationIsolatorType : IfcDiscreteAccessoryType, ObjectHelper<IfcVibrationIsolatorType,1> { IfcVibrationIsolatorType() : Object("IfcVibrationIsolatorType") {} + IfcVibrationIsolatorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcVirtualElement + struct IfcVirtualElement : IfcElement, ObjectHelper<IfcVirtualElement,0> { IfcVirtualElement() : Object("IfcVirtualElement") {} + + }; + + // C++ wrapper for IfcWall + struct IfcWall : IfcBuildingElement, ObjectHelper<IfcWall,0> { IfcWall() : Object("IfcWall") {} + + }; + + // C++ wrapper for IfcWallStandardCase + struct IfcWallStandardCase : IfcWall, ObjectHelper<IfcWallStandardCase,0> { IfcWallStandardCase() : Object("IfcWallStandardCase") {} + + }; + + // C++ wrapper for IfcWallType + struct IfcWallType : IfcBuildingElementType, ObjectHelper<IfcWallType,1> { IfcWallType() : Object("IfcWallType") {} + IfcWallTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcWasteTerminalType + struct IfcWasteTerminalType : IfcFlowTerminalType, ObjectHelper<IfcWasteTerminalType,1> { IfcWasteTerminalType() : Object("IfcWasteTerminalType") {} + IfcWasteTerminalTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcWindow + struct IfcWindow : IfcBuildingElement, ObjectHelper<IfcWindow,2> { IfcWindow() : Object("IfcWindow") {} + Maybe< IfcPositiveLengthMeasure::Out > OverallHeight; + Maybe< IfcPositiveLengthMeasure::Out > OverallWidth; + }; + + // C++ wrapper for IfcWindowStyle + struct IfcWindowStyle : IfcTypeProduct, ObjectHelper<IfcWindowStyle,4> { IfcWindowStyle() : Object("IfcWindowStyle") {} + IfcWindowStyleConstructionEnum::Out ConstructionType; + IfcWindowStyleOperationEnum::Out OperationType; + BOOLEAN::Out ParameterTakesPrecedence; + BOOLEAN::Out Sizeable; + }; + + // C++ wrapper for IfcWorkControl + struct IfcWorkControl : IfcControl, ObjectHelper<IfcWorkControl,10> { IfcWorkControl() : Object("IfcWorkControl") {} + IfcIdentifier::Out Identifier; + IfcDateTimeSelect::Out CreationDate; + Maybe< ListOf< Lazy< NotImplemented >, 1, 0 > > Creators; + Maybe< IfcLabel::Out > Purpose; + Maybe< IfcTimeMeasure::Out > Duration; + Maybe< IfcTimeMeasure::Out > TotalFloat; + IfcDateTimeSelect::Out StartTime; + Maybe< IfcDateTimeSelect::Out > FinishTime; + Maybe< IfcWorkControlTypeEnum::Out > WorkControlType; + Maybe< IfcLabel::Out > UserDefinedControlType; + }; + + // C++ wrapper for IfcWorkPlan + struct IfcWorkPlan : IfcWorkControl, ObjectHelper<IfcWorkPlan,0> { IfcWorkPlan() : Object("IfcWorkPlan") {} + + }; + + // C++ wrapper for IfcWorkSchedule + struct IfcWorkSchedule : IfcWorkControl, ObjectHelper<IfcWorkSchedule,0> { IfcWorkSchedule() : Object("IfcWorkSchedule") {} + + }; + + // C++ wrapper for IfcZShapeProfileDef + struct IfcZShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcZShapeProfileDef,6> { IfcZShapeProfileDef() : Object("IfcZShapeProfileDef") {} + IfcPositiveLengthMeasure::Out Depth; + IfcPositiveLengthMeasure::Out FlangeWidth; + IfcPositiveLengthMeasure::Out WebThickness; + IfcPositiveLengthMeasure::Out FlangeThickness; + Maybe< IfcPositiveLengthMeasure::Out > FilletRadius; + Maybe< IfcPositiveLengthMeasure::Out > EdgeRadius; + }; + + // C++ wrapper for IfcZone + struct IfcZone : IfcGroup, ObjectHelper<IfcZone,0> { IfcZone() : Object("IfcZone") {} + + }; + + void GetSchema(EXPRESS::ConversionSchema& out); + } //! Schema_2x3 +} //! IFC + +namespace STEP { + + // ****************************************************************************** + // Converter stubs + // ****************************************************************************** + +#define DECL_CONV_STUB(type) template <> size_t GenericFill<IFC::Schema_2x3::type>(const STEP::DB& db, const EXPRESS::LIST& params, IFC::Schema_2x3::type* in) + + DECL_CONV_STUB(IfcRepresentationItem); + DECL_CONV_STUB(IfcGeometricRepresentationItem); + DECL_CONV_STUB(IfcCurve); + DECL_CONV_STUB(IfcBoundedCurve); + DECL_CONV_STUB(IfcCompositeCurve); + DECL_CONV_STUB(Ifc2DCompositeCurve); + DECL_CONV_STUB(IfcRoot); + DECL_CONV_STUB(IfcObjectDefinition); + DECL_CONV_STUB(IfcObject); + DECL_CONV_STUB(IfcControl); + DECL_CONV_STUB(IfcActionRequest); + DECL_CONV_STUB(IfcActor); + DECL_CONV_STUB(IfcTypeObject); + DECL_CONV_STUB(IfcTypeProduct); + DECL_CONV_STUB(IfcElementType); + DECL_CONV_STUB(IfcDistributionElementType); + DECL_CONV_STUB(IfcDistributionControlElementType); + DECL_CONV_STUB(IfcActuatorType); + DECL_CONV_STUB(IfcDistributionFlowElementType); + DECL_CONV_STUB(IfcFlowControllerType); + DECL_CONV_STUB(IfcAirTerminalBoxType); + DECL_CONV_STUB(IfcFlowTerminalType); + DECL_CONV_STUB(IfcAirTerminalType); + DECL_CONV_STUB(IfcEnergyConversionDeviceType); + DECL_CONV_STUB(IfcAirToAirHeatRecoveryType); + DECL_CONV_STUB(IfcAlarmType); + DECL_CONV_STUB(IfcDraughtingCallout); + DECL_CONV_STUB(IfcDimensionCurveDirectedCallout); + DECL_CONV_STUB(IfcAngularDimension); + DECL_CONV_STUB(IfcProduct); + DECL_CONV_STUB(IfcAnnotation); + DECL_CONV_STUB(IfcStyledItem); + DECL_CONV_STUB(IfcAnnotationOccurrence); + DECL_CONV_STUB(IfcAnnotationCurveOccurrence); + DECL_CONV_STUB(IfcAnnotationFillArea); + DECL_CONV_STUB(IfcAnnotationFillAreaOccurrence); + DECL_CONV_STUB(IfcAnnotationSurface); + DECL_CONV_STUB(IfcAnnotationSurfaceOccurrence); + DECL_CONV_STUB(IfcAnnotationSymbolOccurrence); + DECL_CONV_STUB(IfcAnnotationTextOccurrence); + DECL_CONV_STUB(IfcProfileDef); + DECL_CONV_STUB(IfcArbitraryClosedProfileDef); + DECL_CONV_STUB(IfcArbitraryOpenProfileDef); + DECL_CONV_STUB(IfcArbitraryProfileDefWithVoids); + DECL_CONV_STUB(IfcGroup); + DECL_CONV_STUB(IfcAsset); + DECL_CONV_STUB(IfcParameterizedProfileDef); + DECL_CONV_STUB(IfcIShapeProfileDef); + DECL_CONV_STUB(IfcAsymmetricIShapeProfileDef); + DECL_CONV_STUB(IfcPlacement); + DECL_CONV_STUB(IfcAxis1Placement); + DECL_CONV_STUB(IfcAxis2Placement2D); + DECL_CONV_STUB(IfcAxis2Placement3D); + DECL_CONV_STUB(IfcBSplineCurve); + DECL_CONV_STUB(IfcElement); + DECL_CONV_STUB(IfcBuildingElement); + DECL_CONV_STUB(IfcBeam); + DECL_CONV_STUB(IfcBuildingElementType); + DECL_CONV_STUB(IfcBeamType); + DECL_CONV_STUB(IfcBezierCurve); + DECL_CONV_STUB(IfcCsgPrimitive3D); + DECL_CONV_STUB(IfcBlock); + DECL_CONV_STUB(IfcBoilerType); + DECL_CONV_STUB(IfcBooleanResult); + DECL_CONV_STUB(IfcBooleanClippingResult); + DECL_CONV_STUB(IfcSurface); + DECL_CONV_STUB(IfcBoundedSurface); + DECL_CONV_STUB(IfcBoundingBox); + DECL_CONV_STUB(IfcHalfSpaceSolid); + DECL_CONV_STUB(IfcBoxedHalfSpace); + DECL_CONV_STUB(IfcSpatialStructureElement); + DECL_CONV_STUB(IfcBuilding); + DECL_CONV_STUB(IfcBuildingElementComponent); + DECL_CONV_STUB(IfcBuildingElementPart); + DECL_CONV_STUB(IfcBuildingElementProxy); + DECL_CONV_STUB(IfcBuildingElementProxyType); + DECL_CONV_STUB(IfcBuildingStorey); + DECL_CONV_STUB(IfcCShapeProfileDef); + DECL_CONV_STUB(IfcFlowFittingType); + DECL_CONV_STUB(IfcCableCarrierFittingType); + DECL_CONV_STUB(IfcFlowSegmentType); + DECL_CONV_STUB(IfcCableCarrierSegmentType); + DECL_CONV_STUB(IfcCableSegmentType); + DECL_CONV_STUB(IfcPoint); + DECL_CONV_STUB(IfcCartesianPoint); + DECL_CONV_STUB(IfcCartesianTransformationOperator); + DECL_CONV_STUB(IfcCartesianTransformationOperator2D); + DECL_CONV_STUB(IfcCartesianTransformationOperator2DnonUniform); + DECL_CONV_STUB(IfcCartesianTransformationOperator3D); + DECL_CONV_STUB(IfcCartesianTransformationOperator3DnonUniform); + DECL_CONV_STUB(IfcCenterLineProfileDef); + DECL_CONV_STUB(IfcFeatureElement); + DECL_CONV_STUB(IfcFeatureElementSubtraction); + DECL_CONV_STUB(IfcEdgeFeature); + DECL_CONV_STUB(IfcChamferEdgeFeature); + DECL_CONV_STUB(IfcChillerType); + DECL_CONV_STUB(IfcConic); + DECL_CONV_STUB(IfcCircle); + DECL_CONV_STUB(IfcCircleProfileDef); + DECL_CONV_STUB(IfcCircleHollowProfileDef); + DECL_CONV_STUB(IfcTopologicalRepresentationItem); + DECL_CONV_STUB(IfcConnectedFaceSet); + DECL_CONV_STUB(IfcClosedShell); + DECL_CONV_STUB(IfcCoilType); + DECL_CONV_STUB(IfcColourSpecification); + DECL_CONV_STUB(IfcColourRgb); + DECL_CONV_STUB(IfcColumn); + DECL_CONV_STUB(IfcColumnType); + DECL_CONV_STUB(IfcProperty); + DECL_CONV_STUB(IfcComplexProperty); + DECL_CONV_STUB(IfcCompositeCurveSegment); + DECL_CONV_STUB(IfcCompositeProfileDef); + DECL_CONV_STUB(IfcFlowMovingDeviceType); + DECL_CONV_STUB(IfcCompressorType); + DECL_CONV_STUB(IfcCondenserType); + DECL_CONV_STUB(IfcCondition); + DECL_CONV_STUB(IfcConditionCriterion); + DECL_CONV_STUB(IfcResource); + DECL_CONV_STUB(IfcConstructionResource); + DECL_CONV_STUB(IfcConstructionEquipmentResource); + DECL_CONV_STUB(IfcConstructionMaterialResource); + DECL_CONV_STUB(IfcConstructionProductResource); + DECL_CONV_STUB(IfcNamedUnit); + DECL_CONV_STUB(IfcContextDependentUnit); + DECL_CONV_STUB(IfcControllerType); + DECL_CONV_STUB(IfcConversionBasedUnit); + DECL_CONV_STUB(IfcCooledBeamType); + DECL_CONV_STUB(IfcCoolingTowerType); + DECL_CONV_STUB(IfcCostItem); + DECL_CONV_STUB(IfcCostSchedule); + DECL_CONV_STUB(IfcCovering); + DECL_CONV_STUB(IfcCoveringType); + DECL_CONV_STUB(IfcCraneRailAShapeProfileDef); + DECL_CONV_STUB(IfcCraneRailFShapeProfileDef); + DECL_CONV_STUB(IfcCrewResource); + DECL_CONV_STUB(IfcSolidModel); + DECL_CONV_STUB(IfcCsgSolid); + DECL_CONV_STUB(IfcCurtainWall); + DECL_CONV_STUB(IfcCurtainWallType); + DECL_CONV_STUB(IfcCurveBoundedPlane); + DECL_CONV_STUB(IfcPresentationStyle); + DECL_CONV_STUB(IfcDamperType); + DECL_CONV_STUB(IfcDefinedSymbol); + DECL_CONV_STUB(IfcDerivedProfileDef); + DECL_CONV_STUB(IfcDiameterDimension); + DECL_CONV_STUB(IfcDimensionCurve); + DECL_CONV_STUB(IfcTerminatorSymbol); + DECL_CONV_STUB(IfcDimensionCurveTerminator); + DECL_CONV_STUB(IfcDirection); + DECL_CONV_STUB(IfcElementComponent); + DECL_CONV_STUB(IfcDiscreteAccessory); + DECL_CONV_STUB(IfcElementComponentType); + DECL_CONV_STUB(IfcDiscreteAccessoryType); + DECL_CONV_STUB(IfcDistributionElement); + DECL_CONV_STUB(IfcDistributionFlowElement); + DECL_CONV_STUB(IfcDistributionChamberElement); + DECL_CONV_STUB(IfcDistributionChamberElementType); + DECL_CONV_STUB(IfcDistributionControlElement); + DECL_CONV_STUB(IfcPort); + DECL_CONV_STUB(IfcDistributionPort); + DECL_CONV_STUB(IfcDoor); + DECL_CONV_STUB(IfcPropertyDefinition); + DECL_CONV_STUB(IfcPropertySetDefinition); + DECL_CONV_STUB(IfcDoorStyle); + DECL_CONV_STUB(IfcDuctFittingType); + DECL_CONV_STUB(IfcDuctSegmentType); + DECL_CONV_STUB(IfcFlowTreatmentDeviceType); + DECL_CONV_STUB(IfcDuctSilencerType); + DECL_CONV_STUB(IfcEdge); + DECL_CONV_STUB(IfcEdgeCurve); + DECL_CONV_STUB(IfcLoop); + DECL_CONV_STUB(IfcEdgeLoop); + DECL_CONV_STUB(IfcElectricApplianceType); + DECL_CONV_STUB(IfcFlowController); + DECL_CONV_STUB(IfcElectricDistributionPoint); + DECL_CONV_STUB(IfcFlowStorageDeviceType); + DECL_CONV_STUB(IfcElectricFlowStorageDeviceType); + DECL_CONV_STUB(IfcElectricGeneratorType); + DECL_CONV_STUB(IfcElectricHeaterType); + DECL_CONV_STUB(IfcElectricMotorType); + DECL_CONV_STUB(IfcElectricTimeControlType); + DECL_CONV_STUB(IfcSystem); + DECL_CONV_STUB(IfcElectricalCircuit); + DECL_CONV_STUB(IfcElectricalElement); + DECL_CONV_STUB(IfcElementAssembly); + DECL_CONV_STUB(IfcElementQuantity); + DECL_CONV_STUB(IfcElementarySurface); + DECL_CONV_STUB(IfcEllipse); + DECL_CONV_STUB(IfcEllipseProfileDef); + DECL_CONV_STUB(IfcEnergyConversionDevice); + DECL_CONV_STUB(IfcEquipmentElement); + DECL_CONV_STUB(IfcEquipmentStandard); + DECL_CONV_STUB(IfcEvaporativeCoolerType); + DECL_CONV_STUB(IfcEvaporatorType); + DECL_CONV_STUB(IfcSweptAreaSolid); + DECL_CONV_STUB(IfcExtrudedAreaSolid); + DECL_CONV_STUB(IfcFace); + DECL_CONV_STUB(IfcFaceBasedSurfaceModel); + DECL_CONV_STUB(IfcFaceBound); + DECL_CONV_STUB(IfcFaceOuterBound); + DECL_CONV_STUB(IfcFaceSurface); + DECL_CONV_STUB(IfcManifoldSolidBrep); + DECL_CONV_STUB(IfcFacetedBrep); + DECL_CONV_STUB(IfcFacetedBrepWithVoids); + DECL_CONV_STUB(IfcFanType); + DECL_CONV_STUB(IfcFastener); + DECL_CONV_STUB(IfcFastenerType); + DECL_CONV_STUB(IfcFeatureElementAddition); + DECL_CONV_STUB(IfcFillAreaStyleHatching); + DECL_CONV_STUB(IfcFillAreaStyleTileSymbolWithStyle); + DECL_CONV_STUB(IfcFillAreaStyleTiles); + DECL_CONV_STUB(IfcFilterType); + DECL_CONV_STUB(IfcFireSuppressionTerminalType); + DECL_CONV_STUB(IfcFlowFitting); + DECL_CONV_STUB(IfcFlowInstrumentType); + DECL_CONV_STUB(IfcFlowMeterType); + DECL_CONV_STUB(IfcFlowMovingDevice); + DECL_CONV_STUB(IfcFlowSegment); + DECL_CONV_STUB(IfcFlowStorageDevice); + DECL_CONV_STUB(IfcFlowTerminal); + DECL_CONV_STUB(IfcFlowTreatmentDevice); + DECL_CONV_STUB(IfcFooting); + DECL_CONV_STUB(IfcFurnishingElement); + DECL_CONV_STUB(IfcFurnishingElementType); + DECL_CONV_STUB(IfcFurnitureStandard); + DECL_CONV_STUB(IfcFurnitureType); + DECL_CONV_STUB(IfcGasTerminalType); + DECL_CONV_STUB(IfcGeometricSet); + DECL_CONV_STUB(IfcGeometricCurveSet); + DECL_CONV_STUB(IfcRepresentationContext); + DECL_CONV_STUB(IfcGeometricRepresentationContext); + DECL_CONV_STUB(IfcGeometricRepresentationSubContext); + DECL_CONV_STUB(IfcGrid); + DECL_CONV_STUB(IfcObjectPlacement); + DECL_CONV_STUB(IfcGridPlacement); + DECL_CONV_STUB(IfcHeatExchangerType); + DECL_CONV_STUB(IfcHumidifierType); + DECL_CONV_STUB(IfcInventory); + DECL_CONV_STUB(IfcJunctionBoxType); + DECL_CONV_STUB(IfcLShapeProfileDef); + DECL_CONV_STUB(IfcLaborResource); + DECL_CONV_STUB(IfcLampType); + DECL_CONV_STUB(IfcLightFixtureType); + DECL_CONV_STUB(IfcLightSource); + DECL_CONV_STUB(IfcLightSourceAmbient); + DECL_CONV_STUB(IfcLightSourceDirectional); + DECL_CONV_STUB(IfcLightSourceGoniometric); + DECL_CONV_STUB(IfcLightSourcePositional); + DECL_CONV_STUB(IfcLightSourceSpot); + DECL_CONV_STUB(IfcLine); + DECL_CONV_STUB(IfcLinearDimension); + DECL_CONV_STUB(IfcLocalPlacement); + DECL_CONV_STUB(IfcMappedItem); + DECL_CONV_STUB(IfcProductRepresentation); + DECL_CONV_STUB(IfcMaterialDefinitionRepresentation); + DECL_CONV_STUB(IfcMeasureWithUnit); + DECL_CONV_STUB(IfcMechanicalFastener); + DECL_CONV_STUB(IfcMechanicalFastenerType); + DECL_CONV_STUB(IfcMember); + DECL_CONV_STUB(IfcMemberType); + DECL_CONV_STUB(IfcMotorConnectionType); + DECL_CONV_STUB(IfcProcess); + DECL_CONV_STUB(IfcTask); + DECL_CONV_STUB(IfcMove); + DECL_CONV_STUB(IfcOccupant); + DECL_CONV_STUB(IfcOffsetCurve2D); + DECL_CONV_STUB(IfcOffsetCurve3D); + DECL_CONV_STUB(IfcOneDirectionRepeatFactor); + DECL_CONV_STUB(IfcOpenShell); + DECL_CONV_STUB(IfcOpeningElement); + DECL_CONV_STUB(IfcOrderAction); + DECL_CONV_STUB(IfcOrientedEdge); + DECL_CONV_STUB(IfcOutletType); + DECL_CONV_STUB(IfcPath); + DECL_CONV_STUB(IfcPerformanceHistory); + DECL_CONV_STUB(IfcPermit); + DECL_CONV_STUB(IfcPile); + DECL_CONV_STUB(IfcPipeFittingType); + DECL_CONV_STUB(IfcPipeSegmentType); + DECL_CONV_STUB(IfcPlanarExtent); + DECL_CONV_STUB(IfcPlanarBox); + DECL_CONV_STUB(IfcPlane); + DECL_CONV_STUB(IfcPlate); + DECL_CONV_STUB(IfcPlateType); + DECL_CONV_STUB(IfcPointOnCurve); + DECL_CONV_STUB(IfcPointOnSurface); + DECL_CONV_STUB(IfcPolyLoop); + DECL_CONV_STUB(IfcPolygonalBoundedHalfSpace); + DECL_CONV_STUB(IfcPolyline); + DECL_CONV_STUB(IfcPresentationStyleAssignment); + DECL_CONV_STUB(IfcProcedure); + DECL_CONV_STUB(IfcProductDefinitionShape); + DECL_CONV_STUB(IfcProject); + DECL_CONV_STUB(IfcProjectOrder); + DECL_CONV_STUB(IfcProjectOrderRecord); + DECL_CONV_STUB(IfcProjectionCurve); + DECL_CONV_STUB(IfcProjectionElement); + DECL_CONV_STUB(IfcSimpleProperty); + DECL_CONV_STUB(IfcPropertyBoundedValue); + DECL_CONV_STUB(IfcPropertyEnumeratedValue); + DECL_CONV_STUB(IfcPropertyListValue); + DECL_CONV_STUB(IfcPropertyReferenceValue); + DECL_CONV_STUB(IfcPropertySet); + DECL_CONV_STUB(IfcPropertySingleValue); + DECL_CONV_STUB(IfcPropertyTableValue); + DECL_CONV_STUB(IfcProtectiveDeviceType); + DECL_CONV_STUB(IfcProxy); + DECL_CONV_STUB(IfcPumpType); + DECL_CONV_STUB(IfcRadiusDimension); + DECL_CONV_STUB(IfcRailing); + DECL_CONV_STUB(IfcRailingType); + DECL_CONV_STUB(IfcRamp); + DECL_CONV_STUB(IfcRampFlight); + DECL_CONV_STUB(IfcRampFlightType); + DECL_CONV_STUB(IfcRationalBezierCurve); + DECL_CONV_STUB(IfcRectangleProfileDef); + DECL_CONV_STUB(IfcRectangleHollowProfileDef); + DECL_CONV_STUB(IfcRectangularPyramid); + DECL_CONV_STUB(IfcRectangularTrimmedSurface); + DECL_CONV_STUB(IfcReinforcingElement); + DECL_CONV_STUB(IfcReinforcingBar); + DECL_CONV_STUB(IfcReinforcingMesh); + DECL_CONV_STUB(IfcRelationship); + DECL_CONV_STUB(IfcRelDecomposes); + DECL_CONV_STUB(IfcRelAggregates); + DECL_CONV_STUB(IfcRelConnects); + DECL_CONV_STUB(IfcRelContainedInSpatialStructure); + DECL_CONV_STUB(IfcRelDefines); + DECL_CONV_STUB(IfcRelDefinesByProperties); + DECL_CONV_STUB(IfcRelFillsElement); + DECL_CONV_STUB(IfcRelOverridesProperties); + DECL_CONV_STUB(IfcRelVoidsElement); + DECL_CONV_STUB(IfcRepresentation); + DECL_CONV_STUB(IfcRepresentationMap); + DECL_CONV_STUB(IfcRevolvedAreaSolid); + DECL_CONV_STUB(IfcRightCircularCone); + DECL_CONV_STUB(IfcRightCircularCylinder); + DECL_CONV_STUB(IfcRoof); + DECL_CONV_STUB(IfcRoundedEdgeFeature); + DECL_CONV_STUB(IfcRoundedRectangleProfileDef); + DECL_CONV_STUB(IfcSIUnit); + DECL_CONV_STUB(IfcSanitaryTerminalType); + DECL_CONV_STUB(IfcScheduleTimeControl); + DECL_CONV_STUB(IfcSectionedSpine); + DECL_CONV_STUB(IfcSensorType); + DECL_CONV_STUB(IfcServiceLife); + DECL_CONV_STUB(IfcShapeModel); + DECL_CONV_STUB(IfcShapeRepresentation); + DECL_CONV_STUB(IfcShellBasedSurfaceModel); + DECL_CONV_STUB(IfcSite); + DECL_CONV_STUB(IfcSlab); + DECL_CONV_STUB(IfcSlabType); + DECL_CONV_STUB(IfcSpace); + DECL_CONV_STUB(IfcSpaceHeaterType); + DECL_CONV_STUB(IfcSpaceProgram); + DECL_CONV_STUB(IfcSpatialStructureElementType); + DECL_CONV_STUB(IfcSpaceType); + DECL_CONV_STUB(IfcSphere); + DECL_CONV_STUB(IfcStackTerminalType); + DECL_CONV_STUB(IfcStair); + DECL_CONV_STUB(IfcStairFlight); + DECL_CONV_STUB(IfcStairFlightType); + DECL_CONV_STUB(IfcStructuralActivity); + DECL_CONV_STUB(IfcStructuralAction); + DECL_CONV_STUB(IfcStructuralAnalysisModel); + DECL_CONV_STUB(IfcStructuralItem); + DECL_CONV_STUB(IfcStructuralConnection); + DECL_CONV_STUB(IfcStructuralCurveConnection); + DECL_CONV_STUB(IfcStructuralMember); + DECL_CONV_STUB(IfcStructuralCurveMember); + DECL_CONV_STUB(IfcStructuralCurveMemberVarying); + DECL_CONV_STUB(IfcStructuralLinearAction); + DECL_CONV_STUB(IfcStructuralLinearActionVarying); + DECL_CONV_STUB(IfcStructuralLoadGroup); + DECL_CONV_STUB(IfcStructuralPlanarAction); + DECL_CONV_STUB(IfcStructuralPlanarActionVarying); + DECL_CONV_STUB(IfcStructuralPointAction); + DECL_CONV_STUB(IfcStructuralPointConnection); + DECL_CONV_STUB(IfcStructuralReaction); + DECL_CONV_STUB(IfcStructuralPointReaction); + DECL_CONV_STUB(IfcStructuralResultGroup); + DECL_CONV_STUB(IfcStructuralSurfaceConnection); + DECL_CONV_STUB(IfcStructuralSurfaceMember); + DECL_CONV_STUB(IfcStructuralSurfaceMemberVarying); + DECL_CONV_STUB(IfcStructuredDimensionCallout); + DECL_CONV_STUB(IfcStyleModel); + DECL_CONV_STUB(IfcStyledRepresentation); + DECL_CONV_STUB(IfcSubContractResource); + DECL_CONV_STUB(IfcSubedge); + DECL_CONV_STUB(IfcSurfaceCurveSweptAreaSolid); + DECL_CONV_STUB(IfcSweptSurface); + DECL_CONV_STUB(IfcSurfaceOfLinearExtrusion); + DECL_CONV_STUB(IfcSurfaceOfRevolution); + DECL_CONV_STUB(IfcSurfaceStyle); + DECL_CONV_STUB(IfcSurfaceStyleShading); + DECL_CONV_STUB(IfcSurfaceStyleRendering); + DECL_CONV_STUB(IfcSurfaceStyleWithTextures); + DECL_CONV_STUB(IfcSweptDiskSolid); + DECL_CONV_STUB(IfcSwitchingDeviceType); + DECL_CONV_STUB(IfcSystemFurnitureElementType); + DECL_CONV_STUB(IfcTShapeProfileDef); + DECL_CONV_STUB(IfcTankType); + DECL_CONV_STUB(IfcTendon); + DECL_CONV_STUB(IfcTendonAnchor); + DECL_CONV_STUB(IfcTextLiteral); + DECL_CONV_STUB(IfcTextLiteralWithExtent); + DECL_CONV_STUB(IfcTimeSeriesSchedule); + DECL_CONV_STUB(IfcTopologyRepresentation); + DECL_CONV_STUB(IfcTransformerType); + DECL_CONV_STUB(IfcTransportElement); + DECL_CONV_STUB(IfcTransportElementType); + DECL_CONV_STUB(IfcTrapeziumProfileDef); + DECL_CONV_STUB(IfcTrimmedCurve); + DECL_CONV_STUB(IfcTubeBundleType); + DECL_CONV_STUB(IfcTwoDirectionRepeatFactor); + DECL_CONV_STUB(IfcUShapeProfileDef); + DECL_CONV_STUB(IfcUnitAssignment); + DECL_CONV_STUB(IfcUnitaryEquipmentType); + DECL_CONV_STUB(IfcValveType); + DECL_CONV_STUB(IfcVector); + DECL_CONV_STUB(IfcVertex); + DECL_CONV_STUB(IfcVertexLoop); + DECL_CONV_STUB(IfcVertexPoint); + DECL_CONV_STUB(IfcVibrationIsolatorType); + DECL_CONV_STUB(IfcVirtualElement); + DECL_CONV_STUB(IfcWall); + DECL_CONV_STUB(IfcWallStandardCase); + DECL_CONV_STUB(IfcWallType); + DECL_CONV_STUB(IfcWasteTerminalType); + DECL_CONV_STUB(IfcWindow); + DECL_CONV_STUB(IfcWindowStyle); + DECL_CONV_STUB(IfcWorkControl); + DECL_CONV_STUB(IfcWorkPlan); + DECL_CONV_STUB(IfcWorkSchedule); + DECL_CONV_STUB(IfcZShapeProfileDef); + DECL_CONV_STUB(IfcZone); + + +#undef DECL_CONV_STUB + +} //! STEP +} //! Assimp + +#ifdef _MSC_VER +# pragma warning(pop) +#endif // _MSC_VER + +#endif // INCLUDED_IFC_READER_GEN_H diff --git a/libs/assimp/code/AssetLib/IFC/IFCReaderGen_4.cpp b/libs/assimp/code/AssetLib/IFC/IFCReaderGen_4.cpp new file mode 100644 index 0000000..1793229 --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCReaderGen_4.cpp @@ -0,0 +1,6207 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, ASSIMP Development 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 Development 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. + +---------------------------------------------------------------------- +*/ + +/** MACHINE-GENERATED by scripts/ICFImporter/CppGenerator.py */ + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER + +#include "AssimpPCH.h" +#include "IFCReaderGen_4.h" + +namespace Assimp { +using namespace IFC; +using namespace ::Assimp::IFC::Schema_4; + +namespace { + + typedef EXPRESS::ConversionSchema::SchemaEntry SchemaEntry; + const SchemaEntry schema_raw[] = { + SchemaEntry("ifcstrippedoptional",NULL ) +, SchemaEntry("ifcabsorbeddosemeasure",NULL ) +, SchemaEntry("ifcaccelerationmeasure",NULL ) +, SchemaEntry("ifcamountofsubstancemeasure",NULL ) +, SchemaEntry("ifcangularvelocitymeasure",NULL ) +, SchemaEntry("ifcarcindex",NULL ) +, SchemaEntry("ifcareadensitymeasure",NULL ) +, SchemaEntry("ifcareameasure",NULL ) +, SchemaEntry("ifcbinary",NULL ) +, SchemaEntry("ifcboolean",NULL ) +, SchemaEntry("ifcboxalignment",NULL ) +, SchemaEntry("ifccardinalpointreference",NULL ) +, SchemaEntry("ifccomplexnumber",NULL ) +, SchemaEntry("ifccompoundplaneanglemeasure",NULL ) +, SchemaEntry("ifccontextdependentmeasure",NULL ) +, SchemaEntry("ifccountmeasure",NULL ) +, SchemaEntry("ifccurvaturemeasure",NULL ) +, SchemaEntry("ifcdate",NULL ) +, SchemaEntry("ifcdatetime",NULL ) +, SchemaEntry("ifcdayinmonthnumber",NULL ) +, SchemaEntry("ifcdayinweeknumber",NULL ) +, SchemaEntry("ifcdescriptivemeasure",NULL ) +, SchemaEntry("ifcdimensioncount",NULL ) +, SchemaEntry("ifcdoseequivalentmeasure",NULL ) +, SchemaEntry("ifcduration",NULL ) +, SchemaEntry("ifcdynamicviscositymeasure",NULL ) +, SchemaEntry("ifcelectriccapacitancemeasure",NULL ) +, SchemaEntry("ifcelectricchargemeasure",NULL ) +, SchemaEntry("ifcelectricconductancemeasure",NULL ) +, SchemaEntry("ifcelectriccurrentmeasure",NULL ) +, SchemaEntry("ifcelectricresistancemeasure",NULL ) +, SchemaEntry("ifcelectricvoltagemeasure",NULL ) +, SchemaEntry("ifcenergymeasure",NULL ) +, SchemaEntry("ifcfontstyle",NULL ) +, SchemaEntry("ifcfontvariant",NULL ) +, SchemaEntry("ifcfontweight",NULL ) +, SchemaEntry("ifcforcemeasure",NULL ) +, SchemaEntry("ifcfrequencymeasure",NULL ) +, SchemaEntry("ifcgloballyuniqueid",NULL ) +, SchemaEntry("ifcheatfluxdensitymeasure",NULL ) +, SchemaEntry("ifcheatingvaluemeasure",NULL ) +, SchemaEntry("ifcidentifier",NULL ) +, SchemaEntry("ifcilluminancemeasure",NULL ) +, SchemaEntry("ifcinductancemeasure",NULL ) +, SchemaEntry("ifcinteger",NULL ) +, SchemaEntry("ifcintegercountratemeasure",NULL ) +, SchemaEntry("ifcionconcentrationmeasure",NULL ) +, SchemaEntry("ifcisothermalmoisturecapacitymeasure",NULL ) +, SchemaEntry("ifckinematicviscositymeasure",NULL ) +, SchemaEntry("ifclabel",NULL ) +, SchemaEntry("ifclanguageid",NULL ) +, SchemaEntry("ifclengthmeasure",NULL ) +, SchemaEntry("ifclineindex",NULL ) +, SchemaEntry("ifclinearforcemeasure",NULL ) +, SchemaEntry("ifclinearmomentmeasure",NULL ) +, SchemaEntry("ifclinearstiffnessmeasure",NULL ) +, SchemaEntry("ifclinearvelocitymeasure",NULL ) +, SchemaEntry("ifclogical",NULL ) +, SchemaEntry("ifcluminousfluxmeasure",NULL ) +, SchemaEntry("ifcluminousintensitydistributionmeasure",NULL ) +, SchemaEntry("ifcluminousintensitymeasure",NULL ) +, SchemaEntry("ifcmagneticfluxdensitymeasure",NULL ) +, SchemaEntry("ifcmagneticfluxmeasure",NULL ) +, SchemaEntry("ifcmassdensitymeasure",NULL ) +, SchemaEntry("ifcmassflowratemeasure",NULL ) +, SchemaEntry("ifcmassmeasure",NULL ) +, SchemaEntry("ifcmassperlengthmeasure",NULL ) +, SchemaEntry("ifcmodulusofelasticitymeasure",NULL ) +, SchemaEntry("ifcmodulusoflinearsubgradereactionmeasure",NULL ) +, SchemaEntry("ifcmodulusofrotationalsubgradereactionmeasure",NULL ) +, SchemaEntry("ifcmodulusofsubgradereactionmeasure",NULL ) +, SchemaEntry("ifcmoisturediffusivitymeasure",NULL ) +, SchemaEntry("ifcmolecularweightmeasure",NULL ) +, SchemaEntry("ifcmomentofinertiameasure",NULL ) +, SchemaEntry("ifcmonetarymeasure",NULL ) +, SchemaEntry("ifcmonthinyearnumber",NULL ) +, SchemaEntry("ifcnonnegativelengthmeasure",NULL ) +, SchemaEntry("ifcnormalisedratiomeasure",NULL ) +, SchemaEntry("ifcnumericmeasure",NULL ) +, SchemaEntry("ifcphmeasure",NULL ) +, SchemaEntry("ifcparametervalue",NULL ) +, SchemaEntry("ifcplanarforcemeasure",NULL ) +, SchemaEntry("ifcplaneanglemeasure",NULL ) +, SchemaEntry("ifcpositiveinteger",NULL ) +, SchemaEntry("ifcpositivelengthmeasure",NULL ) +, SchemaEntry("ifcpositiveplaneanglemeasure",NULL ) +, SchemaEntry("ifcpositiveratiomeasure",NULL ) +, SchemaEntry("ifcpowermeasure",NULL ) +, SchemaEntry("ifcpresentabletext",NULL ) +, SchemaEntry("ifcpressuremeasure",NULL ) +, SchemaEntry("ifcpropertysetdefinitionset",NULL ) +, SchemaEntry("ifcradioactivitymeasure",NULL ) +, SchemaEntry("ifcratiomeasure",NULL ) +, SchemaEntry("ifcreal",NULL ) +, SchemaEntry("ifcrotationalfrequencymeasure",NULL ) +, SchemaEntry("ifcrotationalmassmeasure",NULL ) +, SchemaEntry("ifcrotationalstiffnessmeasure",NULL ) +, SchemaEntry("ifcsectionmodulusmeasure",NULL ) +, SchemaEntry("ifcsectionalareaintegralmeasure",NULL ) +, SchemaEntry("ifcshearmodulusmeasure",NULL ) +, SchemaEntry("ifcsolidanglemeasure",NULL ) +, SchemaEntry("ifcsoundpowerlevelmeasure",NULL ) +, SchemaEntry("ifcsoundpowermeasure",NULL ) +, SchemaEntry("ifcsoundpressurelevelmeasure",NULL ) +, SchemaEntry("ifcsoundpressuremeasure",NULL ) +, SchemaEntry("ifcspecificheatcapacitymeasure",NULL ) +, SchemaEntry("ifcspecularexponent",NULL ) +, SchemaEntry("ifcspecularroughness",NULL ) +, SchemaEntry("ifctemperaturegradientmeasure",NULL ) +, SchemaEntry("ifctemperaturerateofchangemeasure",NULL ) +, SchemaEntry("ifctext",NULL ) +, SchemaEntry("ifctextalignment",NULL ) +, SchemaEntry("ifctextdecoration",NULL ) +, SchemaEntry("ifctextfontname",NULL ) +, SchemaEntry("ifctexttransformation",NULL ) +, SchemaEntry("ifcthermaladmittancemeasure",NULL ) +, SchemaEntry("ifcthermalconductivitymeasure",NULL ) +, SchemaEntry("ifcthermalexpansioncoefficientmeasure",NULL ) +, SchemaEntry("ifcthermalresistancemeasure",NULL ) +, SchemaEntry("ifcthermaltransmittancemeasure",NULL ) +, SchemaEntry("ifcthermodynamictemperaturemeasure",NULL ) +, SchemaEntry("ifctime",NULL ) +, SchemaEntry("ifctimemeasure",NULL ) +, SchemaEntry("ifctimestamp",NULL ) +, SchemaEntry("ifctorquemeasure",NULL ) +, SchemaEntry("ifcurireference",NULL ) +, SchemaEntry("ifcvaporpermeabilitymeasure",NULL ) +, SchemaEntry("ifcvolumemeasure",NULL ) +, SchemaEntry("ifcvolumetricflowratemeasure",NULL ) +, SchemaEntry("ifcwarpingconstantmeasure",NULL ) +, SchemaEntry("ifcwarpingmomentmeasure",NULL ) +, SchemaEntry("ifcactionrequesttypeenum",NULL ) +, SchemaEntry("ifcactionsourcetypeenum",NULL ) +, SchemaEntry("ifcactiontypeenum",NULL ) +, SchemaEntry("ifcactuatortypeenum",NULL ) +, SchemaEntry("ifcaddresstypeenum",NULL ) +, SchemaEntry("ifcairterminalboxtypeenum",NULL ) +, SchemaEntry("ifcairterminaltypeenum",NULL ) +, SchemaEntry("ifcairtoairheatrecoverytypeenum",NULL ) +, SchemaEntry("ifcalarmtypeenum",NULL ) +, SchemaEntry("ifcanalysismodeltypeenum",NULL ) +, SchemaEntry("ifcanalysistheorytypeenum",NULL ) +, SchemaEntry("ifcarithmeticoperatorenum",NULL ) +, SchemaEntry("ifcassemblyplaceenum",NULL ) +, SchemaEntry("ifcaudiovisualappliancetypeenum",NULL ) +, SchemaEntry("ifcbsplinecurveform",NULL ) +, SchemaEntry("ifcbsplinesurfaceform",NULL ) +, SchemaEntry("ifcbeamtypeenum",NULL ) +, SchemaEntry("ifcbenchmarkenum",NULL ) +, SchemaEntry("ifcboilertypeenum",NULL ) +, SchemaEntry("ifcbooleanoperator",NULL ) +, SchemaEntry("ifcbuildingelementparttypeenum",NULL ) +, SchemaEntry("ifcbuildingelementproxytypeenum",NULL ) +, SchemaEntry("ifcbuildingsystemtypeenum",NULL ) +, SchemaEntry("ifcburnertypeenum",NULL ) +, SchemaEntry("ifccablecarrierfittingtypeenum",NULL ) +, SchemaEntry("ifccablecarriersegmenttypeenum",NULL ) +, SchemaEntry("ifccablefittingtypeenum",NULL ) +, SchemaEntry("ifccablesegmenttypeenum",NULL ) +, SchemaEntry("ifcchangeactionenum",NULL ) +, SchemaEntry("ifcchillertypeenum",NULL ) +, SchemaEntry("ifcchimneytypeenum",NULL ) +, SchemaEntry("ifccoiltypeenum",NULL ) +, SchemaEntry("ifccolumntypeenum",NULL ) +, SchemaEntry("ifccommunicationsappliancetypeenum",NULL ) +, SchemaEntry("ifccomplexpropertytemplatetypeenum",NULL ) +, SchemaEntry("ifccompressortypeenum",NULL ) +, SchemaEntry("ifccondensertypeenum",NULL ) +, SchemaEntry("ifcconnectiontypeenum",NULL ) +, SchemaEntry("ifcconstraintenum",NULL ) +, SchemaEntry("ifcconstructionequipmentresourcetypeenum",NULL ) +, SchemaEntry("ifcconstructionmaterialresourcetypeenum",NULL ) +, SchemaEntry("ifcconstructionproductresourcetypeenum",NULL ) +, SchemaEntry("ifccontrollertypeenum",NULL ) +, SchemaEntry("ifccooledbeamtypeenum",NULL ) +, SchemaEntry("ifccoolingtowertypeenum",NULL ) +, SchemaEntry("ifccostitemtypeenum",NULL ) +, SchemaEntry("ifccostscheduletypeenum",NULL ) +, SchemaEntry("ifccoveringtypeenum",NULL ) +, SchemaEntry("ifccrewresourcetypeenum",NULL ) +, SchemaEntry("ifccurtainwalltypeenum",NULL ) +, SchemaEntry("ifccurveinterpolationenum",NULL ) +, SchemaEntry("ifcdampertypeenum",NULL ) +, SchemaEntry("ifcdataoriginenum",NULL ) +, SchemaEntry("ifcderivedunitenum",NULL ) +, SchemaEntry("ifcdirectionsenseenum",NULL ) +, SchemaEntry("ifcdiscreteaccessorytypeenum",NULL ) +, SchemaEntry("ifcdistributionchamberelementtypeenum",NULL ) +, SchemaEntry("ifcdistributionporttypeenum",NULL ) +, SchemaEntry("ifcdistributionsystemenum",NULL ) +, SchemaEntry("ifcdocumentconfidentialityenum",NULL ) +, SchemaEntry("ifcdocumentstatusenum",NULL ) +, SchemaEntry("ifcdoorpaneloperationenum",NULL ) +, SchemaEntry("ifcdoorpanelpositionenum",NULL ) +, SchemaEntry("ifcdoorstyleconstructionenum",NULL ) +, SchemaEntry("ifcdoorstyleoperationenum",NULL ) +, SchemaEntry("ifcdoortypeenum",NULL ) +, SchemaEntry("ifcdoortypeoperationenum",NULL ) +, SchemaEntry("ifcductfittingtypeenum",NULL ) +, SchemaEntry("ifcductsegmenttypeenum",NULL ) +, SchemaEntry("ifcductsilencertypeenum",NULL ) +, SchemaEntry("ifcelectricappliancetypeenum",NULL ) +, SchemaEntry("ifcelectricdistributionboardtypeenum",NULL ) +, SchemaEntry("ifcelectricflowstoragedevicetypeenum",NULL ) +, SchemaEntry("ifcelectricgeneratortypeenum",NULL ) +, SchemaEntry("ifcelectricmotortypeenum",NULL ) +, SchemaEntry("ifcelectrictimecontroltypeenum",NULL ) +, SchemaEntry("ifcelementassemblytypeenum",NULL ) +, SchemaEntry("ifcelementcompositionenum",NULL ) +, SchemaEntry("ifcenginetypeenum",NULL ) +, SchemaEntry("ifcevaporativecoolertypeenum",NULL ) +, SchemaEntry("ifcevaporatortypeenum",NULL ) +, SchemaEntry("ifceventtriggertypeenum",NULL ) +, SchemaEntry("ifceventtypeenum",NULL ) +, SchemaEntry("ifcexternalspatialelementtypeenum",NULL ) +, SchemaEntry("ifcfantypeenum",NULL ) +, SchemaEntry("ifcfastenertypeenum",NULL ) +, SchemaEntry("ifcfiltertypeenum",NULL ) +, SchemaEntry("ifcfiresuppressionterminaltypeenum",NULL ) +, SchemaEntry("ifcflowdirectionenum",NULL ) +, SchemaEntry("ifcflowinstrumenttypeenum",NULL ) +, SchemaEntry("ifcflowmetertypeenum",NULL ) +, SchemaEntry("ifcfootingtypeenum",NULL ) +, SchemaEntry("ifcfurnituretypeenum",NULL ) +, SchemaEntry("ifcgeographicelementtypeenum",NULL ) +, SchemaEntry("ifcgeometricprojectionenum",NULL ) +, SchemaEntry("ifcglobalorlocalenum",NULL ) +, SchemaEntry("ifcgridtypeenum",NULL ) +, SchemaEntry("ifcheatexchangertypeenum",NULL ) +, SchemaEntry("ifchumidifiertypeenum",NULL ) +, SchemaEntry("ifcinterceptortypeenum",NULL ) +, SchemaEntry("ifcinternalorexternalenum",NULL ) +, SchemaEntry("ifcinventorytypeenum",NULL ) +, SchemaEntry("ifcjunctionboxtypeenum",NULL ) +, SchemaEntry("ifcknottype",NULL ) +, SchemaEntry("ifclaborresourcetypeenum",NULL ) +, SchemaEntry("ifclamptypeenum",NULL ) +, SchemaEntry("ifclayersetdirectionenum",NULL ) +, SchemaEntry("ifclightdistributioncurveenum",NULL ) +, SchemaEntry("ifclightemissionsourceenum",NULL ) +, SchemaEntry("ifclightfixturetypeenum",NULL ) +, SchemaEntry("ifcloadgrouptypeenum",NULL ) +, SchemaEntry("ifclogicaloperatorenum",NULL ) +, SchemaEntry("ifcmechanicalfastenertypeenum",NULL ) +, SchemaEntry("ifcmedicaldevicetypeenum",NULL ) +, SchemaEntry("ifcmembertypeenum",NULL ) +, SchemaEntry("ifcmotorconnectiontypeenum",NULL ) +, SchemaEntry("ifcnullstyle",NULL ) +, SchemaEntry("ifcobjecttypeenum",NULL ) +, SchemaEntry("ifcobjectiveenum",NULL ) +, SchemaEntry("ifcoccupanttypeenum",NULL ) +, SchemaEntry("ifcopeningelementtypeenum",NULL ) +, SchemaEntry("ifcoutlettypeenum",NULL ) +, SchemaEntry("ifcperformancehistorytypeenum",NULL ) +, SchemaEntry("ifcpermeablecoveringoperationenum",NULL ) +, SchemaEntry("ifcpermittypeenum",NULL ) +, SchemaEntry("ifcphysicalorvirtualenum",NULL ) +, SchemaEntry("ifcpileconstructionenum",NULL ) +, SchemaEntry("ifcpiletypeenum",NULL ) +, SchemaEntry("ifcpipefittingtypeenum",NULL ) +, SchemaEntry("ifcpipesegmenttypeenum",NULL ) +, SchemaEntry("ifcplatetypeenum",NULL ) +, SchemaEntry("ifcpreferredsurfacecurverepresentation",NULL ) +, SchemaEntry("ifcproceduretypeenum",NULL ) +, SchemaEntry("ifcprofiletypeenum",NULL ) +, SchemaEntry("ifcprojectordertypeenum",NULL ) +, SchemaEntry("ifcprojectedortruelengthenum",NULL ) +, SchemaEntry("ifcprojectionelementtypeenum",NULL ) +, SchemaEntry("ifcpropertysettemplatetypeenum",NULL ) +, SchemaEntry("ifcprotectivedevicetrippingunittypeenum",NULL ) +, SchemaEntry("ifcprotectivedevicetypeenum",NULL ) +, SchemaEntry("ifcpumptypeenum",NULL ) +, SchemaEntry("ifcrailingtypeenum",NULL ) +, SchemaEntry("ifcrampflighttypeenum",NULL ) +, SchemaEntry("ifcramptypeenum",NULL ) +, SchemaEntry("ifcrecurrencetypeenum",NULL ) +, SchemaEntry("ifcreflectancemethodenum",NULL ) +, SchemaEntry("ifcreinforcingbarroleenum",NULL ) +, SchemaEntry("ifcreinforcingbarsurfaceenum",NULL ) +, SchemaEntry("ifcreinforcingbartypeenum",NULL ) +, SchemaEntry("ifcreinforcingmeshtypeenum",NULL ) +, SchemaEntry("ifcroleenum",NULL ) +, SchemaEntry("ifcrooftypeenum",NULL ) +, SchemaEntry("ifcsiprefix",NULL ) +, SchemaEntry("ifcsiunitname",NULL ) +, SchemaEntry("ifcsanitaryterminaltypeenum",NULL ) +, SchemaEntry("ifcsectiontypeenum",NULL ) +, SchemaEntry("ifcsensortypeenum",NULL ) +, SchemaEntry("ifcsequenceenum",NULL ) +, SchemaEntry("ifcshadingdevicetypeenum",NULL ) +, SchemaEntry("ifcsimplepropertytemplatetypeenum",NULL ) +, SchemaEntry("ifcslabtypeenum",NULL ) +, SchemaEntry("ifcsolardevicetypeenum",NULL ) +, SchemaEntry("ifcspaceheatertypeenum",NULL ) +, SchemaEntry("ifcspacetypeenum",NULL ) +, SchemaEntry("ifcspatialzonetypeenum",NULL ) +, SchemaEntry("ifcstackterminaltypeenum",NULL ) +, SchemaEntry("ifcstairflighttypeenum",NULL ) +, SchemaEntry("ifcstairtypeenum",NULL ) +, SchemaEntry("ifcstateenum",NULL ) +, SchemaEntry("ifcstructuralcurveactivitytypeenum",NULL ) +, SchemaEntry("ifcstructuralcurvemembertypeenum",NULL ) +, SchemaEntry("ifcstructuralsurfaceactivitytypeenum",NULL ) +, SchemaEntry("ifcstructuralsurfacemembertypeenum",NULL ) +, SchemaEntry("ifcsubcontractresourcetypeenum",NULL ) +, SchemaEntry("ifcsurfacefeaturetypeenum",NULL ) +, SchemaEntry("ifcsurfaceside",NULL ) +, SchemaEntry("ifcswitchingdevicetypeenum",NULL ) +, SchemaEntry("ifcsystemfurnitureelementtypeenum",NULL ) +, SchemaEntry("ifctanktypeenum",NULL ) +, SchemaEntry("ifctaskdurationenum",NULL ) +, SchemaEntry("ifctasktypeenum",NULL ) +, SchemaEntry("ifctendonanchortypeenum",NULL ) +, SchemaEntry("ifctendontypeenum",NULL ) +, SchemaEntry("ifctextpath",NULL ) +, SchemaEntry("ifctimeseriesdatatypeenum",NULL ) +, SchemaEntry("ifctransformertypeenum",NULL ) +, SchemaEntry("ifctransitioncode",NULL ) +, SchemaEntry("ifctransportelementtypeenum",NULL ) +, SchemaEntry("ifctrimmingpreference",NULL ) +, SchemaEntry("ifctubebundletypeenum",NULL ) +, SchemaEntry("ifcunitenum",NULL ) +, SchemaEntry("ifcunitarycontrolelementtypeenum",NULL ) +, SchemaEntry("ifcunitaryequipmenttypeenum",NULL ) +, SchemaEntry("ifcvalvetypeenum",NULL ) +, SchemaEntry("ifcvibrationisolatortypeenum",NULL ) +, SchemaEntry("ifcvoidingfeaturetypeenum",NULL ) +, SchemaEntry("ifcwalltypeenum",NULL ) +, SchemaEntry("ifcwasteterminaltypeenum",NULL ) +, SchemaEntry("ifcwindowpaneloperationenum",NULL ) +, SchemaEntry("ifcwindowpanelpositionenum",NULL ) +, SchemaEntry("ifcwindowstyleconstructionenum",NULL ) +, SchemaEntry("ifcwindowstyleoperationenum",NULL ) +, SchemaEntry("ifcwindowtypeenum",NULL ) +, SchemaEntry("ifcwindowtypepartitioningenum",NULL ) +, SchemaEntry("ifcworkcalendartypeenum",NULL ) +, SchemaEntry("ifcworkplantypeenum",NULL ) +, SchemaEntry("ifcworkscheduletypeenum",NULL ) +, SchemaEntry("ifcactorselect",NULL ) +, SchemaEntry("ifcappliedvalueselect",NULL ) +, SchemaEntry("ifcaxis2placement",NULL ) +, SchemaEntry("ifcbendingparameterselect",NULL ) +, SchemaEntry("ifcbooleanoperand",NULL ) +, SchemaEntry("ifcclassificationreferenceselect",NULL ) +, SchemaEntry("ifcclassificationselect",NULL ) +, SchemaEntry("ifccolour",NULL ) +, SchemaEntry("ifccolourorfactor",NULL ) +, SchemaEntry("ifccoordinatereferencesystemselect",NULL ) +, SchemaEntry("ifccsgselect",NULL ) +, SchemaEntry("ifccurvefontorscaledcurvefontselect",NULL ) +, SchemaEntry("ifccurveonsurface",NULL ) +, SchemaEntry("ifccurveoredgecurve",NULL ) +, SchemaEntry("ifccurvestylefontselect",NULL ) +, SchemaEntry("ifcdefinitionselect",NULL ) +, SchemaEntry("ifcderivedmeasurevalue",NULL ) +, SchemaEntry("ifcdocumentselect",NULL ) +, SchemaEntry("ifcfillstyleselect",NULL ) +, SchemaEntry("ifcgeometricsetselect",NULL ) +, SchemaEntry("ifcgridplacementdirectionselect",NULL ) +, SchemaEntry("ifchatchlinedistanceselect",NULL ) +, SchemaEntry("ifclayereditem",NULL ) +, SchemaEntry("ifclibraryselect",NULL ) +, SchemaEntry("ifclightdistributiondatasourceselect",NULL ) +, SchemaEntry("ifcmaterialselect",NULL ) +, SchemaEntry("ifcmeasurevalue",NULL ) +, SchemaEntry("ifcmetricvalueselect",NULL ) +, SchemaEntry("ifcmodulusofrotationalsubgradereactionselect",NULL ) +, SchemaEntry("ifcmodulusofsubgradereactionselect",NULL ) +, SchemaEntry("ifcmodulusoftranslationalsubgradereactionselect",NULL ) +, SchemaEntry("ifcobjectreferenceselect",NULL ) +, SchemaEntry("ifcpointorvertexpoint",NULL ) +, SchemaEntry("ifcpresentationstyleselect",NULL ) +, SchemaEntry("ifcprocessselect",NULL ) +, SchemaEntry("ifcproductrepresentationselect",NULL ) +, SchemaEntry("ifcproductselect",NULL ) +, SchemaEntry("ifcpropertysetdefinitionselect",NULL ) +, SchemaEntry("ifcresourceobjectselect",NULL ) +, SchemaEntry("ifcresourceselect",NULL ) +, SchemaEntry("ifcrotationalstiffnessselect",NULL ) +, SchemaEntry("ifcsegmentindexselect",NULL ) +, SchemaEntry("ifcshell",NULL ) +, SchemaEntry("ifcsimplevalue",NULL ) +, SchemaEntry("ifcsizeselect",NULL ) +, SchemaEntry("ifcsolidorshell",NULL ) +, SchemaEntry("ifcspaceboundaryselect",NULL ) +, SchemaEntry("ifcspecularhighlightselect",NULL ) +, SchemaEntry("ifcstructuralactivityassignmentselect",NULL ) +, SchemaEntry("ifcstyleassignmentselect",NULL ) +, SchemaEntry("ifcsurfaceorfacesurface",NULL ) +, SchemaEntry("ifcsurfacestyleelementselect",NULL ) +, SchemaEntry("ifctextfontselect",NULL ) +, SchemaEntry("ifctimeorratioselect",NULL ) +, SchemaEntry("ifctranslationalstiffnessselect",NULL ) +, SchemaEntry("ifctrimmingselect",NULL ) +, SchemaEntry("ifcunit",NULL ) +, SchemaEntry("ifcvalue",NULL ) +, SchemaEntry("ifcvectorordirection",NULL ) +, SchemaEntry("ifcwarpingstiffnessselect",NULL ) +, SchemaEntry("ifcroot",&STEP::ObjectHelper<IfcRoot,4>::Construct ) +, SchemaEntry("ifcobjectdefinition",&STEP::ObjectHelper<IfcObjectDefinition,0>::Construct ) +, SchemaEntry("ifcobject",&STEP::ObjectHelper<IfcObject,1>::Construct ) +, SchemaEntry("ifccontrol",&STEP::ObjectHelper<IfcControl,1>::Construct ) +, SchemaEntry("ifcactionrequest",&STEP::ObjectHelper<IfcActionRequest,3>::Construct ) +, SchemaEntry("ifcactor",&STEP::ObjectHelper<IfcActor,1>::Construct ) +, SchemaEntry("ifcactorrole",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcproduct",&STEP::ObjectHelper<IfcProduct,2>::Construct ) +, SchemaEntry("ifcelement",&STEP::ObjectHelper<IfcElement,1>::Construct ) +, SchemaEntry("ifcdistributionelement",&STEP::ObjectHelper<IfcDistributionElement,0>::Construct ) +, SchemaEntry("ifcdistributioncontrolelement",&STEP::ObjectHelper<IfcDistributionControlElement,0>::Construct ) +, SchemaEntry("ifcactuator",&STEP::ObjectHelper<IfcActuator,1>::Construct ) +, SchemaEntry("ifctypeobject",&STEP::ObjectHelper<IfcTypeObject,2>::Construct ) +, SchemaEntry("ifctypeproduct",&STEP::ObjectHelper<IfcTypeProduct,2>::Construct ) +, SchemaEntry("ifcelementtype",&STEP::ObjectHelper<IfcElementType,1>::Construct ) +, SchemaEntry("ifcdistributionelementtype",&STEP::ObjectHelper<IfcDistributionElementType,0>::Construct ) +, SchemaEntry("ifcdistributioncontrolelementtype",&STEP::ObjectHelper<IfcDistributionControlElementType,0>::Construct ) +, SchemaEntry("ifcactuatortype",&STEP::ObjectHelper<IfcActuatorType,1>::Construct ) +, SchemaEntry("ifcaddress",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrepresentationitem",&STEP::ObjectHelper<IfcRepresentationItem,0>::Construct ) +, SchemaEntry("ifcgeometricrepresentationitem",&STEP::ObjectHelper<IfcGeometricRepresentationItem,0>::Construct ) +, SchemaEntry("ifcsolidmodel",&STEP::ObjectHelper<IfcSolidModel,0>::Construct ) +, SchemaEntry("ifcmanifoldsolidbrep",&STEP::ObjectHelper<IfcManifoldSolidBrep,1>::Construct ) +, SchemaEntry("ifcadvancedbrep",&STEP::ObjectHelper<IfcAdvancedBrep,0>::Construct ) +, SchemaEntry("ifcadvancedbrepwithvoids",&STEP::ObjectHelper<IfcAdvancedBrepWithVoids,1>::Construct ) +, SchemaEntry("ifctopologicalrepresentationitem",&STEP::ObjectHelper<IfcTopologicalRepresentationItem,0>::Construct ) +, SchemaEntry("ifcface",&STEP::ObjectHelper<IfcFace,1>::Construct ) +, SchemaEntry("ifcfacesurface",&STEP::ObjectHelper<IfcFaceSurface,2>::Construct ) +, SchemaEntry("ifcadvancedface",&STEP::ObjectHelper<IfcAdvancedFace,0>::Construct ) +, SchemaEntry("ifcdistributionflowelement",&STEP::ObjectHelper<IfcDistributionFlowElement,0>::Construct ) +, SchemaEntry("ifcflowterminal",&STEP::ObjectHelper<IfcFlowTerminal,0>::Construct ) +, SchemaEntry("ifcairterminal",&STEP::ObjectHelper<IfcAirTerminal,1>::Construct ) +, SchemaEntry("ifcflowcontroller",&STEP::ObjectHelper<IfcFlowController,0>::Construct ) +, SchemaEntry("ifcairterminalbox",&STEP::ObjectHelper<IfcAirTerminalBox,1>::Construct ) +, SchemaEntry("ifcdistributionflowelementtype",&STEP::ObjectHelper<IfcDistributionFlowElementType,0>::Construct ) +, SchemaEntry("ifcflowcontrollertype",&STEP::ObjectHelper<IfcFlowControllerType,0>::Construct ) +, SchemaEntry("ifcairterminalboxtype",&STEP::ObjectHelper<IfcAirTerminalBoxType,1>::Construct ) +, SchemaEntry("ifcflowterminaltype",&STEP::ObjectHelper<IfcFlowTerminalType,0>::Construct ) +, SchemaEntry("ifcairterminaltype",&STEP::ObjectHelper<IfcAirTerminalType,1>::Construct ) +, SchemaEntry("ifcenergyconversiondevice",&STEP::ObjectHelper<IfcEnergyConversionDevice,0>::Construct ) +, SchemaEntry("ifcairtoairheatrecovery",&STEP::ObjectHelper<IfcAirToAirHeatRecovery,1>::Construct ) +, SchemaEntry("ifcenergyconversiondevicetype",&STEP::ObjectHelper<IfcEnergyConversionDeviceType,0>::Construct ) +, SchemaEntry("ifcairtoairheatrecoverytype",&STEP::ObjectHelper<IfcAirToAirHeatRecoveryType,1>::Construct ) +, SchemaEntry("ifcalarm",&STEP::ObjectHelper<IfcAlarm,1>::Construct ) +, SchemaEntry("ifcalarmtype",&STEP::ObjectHelper<IfcAlarmType,1>::Construct ) +, SchemaEntry("ifcannotation",&STEP::ObjectHelper<IfcAnnotation,0>::Construct ) +, SchemaEntry("ifcannotationfillarea",&STEP::ObjectHelper<IfcAnnotationFillArea,2>::Construct ) +, SchemaEntry("ifcapplication",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcappliedvalue",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcapproval",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcresourcelevelrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcapprovalrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcprofiledef",&STEP::ObjectHelper<IfcProfileDef,2>::Construct ) +, SchemaEntry("ifcarbitraryclosedprofiledef",&STEP::ObjectHelper<IfcArbitraryClosedProfileDef,1>::Construct ) +, SchemaEntry("ifcarbitraryopenprofiledef",&STEP::ObjectHelper<IfcArbitraryOpenProfileDef,1>::Construct ) +, SchemaEntry("ifcarbitraryprofiledefwithvoids",&STEP::ObjectHelper<IfcArbitraryProfileDefWithVoids,1>::Construct ) +, SchemaEntry("ifcgroup",&STEP::ObjectHelper<IfcGroup,0>::Construct ) +, SchemaEntry("ifcasset",&STEP::ObjectHelper<IfcAsset,9>::Construct ) +, SchemaEntry("ifcparameterizedprofiledef",&STEP::ObjectHelper<IfcParameterizedProfileDef,1>::Construct ) +, SchemaEntry("ifcasymmetricishapeprofiledef",&STEP::ObjectHelper<IfcAsymmetricIShapeProfileDef,12>::Construct ) +, SchemaEntry("ifcaudiovisualappliance",&STEP::ObjectHelper<IfcAudioVisualAppliance,1>::Construct ) +, SchemaEntry("ifcaudiovisualappliancetype",&STEP::ObjectHelper<IfcAudioVisualApplianceType,1>::Construct ) +, SchemaEntry("ifcplacement",&STEP::ObjectHelper<IfcPlacement,1>::Construct ) +, SchemaEntry("ifcaxis1placement",&STEP::ObjectHelper<IfcAxis1Placement,1>::Construct ) +, SchemaEntry("ifcaxis2placement2d",&STEP::ObjectHelper<IfcAxis2Placement2D,1>::Construct ) +, SchemaEntry("ifcaxis2placement3d",&STEP::ObjectHelper<IfcAxis2Placement3D,2>::Construct ) +, SchemaEntry("ifccurve",&STEP::ObjectHelper<IfcCurve,0>::Construct ) +, SchemaEntry("ifcboundedcurve",&STEP::ObjectHelper<IfcBoundedCurve,0>::Construct ) +, SchemaEntry("ifcbsplinecurve",&STEP::ObjectHelper<IfcBSplineCurve,5>::Construct ) +, SchemaEntry("ifcbsplinecurvewithknots",&STEP::ObjectHelper<IfcBSplineCurveWithKnots,3>::Construct ) +, SchemaEntry("ifcsurface",&STEP::ObjectHelper<IfcSurface,0>::Construct ) +, SchemaEntry("ifcboundedsurface",&STEP::ObjectHelper<IfcBoundedSurface,0>::Construct ) +, SchemaEntry("ifcbsplinesurface",&STEP::ObjectHelper<IfcBSplineSurface,6>::Construct ) +, SchemaEntry("ifcbsplinesurfacewithknots",&STEP::ObjectHelper<IfcBSplineSurfaceWithKnots,5>::Construct ) +, SchemaEntry("ifcbuildingelement",&STEP::ObjectHelper<IfcBuildingElement,0>::Construct ) +, SchemaEntry("ifcbeam",&STEP::ObjectHelper<IfcBeam,1>::Construct ) +, SchemaEntry("ifcbeamstandardcase",&STEP::ObjectHelper<IfcBeamStandardCase,0>::Construct ) +, SchemaEntry("ifcbuildingelementtype",&STEP::ObjectHelper<IfcBuildingElementType,0>::Construct ) +, SchemaEntry("ifcbeamtype",&STEP::ObjectHelper<IfcBeamType,1>::Construct ) +, SchemaEntry("ifcpresentationitem",&STEP::ObjectHelper<IfcPresentationItem,0>::Construct ) +, SchemaEntry("ifcsurfacetexture",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcblobtexture",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccsgprimitive3d",&STEP::ObjectHelper<IfcCsgPrimitive3D,1>::Construct ) +, SchemaEntry("ifcblock",&STEP::ObjectHelper<IfcBlock,3>::Construct ) +, SchemaEntry("ifcboiler",&STEP::ObjectHelper<IfcBoiler,1>::Construct ) +, SchemaEntry("ifcboilertype",&STEP::ObjectHelper<IfcBoilerType,1>::Construct ) +, SchemaEntry("ifcbooleanresult",&STEP::ObjectHelper<IfcBooleanResult,3>::Construct ) +, SchemaEntry("ifcbooleanclippingresult",&STEP::ObjectHelper<IfcBooleanClippingResult,0>::Construct ) +, SchemaEntry("ifcboundarycondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccompositecurve",&STEP::ObjectHelper<IfcCompositeCurve,2>::Construct ) +, SchemaEntry("ifccompositecurveonsurface",&STEP::ObjectHelper<IfcCompositeCurveOnSurface,0>::Construct ) +, SchemaEntry("ifcboundarycurve",&STEP::ObjectHelper<IfcBoundaryCurve,0>::Construct ) +, SchemaEntry("ifcboundaryedgecondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcboundaryfacecondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcboundarynodecondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcboundarynodeconditionwarping",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcboundingbox",&STEP::ObjectHelper<IfcBoundingBox,4>::Construct ) +, SchemaEntry("ifchalfspacesolid",&STEP::ObjectHelper<IfcHalfSpaceSolid,2>::Construct ) +, SchemaEntry("ifcboxedhalfspace",&STEP::ObjectHelper<IfcBoxedHalfSpace,1>::Construct ) +, SchemaEntry("ifcspatialelement",&STEP::ObjectHelper<IfcSpatialElement,1>::Construct ) +, SchemaEntry("ifcspatialstructureelement",&STEP::ObjectHelper<IfcSpatialStructureElement,1>::Construct ) +, SchemaEntry("ifcbuilding",&STEP::ObjectHelper<IfcBuilding,3>::Construct ) +, SchemaEntry("ifcelementcomponent",&STEP::ObjectHelper<IfcElementComponent,0>::Construct ) +, SchemaEntry("ifcbuildingelementpart",&STEP::ObjectHelper<IfcBuildingElementPart,1>::Construct ) +, SchemaEntry("ifcelementcomponenttype",&STEP::ObjectHelper<IfcElementComponentType,0>::Construct ) +, SchemaEntry("ifcbuildingelementparttype",&STEP::ObjectHelper<IfcBuildingElementPartType,1>::Construct ) +, SchemaEntry("ifcbuildingelementproxy",&STEP::ObjectHelper<IfcBuildingElementProxy,1>::Construct ) +, SchemaEntry("ifcbuildingelementproxytype",&STEP::ObjectHelper<IfcBuildingElementProxyType,1>::Construct ) +, SchemaEntry("ifcbuildingstorey",&STEP::ObjectHelper<IfcBuildingStorey,1>::Construct ) +, SchemaEntry("ifcsystem",&STEP::ObjectHelper<IfcSystem,0>::Construct ) +, SchemaEntry("ifcbuildingsystem",&STEP::ObjectHelper<IfcBuildingSystem,2>::Construct ) +, SchemaEntry("ifcburner",&STEP::ObjectHelper<IfcBurner,1>::Construct ) +, SchemaEntry("ifcburnertype",&STEP::ObjectHelper<IfcBurnerType,1>::Construct ) +, SchemaEntry("ifccshapeprofiledef",&STEP::ObjectHelper<IfcCShapeProfileDef,5>::Construct ) +, SchemaEntry("ifcflowfitting",&STEP::ObjectHelper<IfcFlowFitting,0>::Construct ) +, SchemaEntry("ifccablecarrierfitting",&STEP::ObjectHelper<IfcCableCarrierFitting,1>::Construct ) +, SchemaEntry("ifcflowfittingtype",&STEP::ObjectHelper<IfcFlowFittingType,0>::Construct ) +, SchemaEntry("ifccablecarrierfittingtype",&STEP::ObjectHelper<IfcCableCarrierFittingType,1>::Construct ) +, SchemaEntry("ifcflowsegment",&STEP::ObjectHelper<IfcFlowSegment,0>::Construct ) +, SchemaEntry("ifccablecarriersegment",&STEP::ObjectHelper<IfcCableCarrierSegment,1>::Construct ) +, SchemaEntry("ifcflowsegmenttype",&STEP::ObjectHelper<IfcFlowSegmentType,0>::Construct ) +, SchemaEntry("ifccablecarriersegmenttype",&STEP::ObjectHelper<IfcCableCarrierSegmentType,1>::Construct ) +, SchemaEntry("ifccablefitting",&STEP::ObjectHelper<IfcCableFitting,1>::Construct ) +, SchemaEntry("ifccablefittingtype",&STEP::ObjectHelper<IfcCableFittingType,1>::Construct ) +, SchemaEntry("ifccablesegment",&STEP::ObjectHelper<IfcCableSegment,1>::Construct ) +, SchemaEntry("ifccablesegmenttype",&STEP::ObjectHelper<IfcCableSegmentType,1>::Construct ) +, SchemaEntry("ifcpoint",&STEP::ObjectHelper<IfcPoint,0>::Construct ) +, SchemaEntry("ifccartesianpoint",&STEP::ObjectHelper<IfcCartesianPoint,1>::Construct ) +, SchemaEntry("ifccartesianpointlist",&STEP::ObjectHelper<IfcCartesianPointList,0>::Construct ) +, SchemaEntry("ifccartesianpointlist2d",&STEP::ObjectHelper<IfcCartesianPointList2D,0>::Construct ) +, SchemaEntry("ifccartesianpointlist3d",&STEP::ObjectHelper<IfcCartesianPointList3D,0>::Construct ) +, SchemaEntry("ifccartesiantransformationoperator",&STEP::ObjectHelper<IfcCartesianTransformationOperator,4>::Construct ) +, SchemaEntry("ifccartesiantransformationoperator2d",&STEP::ObjectHelper<IfcCartesianTransformationOperator2D,0>::Construct ) +, SchemaEntry("ifccartesiantransformationoperator2dnonuniform",&STEP::ObjectHelper<IfcCartesianTransformationOperator2DnonUniform,1>::Construct ) +, SchemaEntry("ifccartesiantransformationoperator3d",&STEP::ObjectHelper<IfcCartesianTransformationOperator3D,1>::Construct ) +, SchemaEntry("ifccartesiantransformationoperator3dnonuniform",&STEP::ObjectHelper<IfcCartesianTransformationOperator3DnonUniform,2>::Construct ) +, SchemaEntry("ifccenterlineprofiledef",&STEP::ObjectHelper<IfcCenterLineProfileDef,1>::Construct ) +, SchemaEntry("ifcchiller",&STEP::ObjectHelper<IfcChiller,1>::Construct ) +, SchemaEntry("ifcchillertype",&STEP::ObjectHelper<IfcChillerType,1>::Construct ) +, SchemaEntry("ifcchimney",&STEP::ObjectHelper<IfcChimney,1>::Construct ) +, SchemaEntry("ifcchimneytype",&STEP::ObjectHelper<IfcChimneyType,1>::Construct ) +, SchemaEntry("ifcconic",&STEP::ObjectHelper<IfcConic,1>::Construct ) +, SchemaEntry("ifccircle",&STEP::ObjectHelper<IfcCircle,1>::Construct ) +, SchemaEntry("ifccircleprofiledef",&STEP::ObjectHelper<IfcCircleProfileDef,1>::Construct ) +, SchemaEntry("ifccirclehollowprofiledef",&STEP::ObjectHelper<IfcCircleHollowProfileDef,1>::Construct ) +, SchemaEntry("ifccivilelement",&STEP::ObjectHelper<IfcCivilElement,0>::Construct ) +, SchemaEntry("ifccivilelementtype",&STEP::ObjectHelper<IfcCivilElementType,0>::Construct ) +, SchemaEntry("ifcexternalinformation",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcclassification",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcexternalreference",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcclassificationreference",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconnectedfaceset",&STEP::ObjectHelper<IfcConnectedFaceSet,1>::Construct ) +, SchemaEntry("ifcclosedshell",&STEP::ObjectHelper<IfcClosedShell,0>::Construct ) +, SchemaEntry("ifccoil",&STEP::ObjectHelper<IfcCoil,1>::Construct ) +, SchemaEntry("ifccoiltype",&STEP::ObjectHelper<IfcCoilType,1>::Construct ) +, SchemaEntry("ifccolourspecification",&STEP::ObjectHelper<IfcColourSpecification,1>::Construct ) +, SchemaEntry("ifccolourrgb",&STEP::ObjectHelper<IfcColourRgb,3>::Construct ) +, SchemaEntry("ifccolourrgblist",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccolumn",&STEP::ObjectHelper<IfcColumn,1>::Construct ) +, SchemaEntry("ifccolumnstandardcase",&STEP::ObjectHelper<IfcColumnStandardCase,0>::Construct ) +, SchemaEntry("ifccolumntype",&STEP::ObjectHelper<IfcColumnType,1>::Construct ) +, SchemaEntry("ifccommunicationsappliance",&STEP::ObjectHelper<IfcCommunicationsAppliance,1>::Construct ) +, SchemaEntry("ifccommunicationsappliancetype",&STEP::ObjectHelper<IfcCommunicationsApplianceType,1>::Construct ) +, SchemaEntry("ifcpropertyabstraction",&STEP::ObjectHelper<IfcPropertyAbstraction,0>::Construct ) +, SchemaEntry("ifcproperty",&STEP::ObjectHelper<IfcProperty,2>::Construct ) +, SchemaEntry("ifccomplexproperty",&STEP::ObjectHelper<IfcComplexProperty,2>::Construct ) +, SchemaEntry("ifcpropertydefinition",&STEP::ObjectHelper<IfcPropertyDefinition,0>::Construct ) +, SchemaEntry("ifcpropertytemplatedefinition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpropertytemplate",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccomplexpropertytemplate",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccompositecurvesegment",&STEP::ObjectHelper<IfcCompositeCurveSegment,3>::Construct ) +, SchemaEntry("ifccompositeprofiledef",&STEP::ObjectHelper<IfcCompositeProfileDef,2>::Construct ) +, SchemaEntry("ifcflowmovingdevice",&STEP::ObjectHelper<IfcFlowMovingDevice,0>::Construct ) +, SchemaEntry("ifccompressor",&STEP::ObjectHelper<IfcCompressor,1>::Construct ) +, SchemaEntry("ifcflowmovingdevicetype",&STEP::ObjectHelper<IfcFlowMovingDeviceType,0>::Construct ) +, SchemaEntry("ifccompressortype",&STEP::ObjectHelper<IfcCompressorType,1>::Construct ) +, SchemaEntry("ifccondenser",&STEP::ObjectHelper<IfcCondenser,1>::Construct ) +, SchemaEntry("ifccondensertype",&STEP::ObjectHelper<IfcCondenserType,1>::Construct ) +, SchemaEntry("ifcconnectiongeometry",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconnectioncurvegeometry",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconnectionpointgeometry",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconnectionpointeccentricity",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconnectionsurfacegeometry",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconnectionvolumegeometry",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcconstraint",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcresource",&STEP::ObjectHelper<IfcResource,2>::Construct ) +, SchemaEntry("ifcconstructionresource",&STEP::ObjectHelper<IfcConstructionResource,3>::Construct ) +, SchemaEntry("ifcconstructionequipmentresource",&STEP::ObjectHelper<IfcConstructionEquipmentResource,1>::Construct ) +, SchemaEntry("ifctyperesource",&STEP::ObjectHelper<IfcTypeResource,3>::Construct ) +, SchemaEntry("ifcconstructionresourcetype",&STEP::ObjectHelper<IfcConstructionResourceType,2>::Construct ) +, SchemaEntry("ifcconstructionequipmentresourcetype",&STEP::ObjectHelper<IfcConstructionEquipmentResourceType,1>::Construct ) +, SchemaEntry("ifcconstructionmaterialresource",&STEP::ObjectHelper<IfcConstructionMaterialResource,1>::Construct ) +, SchemaEntry("ifcconstructionmaterialresourcetype",&STEP::ObjectHelper<IfcConstructionMaterialResourceType,1>::Construct ) +, SchemaEntry("ifcconstructionproductresource",&STEP::ObjectHelper<IfcConstructionProductResource,1>::Construct ) +, SchemaEntry("ifcconstructionproductresourcetype",&STEP::ObjectHelper<IfcConstructionProductResourceType,1>::Construct ) +, SchemaEntry("ifccontext",&STEP::ObjectHelper<IfcContext,5>::Construct ) +, SchemaEntry("ifcnamedunit",&STEP::ObjectHelper<IfcNamedUnit,2>::Construct ) +, SchemaEntry("ifccontextdependentunit",&STEP::ObjectHelper<IfcContextDependentUnit,1>::Construct ) +, SchemaEntry("ifccontroller",&STEP::ObjectHelper<IfcController,1>::Construct ) +, SchemaEntry("ifccontrollertype",&STEP::ObjectHelper<IfcControllerType,1>::Construct ) +, SchemaEntry("ifcconversionbasedunit",&STEP::ObjectHelper<IfcConversionBasedUnit,2>::Construct ) +, SchemaEntry("ifcconversionbasedunitwithoffset",&STEP::ObjectHelper<IfcConversionBasedUnitWithOffset,1>::Construct ) +, SchemaEntry("ifccooledbeam",&STEP::ObjectHelper<IfcCooledBeam,1>::Construct ) +, SchemaEntry("ifccooledbeamtype",&STEP::ObjectHelper<IfcCooledBeamType,1>::Construct ) +, SchemaEntry("ifccoolingtower",&STEP::ObjectHelper<IfcCoolingTower,1>::Construct ) +, SchemaEntry("ifccoolingtowertype",&STEP::ObjectHelper<IfcCoolingTowerType,1>::Construct ) +, SchemaEntry("ifccoordinateoperation",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccoordinatereferencesystem",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccostitem",&STEP::ObjectHelper<IfcCostItem,3>::Construct ) +, SchemaEntry("ifccostschedule",&STEP::ObjectHelper<IfcCostSchedule,4>::Construct ) +, SchemaEntry("ifccostvalue",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccovering",&STEP::ObjectHelper<IfcCovering,1>::Construct ) +, SchemaEntry("ifccoveringtype",&STEP::ObjectHelper<IfcCoveringType,1>::Construct ) +, SchemaEntry("ifccrewresource",&STEP::ObjectHelper<IfcCrewResource,1>::Construct ) +, SchemaEntry("ifccrewresourcetype",&STEP::ObjectHelper<IfcCrewResourceType,1>::Construct ) +, SchemaEntry("ifccsgsolid",&STEP::ObjectHelper<IfcCsgSolid,1>::Construct ) +, SchemaEntry("ifccurrencyrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccurtainwall",&STEP::ObjectHelper<IfcCurtainWall,1>::Construct ) +, SchemaEntry("ifccurtainwalltype",&STEP::ObjectHelper<IfcCurtainWallType,1>::Construct ) +, SchemaEntry("ifccurveboundedplane",&STEP::ObjectHelper<IfcCurveBoundedPlane,3>::Construct ) +, SchemaEntry("ifccurveboundedsurface",&STEP::ObjectHelper<IfcCurveBoundedSurface,3>::Construct ) +, SchemaEntry("ifcpresentationstyle",&STEP::ObjectHelper<IfcPresentationStyle,1>::Construct ) +, SchemaEntry("ifccurvestyle",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccurvestylefont",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccurvestylefontandscaling",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifccurvestylefontpattern",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcelementarysurface",&STEP::ObjectHelper<IfcElementarySurface,1>::Construct ) +, SchemaEntry("ifccylindricalsurface",&STEP::ObjectHelper<IfcCylindricalSurface,1>::Construct ) +, SchemaEntry("ifcdamper",&STEP::ObjectHelper<IfcDamper,1>::Construct ) +, SchemaEntry("ifcdampertype",&STEP::ObjectHelper<IfcDamperType,1>::Construct ) +, SchemaEntry("ifcderivedprofiledef",&STEP::ObjectHelper<IfcDerivedProfileDef,3>::Construct ) +, SchemaEntry("ifcderivedunit",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcderivedunitelement",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdimensionalexponents",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdirection",&STEP::ObjectHelper<IfcDirection,1>::Construct ) +, SchemaEntry("ifcdiscreteaccessory",&STEP::ObjectHelper<IfcDiscreteAccessory,1>::Construct ) +, SchemaEntry("ifcdiscreteaccessorytype",&STEP::ObjectHelper<IfcDiscreteAccessoryType,1>::Construct ) +, SchemaEntry("ifcdistributionchamberelement",&STEP::ObjectHelper<IfcDistributionChamberElement,1>::Construct ) +, SchemaEntry("ifcdistributionchamberelementtype",&STEP::ObjectHelper<IfcDistributionChamberElementType,1>::Construct ) +, SchemaEntry("ifcdistributionsystem",&STEP::ObjectHelper<IfcDistributionSystem,2>::Construct ) +, SchemaEntry("ifcdistributioncircuit",&STEP::ObjectHelper<IfcDistributionCircuit,0>::Construct ) +, SchemaEntry("ifcport",&STEP::ObjectHelper<IfcPort,0>::Construct ) +, SchemaEntry("ifcdistributionport",&STEP::ObjectHelper<IfcDistributionPort,3>::Construct ) +, SchemaEntry("ifcdocumentinformation",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdocumentinformationrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdocumentreference",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdoor",&STEP::ObjectHelper<IfcDoor,5>::Construct ) +, SchemaEntry("ifcpropertysetdefinition",&STEP::ObjectHelper<IfcPropertySetDefinition,0>::Construct ) +, SchemaEntry("ifcpredefinedpropertyset",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdoorliningproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdoorpanelproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdoorstandardcase",&STEP::ObjectHelper<IfcDoorStandardCase,0>::Construct ) +, SchemaEntry("ifcdoorstyle",&STEP::ObjectHelper<IfcDoorStyle,4>::Construct ) +, SchemaEntry("ifcdoortype",&STEP::ObjectHelper<IfcDoorType,4>::Construct ) +, SchemaEntry("ifcpredefineditem",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpredefinedcolour",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdraughtingpredefinedcolour",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpredefinedcurvefont",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcdraughtingpredefinedcurvefont",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcductfitting",&STEP::ObjectHelper<IfcDuctFitting,1>::Construct ) +, SchemaEntry("ifcductfittingtype",&STEP::ObjectHelper<IfcDuctFittingType,1>::Construct ) +, SchemaEntry("ifcductsegment",&STEP::ObjectHelper<IfcDuctSegment,1>::Construct ) +, SchemaEntry("ifcductsegmenttype",&STEP::ObjectHelper<IfcDuctSegmentType,1>::Construct ) +, SchemaEntry("ifcflowtreatmentdevice",&STEP::ObjectHelper<IfcFlowTreatmentDevice,0>::Construct ) +, SchemaEntry("ifcductsilencer",&STEP::ObjectHelper<IfcDuctSilencer,1>::Construct ) +, SchemaEntry("ifcflowtreatmentdevicetype",&STEP::ObjectHelper<IfcFlowTreatmentDeviceType,0>::Construct ) +, SchemaEntry("ifcductsilencertype",&STEP::ObjectHelper<IfcDuctSilencerType,1>::Construct ) +, SchemaEntry("ifcedge",&STEP::ObjectHelper<IfcEdge,2>::Construct ) +, SchemaEntry("ifcedgecurve",&STEP::ObjectHelper<IfcEdgeCurve,2>::Construct ) +, SchemaEntry("ifcloop",&STEP::ObjectHelper<IfcLoop,0>::Construct ) +, SchemaEntry("ifcedgeloop",&STEP::ObjectHelper<IfcEdgeLoop,1>::Construct ) +, SchemaEntry("ifcelectricappliance",&STEP::ObjectHelper<IfcElectricAppliance,1>::Construct ) +, SchemaEntry("ifcelectricappliancetype",&STEP::ObjectHelper<IfcElectricApplianceType,1>::Construct ) +, SchemaEntry("ifcelectricdistributionboard",&STEP::ObjectHelper<IfcElectricDistributionBoard,1>::Construct ) +, SchemaEntry("ifcelectricdistributionboardtype",&STEP::ObjectHelper<IfcElectricDistributionBoardType,1>::Construct ) +, SchemaEntry("ifcflowstoragedevice",&STEP::ObjectHelper<IfcFlowStorageDevice,0>::Construct ) +, SchemaEntry("ifcelectricflowstoragedevice",&STEP::ObjectHelper<IfcElectricFlowStorageDevice,1>::Construct ) +, SchemaEntry("ifcflowstoragedevicetype",&STEP::ObjectHelper<IfcFlowStorageDeviceType,0>::Construct ) +, SchemaEntry("ifcelectricflowstoragedevicetype",&STEP::ObjectHelper<IfcElectricFlowStorageDeviceType,1>::Construct ) +, SchemaEntry("ifcelectricgenerator",&STEP::ObjectHelper<IfcElectricGenerator,1>::Construct ) +, SchemaEntry("ifcelectricgeneratortype",&STEP::ObjectHelper<IfcElectricGeneratorType,1>::Construct ) +, SchemaEntry("ifcelectricmotor",&STEP::ObjectHelper<IfcElectricMotor,1>::Construct ) +, SchemaEntry("ifcelectricmotortype",&STEP::ObjectHelper<IfcElectricMotorType,1>::Construct ) +, SchemaEntry("ifcelectrictimecontrol",&STEP::ObjectHelper<IfcElectricTimeControl,1>::Construct ) +, SchemaEntry("ifcelectrictimecontroltype",&STEP::ObjectHelper<IfcElectricTimeControlType,1>::Construct ) +, SchemaEntry("ifcelementassembly",&STEP::ObjectHelper<IfcElementAssembly,2>::Construct ) +, SchemaEntry("ifcelementassemblytype",&STEP::ObjectHelper<IfcElementAssemblyType,1>::Construct ) +, SchemaEntry("ifcquantityset",&STEP::ObjectHelper<IfcQuantitySet,0>::Construct ) +, SchemaEntry("ifcelementquantity",&STEP::ObjectHelper<IfcElementQuantity,2>::Construct ) +, SchemaEntry("ifcellipse",&STEP::ObjectHelper<IfcEllipse,2>::Construct ) +, SchemaEntry("ifcellipseprofiledef",&STEP::ObjectHelper<IfcEllipseProfileDef,2>::Construct ) +, SchemaEntry("ifcengine",&STEP::ObjectHelper<IfcEngine,1>::Construct ) +, SchemaEntry("ifcenginetype",&STEP::ObjectHelper<IfcEngineType,1>::Construct ) +, SchemaEntry("ifcevaporativecooler",&STEP::ObjectHelper<IfcEvaporativeCooler,1>::Construct ) +, SchemaEntry("ifcevaporativecoolertype",&STEP::ObjectHelper<IfcEvaporativeCoolerType,1>::Construct ) +, SchemaEntry("ifcevaporator",&STEP::ObjectHelper<IfcEvaporator,1>::Construct ) +, SchemaEntry("ifcevaporatortype",&STEP::ObjectHelper<IfcEvaporatorType,1>::Construct ) +, SchemaEntry("ifcprocess",&STEP::ObjectHelper<IfcProcess,2>::Construct ) +, SchemaEntry("ifcevent",&STEP::ObjectHelper<IfcEvent,4>::Construct ) +, SchemaEntry("ifcschedulingtime",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifceventtime",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctypeprocess",&STEP::ObjectHelper<IfcTypeProcess,3>::Construct ) +, SchemaEntry("ifceventtype",&STEP::ObjectHelper<IfcEventType,3>::Construct ) +, SchemaEntry("ifcextendedproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcexternalreferencerelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcexternalspatialstructureelement",&STEP::ObjectHelper<IfcExternalSpatialStructureElement,0>::Construct ) +, SchemaEntry("ifcexternalspatialelement",&STEP::ObjectHelper<IfcExternalSpatialElement,1>::Construct ) +, SchemaEntry("ifcexternallydefinedhatchstyle",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcexternallydefinedsurfacestyle",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcexternallydefinedtextfont",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsweptareasolid",&STEP::ObjectHelper<IfcSweptAreaSolid,2>::Construct ) +, SchemaEntry("ifcextrudedareasolid",&STEP::ObjectHelper<IfcExtrudedAreaSolid,2>::Construct ) +, SchemaEntry("ifcextrudedareasolidtapered",&STEP::ObjectHelper<IfcExtrudedAreaSolidTapered,1>::Construct ) +, SchemaEntry("ifcfacebasedsurfacemodel",&STEP::ObjectHelper<IfcFaceBasedSurfaceModel,1>::Construct ) +, SchemaEntry("ifcfacebound",&STEP::ObjectHelper<IfcFaceBound,2>::Construct ) +, SchemaEntry("ifcfaceouterbound",&STEP::ObjectHelper<IfcFaceOuterBound,0>::Construct ) +, SchemaEntry("ifcfacetedbrep",&STEP::ObjectHelper<IfcFacetedBrep,0>::Construct ) +, SchemaEntry("ifcfacetedbrepwithvoids",&STEP::ObjectHelper<IfcFacetedBrepWithVoids,1>::Construct ) +, SchemaEntry("ifcstructuralconnectioncondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcfailureconnectioncondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcfan",&STEP::ObjectHelper<IfcFan,1>::Construct ) +, SchemaEntry("ifcfantype",&STEP::ObjectHelper<IfcFanType,1>::Construct ) +, SchemaEntry("ifcfastener",&STEP::ObjectHelper<IfcFastener,1>::Construct ) +, SchemaEntry("ifcfastenertype",&STEP::ObjectHelper<IfcFastenerType,1>::Construct ) +, SchemaEntry("ifcfeatureelement",&STEP::ObjectHelper<IfcFeatureElement,0>::Construct ) +, SchemaEntry("ifcfeatureelementaddition",&STEP::ObjectHelper<IfcFeatureElementAddition,0>::Construct ) +, SchemaEntry("ifcfeatureelementsubtraction",&STEP::ObjectHelper<IfcFeatureElementSubtraction,0>::Construct ) +, SchemaEntry("ifcfillareastyle",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcfillareastylehatching",&STEP::ObjectHelper<IfcFillAreaStyleHatching,5>::Construct ) +, SchemaEntry("ifcfillareastyletiles",&STEP::ObjectHelper<IfcFillAreaStyleTiles,3>::Construct ) +, SchemaEntry("ifcfilter",&STEP::ObjectHelper<IfcFilter,1>::Construct ) +, SchemaEntry("ifcfiltertype",&STEP::ObjectHelper<IfcFilterType,1>::Construct ) +, SchemaEntry("ifcfiresuppressionterminal",&STEP::ObjectHelper<IfcFireSuppressionTerminal,1>::Construct ) +, SchemaEntry("ifcfiresuppressionterminaltype",&STEP::ObjectHelper<IfcFireSuppressionTerminalType,1>::Construct ) +, SchemaEntry("ifcfixedreferencesweptareasolid",&STEP::ObjectHelper<IfcFixedReferenceSweptAreaSolid,4>::Construct ) +, SchemaEntry("ifcflowinstrument",&STEP::ObjectHelper<IfcFlowInstrument,1>::Construct ) +, SchemaEntry("ifcflowinstrumenttype",&STEP::ObjectHelper<IfcFlowInstrumentType,1>::Construct ) +, SchemaEntry("ifcflowmeter",&STEP::ObjectHelper<IfcFlowMeter,1>::Construct ) +, SchemaEntry("ifcflowmetertype",&STEP::ObjectHelper<IfcFlowMeterType,1>::Construct ) +, SchemaEntry("ifcfooting",&STEP::ObjectHelper<IfcFooting,1>::Construct ) +, SchemaEntry("ifcfootingtype",&STEP::ObjectHelper<IfcFootingType,1>::Construct ) +, SchemaEntry("ifcfurnishingelement",&STEP::ObjectHelper<IfcFurnishingElement,0>::Construct ) +, SchemaEntry("ifcfurnishingelementtype",&STEP::ObjectHelper<IfcFurnishingElementType,0>::Construct ) +, SchemaEntry("ifcfurniture",&STEP::ObjectHelper<IfcFurniture,1>::Construct ) +, SchemaEntry("ifcfurnituretype",&STEP::ObjectHelper<IfcFurnitureType,2>::Construct ) +, SchemaEntry("ifcgeographicelement",&STEP::ObjectHelper<IfcGeographicElement,1>::Construct ) +, SchemaEntry("ifcgeographicelementtype",&STEP::ObjectHelper<IfcGeographicElementType,1>::Construct ) +, SchemaEntry("ifcgeometricset",&STEP::ObjectHelper<IfcGeometricSet,1>::Construct ) +, SchemaEntry("ifcgeometriccurveset",&STEP::ObjectHelper<IfcGeometricCurveSet,0>::Construct ) +, SchemaEntry("ifcrepresentationcontext",&STEP::ObjectHelper<IfcRepresentationContext,2>::Construct ) +, SchemaEntry("ifcgeometricrepresentationcontext",&STEP::ObjectHelper<IfcGeometricRepresentationContext,4>::Construct ) +, SchemaEntry("ifcgeometricrepresentationsubcontext",&STEP::ObjectHelper<IfcGeometricRepresentationSubContext,4>::Construct ) +, SchemaEntry("ifcgrid",&STEP::ObjectHelper<IfcGrid,4>::Construct ) +, SchemaEntry("ifcgridaxis",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcobjectplacement",&STEP::ObjectHelper<IfcObjectPlacement,0>::Construct ) +, SchemaEntry("ifcgridplacement",&STEP::ObjectHelper<IfcGridPlacement,2>::Construct ) +, SchemaEntry("ifcheatexchanger",&STEP::ObjectHelper<IfcHeatExchanger,1>::Construct ) +, SchemaEntry("ifcheatexchangertype",&STEP::ObjectHelper<IfcHeatExchangerType,1>::Construct ) +, SchemaEntry("ifchumidifier",&STEP::ObjectHelper<IfcHumidifier,1>::Construct ) +, SchemaEntry("ifchumidifiertype",&STEP::ObjectHelper<IfcHumidifierType,1>::Construct ) +, SchemaEntry("ifcishapeprofiledef",&STEP::ObjectHelper<IfcIShapeProfileDef,7>::Construct ) +, SchemaEntry("ifcimagetexture",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcindexedcolourmap",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcindexedpolycurve",&STEP::ObjectHelper<IfcIndexedPolyCurve,3>::Construct ) +, SchemaEntry("ifctessellateditem",&STEP::ObjectHelper<IfcTessellatedItem,0>::Construct ) +, SchemaEntry("ifcindexedpolygonalface",&STEP::ObjectHelper<IfcIndexedPolygonalFace,1>::Construct ) +, SchemaEntry("ifcindexedpolygonalfacewithvoids",&STEP::ObjectHelper<IfcIndexedPolygonalFaceWithVoids,0>::Construct ) +, SchemaEntry("ifctexturecoordinate",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcindexedtexturemap",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcindexedtriangletexturemap",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcinterceptor",&STEP::ObjectHelper<IfcInterceptor,1>::Construct ) +, SchemaEntry("ifcinterceptortype",&STEP::ObjectHelper<IfcInterceptorType,1>::Construct ) +, SchemaEntry("ifcsurfacecurve",&STEP::ObjectHelper<IfcSurfaceCurve,3>::Construct ) +, SchemaEntry("ifcintersectioncurve",&STEP::ObjectHelper<IfcIntersectionCurve,0>::Construct ) +, SchemaEntry("ifcinventory",&STEP::ObjectHelper<IfcInventory,6>::Construct ) +, SchemaEntry("ifctimeseries",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcirregulartimeseries",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcirregulartimeseriesvalue",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcjunctionbox",&STEP::ObjectHelper<IfcJunctionBox,1>::Construct ) +, SchemaEntry("ifcjunctionboxtype",&STEP::ObjectHelper<IfcJunctionBoxType,1>::Construct ) +, SchemaEntry("ifclshapeprofiledef",&STEP::ObjectHelper<IfcLShapeProfileDef,6>::Construct ) +, SchemaEntry("ifclaborresource",&STEP::ObjectHelper<IfcLaborResource,1>::Construct ) +, SchemaEntry("ifclaborresourcetype",&STEP::ObjectHelper<IfcLaborResourceType,1>::Construct ) +, SchemaEntry("ifclagtime",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifclamp",&STEP::ObjectHelper<IfcLamp,1>::Construct ) +, SchemaEntry("ifclamptype",&STEP::ObjectHelper<IfcLampType,1>::Construct ) +, SchemaEntry("ifclibraryinformation",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifclibraryreference",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifclightdistributiondata",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifclightfixture",&STEP::ObjectHelper<IfcLightFixture,1>::Construct ) +, SchemaEntry("ifclightfixturetype",&STEP::ObjectHelper<IfcLightFixtureType,1>::Construct ) +, SchemaEntry("ifclightintensitydistribution",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifclightsource",&STEP::ObjectHelper<IfcLightSource,4>::Construct ) +, SchemaEntry("ifclightsourceambient",&STEP::ObjectHelper<IfcLightSourceAmbient,0>::Construct ) +, SchemaEntry("ifclightsourcedirectional",&STEP::ObjectHelper<IfcLightSourceDirectional,1>::Construct ) +, SchemaEntry("ifclightsourcegoniometric",&STEP::ObjectHelper<IfcLightSourceGoniometric,6>::Construct ) +, SchemaEntry("ifclightsourcepositional",&STEP::ObjectHelper<IfcLightSourcePositional,5>::Construct ) +, SchemaEntry("ifclightsourcespot",&STEP::ObjectHelper<IfcLightSourceSpot,4>::Construct ) +, SchemaEntry("ifcline",&STEP::ObjectHelper<IfcLine,2>::Construct ) +, SchemaEntry("ifclocalplacement",&STEP::ObjectHelper<IfcLocalPlacement,2>::Construct ) +, SchemaEntry("ifcmapconversion",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmappeditem",&STEP::ObjectHelper<IfcMappedItem,2>::Construct ) +, SchemaEntry("ifcmaterialdefinition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmaterial",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmaterialclassificationrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmaterialconstituent",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmaterialconstituentset",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcproductrepresentation",&STEP::ObjectHelper<IfcProductRepresentation,3>::Construct ) +, SchemaEntry("ifcmaterialdefinitionrepresentation",&STEP::ObjectHelper<IfcMaterialDefinitionRepresentation,1>::Construct ) +, SchemaEntry("ifcmateriallayer",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmateriallayerset",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmaterialusagedefinition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmateriallayersetusage",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmateriallayerwithoffsets",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmateriallist",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmaterialprofile",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmaterialprofileset",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmaterialprofilesetusage",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmaterialprofilesetusagetapering",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmaterialprofilewithoffsets",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmaterialproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmaterialrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmeasurewithunit",&STEP::ObjectHelper<IfcMeasureWithUnit,2>::Construct ) +, SchemaEntry("ifcmechanicalfastener",&STEP::ObjectHelper<IfcMechanicalFastener,3>::Construct ) +, SchemaEntry("ifcmechanicalfastenertype",&STEP::ObjectHelper<IfcMechanicalFastenerType,3>::Construct ) +, SchemaEntry("ifcmedicaldevice",&STEP::ObjectHelper<IfcMedicalDevice,1>::Construct ) +, SchemaEntry("ifcmedicaldevicetype",&STEP::ObjectHelper<IfcMedicalDeviceType,1>::Construct ) +, SchemaEntry("ifcmember",&STEP::ObjectHelper<IfcMember,1>::Construct ) +, SchemaEntry("ifcmemberstandardcase",&STEP::ObjectHelper<IfcMemberStandardCase,0>::Construct ) +, SchemaEntry("ifcmembertype",&STEP::ObjectHelper<IfcMemberType,1>::Construct ) +, SchemaEntry("ifcmetric",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmirroredprofiledef",&STEP::ObjectHelper<IfcMirroredProfileDef,0>::Construct ) +, SchemaEntry("ifcmonetaryunit",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcmotorconnection",&STEP::ObjectHelper<IfcMotorConnection,1>::Construct ) +, SchemaEntry("ifcmotorconnectiontype",&STEP::ObjectHelper<IfcMotorConnectionType,1>::Construct ) +, SchemaEntry("ifcobjective",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcoccupant",&STEP::ObjectHelper<IfcOccupant,1>::Construct ) +, SchemaEntry("ifcoffsetcurve2d",&STEP::ObjectHelper<IfcOffsetCurve2D,3>::Construct ) +, SchemaEntry("ifcoffsetcurve3d",&STEP::ObjectHelper<IfcOffsetCurve3D,4>::Construct ) +, SchemaEntry("ifcopenshell",&STEP::ObjectHelper<IfcOpenShell,0>::Construct ) +, SchemaEntry("ifcopeningelement",&STEP::ObjectHelper<IfcOpeningElement,1>::Construct ) +, SchemaEntry("ifcopeningstandardcase",&STEP::ObjectHelper<IfcOpeningStandardCase,0>::Construct ) +, SchemaEntry("ifcorganization",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcorganizationrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcorientededge",&STEP::ObjectHelper<IfcOrientedEdge,2>::Construct ) +, SchemaEntry("ifcouterboundarycurve",&STEP::ObjectHelper<IfcOuterBoundaryCurve,0>::Construct ) +, SchemaEntry("ifcoutlet",&STEP::ObjectHelper<IfcOutlet,1>::Construct ) +, SchemaEntry("ifcoutlettype",&STEP::ObjectHelper<IfcOutletType,1>::Construct ) +, SchemaEntry("ifcownerhistory",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpath",&STEP::ObjectHelper<IfcPath,1>::Construct ) +, SchemaEntry("ifcpcurve",&STEP::ObjectHelper<IfcPcurve,2>::Construct ) +, SchemaEntry("ifcperformancehistory",&STEP::ObjectHelper<IfcPerformanceHistory,2>::Construct ) +, SchemaEntry("ifcpermeablecoveringproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpermit",&STEP::ObjectHelper<IfcPermit,3>::Construct ) +, SchemaEntry("ifcperson",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpersonandorganization",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcphysicalquantity",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcphysicalcomplexquantity",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcphysicalsimplequantity",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpile",&STEP::ObjectHelper<IfcPile,2>::Construct ) +, SchemaEntry("ifcpiletype",&STEP::ObjectHelper<IfcPileType,1>::Construct ) +, SchemaEntry("ifcpipefitting",&STEP::ObjectHelper<IfcPipeFitting,1>::Construct ) +, SchemaEntry("ifcpipefittingtype",&STEP::ObjectHelper<IfcPipeFittingType,1>::Construct ) +, SchemaEntry("ifcpipesegment",&STEP::ObjectHelper<IfcPipeSegment,1>::Construct ) +, SchemaEntry("ifcpipesegmenttype",&STEP::ObjectHelper<IfcPipeSegmentType,1>::Construct ) +, SchemaEntry("ifcpixeltexture",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcplanarextent",&STEP::ObjectHelper<IfcPlanarExtent,2>::Construct ) +, SchemaEntry("ifcplanarbox",&STEP::ObjectHelper<IfcPlanarBox,1>::Construct ) +, SchemaEntry("ifcplane",&STEP::ObjectHelper<IfcPlane,0>::Construct ) +, SchemaEntry("ifcplate",&STEP::ObjectHelper<IfcPlate,1>::Construct ) +, SchemaEntry("ifcplatestandardcase",&STEP::ObjectHelper<IfcPlateStandardCase,0>::Construct ) +, SchemaEntry("ifcplatetype",&STEP::ObjectHelper<IfcPlateType,1>::Construct ) +, SchemaEntry("ifcpointoncurve",&STEP::ObjectHelper<IfcPointOnCurve,2>::Construct ) +, SchemaEntry("ifcpointonsurface",&STEP::ObjectHelper<IfcPointOnSurface,3>::Construct ) +, SchemaEntry("ifcpolyloop",&STEP::ObjectHelper<IfcPolyLoop,1>::Construct ) +, SchemaEntry("ifcpolygonalboundedhalfspace",&STEP::ObjectHelper<IfcPolygonalBoundedHalfSpace,2>::Construct ) +, SchemaEntry("ifctessellatedfaceset",&STEP::ObjectHelper<IfcTessellatedFaceSet,1>::Construct ) +, SchemaEntry("ifcpolygonalfaceset",&STEP::ObjectHelper<IfcPolygonalFaceSet,3>::Construct ) +, SchemaEntry("ifcpolyline",&STEP::ObjectHelper<IfcPolyline,1>::Construct ) +, SchemaEntry("ifcpostaladdress",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpredefinedproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpredefinedtextfont",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpresentationlayerassignment",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpresentationlayerwithstyle",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpresentationstyleassignment",&STEP::ObjectHelper<IfcPresentationStyleAssignment,1>::Construct ) +, SchemaEntry("ifcprocedure",&STEP::ObjectHelper<IfcProcedure,1>::Construct ) +, SchemaEntry("ifcproceduretype",&STEP::ObjectHelper<IfcProcedureType,1>::Construct ) +, SchemaEntry("ifcproductdefinitionshape",&STEP::ObjectHelper<IfcProductDefinitionShape,0>::Construct ) +, SchemaEntry("ifcprofileproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcproject",&STEP::ObjectHelper<IfcProject,0>::Construct ) +, SchemaEntry("ifcprojectlibrary",&STEP::ObjectHelper<IfcProjectLibrary,0>::Construct ) +, SchemaEntry("ifcprojectorder",&STEP::ObjectHelper<IfcProjectOrder,3>::Construct ) +, SchemaEntry("ifcprojectedcrs",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcprojectionelement",&STEP::ObjectHelper<IfcProjectionElement,1>::Construct ) +, SchemaEntry("ifcsimpleproperty",&STEP::ObjectHelper<IfcSimpleProperty,0>::Construct ) +, SchemaEntry("ifcpropertyboundedvalue",&STEP::ObjectHelper<IfcPropertyBoundedValue,4>::Construct ) +, SchemaEntry("ifcpropertydependencyrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpropertyenumeratedvalue",&STEP::ObjectHelper<IfcPropertyEnumeratedValue,2>::Construct ) +, SchemaEntry("ifcpropertyenumeration",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpropertylistvalue",&STEP::ObjectHelper<IfcPropertyListValue,2>::Construct ) +, SchemaEntry("ifcpropertyreferencevalue",&STEP::ObjectHelper<IfcPropertyReferenceValue,2>::Construct ) +, SchemaEntry("ifcpropertyset",&STEP::ObjectHelper<IfcPropertySet,1>::Construct ) +, SchemaEntry("ifcpropertysettemplate",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcpropertysinglevalue",&STEP::ObjectHelper<IfcPropertySingleValue,2>::Construct ) +, SchemaEntry("ifcpropertytablevalue",&STEP::ObjectHelper<IfcPropertyTableValue,6>::Construct ) +, SchemaEntry("ifcprotectivedevice",&STEP::ObjectHelper<IfcProtectiveDevice,1>::Construct ) +, SchemaEntry("ifcprotectivedevicetrippingunit",&STEP::ObjectHelper<IfcProtectiveDeviceTrippingUnit,1>::Construct ) +, SchemaEntry("ifcprotectivedevicetrippingunittype",&STEP::ObjectHelper<IfcProtectiveDeviceTrippingUnitType,1>::Construct ) +, SchemaEntry("ifcprotectivedevicetype",&STEP::ObjectHelper<IfcProtectiveDeviceType,1>::Construct ) +, SchemaEntry("ifcproxy",&STEP::ObjectHelper<IfcProxy,2>::Construct ) +, SchemaEntry("ifcpump",&STEP::ObjectHelper<IfcPump,1>::Construct ) +, SchemaEntry("ifcpumptype",&STEP::ObjectHelper<IfcPumpType,1>::Construct ) +, SchemaEntry("ifcquantityarea",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcquantitycount",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcquantitylength",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcquantitytime",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcquantityvolume",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcquantityweight",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrailing",&STEP::ObjectHelper<IfcRailing,1>::Construct ) +, SchemaEntry("ifcrailingtype",&STEP::ObjectHelper<IfcRailingType,1>::Construct ) +, SchemaEntry("ifcramp",&STEP::ObjectHelper<IfcRamp,1>::Construct ) +, SchemaEntry("ifcrampflight",&STEP::ObjectHelper<IfcRampFlight,1>::Construct ) +, SchemaEntry("ifcrampflighttype",&STEP::ObjectHelper<IfcRampFlightType,1>::Construct ) +, SchemaEntry("ifcramptype",&STEP::ObjectHelper<IfcRampType,1>::Construct ) +, SchemaEntry("ifcrationalbsplinecurvewithknots",&STEP::ObjectHelper<IfcRationalBSplineCurveWithKnots,1>::Construct ) +, SchemaEntry("ifcrationalbsplinesurfacewithknots",&STEP::ObjectHelper<IfcRationalBSplineSurfaceWithKnots,0>::Construct ) +, SchemaEntry("ifcrectangleprofiledef",&STEP::ObjectHelper<IfcRectangleProfileDef,2>::Construct ) +, SchemaEntry("ifcrectanglehollowprofiledef",&STEP::ObjectHelper<IfcRectangleHollowProfileDef,3>::Construct ) +, SchemaEntry("ifcrectangularpyramid",&STEP::ObjectHelper<IfcRectangularPyramid,3>::Construct ) +, SchemaEntry("ifcrectangulartrimmedsurface",&STEP::ObjectHelper<IfcRectangularTrimmedSurface,7>::Construct ) +, SchemaEntry("ifcrecurrencepattern",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcreference",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcregulartimeseries",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcreinforcementbarproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcreinforcementdefinitionproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcreinforcingelement",&STEP::ObjectHelper<IfcReinforcingElement,1>::Construct ) +, SchemaEntry("ifcreinforcingbar",&STEP::ObjectHelper<IfcReinforcingBar,5>::Construct ) +, SchemaEntry("ifcreinforcingelementtype",&STEP::ObjectHelper<IfcReinforcingElementType,0>::Construct ) +, SchemaEntry("ifcreinforcingbartype",&STEP::ObjectHelper<IfcReinforcingBarType,7>::Construct ) +, SchemaEntry("ifcreinforcingmesh",&STEP::ObjectHelper<IfcReinforcingMesh,9>::Construct ) +, SchemaEntry("ifcreinforcingmeshtype",&STEP::ObjectHelper<IfcReinforcingMeshType,11>::Construct ) +, SchemaEntry("ifcrelationship",&STEP::ObjectHelper<IfcRelationship,0>::Construct ) +, SchemaEntry("ifcreldecomposes",&STEP::ObjectHelper<IfcRelDecomposes,0>::Construct ) +, SchemaEntry("ifcrelaggregates",&STEP::ObjectHelper<IfcRelAggregates,2>::Construct ) +, SchemaEntry("ifcrelassigns",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassignstoactor",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassignstocontrol",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassignstogroup",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassignstogroupbyfactor",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassignstoprocess",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassignstoproduct",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassignstoresource",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassociates",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassociatesapproval",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassociatesclassification",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassociatesconstraint",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassociatesdocument",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassociateslibrary",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelassociatesmaterial",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelconnects",&STEP::ObjectHelper<IfcRelConnects,0>::Construct ) +, SchemaEntry("ifcrelconnectselements",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelconnectspathelements",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelconnectsporttoelement",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelconnectsports",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelconnectsstructuralactivity",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelconnectsstructuralmember",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelconnectswitheccentricity",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelconnectswithrealizingelements",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelcontainedinspatialstructure",&STEP::ObjectHelper<IfcRelContainedInSpatialStructure,2>::Construct ) +, SchemaEntry("ifcrelcoversbldgelements",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelcoversspaces",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcreldeclares",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcreldefines",&STEP::ObjectHelper<IfcRelDefines,0>::Construct ) +, SchemaEntry("ifcreldefinesbyobject",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcreldefinesbyproperties",&STEP::ObjectHelper<IfcRelDefinesByProperties,2>::Construct ) +, SchemaEntry("ifcreldefinesbytemplate",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcreldefinesbytype",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelfillselement",&STEP::ObjectHelper<IfcRelFillsElement,2>::Construct ) +, SchemaEntry("ifcrelflowcontrolelements",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelinterfereselements",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelnests",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelprojectselement",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelreferencedinspatialstructure",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelsequence",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelservicesbuildings",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelspaceboundary",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelspaceboundary1stlevel",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelspaceboundary2ndlevel",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrelvoidselement",&STEP::ObjectHelper<IfcRelVoidsElement,2>::Construct ) +, SchemaEntry("ifcreparametrisedcompositecurvesegment",&STEP::ObjectHelper<IfcReparametrisedCompositeCurveSegment,1>::Construct ) +, SchemaEntry("ifcrepresentation",&STEP::ObjectHelper<IfcRepresentation,4>::Construct ) +, SchemaEntry("ifcrepresentationmap",&STEP::ObjectHelper<IfcRepresentationMap,2>::Construct ) +, SchemaEntry("ifcresourceapprovalrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcresourceconstraintrelationship",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcresourcetime",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcrevolvedareasolid",&STEP::ObjectHelper<IfcRevolvedAreaSolid,2>::Construct ) +, SchemaEntry("ifcrevolvedareasolidtapered",&STEP::ObjectHelper<IfcRevolvedAreaSolidTapered,1>::Construct ) +, SchemaEntry("ifcrightcircularcone",&STEP::ObjectHelper<IfcRightCircularCone,2>::Construct ) +, SchemaEntry("ifcrightcircularcylinder",&STEP::ObjectHelper<IfcRightCircularCylinder,2>::Construct ) +, SchemaEntry("ifcroof",&STEP::ObjectHelper<IfcRoof,1>::Construct ) +, SchemaEntry("ifcrooftype",&STEP::ObjectHelper<IfcRoofType,1>::Construct ) +, SchemaEntry("ifcroundedrectangleprofiledef",&STEP::ObjectHelper<IfcRoundedRectangleProfileDef,1>::Construct ) +, SchemaEntry("ifcsiunit",&STEP::ObjectHelper<IfcSIUnit,2>::Construct ) +, SchemaEntry("ifcsanitaryterminal",&STEP::ObjectHelper<IfcSanitaryTerminal,1>::Construct ) +, SchemaEntry("ifcsanitaryterminaltype",&STEP::ObjectHelper<IfcSanitaryTerminalType,1>::Construct ) +, SchemaEntry("ifcseamcurve",&STEP::ObjectHelper<IfcSeamCurve,0>::Construct ) +, SchemaEntry("ifcsectionproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsectionreinforcementproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsectionedspine",&STEP::ObjectHelper<IfcSectionedSpine,3>::Construct ) +, SchemaEntry("ifcsensor",&STEP::ObjectHelper<IfcSensor,1>::Construct ) +, SchemaEntry("ifcsensortype",&STEP::ObjectHelper<IfcSensorType,1>::Construct ) +, SchemaEntry("ifcshadingdevice",&STEP::ObjectHelper<IfcShadingDevice,1>::Construct ) +, SchemaEntry("ifcshadingdevicetype",&STEP::ObjectHelper<IfcShadingDeviceType,1>::Construct ) +, SchemaEntry("ifcshapeaspect",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcshapemodel",&STEP::ObjectHelper<IfcShapeModel,0>::Construct ) +, SchemaEntry("ifcshaperepresentation",&STEP::ObjectHelper<IfcShapeRepresentation,0>::Construct ) +, SchemaEntry("ifcshellbasedsurfacemodel",&STEP::ObjectHelper<IfcShellBasedSurfaceModel,1>::Construct ) +, SchemaEntry("ifcsimplepropertytemplate",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsite",&STEP::ObjectHelper<IfcSite,5>::Construct ) +, SchemaEntry("ifcslab",&STEP::ObjectHelper<IfcSlab,1>::Construct ) +, SchemaEntry("ifcslabelementedcase",&STEP::ObjectHelper<IfcSlabElementedCase,0>::Construct ) +, SchemaEntry("ifcslabstandardcase",&STEP::ObjectHelper<IfcSlabStandardCase,0>::Construct ) +, SchemaEntry("ifcslabtype",&STEP::ObjectHelper<IfcSlabType,1>::Construct ) +, SchemaEntry("ifcslippageconnectioncondition",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsolardevice",&STEP::ObjectHelper<IfcSolarDevice,1>::Construct ) +, SchemaEntry("ifcsolardevicetype",&STEP::ObjectHelper<IfcSolarDeviceType,1>::Construct ) +, SchemaEntry("ifcspace",&STEP::ObjectHelper<IfcSpace,2>::Construct ) +, SchemaEntry("ifcspaceheater",&STEP::ObjectHelper<IfcSpaceHeater,1>::Construct ) +, SchemaEntry("ifcspaceheatertype",&STEP::ObjectHelper<IfcSpaceHeaterType,1>::Construct ) +, SchemaEntry("ifcspatialelementtype",&STEP::ObjectHelper<IfcSpatialElementType,1>::Construct ) +, SchemaEntry("ifcspatialstructureelementtype",&STEP::ObjectHelper<IfcSpatialStructureElementType,0>::Construct ) +, SchemaEntry("ifcspacetype",&STEP::ObjectHelper<IfcSpaceType,2>::Construct ) +, SchemaEntry("ifcspatialzone",&STEP::ObjectHelper<IfcSpatialZone,1>::Construct ) +, SchemaEntry("ifcspatialzonetype",&STEP::ObjectHelper<IfcSpatialZoneType,2>::Construct ) +, SchemaEntry("ifcsphere",&STEP::ObjectHelper<IfcSphere,1>::Construct ) +, SchemaEntry("ifcsphericalsurface",&STEP::ObjectHelper<IfcSphericalSurface,1>::Construct ) +, SchemaEntry("ifcstackterminal",&STEP::ObjectHelper<IfcStackTerminal,1>::Construct ) +, SchemaEntry("ifcstackterminaltype",&STEP::ObjectHelper<IfcStackTerminalType,1>::Construct ) +, SchemaEntry("ifcstair",&STEP::ObjectHelper<IfcStair,1>::Construct ) +, SchemaEntry("ifcstairflight",&STEP::ObjectHelper<IfcStairFlight,5>::Construct ) +, SchemaEntry("ifcstairflighttype",&STEP::ObjectHelper<IfcStairFlightType,1>::Construct ) +, SchemaEntry("ifcstairtype",&STEP::ObjectHelper<IfcStairType,1>::Construct ) +, SchemaEntry("ifcstructuralactivity",&STEP::ObjectHelper<IfcStructuralActivity,2>::Construct ) +, SchemaEntry("ifcstructuralaction",&STEP::ObjectHelper<IfcStructuralAction,1>::Construct ) +, SchemaEntry("ifcstructuralanalysismodel",&STEP::ObjectHelper<IfcStructuralAnalysisModel,5>::Construct ) +, SchemaEntry("ifcstructuralitem",&STEP::ObjectHelper<IfcStructuralItem,0>::Construct ) +, SchemaEntry("ifcstructuralconnection",&STEP::ObjectHelper<IfcStructuralConnection,1>::Construct ) +, SchemaEntry("ifcstructuralcurveaction",&STEP::ObjectHelper<IfcStructuralCurveAction,2>::Construct ) +, SchemaEntry("ifcstructuralcurveconnection",&STEP::ObjectHelper<IfcStructuralCurveConnection,1>::Construct ) +, SchemaEntry("ifcstructuralmember",&STEP::ObjectHelper<IfcStructuralMember,0>::Construct ) +, SchemaEntry("ifcstructuralcurvemember",&STEP::ObjectHelper<IfcStructuralCurveMember,2>::Construct ) +, SchemaEntry("ifcstructuralcurvemembervarying",&STEP::ObjectHelper<IfcStructuralCurveMemberVarying,0>::Construct ) +, SchemaEntry("ifcstructuralreaction",&STEP::ObjectHelper<IfcStructuralReaction,0>::Construct ) +, SchemaEntry("ifcstructuralcurvereaction",&STEP::ObjectHelper<IfcStructuralCurveReaction,1>::Construct ) +, SchemaEntry("ifcstructurallinearaction",&STEP::ObjectHelper<IfcStructuralLinearAction,0>::Construct ) +, SchemaEntry("ifcstructuralload",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadgroup",&STEP::ObjectHelper<IfcStructuralLoadGroup,5>::Construct ) +, SchemaEntry("ifcstructuralloadcase",&STEP::ObjectHelper<IfcStructuralLoadCase,1>::Construct ) +, SchemaEntry("ifcstructuralloadconfiguration",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadorresult",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadstatic",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadlinearforce",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadplanarforce",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadsingledisplacement",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadsingledisplacementdistortion",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadsingleforce",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadsingleforcewarping",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralloadtemperature",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcstructuralsurfaceaction",&STEP::ObjectHelper<IfcStructuralSurfaceAction,2>::Construct ) +, SchemaEntry("ifcstructuralplanaraction",&STEP::ObjectHelper<IfcStructuralPlanarAction,0>::Construct ) +, SchemaEntry("ifcstructuralpointaction",&STEP::ObjectHelper<IfcStructuralPointAction,0>::Construct ) +, SchemaEntry("ifcstructuralpointconnection",&STEP::ObjectHelper<IfcStructuralPointConnection,1>::Construct ) +, SchemaEntry("ifcstructuralpointreaction",&STEP::ObjectHelper<IfcStructuralPointReaction,0>::Construct ) +, SchemaEntry("ifcstructuralresultgroup",&STEP::ObjectHelper<IfcStructuralResultGroup,3>::Construct ) +, SchemaEntry("ifcstructuralsurfaceconnection",&STEP::ObjectHelper<IfcStructuralSurfaceConnection,0>::Construct ) +, SchemaEntry("ifcstructuralsurfacemember",&STEP::ObjectHelper<IfcStructuralSurfaceMember,2>::Construct ) +, SchemaEntry("ifcstructuralsurfacemembervarying",&STEP::ObjectHelper<IfcStructuralSurfaceMemberVarying,0>::Construct ) +, SchemaEntry("ifcstructuralsurfacereaction",&STEP::ObjectHelper<IfcStructuralSurfaceReaction,1>::Construct ) +, SchemaEntry("ifcstylemodel",&STEP::ObjectHelper<IfcStyleModel,0>::Construct ) +, SchemaEntry("ifcstyleditem",&STEP::ObjectHelper<IfcStyledItem,3>::Construct ) +, SchemaEntry("ifcstyledrepresentation",&STEP::ObjectHelper<IfcStyledRepresentation,0>::Construct ) +, SchemaEntry("ifcsubcontractresource",&STEP::ObjectHelper<IfcSubContractResource,1>::Construct ) +, SchemaEntry("ifcsubcontractresourcetype",&STEP::ObjectHelper<IfcSubContractResourceType,1>::Construct ) +, SchemaEntry("ifcsubedge",&STEP::ObjectHelper<IfcSubedge,1>::Construct ) +, SchemaEntry("ifcsurfacecurvesweptareasolid",&STEP::ObjectHelper<IfcSurfaceCurveSweptAreaSolid,4>::Construct ) +, SchemaEntry("ifcsurfacefeature",&STEP::ObjectHelper<IfcSurfaceFeature,1>::Construct ) +, SchemaEntry("ifcsweptsurface",&STEP::ObjectHelper<IfcSweptSurface,2>::Construct ) +, SchemaEntry("ifcsurfaceoflinearextrusion",&STEP::ObjectHelper<IfcSurfaceOfLinearExtrusion,2>::Construct ) +, SchemaEntry("ifcsurfaceofrevolution",&STEP::ObjectHelper<IfcSurfaceOfRevolution,1>::Construct ) +, SchemaEntry("ifcsurfacereinforcementarea",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsurfacestyle",&STEP::ObjectHelper<IfcSurfaceStyle,2>::Construct ) +, SchemaEntry("ifcsurfacestylelighting",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsurfacestylerefraction",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcsurfacestyleshading",&STEP::ObjectHelper<IfcSurfaceStyleShading,2>::Construct ) +, SchemaEntry("ifcsurfacestylerendering",&STEP::ObjectHelper<IfcSurfaceStyleRendering,7>::Construct ) +, SchemaEntry("ifcsurfacestylewithtextures",&STEP::ObjectHelper<IfcSurfaceStyleWithTextures,1>::Construct ) +, SchemaEntry("ifcsweptdisksolid",&STEP::ObjectHelper<IfcSweptDiskSolid,5>::Construct ) +, SchemaEntry("ifcsweptdisksolidpolygonal",&STEP::ObjectHelper<IfcSweptDiskSolidPolygonal,1>::Construct ) +, SchemaEntry("ifcswitchingdevice",&STEP::ObjectHelper<IfcSwitchingDevice,1>::Construct ) +, SchemaEntry("ifcswitchingdevicetype",&STEP::ObjectHelper<IfcSwitchingDeviceType,1>::Construct ) +, SchemaEntry("ifcsystemfurnitureelement",&STEP::ObjectHelper<IfcSystemFurnitureElement,1>::Construct ) +, SchemaEntry("ifcsystemfurnitureelementtype",&STEP::ObjectHelper<IfcSystemFurnitureElementType,1>::Construct ) +, SchemaEntry("ifctshapeprofiledef",&STEP::ObjectHelper<IfcTShapeProfileDef,9>::Construct ) +, SchemaEntry("ifctable",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctablecolumn",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctablerow",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctank",&STEP::ObjectHelper<IfcTank,1>::Construct ) +, SchemaEntry("ifctanktype",&STEP::ObjectHelper<IfcTankType,1>::Construct ) +, SchemaEntry("ifctask",&STEP::ObjectHelper<IfcTask,6>::Construct ) +, SchemaEntry("ifctasktime",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctasktimerecurring",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctasktype",&STEP::ObjectHelper<IfcTaskType,2>::Construct ) +, SchemaEntry("ifctelecomaddress",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctendon",&STEP::ObjectHelper<IfcTendon,8>::Construct ) +, SchemaEntry("ifctendonanchor",&STEP::ObjectHelper<IfcTendonAnchor,1>::Construct ) +, SchemaEntry("ifctendonanchortype",&STEP::ObjectHelper<IfcTendonAnchorType,1>::Construct ) +, SchemaEntry("ifctendontype",&STEP::ObjectHelper<IfcTendonType,4>::Construct ) +, SchemaEntry("ifctextliteral",&STEP::ObjectHelper<IfcTextLiteral,3>::Construct ) +, SchemaEntry("ifctextliteralwithextent",&STEP::ObjectHelper<IfcTextLiteralWithExtent,2>::Construct ) +, SchemaEntry("ifctextstyle",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctextstylefontmodel",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctextstylefordefinedfont",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctextstyletextmodel",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctexturecoordinategenerator",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctexturemap",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctexturevertex",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctexturevertexlist",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctimeperiod",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctimeseriesvalue",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifctopologyrepresentation",&STEP::ObjectHelper<IfcTopologyRepresentation,0>::Construct ) +, SchemaEntry("ifctoroidalsurface",&STEP::ObjectHelper<IfcToroidalSurface,2>::Construct ) +, SchemaEntry("ifctransformer",&STEP::ObjectHelper<IfcTransformer,1>::Construct ) +, SchemaEntry("ifctransformertype",&STEP::ObjectHelper<IfcTransformerType,1>::Construct ) +, SchemaEntry("ifctransportelement",&STEP::ObjectHelper<IfcTransportElement,1>::Construct ) +, SchemaEntry("ifctransportelementtype",&STEP::ObjectHelper<IfcTransportElementType,1>::Construct ) +, SchemaEntry("ifctrapeziumprofiledef",&STEP::ObjectHelper<IfcTrapeziumProfileDef,4>::Construct ) +, SchemaEntry("ifctriangulatedfaceset",&STEP::ObjectHelper<IfcTriangulatedFaceSet,2>::Construct ) +, SchemaEntry("ifctrimmedcurve",&STEP::ObjectHelper<IfcTrimmedCurve,5>::Construct ) +, SchemaEntry("ifctubebundle",&STEP::ObjectHelper<IfcTubeBundle,1>::Construct ) +, SchemaEntry("ifctubebundletype",&STEP::ObjectHelper<IfcTubeBundleType,1>::Construct ) +, SchemaEntry("ifcushapeprofiledef",&STEP::ObjectHelper<IfcUShapeProfileDef,7>::Construct ) +, SchemaEntry("ifcunitassignment",&STEP::ObjectHelper<IfcUnitAssignment,1>::Construct ) +, SchemaEntry("ifcunitarycontrolelement",&STEP::ObjectHelper<IfcUnitaryControlElement,1>::Construct ) +, SchemaEntry("ifcunitarycontrolelementtype",&STEP::ObjectHelper<IfcUnitaryControlElementType,1>::Construct ) +, SchemaEntry("ifcunitaryequipment",&STEP::ObjectHelper<IfcUnitaryEquipment,1>::Construct ) +, SchemaEntry("ifcunitaryequipmenttype",&STEP::ObjectHelper<IfcUnitaryEquipmentType,1>::Construct ) +, SchemaEntry("ifcvalve",&STEP::ObjectHelper<IfcValve,1>::Construct ) +, SchemaEntry("ifcvalvetype",&STEP::ObjectHelper<IfcValveType,1>::Construct ) +, SchemaEntry("ifcvector",&STEP::ObjectHelper<IfcVector,2>::Construct ) +, SchemaEntry("ifcvertex",&STEP::ObjectHelper<IfcVertex,0>::Construct ) +, SchemaEntry("ifcvertexloop",&STEP::ObjectHelper<IfcVertexLoop,1>::Construct ) +, SchemaEntry("ifcvertexpoint",&STEP::ObjectHelper<IfcVertexPoint,1>::Construct ) +, SchemaEntry("ifcvibrationisolator",&STEP::ObjectHelper<IfcVibrationIsolator,1>::Construct ) +, SchemaEntry("ifcvibrationisolatortype",&STEP::ObjectHelper<IfcVibrationIsolatorType,1>::Construct ) +, SchemaEntry("ifcvirtualelement",&STEP::ObjectHelper<IfcVirtualElement,0>::Construct ) +, SchemaEntry("ifcvirtualgridintersection",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcvoidingfeature",&STEP::ObjectHelper<IfcVoidingFeature,1>::Construct ) +, SchemaEntry("ifcwall",&STEP::ObjectHelper<IfcWall,1>::Construct ) +, SchemaEntry("ifcwallelementedcase",&STEP::ObjectHelper<IfcWallElementedCase,0>::Construct ) +, SchemaEntry("ifcwallstandardcase",&STEP::ObjectHelper<IfcWallStandardCase,0>::Construct ) +, SchemaEntry("ifcwalltype",&STEP::ObjectHelper<IfcWallType,1>::Construct ) +, SchemaEntry("ifcwasteterminal",&STEP::ObjectHelper<IfcWasteTerminal,1>::Construct ) +, SchemaEntry("ifcwasteterminaltype",&STEP::ObjectHelper<IfcWasteTerminalType,1>::Construct ) +, SchemaEntry("ifcwindow",&STEP::ObjectHelper<IfcWindow,5>::Construct ) +, SchemaEntry("ifcwindowliningproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcwindowpanelproperties",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifcwindowstandardcase",&STEP::ObjectHelper<IfcWindowStandardCase,0>::Construct ) +, SchemaEntry("ifcwindowstyle",&STEP::ObjectHelper<IfcWindowStyle,4>::Construct ) +, SchemaEntry("ifcwindowtype",&STEP::ObjectHelper<IfcWindowType,4>::Construct ) +, SchemaEntry("ifcworkcalendar",&STEP::ObjectHelper<IfcWorkCalendar,3>::Construct ) +, SchemaEntry("ifcworkcontrol",&STEP::ObjectHelper<IfcWorkControl,7>::Construct ) +, SchemaEntry("ifcworkplan",&STEP::ObjectHelper<IfcWorkPlan,1>::Construct ) +, SchemaEntry("ifcworkschedule",&STEP::ObjectHelper<IfcWorkSchedule,1>::Construct ) +, SchemaEntry("ifcworktime",&STEP::ObjectHelper<NotImplemented,0>::Construct ) +, SchemaEntry("ifczshapeprofiledef",&STEP::ObjectHelper<IfcZShapeProfileDef,6>::Construct ) +, SchemaEntry("ifczone",&STEP::ObjectHelper<IfcZone,1>::Construct ) + + }; +} + +// ----------------------------------------------------------------------------------------------------------- +void IFC::GetSchema(EXPRESS::ConversionSchema& out) +{ + out = EXPRESS::ConversionSchema(schema_raw); +} + +namespace STEP { + +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<NotImplemented>(const STEP::DB& db, const LIST& params, NotImplemented* in) +{ + return 0; +} + + + +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRoot>(const DB& db, const LIST& params, IfcRoot* in) +{ + size_t base = 0; + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRoot"); } do { // convert the 'GlobalId' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRoot,4>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->GlobalId, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRoot to be a `IfcGloballyUniqueId`")); } + } while(0); + do { // convert the 'OwnerHistory' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRoot,4>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->OwnerHistory, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRoot to be a `IfcOwnerHistory`")); } + } while(0); + do { // convert the 'Name' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRoot,4>::aux_is_derived[2]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRoot to be a `IfcLabel`")); } + } while(0); + do { // convert the 'Description' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRoot,4>::aux_is_derived[3]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Description, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRoot to be a `IfcText`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcObjectDefinition>(const DB& db, const LIST& params, IfcObjectDefinition* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRoot*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcObjectDefinition"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcObject>(const DB& db, const LIST& params, IfcObject* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObjectDefinition*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcObject"); } do { // convert the 'ObjectType' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcObject,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ObjectType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcObject to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcControl>(const DB& db, const LIST& params, IfcControl* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcActionRequest>(const DB& db, const LIST& params, IfcActionRequest* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcActor>(const DB& db, const LIST& params, IfcActor* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProduct>(const DB& db, const LIST& params, IfcProduct* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObject*>(in)); + if (params.GetSize() < 7) { throw STEP::TypeError("expected 7 arguments to IfcProduct"); } do { // convert the 'ObjectPlacement' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcProduct,2>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ObjectPlacement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcProduct to be a `IfcObjectPlacement`")); } + } while(0); + do { // convert the 'Representation' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcProduct,2>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Representation, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcProduct to be a `IfcProductRepresentation`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElement>(const DB& db, const LIST& params, IfcElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcElement"); } do { // convert the 'Tag' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcElement,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Tag, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcElement to be a `IfcIdentifier`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionElement>(const DB& db, const LIST& params, IfcDistributionElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionControlElement>(const DB& db, const LIST& params, IfcDistributionControlElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcActuator>(const DB& db, const LIST& params, IfcActuator* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTypeObject>(const DB& db, const LIST& params, IfcTypeObject* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObjectDefinition*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTypeProduct>(const DB& db, const LIST& params, IfcTypeProduct* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElementType>(const DB& db, const LIST& params, IfcElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionElementType>(const DB& db, const LIST& params, IfcDistributionElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionControlElementType>(const DB& db, const LIST& params, IfcDistributionControlElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcActuatorType>(const DB& db, const LIST& params, IfcActuatorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRepresentationItem>(const DB& db, const LIST& params, IfcRepresentationItem* in) +{ + size_t base = 0; + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGeometricRepresentationItem>(const DB& db, const LIST& params, IfcGeometricRepresentationItem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSolidModel>(const DB& db, const LIST& params, IfcSolidModel* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcManifoldSolidBrep>(const DB& db, const LIST& params, IfcManifoldSolidBrep* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSolidModel*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcManifoldSolidBrep"); } do { // convert the 'Outer' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcManifoldSolidBrep,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Outer, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcManifoldSolidBrep to be a `IfcClosedShell`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAdvancedBrep>(const DB& db, const LIST& params, IfcAdvancedBrep* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcManifoldSolidBrep*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAdvancedBrepWithVoids>(const DB& db, const LIST& params, IfcAdvancedBrepWithVoids* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcAdvancedBrep*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTopologicalRepresentationItem>(const DB& db, const LIST& params, IfcTopologicalRepresentationItem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFace>(const DB& db, const LIST& params, IfcFace* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcFace"); } do { // convert the 'Bounds' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcFace,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Bounds, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFace to be a `SET [1:?] OF IfcFaceBound`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFaceSurface>(const DB& db, const LIST& params, IfcFaceSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFace*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAdvancedFace>(const DB& db, const LIST& params, IfcAdvancedFace* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFaceSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionFlowElement>(const DB& db, const LIST& params, IfcDistributionFlowElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowTerminal>(const DB& db, const LIST& params, IfcFlowTerminal* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAirTerminal>(const DB& db, const LIST& params, IfcAirTerminal* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminal*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowController>(const DB& db, const LIST& params, IfcFlowController* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAirTerminalBox>(const DB& db, const LIST& params, IfcAirTerminalBox* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowController*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionFlowElementType>(const DB& db, const LIST& params, IfcDistributionFlowElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowControllerType>(const DB& db, const LIST& params, IfcFlowControllerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAirTerminalBoxType>(const DB& db, const LIST& params, IfcAirTerminalBoxType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowTerminalType>(const DB& db, const LIST& params, IfcFlowTerminalType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAirTerminalType>(const DB& db, const LIST& params, IfcAirTerminalType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEnergyConversionDevice>(const DB& db, const LIST& params, IfcEnergyConversionDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAirToAirHeatRecovery>(const DB& db, const LIST& params, IfcAirToAirHeatRecovery* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEnergyConversionDeviceType>(const DB& db, const LIST& params, IfcEnergyConversionDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAirToAirHeatRecoveryType>(const DB& db, const LIST& params, IfcAirToAirHeatRecoveryType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAlarm>(const DB& db, const LIST& params, IfcAlarm* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAlarmType>(const DB& db, const LIST& params, IfcAlarmType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAnnotation>(const DB& db, const LIST& params, IfcAnnotation* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); + if (params.GetSize() < 7) { throw STEP::TypeError("expected 7 arguments to IfcAnnotation"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAnnotationFillArea>(const DB& db, const LIST& params, IfcAnnotationFillArea* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProfileDef>(const DB& db, const LIST& params, IfcProfileDef* in) +{ + size_t base = 0; + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcProfileDef"); } do { // convert the 'ProfileType' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcProfileDef,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->ProfileType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProfileDef to be a `IfcProfileTypeEnum`")); } + } while(0); + do { // convert the 'ProfileName' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcProfileDef,2>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ProfileName, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProfileDef to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcArbitraryClosedProfileDef>(const DB& db, const LIST& params, IfcArbitraryClosedProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProfileDef*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcArbitraryClosedProfileDef"); } do { // convert the 'OuterCurve' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcArbitraryClosedProfileDef,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->OuterCurve, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcArbitraryClosedProfileDef to be a `IfcCurve`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcArbitraryOpenProfileDef>(const DB& db, const LIST& params, IfcArbitraryOpenProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProfileDef*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcArbitraryOpenProfileDef"); } do { // convert the 'Curve' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcArbitraryOpenProfileDef,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Curve, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcArbitraryOpenProfileDef to be a `IfcBoundedCurve`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcArbitraryProfileDefWithVoids>(const DB& db, const LIST& params, IfcArbitraryProfileDefWithVoids* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcArbitraryClosedProfileDef*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcArbitraryProfileDefWithVoids"); } do { // convert the 'InnerCurves' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->InnerCurves, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcArbitraryProfileDefWithVoids to be a `SET [1:?] OF IfcCurve`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGroup>(const DB& db, const LIST& params, IfcGroup* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAsset>(const DB& db, const LIST& params, IfcAsset* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGroup*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcParameterizedProfileDef>(const DB& db, const LIST& params, IfcParameterizedProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProfileDef*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcParameterizedProfileDef"); } do { // convert the 'Position' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcParameterizedProfileDef,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Position, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcParameterizedProfileDef to be a `IfcAxis2Placement2D`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAsymmetricIShapeProfileDef>(const DB& db, const LIST& params, IfcAsymmetricIShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAudioVisualAppliance>(const DB& db, const LIST& params, IfcAudioVisualAppliance* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminal*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAudioVisualApplianceType>(const DB& db, const LIST& params, IfcAudioVisualApplianceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPlacement>(const DB& db, const LIST& params, IfcPlacement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPlacement"); } do { // convert the 'Location' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcPlacement,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Location, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPlacement to be a `IfcCartesianPoint`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAxis1Placement>(const DB& db, const LIST& params, IfcAxis1Placement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPlacement*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcAxis1Placement"); } do { // convert the 'Axis' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Axis, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis1Placement to be a `IfcDirection`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAxis2Placement2D>(const DB& db, const LIST& params, IfcAxis2Placement2D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPlacement*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcAxis2Placement2D"); } do { // convert the 'RefDirection' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RefDirection, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis2Placement2D to be a `IfcDirection`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcAxis2Placement3D>(const DB& db, const LIST& params, IfcAxis2Placement3D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPlacement*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcAxis2Placement3D"); } do { // convert the 'Axis' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Axis, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis2Placement3D to be a `IfcDirection`")); } + } while(0); + do { // convert the 'RefDirection' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RefDirection, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcAxis2Placement3D to be a `IfcDirection`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCurve>(const DB& db, const LIST& params, IfcCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBoundedCurve>(const DB& db, const LIST& params, IfcBoundedCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCurve*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBSplineCurve>(const DB& db, const LIST& params, IfcBSplineCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedCurve*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcBSplineCurve"); } do { // convert the 'Degree' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcBSplineCurve,5>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Degree, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBSplineCurve to be a `IfcInteger`")); } + } while(0); + do { // convert the 'ControlPointsList' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcBSplineCurve,5>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->ControlPointsList, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBSplineCurve to be a `LIST [2:?] OF IfcCartesianPoint`")); } + } while(0); + do { // convert the 'CurveForm' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcBSplineCurve,5>::aux_is_derived[2]=true; break; } + try { GenericConvert( in->CurveForm, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBSplineCurve to be a `IfcBSplineCurveForm`")); } + } while(0); + do { // convert the 'ClosedCurve' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcBSplineCurve,5>::aux_is_derived[3]=true; break; } + try { GenericConvert( in->ClosedCurve, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcBSplineCurve to be a `IfcLogical`")); } + } while(0); + do { // convert the 'SelfIntersect' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcBSplineCurve,5>::aux_is_derived[4]=true; break; } + try { GenericConvert( in->SelfIntersect, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcBSplineCurve to be a `IfcLogical`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBSplineCurveWithKnots>(const DB& db, const LIST& params, IfcBSplineCurveWithKnots* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBSplineCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurface>(const DB& db, const LIST& params, IfcSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBoundedSurface>(const DB& db, const LIST& params, IfcBoundedSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBSplineSurface>(const DB& db, const LIST& params, IfcBSplineSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBSplineSurfaceWithKnots>(const DB& db, const LIST& params, IfcBSplineSurfaceWithKnots* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBSplineSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingElement>(const DB& db, const LIST& params, IfcBuildingElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcBuildingElement"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBeam>(const DB& db, const LIST& params, IfcBeam* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBeamStandardCase>(const DB& db, const LIST& params, IfcBeamStandardCase* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBeam*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingElementType>(const DB& db, const LIST& params, IfcBuildingElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBeamType>(const DB& db, const LIST& params, IfcBeamType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPresentationItem>(const DB& db, const LIST& params, IfcPresentationItem* in) +{ + size_t base = 0; + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCsgPrimitive3D>(const DB& db, const LIST& params, IfcCsgPrimitive3D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBlock>(const DB& db, const LIST& params, IfcBlock* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCsgPrimitive3D*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBoiler>(const DB& db, const LIST& params, IfcBoiler* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBoilerType>(const DB& db, const LIST& params, IfcBoilerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBooleanResult>(const DB& db, const LIST& params, IfcBooleanResult* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcBooleanResult"); } do { // convert the 'Operator' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcBooleanResult,3>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Operator, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBooleanResult to be a `IfcBooleanOperator`")); } + } while(0); + do { // convert the 'FirstOperand' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcBooleanResult,3>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->FirstOperand, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBooleanResult to be a `IfcBooleanOperand`")); } + } while(0); + do { // convert the 'SecondOperand' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcBooleanResult,3>::aux_is_derived[2]=true; break; } + try { GenericConvert( in->SecondOperand, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBooleanResult to be a `IfcBooleanOperand`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBooleanClippingResult>(const DB& db, const LIST& params, IfcBooleanClippingResult* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBooleanResult*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcBooleanClippingResult"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCompositeCurve>(const DB& db, const LIST& params, IfcCompositeCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedCurve*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcCompositeCurve"); } do { // convert the 'Segments' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcCompositeCurve,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Segments, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCompositeCurve to be a `LIST [1:?] OF IfcCompositeCurveSegment`")); } + } while(0); + do { // convert the 'SelfIntersect' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcCompositeCurve,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->SelfIntersect, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCompositeCurve to be a `IfcLogical`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCompositeCurveOnSurface>(const DB& db, const LIST& params, IfcCompositeCurveOnSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCompositeCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBoundaryCurve>(const DB& db, const LIST& params, IfcBoundaryCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCompositeCurveOnSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBoundingBox>(const DB& db, const LIST& params, IfcBoundingBox* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcBoundingBox"); } do { // convert the 'Corner' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Corner, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBoundingBox to be a `IfcCartesianPoint`")); } + } while(0); + do { // convert the 'XDim' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->XDim, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'YDim' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->YDim, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'ZDim' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->ZDim, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcHalfSpaceSolid>(const DB& db, const LIST& params, IfcHalfSpaceSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcHalfSpaceSolid"); } do { // convert the 'BaseSurface' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcHalfSpaceSolid,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->BaseSurface, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcHalfSpaceSolid to be a `IfcSurface`")); } + } while(0); + do { // convert the 'AgreementFlag' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcHalfSpaceSolid,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->AgreementFlag, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcHalfSpaceSolid to be a `IfcBoolean`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBoxedHalfSpace>(const DB& db, const LIST& params, IfcBoxedHalfSpace* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcHalfSpaceSolid*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpatialElement>(const DB& db, const LIST& params, IfcSpatialElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcSpatialElement"); } do { // convert the 'LongName' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcSpatialElement,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->LongName, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcSpatialElement to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpatialStructureElement>(const DB& db, const LIST& params, IfcSpatialStructureElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialElement*>(in)); + if (params.GetSize() < 9) { throw STEP::TypeError("expected 9 arguments to IfcSpatialStructureElement"); } do { // convert the 'CompositionType' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcSpatialStructureElement,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->CompositionType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcSpatialStructureElement to be a `IfcElementCompositionEnum`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuilding>(const DB& db, const LIST& params, IfcBuilding* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialStructureElement*>(in)); + if (params.GetSize() < 12) { throw STEP::TypeError("expected 12 arguments to IfcBuilding"); } do { // convert the 'ElevationOfRefHeight' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ElevationOfRefHeight, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcBuilding to be a `IfcLengthMeasure`")); } + } while(0); + do { // convert the 'ElevationOfTerrain' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ElevationOfTerrain, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcBuilding to be a `IfcLengthMeasure`")); } + } while(0); + do { // convert the 'BuildingAddress' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->BuildingAddress, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 11 to IfcBuilding to be a `IfcPostalAddress`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElementComponent>(const DB& db, const LIST& params, IfcElementComponent* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingElementPart>(const DB& db, const LIST& params, IfcBuildingElementPart* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponent*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElementComponentType>(const DB& db, const LIST& params, IfcElementComponentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingElementPartType>(const DB& db, const LIST& params, IfcBuildingElementPartType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingElementProxy>(const DB& db, const LIST& params, IfcBuildingElementProxy* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingElementProxyType>(const DB& db, const LIST& params, IfcBuildingElementProxyType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingStorey>(const DB& db, const LIST& params, IfcBuildingStorey* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialStructureElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSystem>(const DB& db, const LIST& params, IfcSystem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGroup*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBuildingSystem>(const DB& db, const LIST& params, IfcBuildingSystem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSystem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBurner>(const DB& db, const LIST& params, IfcBurner* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcBurnerType>(const DB& db, const LIST& params, IfcBurnerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCShapeProfileDef>(const DB& db, const LIST& params, IfcCShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowFitting>(const DB& db, const LIST& params, IfcFlowFitting* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCableCarrierFitting>(const DB& db, const LIST& params, IfcCableCarrierFitting* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFitting*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowFittingType>(const DB& db, const LIST& params, IfcFlowFittingType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCableCarrierFittingType>(const DB& db, const LIST& params, IfcCableCarrierFittingType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFittingType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowSegment>(const DB& db, const LIST& params, IfcFlowSegment* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCableCarrierSegment>(const DB& db, const LIST& params, IfcCableCarrierSegment* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowSegment*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowSegmentType>(const DB& db, const LIST& params, IfcFlowSegmentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCableCarrierSegmentType>(const DB& db, const LIST& params, IfcCableCarrierSegmentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowSegmentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCableFitting>(const DB& db, const LIST& params, IfcCableFitting* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFitting*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCableFittingType>(const DB& db, const LIST& params, IfcCableFittingType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFittingType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCableSegment>(const DB& db, const LIST& params, IfcCableSegment* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowSegment*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCableSegmentType>(const DB& db, const LIST& params, IfcCableSegmentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowSegmentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPoint>(const DB& db, const LIST& params, IfcPoint* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianPoint>(const DB& db, const LIST& params, IfcCartesianPoint* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPoint*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcCartesianPoint"); } do { // convert the 'Coordinates' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Coordinates, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCartesianPoint to be a `LIST [1:3] OF IfcLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianPointList>(const DB& db, const LIST& params, IfcCartesianPointList* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianPointList2D>(const DB& db, const LIST& params, IfcCartesianPointList2D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCartesianPointList*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianPointList3D>(const DB& db, const LIST& params, IfcCartesianPointList3D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCartesianPointList*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianTransformationOperator>(const DB& db, const LIST& params, IfcCartesianTransformationOperator* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcCartesianTransformationOperator"); } do { // convert the 'Axis1' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcCartesianTransformationOperator,4>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Axis1, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCartesianTransformationOperator to be a `IfcDirection`")); } + } while(0); + do { // convert the 'Axis2' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcCartesianTransformationOperator,4>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Axis2, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCartesianTransformationOperator to be a `IfcDirection`")); } + } while(0); + do { // convert the 'LocalOrigin' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcCartesianTransformationOperator,4>::aux_is_derived[2]=true; break; } + try { GenericConvert( in->LocalOrigin, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcCartesianTransformationOperator to be a `IfcCartesianPoint`")); } + } while(0); + do { // convert the 'Scale' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcCartesianTransformationOperator,4>::aux_is_derived[3]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Scale, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcCartesianTransformationOperator to be a `IfcReal`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianTransformationOperator2D>(const DB& db, const LIST& params, IfcCartesianTransformationOperator2D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCartesianTransformationOperator*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianTransformationOperator2DnonUniform>(const DB& db, const LIST& params, IfcCartesianTransformationOperator2DnonUniform* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCartesianTransformationOperator2D*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianTransformationOperator3D>(const DB& db, const LIST& params, IfcCartesianTransformationOperator3D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCartesianTransformationOperator*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcCartesianTransformationOperator3D"); } do { // convert the 'Axis3' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcCartesianTransformationOperator3D,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Axis3, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcCartesianTransformationOperator3D to be a `IfcDirection`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCartesianTransformationOperator3DnonUniform>(const DB& db, const LIST& params, IfcCartesianTransformationOperator3DnonUniform* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCartesianTransformationOperator3D*>(in)); + if (params.GetSize() < 7) { throw STEP::TypeError("expected 7 arguments to IfcCartesianTransformationOperator3DnonUniform"); } do { // convert the 'Scale2' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Scale2, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcCartesianTransformationOperator3DnonUniform to be a `IfcReal`")); } + } while(0); + do { // convert the 'Scale3' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Scale3, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcCartesianTransformationOperator3DnonUniform to be a `IfcReal`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCenterLineProfileDef>(const DB& db, const LIST& params, IfcCenterLineProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcArbitraryOpenProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcChiller>(const DB& db, const LIST& params, IfcChiller* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcChillerType>(const DB& db, const LIST& params, IfcChillerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcChimney>(const DB& db, const LIST& params, IfcChimney* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcChimneyType>(const DB& db, const LIST& params, IfcChimneyType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConic>(const DB& db, const LIST& params, IfcConic* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCurve*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcConic"); } do { // convert the 'Position' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcConic,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Position, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcConic to be a `IfcAxis2Placement`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCircle>(const DB& db, const LIST& params, IfcCircle* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConic*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcCircle"); } do { // convert the 'Radius' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Radius, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCircle to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCircleProfileDef>(const DB& db, const LIST& params, IfcCircleProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcCircleProfileDef"); } do { // convert the 'Radius' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcCircleProfileDef,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Radius, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcCircleProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCircleHollowProfileDef>(const DB& db, const LIST& params, IfcCircleHollowProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCircleProfileDef*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcCircleHollowProfileDef"); } do { // convert the 'WallThickness' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->WallThickness, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcCircleHollowProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCivilElement>(const DB& db, const LIST& params, IfcCivilElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCivilElementType>(const DB& db, const LIST& params, IfcCivilElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConnectedFaceSet>(const DB& db, const LIST& params, IfcConnectedFaceSet* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcConnectedFaceSet"); } do { // convert the 'CfsFaces' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcConnectedFaceSet,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->CfsFaces, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcConnectedFaceSet to be a `SET [1:?] OF IfcFace`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcClosedShell>(const DB& db, const LIST& params, IfcClosedShell* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConnectedFaceSet*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcClosedShell"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCoil>(const DB& db, const LIST& params, IfcCoil* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCoilType>(const DB& db, const LIST& params, IfcCoilType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcColourSpecification>(const DB& db, const LIST& params, IfcColourSpecification* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcColourSpecification"); } do { // convert the 'Name' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcColourSpecification,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcColourSpecification to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcColourRgb>(const DB& db, const LIST& params, IfcColourRgb* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcColourSpecification*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcColourRgb"); } do { // convert the 'Red' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Red, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } + } while(0); + do { // convert the 'Green' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Green, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } + } while(0); + do { // convert the 'Blue' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Blue, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcColumn>(const DB& db, const LIST& params, IfcColumn* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcColumnStandardCase>(const DB& db, const LIST& params, IfcColumnStandardCase* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcColumn*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcColumnType>(const DB& db, const LIST& params, IfcColumnType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCommunicationsAppliance>(const DB& db, const LIST& params, IfcCommunicationsAppliance* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminal*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCommunicationsApplianceType>(const DB& db, const LIST& params, IfcCommunicationsApplianceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertyAbstraction>(const DB& db, const LIST& params, IfcPropertyAbstraction* in) +{ + size_t base = 0; + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProperty>(const DB& db, const LIST& params, IfcProperty* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPropertyAbstraction*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcProperty"); } do { // convert the 'Name' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcProperty,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProperty to be a `IfcIdentifier`")); } + } while(0); + do { // convert the 'Description' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcProperty,2>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Description, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProperty to be a `IfcText`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcComplexProperty>(const DB& db, const LIST& params, IfcComplexProperty* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProperty*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcComplexProperty"); } do { // convert the 'UsageName' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->UsageName, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcComplexProperty to be a `IfcIdentifier`")); } + } while(0); + do { // convert the 'HasProperties' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->HasProperties, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcComplexProperty to be a `SET [1:?] OF IfcProperty`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertyDefinition>(const DB& db, const LIST& params, IfcPropertyDefinition* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRoot*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPropertyDefinition"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCompositeCurveSegment>(const DB& db, const LIST& params, IfcCompositeCurveSegment* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcCompositeCurveSegment"); } do { // convert the 'Transition' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcCompositeCurveSegment,3>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Transition, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCompositeCurveSegment to be a `IfcTransitionCode`")); } + } while(0); + do { // convert the 'SameSense' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcCompositeCurveSegment,3>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->SameSense, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCompositeCurveSegment to be a `IfcBoolean`")); } + } while(0); + do { // convert the 'ParentCurve' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcCompositeCurveSegment,3>::aux_is_derived[2]=true; break; } + try { GenericConvert( in->ParentCurve, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcCompositeCurveSegment to be a `IfcCurve`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCompositeProfileDef>(const DB& db, const LIST& params, IfcCompositeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowMovingDevice>(const DB& db, const LIST& params, IfcFlowMovingDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCompressor>(const DB& db, const LIST& params, IfcCompressor* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowMovingDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowMovingDeviceType>(const DB& db, const LIST& params, IfcFlowMovingDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCompressorType>(const DB& db, const LIST& params, IfcCompressorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowMovingDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCondenser>(const DB& db, const LIST& params, IfcCondenser* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCondenserType>(const DB& db, const LIST& params, IfcCondenserType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcResource>(const DB& db, const LIST& params, IfcResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConstructionResource>(const DB& db, const LIST& params, IfcConstructionResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConstructionEquipmentResource>(const DB& db, const LIST& params, IfcConstructionEquipmentResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTypeResource>(const DB& db, const LIST& params, IfcTypeResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConstructionResourceType>(const DB& db, const LIST& params, IfcConstructionResourceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConstructionEquipmentResourceType>(const DB& db, const LIST& params, IfcConstructionEquipmentResourceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResourceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConstructionMaterialResource>(const DB& db, const LIST& params, IfcConstructionMaterialResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConstructionMaterialResourceType>(const DB& db, const LIST& params, IfcConstructionMaterialResourceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResourceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConstructionProductResource>(const DB& db, const LIST& params, IfcConstructionProductResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConstructionProductResourceType>(const DB& db, const LIST& params, IfcConstructionProductResourceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResourceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcContext>(const DB& db, const LIST& params, IfcContext* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObjectDefinition*>(in)); + if (params.GetSize() < 9) { throw STEP::TypeError("expected 9 arguments to IfcContext"); } do { // convert the 'ObjectType' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcContext,5>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ObjectType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcContext to be a `IfcLabel`")); } + } while(0); + do { // convert the 'LongName' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcContext,5>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->LongName, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcContext to be a `IfcLabel`")); } + } while(0); + do { // convert the 'Phase' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcContext,5>::aux_is_derived[2]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Phase, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcContext to be a `IfcLabel`")); } + } while(0); + do { // convert the 'RepresentationContexts' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcContext,5>::aux_is_derived[3]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RepresentationContexts, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcContext to be a `SET [1:?] OF IfcRepresentationContext`")); } + } while(0); + do { // convert the 'UnitsInContext' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcContext,5>::aux_is_derived[4]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->UnitsInContext, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcContext to be a `IfcUnitAssignment`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcNamedUnit>(const DB& db, const LIST& params, IfcNamedUnit* in) +{ + size_t base = 0; + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcNamedUnit"); } do { // convert the 'Dimensions' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcNamedUnit,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Dimensions, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcNamedUnit to be a `IfcDimensionalExponents`")); } + } while(0); + do { // convert the 'UnitType' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcNamedUnit,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->UnitType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcNamedUnit to be a `IfcUnitEnum`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcContextDependentUnit>(const DB& db, const LIST& params, IfcContextDependentUnit* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcNamedUnit*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcController>(const DB& db, const LIST& params, IfcController* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcControllerType>(const DB& db, const LIST& params, IfcControllerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConversionBasedUnit>(const DB& db, const LIST& params, IfcConversionBasedUnit* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcNamedUnit*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcConversionBasedUnit"); } do { // convert the 'Name' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcConversionBasedUnit,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcConversionBasedUnit to be a `IfcLabel`")); } + } while(0); + do { // convert the 'ConversionFactor' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcConversionBasedUnit,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->ConversionFactor, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcConversionBasedUnit to be a `IfcMeasureWithUnit`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcConversionBasedUnitWithOffset>(const DB& db, const LIST& params, IfcConversionBasedUnitWithOffset* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConversionBasedUnit*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCooledBeam>(const DB& db, const LIST& params, IfcCooledBeam* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCooledBeamType>(const DB& db, const LIST& params, IfcCooledBeamType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCoolingTower>(const DB& db, const LIST& params, IfcCoolingTower* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCoolingTowerType>(const DB& db, const LIST& params, IfcCoolingTowerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCostItem>(const DB& db, const LIST& params, IfcCostItem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCostSchedule>(const DB& db, const LIST& params, IfcCostSchedule* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCovering>(const DB& db, const LIST& params, IfcCovering* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCoveringType>(const DB& db, const LIST& params, IfcCoveringType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCrewResource>(const DB& db, const LIST& params, IfcCrewResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCrewResourceType>(const DB& db, const LIST& params, IfcCrewResourceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResourceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCsgSolid>(const DB& db, const LIST& params, IfcCsgSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSolidModel*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCurtainWall>(const DB& db, const LIST& params, IfcCurtainWall* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCurtainWallType>(const DB& db, const LIST& params, IfcCurtainWallType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCurveBoundedPlane>(const DB& db, const LIST& params, IfcCurveBoundedPlane* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCurveBoundedSurface>(const DB& db, const LIST& params, IfcCurveBoundedSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPresentationStyle>(const DB& db, const LIST& params, IfcPresentationStyle* in) +{ + size_t base = 0; + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPresentationStyle"); } do { // convert the 'Name' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcPresentationStyle,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPresentationStyle to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElementarySurface>(const DB& db, const LIST& params, IfcElementarySurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSurface*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcElementarySurface"); } do { // convert the 'Position' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcElementarySurface,1>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Position, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcElementarySurface to be a `IfcAxis2Placement3D`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcCylindricalSurface>(const DB& db, const LIST& params, IfcCylindricalSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementarySurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDamper>(const DB& db, const LIST& params, IfcDamper* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowController*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDamperType>(const DB& db, const LIST& params, IfcDamperType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDerivedProfileDef>(const DB& db, const LIST& params, IfcDerivedProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDirection>(const DB& db, const LIST& params, IfcDirection* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcDirection"); } do { // convert the 'DirectionRatios' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->DirectionRatios, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcDirection to be a `LIST [2:3] OF IfcReal`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDiscreteAccessory>(const DB& db, const LIST& params, IfcDiscreteAccessory* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponent*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDiscreteAccessoryType>(const DB& db, const LIST& params, IfcDiscreteAccessoryType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionChamberElement>(const DB& db, const LIST& params, IfcDistributionChamberElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionChamberElementType>(const DB& db, const LIST& params, IfcDistributionChamberElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionSystem>(const DB& db, const LIST& params, IfcDistributionSystem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSystem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionCircuit>(const DB& db, const LIST& params, IfcDistributionCircuit* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionSystem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPort>(const DB& db, const LIST& params, IfcPort* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDistributionPort>(const DB& db, const LIST& params, IfcDistributionPort* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPort*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDoor>(const DB& db, const LIST& params, IfcDoor* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); + if (params.GetSize() < 13) { throw STEP::TypeError("expected 13 arguments to IfcDoor"); } do { // convert the 'OverallHeight' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcDoor,5>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->OverallHeight, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcDoor to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'OverallWidth' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcDoor,5>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->OverallWidth, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcDoor to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'PredefinedType' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcDoor,5>::aux_is_derived[2]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->PredefinedType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcDoor to be a `IfcDoorTypeEnum`")); } + } while(0); + do { // convert the 'OperationType' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcDoor,5>::aux_is_derived[3]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->OperationType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 11 to IfcDoor to be a `IfcDoorTypeOperationEnum`")); } + } while(0); + do { // convert the 'UserDefinedOperationType' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcDoor,5>::aux_is_derived[4]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->UserDefinedOperationType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 12 to IfcDoor to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertySetDefinition>(const DB& db, const LIST& params, IfcPropertySetDefinition* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPropertyDefinition*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPropertySetDefinition"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDoorStandardCase>(const DB& db, const LIST& params, IfcDoorStandardCase* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDoor*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDoorStyle>(const DB& db, const LIST& params, IfcDoorStyle* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDoorType>(const DB& db, const LIST& params, IfcDoorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDuctFitting>(const DB& db, const LIST& params, IfcDuctFitting* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFitting*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDuctFittingType>(const DB& db, const LIST& params, IfcDuctFittingType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFittingType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDuctSegment>(const DB& db, const LIST& params, IfcDuctSegment* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowSegment*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDuctSegmentType>(const DB& db, const LIST& params, IfcDuctSegmentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowSegmentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowTreatmentDevice>(const DB& db, const LIST& params, IfcFlowTreatmentDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDuctSilencer>(const DB& db, const LIST& params, IfcDuctSilencer* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTreatmentDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowTreatmentDeviceType>(const DB& db, const LIST& params, IfcFlowTreatmentDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcDuctSilencerType>(const DB& db, const LIST& params, IfcDuctSilencerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTreatmentDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEdge>(const DB& db, const LIST& params, IfcEdge* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEdgeCurve>(const DB& db, const LIST& params, IfcEdgeCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEdge*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLoop>(const DB& db, const LIST& params, IfcLoop* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEdgeLoop>(const DB& db, const LIST& params, IfcEdgeLoop* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLoop*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricAppliance>(const DB& db, const LIST& params, IfcElectricAppliance* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminal*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricApplianceType>(const DB& db, const LIST& params, IfcElectricApplianceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricDistributionBoard>(const DB& db, const LIST& params, IfcElectricDistributionBoard* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowController*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricDistributionBoardType>(const DB& db, const LIST& params, IfcElectricDistributionBoardType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowStorageDevice>(const DB& db, const LIST& params, IfcFlowStorageDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricFlowStorageDevice>(const DB& db, const LIST& params, IfcElectricFlowStorageDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowStorageDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowStorageDeviceType>(const DB& db, const LIST& params, IfcFlowStorageDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionFlowElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricFlowStorageDeviceType>(const DB& db, const LIST& params, IfcElectricFlowStorageDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowStorageDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricGenerator>(const DB& db, const LIST& params, IfcElectricGenerator* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricGeneratorType>(const DB& db, const LIST& params, IfcElectricGeneratorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricMotor>(const DB& db, const LIST& params, IfcElectricMotor* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricMotorType>(const DB& db, const LIST& params, IfcElectricMotorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricTimeControl>(const DB& db, const LIST& params, IfcElectricTimeControl* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowController*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElectricTimeControlType>(const DB& db, const LIST& params, IfcElectricTimeControlType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElementAssembly>(const DB& db, const LIST& params, IfcElementAssembly* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElementAssemblyType>(const DB& db, const LIST& params, IfcElementAssemblyType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcQuantitySet>(const DB& db, const LIST& params, IfcQuantitySet* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPropertySetDefinition*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcQuantitySet"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcElementQuantity>(const DB& db, const LIST& params, IfcElementQuantity* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcQuantitySet*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcElementQuantity"); } do { // convert the 'MethodOfMeasurement' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->MethodOfMeasurement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcElementQuantity to be a `IfcLabel`")); } + } while(0); + do { // convert the 'Quantities' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Quantities, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcElementQuantity to be a `SET [1:?] OF IfcPhysicalQuantity`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEllipse>(const DB& db, const LIST& params, IfcEllipse* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConic*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcEllipse"); } do { // convert the 'SemiAxis1' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->SemiAxis1, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcEllipse to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'SemiAxis2' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->SemiAxis2, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcEllipse to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEllipseProfileDef>(const DB& db, const LIST& params, IfcEllipseProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEngine>(const DB& db, const LIST& params, IfcEngine* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEngineType>(const DB& db, const LIST& params, IfcEngineType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEvaporativeCooler>(const DB& db, const LIST& params, IfcEvaporativeCooler* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEvaporativeCoolerType>(const DB& db, const LIST& params, IfcEvaporativeCoolerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEvaporator>(const DB& db, const LIST& params, IfcEvaporator* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEvaporatorType>(const DB& db, const LIST& params, IfcEvaporatorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProcess>(const DB& db, const LIST& params, IfcProcess* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEvent>(const DB& db, const LIST& params, IfcEvent* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProcess*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTypeProcess>(const DB& db, const LIST& params, IfcTypeProcess* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeObject*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcEventType>(const DB& db, const LIST& params, IfcEventType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeProcess*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcExternalSpatialStructureElement>(const DB& db, const LIST& params, IfcExternalSpatialStructureElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcExternalSpatialElement>(const DB& db, const LIST& params, IfcExternalSpatialElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcExternalSpatialStructureElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSweptAreaSolid>(const DB& db, const LIST& params, IfcSweptAreaSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSolidModel*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcSweptAreaSolid"); } do { // convert the 'SweptArea' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcSweptAreaSolid,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->SweptArea, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSweptAreaSolid to be a `IfcProfileDef`")); } + } while(0); + do { // convert the 'Position' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcSweptAreaSolid,2>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Position, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSweptAreaSolid to be a `IfcAxis2Placement3D`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcExtrudedAreaSolid>(const DB& db, const LIST& params, IfcExtrudedAreaSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSweptAreaSolid*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcExtrudedAreaSolid"); } do { // convert the 'ExtrudedDirection' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcExtrudedAreaSolid,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->ExtrudedDirection, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcExtrudedAreaSolid to be a `IfcDirection`")); } + } while(0); + do { // convert the 'Depth' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcExtrudedAreaSolid,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->Depth, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcExtrudedAreaSolid to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcExtrudedAreaSolidTapered>(const DB& db, const LIST& params, IfcExtrudedAreaSolidTapered* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcExtrudedAreaSolid*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFaceBasedSurfaceModel>(const DB& db, const LIST& params, IfcFaceBasedSurfaceModel* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcFaceBasedSurfaceModel"); } do { // convert the 'FbsmFaces' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->FbsmFaces, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFaceBasedSurfaceModel to be a `SET [1:?] OF IfcConnectedFaceSet`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFaceBound>(const DB& db, const LIST& params, IfcFaceBound* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcFaceBound"); } do { // convert the 'Bound' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcFaceBound,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Bound, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFaceBound to be a `IfcLoop`")); } + } while(0); + do { // convert the 'Orientation' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcFaceBound,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->Orientation, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcFaceBound to be a `IfcBoolean`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFaceOuterBound>(const DB& db, const LIST& params, IfcFaceOuterBound* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFaceBound*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcFaceOuterBound"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFacetedBrep>(const DB& db, const LIST& params, IfcFacetedBrep* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcManifoldSolidBrep*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFacetedBrepWithVoids>(const DB& db, const LIST& params, IfcFacetedBrepWithVoids* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFacetedBrep*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFan>(const DB& db, const LIST& params, IfcFan* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowMovingDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFanType>(const DB& db, const LIST& params, IfcFanType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowMovingDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFastener>(const DB& db, const LIST& params, IfcFastener* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponent*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFastenerType>(const DB& db, const LIST& params, IfcFastenerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFeatureElement>(const DB& db, const LIST& params, IfcFeatureElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcFeatureElement"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFeatureElementAddition>(const DB& db, const LIST& params, IfcFeatureElementAddition* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFeatureElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFeatureElementSubtraction>(const DB& db, const LIST& params, IfcFeatureElementSubtraction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFeatureElement*>(in)); + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcFeatureElementSubtraction"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFillAreaStyleHatching>(const DB& db, const LIST& params, IfcFillAreaStyleHatching* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFillAreaStyleTiles>(const DB& db, const LIST& params, IfcFillAreaStyleTiles* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFilter>(const DB& db, const LIST& params, IfcFilter* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTreatmentDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFilterType>(const DB& db, const LIST& params, IfcFilterType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTreatmentDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFireSuppressionTerminal>(const DB& db, const LIST& params, IfcFireSuppressionTerminal* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminal*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFireSuppressionTerminalType>(const DB& db, const LIST& params, IfcFireSuppressionTerminalType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFixedReferenceSweptAreaSolid>(const DB& db, const LIST& params, IfcFixedReferenceSweptAreaSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSweptAreaSolid*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowInstrument>(const DB& db, const LIST& params, IfcFlowInstrument* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowInstrumentType>(const DB& db, const LIST& params, IfcFlowInstrumentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowMeter>(const DB& db, const LIST& params, IfcFlowMeter* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowController*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFlowMeterType>(const DB& db, const LIST& params, IfcFlowMeterType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFooting>(const DB& db, const LIST& params, IfcFooting* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFootingType>(const DB& db, const LIST& params, IfcFootingType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFurnishingElement>(const DB& db, const LIST& params, IfcFurnishingElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFurnishingElementType>(const DB& db, const LIST& params, IfcFurnishingElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFurniture>(const DB& db, const LIST& params, IfcFurniture* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFurnishingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcFurnitureType>(const DB& db, const LIST& params, IfcFurnitureType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFurnishingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGeographicElement>(const DB& db, const LIST& params, IfcGeographicElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGeographicElementType>(const DB& db, const LIST& params, IfcGeographicElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGeometricSet>(const DB& db, const LIST& params, IfcGeometricSet* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGeometricCurveSet>(const DB& db, const LIST& params, IfcGeometricCurveSet* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricSet*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRepresentationContext>(const DB& db, const LIST& params, IfcRepresentationContext* in) +{ + size_t base = 0; + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcRepresentationContext"); } do { // convert the 'ContextIdentifier' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRepresentationContext,2>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ContextIdentifier, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentationContext to be a `IfcLabel`")); } + } while(0); + do { // convert the 'ContextType' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRepresentationContext,2>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ContextType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentationContext to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGeometricRepresentationContext>(const DB& db, const LIST& params, IfcGeometricRepresentationContext* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentationContext*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcGeometricRepresentationContext"); } do { // convert the 'CoordinateSpaceDimension' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcGeometricRepresentationContext,4>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->CoordinateSpaceDimension, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcGeometricRepresentationContext to be a `IfcDimensionCount`")); } + } while(0); + do { // convert the 'Precision' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcGeometricRepresentationContext,4>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Precision, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcGeometricRepresentationContext to be a `IfcReal`")); } + } while(0); + do { // convert the 'WorldCoordinateSystem' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcGeometricRepresentationContext,4>::aux_is_derived[2]=true; break; } + try { GenericConvert( in->WorldCoordinateSystem, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcGeometricRepresentationContext to be a `IfcAxis2Placement`")); } + } while(0); + do { // convert the 'TrueNorth' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcGeometricRepresentationContext,4>::aux_is_derived[3]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->TrueNorth, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcGeometricRepresentationContext to be a `IfcDirection`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGeometricRepresentationSubContext>(const DB& db, const LIST& params, IfcGeometricRepresentationSubContext* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationContext*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGrid>(const DB& db, const LIST& params, IfcGrid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcObjectPlacement>(const DB& db, const LIST& params, IfcObjectPlacement* in) +{ + size_t base = 0; + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcGridPlacement>(const DB& db, const LIST& params, IfcGridPlacement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObjectPlacement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcHeatExchanger>(const DB& db, const LIST& params, IfcHeatExchanger* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcHeatExchangerType>(const DB& db, const LIST& params, IfcHeatExchangerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcHumidifier>(const DB& db, const LIST& params, IfcHumidifier* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcHumidifierType>(const DB& db, const LIST& params, IfcHumidifierType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcIShapeProfileDef>(const DB& db, const LIST& params, IfcIShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); + if (params.GetSize() < 10) { throw STEP::TypeError("expected 10 arguments to IfcIShapeProfileDef"); } do { // convert the 'OverallWidth' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->OverallWidth, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'OverallDepth' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->OverallDepth, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'WebThickness' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->WebThickness, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'FlangeThickness' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->FlangeThickness, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'FilletRadius' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->FilletRadius, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcIShapeProfileDef to be a `IfcNonNegativeLengthMeasure`")); } + } while(0); + do { // convert the 'FlangeEdgeRadius' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->FlangeEdgeRadius, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcIShapeProfileDef to be a `IfcNonNegativeLengthMeasure`")); } + } while(0); + do { // convert the 'FlangeSlope' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->FlangeSlope, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcIShapeProfileDef to be a `IfcPlaneAngleMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcIndexedPolyCurve>(const DB& db, const LIST& params, IfcIndexedPolyCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTessellatedItem>(const DB& db, const LIST& params, IfcTessellatedItem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcIndexedPolygonalFace>(const DB& db, const LIST& params, IfcIndexedPolygonalFace* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTessellatedItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcIndexedPolygonalFaceWithVoids>(const DB& db, const LIST& params, IfcIndexedPolygonalFaceWithVoids* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcIndexedPolygonalFace*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcInterceptor>(const DB& db, const LIST& params, IfcInterceptor* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTreatmentDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcInterceptorType>(const DB& db, const LIST& params, IfcInterceptorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTreatmentDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceCurve>(const DB& db, const LIST& params, IfcSurfaceCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcIntersectionCurve>(const DB& db, const LIST& params, IfcIntersectionCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSurfaceCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcInventory>(const DB& db, const LIST& params, IfcInventory* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGroup*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcJunctionBox>(const DB& db, const LIST& params, IfcJunctionBox* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFitting*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcJunctionBoxType>(const DB& db, const LIST& params, IfcJunctionBoxType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFittingType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLShapeProfileDef>(const DB& db, const LIST& params, IfcLShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLaborResource>(const DB& db, const LIST& params, IfcLaborResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLaborResourceType>(const DB& db, const LIST& params, IfcLaborResourceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResourceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLamp>(const DB& db, const LIST& params, IfcLamp* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminal*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLampType>(const DB& db, const LIST& params, IfcLampType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightFixture>(const DB& db, const LIST& params, IfcLightFixture* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminal*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightFixtureType>(const DB& db, const LIST& params, IfcLightFixtureType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightSource>(const DB& db, const LIST& params, IfcLightSource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightSourceAmbient>(const DB& db, const LIST& params, IfcLightSourceAmbient* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLightSource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightSourceDirectional>(const DB& db, const LIST& params, IfcLightSourceDirectional* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLightSource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightSourceGoniometric>(const DB& db, const LIST& params, IfcLightSourceGoniometric* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLightSource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightSourcePositional>(const DB& db, const LIST& params, IfcLightSourcePositional* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLightSource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLightSourceSpot>(const DB& db, const LIST& params, IfcLightSourceSpot* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLightSourcePositional*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLine>(const DB& db, const LIST& params, IfcLine* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCurve*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcLine"); } do { // convert the 'Pnt' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Pnt, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcLine to be a `IfcCartesianPoint`")); } + } while(0); + do { // convert the 'Dir' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Dir, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcLine to be a `IfcVector`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcLocalPlacement>(const DB& db, const LIST& params, IfcLocalPlacement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcObjectPlacement*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcLocalPlacement"); } do { // convert the 'PlacementRelTo' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->PlacementRelTo, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcLocalPlacement to be a `IfcObjectPlacement`")); } + } while(0); + do { // convert the 'RelativePlacement' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelativePlacement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcLocalPlacement to be a `IfcAxis2Placement`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMappedItem>(const DB& db, const LIST& params, IfcMappedItem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentationItem*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcMappedItem"); } do { // convert the 'MappingSource' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->MappingSource, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcMappedItem to be a `IfcRepresentationMap`")); } + } while(0); + do { // convert the 'MappingTarget' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->MappingTarget, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcMappedItem to be a `IfcCartesianTransformationOperator`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProductRepresentation>(const DB& db, const LIST& params, IfcProductRepresentation* in) +{ + size_t base = 0; + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcProductRepresentation"); } do { // convert the 'Name' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcProductRepresentation,3>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProductRepresentation to be a `IfcLabel`")); } + } while(0); + do { // convert the 'Description' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcProductRepresentation,3>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Description, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProductRepresentation to be a `IfcText`")); } + } while(0); + do { // convert the 'Representations' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcProductRepresentation,3>::aux_is_derived[2]=true; break; } + try { GenericConvert( in->Representations, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcProductRepresentation to be a `LIST [1:?] OF IfcRepresentation`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMaterialDefinitionRepresentation>(const DB& db, const LIST& params, IfcMaterialDefinitionRepresentation* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProductRepresentation*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMeasureWithUnit>(const DB& db, const LIST& params, IfcMeasureWithUnit* in) +{ + size_t base = 0; + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcMeasureWithUnit"); } do { // convert the 'ValueComponent' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->ValueComponent, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcMeasureWithUnit to be a `IfcValue`")); } + } while(0); + do { // convert the 'UnitComponent' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->UnitComponent, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcMeasureWithUnit to be a `IfcUnit`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMechanicalFastener>(const DB& db, const LIST& params, IfcMechanicalFastener* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponent*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMechanicalFastenerType>(const DB& db, const LIST& params, IfcMechanicalFastenerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMedicalDevice>(const DB& db, const LIST& params, IfcMedicalDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminal*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMedicalDeviceType>(const DB& db, const LIST& params, IfcMedicalDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMember>(const DB& db, const LIST& params, IfcMember* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMemberStandardCase>(const DB& db, const LIST& params, IfcMemberStandardCase* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcMember*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMemberType>(const DB& db, const LIST& params, IfcMemberType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMirroredProfileDef>(const DB& db, const LIST& params, IfcMirroredProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDerivedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMotorConnection>(const DB& db, const LIST& params, IfcMotorConnection* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcMotorConnectionType>(const DB& db, const LIST& params, IfcMotorConnectionType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOccupant>(const DB& db, const LIST& params, IfcOccupant* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcActor*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOffsetCurve2D>(const DB& db, const LIST& params, IfcOffsetCurve2D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOffsetCurve3D>(const DB& db, const LIST& params, IfcOffsetCurve3D* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOpenShell>(const DB& db, const LIST& params, IfcOpenShell* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConnectedFaceSet*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOpeningElement>(const DB& db, const LIST& params, IfcOpeningElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFeatureElementSubtraction*>(in)); + if (params.GetSize() < 9) { throw STEP::TypeError("expected 9 arguments to IfcOpeningElement"); } do { // convert the 'PredefinedType' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcOpeningElement,1>::aux_is_derived[0]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->PredefinedType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcOpeningElement to be a `IfcOpeningElementTypeEnum`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOpeningStandardCase>(const DB& db, const LIST& params, IfcOpeningStandardCase* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcOpeningElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOrientedEdge>(const DB& db, const LIST& params, IfcOrientedEdge* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEdge*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOuterBoundaryCurve>(const DB& db, const LIST& params, IfcOuterBoundaryCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundaryCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOutlet>(const DB& db, const LIST& params, IfcOutlet* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminal*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcOutletType>(const DB& db, const LIST& params, IfcOutletType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPath>(const DB& db, const LIST& params, IfcPath* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPcurve>(const DB& db, const LIST& params, IfcPcurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPerformanceHistory>(const DB& db, const LIST& params, IfcPerformanceHistory* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPermit>(const DB& db, const LIST& params, IfcPermit* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPile>(const DB& db, const LIST& params, IfcPile* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPileType>(const DB& db, const LIST& params, IfcPileType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPipeFitting>(const DB& db, const LIST& params, IfcPipeFitting* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFitting*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPipeFittingType>(const DB& db, const LIST& params, IfcPipeFittingType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowFittingType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPipeSegment>(const DB& db, const LIST& params, IfcPipeSegment* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowSegment*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPipeSegmentType>(const DB& db, const LIST& params, IfcPipeSegmentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowSegmentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPlanarExtent>(const DB& db, const LIST& params, IfcPlanarExtent* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPlanarBox>(const DB& db, const LIST& params, IfcPlanarBox* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPlanarExtent*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPlane>(const DB& db, const LIST& params, IfcPlane* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementarySurface*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPlane"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPlate>(const DB& db, const LIST& params, IfcPlate* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPlateStandardCase>(const DB& db, const LIST& params, IfcPlateStandardCase* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPlate*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPlateType>(const DB& db, const LIST& params, IfcPlateType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPointOnCurve>(const DB& db, const LIST& params, IfcPointOnCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPoint*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPointOnSurface>(const DB& db, const LIST& params, IfcPointOnSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPoint*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPolyLoop>(const DB& db, const LIST& params, IfcPolyLoop* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLoop*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPolyLoop"); } do { // convert the 'Polygon' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Polygon, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPolyLoop to be a `LIST [3:?] OF IfcCartesianPoint`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPolygonalBoundedHalfSpace>(const DB& db, const LIST& params, IfcPolygonalBoundedHalfSpace* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcHalfSpaceSolid*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPolygonalBoundedHalfSpace"); } do { // convert the 'Position' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Position, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPolygonalBoundedHalfSpace to be a `IfcAxis2Placement3D`")); } + } while(0); + do { // convert the 'PolygonalBoundary' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->PolygonalBoundary, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPolygonalBoundedHalfSpace to be a `IfcBoundedCurve`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTessellatedFaceSet>(const DB& db, const LIST& params, IfcTessellatedFaceSet* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTessellatedItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPolygonalFaceSet>(const DB& db, const LIST& params, IfcPolygonalFaceSet* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTessellatedFaceSet*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPolyline>(const DB& db, const LIST& params, IfcPolyline* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedCurve*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPolyline"); } do { // convert the 'Points' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Points, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPolyline to be a `LIST [2:?] OF IfcCartesianPoint`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPresentationStyleAssignment>(const DB& db, const LIST& params, IfcPresentationStyleAssignment* in) +{ + size_t base = 0; + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPresentationStyleAssignment"); } do { // convert the 'Styles' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Styles, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPresentationStyleAssignment to be a `SET [1:?] OF IfcPresentationStyleSelect`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProcedure>(const DB& db, const LIST& params, IfcProcedure* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProcess*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProcedureType>(const DB& db, const LIST& params, IfcProcedureType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeProcess*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProductDefinitionShape>(const DB& db, const LIST& params, IfcProductDefinitionShape* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProductRepresentation*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProject>(const DB& db, const LIST& params, IfcProject* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcContext*>(in)); + if (params.GetSize() < 9) { throw STEP::TypeError("expected 9 arguments to IfcProject"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProjectLibrary>(const DB& db, const LIST& params, IfcProjectLibrary* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcContext*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProjectOrder>(const DB& db, const LIST& params, IfcProjectOrder* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProjectionElement>(const DB& db, const LIST& params, IfcProjectionElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFeatureElementAddition*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSimpleProperty>(const DB& db, const LIST& params, IfcSimpleProperty* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProperty*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcSimpleProperty"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertyBoundedValue>(const DB& db, const LIST& params, IfcPropertyBoundedValue* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSimpleProperty*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertyEnumeratedValue>(const DB& db, const LIST& params, IfcPropertyEnumeratedValue* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSimpleProperty*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertyListValue>(const DB& db, const LIST& params, IfcPropertyListValue* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSimpleProperty*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPropertyListValue"); } do { // convert the 'ListValues' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ListValues, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPropertyListValue to be a `LIST [1:?] OF IfcValue`")); } + } while(0); + do { // convert the 'Unit' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Unit, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPropertyListValue to be a `IfcUnit`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertyReferenceValue>(const DB& db, const LIST& params, IfcPropertyReferenceValue* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSimpleProperty*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertySet>(const DB& db, const LIST& params, IfcPropertySet* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPropertySetDefinition*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcPropertySet"); } do { // convert the 'HasProperties' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->HasProperties, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcPropertySet to be a `SET [1:?] OF IfcProperty`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertySingleValue>(const DB& db, const LIST& params, IfcPropertySingleValue* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSimpleProperty*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPropertySingleValue"); } do { // convert the 'NominalValue' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->NominalValue, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPropertySingleValue to be a `IfcValue`")); } + } while(0); + do { // convert the 'Unit' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Unit, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPropertySingleValue to be a `IfcUnit`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPropertyTableValue>(const DB& db, const LIST& params, IfcPropertyTableValue* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSimpleProperty*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProtectiveDevice>(const DB& db, const LIST& params, IfcProtectiveDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowController*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProtectiveDeviceTrippingUnit>(const DB& db, const LIST& params, IfcProtectiveDeviceTrippingUnit* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProtectiveDeviceTrippingUnitType>(const DB& db, const LIST& params, IfcProtectiveDeviceTrippingUnitType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProtectiveDeviceType>(const DB& db, const LIST& params, IfcProtectiveDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcProxy>(const DB& db, const LIST& params, IfcProxy* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPump>(const DB& db, const LIST& params, IfcPump* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowMovingDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcPumpType>(const DB& db, const LIST& params, IfcPumpType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowMovingDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRailing>(const DB& db, const LIST& params, IfcRailing* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRailingType>(const DB& db, const LIST& params, IfcRailingType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRamp>(const DB& db, const LIST& params, IfcRamp* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRampFlight>(const DB& db, const LIST& params, IfcRampFlight* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRampFlightType>(const DB& db, const LIST& params, IfcRampFlightType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRampType>(const DB& db, const LIST& params, IfcRampType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRationalBSplineCurveWithKnots>(const DB& db, const LIST& params, IfcRationalBSplineCurveWithKnots* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBSplineCurveWithKnots*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRationalBSplineSurfaceWithKnots>(const DB& db, const LIST& params, IfcRationalBSplineSurfaceWithKnots* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBSplineSurfaceWithKnots*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRectangleProfileDef>(const DB& db, const LIST& params, IfcRectangleProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcRectangleProfileDef"); } do { // convert the 'XDim' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRectangleProfileDef,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->XDim, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRectangleProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'YDim' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRectangleProfileDef,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->YDim, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRectangleProfileDef to be a `IfcPositiveLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRectangleHollowProfileDef>(const DB& db, const LIST& params, IfcRectangleHollowProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRectangleProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRectangularPyramid>(const DB& db, const LIST& params, IfcRectangularPyramid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCsgPrimitive3D*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRectangularTrimmedSurface>(const DB& db, const LIST& params, IfcRectangularTrimmedSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcReinforcingElement>(const DB& db, const LIST& params, IfcReinforcingElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponent*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcReinforcingBar>(const DB& db, const LIST& params, IfcReinforcingBar* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcReinforcingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcReinforcingElementType>(const DB& db, const LIST& params, IfcReinforcingElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcReinforcingBarType>(const DB& db, const LIST& params, IfcReinforcingBarType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcReinforcingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcReinforcingMesh>(const DB& db, const LIST& params, IfcReinforcingMesh* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcReinforcingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcReinforcingMeshType>(const DB& db, const LIST& params, IfcReinforcingMeshType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcReinforcingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelationship>(const DB& db, const LIST& params, IfcRelationship* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRoot*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRelationship"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelDecomposes>(const DB& db, const LIST& params, IfcRelDecomposes* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelationship*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRelDecomposes"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelAggregates>(const DB& db, const LIST& params, IfcRelAggregates* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelDecomposes*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelAggregates"); } do { // convert the 'RelatingObject' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatingObject, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelAggregates to be a `IfcObjectDefinition`")); } + } while(0); + do { // convert the 'RelatedObjects' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatedObjects, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelAggregates to be a `SET [1:?] OF IfcObjectDefinition`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelConnects>(const DB& db, const LIST& params, IfcRelConnects* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelationship*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRelConnects"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelContainedInSpatialStructure>(const DB& db, const LIST& params, IfcRelContainedInSpatialStructure* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelConnects*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelContainedInSpatialStructure"); } do { // convert the 'RelatedElements' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatedElements, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelContainedInSpatialStructure to be a `SET [1:?] OF IfcProduct`")); } + } while(0); + do { // convert the 'RelatingStructure' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatingStructure, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelContainedInSpatialStructure to be a `IfcSpatialElement`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelDefines>(const DB& db, const LIST& params, IfcRelDefines* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelationship*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRelDefines"); } return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelDefinesByProperties>(const DB& db, const LIST& params, IfcRelDefinesByProperties* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelDefines*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelDefinesByProperties"); } do { // convert the 'RelatedObjects' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatedObjects, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelDefinesByProperties to be a `SET [1:?] OF IfcObjectDefinition`")); } + } while(0); + do { // convert the 'RelatingPropertyDefinition' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatingPropertyDefinition, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelDefinesByProperties to be a `IfcPropertySetDefinitionSelect`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelFillsElement>(const DB& db, const LIST& params, IfcRelFillsElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelConnects*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelFillsElement"); } do { // convert the 'RelatingOpeningElement' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatingOpeningElement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelFillsElement to be a `IfcOpeningElement`")); } + } while(0); + do { // convert the 'RelatedBuildingElement' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatedBuildingElement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelFillsElement to be a `IfcElement`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRelVoidsElement>(const DB& db, const LIST& params, IfcRelVoidsElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRelDecomposes*>(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelVoidsElement"); } do { // convert the 'RelatingBuildingElement' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatingBuildingElement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelVoidsElement to be a `IfcElement`")); } + } while(0); + do { // convert the 'RelatedOpeningElement' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->RelatedOpeningElement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelVoidsElement to be a `IfcFeatureElementSubtraction`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcReparametrisedCompositeCurveSegment>(const DB& db, const LIST& params, IfcReparametrisedCompositeCurveSegment* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCompositeCurveSegment*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRepresentation>(const DB& db, const LIST& params, IfcRepresentation* in) +{ + size_t base = 0; + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRepresentation"); } do { // convert the 'ContextOfItems' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRepresentation,4>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->ContextOfItems, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentation to be a `IfcRepresentationContext`")); } + } while(0); + do { // convert the 'RepresentationIdentifier' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRepresentation,4>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RepresentationIdentifier, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentation to be a `IfcLabel`")); } + } while(0); + do { // convert the 'RepresentationType' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRepresentation,4>::aux_is_derived[2]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RepresentationType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRepresentation to be a `IfcLabel`")); } + } while(0); + do { // convert the 'Items' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRepresentation,4>::aux_is_derived[3]=true; break; } + try { GenericConvert( in->Items, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRepresentation to be a `SET [1:?] OF IfcRepresentationItem`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRepresentationMap>(const DB& db, const LIST& params, IfcRepresentationMap* in) +{ + size_t base = 0; + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcRepresentationMap"); } do { // convert the 'MappingOrigin' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->MappingOrigin, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentationMap to be a `IfcAxis2Placement`")); } + } while(0); + do { // convert the 'MappedRepresentation' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->MappedRepresentation, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentationMap to be a `IfcRepresentation`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRevolvedAreaSolid>(const DB& db, const LIST& params, IfcRevolvedAreaSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSweptAreaSolid*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRevolvedAreaSolid"); } do { // convert the 'Axis' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRevolvedAreaSolid,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Axis, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRevolvedAreaSolid to be a `IfcAxis1Placement`")); } + } while(0); + do { // convert the 'Angle' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcRevolvedAreaSolid,2>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->Angle, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRevolvedAreaSolid to be a `IfcPlaneAngleMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRevolvedAreaSolidTapered>(const DB& db, const LIST& params, IfcRevolvedAreaSolidTapered* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRevolvedAreaSolid*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRightCircularCone>(const DB& db, const LIST& params, IfcRightCircularCone* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCsgPrimitive3D*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRightCircularCylinder>(const DB& db, const LIST& params, IfcRightCircularCylinder* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCsgPrimitive3D*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRoof>(const DB& db, const LIST& params, IfcRoof* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRoofType>(const DB& db, const LIST& params, IfcRoofType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcRoundedRectangleProfileDef>(const DB& db, const LIST& params, IfcRoundedRectangleProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRectangleProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSIUnit>(const DB& db, const LIST& params, IfcSIUnit* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcNamedUnit*>(in)); + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcSIUnit"); } do { // convert the 'Prefix' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Prefix, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSIUnit to be a `IfcSIPrefix`")); } + } while(0); + do { // convert the 'Name' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSIUnit to be a `IfcSIUnitName`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSanitaryTerminal>(const DB& db, const LIST& params, IfcSanitaryTerminal* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminal*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSanitaryTerminalType>(const DB& db, const LIST& params, IfcSanitaryTerminalType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSeamCurve>(const DB& db, const LIST& params, IfcSeamCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSurfaceCurve*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSectionedSpine>(const DB& db, const LIST& params, IfcSectionedSpine* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSensor>(const DB& db, const LIST& params, IfcSensor* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSensorType>(const DB& db, const LIST& params, IfcSensorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcShadingDevice>(const DB& db, const LIST& params, IfcShadingDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcShadingDeviceType>(const DB& db, const LIST& params, IfcShadingDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcShapeModel>(const DB& db, const LIST& params, IfcShapeModel* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentation*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcShapeRepresentation>(const DB& db, const LIST& params, IfcShapeRepresentation* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcShapeModel*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcShellBasedSurfaceModel>(const DB& db, const LIST& params, IfcShellBasedSurfaceModel* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcShellBasedSurfaceModel"); } do { // convert the 'SbsmBoundary' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->SbsmBoundary, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcShellBasedSurfaceModel to be a `SET [1:?] OF IfcShell`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSite>(const DB& db, const LIST& params, IfcSite* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialStructureElement*>(in)); + if (params.GetSize() < 14) { throw STEP::TypeError("expected 14 arguments to IfcSite"); } do { // convert the 'RefLatitude' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RefLatitude, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcSite to be a `IfcCompoundPlaneAngleMeasure`")); } + } while(0); + do { // convert the 'RefLongitude' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RefLongitude, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcSite to be a `IfcCompoundPlaneAngleMeasure`")); } + } while(0); + do { // convert the 'RefElevation' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->RefElevation, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 11 to IfcSite to be a `IfcLengthMeasure`")); } + } while(0); + do { // convert the 'LandTitleNumber' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->LandTitleNumber, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 12 to IfcSite to be a `IfcLabel`")); } + } while(0); + do { // convert the 'SiteAddress' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->SiteAddress, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 13 to IfcSite to be a `IfcPostalAddress`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSlab>(const DB& db, const LIST& params, IfcSlab* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSlabElementedCase>(const DB& db, const LIST& params, IfcSlabElementedCase* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSlab*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSlabStandardCase>(const DB& db, const LIST& params, IfcSlabStandardCase* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSlab*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSlabType>(const DB& db, const LIST& params, IfcSlabType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSolarDevice>(const DB& db, const LIST& params, IfcSolarDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSolarDeviceType>(const DB& db, const LIST& params, IfcSolarDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpace>(const DB& db, const LIST& params, IfcSpace* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialStructureElement*>(in)); + if (params.GetSize() < 11) { throw STEP::TypeError("expected 11 arguments to IfcSpace"); } do { // convert the 'PredefinedType' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->PredefinedType, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcSpace to be a `IfcSpaceTypeEnum`")); } + } while(0); + do { // convert the 'ElevationWithFlooring' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ElevationWithFlooring, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcSpace to be a `IfcLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpaceHeater>(const DB& db, const LIST& params, IfcSpaceHeater* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminal*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpaceHeaterType>(const DB& db, const LIST& params, IfcSpaceHeaterType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpatialElementType>(const DB& db, const LIST& params, IfcSpatialElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpatialStructureElementType>(const DB& db, const LIST& params, IfcSpatialStructureElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpaceType>(const DB& db, const LIST& params, IfcSpaceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialStructureElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpatialZone>(const DB& db, const LIST& params, IfcSpatialZone* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSpatialZoneType>(const DB& db, const LIST& params, IfcSpatialZoneType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSpatialElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSphere>(const DB& db, const LIST& params, IfcSphere* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcCsgPrimitive3D*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSphericalSurface>(const DB& db, const LIST& params, IfcSphericalSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementarySurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStackTerminal>(const DB& db, const LIST& params, IfcStackTerminal* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminal*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStackTerminalType>(const DB& db, const LIST& params, IfcStackTerminalType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStair>(const DB& db, const LIST& params, IfcStair* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStairFlight>(const DB& db, const LIST& params, IfcStairFlight* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStairFlightType>(const DB& db, const LIST& params, IfcStairFlightType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStairType>(const DB& db, const LIST& params, IfcStairType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralActivity>(const DB& db, const LIST& params, IfcStructuralActivity* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralAction>(const DB& db, const LIST& params, IfcStructuralAction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralActivity*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralAnalysisModel>(const DB& db, const LIST& params, IfcStructuralAnalysisModel* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSystem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralItem>(const DB& db, const LIST& params, IfcStructuralItem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralConnection>(const DB& db, const LIST& params, IfcStructuralConnection* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralCurveAction>(const DB& db, const LIST& params, IfcStructuralCurveAction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralAction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralCurveConnection>(const DB& db, const LIST& params, IfcStructuralCurveConnection* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralConnection*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralMember>(const DB& db, const LIST& params, IfcStructuralMember* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralCurveMember>(const DB& db, const LIST& params, IfcStructuralCurveMember* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralMember*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralCurveMemberVarying>(const DB& db, const LIST& params, IfcStructuralCurveMemberVarying* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralCurveMember*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralReaction>(const DB& db, const LIST& params, IfcStructuralReaction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralActivity*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralCurveReaction>(const DB& db, const LIST& params, IfcStructuralCurveReaction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralReaction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralLinearAction>(const DB& db, const LIST& params, IfcStructuralLinearAction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralCurveAction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralLoadGroup>(const DB& db, const LIST& params, IfcStructuralLoadGroup* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGroup*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralLoadCase>(const DB& db, const LIST& params, IfcStructuralLoadCase* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralLoadGroup*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralSurfaceAction>(const DB& db, const LIST& params, IfcStructuralSurfaceAction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralAction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralPlanarAction>(const DB& db, const LIST& params, IfcStructuralPlanarAction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralSurfaceAction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralPointAction>(const DB& db, const LIST& params, IfcStructuralPointAction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralAction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralPointConnection>(const DB& db, const LIST& params, IfcStructuralPointConnection* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralConnection*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralPointReaction>(const DB& db, const LIST& params, IfcStructuralPointReaction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralReaction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralResultGroup>(const DB& db, const LIST& params, IfcStructuralResultGroup* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGroup*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralSurfaceConnection>(const DB& db, const LIST& params, IfcStructuralSurfaceConnection* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralConnection*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralSurfaceMember>(const DB& db, const LIST& params, IfcStructuralSurfaceMember* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralMember*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralSurfaceMemberVarying>(const DB& db, const LIST& params, IfcStructuralSurfaceMemberVarying* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralSurfaceMember*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStructuralSurfaceReaction>(const DB& db, const LIST& params, IfcStructuralSurfaceReaction* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStructuralReaction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStyleModel>(const DB& db, const LIST& params, IfcStyleModel* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentation*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStyledItem>(const DB& db, const LIST& params, IfcStyledItem* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcRepresentationItem*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcStyledItem"); } do { // convert the 'Item' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Item, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcStyledItem to be a `IfcRepresentationItem`")); } + } while(0); + do { // convert the 'Styles' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Styles, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcStyledItem to be a `SET [1:?] OF IfcStyleAssignmentSelect`")); } + } while(0); + do { // convert the 'Name' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Name, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcStyledItem to be a `IfcLabel`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcStyledRepresentation>(const DB& db, const LIST& params, IfcStyledRepresentation* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcStyleModel*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSubContractResource>(const DB& db, const LIST& params, IfcSubContractResource* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResource*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSubContractResourceType>(const DB& db, const LIST& params, IfcSubContractResourceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcConstructionResourceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSubedge>(const DB& db, const LIST& params, IfcSubedge* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEdge*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceCurveSweptAreaSolid>(const DB& db, const LIST& params, IfcSurfaceCurveSweptAreaSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSweptAreaSolid*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceFeature>(const DB& db, const LIST& params, IfcSurfaceFeature* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFeatureElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSweptSurface>(const DB& db, const LIST& params, IfcSweptSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceOfLinearExtrusion>(const DB& db, const LIST& params, IfcSurfaceOfLinearExtrusion* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSweptSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceOfRevolution>(const DB& db, const LIST& params, IfcSurfaceOfRevolution* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSweptSurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceStyle>(const DB& db, const LIST& params, IfcSurfaceStyle* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPresentationStyle*>(in)); + if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcSurfaceStyle"); } do { // convert the 'Side' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Side, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSurfaceStyle to be a `IfcSurfaceSide`")); } + } while(0); + do { // convert the 'Styles' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Styles, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSurfaceStyle to be a `SET [1:5] OF IfcSurfaceStyleElementSelect`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceStyleShading>(const DB& db, const LIST& params, IfcSurfaceStyleShading* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPresentationItem*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcSurfaceStyleShading"); } do { // convert the 'SurfaceColour' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcSurfaceStyleShading,2>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->SurfaceColour, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSurfaceStyleShading to be a `IfcColourRgb`")); } + } while(0); + do { // convert the 'Transparency' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcSurfaceStyleShading,2>::aux_is_derived[1]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->Transparency, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSurfaceStyleShading to be a `IfcNormalisedRatioMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceStyleRendering>(const DB& db, const LIST& params, IfcSurfaceStyleRendering* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSurfaceStyleShading*>(in)); + if (params.GetSize() < 9) { throw STEP::TypeError("expected 9 arguments to IfcSurfaceStyleRendering"); } do { // convert the 'DiffuseColour' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->DiffuseColour, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } + } while(0); + do { // convert the 'TransmissionColour' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->TransmissionColour, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } + } while(0); + do { // convert the 'DiffuseTransmissionColour' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->DiffuseTransmissionColour, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } + } while(0); + do { // convert the 'ReflectionColour' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->ReflectionColour, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } + } while(0); + do { // convert the 'SpecularColour' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->SpecularColour, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } + } while(0); + do { // convert the 'SpecularHighlight' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->SpecularHighlight, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcSurfaceStyleRendering to be a `IfcSpecularHighlightSelect`")); } + } while(0); + do { // convert the 'ReflectanceMethod' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->ReflectanceMethod, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcSurfaceStyleRendering to be a `IfcReflectanceMethodEnum`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSurfaceStyleWithTextures>(const DB& db, const LIST& params, IfcSurfaceStyleWithTextures* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcPresentationItem*>(in)); + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcSurfaceStyleWithTextures"); } do { // convert the 'Textures' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Textures, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSurfaceStyleWithTextures to be a `LIST [1:?] OF IfcSurfaceTexture`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSweptDiskSolid>(const DB& db, const LIST& params, IfcSweptDiskSolid* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSolidModel*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcSweptDiskSolid"); } do { // convert the 'Directrix' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcSweptDiskSolid,5>::aux_is_derived[0]=true; break; } + try { GenericConvert( in->Directrix, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSweptDiskSolid to be a `IfcCurve`")); } + } while(0); + do { // convert the 'Radius' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcSweptDiskSolid,5>::aux_is_derived[1]=true; break; } + try { GenericConvert( in->Radius, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSweptDiskSolid to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'InnerRadius' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcSweptDiskSolid,5>::aux_is_derived[2]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->InnerRadius, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSweptDiskSolid to be a `IfcPositiveLengthMeasure`")); } + } while(0); + do { // convert the 'StartParam' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcSweptDiskSolid,5>::aux_is_derived[3]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->StartParam, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSweptDiskSolid to be a `IfcParameterValue`")); } + } while(0); + do { // convert the 'EndParam' argument + boost::shared_ptr<const DataType> arg = params[base++]; + if (dynamic_cast<const ISDERIVED*>(&*arg)) { in->ObjectHelper<Assimp::IFC::IfcSweptDiskSolid,5>::aux_is_derived[4]=true; break; } + if (dynamic_cast<const UNSET*>(&*arg)) break; + try { GenericConvert( in->EndParam, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcSweptDiskSolid to be a `IfcParameterValue`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSweptDiskSolidPolygonal>(const DB& db, const LIST& params, IfcSweptDiskSolidPolygonal* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSweptDiskSolid*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSwitchingDevice>(const DB& db, const LIST& params, IfcSwitchingDevice* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowController*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSwitchingDeviceType>(const DB& db, const LIST& params, IfcSwitchingDeviceType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSystemFurnitureElement>(const DB& db, const LIST& params, IfcSystemFurnitureElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFurnishingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcSystemFurnitureElementType>(const DB& db, const LIST& params, IfcSystemFurnitureElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFurnishingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTShapeProfileDef>(const DB& db, const LIST& params, IfcTShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTank>(const DB& db, const LIST& params, IfcTank* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowStorageDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTankType>(const DB& db, const LIST& params, IfcTankType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowStorageDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTask>(const DB& db, const LIST& params, IfcTask* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcProcess*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTaskType>(const DB& db, const LIST& params, IfcTaskType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeProcess*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTendon>(const DB& db, const LIST& params, IfcTendon* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcReinforcingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTendonAnchor>(const DB& db, const LIST& params, IfcTendonAnchor* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcReinforcingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTendonAnchorType>(const DB& db, const LIST& params, IfcTendonAnchorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcReinforcingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTendonType>(const DB& db, const LIST& params, IfcTendonType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcReinforcingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTextLiteral>(const DB& db, const LIST& params, IfcTextLiteral* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTextLiteralWithExtent>(const DB& db, const LIST& params, IfcTextLiteralWithExtent* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTextLiteral*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTopologyRepresentation>(const DB& db, const LIST& params, IfcTopologyRepresentation* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcShapeModel*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcToroidalSurface>(const DB& db, const LIST& params, IfcToroidalSurface* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementarySurface*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTransformer>(const DB& db, const LIST& params, IfcTransformer* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTransformerType>(const DB& db, const LIST& params, IfcTransformerType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTransportElement>(const DB& db, const LIST& params, IfcTransportElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTransportElementType>(const DB& db, const LIST& params, IfcTransportElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTrapeziumProfileDef>(const DB& db, const LIST& params, IfcTrapeziumProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTriangulatedFaceSet>(const DB& db, const LIST& params, IfcTriangulatedFaceSet* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTessellatedFaceSet*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTrimmedCurve>(const DB& db, const LIST& params, IfcTrimmedCurve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBoundedCurve*>(in)); + if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcTrimmedCurve"); } do { // convert the 'BasisCurve' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->BasisCurve, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcTrimmedCurve to be a `IfcCurve`")); } + } while(0); + do { // convert the 'Trim1' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Trim1, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcTrimmedCurve to be a `SET [1:2] OF IfcTrimmingSelect`")); } + } while(0); + do { // convert the 'Trim2' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Trim2, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcTrimmedCurve to be a `SET [1:2] OF IfcTrimmingSelect`")); } + } while(0); + do { // convert the 'SenseAgreement' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->SenseAgreement, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcTrimmedCurve to be a `IfcBoolean`")); } + } while(0); + do { // convert the 'MasterRepresentation' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->MasterRepresentation, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcTrimmedCurve to be a `IfcTrimmingPreference`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTubeBundle>(const DB& db, const LIST& params, IfcTubeBundle* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcTubeBundleType>(const DB& db, const LIST& params, IfcTubeBundleType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcUShapeProfileDef>(const DB& db, const LIST& params, IfcUShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcUnitAssignment>(const DB& db, const LIST& params, IfcUnitAssignment* in) +{ + size_t base = 0; + if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcUnitAssignment"); } do { // convert the 'Units' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Units, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcUnitAssignment to be a `SET [1:?] OF IfcUnit`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcUnitaryControlElement>(const DB& db, const LIST& params, IfcUnitaryControlElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcUnitaryControlElementType>(const DB& db, const LIST& params, IfcUnitaryControlElementType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcDistributionControlElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcUnitaryEquipment>(const DB& db, const LIST& params, IfcUnitaryEquipment* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDevice*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcUnitaryEquipmentType>(const DB& db, const LIST& params, IfcUnitaryEquipmentType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcEnergyConversionDeviceType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcValve>(const DB& db, const LIST& params, IfcValve* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowController*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcValveType>(const DB& db, const LIST& params, IfcValveType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowControllerType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVector>(const DB& db, const LIST& params, IfcVector* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcGeometricRepresentationItem*>(in)); + if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcVector"); } do { // convert the 'Orientation' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Orientation, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcVector to be a `IfcDirection`")); } + } while(0); + do { // convert the 'Magnitude' argument + boost::shared_ptr<const DataType> arg = params[base++]; + try { GenericConvert( in->Magnitude, arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcVector to be a `IfcLengthMeasure`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVertex>(const DB& db, const LIST& params, IfcVertex* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTopologicalRepresentationItem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVertexLoop>(const DB& db, const LIST& params, IfcVertexLoop* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcLoop*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVertexPoint>(const DB& db, const LIST& params, IfcVertexPoint* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcVertex*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVibrationIsolator>(const DB& db, const LIST& params, IfcVibrationIsolator* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponent*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVibrationIsolatorType>(const DB& db, const LIST& params, IfcVibrationIsolatorType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElementComponentType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVirtualElement>(const DB& db, const LIST& params, IfcVirtualElement* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcVoidingFeature>(const DB& db, const LIST& params, IfcVoidingFeature* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFeatureElementSubtraction*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWall>(const DB& db, const LIST& params, IfcWall* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWallElementedCase>(const DB& db, const LIST& params, IfcWallElementedCase* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcWall*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWallStandardCase>(const DB& db, const LIST& params, IfcWallStandardCase* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcWall*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWallType>(const DB& db, const LIST& params, IfcWallType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWasteTerminal>(const DB& db, const LIST& params, IfcWasteTerminal* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminal*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWasteTerminalType>(const DB& db, const LIST& params, IfcWasteTerminalType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcFlowTerminalType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWindow>(const DB& db, const LIST& params, IfcWindow* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElement*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWindowStandardCase>(const DB& db, const LIST& params, IfcWindowStandardCase* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcWindow*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWindowStyle>(const DB& db, const LIST& params, IfcWindowStyle* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcTypeProduct*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWindowType>(const DB& db, const LIST& params, IfcWindowType* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcBuildingElementType*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWorkCalendar>(const DB& db, const LIST& params, IfcWorkCalendar* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWorkControl>(const DB& db, const LIST& params, IfcWorkControl* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWorkPlan>(const DB& db, const LIST& params, IfcWorkPlan* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcWorkControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcWorkSchedule>(const DB& db, const LIST& params, IfcWorkSchedule* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcWorkControl*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcZShapeProfileDef>(const DB& db, const LIST& params, IfcZShapeProfileDef* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcParameterizedProfileDef*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} +// ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill<IfcZone>(const DB& db, const LIST& params, IfcZone* in) +{ + size_t base = GenericFill(db,params,static_cast<IfcSystem*>(in)); +// this data structure is not used yet, so there is no code generated to fill its members + return base; +} + +} // ! STEP +} // ! Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/IFC/IFCReaderGen_4.h b/libs/assimp/code/AssetLib/IFC/IFCReaderGen_4.h new file mode 100644 index 0000000..abf0219 --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCReaderGen_4.h @@ -0,0 +1,5452 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, ASSIMP Development 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 Development 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. + +---------------------------------------------------------------------- +*/ + +/** MACHINE-GENERATED by scripts/ICFImporter/CppGenerator.py */ + +#ifndef INCLUDED_IFC_READER_GEN_H +#define INCLUDED_IFC_READER_GEN_H + +#include "STEPFile.h" + +namespace Assimp { +namespace IFC { +namespace Schema_4 { + + using namespace STEP; + using namespace STEP::EXPRESS; + + + struct NotImplemented : public ObjectHelper<NotImplemented,0> { + + }; + + + // ****************************************************************************** + // IFC Custom data types + // ****************************************************************************** + + + // C++ wrapper type for IfcStrippedOptional + typedef BOOLEAN IfcStrippedOptional; + // C++ wrapper type for IfcAbsorbedDoseMeasure + typedef REAL IfcAbsorbedDoseMeasure; + // C++ wrapper type for IfcAccelerationMeasure + typedef REAL IfcAccelerationMeasure; + // C++ wrapper type for IfcAmountOfSubstanceMeasure + typedef REAL IfcAmountOfSubstanceMeasure; + // C++ wrapper type for IfcAngularVelocityMeasure + typedef REAL IfcAngularVelocityMeasure; + // C++ wrapper type for IfcArcIndex + typedef ListOf< INTEGER, 3, 3 > IfcArcIndex; + // C++ wrapper type for IfcAreaDensityMeasure + typedef REAL IfcAreaDensityMeasure; + // C++ wrapper type for IfcAreaMeasure + typedef REAL IfcAreaMeasure; + // C++ wrapper type for IfcBoolean + typedef BOOLEAN IfcBoolean; + // C++ wrapper type for IfcBoxAlignment + typedef STRING IfcBoxAlignment; + // C++ wrapper type for IfcCardinalPointReference + typedef INTEGER IfcCardinalPointReference; + // C++ wrapper type for IfcCompoundPlaneAngleMeasure + typedef ListOf< INTEGER, 3, 4 > IfcCompoundPlaneAngleMeasure; + // C++ wrapper type for IfcContextDependentMeasure + typedef REAL IfcContextDependentMeasure; + // C++ wrapper type for IfcCountMeasure + typedef NUMBER IfcCountMeasure; + // C++ wrapper type for IfcCurvatureMeasure + typedef REAL IfcCurvatureMeasure; + // C++ wrapper type for IfcDate + typedef STRING IfcDate; + // C++ wrapper type for IfcDateTime + typedef STRING IfcDateTime; + // C++ wrapper type for IfcDayInMonthNumber + typedef INTEGER IfcDayInMonthNumber; + // C++ wrapper type for IfcDayInWeekNumber + typedef INTEGER IfcDayInWeekNumber; + // C++ wrapper type for IfcDescriptiveMeasure + typedef STRING IfcDescriptiveMeasure; + // C++ wrapper type for IfcDimensionCount + typedef INTEGER IfcDimensionCount; + // C++ wrapper type for IfcDoseEquivalentMeasure + typedef REAL IfcDoseEquivalentMeasure; + // C++ wrapper type for IfcDuration + typedef STRING IfcDuration; + // C++ wrapper type for IfcDynamicViscosityMeasure + typedef REAL IfcDynamicViscosityMeasure; + // C++ wrapper type for IfcElectricCapacitanceMeasure + typedef REAL IfcElectricCapacitanceMeasure; + // C++ wrapper type for IfcElectricChargeMeasure + typedef REAL IfcElectricChargeMeasure; + // C++ wrapper type for IfcElectricConductanceMeasure + typedef REAL IfcElectricConductanceMeasure; + // C++ wrapper type for IfcElectricCurrentMeasure + typedef REAL IfcElectricCurrentMeasure; + // C++ wrapper type for IfcElectricResistanceMeasure + typedef REAL IfcElectricResistanceMeasure; + // C++ wrapper type for IfcElectricVoltageMeasure + typedef REAL IfcElectricVoltageMeasure; + // C++ wrapper type for IfcEnergyMeasure + typedef REAL IfcEnergyMeasure; + // C++ wrapper type for IfcFontStyle + typedef STRING IfcFontStyle; + // C++ wrapper type for IfcFontVariant + typedef STRING IfcFontVariant; + // C++ wrapper type for IfcFontWeight + typedef STRING IfcFontWeight; + // C++ wrapper type for IfcForceMeasure + typedef REAL IfcForceMeasure; + // C++ wrapper type for IfcFrequencyMeasure + typedef REAL IfcFrequencyMeasure; + // C++ wrapper type for IfcGloballyUniqueId + typedef STRING IfcGloballyUniqueId; + // C++ wrapper type for IfcHeatFluxDensityMeasure + typedef REAL IfcHeatFluxDensityMeasure; + // C++ wrapper type for IfcHeatingValueMeasure + typedef REAL IfcHeatingValueMeasure; + // C++ wrapper type for IfcIdentifier + typedef STRING IfcIdentifier; + // C++ wrapper type for IfcIlluminanceMeasure + typedef REAL IfcIlluminanceMeasure; + // C++ wrapper type for IfcInductanceMeasure + typedef REAL IfcInductanceMeasure; + // C++ wrapper type for IfcInteger + typedef INTEGER IfcInteger; + // C++ wrapper type for IfcIntegerCountRateMeasure + typedef INTEGER IfcIntegerCountRateMeasure; + // C++ wrapper type for IfcIonConcentrationMeasure + typedef REAL IfcIonConcentrationMeasure; + // C++ wrapper type for IfcIsothermalMoistureCapacityMeasure + typedef REAL IfcIsothermalMoistureCapacityMeasure; + // C++ wrapper type for IfcKinematicViscosityMeasure + typedef REAL IfcKinematicViscosityMeasure; + // C++ wrapper type for IfcLabel + typedef STRING IfcLabel; + // C++ wrapper type for IfcLanguageId + typedef STRING IfcLanguageId; + // C++ wrapper type for IfcLengthMeasure + typedef REAL IfcLengthMeasure; + // C++ wrapper type for IfcLineIndex + typedef ListOf< INTEGER, 2, 0 > IfcLineIndex; + // C++ wrapper type for IfcLinearForceMeasure + typedef REAL IfcLinearForceMeasure; + // C++ wrapper type for IfcLinearMomentMeasure + typedef REAL IfcLinearMomentMeasure; + // C++ wrapper type for IfcLinearStiffnessMeasure + typedef REAL IfcLinearStiffnessMeasure; + // C++ wrapper type for IfcLinearVelocityMeasure + typedef REAL IfcLinearVelocityMeasure; + // C++ wrapper type for IfcLogical + typedef LOGICAL IfcLogical; + // C++ wrapper type for IfcLuminousFluxMeasure + typedef REAL IfcLuminousFluxMeasure; + // C++ wrapper type for IfcLuminousIntensityDistributionMeasure + typedef REAL IfcLuminousIntensityDistributionMeasure; + // C++ wrapper type for IfcLuminousIntensityMeasure + typedef REAL IfcLuminousIntensityMeasure; + // C++ wrapper type for IfcMagneticFluxDensityMeasure + typedef REAL IfcMagneticFluxDensityMeasure; + // C++ wrapper type for IfcMagneticFluxMeasure + typedef REAL IfcMagneticFluxMeasure; + // C++ wrapper type for IfcMassDensityMeasure + typedef REAL IfcMassDensityMeasure; + // C++ wrapper type for IfcMassFlowRateMeasure + typedef REAL IfcMassFlowRateMeasure; + // C++ wrapper type for IfcMassMeasure + typedef REAL IfcMassMeasure; + // C++ wrapper type for IfcMassPerLengthMeasure + typedef REAL IfcMassPerLengthMeasure; + // C++ wrapper type for IfcModulusOfElasticityMeasure + typedef REAL IfcModulusOfElasticityMeasure; + // C++ wrapper type for IfcModulusOfLinearSubgradeReactionMeasure + typedef REAL IfcModulusOfLinearSubgradeReactionMeasure; + // C++ wrapper type for IfcModulusOfRotationalSubgradeReactionMeasure + typedef REAL IfcModulusOfRotationalSubgradeReactionMeasure; + // C++ wrapper type for IfcModulusOfSubgradeReactionMeasure + typedef REAL IfcModulusOfSubgradeReactionMeasure; + // C++ wrapper type for IfcMoistureDiffusivityMeasure + typedef REAL IfcMoistureDiffusivityMeasure; + // C++ wrapper type for IfcMolecularWeightMeasure + typedef REAL IfcMolecularWeightMeasure; + // C++ wrapper type for IfcMomentOfInertiaMeasure + typedef REAL IfcMomentOfInertiaMeasure; + // C++ wrapper type for IfcMonetaryMeasure + typedef REAL IfcMonetaryMeasure; + // C++ wrapper type for IfcMonthInYearNumber + typedef INTEGER IfcMonthInYearNumber; + // C++ wrapper type for IfcNonNegativeLengthMeasure + typedef REAL IfcNonNegativeLengthMeasure; + // C++ wrapper type for IfcNormalisedRatioMeasure + typedef REAL IfcNormalisedRatioMeasure; + // C++ wrapper type for IfcNumericMeasure + typedef NUMBER IfcNumericMeasure; + // C++ wrapper type for IfcPHMeasure + typedef REAL IfcPHMeasure; + // C++ wrapper type for IfcParameterValue + typedef REAL IfcParameterValue; + // C++ wrapper type for IfcPlanarForceMeasure + typedef REAL IfcPlanarForceMeasure; + // C++ wrapper type for IfcPlaneAngleMeasure + typedef REAL IfcPlaneAngleMeasure; + // C++ wrapper type for IfcPositiveInteger + typedef INTEGER IfcPositiveInteger; + // C++ wrapper type for IfcPositiveLengthMeasure + typedef REAL IfcPositiveLengthMeasure; + // C++ wrapper type for IfcPositivePlaneAngleMeasure + typedef REAL IfcPositivePlaneAngleMeasure; + // C++ wrapper type for IfcPositiveRatioMeasure + typedef REAL IfcPositiveRatioMeasure; + // C++ wrapper type for IfcPowerMeasure + typedef REAL IfcPowerMeasure; + // C++ wrapper type for IfcPresentableText + typedef STRING IfcPresentableText; + // C++ wrapper type for IfcPressureMeasure + typedef REAL IfcPressureMeasure; + // C++ wrapper type for IfcRadioActivityMeasure + typedef REAL IfcRadioActivityMeasure; + // C++ wrapper type for IfcRatioMeasure + typedef REAL IfcRatioMeasure; + // C++ wrapper type for IfcReal + typedef REAL IfcReal; + // C++ wrapper type for IfcRotationalFrequencyMeasure + typedef REAL IfcRotationalFrequencyMeasure; + // C++ wrapper type for IfcRotationalMassMeasure + typedef REAL IfcRotationalMassMeasure; + // C++ wrapper type for IfcRotationalStiffnessMeasure + typedef REAL IfcRotationalStiffnessMeasure; + // C++ wrapper type for IfcSectionModulusMeasure + typedef REAL IfcSectionModulusMeasure; + // C++ wrapper type for IfcSectionalAreaIntegralMeasure + typedef REAL IfcSectionalAreaIntegralMeasure; + // C++ wrapper type for IfcShearModulusMeasure + typedef REAL IfcShearModulusMeasure; + // C++ wrapper type for IfcSolidAngleMeasure + typedef REAL IfcSolidAngleMeasure; + // C++ wrapper type for IfcSoundPowerLevelMeasure + typedef REAL IfcSoundPowerLevelMeasure; + // C++ wrapper type for IfcSoundPowerMeasure + typedef REAL IfcSoundPowerMeasure; + // C++ wrapper type for IfcSoundPressureLevelMeasure + typedef REAL IfcSoundPressureLevelMeasure; + // C++ wrapper type for IfcSoundPressureMeasure + typedef REAL IfcSoundPressureMeasure; + // C++ wrapper type for IfcSpecificHeatCapacityMeasure + typedef REAL IfcSpecificHeatCapacityMeasure; + // C++ wrapper type for IfcSpecularExponent + typedef REAL IfcSpecularExponent; + // C++ wrapper type for IfcSpecularRoughness + typedef REAL IfcSpecularRoughness; + // C++ wrapper type for IfcTemperatureGradientMeasure + typedef REAL IfcTemperatureGradientMeasure; + // C++ wrapper type for IfcTemperatureRateOfChangeMeasure + typedef REAL IfcTemperatureRateOfChangeMeasure; + // C++ wrapper type for IfcText + typedef STRING IfcText; + // C++ wrapper type for IfcTextAlignment + typedef STRING IfcTextAlignment; + // C++ wrapper type for IfcTextDecoration + typedef STRING IfcTextDecoration; + // C++ wrapper type for IfcTextFontName + typedef STRING IfcTextFontName; + // C++ wrapper type for IfcTextTransformation + typedef STRING IfcTextTransformation; + // C++ wrapper type for IfcThermalAdmittanceMeasure + typedef REAL IfcThermalAdmittanceMeasure; + // C++ wrapper type for IfcThermalConductivityMeasure + typedef REAL IfcThermalConductivityMeasure; + // C++ wrapper type for IfcThermalExpansionCoefficientMeasure + typedef REAL IfcThermalExpansionCoefficientMeasure; + // C++ wrapper type for IfcThermalResistanceMeasure + typedef REAL IfcThermalResistanceMeasure; + // C++ wrapper type for IfcThermalTransmittanceMeasure + typedef REAL IfcThermalTransmittanceMeasure; + // C++ wrapper type for IfcThermodynamicTemperatureMeasure + typedef REAL IfcThermodynamicTemperatureMeasure; + // C++ wrapper type for IfcTime + typedef STRING IfcTime; + // C++ wrapper type for IfcTimeMeasure + typedef REAL IfcTimeMeasure; + // C++ wrapper type for IfcTimeStamp + typedef INTEGER IfcTimeStamp; + // C++ wrapper type for IfcTorqueMeasure + typedef REAL IfcTorqueMeasure; + // C++ wrapper type for IfcURIReference + typedef STRING IfcURIReference; + // C++ wrapper type for IfcVaporPermeabilityMeasure + typedef REAL IfcVaporPermeabilityMeasure; + // C++ wrapper type for IfcVolumeMeasure + typedef REAL IfcVolumeMeasure; + // C++ wrapper type for IfcVolumetricFlowRateMeasure + typedef REAL IfcVolumetricFlowRateMeasure; + // C++ wrapper type for IfcWarpingConstantMeasure + typedef REAL IfcWarpingConstantMeasure; + // C++ wrapper type for IfcWarpingMomentMeasure + typedef REAL IfcWarpingMomentMeasure; + // C++ wrapper type for IfcActionRequestTypeEnum + typedef ENUMERATION IfcActionRequestTypeEnum; + // C++ wrapper type for IfcActionSourceTypeEnum + typedef ENUMERATION IfcActionSourceTypeEnum; + // C++ wrapper type for IfcActionTypeEnum + typedef ENUMERATION IfcActionTypeEnum; + // C++ wrapper type for IfcActuatorTypeEnum + typedef ENUMERATION IfcActuatorTypeEnum; + // C++ wrapper type for IfcAddressTypeEnum + typedef ENUMERATION IfcAddressTypeEnum; + // C++ wrapper type for IfcAirTerminalBoxTypeEnum + typedef ENUMERATION IfcAirTerminalBoxTypeEnum; + // C++ wrapper type for IfcAirTerminalTypeEnum + typedef ENUMERATION IfcAirTerminalTypeEnum; + // C++ wrapper type for IfcAirToAirHeatRecoveryTypeEnum + typedef ENUMERATION IfcAirToAirHeatRecoveryTypeEnum; + // C++ wrapper type for IfcAlarmTypeEnum + typedef ENUMERATION IfcAlarmTypeEnum; + // C++ wrapper type for IfcAnalysisModelTypeEnum + typedef ENUMERATION IfcAnalysisModelTypeEnum; + // C++ wrapper type for IfcAnalysisTheoryTypeEnum + typedef ENUMERATION IfcAnalysisTheoryTypeEnum; + // C++ wrapper type for IfcArithmeticOperatorEnum + typedef ENUMERATION IfcArithmeticOperatorEnum; + // C++ wrapper type for IfcAssemblyPlaceEnum + typedef ENUMERATION IfcAssemblyPlaceEnum; + // C++ wrapper type for IfcAudioVisualApplianceTypeEnum + typedef ENUMERATION IfcAudioVisualApplianceTypeEnum; + // C++ wrapper type for IfcBSplineCurveForm + typedef ENUMERATION IfcBSplineCurveForm; + // C++ wrapper type for IfcBSplineSurfaceForm + typedef ENUMERATION IfcBSplineSurfaceForm; + // C++ wrapper type for IfcBeamTypeEnum + typedef ENUMERATION IfcBeamTypeEnum; + // C++ wrapper type for IfcBenchmarkEnum + typedef ENUMERATION IfcBenchmarkEnum; + // C++ wrapper type for IfcBoilerTypeEnum + typedef ENUMERATION IfcBoilerTypeEnum; + // C++ wrapper type for IfcBooleanOperator + typedef ENUMERATION IfcBooleanOperator; + // C++ wrapper type for IfcBuildingElementPartTypeEnum + typedef ENUMERATION IfcBuildingElementPartTypeEnum; + // C++ wrapper type for IfcBuildingElementProxyTypeEnum + typedef ENUMERATION IfcBuildingElementProxyTypeEnum; + // C++ wrapper type for IfcBuildingSystemTypeEnum + typedef ENUMERATION IfcBuildingSystemTypeEnum; + // C++ wrapper type for IfcBurnerTypeEnum + typedef ENUMERATION IfcBurnerTypeEnum; + // C++ wrapper type for IfcCableCarrierFittingTypeEnum + typedef ENUMERATION IfcCableCarrierFittingTypeEnum; + // C++ wrapper type for IfcCableCarrierSegmentTypeEnum + typedef ENUMERATION IfcCableCarrierSegmentTypeEnum; + // C++ wrapper type for IfcCableFittingTypeEnum + typedef ENUMERATION IfcCableFittingTypeEnum; + // C++ wrapper type for IfcCableSegmentTypeEnum + typedef ENUMERATION IfcCableSegmentTypeEnum; + // C++ wrapper type for IfcChangeActionEnum + typedef ENUMERATION IfcChangeActionEnum; + // C++ wrapper type for IfcChillerTypeEnum + typedef ENUMERATION IfcChillerTypeEnum; + // C++ wrapper type for IfcChimneyTypeEnum + typedef ENUMERATION IfcChimneyTypeEnum; + // C++ wrapper type for IfcCoilTypeEnum + typedef ENUMERATION IfcCoilTypeEnum; + // C++ wrapper type for IfcColumnTypeEnum + typedef ENUMERATION IfcColumnTypeEnum; + // C++ wrapper type for IfcCommunicationsApplianceTypeEnum + typedef ENUMERATION IfcCommunicationsApplianceTypeEnum; + // C++ wrapper type for IfcComplexPropertyTemplateTypeEnum + typedef ENUMERATION IfcComplexPropertyTemplateTypeEnum; + // C++ wrapper type for IfcCompressorTypeEnum + typedef ENUMERATION IfcCompressorTypeEnum; + // C++ wrapper type for IfcCondenserTypeEnum + typedef ENUMERATION IfcCondenserTypeEnum; + // C++ wrapper type for IfcConnectionTypeEnum + typedef ENUMERATION IfcConnectionTypeEnum; + // C++ wrapper type for IfcConstraintEnum + typedef ENUMERATION IfcConstraintEnum; + // C++ wrapper type for IfcConstructionEquipmentResourceTypeEnum + typedef ENUMERATION IfcConstructionEquipmentResourceTypeEnum; + // C++ wrapper type for IfcConstructionMaterialResourceTypeEnum + typedef ENUMERATION IfcConstructionMaterialResourceTypeEnum; + // C++ wrapper type for IfcConstructionProductResourceTypeEnum + typedef ENUMERATION IfcConstructionProductResourceTypeEnum; + // C++ wrapper type for IfcControllerTypeEnum + typedef ENUMERATION IfcControllerTypeEnum; + // C++ wrapper type for IfcCooledBeamTypeEnum + typedef ENUMERATION IfcCooledBeamTypeEnum; + // C++ wrapper type for IfcCoolingTowerTypeEnum + typedef ENUMERATION IfcCoolingTowerTypeEnum; + // C++ wrapper type for IfcCostItemTypeEnum + typedef ENUMERATION IfcCostItemTypeEnum; + // C++ wrapper type for IfcCostScheduleTypeEnum + typedef ENUMERATION IfcCostScheduleTypeEnum; + // C++ wrapper type for IfcCoveringTypeEnum + typedef ENUMERATION IfcCoveringTypeEnum; + // C++ wrapper type for IfcCrewResourceTypeEnum + typedef ENUMERATION IfcCrewResourceTypeEnum; + // C++ wrapper type for IfcCurtainWallTypeEnum + typedef ENUMERATION IfcCurtainWallTypeEnum; + // C++ wrapper type for IfcCurveInterpolationEnum + typedef ENUMERATION IfcCurveInterpolationEnum; + // C++ wrapper type for IfcDamperTypeEnum + typedef ENUMERATION IfcDamperTypeEnum; + // C++ wrapper type for IfcDataOriginEnum + typedef ENUMERATION IfcDataOriginEnum; + // C++ wrapper type for IfcDerivedUnitEnum + typedef ENUMERATION IfcDerivedUnitEnum; + // C++ wrapper type for IfcDirectionSenseEnum + typedef ENUMERATION IfcDirectionSenseEnum; + // C++ wrapper type for IfcDiscreteAccessoryTypeEnum + typedef ENUMERATION IfcDiscreteAccessoryTypeEnum; + // C++ wrapper type for IfcDistributionChamberElementTypeEnum + typedef ENUMERATION IfcDistributionChamberElementTypeEnum; + // C++ wrapper type for IfcDistributionPortTypeEnum + typedef ENUMERATION IfcDistributionPortTypeEnum; + // C++ wrapper type for IfcDistributionSystemEnum + typedef ENUMERATION IfcDistributionSystemEnum; + // C++ wrapper type for IfcDocumentConfidentialityEnum + typedef ENUMERATION IfcDocumentConfidentialityEnum; + // C++ wrapper type for IfcDocumentStatusEnum + typedef ENUMERATION IfcDocumentStatusEnum; + // C++ wrapper type for IfcDoorPanelOperationEnum + typedef ENUMERATION IfcDoorPanelOperationEnum; + // C++ wrapper type for IfcDoorPanelPositionEnum + typedef ENUMERATION IfcDoorPanelPositionEnum; + // C++ wrapper type for IfcDoorStyleConstructionEnum + typedef ENUMERATION IfcDoorStyleConstructionEnum; + // C++ wrapper type for IfcDoorStyleOperationEnum + typedef ENUMERATION IfcDoorStyleOperationEnum; + // C++ wrapper type for IfcDoorTypeEnum + typedef ENUMERATION IfcDoorTypeEnum; + // C++ wrapper type for IfcDoorTypeOperationEnum + typedef ENUMERATION IfcDoorTypeOperationEnum; + // C++ wrapper type for IfcDuctFittingTypeEnum + typedef ENUMERATION IfcDuctFittingTypeEnum; + // C++ wrapper type for IfcDuctSegmentTypeEnum + typedef ENUMERATION IfcDuctSegmentTypeEnum; + // C++ wrapper type for IfcDuctSilencerTypeEnum + typedef ENUMERATION IfcDuctSilencerTypeEnum; + // C++ wrapper type for IfcElectricApplianceTypeEnum + typedef ENUMERATION IfcElectricApplianceTypeEnum; + // C++ wrapper type for IfcElectricDistributionBoardTypeEnum + typedef ENUMERATION IfcElectricDistributionBoardTypeEnum; + // C++ wrapper type for IfcElectricFlowStorageDeviceTypeEnum + typedef ENUMERATION IfcElectricFlowStorageDeviceTypeEnum; + // C++ wrapper type for IfcElectricGeneratorTypeEnum + typedef ENUMERATION IfcElectricGeneratorTypeEnum; + // C++ wrapper type for IfcElectricMotorTypeEnum + typedef ENUMERATION IfcElectricMotorTypeEnum; + // C++ wrapper type for IfcElectricTimeControlTypeEnum + typedef ENUMERATION IfcElectricTimeControlTypeEnum; + // C++ wrapper type for IfcElementAssemblyTypeEnum + typedef ENUMERATION IfcElementAssemblyTypeEnum; + // C++ wrapper type for IfcElementCompositionEnum + typedef ENUMERATION IfcElementCompositionEnum; + // C++ wrapper type for IfcEngineTypeEnum + typedef ENUMERATION IfcEngineTypeEnum; + // C++ wrapper type for IfcEvaporativeCoolerTypeEnum + typedef ENUMERATION IfcEvaporativeCoolerTypeEnum; + // C++ wrapper type for IfcEvaporatorTypeEnum + typedef ENUMERATION IfcEvaporatorTypeEnum; + // C++ wrapper type for IfcEventTriggerTypeEnum + typedef ENUMERATION IfcEventTriggerTypeEnum; + // C++ wrapper type for IfcEventTypeEnum + typedef ENUMERATION IfcEventTypeEnum; + // C++ wrapper type for IfcExternalSpatialElementTypeEnum + typedef ENUMERATION IfcExternalSpatialElementTypeEnum; + // C++ wrapper type for IfcFanTypeEnum + typedef ENUMERATION IfcFanTypeEnum; + // C++ wrapper type for IfcFastenerTypeEnum + typedef ENUMERATION IfcFastenerTypeEnum; + // C++ wrapper type for IfcFilterTypeEnum + typedef ENUMERATION IfcFilterTypeEnum; + // C++ wrapper type for IfcFireSuppressionTerminalTypeEnum + typedef ENUMERATION IfcFireSuppressionTerminalTypeEnum; + // C++ wrapper type for IfcFlowDirectionEnum + typedef ENUMERATION IfcFlowDirectionEnum; + // C++ wrapper type for IfcFlowInstrumentTypeEnum + typedef ENUMERATION IfcFlowInstrumentTypeEnum; + // C++ wrapper type for IfcFlowMeterTypeEnum + typedef ENUMERATION IfcFlowMeterTypeEnum; + // C++ wrapper type for IfcFootingTypeEnum + typedef ENUMERATION IfcFootingTypeEnum; + // C++ wrapper type for IfcFurnitureTypeEnum + typedef ENUMERATION IfcFurnitureTypeEnum; + // C++ wrapper type for IfcGeographicElementTypeEnum + typedef ENUMERATION IfcGeographicElementTypeEnum; + // C++ wrapper type for IfcGeometricProjectionEnum + typedef ENUMERATION IfcGeometricProjectionEnum; + // C++ wrapper type for IfcGlobalOrLocalEnum + typedef ENUMERATION IfcGlobalOrLocalEnum; + // C++ wrapper type for IfcGridTypeEnum + typedef ENUMERATION IfcGridTypeEnum; + // C++ wrapper type for IfcHeatExchangerTypeEnum + typedef ENUMERATION IfcHeatExchangerTypeEnum; + // C++ wrapper type for IfcHumidifierTypeEnum + typedef ENUMERATION IfcHumidifierTypeEnum; + // C++ wrapper type for IfcInterceptorTypeEnum + typedef ENUMERATION IfcInterceptorTypeEnum; + // C++ wrapper type for IfcInternalOrExternalEnum + typedef ENUMERATION IfcInternalOrExternalEnum; + // C++ wrapper type for IfcInventoryTypeEnum + typedef ENUMERATION IfcInventoryTypeEnum; + // C++ wrapper type for IfcJunctionBoxTypeEnum + typedef ENUMERATION IfcJunctionBoxTypeEnum; + // C++ wrapper type for IfcKnotType + typedef ENUMERATION IfcKnotType; + // C++ wrapper type for IfcLaborResourceTypeEnum + typedef ENUMERATION IfcLaborResourceTypeEnum; + // C++ wrapper type for IfcLampTypeEnum + typedef ENUMERATION IfcLampTypeEnum; + // C++ wrapper type for IfcLayerSetDirectionEnum + typedef ENUMERATION IfcLayerSetDirectionEnum; + // C++ wrapper type for IfcLightDistributionCurveEnum + typedef ENUMERATION IfcLightDistributionCurveEnum; + // C++ wrapper type for IfcLightEmissionSourceEnum + typedef ENUMERATION IfcLightEmissionSourceEnum; + // C++ wrapper type for IfcLightFixtureTypeEnum + typedef ENUMERATION IfcLightFixtureTypeEnum; + // C++ wrapper type for IfcLoadGroupTypeEnum + typedef ENUMERATION IfcLoadGroupTypeEnum; + // C++ wrapper type for IfcLogicalOperatorEnum + typedef ENUMERATION IfcLogicalOperatorEnum; + // C++ wrapper type for IfcMechanicalFastenerTypeEnum + typedef ENUMERATION IfcMechanicalFastenerTypeEnum; + // C++ wrapper type for IfcMedicalDeviceTypeEnum + typedef ENUMERATION IfcMedicalDeviceTypeEnum; + // C++ wrapper type for IfcMemberTypeEnum + typedef ENUMERATION IfcMemberTypeEnum; + // C++ wrapper type for IfcMotorConnectionTypeEnum + typedef ENUMERATION IfcMotorConnectionTypeEnum; + // C++ wrapper type for IfcNullStyle + typedef ENUMERATION IfcNullStyle; + // C++ wrapper type for IfcObjectTypeEnum + typedef ENUMERATION IfcObjectTypeEnum; + // C++ wrapper type for IfcObjectiveEnum + typedef ENUMERATION IfcObjectiveEnum; + // C++ wrapper type for IfcOccupantTypeEnum + typedef ENUMERATION IfcOccupantTypeEnum; + // C++ wrapper type for IfcOpeningElementTypeEnum + typedef ENUMERATION IfcOpeningElementTypeEnum; + // C++ wrapper type for IfcOutletTypeEnum + typedef ENUMERATION IfcOutletTypeEnum; + // C++ wrapper type for IfcPerformanceHistoryTypeEnum + typedef ENUMERATION IfcPerformanceHistoryTypeEnum; + // C++ wrapper type for IfcPermeableCoveringOperationEnum + typedef ENUMERATION IfcPermeableCoveringOperationEnum; + // C++ wrapper type for IfcPermitTypeEnum + typedef ENUMERATION IfcPermitTypeEnum; + // C++ wrapper type for IfcPhysicalOrVirtualEnum + typedef ENUMERATION IfcPhysicalOrVirtualEnum; + // C++ wrapper type for IfcPileConstructionEnum + typedef ENUMERATION IfcPileConstructionEnum; + // C++ wrapper type for IfcPileTypeEnum + typedef ENUMERATION IfcPileTypeEnum; + // C++ wrapper type for IfcPipeFittingTypeEnum + typedef ENUMERATION IfcPipeFittingTypeEnum; + // C++ wrapper type for IfcPipeSegmentTypeEnum + typedef ENUMERATION IfcPipeSegmentTypeEnum; + // C++ wrapper type for IfcPlateTypeEnum + typedef ENUMERATION IfcPlateTypeEnum; + // C++ wrapper type for IfcPreferredSurfaceCurveRepresentation + typedef ENUMERATION IfcPreferredSurfaceCurveRepresentation; + // C++ wrapper type for IfcProcedureTypeEnum + typedef ENUMERATION IfcProcedureTypeEnum; + // C++ wrapper type for IfcProfileTypeEnum + typedef ENUMERATION IfcProfileTypeEnum; + // C++ wrapper type for IfcProjectOrderTypeEnum + typedef ENUMERATION IfcProjectOrderTypeEnum; + // C++ wrapper type for IfcProjectedOrTrueLengthEnum + typedef ENUMERATION IfcProjectedOrTrueLengthEnum; + // C++ wrapper type for IfcProjectionElementTypeEnum + typedef ENUMERATION IfcProjectionElementTypeEnum; + // C++ wrapper type for IfcPropertySetTemplateTypeEnum + typedef ENUMERATION IfcPropertySetTemplateTypeEnum; + // C++ wrapper type for IfcProtectiveDeviceTrippingUnitTypeEnum + typedef ENUMERATION IfcProtectiveDeviceTrippingUnitTypeEnum; + // C++ wrapper type for IfcProtectiveDeviceTypeEnum + typedef ENUMERATION IfcProtectiveDeviceTypeEnum; + // C++ wrapper type for IfcPumpTypeEnum + typedef ENUMERATION IfcPumpTypeEnum; + // C++ wrapper type for IfcRailingTypeEnum + typedef ENUMERATION IfcRailingTypeEnum; + // C++ wrapper type for IfcRampFlightTypeEnum + typedef ENUMERATION IfcRampFlightTypeEnum; + // C++ wrapper type for IfcRampTypeEnum + typedef ENUMERATION IfcRampTypeEnum; + // C++ wrapper type for IfcRecurrenceTypeEnum + typedef ENUMERATION IfcRecurrenceTypeEnum; + // C++ wrapper type for IfcReflectanceMethodEnum + typedef ENUMERATION IfcReflectanceMethodEnum; + // C++ wrapper type for IfcReinforcingBarRoleEnum + typedef ENUMERATION IfcReinforcingBarRoleEnum; + // C++ wrapper type for IfcReinforcingBarSurfaceEnum + typedef ENUMERATION IfcReinforcingBarSurfaceEnum; + // C++ wrapper type for IfcReinforcingBarTypeEnum + typedef ENUMERATION IfcReinforcingBarTypeEnum; + // C++ wrapper type for IfcReinforcingMeshTypeEnum + typedef ENUMERATION IfcReinforcingMeshTypeEnum; + // C++ wrapper type for IfcRoleEnum + typedef ENUMERATION IfcRoleEnum; + // C++ wrapper type for IfcRoofTypeEnum + typedef ENUMERATION IfcRoofTypeEnum; + // C++ wrapper type for IfcSIPrefix + typedef ENUMERATION IfcSIPrefix; + // C++ wrapper type for IfcSIUnitName + typedef ENUMERATION IfcSIUnitName; + // C++ wrapper type for IfcSanitaryTerminalTypeEnum + typedef ENUMERATION IfcSanitaryTerminalTypeEnum; + // C++ wrapper type for IfcSectionTypeEnum + typedef ENUMERATION IfcSectionTypeEnum; + // C++ wrapper type for IfcSensorTypeEnum + typedef ENUMERATION IfcSensorTypeEnum; + // C++ wrapper type for IfcSequenceEnum + typedef ENUMERATION IfcSequenceEnum; + // C++ wrapper type for IfcShadingDeviceTypeEnum + typedef ENUMERATION IfcShadingDeviceTypeEnum; + // C++ wrapper type for IfcSimplePropertyTemplateTypeEnum + typedef ENUMERATION IfcSimplePropertyTemplateTypeEnum; + // C++ wrapper type for IfcSlabTypeEnum + typedef ENUMERATION IfcSlabTypeEnum; + // C++ wrapper type for IfcSolarDeviceTypeEnum + typedef ENUMERATION IfcSolarDeviceTypeEnum; + // C++ wrapper type for IfcSpaceHeaterTypeEnum + typedef ENUMERATION IfcSpaceHeaterTypeEnum; + // C++ wrapper type for IfcSpaceTypeEnum + typedef ENUMERATION IfcSpaceTypeEnum; + // C++ wrapper type for IfcSpatialZoneTypeEnum + typedef ENUMERATION IfcSpatialZoneTypeEnum; + // C++ wrapper type for IfcStackTerminalTypeEnum + typedef ENUMERATION IfcStackTerminalTypeEnum; + // C++ wrapper type for IfcStairFlightTypeEnum + typedef ENUMERATION IfcStairFlightTypeEnum; + // C++ wrapper type for IfcStairTypeEnum + typedef ENUMERATION IfcStairTypeEnum; + // C++ wrapper type for IfcStateEnum + typedef ENUMERATION IfcStateEnum; + // C++ wrapper type for IfcStructuralCurveActivityTypeEnum + typedef ENUMERATION IfcStructuralCurveActivityTypeEnum; + // C++ wrapper type for IfcStructuralCurveMemberTypeEnum + typedef ENUMERATION IfcStructuralCurveMemberTypeEnum; + // C++ wrapper type for IfcStructuralSurfaceActivityTypeEnum + typedef ENUMERATION IfcStructuralSurfaceActivityTypeEnum; + // C++ wrapper type for IfcStructuralSurfaceMemberTypeEnum + typedef ENUMERATION IfcStructuralSurfaceMemberTypeEnum; + // C++ wrapper type for IfcSubContractResourceTypeEnum + typedef ENUMERATION IfcSubContractResourceTypeEnum; + // C++ wrapper type for IfcSurfaceFeatureTypeEnum + typedef ENUMERATION IfcSurfaceFeatureTypeEnum; + // C++ wrapper type for IfcSurfaceSide + typedef ENUMERATION IfcSurfaceSide; + // C++ wrapper type for IfcSwitchingDeviceTypeEnum + typedef ENUMERATION IfcSwitchingDeviceTypeEnum; + // C++ wrapper type for IfcSystemFurnitureElementTypeEnum + typedef ENUMERATION IfcSystemFurnitureElementTypeEnum; + // C++ wrapper type for IfcTankTypeEnum + typedef ENUMERATION IfcTankTypeEnum; + // C++ wrapper type for IfcTaskDurationEnum + typedef ENUMERATION IfcTaskDurationEnum; + // C++ wrapper type for IfcTaskTypeEnum + typedef ENUMERATION IfcTaskTypeEnum; + // C++ wrapper type for IfcTendonAnchorTypeEnum + typedef ENUMERATION IfcTendonAnchorTypeEnum; + // C++ wrapper type for IfcTendonTypeEnum + typedef ENUMERATION IfcTendonTypeEnum; + // C++ wrapper type for IfcTextPath + typedef ENUMERATION IfcTextPath; + // C++ wrapper type for IfcTimeSeriesDataTypeEnum + typedef ENUMERATION IfcTimeSeriesDataTypeEnum; + // C++ wrapper type for IfcTransformerTypeEnum + typedef ENUMERATION IfcTransformerTypeEnum; + // C++ wrapper type for IfcTransitionCode + typedef ENUMERATION IfcTransitionCode; + // C++ wrapper type for IfcTransportElementTypeEnum + typedef ENUMERATION IfcTransportElementTypeEnum; + // C++ wrapper type for IfcTrimmingPreference + typedef ENUMERATION IfcTrimmingPreference; + // C++ wrapper type for IfcTubeBundleTypeEnum + typedef ENUMERATION IfcTubeBundleTypeEnum; + // C++ wrapper type for IfcUnitEnum + typedef ENUMERATION IfcUnitEnum; + // C++ wrapper type for IfcUnitaryControlElementTypeEnum + typedef ENUMERATION IfcUnitaryControlElementTypeEnum; + // C++ wrapper type for IfcUnitaryEquipmentTypeEnum + typedef ENUMERATION IfcUnitaryEquipmentTypeEnum; + // C++ wrapper type for IfcValveTypeEnum + typedef ENUMERATION IfcValveTypeEnum; + // C++ wrapper type for IfcVibrationIsolatorTypeEnum + typedef ENUMERATION IfcVibrationIsolatorTypeEnum; + // C++ wrapper type for IfcVoidingFeatureTypeEnum + typedef ENUMERATION IfcVoidingFeatureTypeEnum; + // C++ wrapper type for IfcWallTypeEnum + typedef ENUMERATION IfcWallTypeEnum; + // C++ wrapper type for IfcWasteTerminalTypeEnum + typedef ENUMERATION IfcWasteTerminalTypeEnum; + // C++ wrapper type for IfcWindowPanelOperationEnum + typedef ENUMERATION IfcWindowPanelOperationEnum; + // C++ wrapper type for IfcWindowPanelPositionEnum + typedef ENUMERATION IfcWindowPanelPositionEnum; + // C++ wrapper type for IfcWindowStyleConstructionEnum + typedef ENUMERATION IfcWindowStyleConstructionEnum; + // C++ wrapper type for IfcWindowStyleOperationEnum + typedef ENUMERATION IfcWindowStyleOperationEnum; + // C++ wrapper type for IfcWindowTypeEnum + typedef ENUMERATION IfcWindowTypeEnum; + // C++ wrapper type for IfcWindowTypePartitioningEnum + typedef ENUMERATION IfcWindowTypePartitioningEnum; + // C++ wrapper type for IfcWorkCalendarTypeEnum + typedef ENUMERATION IfcWorkCalendarTypeEnum; + // C++ wrapper type for IfcWorkPlanTypeEnum + typedef ENUMERATION IfcWorkPlanTypeEnum; + // C++ wrapper type for IfcWorkScheduleTypeEnum + typedef ENUMERATION IfcWorkScheduleTypeEnum; + // C++ wrapper type for IfcActorSelect + typedef SELECT IfcActorSelect; + // C++ wrapper type for IfcAppliedValueSelect + typedef SELECT IfcAppliedValueSelect; + // C++ wrapper type for IfcAxis2Placement + typedef SELECT IfcAxis2Placement; + // C++ wrapper type for IfcBendingParameterSelect + typedef SELECT IfcBendingParameterSelect; + // C++ wrapper type for IfcBooleanOperand + typedef SELECT IfcBooleanOperand; + // C++ wrapper type for IfcClassificationReferenceSelect + typedef SELECT IfcClassificationReferenceSelect; + // C++ wrapper type for IfcClassificationSelect + typedef SELECT IfcClassificationSelect; + // C++ wrapper type for IfcColour + typedef SELECT IfcColour; + // C++ wrapper type for IfcColourOrFactor + typedef SELECT IfcColourOrFactor; + // C++ wrapper type for IfcCoordinateReferenceSystemSelect + typedef SELECT IfcCoordinateReferenceSystemSelect; + // C++ wrapper type for IfcCsgSelect + typedef SELECT IfcCsgSelect; + // C++ wrapper type for IfcCurveFontOrScaledCurveFontSelect + typedef SELECT IfcCurveFontOrScaledCurveFontSelect; + // C++ wrapper type for IfcCurveOnSurface + typedef SELECT IfcCurveOnSurface; + // C++ wrapper type for IfcCurveOrEdgeCurve + typedef SELECT IfcCurveOrEdgeCurve; + // C++ wrapper type for IfcCurveStyleFontSelect + typedef SELECT IfcCurveStyleFontSelect; + // C++ wrapper type for IfcDefinitionSelect + typedef SELECT IfcDefinitionSelect; + // C++ wrapper type for IfcDerivedMeasureValue + typedef SELECT IfcDerivedMeasureValue; + // C++ wrapper type for IfcDocumentSelect + typedef SELECT IfcDocumentSelect; + // C++ wrapper type for IfcFillStyleSelect + typedef SELECT IfcFillStyleSelect; + // C++ wrapper type for IfcGeometricSetSelect + typedef SELECT IfcGeometricSetSelect; + // C++ wrapper type for IfcGridPlacementDirectionSelect + typedef SELECT IfcGridPlacementDirectionSelect; + // C++ wrapper type for IfcHatchLineDistanceSelect + typedef SELECT IfcHatchLineDistanceSelect; + // C++ wrapper type for IfcLayeredItem + typedef SELECT IfcLayeredItem; + // C++ wrapper type for IfcLibrarySelect + typedef SELECT IfcLibrarySelect; + // C++ wrapper type for IfcLightDistributionDataSourceSelect + typedef SELECT IfcLightDistributionDataSourceSelect; + // C++ wrapper type for IfcMaterialSelect + typedef SELECT IfcMaterialSelect; + // C++ wrapper type for IfcMeasureValue + typedef SELECT IfcMeasureValue; + // C++ wrapper type for IfcMetricValueSelect + typedef SELECT IfcMetricValueSelect; + // C++ wrapper type for IfcModulusOfRotationalSubgradeReactionSelect + typedef SELECT IfcModulusOfRotationalSubgradeReactionSelect; + // C++ wrapper type for IfcModulusOfSubgradeReactionSelect + typedef SELECT IfcModulusOfSubgradeReactionSelect; + // C++ wrapper type for IfcModulusOfTranslationalSubgradeReactionSelect + typedef SELECT IfcModulusOfTranslationalSubgradeReactionSelect; + // C++ wrapper type for IfcObjectReferenceSelect + typedef SELECT IfcObjectReferenceSelect; + // C++ wrapper type for IfcPointOrVertexPoint + typedef SELECT IfcPointOrVertexPoint; + // C++ wrapper type for IfcPresentationStyleSelect + typedef SELECT IfcPresentationStyleSelect; + // C++ wrapper type for IfcProcessSelect + typedef SELECT IfcProcessSelect; + // C++ wrapper type for IfcProductRepresentationSelect + typedef SELECT IfcProductRepresentationSelect; + // C++ wrapper type for IfcProductSelect + typedef SELECT IfcProductSelect; + // C++ wrapper type for IfcPropertySetDefinitionSelect + typedef SELECT IfcPropertySetDefinitionSelect; + // C++ wrapper type for IfcResourceObjectSelect + typedef SELECT IfcResourceObjectSelect; + // C++ wrapper type for IfcResourceSelect + typedef SELECT IfcResourceSelect; + // C++ wrapper type for IfcRotationalStiffnessSelect + typedef SELECT IfcRotationalStiffnessSelect; + // C++ wrapper type for IfcSegmentIndexSelect + typedef SELECT IfcSegmentIndexSelect; + // C++ wrapper type for IfcShell + typedef SELECT IfcShell; + // C++ wrapper type for IfcSimpleValue + typedef SELECT IfcSimpleValue; + // C++ wrapper type for IfcSizeSelect + typedef SELECT IfcSizeSelect; + // C++ wrapper type for IfcSolidOrShell + typedef SELECT IfcSolidOrShell; + // C++ wrapper type for IfcSpaceBoundarySelect + typedef SELECT IfcSpaceBoundarySelect; + // C++ wrapper type for IfcSpecularHighlightSelect + typedef SELECT IfcSpecularHighlightSelect; + // C++ wrapper type for IfcStructuralActivityAssignmentSelect + typedef SELECT IfcStructuralActivityAssignmentSelect; + // C++ wrapper type for IfcStyleAssignmentSelect + typedef SELECT IfcStyleAssignmentSelect; + // C++ wrapper type for IfcSurfaceOrFaceSurface + typedef SELECT IfcSurfaceOrFaceSurface; + // C++ wrapper type for IfcSurfaceStyleElementSelect + typedef SELECT IfcSurfaceStyleElementSelect; + // C++ wrapper type for IfcTextFontSelect + typedef SELECT IfcTextFontSelect; + // C++ wrapper type for IfcTimeOrRatioSelect + typedef SELECT IfcTimeOrRatioSelect; + // C++ wrapper type for IfcTranslationalStiffnessSelect + typedef SELECT IfcTranslationalStiffnessSelect; + // C++ wrapper type for IfcTrimmingSelect + typedef SELECT IfcTrimmingSelect; + // C++ wrapper type for IfcUnit + typedef SELECT IfcUnit; + // C++ wrapper type for IfcValue + typedef SELECT IfcValue; + // C++ wrapper type for IfcVectorOrDirection + typedef SELECT IfcVectorOrDirection; + // C++ wrapper type for IfcWarpingStiffnessSelect + typedef SELECT IfcWarpingStiffnessSelect; + + + // ****************************************************************************** + // IFC Entities + // ****************************************************************************** + + struct IfcRoot; + struct IfcObjectDefinition; + struct IfcObject; + struct IfcControl; + struct IfcActionRequest; + struct IfcActor; + typedef NotImplemented IfcActorRole; // (not currently used by Assimp) + struct IfcProduct; + struct IfcElement; + struct IfcDistributionElement; + struct IfcDistributionControlElement; + struct IfcActuator; + struct IfcTypeObject; + struct IfcTypeProduct; + struct IfcElementType; + struct IfcDistributionElementType; + struct IfcDistributionControlElementType; + struct IfcActuatorType; + typedef NotImplemented IfcAddress; // (not currently used by Assimp) + struct IfcRepresentationItem; + struct IfcGeometricRepresentationItem; + struct IfcSolidModel; + struct IfcManifoldSolidBrep; + struct IfcAdvancedBrep; + struct IfcAdvancedBrepWithVoids; + struct IfcTopologicalRepresentationItem; + struct IfcFace; + struct IfcFaceSurface; + struct IfcAdvancedFace; + struct IfcDistributionFlowElement; + struct IfcFlowTerminal; + struct IfcAirTerminal; + struct IfcFlowController; + struct IfcAirTerminalBox; + struct IfcDistributionFlowElementType; + struct IfcFlowControllerType; + struct IfcAirTerminalBoxType; + struct IfcFlowTerminalType; + struct IfcAirTerminalType; + struct IfcEnergyConversionDevice; + struct IfcAirToAirHeatRecovery; + struct IfcEnergyConversionDeviceType; + struct IfcAirToAirHeatRecoveryType; + struct IfcAlarm; + struct IfcAlarmType; + struct IfcAnnotation; + struct IfcAnnotationFillArea; + typedef NotImplemented IfcApplication; // (not currently used by Assimp) + typedef NotImplemented IfcAppliedValue; // (not currently used by Assimp) + typedef NotImplemented IfcApproval; // (not currently used by Assimp) + typedef NotImplemented IfcResourceLevelRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcApprovalRelationship; // (not currently used by Assimp) + struct IfcProfileDef; + struct IfcArbitraryClosedProfileDef; + struct IfcArbitraryOpenProfileDef; + struct IfcArbitraryProfileDefWithVoids; + struct IfcGroup; + struct IfcAsset; + struct IfcParameterizedProfileDef; + struct IfcAsymmetricIShapeProfileDef; + struct IfcAudioVisualAppliance; + struct IfcAudioVisualApplianceType; + struct IfcPlacement; + struct IfcAxis1Placement; + struct IfcAxis2Placement2D; + struct IfcAxis2Placement3D; + struct IfcCurve; + struct IfcBoundedCurve; + struct IfcBSplineCurve; + struct IfcBSplineCurveWithKnots; + struct IfcSurface; + struct IfcBoundedSurface; + struct IfcBSplineSurface; + struct IfcBSplineSurfaceWithKnots; + struct IfcBuildingElement; + struct IfcBeam; + struct IfcBeamStandardCase; + struct IfcBuildingElementType; + struct IfcBeamType; + struct IfcPresentationItem; + typedef NotImplemented IfcSurfaceTexture; // (not currently used by Assimp) + typedef NotImplemented IfcBlobTexture; // (not currently used by Assimp) + struct IfcCsgPrimitive3D; + struct IfcBlock; + struct IfcBoiler; + struct IfcBoilerType; + struct IfcBooleanResult; + struct IfcBooleanClippingResult; + typedef NotImplemented IfcBoundaryCondition; // (not currently used by Assimp) + struct IfcCompositeCurve; + struct IfcCompositeCurveOnSurface; + struct IfcBoundaryCurve; + typedef NotImplemented IfcBoundaryEdgeCondition; // (not currently used by Assimp) + typedef NotImplemented IfcBoundaryFaceCondition; // (not currently used by Assimp) + typedef NotImplemented IfcBoundaryNodeCondition; // (not currently used by Assimp) + typedef NotImplemented IfcBoundaryNodeConditionWarping; // (not currently used by Assimp) + struct IfcBoundingBox; + struct IfcHalfSpaceSolid; + struct IfcBoxedHalfSpace; + struct IfcSpatialElement; + struct IfcSpatialStructureElement; + struct IfcBuilding; + struct IfcElementComponent; + struct IfcBuildingElementPart; + struct IfcElementComponentType; + struct IfcBuildingElementPartType; + struct IfcBuildingElementProxy; + struct IfcBuildingElementProxyType; + struct IfcBuildingStorey; + struct IfcSystem; + struct IfcBuildingSystem; + struct IfcBurner; + struct IfcBurnerType; + struct IfcCShapeProfileDef; + struct IfcFlowFitting; + struct IfcCableCarrierFitting; + struct IfcFlowFittingType; + struct IfcCableCarrierFittingType; + struct IfcFlowSegment; + struct IfcCableCarrierSegment; + struct IfcFlowSegmentType; + struct IfcCableCarrierSegmentType; + struct IfcCableFitting; + struct IfcCableFittingType; + struct IfcCableSegment; + struct IfcCableSegmentType; + struct IfcPoint; + struct IfcCartesianPoint; + struct IfcCartesianPointList; + struct IfcCartesianPointList2D; + struct IfcCartesianPointList3D; + struct IfcCartesianTransformationOperator; + struct IfcCartesianTransformationOperator2D; + struct IfcCartesianTransformationOperator2DnonUniform; + struct IfcCartesianTransformationOperator3D; + struct IfcCartesianTransformationOperator3DnonUniform; + struct IfcCenterLineProfileDef; + struct IfcChiller; + struct IfcChillerType; + struct IfcChimney; + struct IfcChimneyType; + struct IfcConic; + struct IfcCircle; + struct IfcCircleProfileDef; + struct IfcCircleHollowProfileDef; + struct IfcCivilElement; + struct IfcCivilElementType; + typedef NotImplemented IfcExternalInformation; // (not currently used by Assimp) + typedef NotImplemented IfcClassification; // (not currently used by Assimp) + typedef NotImplemented IfcExternalReference; // (not currently used by Assimp) + typedef NotImplemented IfcClassificationReference; // (not currently used by Assimp) + struct IfcConnectedFaceSet; + struct IfcClosedShell; + struct IfcCoil; + struct IfcCoilType; + struct IfcColourSpecification; + struct IfcColourRgb; + typedef NotImplemented IfcColourRgbList; // (not currently used by Assimp) + struct IfcColumn; + struct IfcColumnStandardCase; + struct IfcColumnType; + struct IfcCommunicationsAppliance; + struct IfcCommunicationsApplianceType; + struct IfcPropertyAbstraction; + struct IfcProperty; + struct IfcComplexProperty; + struct IfcPropertyDefinition; + typedef NotImplemented IfcPropertyTemplateDefinition; // (not currently used by Assimp) + typedef NotImplemented IfcPropertyTemplate; // (not currently used by Assimp) + typedef NotImplemented IfcComplexPropertyTemplate; // (not currently used by Assimp) + struct IfcCompositeCurveSegment; + struct IfcCompositeProfileDef; + struct IfcFlowMovingDevice; + struct IfcCompressor; + struct IfcFlowMovingDeviceType; + struct IfcCompressorType; + struct IfcCondenser; + struct IfcCondenserType; + typedef NotImplemented IfcConnectionGeometry; // (not currently used by Assimp) + typedef NotImplemented IfcConnectionCurveGeometry; // (not currently used by Assimp) + typedef NotImplemented IfcConnectionPointGeometry; // (not currently used by Assimp) + typedef NotImplemented IfcConnectionPointEccentricity; // (not currently used by Assimp) + typedef NotImplemented IfcConnectionSurfaceGeometry; // (not currently used by Assimp) + typedef NotImplemented IfcConnectionVolumeGeometry; // (not currently used by Assimp) + typedef NotImplemented IfcConstraint; // (not currently used by Assimp) + struct IfcResource; + struct IfcConstructionResource; + struct IfcConstructionEquipmentResource; + struct IfcTypeResource; + struct IfcConstructionResourceType; + struct IfcConstructionEquipmentResourceType; + struct IfcConstructionMaterialResource; + struct IfcConstructionMaterialResourceType; + struct IfcConstructionProductResource; + struct IfcConstructionProductResourceType; + struct IfcContext; + struct IfcNamedUnit; + struct IfcContextDependentUnit; + struct IfcController; + struct IfcControllerType; + struct IfcConversionBasedUnit; + struct IfcConversionBasedUnitWithOffset; + struct IfcCooledBeam; + struct IfcCooledBeamType; + struct IfcCoolingTower; + struct IfcCoolingTowerType; + typedef NotImplemented IfcCoordinateOperation; // (not currently used by Assimp) + typedef NotImplemented IfcCoordinateReferenceSystem; // (not currently used by Assimp) + struct IfcCostItem; + struct IfcCostSchedule; + typedef NotImplemented IfcCostValue; // (not currently used by Assimp) + struct IfcCovering; + struct IfcCoveringType; + struct IfcCrewResource; + struct IfcCrewResourceType; + struct IfcCsgSolid; + typedef NotImplemented IfcCurrencyRelationship; // (not currently used by Assimp) + struct IfcCurtainWall; + struct IfcCurtainWallType; + struct IfcCurveBoundedPlane; + struct IfcCurveBoundedSurface; + struct IfcPresentationStyle; + typedef NotImplemented IfcCurveStyle; // (not currently used by Assimp) + typedef NotImplemented IfcCurveStyleFont; // (not currently used by Assimp) + typedef NotImplemented IfcCurveStyleFontAndScaling; // (not currently used by Assimp) + typedef NotImplemented IfcCurveStyleFontPattern; // (not currently used by Assimp) + struct IfcElementarySurface; + struct IfcCylindricalSurface; + struct IfcDamper; + struct IfcDamperType; + struct IfcDerivedProfileDef; + typedef NotImplemented IfcDerivedUnit; // (not currently used by Assimp) + typedef NotImplemented IfcDerivedUnitElement; // (not currently used by Assimp) + typedef NotImplemented IfcDimensionalExponents; // (not currently used by Assimp) + struct IfcDirection; + struct IfcDiscreteAccessory; + struct IfcDiscreteAccessoryType; + struct IfcDistributionChamberElement; + struct IfcDistributionChamberElementType; + struct IfcDistributionSystem; + struct IfcDistributionCircuit; + struct IfcPort; + struct IfcDistributionPort; + typedef NotImplemented IfcDocumentInformation; // (not currently used by Assimp) + typedef NotImplemented IfcDocumentInformationRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcDocumentReference; // (not currently used by Assimp) + struct IfcDoor; + struct IfcPropertySetDefinition; + typedef NotImplemented IfcPreDefinedPropertySet; // (not currently used by Assimp) + typedef NotImplemented IfcDoorLiningProperties; // (not currently used by Assimp) + typedef NotImplemented IfcDoorPanelProperties; // (not currently used by Assimp) + struct IfcDoorStandardCase; + struct IfcDoorStyle; + struct IfcDoorType; + typedef NotImplemented IfcPreDefinedItem; // (not currently used by Assimp) + typedef NotImplemented IfcPreDefinedColour; // (not currently used by Assimp) + typedef NotImplemented IfcDraughtingPreDefinedColour; // (not currently used by Assimp) + typedef NotImplemented IfcPreDefinedCurveFont; // (not currently used by Assimp) + typedef NotImplemented IfcDraughtingPreDefinedCurveFont; // (not currently used by Assimp) + struct IfcDuctFitting; + struct IfcDuctFittingType; + struct IfcDuctSegment; + struct IfcDuctSegmentType; + struct IfcFlowTreatmentDevice; + struct IfcDuctSilencer; + struct IfcFlowTreatmentDeviceType; + struct IfcDuctSilencerType; + struct IfcEdge; + struct IfcEdgeCurve; + struct IfcLoop; + struct IfcEdgeLoop; + struct IfcElectricAppliance; + struct IfcElectricApplianceType; + struct IfcElectricDistributionBoard; + struct IfcElectricDistributionBoardType; + struct IfcFlowStorageDevice; + struct IfcElectricFlowStorageDevice; + struct IfcFlowStorageDeviceType; + struct IfcElectricFlowStorageDeviceType; + struct IfcElectricGenerator; + struct IfcElectricGeneratorType; + struct IfcElectricMotor; + struct IfcElectricMotorType; + struct IfcElectricTimeControl; + struct IfcElectricTimeControlType; + struct IfcElementAssembly; + struct IfcElementAssemblyType; + struct IfcQuantitySet; + struct IfcElementQuantity; + struct IfcEllipse; + struct IfcEllipseProfileDef; + struct IfcEngine; + struct IfcEngineType; + struct IfcEvaporativeCooler; + struct IfcEvaporativeCoolerType; + struct IfcEvaporator; + struct IfcEvaporatorType; + struct IfcProcess; + struct IfcEvent; + typedef NotImplemented IfcSchedulingTime; // (not currently used by Assimp) + typedef NotImplemented IfcEventTime; // (not currently used by Assimp) + struct IfcTypeProcess; + struct IfcEventType; + typedef NotImplemented IfcExtendedProperties; // (not currently used by Assimp) + typedef NotImplemented IfcExternalReferenceRelationship; // (not currently used by Assimp) + struct IfcExternalSpatialStructureElement; + struct IfcExternalSpatialElement; + typedef NotImplemented IfcExternallyDefinedHatchStyle; // (not currently used by Assimp) + typedef NotImplemented IfcExternallyDefinedSurfaceStyle; // (not currently used by Assimp) + typedef NotImplemented IfcExternallyDefinedTextFont; // (not currently used by Assimp) + struct IfcSweptAreaSolid; + struct IfcExtrudedAreaSolid; + struct IfcExtrudedAreaSolidTapered; + struct IfcFaceBasedSurfaceModel; + struct IfcFaceBound; + struct IfcFaceOuterBound; + struct IfcFacetedBrep; + struct IfcFacetedBrepWithVoids; + typedef NotImplemented IfcStructuralConnectionCondition; // (not currently used by Assimp) + typedef NotImplemented IfcFailureConnectionCondition; // (not currently used by Assimp) + struct IfcFan; + struct IfcFanType; + struct IfcFastener; + struct IfcFastenerType; + struct IfcFeatureElement; + struct IfcFeatureElementAddition; + struct IfcFeatureElementSubtraction; + typedef NotImplemented IfcFillAreaStyle; // (not currently used by Assimp) + struct IfcFillAreaStyleHatching; + struct IfcFillAreaStyleTiles; + struct IfcFilter; + struct IfcFilterType; + struct IfcFireSuppressionTerminal; + struct IfcFireSuppressionTerminalType; + struct IfcFixedReferenceSweptAreaSolid; + struct IfcFlowInstrument; + struct IfcFlowInstrumentType; + struct IfcFlowMeter; + struct IfcFlowMeterType; + struct IfcFooting; + struct IfcFootingType; + struct IfcFurnishingElement; + struct IfcFurnishingElementType; + struct IfcFurniture; + struct IfcFurnitureType; + struct IfcGeographicElement; + struct IfcGeographicElementType; + struct IfcGeometricSet; + struct IfcGeometricCurveSet; + struct IfcRepresentationContext; + struct IfcGeometricRepresentationContext; + struct IfcGeometricRepresentationSubContext; + struct IfcGrid; + typedef NotImplemented IfcGridAxis; // (not currently used by Assimp) + struct IfcObjectPlacement; + struct IfcGridPlacement; + struct IfcHeatExchanger; + struct IfcHeatExchangerType; + struct IfcHumidifier; + struct IfcHumidifierType; + struct IfcIShapeProfileDef; + typedef NotImplemented IfcImageTexture; // (not currently used by Assimp) + typedef NotImplemented IfcIndexedColourMap; // (not currently used by Assimp) + struct IfcIndexedPolyCurve; + struct IfcTessellatedItem; + struct IfcIndexedPolygonalFace; + struct IfcIndexedPolygonalFaceWithVoids; + typedef NotImplemented IfcTextureCoordinate; // (not currently used by Assimp) + typedef NotImplemented IfcIndexedTextureMap; // (not currently used by Assimp) + typedef NotImplemented IfcIndexedTriangleTextureMap; // (not currently used by Assimp) + struct IfcInterceptor; + struct IfcInterceptorType; + struct IfcSurfaceCurve; + struct IfcIntersectionCurve; + struct IfcInventory; + typedef NotImplemented IfcTimeSeries; // (not currently used by Assimp) + typedef NotImplemented IfcIrregularTimeSeries; // (not currently used by Assimp) + typedef NotImplemented IfcIrregularTimeSeriesValue; // (not currently used by Assimp) + struct IfcJunctionBox; + struct IfcJunctionBoxType; + struct IfcLShapeProfileDef; + struct IfcLaborResource; + struct IfcLaborResourceType; + typedef NotImplemented IfcLagTime; // (not currently used by Assimp) + struct IfcLamp; + struct IfcLampType; + typedef NotImplemented IfcLibraryInformation; // (not currently used by Assimp) + typedef NotImplemented IfcLibraryReference; // (not currently used by Assimp) + typedef NotImplemented IfcLightDistributionData; // (not currently used by Assimp) + struct IfcLightFixture; + struct IfcLightFixtureType; + typedef NotImplemented IfcLightIntensityDistribution; // (not currently used by Assimp) + struct IfcLightSource; + struct IfcLightSourceAmbient; + struct IfcLightSourceDirectional; + struct IfcLightSourceGoniometric; + struct IfcLightSourcePositional; + struct IfcLightSourceSpot; + struct IfcLine; + struct IfcLocalPlacement; + typedef NotImplemented IfcMapConversion; // (not currently used by Assimp) + struct IfcMappedItem; + typedef NotImplemented IfcMaterialDefinition; // (not currently used by Assimp) + typedef NotImplemented IfcMaterial; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialClassificationRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialConstituent; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialConstituentSet; // (not currently used by Assimp) + struct IfcProductRepresentation; + struct IfcMaterialDefinitionRepresentation; + typedef NotImplemented IfcMaterialLayer; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialLayerSet; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialUsageDefinition; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialLayerSetUsage; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialLayerWithOffsets; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialList; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialProfile; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialProfileSet; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialProfileSetUsage; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialProfileSetUsageTapering; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialProfileWithOffsets; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialProperties; // (not currently used by Assimp) + typedef NotImplemented IfcMaterialRelationship; // (not currently used by Assimp) + struct IfcMeasureWithUnit; + struct IfcMechanicalFastener; + struct IfcMechanicalFastenerType; + struct IfcMedicalDevice; + struct IfcMedicalDeviceType; + struct IfcMember; + struct IfcMemberStandardCase; + struct IfcMemberType; + typedef NotImplemented IfcMetric; // (not currently used by Assimp) + struct IfcMirroredProfileDef; + typedef NotImplemented IfcMonetaryUnit; // (not currently used by Assimp) + struct IfcMotorConnection; + struct IfcMotorConnectionType; + typedef NotImplemented IfcObjective; // (not currently used by Assimp) + struct IfcOccupant; + struct IfcOffsetCurve2D; + struct IfcOffsetCurve3D; + struct IfcOpenShell; + struct IfcOpeningElement; + struct IfcOpeningStandardCase; + typedef NotImplemented IfcOrganization; // (not currently used by Assimp) + typedef NotImplemented IfcOrganizationRelationship; // (not currently used by Assimp) + struct IfcOrientedEdge; + struct IfcOuterBoundaryCurve; + struct IfcOutlet; + struct IfcOutletType; + typedef NotImplemented IfcOwnerHistory; // (not currently used by Assimp) + struct IfcPath; + struct IfcPcurve; + struct IfcPerformanceHistory; + typedef NotImplemented IfcPermeableCoveringProperties; // (not currently used by Assimp) + struct IfcPermit; + typedef NotImplemented IfcPerson; // (not currently used by Assimp) + typedef NotImplemented IfcPersonAndOrganization; // (not currently used by Assimp) + typedef NotImplemented IfcPhysicalQuantity; // (not currently used by Assimp) + typedef NotImplemented IfcPhysicalComplexQuantity; // (not currently used by Assimp) + typedef NotImplemented IfcPhysicalSimpleQuantity; // (not currently used by Assimp) + struct IfcPile; + struct IfcPileType; + struct IfcPipeFitting; + struct IfcPipeFittingType; + struct IfcPipeSegment; + struct IfcPipeSegmentType; + typedef NotImplemented IfcPixelTexture; // (not currently used by Assimp) + struct IfcPlanarExtent; + struct IfcPlanarBox; + struct IfcPlane; + struct IfcPlate; + struct IfcPlateStandardCase; + struct IfcPlateType; + struct IfcPointOnCurve; + struct IfcPointOnSurface; + struct IfcPolyLoop; + struct IfcPolygonalBoundedHalfSpace; + struct IfcTessellatedFaceSet; + struct IfcPolygonalFaceSet; + struct IfcPolyline; + typedef NotImplemented IfcPostalAddress; // (not currently used by Assimp) + typedef NotImplemented IfcPreDefinedProperties; // (not currently used by Assimp) + typedef NotImplemented IfcPreDefinedTextFont; // (not currently used by Assimp) + typedef NotImplemented IfcPresentationLayerAssignment; // (not currently used by Assimp) + typedef NotImplemented IfcPresentationLayerWithStyle; // (not currently used by Assimp) + struct IfcPresentationStyleAssignment; + struct IfcProcedure; + struct IfcProcedureType; + struct IfcProductDefinitionShape; + typedef NotImplemented IfcProfileProperties; // (not currently used by Assimp) + struct IfcProject; + struct IfcProjectLibrary; + struct IfcProjectOrder; + typedef NotImplemented IfcProjectedCRS; // (not currently used by Assimp) + struct IfcProjectionElement; + struct IfcSimpleProperty; + struct IfcPropertyBoundedValue; + typedef NotImplemented IfcPropertyDependencyRelationship; // (not currently used by Assimp) + struct IfcPropertyEnumeratedValue; + typedef NotImplemented IfcPropertyEnumeration; // (not currently used by Assimp) + struct IfcPropertyListValue; + struct IfcPropertyReferenceValue; + struct IfcPropertySet; + typedef NotImplemented IfcPropertySetTemplate; // (not currently used by Assimp) + struct IfcPropertySingleValue; + struct IfcPropertyTableValue; + struct IfcProtectiveDevice; + struct IfcProtectiveDeviceTrippingUnit; + struct IfcProtectiveDeviceTrippingUnitType; + struct IfcProtectiveDeviceType; + struct IfcProxy; + struct IfcPump; + struct IfcPumpType; + typedef NotImplemented IfcQuantityArea; // (not currently used by Assimp) + typedef NotImplemented IfcQuantityCount; // (not currently used by Assimp) + typedef NotImplemented IfcQuantityLength; // (not currently used by Assimp) + typedef NotImplemented IfcQuantityTime; // (not currently used by Assimp) + typedef NotImplemented IfcQuantityVolume; // (not currently used by Assimp) + typedef NotImplemented IfcQuantityWeight; // (not currently used by Assimp) + struct IfcRailing; + struct IfcRailingType; + struct IfcRamp; + struct IfcRampFlight; + struct IfcRampFlightType; + struct IfcRampType; + struct IfcRationalBSplineCurveWithKnots; + struct IfcRationalBSplineSurfaceWithKnots; + struct IfcRectangleProfileDef; + struct IfcRectangleHollowProfileDef; + struct IfcRectangularPyramid; + struct IfcRectangularTrimmedSurface; + typedef NotImplemented IfcRecurrencePattern; // (not currently used by Assimp) + typedef NotImplemented IfcReference; // (not currently used by Assimp) + typedef NotImplemented IfcRegularTimeSeries; // (not currently used by Assimp) + typedef NotImplemented IfcReinforcementBarProperties; // (not currently used by Assimp) + typedef NotImplemented IfcReinforcementDefinitionProperties; // (not currently used by Assimp) + struct IfcReinforcingElement; + struct IfcReinforcingBar; + struct IfcReinforcingElementType; + struct IfcReinforcingBarType; + struct IfcReinforcingMesh; + struct IfcReinforcingMeshType; + struct IfcRelationship; + struct IfcRelDecomposes; + struct IfcRelAggregates; + typedef NotImplemented IfcRelAssigns; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToActor; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToControl; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToGroup; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToGroupByFactor; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToProcess; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToProduct; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssignsToResource; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociates; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesApproval; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesClassification; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesConstraint; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesDocument; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesLibrary; // (not currently used by Assimp) + typedef NotImplemented IfcRelAssociatesMaterial; // (not currently used by Assimp) + struct IfcRelConnects; + typedef NotImplemented IfcRelConnectsElements; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsPathElements; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsPortToElement; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsPorts; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsStructuralActivity; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsStructuralMember; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsWithEccentricity; // (not currently used by Assimp) + typedef NotImplemented IfcRelConnectsWithRealizingElements; // (not currently used by Assimp) + struct IfcRelContainedInSpatialStructure; + typedef NotImplemented IfcRelCoversBldgElements; // (not currently used by Assimp) + typedef NotImplemented IfcRelCoversSpaces; // (not currently used by Assimp) + typedef NotImplemented IfcRelDeclares; // (not currently used by Assimp) + struct IfcRelDefines; + typedef NotImplemented IfcRelDefinesByObject; // (not currently used by Assimp) + struct IfcRelDefinesByProperties; + typedef NotImplemented IfcRelDefinesByTemplate; // (not currently used by Assimp) + typedef NotImplemented IfcRelDefinesByType; // (not currently used by Assimp) + struct IfcRelFillsElement; + typedef NotImplemented IfcRelFlowControlElements; // (not currently used by Assimp) + typedef NotImplemented IfcRelInterferesElements; // (not currently used by Assimp) + typedef NotImplemented IfcRelNests; // (not currently used by Assimp) + typedef NotImplemented IfcRelProjectsElement; // (not currently used by Assimp) + typedef NotImplemented IfcRelReferencedInSpatialStructure; // (not currently used by Assimp) + typedef NotImplemented IfcRelSequence; // (not currently used by Assimp) + typedef NotImplemented IfcRelServicesBuildings; // (not currently used by Assimp) + typedef NotImplemented IfcRelSpaceBoundary; // (not currently used by Assimp) + typedef NotImplemented IfcRelSpaceBoundary1stLevel; // (not currently used by Assimp) + typedef NotImplemented IfcRelSpaceBoundary2ndLevel; // (not currently used by Assimp) + struct IfcRelVoidsElement; + struct IfcReparametrisedCompositeCurveSegment; + struct IfcRepresentation; + struct IfcRepresentationMap; + typedef NotImplemented IfcResourceApprovalRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcResourceConstraintRelationship; // (not currently used by Assimp) + typedef NotImplemented IfcResourceTime; // (not currently used by Assimp) + struct IfcRevolvedAreaSolid; + struct IfcRevolvedAreaSolidTapered; + struct IfcRightCircularCone; + struct IfcRightCircularCylinder; + struct IfcRoof; + struct IfcRoofType; + struct IfcRoundedRectangleProfileDef; + struct IfcSIUnit; + struct IfcSanitaryTerminal; + struct IfcSanitaryTerminalType; + struct IfcSeamCurve; + typedef NotImplemented IfcSectionProperties; // (not currently used by Assimp) + typedef NotImplemented IfcSectionReinforcementProperties; // (not currently used by Assimp) + struct IfcSectionedSpine; + struct IfcSensor; + struct IfcSensorType; + struct IfcShadingDevice; + struct IfcShadingDeviceType; + typedef NotImplemented IfcShapeAspect; // (not currently used by Assimp) + struct IfcShapeModel; + struct IfcShapeRepresentation; + struct IfcShellBasedSurfaceModel; + typedef NotImplemented IfcSimplePropertyTemplate; // (not currently used by Assimp) + struct IfcSite; + struct IfcSlab; + struct IfcSlabElementedCase; + struct IfcSlabStandardCase; + struct IfcSlabType; + typedef NotImplemented IfcSlippageConnectionCondition; // (not currently used by Assimp) + struct IfcSolarDevice; + struct IfcSolarDeviceType; + struct IfcSpace; + struct IfcSpaceHeater; + struct IfcSpaceHeaterType; + struct IfcSpatialElementType; + struct IfcSpatialStructureElementType; + struct IfcSpaceType; + struct IfcSpatialZone; + struct IfcSpatialZoneType; + struct IfcSphere; + struct IfcSphericalSurface; + struct IfcStackTerminal; + struct IfcStackTerminalType; + struct IfcStair; + struct IfcStairFlight; + struct IfcStairFlightType; + struct IfcStairType; + struct IfcStructuralActivity; + struct IfcStructuralAction; + struct IfcStructuralAnalysisModel; + struct IfcStructuralItem; + struct IfcStructuralConnection; + struct IfcStructuralCurveAction; + struct IfcStructuralCurveConnection; + struct IfcStructuralMember; + struct IfcStructuralCurveMember; + struct IfcStructuralCurveMemberVarying; + struct IfcStructuralReaction; + struct IfcStructuralCurveReaction; + struct IfcStructuralLinearAction; + typedef NotImplemented IfcStructuralLoad; // (not currently used by Assimp) + struct IfcStructuralLoadGroup; + struct IfcStructuralLoadCase; + typedef NotImplemented IfcStructuralLoadConfiguration; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadOrResult; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadStatic; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadLinearForce; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadPlanarForce; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadSingleDisplacement; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadSingleDisplacementDistortion; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadSingleForce; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadSingleForceWarping; // (not currently used by Assimp) + typedef NotImplemented IfcStructuralLoadTemperature; // (not currently used by Assimp) + struct IfcStructuralSurfaceAction; + struct IfcStructuralPlanarAction; + struct IfcStructuralPointAction; + struct IfcStructuralPointConnection; + struct IfcStructuralPointReaction; + struct IfcStructuralResultGroup; + struct IfcStructuralSurfaceConnection; + struct IfcStructuralSurfaceMember; + struct IfcStructuralSurfaceMemberVarying; + struct IfcStructuralSurfaceReaction; + struct IfcStyleModel; + struct IfcStyledItem; + struct IfcStyledRepresentation; + struct IfcSubContractResource; + struct IfcSubContractResourceType; + struct IfcSubedge; + struct IfcSurfaceCurveSweptAreaSolid; + struct IfcSurfaceFeature; + struct IfcSweptSurface; + struct IfcSurfaceOfLinearExtrusion; + struct IfcSurfaceOfRevolution; + typedef NotImplemented IfcSurfaceReinforcementArea; // (not currently used by Assimp) + struct IfcSurfaceStyle; + typedef NotImplemented IfcSurfaceStyleLighting; // (not currently used by Assimp) + typedef NotImplemented IfcSurfaceStyleRefraction; // (not currently used by Assimp) + struct IfcSurfaceStyleShading; + struct IfcSurfaceStyleRendering; + struct IfcSurfaceStyleWithTextures; + struct IfcSweptDiskSolid; + struct IfcSweptDiskSolidPolygonal; + struct IfcSwitchingDevice; + struct IfcSwitchingDeviceType; + struct IfcSystemFurnitureElement; + struct IfcSystemFurnitureElementType; + struct IfcTShapeProfileDef; + typedef NotImplemented IfcTable; // (not currently used by Assimp) + typedef NotImplemented IfcTableColumn; // (not currently used by Assimp) + typedef NotImplemented IfcTableRow; // (not currently used by Assimp) + struct IfcTank; + struct IfcTankType; + struct IfcTask; + typedef NotImplemented IfcTaskTime; // (not currently used by Assimp) + typedef NotImplemented IfcTaskTimeRecurring; // (not currently used by Assimp) + struct IfcTaskType; + typedef NotImplemented IfcTelecomAddress; // (not currently used by Assimp) + struct IfcTendon; + struct IfcTendonAnchor; + struct IfcTendonAnchorType; + struct IfcTendonType; + struct IfcTextLiteral; + struct IfcTextLiteralWithExtent; + typedef NotImplemented IfcTextStyle; // (not currently used by Assimp) + typedef NotImplemented IfcTextStyleFontModel; // (not currently used by Assimp) + typedef NotImplemented IfcTextStyleForDefinedFont; // (not currently used by Assimp) + typedef NotImplemented IfcTextStyleTextModel; // (not currently used by Assimp) + typedef NotImplemented IfcTextureCoordinateGenerator; // (not currently used by Assimp) + typedef NotImplemented IfcTextureMap; // (not currently used by Assimp) + typedef NotImplemented IfcTextureVertex; // (not currently used by Assimp) + typedef NotImplemented IfcTextureVertexList; // (not currently used by Assimp) + typedef NotImplemented IfcTimePeriod; // (not currently used by Assimp) + typedef NotImplemented IfcTimeSeriesValue; // (not currently used by Assimp) + struct IfcTopologyRepresentation; + struct IfcToroidalSurface; + struct IfcTransformer; + struct IfcTransformerType; + struct IfcTransportElement; + struct IfcTransportElementType; + struct IfcTrapeziumProfileDef; + struct IfcTriangulatedFaceSet; + struct IfcTrimmedCurve; + struct IfcTubeBundle; + struct IfcTubeBundleType; + struct IfcUShapeProfileDef; + struct IfcUnitAssignment; + struct IfcUnitaryControlElement; + struct IfcUnitaryControlElementType; + struct IfcUnitaryEquipment; + struct IfcUnitaryEquipmentType; + struct IfcValve; + struct IfcValveType; + struct IfcVector; + struct IfcVertex; + struct IfcVertexLoop; + struct IfcVertexPoint; + struct IfcVibrationIsolator; + struct IfcVibrationIsolatorType; + struct IfcVirtualElement; + typedef NotImplemented IfcVirtualGridIntersection; // (not currently used by Assimp) + struct IfcVoidingFeature; + struct IfcWall; + struct IfcWallElementedCase; + struct IfcWallStandardCase; + struct IfcWallType; + struct IfcWasteTerminal; + struct IfcWasteTerminalType; + struct IfcWindow; + typedef NotImplemented IfcWindowLiningProperties; // (not currently used by Assimp) + typedef NotImplemented IfcWindowPanelProperties; // (not currently used by Assimp) + struct IfcWindowStandardCase; + struct IfcWindowStyle; + struct IfcWindowType; + struct IfcWorkCalendar; + struct IfcWorkControl; + struct IfcWorkPlan; + struct IfcWorkSchedule; + typedef NotImplemented IfcWorkTime; // (not currently used by Assimp) + struct IfcZShapeProfileDef; + struct IfcZone; + + + + // C++ wrapper for IfcRoot + struct IfcRoot : ObjectHelper<IfcRoot,4> { IfcRoot() : Object("IfcRoot") {} + IfcGloballyUniqueId::Out GlobalId; + Maybe< Lazy< NotImplemented > > OwnerHistory; + Maybe< IfcLabel::Out > Name; + Maybe< IfcText::Out > Description; + }; + + // C++ wrapper for IfcObjectDefinition + struct IfcObjectDefinition : IfcRoot, ObjectHelper<IfcObjectDefinition,0> { IfcObjectDefinition() : Object("IfcObjectDefinition") {} + + }; + + // C++ wrapper for IfcObject + struct IfcObject : IfcObjectDefinition, ObjectHelper<IfcObject,1> { IfcObject() : Object("IfcObject") {} + Maybe< IfcLabel::Out > ObjectType; + }; + + // C++ wrapper for IfcControl + struct IfcControl : IfcObject, ObjectHelper<IfcControl,1> { IfcControl() : Object("IfcControl") {} + Maybe< IfcIdentifier::Out > Identification; + }; + + // C++ wrapper for IfcActionRequest + struct IfcActionRequest : IfcControl, ObjectHelper<IfcActionRequest,3> { IfcActionRequest() : Object("IfcActionRequest") {} + Maybe< IfcActionRequestTypeEnum::Out > PredefinedType; + Maybe< IfcLabel::Out > Status; + Maybe< IfcText::Out > LongDescription; + }; + + // C++ wrapper for IfcActor + struct IfcActor : IfcObject, ObjectHelper<IfcActor,1> { IfcActor() : Object("IfcActor") {} + IfcActorSelect::Out TheActor; + }; + + // C++ wrapper for IfcProduct + struct IfcProduct : IfcObject, ObjectHelper<IfcProduct,2> { IfcProduct() : Object("IfcProduct") {} + Maybe< Lazy< IfcObjectPlacement > > ObjectPlacement; + Maybe< Lazy< IfcProductRepresentation > > Representation; + }; + + // C++ wrapper for IfcElement + struct IfcElement : IfcProduct, ObjectHelper<IfcElement,1> { IfcElement() : Object("IfcElement") {} + Maybe< IfcIdentifier::Out > Tag; + }; + + // C++ wrapper for IfcDistributionElement + struct IfcDistributionElement : IfcElement, ObjectHelper<IfcDistributionElement,0> { IfcDistributionElement() : Object("IfcDistributionElement") {} + + }; + + // C++ wrapper for IfcDistributionControlElement + struct IfcDistributionControlElement : IfcDistributionElement, ObjectHelper<IfcDistributionControlElement,0> { IfcDistributionControlElement() : Object("IfcDistributionControlElement") {} + + }; + + // C++ wrapper for IfcActuator + struct IfcActuator : IfcDistributionControlElement, ObjectHelper<IfcActuator,1> { IfcActuator() : Object("IfcActuator") {} + Maybe< IfcActuatorTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcTypeObject + struct IfcTypeObject : IfcObjectDefinition, ObjectHelper<IfcTypeObject,2> { IfcTypeObject() : Object("IfcTypeObject") {} + Maybe< IfcIdentifier::Out > ApplicableOccurrence; + Maybe< ListOf< Lazy< IfcPropertySetDefinition >, 1, 0 > > HasPropertySets; + }; + + // C++ wrapper for IfcTypeProduct + struct IfcTypeProduct : IfcTypeObject, ObjectHelper<IfcTypeProduct,2> { IfcTypeProduct() : Object("IfcTypeProduct") {} + Maybe< ListOf< Lazy< IfcRepresentationMap >, 1, 0 > > RepresentationMaps; + Maybe< IfcLabel::Out > Tag; + }; + + // C++ wrapper for IfcElementType + struct IfcElementType : IfcTypeProduct, ObjectHelper<IfcElementType,1> { IfcElementType() : Object("IfcElementType") {} + Maybe< IfcLabel::Out > ElementType; + }; + + // C++ wrapper for IfcDistributionElementType + struct IfcDistributionElementType : IfcElementType, ObjectHelper<IfcDistributionElementType,0> { IfcDistributionElementType() : Object("IfcDistributionElementType") {} + + }; + + // C++ wrapper for IfcDistributionControlElementType + struct IfcDistributionControlElementType : IfcDistributionElementType, ObjectHelper<IfcDistributionControlElementType,0> { IfcDistributionControlElementType() : Object("IfcDistributionControlElementType") {} + + }; + + // C++ wrapper for IfcActuatorType + struct IfcActuatorType : IfcDistributionControlElementType, ObjectHelper<IfcActuatorType,1> { IfcActuatorType() : Object("IfcActuatorType") {} + IfcActuatorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcRepresentationItem + struct IfcRepresentationItem : ObjectHelper<IfcRepresentationItem,0> { IfcRepresentationItem() : Object("IfcRepresentationItem") {} + + }; + + // C++ wrapper for IfcGeometricRepresentationItem + struct IfcGeometricRepresentationItem : IfcRepresentationItem, ObjectHelper<IfcGeometricRepresentationItem,0> { IfcGeometricRepresentationItem() : Object("IfcGeometricRepresentationItem") {} + + }; + + // C++ wrapper for IfcSolidModel + struct IfcSolidModel : IfcGeometricRepresentationItem, ObjectHelper<IfcSolidModel,0> { IfcSolidModel() : Object("IfcSolidModel") {} + + }; + + // C++ wrapper for IfcManifoldSolidBrep + struct IfcManifoldSolidBrep : IfcSolidModel, ObjectHelper<IfcManifoldSolidBrep,1> { IfcManifoldSolidBrep() : Object("IfcManifoldSolidBrep") {} + Lazy< IfcClosedShell > Outer; + }; + + // C++ wrapper for IfcAdvancedBrep + struct IfcAdvancedBrep : IfcManifoldSolidBrep, ObjectHelper<IfcAdvancedBrep,0> { IfcAdvancedBrep() : Object("IfcAdvancedBrep") {} + + }; + + // C++ wrapper for IfcAdvancedBrepWithVoids + struct IfcAdvancedBrepWithVoids : IfcAdvancedBrep, ObjectHelper<IfcAdvancedBrepWithVoids,1> { IfcAdvancedBrepWithVoids() : Object("IfcAdvancedBrepWithVoids") {} + ListOf< Lazy< IfcClosedShell >, 1, 0 > Voids; + }; + + // C++ wrapper for IfcTopologicalRepresentationItem + struct IfcTopologicalRepresentationItem : IfcRepresentationItem, ObjectHelper<IfcTopologicalRepresentationItem,0> { IfcTopologicalRepresentationItem() : Object("IfcTopologicalRepresentationItem") {} + + }; + + // C++ wrapper for IfcFace + struct IfcFace : IfcTopologicalRepresentationItem, ObjectHelper<IfcFace,1> { IfcFace() : Object("IfcFace") {} + ListOf< Lazy< IfcFaceBound >, 1, 0 > Bounds; + }; + + // C++ wrapper for IfcFaceSurface + struct IfcFaceSurface : IfcFace, ObjectHelper<IfcFaceSurface,2> { IfcFaceSurface() : Object("IfcFaceSurface") {} + Lazy< IfcSurface > FaceSurface; + IfcBoolean::Out SameSense; + }; + + // C++ wrapper for IfcAdvancedFace + struct IfcAdvancedFace : IfcFaceSurface, ObjectHelper<IfcAdvancedFace,0> { IfcAdvancedFace() : Object("IfcAdvancedFace") {} + + }; + + // C++ wrapper for IfcDistributionFlowElement + struct IfcDistributionFlowElement : IfcDistributionElement, ObjectHelper<IfcDistributionFlowElement,0> { IfcDistributionFlowElement() : Object("IfcDistributionFlowElement") {} + + }; + + // C++ wrapper for IfcFlowTerminal + struct IfcFlowTerminal : IfcDistributionFlowElement, ObjectHelper<IfcFlowTerminal,0> { IfcFlowTerminal() : Object("IfcFlowTerminal") {} + + }; + + // C++ wrapper for IfcAirTerminal + struct IfcAirTerminal : IfcFlowTerminal, ObjectHelper<IfcAirTerminal,1> { IfcAirTerminal() : Object("IfcAirTerminal") {} + Maybe< IfcAirTerminalTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFlowController + struct IfcFlowController : IfcDistributionFlowElement, ObjectHelper<IfcFlowController,0> { IfcFlowController() : Object("IfcFlowController") {} + + }; + + // C++ wrapper for IfcAirTerminalBox + struct IfcAirTerminalBox : IfcFlowController, ObjectHelper<IfcAirTerminalBox,1> { IfcAirTerminalBox() : Object("IfcAirTerminalBox") {} + Maybe< IfcAirTerminalBoxTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcDistributionFlowElementType + struct IfcDistributionFlowElementType : IfcDistributionElementType, ObjectHelper<IfcDistributionFlowElementType,0> { IfcDistributionFlowElementType() : Object("IfcDistributionFlowElementType") {} + + }; + + // C++ wrapper for IfcFlowControllerType + struct IfcFlowControllerType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowControllerType,0> { IfcFlowControllerType() : Object("IfcFlowControllerType") {} + + }; + + // C++ wrapper for IfcAirTerminalBoxType + struct IfcAirTerminalBoxType : IfcFlowControllerType, ObjectHelper<IfcAirTerminalBoxType,1> { IfcAirTerminalBoxType() : Object("IfcAirTerminalBoxType") {} + IfcAirTerminalBoxTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFlowTerminalType + struct IfcFlowTerminalType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowTerminalType,0> { IfcFlowTerminalType() : Object("IfcFlowTerminalType") {} + + }; + + // C++ wrapper for IfcAirTerminalType + struct IfcAirTerminalType : IfcFlowTerminalType, ObjectHelper<IfcAirTerminalType,1> { IfcAirTerminalType() : Object("IfcAirTerminalType") {} + IfcAirTerminalTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcEnergyConversionDevice + struct IfcEnergyConversionDevice : IfcDistributionFlowElement, ObjectHelper<IfcEnergyConversionDevice,0> { IfcEnergyConversionDevice() : Object("IfcEnergyConversionDevice") {} + + }; + + // C++ wrapper for IfcAirToAirHeatRecovery + struct IfcAirToAirHeatRecovery : IfcEnergyConversionDevice, ObjectHelper<IfcAirToAirHeatRecovery,1> { IfcAirToAirHeatRecovery() : Object("IfcAirToAirHeatRecovery") {} + Maybe< IfcAirToAirHeatRecoveryTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcEnergyConversionDeviceType + struct IfcEnergyConversionDeviceType : IfcDistributionFlowElementType, ObjectHelper<IfcEnergyConversionDeviceType,0> { IfcEnergyConversionDeviceType() : Object("IfcEnergyConversionDeviceType") {} + + }; + + // C++ wrapper for IfcAirToAirHeatRecoveryType + struct IfcAirToAirHeatRecoveryType : IfcEnergyConversionDeviceType, ObjectHelper<IfcAirToAirHeatRecoveryType,1> { IfcAirToAirHeatRecoveryType() : Object("IfcAirToAirHeatRecoveryType") {} + IfcAirToAirHeatRecoveryTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcAlarm + struct IfcAlarm : IfcDistributionControlElement, ObjectHelper<IfcAlarm,1> { IfcAlarm() : Object("IfcAlarm") {} + Maybe< IfcAlarmTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcAlarmType + struct IfcAlarmType : IfcDistributionControlElementType, ObjectHelper<IfcAlarmType,1> { IfcAlarmType() : Object("IfcAlarmType") {} + IfcAlarmTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcAnnotation + struct IfcAnnotation : IfcProduct, ObjectHelper<IfcAnnotation,0> { IfcAnnotation() : Object("IfcAnnotation") {} + + }; + + // C++ wrapper for IfcAnnotationFillArea + struct IfcAnnotationFillArea : IfcGeometricRepresentationItem, ObjectHelper<IfcAnnotationFillArea,2> { IfcAnnotationFillArea() : Object("IfcAnnotationFillArea") {} + Lazy< IfcCurve > OuterBoundary; + Maybe< ListOf< Lazy< IfcCurve >, 1, 0 > > InnerBoundaries; + }; + + // C++ wrapper for IfcProfileDef + struct IfcProfileDef : ObjectHelper<IfcProfileDef,2> { IfcProfileDef() : Object("IfcProfileDef") {} + IfcProfileTypeEnum::Out ProfileType; + Maybe< IfcLabel::Out > ProfileName; + }; + + // C++ wrapper for IfcArbitraryClosedProfileDef + struct IfcArbitraryClosedProfileDef : IfcProfileDef, ObjectHelper<IfcArbitraryClosedProfileDef,1> { IfcArbitraryClosedProfileDef() : Object("IfcArbitraryClosedProfileDef") {} + Lazy< IfcCurve > OuterCurve; + }; + + // C++ wrapper for IfcArbitraryOpenProfileDef + struct IfcArbitraryOpenProfileDef : IfcProfileDef, ObjectHelper<IfcArbitraryOpenProfileDef,1> { IfcArbitraryOpenProfileDef() : Object("IfcArbitraryOpenProfileDef") {} + Lazy< IfcBoundedCurve > Curve; + }; + + // C++ wrapper for IfcArbitraryProfileDefWithVoids + struct IfcArbitraryProfileDefWithVoids : IfcArbitraryClosedProfileDef, ObjectHelper<IfcArbitraryProfileDefWithVoids,1> { IfcArbitraryProfileDefWithVoids() : Object("IfcArbitraryProfileDefWithVoids") {} + ListOf< Lazy< IfcCurve >, 1, 0 > InnerCurves; + }; + + // C++ wrapper for IfcGroup + struct IfcGroup : IfcObject, ObjectHelper<IfcGroup,0> { IfcGroup() : Object("IfcGroup") {} + + }; + + // C++ wrapper for IfcAsset + struct IfcAsset : IfcGroup, ObjectHelper<IfcAsset,9> { IfcAsset() : Object("IfcAsset") {} + Maybe< IfcIdentifier::Out > Identification; + Maybe< Lazy< NotImplemented > > OriginalValue; + Maybe< Lazy< NotImplemented > > CurrentValue; + Maybe< Lazy< NotImplemented > > TotalReplacementCost; + Maybe< IfcActorSelect::Out > Owner; + Maybe< IfcActorSelect::Out > User; + Maybe< Lazy< NotImplemented > > ResponsiblePerson; + Maybe< IfcDate::Out > IncorporationDate; + Maybe< Lazy< NotImplemented > > DepreciatedValue; + }; + + // C++ wrapper for IfcParameterizedProfileDef + struct IfcParameterizedProfileDef : IfcProfileDef, ObjectHelper<IfcParameterizedProfileDef,1> { IfcParameterizedProfileDef() : Object("IfcParameterizedProfileDef") {} + Maybe< Lazy< IfcAxis2Placement2D > > Position; + }; + + // C++ wrapper for IfcAsymmetricIShapeProfileDef + struct IfcAsymmetricIShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcAsymmetricIShapeProfileDef,12> { IfcAsymmetricIShapeProfileDef() : Object("IfcAsymmetricIShapeProfileDef") {} + IfcPositiveLengthMeasure::Out BottomFlangeWidth; + IfcPositiveLengthMeasure::Out OverallDepth; + IfcPositiveLengthMeasure::Out WebThickness; + IfcPositiveLengthMeasure::Out BottomFlangeThickness; + Maybe< IfcNonNegativeLengthMeasure::Out > BottomFlangeFilletRadius; + IfcPositiveLengthMeasure::Out TopFlangeWidth; + Maybe< IfcPositiveLengthMeasure::Out > TopFlangeThickness; + Maybe< IfcNonNegativeLengthMeasure::Out > TopFlangeFilletRadius; + Maybe< IfcNonNegativeLengthMeasure::Out > BottomFlangeEdgeRadius; + Maybe< IfcPlaneAngleMeasure::Out > BottomFlangeSlope; + Maybe< IfcNonNegativeLengthMeasure::Out > TopFlangeEdgeRadius; + Maybe< IfcPlaneAngleMeasure::Out > TopFlangeSlope; + }; + + // C++ wrapper for IfcAudioVisualAppliance + struct IfcAudioVisualAppliance : IfcFlowTerminal, ObjectHelper<IfcAudioVisualAppliance,1> { IfcAudioVisualAppliance() : Object("IfcAudioVisualAppliance") {} + Maybe< IfcAudioVisualApplianceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcAudioVisualApplianceType + struct IfcAudioVisualApplianceType : IfcFlowTerminalType, ObjectHelper<IfcAudioVisualApplianceType,1> { IfcAudioVisualApplianceType() : Object("IfcAudioVisualApplianceType") {} + IfcAudioVisualApplianceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPlacement + struct IfcPlacement : IfcGeometricRepresentationItem, ObjectHelper<IfcPlacement,1> { IfcPlacement() : Object("IfcPlacement") {} + Lazy< IfcCartesianPoint > Location; + }; + + // C++ wrapper for IfcAxis1Placement + struct IfcAxis1Placement : IfcPlacement, ObjectHelper<IfcAxis1Placement,1> { IfcAxis1Placement() : Object("IfcAxis1Placement") {} + Maybe< Lazy< IfcDirection > > Axis; + }; + + // C++ wrapper for IfcAxis2Placement2D + struct IfcAxis2Placement2D : IfcPlacement, ObjectHelper<IfcAxis2Placement2D,1> { IfcAxis2Placement2D() : Object("IfcAxis2Placement2D") {} + Maybe< Lazy< IfcDirection > > RefDirection; + }; + + // C++ wrapper for IfcAxis2Placement3D + struct IfcAxis2Placement3D : IfcPlacement, ObjectHelper<IfcAxis2Placement3D,2> { IfcAxis2Placement3D() : Object("IfcAxis2Placement3D") {} + Maybe< Lazy< IfcDirection > > Axis; + Maybe< Lazy< IfcDirection > > RefDirection; + }; + + // C++ wrapper for IfcCurve + struct IfcCurve : IfcGeometricRepresentationItem, ObjectHelper<IfcCurve,0> { IfcCurve() : Object("IfcCurve") {} + + }; + + // C++ wrapper for IfcBoundedCurve + struct IfcBoundedCurve : IfcCurve, ObjectHelper<IfcBoundedCurve,0> { IfcBoundedCurve() : Object("IfcBoundedCurve") {} + + }; + + // C++ wrapper for IfcBSplineCurve + struct IfcBSplineCurve : IfcBoundedCurve, ObjectHelper<IfcBSplineCurve,5> { IfcBSplineCurve() : Object("IfcBSplineCurve") {} + IfcInteger::Out Degree; + ListOf< Lazy< IfcCartesianPoint >, 2, 0 > ControlPointsList; + IfcBSplineCurveForm::Out CurveForm; + IfcLogical::Out ClosedCurve; + IfcLogical::Out SelfIntersect; + }; + + // C++ wrapper for IfcBSplineCurveWithKnots + struct IfcBSplineCurveWithKnots : IfcBSplineCurve, ObjectHelper<IfcBSplineCurveWithKnots,3> { IfcBSplineCurveWithKnots() : Object("IfcBSplineCurveWithKnots") {} + ListOf< IfcInteger, 2, 0 >::Out KnotMultiplicities; + ListOf< IfcParameterValue, 2, 0 >::Out Knots; + IfcKnotType::Out KnotSpec; + }; + + // C++ wrapper for IfcSurface + struct IfcSurface : IfcGeometricRepresentationItem, ObjectHelper<IfcSurface,0> { IfcSurface() : Object("IfcSurface") {} + + }; + + // C++ wrapper for IfcBoundedSurface + struct IfcBoundedSurface : IfcSurface, ObjectHelper<IfcBoundedSurface,0> { IfcBoundedSurface() : Object("IfcBoundedSurface") {} + + }; + + // C++ wrapper for IfcBSplineSurface + struct IfcBSplineSurface : IfcBoundedSurface, ObjectHelper<IfcBSplineSurface,6> { IfcBSplineSurface() : Object("IfcBSplineSurface") {} + IfcInteger::Out UDegree; + IfcInteger::Out VDegree; + IfcBSplineSurfaceForm::Out SurfaceForm; + IfcLogical::Out UClosed; + IfcLogical::Out VClosed; + IfcLogical::Out SelfIntersect; + }; + + // C++ wrapper for IfcBSplineSurfaceWithKnots + struct IfcBSplineSurfaceWithKnots : IfcBSplineSurface, ObjectHelper<IfcBSplineSurfaceWithKnots,5> { IfcBSplineSurfaceWithKnots() : Object("IfcBSplineSurfaceWithKnots") {} + ListOf< IfcInteger, 2, 0 >::Out UMultiplicities; + ListOf< IfcInteger, 2, 0 >::Out VMultiplicities; + ListOf< IfcParameterValue, 2, 0 >::Out UKnots; + ListOf< IfcParameterValue, 2, 0 >::Out VKnots; + IfcKnotType::Out KnotSpec; + }; + + // C++ wrapper for IfcBuildingElement + struct IfcBuildingElement : IfcElement, ObjectHelper<IfcBuildingElement,0> { IfcBuildingElement() : Object("IfcBuildingElement") {} + + }; + + // C++ wrapper for IfcBeam + struct IfcBeam : IfcBuildingElement, ObjectHelper<IfcBeam,1> { IfcBeam() : Object("IfcBeam") {} + Maybe< IfcBeamTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcBeamStandardCase + struct IfcBeamStandardCase : IfcBeam, ObjectHelper<IfcBeamStandardCase,0> { IfcBeamStandardCase() : Object("IfcBeamStandardCase") {} + + }; + + // C++ wrapper for IfcBuildingElementType + struct IfcBuildingElementType : IfcElementType, ObjectHelper<IfcBuildingElementType,0> { IfcBuildingElementType() : Object("IfcBuildingElementType") {} + + }; + + // C++ wrapper for IfcBeamType + struct IfcBeamType : IfcBuildingElementType, ObjectHelper<IfcBeamType,1> { IfcBeamType() : Object("IfcBeamType") {} + IfcBeamTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPresentationItem + struct IfcPresentationItem : ObjectHelper<IfcPresentationItem,0> { IfcPresentationItem() : Object("IfcPresentationItem") {} + + }; + + // C++ wrapper for IfcCsgPrimitive3D + struct IfcCsgPrimitive3D : IfcGeometricRepresentationItem, ObjectHelper<IfcCsgPrimitive3D,1> { IfcCsgPrimitive3D() : Object("IfcCsgPrimitive3D") {} + Lazy< IfcAxis2Placement3D > Position; + }; + + // C++ wrapper for IfcBlock + struct IfcBlock : IfcCsgPrimitive3D, ObjectHelper<IfcBlock,3> { IfcBlock() : Object("IfcBlock") {} + IfcPositiveLengthMeasure::Out XLength; + IfcPositiveLengthMeasure::Out YLength; + IfcPositiveLengthMeasure::Out ZLength; + }; + + // C++ wrapper for IfcBoiler + struct IfcBoiler : IfcEnergyConversionDevice, ObjectHelper<IfcBoiler,1> { IfcBoiler() : Object("IfcBoiler") {} + Maybe< IfcBoilerTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcBoilerType + struct IfcBoilerType : IfcEnergyConversionDeviceType, ObjectHelper<IfcBoilerType,1> { IfcBoilerType() : Object("IfcBoilerType") {} + IfcBoilerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcBooleanResult + struct IfcBooleanResult : IfcGeometricRepresentationItem, ObjectHelper<IfcBooleanResult,3> { IfcBooleanResult() : Object("IfcBooleanResult") {} + IfcBooleanOperator::Out Operator; + IfcBooleanOperand::Out FirstOperand; + IfcBooleanOperand::Out SecondOperand; + }; + + // C++ wrapper for IfcBooleanClippingResult + struct IfcBooleanClippingResult : IfcBooleanResult, ObjectHelper<IfcBooleanClippingResult,0> { IfcBooleanClippingResult() : Object("IfcBooleanClippingResult") {} + + }; + + // C++ wrapper for IfcCompositeCurve + struct IfcCompositeCurve : IfcBoundedCurve, ObjectHelper<IfcCompositeCurve,2> { IfcCompositeCurve() : Object("IfcCompositeCurve") {} + ListOf< Lazy< IfcCompositeCurveSegment >, 1, 0 > Segments; + IfcLogical::Out SelfIntersect; + }; + + // C++ wrapper for IfcCompositeCurveOnSurface + struct IfcCompositeCurveOnSurface : IfcCompositeCurve, ObjectHelper<IfcCompositeCurveOnSurface,0> { IfcCompositeCurveOnSurface() : Object("IfcCompositeCurveOnSurface") {} + + }; + + // C++ wrapper for IfcBoundaryCurve + struct IfcBoundaryCurve : IfcCompositeCurveOnSurface, ObjectHelper<IfcBoundaryCurve,0> { IfcBoundaryCurve() : Object("IfcBoundaryCurve") {} + + }; + + // C++ wrapper for IfcBoundingBox + struct IfcBoundingBox : IfcGeometricRepresentationItem, ObjectHelper<IfcBoundingBox,4> { IfcBoundingBox() : Object("IfcBoundingBox") {} + Lazy< IfcCartesianPoint > Corner; + IfcPositiveLengthMeasure::Out XDim; + IfcPositiveLengthMeasure::Out YDim; + IfcPositiveLengthMeasure::Out ZDim; + }; + + // C++ wrapper for IfcHalfSpaceSolid + struct IfcHalfSpaceSolid : IfcGeometricRepresentationItem, ObjectHelper<IfcHalfSpaceSolid,2> { IfcHalfSpaceSolid() : Object("IfcHalfSpaceSolid") {} + Lazy< IfcSurface > BaseSurface; + IfcBoolean::Out AgreementFlag; + }; + + // C++ wrapper for IfcBoxedHalfSpace + struct IfcBoxedHalfSpace : IfcHalfSpaceSolid, ObjectHelper<IfcBoxedHalfSpace,1> { IfcBoxedHalfSpace() : Object("IfcBoxedHalfSpace") {} + Lazy< IfcBoundingBox > Enclosure; + }; + + // C++ wrapper for IfcSpatialElement + struct IfcSpatialElement : IfcProduct, ObjectHelper<IfcSpatialElement,1> { IfcSpatialElement() : Object("IfcSpatialElement") {} + Maybe< IfcLabel::Out > LongName; + }; + + // C++ wrapper for IfcSpatialStructureElement + struct IfcSpatialStructureElement : IfcSpatialElement, ObjectHelper<IfcSpatialStructureElement,1> { IfcSpatialStructureElement() : Object("IfcSpatialStructureElement") {} + Maybe< IfcElementCompositionEnum::Out > CompositionType; + }; + + // C++ wrapper for IfcBuilding + struct IfcBuilding : IfcSpatialStructureElement, ObjectHelper<IfcBuilding,3> { IfcBuilding() : Object("IfcBuilding") {} + Maybe< IfcLengthMeasure::Out > ElevationOfRefHeight; + Maybe< IfcLengthMeasure::Out > ElevationOfTerrain; + Maybe< Lazy< NotImplemented > > BuildingAddress; + }; + + // C++ wrapper for IfcElementComponent + struct IfcElementComponent : IfcElement, ObjectHelper<IfcElementComponent,0> { IfcElementComponent() : Object("IfcElementComponent") {} + + }; + + // C++ wrapper for IfcBuildingElementPart + struct IfcBuildingElementPart : IfcElementComponent, ObjectHelper<IfcBuildingElementPart,1> { IfcBuildingElementPart() : Object("IfcBuildingElementPart") {} + Maybe< IfcBuildingElementPartTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcElementComponentType + struct IfcElementComponentType : IfcElementType, ObjectHelper<IfcElementComponentType,0> { IfcElementComponentType() : Object("IfcElementComponentType") {} + + }; + + // C++ wrapper for IfcBuildingElementPartType + struct IfcBuildingElementPartType : IfcElementComponentType, ObjectHelper<IfcBuildingElementPartType,1> { IfcBuildingElementPartType() : Object("IfcBuildingElementPartType") {} + IfcBuildingElementPartTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcBuildingElementProxy + struct IfcBuildingElementProxy : IfcBuildingElement, ObjectHelper<IfcBuildingElementProxy,1> { IfcBuildingElementProxy() : Object("IfcBuildingElementProxy") {} + Maybe< IfcBuildingElementProxyTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcBuildingElementProxyType + struct IfcBuildingElementProxyType : IfcBuildingElementType, ObjectHelper<IfcBuildingElementProxyType,1> { IfcBuildingElementProxyType() : Object("IfcBuildingElementProxyType") {} + IfcBuildingElementProxyTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcBuildingStorey + struct IfcBuildingStorey : IfcSpatialStructureElement, ObjectHelper<IfcBuildingStorey,1> { IfcBuildingStorey() : Object("IfcBuildingStorey") {} + Maybe< IfcLengthMeasure::Out > Elevation; + }; + + // C++ wrapper for IfcSystem + struct IfcSystem : IfcGroup, ObjectHelper<IfcSystem,0> { IfcSystem() : Object("IfcSystem") {} + + }; + + // C++ wrapper for IfcBuildingSystem + struct IfcBuildingSystem : IfcSystem, ObjectHelper<IfcBuildingSystem,2> { IfcBuildingSystem() : Object("IfcBuildingSystem") {} + Maybe< IfcBuildingSystemTypeEnum::Out > PredefinedType; + Maybe< IfcLabel::Out > LongName; + }; + + // C++ wrapper for IfcBurner + struct IfcBurner : IfcEnergyConversionDevice, ObjectHelper<IfcBurner,1> { IfcBurner() : Object("IfcBurner") {} + Maybe< IfcBurnerTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcBurnerType + struct IfcBurnerType : IfcEnergyConversionDeviceType, ObjectHelper<IfcBurnerType,1> { IfcBurnerType() : Object("IfcBurnerType") {} + IfcBurnerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCShapeProfileDef + struct IfcCShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcCShapeProfileDef,5> { IfcCShapeProfileDef() : Object("IfcCShapeProfileDef") {} + IfcPositiveLengthMeasure::Out Depth; + IfcPositiveLengthMeasure::Out Width; + IfcPositiveLengthMeasure::Out WallThickness; + IfcPositiveLengthMeasure::Out Girth; + Maybe< IfcNonNegativeLengthMeasure::Out > InternalFilletRadius; + }; + + // C++ wrapper for IfcFlowFitting + struct IfcFlowFitting : IfcDistributionFlowElement, ObjectHelper<IfcFlowFitting,0> { IfcFlowFitting() : Object("IfcFlowFitting") {} + + }; + + // C++ wrapper for IfcCableCarrierFitting + struct IfcCableCarrierFitting : IfcFlowFitting, ObjectHelper<IfcCableCarrierFitting,1> { IfcCableCarrierFitting() : Object("IfcCableCarrierFitting") {} + Maybe< IfcCableCarrierFittingTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFlowFittingType + struct IfcFlowFittingType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowFittingType,0> { IfcFlowFittingType() : Object("IfcFlowFittingType") {} + + }; + + // C++ wrapper for IfcCableCarrierFittingType + struct IfcCableCarrierFittingType : IfcFlowFittingType, ObjectHelper<IfcCableCarrierFittingType,1> { IfcCableCarrierFittingType() : Object("IfcCableCarrierFittingType") {} + IfcCableCarrierFittingTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFlowSegment + struct IfcFlowSegment : IfcDistributionFlowElement, ObjectHelper<IfcFlowSegment,0> { IfcFlowSegment() : Object("IfcFlowSegment") {} + + }; + + // C++ wrapper for IfcCableCarrierSegment + struct IfcCableCarrierSegment : IfcFlowSegment, ObjectHelper<IfcCableCarrierSegment,1> { IfcCableCarrierSegment() : Object("IfcCableCarrierSegment") {} + Maybe< IfcCableCarrierSegmentTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFlowSegmentType + struct IfcFlowSegmentType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowSegmentType,0> { IfcFlowSegmentType() : Object("IfcFlowSegmentType") {} + + }; + + // C++ wrapper for IfcCableCarrierSegmentType + struct IfcCableCarrierSegmentType : IfcFlowSegmentType, ObjectHelper<IfcCableCarrierSegmentType,1> { IfcCableCarrierSegmentType() : Object("IfcCableCarrierSegmentType") {} + IfcCableCarrierSegmentTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCableFitting + struct IfcCableFitting : IfcFlowFitting, ObjectHelper<IfcCableFitting,1> { IfcCableFitting() : Object("IfcCableFitting") {} + Maybe< IfcCableFittingTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcCableFittingType + struct IfcCableFittingType : IfcFlowFittingType, ObjectHelper<IfcCableFittingType,1> { IfcCableFittingType() : Object("IfcCableFittingType") {} + IfcCableFittingTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCableSegment + struct IfcCableSegment : IfcFlowSegment, ObjectHelper<IfcCableSegment,1> { IfcCableSegment() : Object("IfcCableSegment") {} + Maybe< IfcCableSegmentTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcCableSegmentType + struct IfcCableSegmentType : IfcFlowSegmentType, ObjectHelper<IfcCableSegmentType,1> { IfcCableSegmentType() : Object("IfcCableSegmentType") {} + IfcCableSegmentTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPoint + struct IfcPoint : IfcGeometricRepresentationItem, ObjectHelper<IfcPoint,0> { IfcPoint() : Object("IfcPoint") {} + + }; + + // C++ wrapper for IfcCartesianPoint + struct IfcCartesianPoint : IfcPoint, ObjectHelper<IfcCartesianPoint,1> { IfcCartesianPoint() : Object("IfcCartesianPoint") {} + ListOf< IfcLengthMeasure, 1, 3 >::Out Coordinates; + }; + + // C++ wrapper for IfcCartesianPointList + struct IfcCartesianPointList : IfcGeometricRepresentationItem, ObjectHelper<IfcCartesianPointList,0> { IfcCartesianPointList() : Object("IfcCartesianPointList") {} + + }; + + // C++ wrapper for IfcCartesianPointList2D + struct IfcCartesianPointList2D : IfcCartesianPointList, ObjectHelper<IfcCartesianPointList2D,0> { IfcCartesianPointList2D() : Object("IfcCartesianPointList2D") {} + + }; + + // C++ wrapper for IfcCartesianPointList3D + struct IfcCartesianPointList3D : IfcCartesianPointList, ObjectHelper<IfcCartesianPointList3D,0> { IfcCartesianPointList3D() : Object("IfcCartesianPointList3D") {} + + }; + + // C++ wrapper for IfcCartesianTransformationOperator + struct IfcCartesianTransformationOperator : IfcGeometricRepresentationItem, ObjectHelper<IfcCartesianTransformationOperator,4> { IfcCartesianTransformationOperator() : Object("IfcCartesianTransformationOperator") {} + Maybe< Lazy< IfcDirection > > Axis1; + Maybe< Lazy< IfcDirection > > Axis2; + Lazy< IfcCartesianPoint > LocalOrigin; + Maybe< IfcReal::Out > Scale; + }; + + // C++ wrapper for IfcCartesianTransformationOperator2D + struct IfcCartesianTransformationOperator2D : IfcCartesianTransformationOperator, ObjectHelper<IfcCartesianTransformationOperator2D,0> { IfcCartesianTransformationOperator2D() : Object("IfcCartesianTransformationOperator2D") {} + + }; + + // C++ wrapper for IfcCartesianTransformationOperator2DnonUniform + struct IfcCartesianTransformationOperator2DnonUniform : IfcCartesianTransformationOperator2D, ObjectHelper<IfcCartesianTransformationOperator2DnonUniform,1> { IfcCartesianTransformationOperator2DnonUniform() : Object("IfcCartesianTransformationOperator2DnonUniform") {} + Maybe< IfcReal::Out > Scale2; + }; + + // C++ wrapper for IfcCartesianTransformationOperator3D + struct IfcCartesianTransformationOperator3D : IfcCartesianTransformationOperator, ObjectHelper<IfcCartesianTransformationOperator3D,1> { IfcCartesianTransformationOperator3D() : Object("IfcCartesianTransformationOperator3D") {} + Maybe< Lazy< IfcDirection > > Axis3; + }; + + // C++ wrapper for IfcCartesianTransformationOperator3DnonUniform + struct IfcCartesianTransformationOperator3DnonUniform : IfcCartesianTransformationOperator3D, ObjectHelper<IfcCartesianTransformationOperator3DnonUniform,2> { IfcCartesianTransformationOperator3DnonUniform() : Object("IfcCartesianTransformationOperator3DnonUniform") {} + Maybe< IfcReal::Out > Scale2; + Maybe< IfcReal::Out > Scale3; + }; + + // C++ wrapper for IfcCenterLineProfileDef + struct IfcCenterLineProfileDef : IfcArbitraryOpenProfileDef, ObjectHelper<IfcCenterLineProfileDef,1> { IfcCenterLineProfileDef() : Object("IfcCenterLineProfileDef") {} + IfcPositiveLengthMeasure::Out Thickness; + }; + + // C++ wrapper for IfcChiller + struct IfcChiller : IfcEnergyConversionDevice, ObjectHelper<IfcChiller,1> { IfcChiller() : Object("IfcChiller") {} + Maybe< IfcChillerTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcChillerType + struct IfcChillerType : IfcEnergyConversionDeviceType, ObjectHelper<IfcChillerType,1> { IfcChillerType() : Object("IfcChillerType") {} + IfcChillerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcChimney + struct IfcChimney : IfcBuildingElement, ObjectHelper<IfcChimney,1> { IfcChimney() : Object("IfcChimney") {} + Maybe< IfcChimneyTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcChimneyType + struct IfcChimneyType : IfcBuildingElementType, ObjectHelper<IfcChimneyType,1> { IfcChimneyType() : Object("IfcChimneyType") {} + IfcChimneyTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcConic + struct IfcConic : IfcCurve, ObjectHelper<IfcConic,1> { IfcConic() : Object("IfcConic") {} + IfcAxis2Placement::Out Position; + }; + + // C++ wrapper for IfcCircle + struct IfcCircle : IfcConic, ObjectHelper<IfcCircle,1> { IfcCircle() : Object("IfcCircle") {} + IfcPositiveLengthMeasure::Out Radius; + }; + + // C++ wrapper for IfcCircleProfileDef + struct IfcCircleProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcCircleProfileDef,1> { IfcCircleProfileDef() : Object("IfcCircleProfileDef") {} + IfcPositiveLengthMeasure::Out Radius; + }; + + // C++ wrapper for IfcCircleHollowProfileDef + struct IfcCircleHollowProfileDef : IfcCircleProfileDef, ObjectHelper<IfcCircleHollowProfileDef,1> { IfcCircleHollowProfileDef() : Object("IfcCircleHollowProfileDef") {} + IfcPositiveLengthMeasure::Out WallThickness; + }; + + // C++ wrapper for IfcCivilElement + struct IfcCivilElement : IfcElement, ObjectHelper<IfcCivilElement,0> { IfcCivilElement() : Object("IfcCivilElement") {} + + }; + + // C++ wrapper for IfcCivilElementType + struct IfcCivilElementType : IfcElementType, ObjectHelper<IfcCivilElementType,0> { IfcCivilElementType() : Object("IfcCivilElementType") {} + + }; + + // C++ wrapper for IfcConnectedFaceSet + struct IfcConnectedFaceSet : IfcTopologicalRepresentationItem, ObjectHelper<IfcConnectedFaceSet,1> { IfcConnectedFaceSet() : Object("IfcConnectedFaceSet") {} + ListOf< Lazy< IfcFace >, 1, 0 > CfsFaces; + }; + + // C++ wrapper for IfcClosedShell + struct IfcClosedShell : IfcConnectedFaceSet, ObjectHelper<IfcClosedShell,0> { IfcClosedShell() : Object("IfcClosedShell") {} + + }; + + // C++ wrapper for IfcCoil + struct IfcCoil : IfcEnergyConversionDevice, ObjectHelper<IfcCoil,1> { IfcCoil() : Object("IfcCoil") {} + Maybe< IfcCoilTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcCoilType + struct IfcCoilType : IfcEnergyConversionDeviceType, ObjectHelper<IfcCoilType,1> { IfcCoilType() : Object("IfcCoilType") {} + IfcCoilTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcColourSpecification + struct IfcColourSpecification : IfcPresentationItem, ObjectHelper<IfcColourSpecification,1> { IfcColourSpecification() : Object("IfcColourSpecification") {} + Maybe< IfcLabel::Out > Name; + }; + + // C++ wrapper for IfcColourRgb + struct IfcColourRgb : IfcColourSpecification, ObjectHelper<IfcColourRgb,3> { IfcColourRgb() : Object("IfcColourRgb") {} + IfcNormalisedRatioMeasure::Out Red; + IfcNormalisedRatioMeasure::Out Green; + IfcNormalisedRatioMeasure::Out Blue; + }; + + // C++ wrapper for IfcColumn + struct IfcColumn : IfcBuildingElement, ObjectHelper<IfcColumn,1> { IfcColumn() : Object("IfcColumn") {} + Maybe< IfcColumnTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcColumnStandardCase + struct IfcColumnStandardCase : IfcColumn, ObjectHelper<IfcColumnStandardCase,0> { IfcColumnStandardCase() : Object("IfcColumnStandardCase") {} + + }; + + // C++ wrapper for IfcColumnType + struct IfcColumnType : IfcBuildingElementType, ObjectHelper<IfcColumnType,1> { IfcColumnType() : Object("IfcColumnType") {} + IfcColumnTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCommunicationsAppliance + struct IfcCommunicationsAppliance : IfcFlowTerminal, ObjectHelper<IfcCommunicationsAppliance,1> { IfcCommunicationsAppliance() : Object("IfcCommunicationsAppliance") {} + Maybe< IfcCommunicationsApplianceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcCommunicationsApplianceType + struct IfcCommunicationsApplianceType : IfcFlowTerminalType, ObjectHelper<IfcCommunicationsApplianceType,1> { IfcCommunicationsApplianceType() : Object("IfcCommunicationsApplianceType") {} + IfcCommunicationsApplianceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPropertyAbstraction + struct IfcPropertyAbstraction : ObjectHelper<IfcPropertyAbstraction,0> { IfcPropertyAbstraction() : Object("IfcPropertyAbstraction") {} + + }; + + // C++ wrapper for IfcProperty + struct IfcProperty : IfcPropertyAbstraction, ObjectHelper<IfcProperty,2> { IfcProperty() : Object("IfcProperty") {} + IfcIdentifier::Out Name; + Maybe< IfcText::Out > Description; + }; + + // C++ wrapper for IfcComplexProperty + struct IfcComplexProperty : IfcProperty, ObjectHelper<IfcComplexProperty,2> { IfcComplexProperty() : Object("IfcComplexProperty") {} + IfcIdentifier::Out UsageName; + ListOf< Lazy< IfcProperty >, 1, 0 > HasProperties; + }; + + // C++ wrapper for IfcPropertyDefinition + struct IfcPropertyDefinition : IfcRoot, ObjectHelper<IfcPropertyDefinition,0> { IfcPropertyDefinition() : Object("IfcPropertyDefinition") {} + + }; + + // C++ wrapper for IfcCompositeCurveSegment + struct IfcCompositeCurveSegment : IfcGeometricRepresentationItem, ObjectHelper<IfcCompositeCurveSegment,3> { IfcCompositeCurveSegment() : Object("IfcCompositeCurveSegment") {} + IfcTransitionCode::Out Transition; + IfcBoolean::Out SameSense; + Lazy< IfcCurve > ParentCurve; + }; + + // C++ wrapper for IfcCompositeProfileDef + struct IfcCompositeProfileDef : IfcProfileDef, ObjectHelper<IfcCompositeProfileDef,2> { IfcCompositeProfileDef() : Object("IfcCompositeProfileDef") {} + ListOf< Lazy< IfcProfileDef >, 2, 0 > Profiles; + Maybe< IfcLabel::Out > Label; + }; + + // C++ wrapper for IfcFlowMovingDevice + struct IfcFlowMovingDevice : IfcDistributionFlowElement, ObjectHelper<IfcFlowMovingDevice,0> { IfcFlowMovingDevice() : Object("IfcFlowMovingDevice") {} + + }; + + // C++ wrapper for IfcCompressor + struct IfcCompressor : IfcFlowMovingDevice, ObjectHelper<IfcCompressor,1> { IfcCompressor() : Object("IfcCompressor") {} + Maybe< IfcCompressorTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFlowMovingDeviceType + struct IfcFlowMovingDeviceType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowMovingDeviceType,0> { IfcFlowMovingDeviceType() : Object("IfcFlowMovingDeviceType") {} + + }; + + // C++ wrapper for IfcCompressorType + struct IfcCompressorType : IfcFlowMovingDeviceType, ObjectHelper<IfcCompressorType,1> { IfcCompressorType() : Object("IfcCompressorType") {} + IfcCompressorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCondenser + struct IfcCondenser : IfcEnergyConversionDevice, ObjectHelper<IfcCondenser,1> { IfcCondenser() : Object("IfcCondenser") {} + Maybe< IfcCondenserTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcCondenserType + struct IfcCondenserType : IfcEnergyConversionDeviceType, ObjectHelper<IfcCondenserType,1> { IfcCondenserType() : Object("IfcCondenserType") {} + IfcCondenserTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcResource + struct IfcResource : IfcObject, ObjectHelper<IfcResource,2> { IfcResource() : Object("IfcResource") {} + Maybe< IfcIdentifier::Out > Identification; + Maybe< IfcText::Out > LongDescription; + }; + + // C++ wrapper for IfcConstructionResource + struct IfcConstructionResource : IfcResource, ObjectHelper<IfcConstructionResource,3> { IfcConstructionResource() : Object("IfcConstructionResource") {} + Maybe< Lazy< NotImplemented > > Usage; + Maybe< ListOf< Lazy< NotImplemented >, 1, 0 > > BaseCosts; + Maybe< Lazy< NotImplemented > > BaseQuantity; + }; + + // C++ wrapper for IfcConstructionEquipmentResource + struct IfcConstructionEquipmentResource : IfcConstructionResource, ObjectHelper<IfcConstructionEquipmentResource,1> { IfcConstructionEquipmentResource() : Object("IfcConstructionEquipmentResource") {} + Maybe< IfcConstructionEquipmentResourceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcTypeResource + struct IfcTypeResource : IfcTypeObject, ObjectHelper<IfcTypeResource,3> { IfcTypeResource() : Object("IfcTypeResource") {} + Maybe< IfcIdentifier::Out > Identification; + Maybe< IfcText::Out > LongDescription; + Maybe< IfcLabel::Out > ResourceType; + }; + + // C++ wrapper for IfcConstructionResourceType + struct IfcConstructionResourceType : IfcTypeResource, ObjectHelper<IfcConstructionResourceType,2> { IfcConstructionResourceType() : Object("IfcConstructionResourceType") {} + Maybe< ListOf< Lazy< NotImplemented >, 1, 0 > > BaseCosts; + Maybe< Lazy< NotImplemented > > BaseQuantity; + }; + + // C++ wrapper for IfcConstructionEquipmentResourceType + struct IfcConstructionEquipmentResourceType : IfcConstructionResourceType, ObjectHelper<IfcConstructionEquipmentResourceType,1> { IfcConstructionEquipmentResourceType() : Object("IfcConstructionEquipmentResourceType") {} + IfcConstructionEquipmentResourceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcConstructionMaterialResource + struct IfcConstructionMaterialResource : IfcConstructionResource, ObjectHelper<IfcConstructionMaterialResource,1> { IfcConstructionMaterialResource() : Object("IfcConstructionMaterialResource") {} + Maybe< IfcConstructionMaterialResourceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcConstructionMaterialResourceType + struct IfcConstructionMaterialResourceType : IfcConstructionResourceType, ObjectHelper<IfcConstructionMaterialResourceType,1> { IfcConstructionMaterialResourceType() : Object("IfcConstructionMaterialResourceType") {} + IfcConstructionMaterialResourceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcConstructionProductResource + struct IfcConstructionProductResource : IfcConstructionResource, ObjectHelper<IfcConstructionProductResource,1> { IfcConstructionProductResource() : Object("IfcConstructionProductResource") {} + Maybe< IfcConstructionProductResourceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcConstructionProductResourceType + struct IfcConstructionProductResourceType : IfcConstructionResourceType, ObjectHelper<IfcConstructionProductResourceType,1> { IfcConstructionProductResourceType() : Object("IfcConstructionProductResourceType") {} + IfcConstructionProductResourceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcContext + struct IfcContext : IfcObjectDefinition, ObjectHelper<IfcContext,5> { IfcContext() : Object("IfcContext") {} + Maybe< IfcLabel::Out > ObjectType; + Maybe< IfcLabel::Out > LongName; + Maybe< IfcLabel::Out > Phase; + Maybe< ListOf< Lazy< IfcRepresentationContext >, 1, 0 > > RepresentationContexts; + Maybe< Lazy< IfcUnitAssignment > > UnitsInContext; + }; + + // C++ wrapper for IfcNamedUnit + struct IfcNamedUnit : ObjectHelper<IfcNamedUnit,2> { IfcNamedUnit() : Object("IfcNamedUnit") {} + Lazy< NotImplemented > Dimensions; + IfcUnitEnum::Out UnitType; + }; + + // C++ wrapper for IfcContextDependentUnit + struct IfcContextDependentUnit : IfcNamedUnit, ObjectHelper<IfcContextDependentUnit,1> { IfcContextDependentUnit() : Object("IfcContextDependentUnit") {} + IfcLabel::Out Name; + }; + + // C++ wrapper for IfcController + struct IfcController : IfcDistributionControlElement, ObjectHelper<IfcController,1> { IfcController() : Object("IfcController") {} + Maybe< IfcControllerTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcControllerType + struct IfcControllerType : IfcDistributionControlElementType, ObjectHelper<IfcControllerType,1> { IfcControllerType() : Object("IfcControllerType") {} + IfcControllerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcConversionBasedUnit + struct IfcConversionBasedUnit : IfcNamedUnit, ObjectHelper<IfcConversionBasedUnit,2> { IfcConversionBasedUnit() : Object("IfcConversionBasedUnit") {} + IfcLabel::Out Name; + Lazy< IfcMeasureWithUnit > ConversionFactor; + }; + + // C++ wrapper for IfcConversionBasedUnitWithOffset + struct IfcConversionBasedUnitWithOffset : IfcConversionBasedUnit, ObjectHelper<IfcConversionBasedUnitWithOffset,1> { IfcConversionBasedUnitWithOffset() : Object("IfcConversionBasedUnitWithOffset") {} + IfcReal::Out ConversionOffset; + }; + + // C++ wrapper for IfcCooledBeam + struct IfcCooledBeam : IfcEnergyConversionDevice, ObjectHelper<IfcCooledBeam,1> { IfcCooledBeam() : Object("IfcCooledBeam") {} + Maybe< IfcCooledBeamTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcCooledBeamType + struct IfcCooledBeamType : IfcEnergyConversionDeviceType, ObjectHelper<IfcCooledBeamType,1> { IfcCooledBeamType() : Object("IfcCooledBeamType") {} + IfcCooledBeamTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCoolingTower + struct IfcCoolingTower : IfcEnergyConversionDevice, ObjectHelper<IfcCoolingTower,1> { IfcCoolingTower() : Object("IfcCoolingTower") {} + Maybe< IfcCoolingTowerTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcCoolingTowerType + struct IfcCoolingTowerType : IfcEnergyConversionDeviceType, ObjectHelper<IfcCoolingTowerType,1> { IfcCoolingTowerType() : Object("IfcCoolingTowerType") {} + IfcCoolingTowerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCostItem + struct IfcCostItem : IfcControl, ObjectHelper<IfcCostItem,3> { IfcCostItem() : Object("IfcCostItem") {} + Maybe< IfcCostItemTypeEnum::Out > PredefinedType; + Maybe< ListOf< Lazy< NotImplemented >, 1, 0 > > CostValues; + Maybe< ListOf< Lazy< NotImplemented >, 1, 0 > > CostQuantities; + }; + + // C++ wrapper for IfcCostSchedule + struct IfcCostSchedule : IfcControl, ObjectHelper<IfcCostSchedule,4> { IfcCostSchedule() : Object("IfcCostSchedule") {} + Maybe< IfcCostScheduleTypeEnum::Out > PredefinedType; + Maybe< IfcLabel::Out > Status; + Maybe< IfcDateTime::Out > SubmittedOn; + Maybe< IfcDateTime::Out > UpdateDate; + }; + + // C++ wrapper for IfcCovering + struct IfcCovering : IfcBuildingElement, ObjectHelper<IfcCovering,1> { IfcCovering() : Object("IfcCovering") {} + Maybe< IfcCoveringTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcCoveringType + struct IfcCoveringType : IfcBuildingElementType, ObjectHelper<IfcCoveringType,1> { IfcCoveringType() : Object("IfcCoveringType") {} + IfcCoveringTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCrewResource + struct IfcCrewResource : IfcConstructionResource, ObjectHelper<IfcCrewResource,1> { IfcCrewResource() : Object("IfcCrewResource") {} + Maybe< IfcCrewResourceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcCrewResourceType + struct IfcCrewResourceType : IfcConstructionResourceType, ObjectHelper<IfcCrewResourceType,1> { IfcCrewResourceType() : Object("IfcCrewResourceType") {} + IfcCrewResourceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCsgSolid + struct IfcCsgSolid : IfcSolidModel, ObjectHelper<IfcCsgSolid,1> { IfcCsgSolid() : Object("IfcCsgSolid") {} + IfcCsgSelect::Out TreeRootExpression; + }; + + // C++ wrapper for IfcCurtainWall + struct IfcCurtainWall : IfcBuildingElement, ObjectHelper<IfcCurtainWall,1> { IfcCurtainWall() : Object("IfcCurtainWall") {} + Maybe< IfcCurtainWallTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcCurtainWallType + struct IfcCurtainWallType : IfcBuildingElementType, ObjectHelper<IfcCurtainWallType,1> { IfcCurtainWallType() : Object("IfcCurtainWallType") {} + IfcCurtainWallTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcCurveBoundedPlane + struct IfcCurveBoundedPlane : IfcBoundedSurface, ObjectHelper<IfcCurveBoundedPlane,3> { IfcCurveBoundedPlane() : Object("IfcCurveBoundedPlane") {} + Lazy< IfcPlane > BasisSurface; + Lazy< IfcCurve > OuterBoundary; + ListOf< Lazy< IfcCurve >, 0, 0 > InnerBoundaries; + }; + + // C++ wrapper for IfcCurveBoundedSurface + struct IfcCurveBoundedSurface : IfcBoundedSurface, ObjectHelper<IfcCurveBoundedSurface,3> { IfcCurveBoundedSurface() : Object("IfcCurveBoundedSurface") {} + Lazy< IfcSurface > BasisSurface; + ListOf< Lazy< IfcBoundaryCurve >, 1, 0 > Boundaries; + IfcBoolean::Out ImplicitOuter; + }; + + // C++ wrapper for IfcPresentationStyle + struct IfcPresentationStyle : ObjectHelper<IfcPresentationStyle,1> { IfcPresentationStyle() : Object("IfcPresentationStyle") {} + Maybe< IfcLabel::Out > Name; + }; + + // C++ wrapper for IfcElementarySurface + struct IfcElementarySurface : IfcSurface, ObjectHelper<IfcElementarySurface,1> { IfcElementarySurface() : Object("IfcElementarySurface") {} + Lazy< IfcAxis2Placement3D > Position; + }; + + // C++ wrapper for IfcCylindricalSurface + struct IfcCylindricalSurface : IfcElementarySurface, ObjectHelper<IfcCylindricalSurface,1> { IfcCylindricalSurface() : Object("IfcCylindricalSurface") {} + IfcPositiveLengthMeasure::Out Radius; + }; + + // C++ wrapper for IfcDamper + struct IfcDamper : IfcFlowController, ObjectHelper<IfcDamper,1> { IfcDamper() : Object("IfcDamper") {} + Maybe< IfcDamperTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcDamperType + struct IfcDamperType : IfcFlowControllerType, ObjectHelper<IfcDamperType,1> { IfcDamperType() : Object("IfcDamperType") {} + IfcDamperTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcDerivedProfileDef + struct IfcDerivedProfileDef : IfcProfileDef, ObjectHelper<IfcDerivedProfileDef,3> { IfcDerivedProfileDef() : Object("IfcDerivedProfileDef") {} + Lazy< IfcProfileDef > ParentProfile; + Lazy< IfcCartesianTransformationOperator2D > Operator; + Maybe< IfcLabel::Out > Label; + }; + + // C++ wrapper for IfcDirection + struct IfcDirection : IfcGeometricRepresentationItem, ObjectHelper<IfcDirection,1> { IfcDirection() : Object("IfcDirection") {} + ListOf< IfcReal, 2, 3 >::Out DirectionRatios; + }; + + // C++ wrapper for IfcDiscreteAccessory + struct IfcDiscreteAccessory : IfcElementComponent, ObjectHelper<IfcDiscreteAccessory,1> { IfcDiscreteAccessory() : Object("IfcDiscreteAccessory") {} + Maybe< IfcDiscreteAccessoryTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcDiscreteAccessoryType + struct IfcDiscreteAccessoryType : IfcElementComponentType, ObjectHelper<IfcDiscreteAccessoryType,1> { IfcDiscreteAccessoryType() : Object("IfcDiscreteAccessoryType") {} + IfcDiscreteAccessoryTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcDistributionChamberElement + struct IfcDistributionChamberElement : IfcDistributionFlowElement, ObjectHelper<IfcDistributionChamberElement,1> { IfcDistributionChamberElement() : Object("IfcDistributionChamberElement") {} + Maybe< IfcDistributionChamberElementTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcDistributionChamberElementType + struct IfcDistributionChamberElementType : IfcDistributionFlowElementType, ObjectHelper<IfcDistributionChamberElementType,1> { IfcDistributionChamberElementType() : Object("IfcDistributionChamberElementType") {} + IfcDistributionChamberElementTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcDistributionSystem + struct IfcDistributionSystem : IfcSystem, ObjectHelper<IfcDistributionSystem,2> { IfcDistributionSystem() : Object("IfcDistributionSystem") {} + Maybe< IfcLabel::Out > LongName; + Maybe< IfcDistributionSystemEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcDistributionCircuit + struct IfcDistributionCircuit : IfcDistributionSystem, ObjectHelper<IfcDistributionCircuit,0> { IfcDistributionCircuit() : Object("IfcDistributionCircuit") {} + + }; + + // C++ wrapper for IfcPort + struct IfcPort : IfcProduct, ObjectHelper<IfcPort,0> { IfcPort() : Object("IfcPort") {} + + }; + + // C++ wrapper for IfcDistributionPort + struct IfcDistributionPort : IfcPort, ObjectHelper<IfcDistributionPort,3> { IfcDistributionPort() : Object("IfcDistributionPort") {} + Maybe< IfcFlowDirectionEnum::Out > FlowDirection; + Maybe< IfcDistributionPortTypeEnum::Out > PredefinedType; + Maybe< IfcDistributionSystemEnum::Out > SystemType; + }; + + // C++ wrapper for IfcDoor + struct IfcDoor : IfcBuildingElement, ObjectHelper<IfcDoor,5> { IfcDoor() : Object("IfcDoor") {} + Maybe< IfcPositiveLengthMeasure::Out > OverallHeight; + Maybe< IfcPositiveLengthMeasure::Out > OverallWidth; + Maybe< IfcDoorTypeEnum::Out > PredefinedType; + Maybe< IfcDoorTypeOperationEnum::Out > OperationType; + Maybe< IfcLabel::Out > UserDefinedOperationType; + }; + + // C++ wrapper for IfcPropertySetDefinition + struct IfcPropertySetDefinition : IfcPropertyDefinition, ObjectHelper<IfcPropertySetDefinition,0> { IfcPropertySetDefinition() : Object("IfcPropertySetDefinition") {} + + }; + + // C++ wrapper for IfcDoorStandardCase + struct IfcDoorStandardCase : IfcDoor, ObjectHelper<IfcDoorStandardCase,0> { IfcDoorStandardCase() : Object("IfcDoorStandardCase") {} + + }; + + // C++ wrapper for IfcDoorStyle + struct IfcDoorStyle : IfcTypeProduct, ObjectHelper<IfcDoorStyle,4> { IfcDoorStyle() : Object("IfcDoorStyle") {} + IfcDoorStyleOperationEnum::Out OperationType; + IfcDoorStyleConstructionEnum::Out ConstructionType; + IfcBoolean::Out ParameterTakesPrecedence; + IfcBoolean::Out Sizeable; + }; + + // C++ wrapper for IfcDoorType + struct IfcDoorType : IfcBuildingElementType, ObjectHelper<IfcDoorType,4> { IfcDoorType() : Object("IfcDoorType") {} + IfcDoorTypeEnum::Out PredefinedType; + IfcDoorTypeOperationEnum::Out OperationType; + Maybe< IfcBoolean::Out > ParameterTakesPrecedence; + Maybe< IfcLabel::Out > UserDefinedOperationType; + }; + + // C++ wrapper for IfcDuctFitting + struct IfcDuctFitting : IfcFlowFitting, ObjectHelper<IfcDuctFitting,1> { IfcDuctFitting() : Object("IfcDuctFitting") {} + Maybe< IfcDuctFittingTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcDuctFittingType + struct IfcDuctFittingType : IfcFlowFittingType, ObjectHelper<IfcDuctFittingType,1> { IfcDuctFittingType() : Object("IfcDuctFittingType") {} + IfcDuctFittingTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcDuctSegment + struct IfcDuctSegment : IfcFlowSegment, ObjectHelper<IfcDuctSegment,1> { IfcDuctSegment() : Object("IfcDuctSegment") {} + Maybe< IfcDuctSegmentTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcDuctSegmentType + struct IfcDuctSegmentType : IfcFlowSegmentType, ObjectHelper<IfcDuctSegmentType,1> { IfcDuctSegmentType() : Object("IfcDuctSegmentType") {} + IfcDuctSegmentTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFlowTreatmentDevice + struct IfcFlowTreatmentDevice : IfcDistributionFlowElement, ObjectHelper<IfcFlowTreatmentDevice,0> { IfcFlowTreatmentDevice() : Object("IfcFlowTreatmentDevice") {} + + }; + + // C++ wrapper for IfcDuctSilencer + struct IfcDuctSilencer : IfcFlowTreatmentDevice, ObjectHelper<IfcDuctSilencer,1> { IfcDuctSilencer() : Object("IfcDuctSilencer") {} + Maybe< IfcDuctSilencerTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFlowTreatmentDeviceType + struct IfcFlowTreatmentDeviceType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowTreatmentDeviceType,0> { IfcFlowTreatmentDeviceType() : Object("IfcFlowTreatmentDeviceType") {} + + }; + + // C++ wrapper for IfcDuctSilencerType + struct IfcDuctSilencerType : IfcFlowTreatmentDeviceType, ObjectHelper<IfcDuctSilencerType,1> { IfcDuctSilencerType() : Object("IfcDuctSilencerType") {} + IfcDuctSilencerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcEdge + struct IfcEdge : IfcTopologicalRepresentationItem, ObjectHelper<IfcEdge,2> { IfcEdge() : Object("IfcEdge") {} + Lazy< IfcVertex > EdgeStart; + Lazy< IfcVertex > EdgeEnd; + }; + + // C++ wrapper for IfcEdgeCurve + struct IfcEdgeCurve : IfcEdge, ObjectHelper<IfcEdgeCurve,2> { IfcEdgeCurve() : Object("IfcEdgeCurve") {} + Lazy< IfcCurve > EdgeGeometry; + IfcBoolean::Out SameSense; + }; + + // C++ wrapper for IfcLoop + struct IfcLoop : IfcTopologicalRepresentationItem, ObjectHelper<IfcLoop,0> { IfcLoop() : Object("IfcLoop") {} + + }; + + // C++ wrapper for IfcEdgeLoop + struct IfcEdgeLoop : IfcLoop, ObjectHelper<IfcEdgeLoop,1> { IfcEdgeLoop() : Object("IfcEdgeLoop") {} + ListOf< Lazy< IfcOrientedEdge >, 1, 0 > EdgeList; + }; + + // C++ wrapper for IfcElectricAppliance + struct IfcElectricAppliance : IfcFlowTerminal, ObjectHelper<IfcElectricAppliance,1> { IfcElectricAppliance() : Object("IfcElectricAppliance") {} + Maybe< IfcElectricApplianceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcElectricApplianceType + struct IfcElectricApplianceType : IfcFlowTerminalType, ObjectHelper<IfcElectricApplianceType,1> { IfcElectricApplianceType() : Object("IfcElectricApplianceType") {} + IfcElectricApplianceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcElectricDistributionBoard + struct IfcElectricDistributionBoard : IfcFlowController, ObjectHelper<IfcElectricDistributionBoard,1> { IfcElectricDistributionBoard() : Object("IfcElectricDistributionBoard") {} + Maybe< IfcElectricDistributionBoardTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcElectricDistributionBoardType + struct IfcElectricDistributionBoardType : IfcFlowControllerType, ObjectHelper<IfcElectricDistributionBoardType,1> { IfcElectricDistributionBoardType() : Object("IfcElectricDistributionBoardType") {} + IfcElectricDistributionBoardTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFlowStorageDevice + struct IfcFlowStorageDevice : IfcDistributionFlowElement, ObjectHelper<IfcFlowStorageDevice,0> { IfcFlowStorageDevice() : Object("IfcFlowStorageDevice") {} + + }; + + // C++ wrapper for IfcElectricFlowStorageDevice + struct IfcElectricFlowStorageDevice : IfcFlowStorageDevice, ObjectHelper<IfcElectricFlowStorageDevice,1> { IfcElectricFlowStorageDevice() : Object("IfcElectricFlowStorageDevice") {} + Maybe< IfcElectricFlowStorageDeviceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFlowStorageDeviceType + struct IfcFlowStorageDeviceType : IfcDistributionFlowElementType, ObjectHelper<IfcFlowStorageDeviceType,0> { IfcFlowStorageDeviceType() : Object("IfcFlowStorageDeviceType") {} + + }; + + // C++ wrapper for IfcElectricFlowStorageDeviceType + struct IfcElectricFlowStorageDeviceType : IfcFlowStorageDeviceType, ObjectHelper<IfcElectricFlowStorageDeviceType,1> { IfcElectricFlowStorageDeviceType() : Object("IfcElectricFlowStorageDeviceType") {} + IfcElectricFlowStorageDeviceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcElectricGenerator + struct IfcElectricGenerator : IfcEnergyConversionDevice, ObjectHelper<IfcElectricGenerator,1> { IfcElectricGenerator() : Object("IfcElectricGenerator") {} + Maybe< IfcElectricGeneratorTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcElectricGeneratorType + struct IfcElectricGeneratorType : IfcEnergyConversionDeviceType, ObjectHelper<IfcElectricGeneratorType,1> { IfcElectricGeneratorType() : Object("IfcElectricGeneratorType") {} + IfcElectricGeneratorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcElectricMotor + struct IfcElectricMotor : IfcEnergyConversionDevice, ObjectHelper<IfcElectricMotor,1> { IfcElectricMotor() : Object("IfcElectricMotor") {} + Maybe< IfcElectricMotorTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcElectricMotorType + struct IfcElectricMotorType : IfcEnergyConversionDeviceType, ObjectHelper<IfcElectricMotorType,1> { IfcElectricMotorType() : Object("IfcElectricMotorType") {} + IfcElectricMotorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcElectricTimeControl + struct IfcElectricTimeControl : IfcFlowController, ObjectHelper<IfcElectricTimeControl,1> { IfcElectricTimeControl() : Object("IfcElectricTimeControl") {} + Maybe< IfcElectricTimeControlTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcElectricTimeControlType + struct IfcElectricTimeControlType : IfcFlowControllerType, ObjectHelper<IfcElectricTimeControlType,1> { IfcElectricTimeControlType() : Object("IfcElectricTimeControlType") {} + IfcElectricTimeControlTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcElementAssembly + struct IfcElementAssembly : IfcElement, ObjectHelper<IfcElementAssembly,2> { IfcElementAssembly() : Object("IfcElementAssembly") {} + Maybe< IfcAssemblyPlaceEnum::Out > AssemblyPlace; + Maybe< IfcElementAssemblyTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcElementAssemblyType + struct IfcElementAssemblyType : IfcElementType, ObjectHelper<IfcElementAssemblyType,1> { IfcElementAssemblyType() : Object("IfcElementAssemblyType") {} + IfcElementAssemblyTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcQuantitySet + struct IfcQuantitySet : IfcPropertySetDefinition, ObjectHelper<IfcQuantitySet,0> { IfcQuantitySet() : Object("IfcQuantitySet") {} + + }; + + // C++ wrapper for IfcElementQuantity + struct IfcElementQuantity : IfcQuantitySet, ObjectHelper<IfcElementQuantity,2> { IfcElementQuantity() : Object("IfcElementQuantity") {} + Maybe< IfcLabel::Out > MethodOfMeasurement; + ListOf< Lazy< NotImplemented >, 1, 0 > Quantities; + }; + + // C++ wrapper for IfcEllipse + struct IfcEllipse : IfcConic, ObjectHelper<IfcEllipse,2> { IfcEllipse() : Object("IfcEllipse") {} + IfcPositiveLengthMeasure::Out SemiAxis1; + IfcPositiveLengthMeasure::Out SemiAxis2; + }; + + // C++ wrapper for IfcEllipseProfileDef + struct IfcEllipseProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcEllipseProfileDef,2> { IfcEllipseProfileDef() : Object("IfcEllipseProfileDef") {} + IfcPositiveLengthMeasure::Out SemiAxis1; + IfcPositiveLengthMeasure::Out SemiAxis2; + }; + + // C++ wrapper for IfcEngine + struct IfcEngine : IfcEnergyConversionDevice, ObjectHelper<IfcEngine,1> { IfcEngine() : Object("IfcEngine") {} + Maybe< IfcEngineTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcEngineType + struct IfcEngineType : IfcEnergyConversionDeviceType, ObjectHelper<IfcEngineType,1> { IfcEngineType() : Object("IfcEngineType") {} + IfcEngineTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcEvaporativeCooler + struct IfcEvaporativeCooler : IfcEnergyConversionDevice, ObjectHelper<IfcEvaporativeCooler,1> { IfcEvaporativeCooler() : Object("IfcEvaporativeCooler") {} + Maybe< IfcEvaporativeCoolerTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcEvaporativeCoolerType + struct IfcEvaporativeCoolerType : IfcEnergyConversionDeviceType, ObjectHelper<IfcEvaporativeCoolerType,1> { IfcEvaporativeCoolerType() : Object("IfcEvaporativeCoolerType") {} + IfcEvaporativeCoolerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcEvaporator + struct IfcEvaporator : IfcEnergyConversionDevice, ObjectHelper<IfcEvaporator,1> { IfcEvaporator() : Object("IfcEvaporator") {} + Maybe< IfcEvaporatorTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcEvaporatorType + struct IfcEvaporatorType : IfcEnergyConversionDeviceType, ObjectHelper<IfcEvaporatorType,1> { IfcEvaporatorType() : Object("IfcEvaporatorType") {} + IfcEvaporatorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcProcess + struct IfcProcess : IfcObject, ObjectHelper<IfcProcess,2> { IfcProcess() : Object("IfcProcess") {} + Maybe< IfcIdentifier::Out > Identification; + Maybe< IfcText::Out > LongDescription; + }; + + // C++ wrapper for IfcEvent + struct IfcEvent : IfcProcess, ObjectHelper<IfcEvent,4> { IfcEvent() : Object("IfcEvent") {} + Maybe< IfcEventTypeEnum::Out > PredefinedType; + Maybe< IfcEventTriggerTypeEnum::Out > EventTriggerType; + Maybe< IfcLabel::Out > UserDefinedEventTriggerType; + Maybe< Lazy< NotImplemented > > EventOccurenceTime; + }; + + // C++ wrapper for IfcTypeProcess + struct IfcTypeProcess : IfcTypeObject, ObjectHelper<IfcTypeProcess,3> { IfcTypeProcess() : Object("IfcTypeProcess") {} + Maybe< IfcIdentifier::Out > Identification; + Maybe< IfcText::Out > LongDescription; + Maybe< IfcLabel::Out > ProcessType; + }; + + // C++ wrapper for IfcEventType + struct IfcEventType : IfcTypeProcess, ObjectHelper<IfcEventType,3> { IfcEventType() : Object("IfcEventType") {} + IfcEventTypeEnum::Out PredefinedType; + IfcEventTriggerTypeEnum::Out EventTriggerType; + Maybe< IfcLabel::Out > UserDefinedEventTriggerType; + }; + + // C++ wrapper for IfcExternalSpatialStructureElement + struct IfcExternalSpatialStructureElement : IfcSpatialElement, ObjectHelper<IfcExternalSpatialStructureElement,0> { IfcExternalSpatialStructureElement() : Object("IfcExternalSpatialStructureElement") {} + + }; + + // C++ wrapper for IfcExternalSpatialElement + struct IfcExternalSpatialElement : IfcExternalSpatialStructureElement, ObjectHelper<IfcExternalSpatialElement,1> { IfcExternalSpatialElement() : Object("IfcExternalSpatialElement") {} + Maybe< IfcExternalSpatialElementTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcSweptAreaSolid + struct IfcSweptAreaSolid : IfcSolidModel, ObjectHelper<IfcSweptAreaSolid,2> { IfcSweptAreaSolid() : Object("IfcSweptAreaSolid") {} + Lazy< IfcProfileDef > SweptArea; + Maybe< Lazy< IfcAxis2Placement3D > > Position; + }; + + // C++ wrapper for IfcExtrudedAreaSolid + struct IfcExtrudedAreaSolid : IfcSweptAreaSolid, ObjectHelper<IfcExtrudedAreaSolid,2> { IfcExtrudedAreaSolid() : Object("IfcExtrudedAreaSolid") {} + Lazy< IfcDirection > ExtrudedDirection; + IfcPositiveLengthMeasure::Out Depth; + }; + + // C++ wrapper for IfcExtrudedAreaSolidTapered + struct IfcExtrudedAreaSolidTapered : IfcExtrudedAreaSolid, ObjectHelper<IfcExtrudedAreaSolidTapered,1> { IfcExtrudedAreaSolidTapered() : Object("IfcExtrudedAreaSolidTapered") {} + Lazy< IfcProfileDef > EndSweptArea; + }; + + // C++ wrapper for IfcFaceBasedSurfaceModel + struct IfcFaceBasedSurfaceModel : IfcGeometricRepresentationItem, ObjectHelper<IfcFaceBasedSurfaceModel,1> { IfcFaceBasedSurfaceModel() : Object("IfcFaceBasedSurfaceModel") {} + ListOf< Lazy< IfcConnectedFaceSet >, 1, 0 > FbsmFaces; + }; + + // C++ wrapper for IfcFaceBound + struct IfcFaceBound : IfcTopologicalRepresentationItem, ObjectHelper<IfcFaceBound,2> { IfcFaceBound() : Object("IfcFaceBound") {} + Lazy< IfcLoop > Bound; + IfcBoolean::Out Orientation; + }; + + // C++ wrapper for IfcFaceOuterBound + struct IfcFaceOuterBound : IfcFaceBound, ObjectHelper<IfcFaceOuterBound,0> { IfcFaceOuterBound() : Object("IfcFaceOuterBound") {} + + }; + + // C++ wrapper for IfcFacetedBrep + struct IfcFacetedBrep : IfcManifoldSolidBrep, ObjectHelper<IfcFacetedBrep,0> { IfcFacetedBrep() : Object("IfcFacetedBrep") {} + + }; + + // C++ wrapper for IfcFacetedBrepWithVoids + struct IfcFacetedBrepWithVoids : IfcFacetedBrep, ObjectHelper<IfcFacetedBrepWithVoids,1> { IfcFacetedBrepWithVoids() : Object("IfcFacetedBrepWithVoids") {} + ListOf< Lazy< IfcClosedShell >, 1, 0 > Voids; + }; + + // C++ wrapper for IfcFan + struct IfcFan : IfcFlowMovingDevice, ObjectHelper<IfcFan,1> { IfcFan() : Object("IfcFan") {} + Maybe< IfcFanTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFanType + struct IfcFanType : IfcFlowMovingDeviceType, ObjectHelper<IfcFanType,1> { IfcFanType() : Object("IfcFanType") {} + IfcFanTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFastener + struct IfcFastener : IfcElementComponent, ObjectHelper<IfcFastener,1> { IfcFastener() : Object("IfcFastener") {} + Maybe< IfcFastenerTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFastenerType + struct IfcFastenerType : IfcElementComponentType, ObjectHelper<IfcFastenerType,1> { IfcFastenerType() : Object("IfcFastenerType") {} + IfcFastenerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFeatureElement + struct IfcFeatureElement : IfcElement, ObjectHelper<IfcFeatureElement,0> { IfcFeatureElement() : Object("IfcFeatureElement") {} + + }; + + // C++ wrapper for IfcFeatureElementAddition + struct IfcFeatureElementAddition : IfcFeatureElement, ObjectHelper<IfcFeatureElementAddition,0> { IfcFeatureElementAddition() : Object("IfcFeatureElementAddition") {} + + }; + + // C++ wrapper for IfcFeatureElementSubtraction + struct IfcFeatureElementSubtraction : IfcFeatureElement, ObjectHelper<IfcFeatureElementSubtraction,0> { IfcFeatureElementSubtraction() : Object("IfcFeatureElementSubtraction") {} + + }; + + // C++ wrapper for IfcFillAreaStyleHatching + struct IfcFillAreaStyleHatching : IfcGeometricRepresentationItem, ObjectHelper<IfcFillAreaStyleHatching,5> { IfcFillAreaStyleHatching() : Object("IfcFillAreaStyleHatching") {} + Lazy< NotImplemented > HatchLineAppearance; + IfcHatchLineDistanceSelect::Out StartOfNextHatchLine; + Maybe< Lazy< IfcCartesianPoint > > PointOfReferenceHatchLine; + Maybe< Lazy< IfcCartesianPoint > > PatternStart; + IfcPlaneAngleMeasure::Out HatchLineAngle; + }; + + // C++ wrapper for IfcFillAreaStyleTiles + struct IfcFillAreaStyleTiles : IfcGeometricRepresentationItem, ObjectHelper<IfcFillAreaStyleTiles,3> { IfcFillAreaStyleTiles() : Object("IfcFillAreaStyleTiles") {} + ListOf< Lazy< IfcVector >, 2, 2 > TilingPattern; + ListOf< Lazy< IfcStyledItem >, 1, 0 > Tiles; + IfcPositiveRatioMeasure::Out TilingScale; + }; + + // C++ wrapper for IfcFilter + struct IfcFilter : IfcFlowTreatmentDevice, ObjectHelper<IfcFilter,1> { IfcFilter() : Object("IfcFilter") {} + Maybe< IfcFilterTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFilterType + struct IfcFilterType : IfcFlowTreatmentDeviceType, ObjectHelper<IfcFilterType,1> { IfcFilterType() : Object("IfcFilterType") {} + IfcFilterTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFireSuppressionTerminal + struct IfcFireSuppressionTerminal : IfcFlowTerminal, ObjectHelper<IfcFireSuppressionTerminal,1> { IfcFireSuppressionTerminal() : Object("IfcFireSuppressionTerminal") {} + Maybe< IfcFireSuppressionTerminalTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFireSuppressionTerminalType + struct IfcFireSuppressionTerminalType : IfcFlowTerminalType, ObjectHelper<IfcFireSuppressionTerminalType,1> { IfcFireSuppressionTerminalType() : Object("IfcFireSuppressionTerminalType") {} + IfcFireSuppressionTerminalTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFixedReferenceSweptAreaSolid + struct IfcFixedReferenceSweptAreaSolid : IfcSweptAreaSolid, ObjectHelper<IfcFixedReferenceSweptAreaSolid,4> { IfcFixedReferenceSweptAreaSolid() : Object("IfcFixedReferenceSweptAreaSolid") {} + Lazy< IfcCurve > Directrix; + Maybe< IfcParameterValue::Out > StartParam; + Maybe< IfcParameterValue::Out > EndParam; + Lazy< IfcDirection > FixedReference; + }; + + // C++ wrapper for IfcFlowInstrument + struct IfcFlowInstrument : IfcDistributionControlElement, ObjectHelper<IfcFlowInstrument,1> { IfcFlowInstrument() : Object("IfcFlowInstrument") {} + Maybe< IfcFlowInstrumentTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFlowInstrumentType + struct IfcFlowInstrumentType : IfcDistributionControlElementType, ObjectHelper<IfcFlowInstrumentType,1> { IfcFlowInstrumentType() : Object("IfcFlowInstrumentType") {} + IfcFlowInstrumentTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFlowMeter + struct IfcFlowMeter : IfcFlowController, ObjectHelper<IfcFlowMeter,1> { IfcFlowMeter() : Object("IfcFlowMeter") {} + Maybe< IfcFlowMeterTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFlowMeterType + struct IfcFlowMeterType : IfcFlowControllerType, ObjectHelper<IfcFlowMeterType,1> { IfcFlowMeterType() : Object("IfcFlowMeterType") {} + IfcFlowMeterTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFooting + struct IfcFooting : IfcBuildingElement, ObjectHelper<IfcFooting,1> { IfcFooting() : Object("IfcFooting") {} + Maybe< IfcFootingTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFootingType + struct IfcFootingType : IfcBuildingElementType, ObjectHelper<IfcFootingType,1> { IfcFootingType() : Object("IfcFootingType") {} + IfcFootingTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcFurnishingElement + struct IfcFurnishingElement : IfcElement, ObjectHelper<IfcFurnishingElement,0> { IfcFurnishingElement() : Object("IfcFurnishingElement") {} + + }; + + // C++ wrapper for IfcFurnishingElementType + struct IfcFurnishingElementType : IfcElementType, ObjectHelper<IfcFurnishingElementType,0> { IfcFurnishingElementType() : Object("IfcFurnishingElementType") {} + + }; + + // C++ wrapper for IfcFurniture + struct IfcFurniture : IfcFurnishingElement, ObjectHelper<IfcFurniture,1> { IfcFurniture() : Object("IfcFurniture") {} + Maybe< IfcFurnitureTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcFurnitureType + struct IfcFurnitureType : IfcFurnishingElementType, ObjectHelper<IfcFurnitureType,2> { IfcFurnitureType() : Object("IfcFurnitureType") {} + IfcAssemblyPlaceEnum::Out AssemblyPlace; + Maybe< IfcFurnitureTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcGeographicElement + struct IfcGeographicElement : IfcElement, ObjectHelper<IfcGeographicElement,1> { IfcGeographicElement() : Object("IfcGeographicElement") {} + Maybe< IfcGeographicElementTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcGeographicElementType + struct IfcGeographicElementType : IfcElementType, ObjectHelper<IfcGeographicElementType,1> { IfcGeographicElementType() : Object("IfcGeographicElementType") {} + IfcGeographicElementTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcGeometricSet + struct IfcGeometricSet : IfcGeometricRepresentationItem, ObjectHelper<IfcGeometricSet,1> { IfcGeometricSet() : Object("IfcGeometricSet") {} + ListOf< IfcGeometricSetSelect, 1, 0 >::Out Elements; + }; + + // C++ wrapper for IfcGeometricCurveSet + struct IfcGeometricCurveSet : IfcGeometricSet, ObjectHelper<IfcGeometricCurveSet,0> { IfcGeometricCurveSet() : Object("IfcGeometricCurveSet") {} + + }; + + // C++ wrapper for IfcRepresentationContext + struct IfcRepresentationContext : ObjectHelper<IfcRepresentationContext,2> { IfcRepresentationContext() : Object("IfcRepresentationContext") {} + Maybe< IfcLabel::Out > ContextIdentifier; + Maybe< IfcLabel::Out > ContextType; + }; + + // C++ wrapper for IfcGeometricRepresentationContext + struct IfcGeometricRepresentationContext : IfcRepresentationContext, ObjectHelper<IfcGeometricRepresentationContext,4> { IfcGeometricRepresentationContext() : Object("IfcGeometricRepresentationContext") {} + IfcDimensionCount::Out CoordinateSpaceDimension; + Maybe< IfcReal::Out > Precision; + IfcAxis2Placement::Out WorldCoordinateSystem; + Maybe< Lazy< IfcDirection > > TrueNorth; + }; + + // C++ wrapper for IfcGeometricRepresentationSubContext + struct IfcGeometricRepresentationSubContext : IfcGeometricRepresentationContext, ObjectHelper<IfcGeometricRepresentationSubContext,4> { IfcGeometricRepresentationSubContext() : Object("IfcGeometricRepresentationSubContext") {} + Lazy< IfcGeometricRepresentationContext > ParentContext; + Maybe< IfcPositiveRatioMeasure::Out > TargetScale; + IfcGeometricProjectionEnum::Out TargetView; + Maybe< IfcLabel::Out > UserDefinedTargetView; + }; + + // C++ wrapper for IfcGrid + struct IfcGrid : IfcProduct, ObjectHelper<IfcGrid,4> { IfcGrid() : Object("IfcGrid") {} + ListOf< Lazy< NotImplemented >, 1, 0 > UAxes; + ListOf< Lazy< NotImplemented >, 1, 0 > VAxes; + Maybe< ListOf< Lazy< NotImplemented >, 1, 0 > > WAxes; + Maybe< IfcGridTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcObjectPlacement + struct IfcObjectPlacement : ObjectHelper<IfcObjectPlacement,0> { IfcObjectPlacement() : Object("IfcObjectPlacement") {} + + }; + + // C++ wrapper for IfcGridPlacement + struct IfcGridPlacement : IfcObjectPlacement, ObjectHelper<IfcGridPlacement,2> { IfcGridPlacement() : Object("IfcGridPlacement") {} + Lazy< NotImplemented > PlacementLocation; + Maybe< IfcGridPlacementDirectionSelect::Out > PlacementRefDirection; + }; + + // C++ wrapper for IfcHeatExchanger + struct IfcHeatExchanger : IfcEnergyConversionDevice, ObjectHelper<IfcHeatExchanger,1> { IfcHeatExchanger() : Object("IfcHeatExchanger") {} + Maybe< IfcHeatExchangerTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcHeatExchangerType + struct IfcHeatExchangerType : IfcEnergyConversionDeviceType, ObjectHelper<IfcHeatExchangerType,1> { IfcHeatExchangerType() : Object("IfcHeatExchangerType") {} + IfcHeatExchangerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcHumidifier + struct IfcHumidifier : IfcEnergyConversionDevice, ObjectHelper<IfcHumidifier,1> { IfcHumidifier() : Object("IfcHumidifier") {} + Maybe< IfcHumidifierTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcHumidifierType + struct IfcHumidifierType : IfcEnergyConversionDeviceType, ObjectHelper<IfcHumidifierType,1> { IfcHumidifierType() : Object("IfcHumidifierType") {} + IfcHumidifierTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcIShapeProfileDef + struct IfcIShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcIShapeProfileDef,7> { IfcIShapeProfileDef() : Object("IfcIShapeProfileDef") {} + IfcPositiveLengthMeasure::Out OverallWidth; + IfcPositiveLengthMeasure::Out OverallDepth; + IfcPositiveLengthMeasure::Out WebThickness; + IfcPositiveLengthMeasure::Out FlangeThickness; + Maybe< IfcNonNegativeLengthMeasure::Out > FilletRadius; + Maybe< IfcNonNegativeLengthMeasure::Out > FlangeEdgeRadius; + Maybe< IfcPlaneAngleMeasure::Out > FlangeSlope; + }; + + // C++ wrapper for IfcIndexedPolyCurve + struct IfcIndexedPolyCurve : IfcBoundedCurve, ObjectHelper<IfcIndexedPolyCurve,3> { IfcIndexedPolyCurve() : Object("IfcIndexedPolyCurve") {} + Lazy< IfcCartesianPointList > Points; + Maybe< ListOf< IfcSegmentIndexSelect, 1, 0 >::Out > Segments; + Maybe< IfcBoolean::Out > SelfIntersect; + }; + + // C++ wrapper for IfcTessellatedItem + struct IfcTessellatedItem : IfcGeometricRepresentationItem, ObjectHelper<IfcTessellatedItem,0> { IfcTessellatedItem() : Object("IfcTessellatedItem") {} + + }; + + // C++ wrapper for IfcIndexedPolygonalFace + struct IfcIndexedPolygonalFace : IfcTessellatedItem, ObjectHelper<IfcIndexedPolygonalFace,1> { IfcIndexedPolygonalFace() : Object("IfcIndexedPolygonalFace") {} + ListOf< IfcPositiveInteger, 3, 0 >::Out CoordIndex; + }; + + // C++ wrapper for IfcIndexedPolygonalFaceWithVoids + struct IfcIndexedPolygonalFaceWithVoids : IfcIndexedPolygonalFace, ObjectHelper<IfcIndexedPolygonalFaceWithVoids,0> { IfcIndexedPolygonalFaceWithVoids() : Object("IfcIndexedPolygonalFaceWithVoids") {} + + }; + + // C++ wrapper for IfcInterceptor + struct IfcInterceptor : IfcFlowTreatmentDevice, ObjectHelper<IfcInterceptor,1> { IfcInterceptor() : Object("IfcInterceptor") {} + Maybe< IfcInterceptorTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcInterceptorType + struct IfcInterceptorType : IfcFlowTreatmentDeviceType, ObjectHelper<IfcInterceptorType,1> { IfcInterceptorType() : Object("IfcInterceptorType") {} + IfcInterceptorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcSurfaceCurve + struct IfcSurfaceCurve : IfcCurve, ObjectHelper<IfcSurfaceCurve,3> { IfcSurfaceCurve() : Object("IfcSurfaceCurve") {} + Lazy< IfcCurve > Curve3D; + ListOf< Lazy< IfcPcurve >, 1, 2 > AssociatedGeometry; + IfcPreferredSurfaceCurveRepresentation::Out MasterRepresentation; + }; + + // C++ wrapper for IfcIntersectionCurve + struct IfcIntersectionCurve : IfcSurfaceCurve, ObjectHelper<IfcIntersectionCurve,0> { IfcIntersectionCurve() : Object("IfcIntersectionCurve") {} + + }; + + // C++ wrapper for IfcInventory + struct IfcInventory : IfcGroup, ObjectHelper<IfcInventory,6> { IfcInventory() : Object("IfcInventory") {} + Maybe< IfcInventoryTypeEnum::Out > PredefinedType; + Maybe< IfcActorSelect::Out > Jurisdiction; + Maybe< ListOf< Lazy< NotImplemented >, 1, 0 > > ResponsiblePersons; + Maybe< IfcDate::Out > LastUpdateDate; + Maybe< Lazy< NotImplemented > > CurrentValue; + Maybe< Lazy< NotImplemented > > OriginalValue; + }; + + // C++ wrapper for IfcJunctionBox + struct IfcJunctionBox : IfcFlowFitting, ObjectHelper<IfcJunctionBox,1> { IfcJunctionBox() : Object("IfcJunctionBox") {} + Maybe< IfcJunctionBoxTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcJunctionBoxType + struct IfcJunctionBoxType : IfcFlowFittingType, ObjectHelper<IfcJunctionBoxType,1> { IfcJunctionBoxType() : Object("IfcJunctionBoxType") {} + IfcJunctionBoxTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcLShapeProfileDef + struct IfcLShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcLShapeProfileDef,6> { IfcLShapeProfileDef() : Object("IfcLShapeProfileDef") {} + IfcPositiveLengthMeasure::Out Depth; + Maybe< IfcPositiveLengthMeasure::Out > Width; + IfcPositiveLengthMeasure::Out Thickness; + Maybe< IfcNonNegativeLengthMeasure::Out > FilletRadius; + Maybe< IfcNonNegativeLengthMeasure::Out > EdgeRadius; + Maybe< IfcPlaneAngleMeasure::Out > LegSlope; + }; + + // C++ wrapper for IfcLaborResource + struct IfcLaborResource : IfcConstructionResource, ObjectHelper<IfcLaborResource,1> { IfcLaborResource() : Object("IfcLaborResource") {} + Maybe< IfcLaborResourceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcLaborResourceType + struct IfcLaborResourceType : IfcConstructionResourceType, ObjectHelper<IfcLaborResourceType,1> { IfcLaborResourceType() : Object("IfcLaborResourceType") {} + IfcLaborResourceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcLamp + struct IfcLamp : IfcFlowTerminal, ObjectHelper<IfcLamp,1> { IfcLamp() : Object("IfcLamp") {} + Maybe< IfcLampTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcLampType + struct IfcLampType : IfcFlowTerminalType, ObjectHelper<IfcLampType,1> { IfcLampType() : Object("IfcLampType") {} + IfcLampTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcLightFixture + struct IfcLightFixture : IfcFlowTerminal, ObjectHelper<IfcLightFixture,1> { IfcLightFixture() : Object("IfcLightFixture") {} + Maybe< IfcLightFixtureTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcLightFixtureType + struct IfcLightFixtureType : IfcFlowTerminalType, ObjectHelper<IfcLightFixtureType,1> { IfcLightFixtureType() : Object("IfcLightFixtureType") {} + IfcLightFixtureTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcLightSource + struct IfcLightSource : IfcGeometricRepresentationItem, ObjectHelper<IfcLightSource,4> { IfcLightSource() : Object("IfcLightSource") {} + Maybe< IfcLabel::Out > Name; + Lazy< IfcColourRgb > LightColour; + Maybe< IfcNormalisedRatioMeasure::Out > AmbientIntensity; + Maybe< IfcNormalisedRatioMeasure::Out > Intensity; + }; + + // C++ wrapper for IfcLightSourceAmbient + struct IfcLightSourceAmbient : IfcLightSource, ObjectHelper<IfcLightSourceAmbient,0> { IfcLightSourceAmbient() : Object("IfcLightSourceAmbient") {} + + }; + + // C++ wrapper for IfcLightSourceDirectional + struct IfcLightSourceDirectional : IfcLightSource, ObjectHelper<IfcLightSourceDirectional,1> { IfcLightSourceDirectional() : Object("IfcLightSourceDirectional") {} + Lazy< IfcDirection > Orientation; + }; + + // C++ wrapper for IfcLightSourceGoniometric + struct IfcLightSourceGoniometric : IfcLightSource, ObjectHelper<IfcLightSourceGoniometric,6> { IfcLightSourceGoniometric() : Object("IfcLightSourceGoniometric") {} + Lazy< IfcAxis2Placement3D > Position; + Maybe< Lazy< IfcColourRgb > > ColourAppearance; + IfcThermodynamicTemperatureMeasure::Out ColourTemperature; + IfcLuminousFluxMeasure::Out LuminousFlux; + IfcLightEmissionSourceEnum::Out LightEmissionSource; + IfcLightDistributionDataSourceSelect::Out LightDistributionDataSource; + }; + + // C++ wrapper for IfcLightSourcePositional + struct IfcLightSourcePositional : IfcLightSource, ObjectHelper<IfcLightSourcePositional,5> { IfcLightSourcePositional() : Object("IfcLightSourcePositional") {} + Lazy< IfcCartesianPoint > Position; + IfcPositiveLengthMeasure::Out Radius; + IfcReal::Out ConstantAttenuation; + IfcReal::Out DistanceAttenuation; + IfcReal::Out QuadricAttenuation; + }; + + // C++ wrapper for IfcLightSourceSpot + struct IfcLightSourceSpot : IfcLightSourcePositional, ObjectHelper<IfcLightSourceSpot,4> { IfcLightSourceSpot() : Object("IfcLightSourceSpot") {} + Lazy< IfcDirection > Orientation; + Maybe< IfcReal::Out > ConcentrationExponent; + IfcPositivePlaneAngleMeasure::Out SpreadAngle; + IfcPositivePlaneAngleMeasure::Out BeamWidthAngle; + }; + + // C++ wrapper for IfcLine + struct IfcLine : IfcCurve, ObjectHelper<IfcLine,2> { IfcLine() : Object("IfcLine") {} + Lazy< IfcCartesianPoint > Pnt; + Lazy< IfcVector > Dir; + }; + + // C++ wrapper for IfcLocalPlacement + struct IfcLocalPlacement : IfcObjectPlacement, ObjectHelper<IfcLocalPlacement,2> { IfcLocalPlacement() : Object("IfcLocalPlacement") {} + Maybe< Lazy< IfcObjectPlacement > > PlacementRelTo; + IfcAxis2Placement::Out RelativePlacement; + }; + + // C++ wrapper for IfcMappedItem + struct IfcMappedItem : IfcRepresentationItem, ObjectHelper<IfcMappedItem,2> { IfcMappedItem() : Object("IfcMappedItem") {} + Lazy< IfcRepresentationMap > MappingSource; + Lazy< IfcCartesianTransformationOperator > MappingTarget; + }; + + // C++ wrapper for IfcProductRepresentation + struct IfcProductRepresentation : ObjectHelper<IfcProductRepresentation,3> { IfcProductRepresentation() : Object("IfcProductRepresentation") {} + Maybe< IfcLabel::Out > Name; + Maybe< IfcText::Out > Description; + ListOf< Lazy< IfcRepresentation >, 1, 0 > Representations; + }; + + // C++ wrapper for IfcMaterialDefinitionRepresentation + struct IfcMaterialDefinitionRepresentation : IfcProductRepresentation, ObjectHelper<IfcMaterialDefinitionRepresentation,1> { IfcMaterialDefinitionRepresentation() : Object("IfcMaterialDefinitionRepresentation") {} + Lazy< NotImplemented > RepresentedMaterial; + }; + + // C++ wrapper for IfcMeasureWithUnit + struct IfcMeasureWithUnit : ObjectHelper<IfcMeasureWithUnit,2> { IfcMeasureWithUnit() : Object("IfcMeasureWithUnit") {} + IfcValue::Out ValueComponent; + IfcUnit::Out UnitComponent; + }; + + // C++ wrapper for IfcMechanicalFastener + struct IfcMechanicalFastener : IfcElementComponent, ObjectHelper<IfcMechanicalFastener,3> { IfcMechanicalFastener() : Object("IfcMechanicalFastener") {} + Maybe< IfcPositiveLengthMeasure::Out > NominalDiameter; + Maybe< IfcPositiveLengthMeasure::Out > NominalLength; + Maybe< IfcMechanicalFastenerTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcMechanicalFastenerType + struct IfcMechanicalFastenerType : IfcElementComponentType, ObjectHelper<IfcMechanicalFastenerType,3> { IfcMechanicalFastenerType() : Object("IfcMechanicalFastenerType") {} + IfcMechanicalFastenerTypeEnum::Out PredefinedType; + Maybe< IfcPositiveLengthMeasure::Out > NominalDiameter; + Maybe< IfcPositiveLengthMeasure::Out > NominalLength; + }; + + // C++ wrapper for IfcMedicalDevice + struct IfcMedicalDevice : IfcFlowTerminal, ObjectHelper<IfcMedicalDevice,1> { IfcMedicalDevice() : Object("IfcMedicalDevice") {} + Maybe< IfcMedicalDeviceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcMedicalDeviceType + struct IfcMedicalDeviceType : IfcFlowTerminalType, ObjectHelper<IfcMedicalDeviceType,1> { IfcMedicalDeviceType() : Object("IfcMedicalDeviceType") {} + IfcMedicalDeviceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcMember + struct IfcMember : IfcBuildingElement, ObjectHelper<IfcMember,1> { IfcMember() : Object("IfcMember") {} + Maybe< IfcMemberTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcMemberStandardCase + struct IfcMemberStandardCase : IfcMember, ObjectHelper<IfcMemberStandardCase,0> { IfcMemberStandardCase() : Object("IfcMemberStandardCase") {} + + }; + + // C++ wrapper for IfcMemberType + struct IfcMemberType : IfcBuildingElementType, ObjectHelper<IfcMemberType,1> { IfcMemberType() : Object("IfcMemberType") {} + IfcMemberTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcMirroredProfileDef + struct IfcMirroredProfileDef : IfcDerivedProfileDef, ObjectHelper<IfcMirroredProfileDef,0> { IfcMirroredProfileDef() : Object("IfcMirroredProfileDef") {} + + }; + + // C++ wrapper for IfcMotorConnection + struct IfcMotorConnection : IfcEnergyConversionDevice, ObjectHelper<IfcMotorConnection,1> { IfcMotorConnection() : Object("IfcMotorConnection") {} + Maybe< IfcMotorConnectionTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcMotorConnectionType + struct IfcMotorConnectionType : IfcEnergyConversionDeviceType, ObjectHelper<IfcMotorConnectionType,1> { IfcMotorConnectionType() : Object("IfcMotorConnectionType") {} + IfcMotorConnectionTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcOccupant + struct IfcOccupant : IfcActor, ObjectHelper<IfcOccupant,1> { IfcOccupant() : Object("IfcOccupant") {} + Maybe< IfcOccupantTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcOffsetCurve2D + struct IfcOffsetCurve2D : IfcCurve, ObjectHelper<IfcOffsetCurve2D,3> { IfcOffsetCurve2D() : Object("IfcOffsetCurve2D") {} + Lazy< IfcCurve > BasisCurve; + IfcLengthMeasure::Out Distance; + IfcLogical::Out SelfIntersect; + }; + + // C++ wrapper for IfcOffsetCurve3D + struct IfcOffsetCurve3D : IfcCurve, ObjectHelper<IfcOffsetCurve3D,4> { IfcOffsetCurve3D() : Object("IfcOffsetCurve3D") {} + Lazy< IfcCurve > BasisCurve; + IfcLengthMeasure::Out Distance; + IfcLogical::Out SelfIntersect; + Lazy< IfcDirection > RefDirection; + }; + + // C++ wrapper for IfcOpenShell + struct IfcOpenShell : IfcConnectedFaceSet, ObjectHelper<IfcOpenShell,0> { IfcOpenShell() : Object("IfcOpenShell") {} + + }; + + // C++ wrapper for IfcOpeningElement + struct IfcOpeningElement : IfcFeatureElementSubtraction, ObjectHelper<IfcOpeningElement,1> { IfcOpeningElement() : Object("IfcOpeningElement") {} + Maybe< IfcOpeningElementTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcOpeningStandardCase + struct IfcOpeningStandardCase : IfcOpeningElement, ObjectHelper<IfcOpeningStandardCase,0> { IfcOpeningStandardCase() : Object("IfcOpeningStandardCase") {} + + }; + + // C++ wrapper for IfcOrientedEdge + struct IfcOrientedEdge : IfcEdge, ObjectHelper<IfcOrientedEdge,2> { IfcOrientedEdge() : Object("IfcOrientedEdge") {} + Lazy< IfcEdge > EdgeElement; + IfcBoolean::Out Orientation; + }; + + // C++ wrapper for IfcOuterBoundaryCurve + struct IfcOuterBoundaryCurve : IfcBoundaryCurve, ObjectHelper<IfcOuterBoundaryCurve,0> { IfcOuterBoundaryCurve() : Object("IfcOuterBoundaryCurve") {} + + }; + + // C++ wrapper for IfcOutlet + struct IfcOutlet : IfcFlowTerminal, ObjectHelper<IfcOutlet,1> { IfcOutlet() : Object("IfcOutlet") {} + Maybe< IfcOutletTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcOutletType + struct IfcOutletType : IfcFlowTerminalType, ObjectHelper<IfcOutletType,1> { IfcOutletType() : Object("IfcOutletType") {} + IfcOutletTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPath + struct IfcPath : IfcTopologicalRepresentationItem, ObjectHelper<IfcPath,1> { IfcPath() : Object("IfcPath") {} + ListOf< Lazy< IfcOrientedEdge >, 1, 0 > EdgeList; + }; + + // C++ wrapper for IfcPcurve + struct IfcPcurve : IfcCurve, ObjectHelper<IfcPcurve,2> { IfcPcurve() : Object("IfcPcurve") {} + Lazy< IfcSurface > BasisSurface; + Lazy< IfcCurve > ReferenceCurve; + }; + + // C++ wrapper for IfcPerformanceHistory + struct IfcPerformanceHistory : IfcControl, ObjectHelper<IfcPerformanceHistory,2> { IfcPerformanceHistory() : Object("IfcPerformanceHistory") {} + IfcLabel::Out LifeCyclePhase; + Maybe< IfcPerformanceHistoryTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcPermit + struct IfcPermit : IfcControl, ObjectHelper<IfcPermit,3> { IfcPermit() : Object("IfcPermit") {} + Maybe< IfcPermitTypeEnum::Out > PredefinedType; + Maybe< IfcLabel::Out > Status; + Maybe< IfcText::Out > LongDescription; + }; + + // C++ wrapper for IfcPile + struct IfcPile : IfcBuildingElement, ObjectHelper<IfcPile,2> { IfcPile() : Object("IfcPile") {} + Maybe< IfcPileTypeEnum::Out > PredefinedType; + Maybe< IfcPileConstructionEnum::Out > ConstructionType; + }; + + // C++ wrapper for IfcPileType + struct IfcPileType : IfcBuildingElementType, ObjectHelper<IfcPileType,1> { IfcPileType() : Object("IfcPileType") {} + IfcPileTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPipeFitting + struct IfcPipeFitting : IfcFlowFitting, ObjectHelper<IfcPipeFitting,1> { IfcPipeFitting() : Object("IfcPipeFitting") {} + Maybe< IfcPipeFittingTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcPipeFittingType + struct IfcPipeFittingType : IfcFlowFittingType, ObjectHelper<IfcPipeFittingType,1> { IfcPipeFittingType() : Object("IfcPipeFittingType") {} + IfcPipeFittingTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPipeSegment + struct IfcPipeSegment : IfcFlowSegment, ObjectHelper<IfcPipeSegment,1> { IfcPipeSegment() : Object("IfcPipeSegment") {} + Maybe< IfcPipeSegmentTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcPipeSegmentType + struct IfcPipeSegmentType : IfcFlowSegmentType, ObjectHelper<IfcPipeSegmentType,1> { IfcPipeSegmentType() : Object("IfcPipeSegmentType") {} + IfcPipeSegmentTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPlanarExtent + struct IfcPlanarExtent : IfcGeometricRepresentationItem, ObjectHelper<IfcPlanarExtent,2> { IfcPlanarExtent() : Object("IfcPlanarExtent") {} + IfcLengthMeasure::Out SizeInX; + IfcLengthMeasure::Out SizeInY; + }; + + // C++ wrapper for IfcPlanarBox + struct IfcPlanarBox : IfcPlanarExtent, ObjectHelper<IfcPlanarBox,1> { IfcPlanarBox() : Object("IfcPlanarBox") {} + IfcAxis2Placement::Out Placement; + }; + + // C++ wrapper for IfcPlane + struct IfcPlane : IfcElementarySurface, ObjectHelper<IfcPlane,0> { IfcPlane() : Object("IfcPlane") {} + + }; + + // C++ wrapper for IfcPlate + struct IfcPlate : IfcBuildingElement, ObjectHelper<IfcPlate,1> { IfcPlate() : Object("IfcPlate") {} + Maybe< IfcPlateTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcPlateStandardCase + struct IfcPlateStandardCase : IfcPlate, ObjectHelper<IfcPlateStandardCase,0> { IfcPlateStandardCase() : Object("IfcPlateStandardCase") {} + + }; + + // C++ wrapper for IfcPlateType + struct IfcPlateType : IfcBuildingElementType, ObjectHelper<IfcPlateType,1> { IfcPlateType() : Object("IfcPlateType") {} + IfcPlateTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcPointOnCurve + struct IfcPointOnCurve : IfcPoint, ObjectHelper<IfcPointOnCurve,2> { IfcPointOnCurve() : Object("IfcPointOnCurve") {} + Lazy< IfcCurve > BasisCurve; + IfcParameterValue::Out PointParameter; + }; + + // C++ wrapper for IfcPointOnSurface + struct IfcPointOnSurface : IfcPoint, ObjectHelper<IfcPointOnSurface,3> { IfcPointOnSurface() : Object("IfcPointOnSurface") {} + Lazy< IfcSurface > BasisSurface; + IfcParameterValue::Out PointParameterU; + IfcParameterValue::Out PointParameterV; + }; + + // C++ wrapper for IfcPolyLoop + struct IfcPolyLoop : IfcLoop, ObjectHelper<IfcPolyLoop,1> { IfcPolyLoop() : Object("IfcPolyLoop") {} + ListOf< Lazy< IfcCartesianPoint >, 3, 0 > Polygon; + }; + + // C++ wrapper for IfcPolygonalBoundedHalfSpace + struct IfcPolygonalBoundedHalfSpace : IfcHalfSpaceSolid, ObjectHelper<IfcPolygonalBoundedHalfSpace,2> { IfcPolygonalBoundedHalfSpace() : Object("IfcPolygonalBoundedHalfSpace") {} + Lazy< IfcAxis2Placement3D > Position; + Lazy< IfcBoundedCurve > PolygonalBoundary; + }; + + // C++ wrapper for IfcTessellatedFaceSet + struct IfcTessellatedFaceSet : IfcTessellatedItem, ObjectHelper<IfcTessellatedFaceSet,1> { IfcTessellatedFaceSet() : Object("IfcTessellatedFaceSet") {} + Lazy< IfcCartesianPointList3D > Coordinates; + }; + + // C++ wrapper for IfcPolygonalFaceSet + struct IfcPolygonalFaceSet : IfcTessellatedFaceSet, ObjectHelper<IfcPolygonalFaceSet,3> { IfcPolygonalFaceSet() : Object("IfcPolygonalFaceSet") {} + Maybe< IfcBoolean::Out > Closed; + ListOf< Lazy< IfcIndexedPolygonalFace >, 1, 0 > Faces; + Maybe< ListOf< IfcPositiveInteger, 1, 0 >::Out > PnIndex; + }; + + // C++ wrapper for IfcPolyline + struct IfcPolyline : IfcBoundedCurve, ObjectHelper<IfcPolyline,1> { IfcPolyline() : Object("IfcPolyline") {} + ListOf< Lazy< IfcCartesianPoint >, 2, 0 > Points; + }; + + // C++ wrapper for IfcPresentationStyleAssignment + struct IfcPresentationStyleAssignment : ObjectHelper<IfcPresentationStyleAssignment,1> { IfcPresentationStyleAssignment() : Object("IfcPresentationStyleAssignment") {} + ListOf< IfcPresentationStyleSelect, 1, 0 >::Out Styles; + }; + + // C++ wrapper for IfcProcedure + struct IfcProcedure : IfcProcess, ObjectHelper<IfcProcedure,1> { IfcProcedure() : Object("IfcProcedure") {} + Maybe< IfcProcedureTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcProcedureType + struct IfcProcedureType : IfcTypeProcess, ObjectHelper<IfcProcedureType,1> { IfcProcedureType() : Object("IfcProcedureType") {} + IfcProcedureTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcProductDefinitionShape + struct IfcProductDefinitionShape : IfcProductRepresentation, ObjectHelper<IfcProductDefinitionShape,0> { IfcProductDefinitionShape() : Object("IfcProductDefinitionShape") {} + + }; + + // C++ wrapper for IfcProject + struct IfcProject : IfcContext, ObjectHelper<IfcProject,0> { IfcProject() : Object("IfcProject") {} + + }; + + // C++ wrapper for IfcProjectLibrary + struct IfcProjectLibrary : IfcContext, ObjectHelper<IfcProjectLibrary,0> { IfcProjectLibrary() : Object("IfcProjectLibrary") {} + + }; + + // C++ wrapper for IfcProjectOrder + struct IfcProjectOrder : IfcControl, ObjectHelper<IfcProjectOrder,3> { IfcProjectOrder() : Object("IfcProjectOrder") {} + Maybe< IfcProjectOrderTypeEnum::Out > PredefinedType; + Maybe< IfcLabel::Out > Status; + Maybe< IfcText::Out > LongDescription; + }; + + // C++ wrapper for IfcProjectionElement + struct IfcProjectionElement : IfcFeatureElementAddition, ObjectHelper<IfcProjectionElement,1> { IfcProjectionElement() : Object("IfcProjectionElement") {} + Maybe< IfcProjectionElementTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcSimpleProperty + struct IfcSimpleProperty : IfcProperty, ObjectHelper<IfcSimpleProperty,0> { IfcSimpleProperty() : Object("IfcSimpleProperty") {} + + }; + + // C++ wrapper for IfcPropertyBoundedValue + struct IfcPropertyBoundedValue : IfcSimpleProperty, ObjectHelper<IfcPropertyBoundedValue,4> { IfcPropertyBoundedValue() : Object("IfcPropertyBoundedValue") {} + Maybe< IfcValue::Out > UpperBoundValue; + Maybe< IfcValue::Out > LowerBoundValue; + Maybe< IfcUnit::Out > Unit; + Maybe< IfcValue::Out > SetPointValue; + }; + + // C++ wrapper for IfcPropertyEnumeratedValue + struct IfcPropertyEnumeratedValue : IfcSimpleProperty, ObjectHelper<IfcPropertyEnumeratedValue,2> { IfcPropertyEnumeratedValue() : Object("IfcPropertyEnumeratedValue") {} + Maybe< ListOf< IfcValue, 1, 0 >::Out > EnumerationValues; + Maybe< Lazy< NotImplemented > > EnumerationReference; + }; + + // C++ wrapper for IfcPropertyListValue + struct IfcPropertyListValue : IfcSimpleProperty, ObjectHelper<IfcPropertyListValue,2> { IfcPropertyListValue() : Object("IfcPropertyListValue") {} + Maybe< ListOf< IfcValue, 1, 0 >::Out > ListValues; + Maybe< IfcUnit::Out > Unit; + }; + + // C++ wrapper for IfcPropertyReferenceValue + struct IfcPropertyReferenceValue : IfcSimpleProperty, ObjectHelper<IfcPropertyReferenceValue,2> { IfcPropertyReferenceValue() : Object("IfcPropertyReferenceValue") {} + Maybe< IfcText::Out > UsageName; + Maybe< IfcObjectReferenceSelect::Out > PropertyReference; + }; + + // C++ wrapper for IfcPropertySet + struct IfcPropertySet : IfcPropertySetDefinition, ObjectHelper<IfcPropertySet,1> { IfcPropertySet() : Object("IfcPropertySet") {} + ListOf< Lazy< IfcProperty >, 1, 0 > HasProperties; + }; + + // C++ wrapper for IfcPropertySingleValue + struct IfcPropertySingleValue : IfcSimpleProperty, ObjectHelper<IfcPropertySingleValue,2> { IfcPropertySingleValue() : Object("IfcPropertySingleValue") {} + Maybe< IfcValue::Out > NominalValue; + Maybe< IfcUnit::Out > Unit; + }; + + // C++ wrapper for IfcPropertyTableValue + struct IfcPropertyTableValue : IfcSimpleProperty, ObjectHelper<IfcPropertyTableValue,6> { IfcPropertyTableValue() : Object("IfcPropertyTableValue") {} + Maybe< ListOf< IfcValue, 1, 0 >::Out > DefiningValues; + Maybe< ListOf< IfcValue, 1, 0 >::Out > DefinedValues; + Maybe< IfcText::Out > Expression; + Maybe< IfcUnit::Out > DefiningUnit; + Maybe< IfcUnit::Out > DefinedUnit; + Maybe< IfcCurveInterpolationEnum::Out > CurveInterpolation; + }; + + // C++ wrapper for IfcProtectiveDevice + struct IfcProtectiveDevice : IfcFlowController, ObjectHelper<IfcProtectiveDevice,1> { IfcProtectiveDevice() : Object("IfcProtectiveDevice") {} + Maybe< IfcProtectiveDeviceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcProtectiveDeviceTrippingUnit + struct IfcProtectiveDeviceTrippingUnit : IfcDistributionControlElement, ObjectHelper<IfcProtectiveDeviceTrippingUnit,1> { IfcProtectiveDeviceTrippingUnit() : Object("IfcProtectiveDeviceTrippingUnit") {} + Maybe< IfcProtectiveDeviceTrippingUnitTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcProtectiveDeviceTrippingUnitType + struct IfcProtectiveDeviceTrippingUnitType : IfcDistributionControlElementType, ObjectHelper<IfcProtectiveDeviceTrippingUnitType,1> { IfcProtectiveDeviceTrippingUnitType() : Object("IfcProtectiveDeviceTrippingUnitType") {} + IfcProtectiveDeviceTrippingUnitTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcProtectiveDeviceType + struct IfcProtectiveDeviceType : IfcFlowControllerType, ObjectHelper<IfcProtectiveDeviceType,1> { IfcProtectiveDeviceType() : Object("IfcProtectiveDeviceType") {} + IfcProtectiveDeviceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcProxy + struct IfcProxy : IfcProduct, ObjectHelper<IfcProxy,2> { IfcProxy() : Object("IfcProxy") {} + IfcObjectTypeEnum::Out ProxyType; + Maybe< IfcLabel::Out > Tag; + }; + + // C++ wrapper for IfcPump + struct IfcPump : IfcFlowMovingDevice, ObjectHelper<IfcPump,1> { IfcPump() : Object("IfcPump") {} + Maybe< IfcPumpTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcPumpType + struct IfcPumpType : IfcFlowMovingDeviceType, ObjectHelper<IfcPumpType,1> { IfcPumpType() : Object("IfcPumpType") {} + IfcPumpTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcRailing + struct IfcRailing : IfcBuildingElement, ObjectHelper<IfcRailing,1> { IfcRailing() : Object("IfcRailing") {} + Maybe< IfcRailingTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcRailingType + struct IfcRailingType : IfcBuildingElementType, ObjectHelper<IfcRailingType,1> { IfcRailingType() : Object("IfcRailingType") {} + IfcRailingTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcRamp + struct IfcRamp : IfcBuildingElement, ObjectHelper<IfcRamp,1> { IfcRamp() : Object("IfcRamp") {} + Maybe< IfcRampTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcRampFlight + struct IfcRampFlight : IfcBuildingElement, ObjectHelper<IfcRampFlight,1> { IfcRampFlight() : Object("IfcRampFlight") {} + Maybe< IfcRampFlightTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcRampFlightType + struct IfcRampFlightType : IfcBuildingElementType, ObjectHelper<IfcRampFlightType,1> { IfcRampFlightType() : Object("IfcRampFlightType") {} + IfcRampFlightTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcRampType + struct IfcRampType : IfcBuildingElementType, ObjectHelper<IfcRampType,1> { IfcRampType() : Object("IfcRampType") {} + IfcRampTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcRationalBSplineCurveWithKnots + struct IfcRationalBSplineCurveWithKnots : IfcBSplineCurveWithKnots, ObjectHelper<IfcRationalBSplineCurveWithKnots,1> { IfcRationalBSplineCurveWithKnots() : Object("IfcRationalBSplineCurveWithKnots") {} + ListOf< IfcReal, 2, 0 >::Out WeightsData; + }; + + // C++ wrapper for IfcRationalBSplineSurfaceWithKnots + struct IfcRationalBSplineSurfaceWithKnots : IfcBSplineSurfaceWithKnots, ObjectHelper<IfcRationalBSplineSurfaceWithKnots,0> { IfcRationalBSplineSurfaceWithKnots() : Object("IfcRationalBSplineSurfaceWithKnots") {} + + }; + + // C++ wrapper for IfcRectangleProfileDef + struct IfcRectangleProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcRectangleProfileDef,2> { IfcRectangleProfileDef() : Object("IfcRectangleProfileDef") {} + IfcPositiveLengthMeasure::Out XDim; + IfcPositiveLengthMeasure::Out YDim; + }; + + // C++ wrapper for IfcRectangleHollowProfileDef + struct IfcRectangleHollowProfileDef : IfcRectangleProfileDef, ObjectHelper<IfcRectangleHollowProfileDef,3> { IfcRectangleHollowProfileDef() : Object("IfcRectangleHollowProfileDef") {} + IfcPositiveLengthMeasure::Out WallThickness; + Maybe< IfcNonNegativeLengthMeasure::Out > InnerFilletRadius; + Maybe< IfcNonNegativeLengthMeasure::Out > OuterFilletRadius; + }; + + // C++ wrapper for IfcRectangularPyramid + struct IfcRectangularPyramid : IfcCsgPrimitive3D, ObjectHelper<IfcRectangularPyramid,3> { IfcRectangularPyramid() : Object("IfcRectangularPyramid") {} + IfcPositiveLengthMeasure::Out XLength; + IfcPositiveLengthMeasure::Out YLength; + IfcPositiveLengthMeasure::Out Height; + }; + + // C++ wrapper for IfcRectangularTrimmedSurface + struct IfcRectangularTrimmedSurface : IfcBoundedSurface, ObjectHelper<IfcRectangularTrimmedSurface,7> { IfcRectangularTrimmedSurface() : Object("IfcRectangularTrimmedSurface") {} + Lazy< IfcSurface > BasisSurface; + IfcParameterValue::Out U1; + IfcParameterValue::Out V1; + IfcParameterValue::Out U2; + IfcParameterValue::Out V2; + IfcBoolean::Out Usense; + IfcBoolean::Out Vsense; + }; + + // C++ wrapper for IfcReinforcingElement + struct IfcReinforcingElement : IfcElementComponent, ObjectHelper<IfcReinforcingElement,1> { IfcReinforcingElement() : Object("IfcReinforcingElement") {} + Maybe< IfcLabel::Out > SteelGrade; + }; + + // C++ wrapper for IfcReinforcingBar + struct IfcReinforcingBar : IfcReinforcingElement, ObjectHelper<IfcReinforcingBar,5> { IfcReinforcingBar() : Object("IfcReinforcingBar") {} + Maybe< IfcPositiveLengthMeasure::Out > NominalDiameter; + Maybe< IfcAreaMeasure::Out > CrossSectionArea; + Maybe< IfcPositiveLengthMeasure::Out > BarLength; + Maybe< IfcReinforcingBarTypeEnum::Out > PredefinedType; + Maybe< IfcReinforcingBarSurfaceEnum::Out > BarSurface; + }; + + // C++ wrapper for IfcReinforcingElementType + struct IfcReinforcingElementType : IfcElementComponentType, ObjectHelper<IfcReinforcingElementType,0> { IfcReinforcingElementType() : Object("IfcReinforcingElementType") {} + + }; + + // C++ wrapper for IfcReinforcingBarType + struct IfcReinforcingBarType : IfcReinforcingElementType, ObjectHelper<IfcReinforcingBarType,7> { IfcReinforcingBarType() : Object("IfcReinforcingBarType") {} + IfcReinforcingBarTypeEnum::Out PredefinedType; + Maybe< IfcPositiveLengthMeasure::Out > NominalDiameter; + Maybe< IfcAreaMeasure::Out > CrossSectionArea; + Maybe< IfcPositiveLengthMeasure::Out > BarLength; + Maybe< IfcReinforcingBarSurfaceEnum::Out > BarSurface; + Maybe< IfcLabel::Out > BendingShapeCode; + Maybe< ListOf< IfcBendingParameterSelect, 1, 0 >::Out > BendingParameters; + }; + + // C++ wrapper for IfcReinforcingMesh + struct IfcReinforcingMesh : IfcReinforcingElement, ObjectHelper<IfcReinforcingMesh,9> { IfcReinforcingMesh() : Object("IfcReinforcingMesh") {} + Maybe< IfcPositiveLengthMeasure::Out > MeshLength; + Maybe< IfcPositiveLengthMeasure::Out > MeshWidth; + Maybe< IfcPositiveLengthMeasure::Out > LongitudinalBarNominalDiameter; + Maybe< IfcPositiveLengthMeasure::Out > TransverseBarNominalDiameter; + Maybe< IfcAreaMeasure::Out > LongitudinalBarCrossSectionArea; + Maybe< IfcAreaMeasure::Out > TransverseBarCrossSectionArea; + Maybe< IfcPositiveLengthMeasure::Out > LongitudinalBarSpacing; + Maybe< IfcPositiveLengthMeasure::Out > TransverseBarSpacing; + Maybe< IfcReinforcingMeshTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcReinforcingMeshType + struct IfcReinforcingMeshType : IfcReinforcingElementType, ObjectHelper<IfcReinforcingMeshType,11> { IfcReinforcingMeshType() : Object("IfcReinforcingMeshType") {} + IfcReinforcingMeshTypeEnum::Out PredefinedType; + Maybe< IfcPositiveLengthMeasure::Out > MeshLength; + Maybe< IfcPositiveLengthMeasure::Out > MeshWidth; + Maybe< IfcPositiveLengthMeasure::Out > LongitudinalBarNominalDiameter; + Maybe< IfcPositiveLengthMeasure::Out > TransverseBarNominalDiameter; + Maybe< IfcAreaMeasure::Out > LongitudinalBarCrossSectionArea; + Maybe< IfcAreaMeasure::Out > TransverseBarCrossSectionArea; + Maybe< IfcPositiveLengthMeasure::Out > LongitudinalBarSpacing; + Maybe< IfcPositiveLengthMeasure::Out > TransverseBarSpacing; + Maybe< IfcLabel::Out > BendingShapeCode; + Maybe< ListOf< IfcBendingParameterSelect, 1, 0 >::Out > BendingParameters; + }; + + // C++ wrapper for IfcRelationship + struct IfcRelationship : IfcRoot, ObjectHelper<IfcRelationship,0> { IfcRelationship() : Object("IfcRelationship") {} + + }; + + // C++ wrapper for IfcRelDecomposes + struct IfcRelDecomposes : IfcRelationship, ObjectHelper<IfcRelDecomposes,0> { IfcRelDecomposes() : Object("IfcRelDecomposes") {} + + }; + + // C++ wrapper for IfcRelAggregates + struct IfcRelAggregates : IfcRelDecomposes, ObjectHelper<IfcRelAggregates,2> { IfcRelAggregates() : Object("IfcRelAggregates") {} + Lazy< IfcObjectDefinition > RelatingObject; + ListOf< Lazy< IfcObjectDefinition >, 1, 0 > RelatedObjects; + }; + + // C++ wrapper for IfcRelConnects + struct IfcRelConnects : IfcRelationship, ObjectHelper<IfcRelConnects,0> { IfcRelConnects() : Object("IfcRelConnects") {} + + }; + + // C++ wrapper for IfcRelContainedInSpatialStructure + struct IfcRelContainedInSpatialStructure : IfcRelConnects, ObjectHelper<IfcRelContainedInSpatialStructure,2> { IfcRelContainedInSpatialStructure() : Object("IfcRelContainedInSpatialStructure") {} + ListOf< Lazy< IfcProduct >, 1, 0 > RelatedElements; + Lazy< IfcSpatialElement > RelatingStructure; + }; + + // C++ wrapper for IfcRelDefines + struct IfcRelDefines : IfcRelationship, ObjectHelper<IfcRelDefines,0> { IfcRelDefines() : Object("IfcRelDefines") {} + + }; + + // C++ wrapper for IfcRelDefinesByProperties + struct IfcRelDefinesByProperties : IfcRelDefines, ObjectHelper<IfcRelDefinesByProperties,2> { IfcRelDefinesByProperties() : Object("IfcRelDefinesByProperties") {} + ListOf< Lazy< IfcObjectDefinition >, 1, 0 > RelatedObjects; + IfcPropertySetDefinitionSelect::Out RelatingPropertyDefinition; + }; + + // C++ wrapper for IfcRelFillsElement + struct IfcRelFillsElement : IfcRelConnects, ObjectHelper<IfcRelFillsElement,2> { IfcRelFillsElement() : Object("IfcRelFillsElement") {} + Lazy< IfcOpeningElement > RelatingOpeningElement; + Lazy< IfcElement > RelatedBuildingElement; + }; + + // C++ wrapper for IfcRelVoidsElement + struct IfcRelVoidsElement : IfcRelDecomposes, ObjectHelper<IfcRelVoidsElement,2> { IfcRelVoidsElement() : Object("IfcRelVoidsElement") {} + Lazy< IfcElement > RelatingBuildingElement; + Lazy< IfcFeatureElementSubtraction > RelatedOpeningElement; + }; + + // C++ wrapper for IfcReparametrisedCompositeCurveSegment + struct IfcReparametrisedCompositeCurveSegment : IfcCompositeCurveSegment, ObjectHelper<IfcReparametrisedCompositeCurveSegment,1> { IfcReparametrisedCompositeCurveSegment() : Object("IfcReparametrisedCompositeCurveSegment") {} + IfcParameterValue::Out ParamLength; + }; + + // C++ wrapper for IfcRepresentation + struct IfcRepresentation : ObjectHelper<IfcRepresentation,4> { IfcRepresentation() : Object("IfcRepresentation") {} + Lazy< IfcRepresentationContext > ContextOfItems; + Maybe< IfcLabel::Out > RepresentationIdentifier; + Maybe< IfcLabel::Out > RepresentationType; + ListOf< Lazy< IfcRepresentationItem >, 1, 0 > Items; + }; + + // C++ wrapper for IfcRepresentationMap + struct IfcRepresentationMap : ObjectHelper<IfcRepresentationMap,2> { IfcRepresentationMap() : Object("IfcRepresentationMap") {} + IfcAxis2Placement::Out MappingOrigin; + Lazy< IfcRepresentation > MappedRepresentation; + }; + + // C++ wrapper for IfcRevolvedAreaSolid + struct IfcRevolvedAreaSolid : IfcSweptAreaSolid, ObjectHelper<IfcRevolvedAreaSolid,2> { IfcRevolvedAreaSolid() : Object("IfcRevolvedAreaSolid") {} + Lazy< IfcAxis1Placement > Axis; + IfcPlaneAngleMeasure::Out Angle; + }; + + // C++ wrapper for IfcRevolvedAreaSolidTapered + struct IfcRevolvedAreaSolidTapered : IfcRevolvedAreaSolid, ObjectHelper<IfcRevolvedAreaSolidTapered,1> { IfcRevolvedAreaSolidTapered() : Object("IfcRevolvedAreaSolidTapered") {} + Lazy< IfcProfileDef > EndSweptArea; + }; + + // C++ wrapper for IfcRightCircularCone + struct IfcRightCircularCone : IfcCsgPrimitive3D, ObjectHelper<IfcRightCircularCone,2> { IfcRightCircularCone() : Object("IfcRightCircularCone") {} + IfcPositiveLengthMeasure::Out Height; + IfcPositiveLengthMeasure::Out BottomRadius; + }; + + // C++ wrapper for IfcRightCircularCylinder + struct IfcRightCircularCylinder : IfcCsgPrimitive3D, ObjectHelper<IfcRightCircularCylinder,2> { IfcRightCircularCylinder() : Object("IfcRightCircularCylinder") {} + IfcPositiveLengthMeasure::Out Height; + IfcPositiveLengthMeasure::Out Radius; + }; + + // C++ wrapper for IfcRoof + struct IfcRoof : IfcBuildingElement, ObjectHelper<IfcRoof,1> { IfcRoof() : Object("IfcRoof") {} + Maybe< IfcRoofTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcRoofType + struct IfcRoofType : IfcBuildingElementType, ObjectHelper<IfcRoofType,1> { IfcRoofType() : Object("IfcRoofType") {} + IfcRoofTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcRoundedRectangleProfileDef + struct IfcRoundedRectangleProfileDef : IfcRectangleProfileDef, ObjectHelper<IfcRoundedRectangleProfileDef,1> { IfcRoundedRectangleProfileDef() : Object("IfcRoundedRectangleProfileDef") {} + IfcPositiveLengthMeasure::Out RoundingRadius; + }; + + // C++ wrapper for IfcSIUnit + struct IfcSIUnit : IfcNamedUnit, ObjectHelper<IfcSIUnit,2> { IfcSIUnit() : Object("IfcSIUnit") {} + Maybe< IfcSIPrefix::Out > Prefix; + IfcSIUnitName::Out Name; + }; + + // C++ wrapper for IfcSanitaryTerminal + struct IfcSanitaryTerminal : IfcFlowTerminal, ObjectHelper<IfcSanitaryTerminal,1> { IfcSanitaryTerminal() : Object("IfcSanitaryTerminal") {} + Maybe< IfcSanitaryTerminalTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcSanitaryTerminalType + struct IfcSanitaryTerminalType : IfcFlowTerminalType, ObjectHelper<IfcSanitaryTerminalType,1> { IfcSanitaryTerminalType() : Object("IfcSanitaryTerminalType") {} + IfcSanitaryTerminalTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcSeamCurve + struct IfcSeamCurve : IfcSurfaceCurve, ObjectHelper<IfcSeamCurve,0> { IfcSeamCurve() : Object("IfcSeamCurve") {} + + }; + + // C++ wrapper for IfcSectionedSpine + struct IfcSectionedSpine : IfcGeometricRepresentationItem, ObjectHelper<IfcSectionedSpine,3> { IfcSectionedSpine() : Object("IfcSectionedSpine") {} + Lazy< IfcCompositeCurve > SpineCurve; + ListOf< Lazy< IfcProfileDef >, 2, 0 > CrossSections; + ListOf< Lazy< IfcAxis2Placement3D >, 2, 0 > CrossSectionPositions; + }; + + // C++ wrapper for IfcSensor + struct IfcSensor : IfcDistributionControlElement, ObjectHelper<IfcSensor,1> { IfcSensor() : Object("IfcSensor") {} + Maybe< IfcSensorTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcSensorType + struct IfcSensorType : IfcDistributionControlElementType, ObjectHelper<IfcSensorType,1> { IfcSensorType() : Object("IfcSensorType") {} + IfcSensorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcShadingDevice + struct IfcShadingDevice : IfcBuildingElement, ObjectHelper<IfcShadingDevice,1> { IfcShadingDevice() : Object("IfcShadingDevice") {} + Maybe< IfcShadingDeviceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcShadingDeviceType + struct IfcShadingDeviceType : IfcBuildingElementType, ObjectHelper<IfcShadingDeviceType,1> { IfcShadingDeviceType() : Object("IfcShadingDeviceType") {} + IfcShadingDeviceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcShapeModel + struct IfcShapeModel : IfcRepresentation, ObjectHelper<IfcShapeModel,0> { IfcShapeModel() : Object("IfcShapeModel") {} + + }; + + // C++ wrapper for IfcShapeRepresentation + struct IfcShapeRepresentation : IfcShapeModel, ObjectHelper<IfcShapeRepresentation,0> { IfcShapeRepresentation() : Object("IfcShapeRepresentation") {} + + }; + + // C++ wrapper for IfcShellBasedSurfaceModel + struct IfcShellBasedSurfaceModel : IfcGeometricRepresentationItem, ObjectHelper<IfcShellBasedSurfaceModel,1> { IfcShellBasedSurfaceModel() : Object("IfcShellBasedSurfaceModel") {} + ListOf< IfcShell, 1, 0 >::Out SbsmBoundary; + }; + + // C++ wrapper for IfcSite + struct IfcSite : IfcSpatialStructureElement, ObjectHelper<IfcSite,5> { IfcSite() : Object("IfcSite") {} + Maybe< IfcCompoundPlaneAngleMeasure::Out > RefLatitude; + Maybe< IfcCompoundPlaneAngleMeasure::Out > RefLongitude; + Maybe< IfcLengthMeasure::Out > RefElevation; + Maybe< IfcLabel::Out > LandTitleNumber; + Maybe< Lazy< NotImplemented > > SiteAddress; + }; + + // C++ wrapper for IfcSlab + struct IfcSlab : IfcBuildingElement, ObjectHelper<IfcSlab,1> { IfcSlab() : Object("IfcSlab") {} + Maybe< IfcSlabTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcSlabElementedCase + struct IfcSlabElementedCase : IfcSlab, ObjectHelper<IfcSlabElementedCase,0> { IfcSlabElementedCase() : Object("IfcSlabElementedCase") {} + + }; + + // C++ wrapper for IfcSlabStandardCase + struct IfcSlabStandardCase : IfcSlab, ObjectHelper<IfcSlabStandardCase,0> { IfcSlabStandardCase() : Object("IfcSlabStandardCase") {} + + }; + + // C++ wrapper for IfcSlabType + struct IfcSlabType : IfcBuildingElementType, ObjectHelper<IfcSlabType,1> { IfcSlabType() : Object("IfcSlabType") {} + IfcSlabTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcSolarDevice + struct IfcSolarDevice : IfcEnergyConversionDevice, ObjectHelper<IfcSolarDevice,1> { IfcSolarDevice() : Object("IfcSolarDevice") {} + Maybe< IfcSolarDeviceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcSolarDeviceType + struct IfcSolarDeviceType : IfcEnergyConversionDeviceType, ObjectHelper<IfcSolarDeviceType,1> { IfcSolarDeviceType() : Object("IfcSolarDeviceType") {} + IfcSolarDeviceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcSpace + struct IfcSpace : IfcSpatialStructureElement, ObjectHelper<IfcSpace,2> { IfcSpace() : Object("IfcSpace") {} + Maybe< IfcSpaceTypeEnum::Out > PredefinedType; + Maybe< IfcLengthMeasure::Out > ElevationWithFlooring; + }; + + // C++ wrapper for IfcSpaceHeater + struct IfcSpaceHeater : IfcFlowTerminal, ObjectHelper<IfcSpaceHeater,1> { IfcSpaceHeater() : Object("IfcSpaceHeater") {} + Maybe< IfcSpaceHeaterTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcSpaceHeaterType + struct IfcSpaceHeaterType : IfcFlowTerminalType, ObjectHelper<IfcSpaceHeaterType,1> { IfcSpaceHeaterType() : Object("IfcSpaceHeaterType") {} + IfcSpaceHeaterTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcSpatialElementType + struct IfcSpatialElementType : IfcTypeProduct, ObjectHelper<IfcSpatialElementType,1> { IfcSpatialElementType() : Object("IfcSpatialElementType") {} + Maybe< IfcLabel::Out > ElementType; + }; + + // C++ wrapper for IfcSpatialStructureElementType + struct IfcSpatialStructureElementType : IfcSpatialElementType, ObjectHelper<IfcSpatialStructureElementType,0> { IfcSpatialStructureElementType() : Object("IfcSpatialStructureElementType") {} + + }; + + // C++ wrapper for IfcSpaceType + struct IfcSpaceType : IfcSpatialStructureElementType, ObjectHelper<IfcSpaceType,2> { IfcSpaceType() : Object("IfcSpaceType") {} + IfcSpaceTypeEnum::Out PredefinedType; + Maybe< IfcLabel::Out > LongName; + }; + + // C++ wrapper for IfcSpatialZone + struct IfcSpatialZone : IfcSpatialElement, ObjectHelper<IfcSpatialZone,1> { IfcSpatialZone() : Object("IfcSpatialZone") {} + Maybe< IfcSpatialZoneTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcSpatialZoneType + struct IfcSpatialZoneType : IfcSpatialElementType, ObjectHelper<IfcSpatialZoneType,2> { IfcSpatialZoneType() : Object("IfcSpatialZoneType") {} + IfcSpatialZoneTypeEnum::Out PredefinedType; + Maybe< IfcLabel::Out > LongName; + }; + + // C++ wrapper for IfcSphere + struct IfcSphere : IfcCsgPrimitive3D, ObjectHelper<IfcSphere,1> { IfcSphere() : Object("IfcSphere") {} + IfcPositiveLengthMeasure::Out Radius; + }; + + // C++ wrapper for IfcSphericalSurface + struct IfcSphericalSurface : IfcElementarySurface, ObjectHelper<IfcSphericalSurface,1> { IfcSphericalSurface() : Object("IfcSphericalSurface") {} + IfcPositiveLengthMeasure::Out Radius; + }; + + // C++ wrapper for IfcStackTerminal + struct IfcStackTerminal : IfcFlowTerminal, ObjectHelper<IfcStackTerminal,1> { IfcStackTerminal() : Object("IfcStackTerminal") {} + Maybe< IfcStackTerminalTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcStackTerminalType + struct IfcStackTerminalType : IfcFlowTerminalType, ObjectHelper<IfcStackTerminalType,1> { IfcStackTerminalType() : Object("IfcStackTerminalType") {} + IfcStackTerminalTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcStair + struct IfcStair : IfcBuildingElement, ObjectHelper<IfcStair,1> { IfcStair() : Object("IfcStair") {} + Maybe< IfcStairTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcStairFlight + struct IfcStairFlight : IfcBuildingElement, ObjectHelper<IfcStairFlight,5> { IfcStairFlight() : Object("IfcStairFlight") {} + Maybe< IfcInteger::Out > NumberOfRisers; + Maybe< IfcInteger::Out > NumberOfTreads; + Maybe< IfcPositiveLengthMeasure::Out > RiserHeight; + Maybe< IfcPositiveLengthMeasure::Out > TreadLength; + Maybe< IfcStairFlightTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcStairFlightType + struct IfcStairFlightType : IfcBuildingElementType, ObjectHelper<IfcStairFlightType,1> { IfcStairFlightType() : Object("IfcStairFlightType") {} + IfcStairFlightTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcStairType + struct IfcStairType : IfcBuildingElementType, ObjectHelper<IfcStairType,1> { IfcStairType() : Object("IfcStairType") {} + IfcStairTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcStructuralActivity + struct IfcStructuralActivity : IfcProduct, ObjectHelper<IfcStructuralActivity,2> { IfcStructuralActivity() : Object("IfcStructuralActivity") {} + Lazy< NotImplemented > AppliedLoad; + IfcGlobalOrLocalEnum::Out GlobalOrLocal; + }; + + // C++ wrapper for IfcStructuralAction + struct IfcStructuralAction : IfcStructuralActivity, ObjectHelper<IfcStructuralAction,1> { IfcStructuralAction() : Object("IfcStructuralAction") {} + Maybe< IfcBoolean::Out > DestabilizingLoad; + }; + + // C++ wrapper for IfcStructuralAnalysisModel + struct IfcStructuralAnalysisModel : IfcSystem, ObjectHelper<IfcStructuralAnalysisModel,5> { IfcStructuralAnalysisModel() : Object("IfcStructuralAnalysisModel") {} + IfcAnalysisModelTypeEnum::Out PredefinedType; + Maybe< Lazy< IfcAxis2Placement3D > > OrientationOf2DPlane; + Maybe< ListOf< Lazy< IfcStructuralLoadGroup >, 1, 0 > > LoadedBy; + Maybe< ListOf< Lazy< IfcStructuralResultGroup >, 1, 0 > > HasResults; + Maybe< Lazy< IfcObjectPlacement > > SharedPlacement; + }; + + // C++ wrapper for IfcStructuralItem + struct IfcStructuralItem : IfcProduct, ObjectHelper<IfcStructuralItem,0> { IfcStructuralItem() : Object("IfcStructuralItem") {} + + }; + + // C++ wrapper for IfcStructuralConnection + struct IfcStructuralConnection : IfcStructuralItem, ObjectHelper<IfcStructuralConnection,1> { IfcStructuralConnection() : Object("IfcStructuralConnection") {} + Maybe< Lazy< NotImplemented > > AppliedCondition; + }; + + // C++ wrapper for IfcStructuralCurveAction + struct IfcStructuralCurveAction : IfcStructuralAction, ObjectHelper<IfcStructuralCurveAction,2> { IfcStructuralCurveAction() : Object("IfcStructuralCurveAction") {} + Maybe< IfcProjectedOrTrueLengthEnum::Out > ProjectedOrTrue; + IfcStructuralCurveActivityTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcStructuralCurveConnection + struct IfcStructuralCurveConnection : IfcStructuralConnection, ObjectHelper<IfcStructuralCurveConnection,1> { IfcStructuralCurveConnection() : Object("IfcStructuralCurveConnection") {} + Lazy< IfcDirection > Axis; + }; + + // C++ wrapper for IfcStructuralMember + struct IfcStructuralMember : IfcStructuralItem, ObjectHelper<IfcStructuralMember,0> { IfcStructuralMember() : Object("IfcStructuralMember") {} + + }; + + // C++ wrapper for IfcStructuralCurveMember + struct IfcStructuralCurveMember : IfcStructuralMember, ObjectHelper<IfcStructuralCurveMember,2> { IfcStructuralCurveMember() : Object("IfcStructuralCurveMember") {} + IfcStructuralCurveMemberTypeEnum::Out PredefinedType; + Lazy< IfcDirection > Axis; + }; + + // C++ wrapper for IfcStructuralCurveMemberVarying + struct IfcStructuralCurveMemberVarying : IfcStructuralCurveMember, ObjectHelper<IfcStructuralCurveMemberVarying,0> { IfcStructuralCurveMemberVarying() : Object("IfcStructuralCurveMemberVarying") {} + + }; + + // C++ wrapper for IfcStructuralReaction + struct IfcStructuralReaction : IfcStructuralActivity, ObjectHelper<IfcStructuralReaction,0> { IfcStructuralReaction() : Object("IfcStructuralReaction") {} + + }; + + // C++ wrapper for IfcStructuralCurveReaction + struct IfcStructuralCurveReaction : IfcStructuralReaction, ObjectHelper<IfcStructuralCurveReaction,1> { IfcStructuralCurveReaction() : Object("IfcStructuralCurveReaction") {} + IfcStructuralCurveActivityTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcStructuralLinearAction + struct IfcStructuralLinearAction : IfcStructuralCurveAction, ObjectHelper<IfcStructuralLinearAction,0> { IfcStructuralLinearAction() : Object("IfcStructuralLinearAction") {} + + }; + + // C++ wrapper for IfcStructuralLoadGroup + struct IfcStructuralLoadGroup : IfcGroup, ObjectHelper<IfcStructuralLoadGroup,5> { IfcStructuralLoadGroup() : Object("IfcStructuralLoadGroup") {} + IfcLoadGroupTypeEnum::Out PredefinedType; + IfcActionTypeEnum::Out ActionType; + IfcActionSourceTypeEnum::Out ActionSource; + Maybe< IfcRatioMeasure::Out > Coefficient; + Maybe< IfcLabel::Out > Purpose; + }; + + // C++ wrapper for IfcStructuralLoadCase + struct IfcStructuralLoadCase : IfcStructuralLoadGroup, ObjectHelper<IfcStructuralLoadCase,1> { IfcStructuralLoadCase() : Object("IfcStructuralLoadCase") {} + Maybe< ListOf< IfcRatioMeasure, 3, 3 >::Out > SelfWeightCoefficients; + }; + + // C++ wrapper for IfcStructuralSurfaceAction + struct IfcStructuralSurfaceAction : IfcStructuralAction, ObjectHelper<IfcStructuralSurfaceAction,2> { IfcStructuralSurfaceAction() : Object("IfcStructuralSurfaceAction") {} + Maybe< IfcProjectedOrTrueLengthEnum::Out > ProjectedOrTrue; + IfcStructuralSurfaceActivityTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcStructuralPlanarAction + struct IfcStructuralPlanarAction : IfcStructuralSurfaceAction, ObjectHelper<IfcStructuralPlanarAction,0> { IfcStructuralPlanarAction() : Object("IfcStructuralPlanarAction") {} + + }; + + // C++ wrapper for IfcStructuralPointAction + struct IfcStructuralPointAction : IfcStructuralAction, ObjectHelper<IfcStructuralPointAction,0> { IfcStructuralPointAction() : Object("IfcStructuralPointAction") {} + + }; + + // C++ wrapper for IfcStructuralPointConnection + struct IfcStructuralPointConnection : IfcStructuralConnection, ObjectHelper<IfcStructuralPointConnection,1> { IfcStructuralPointConnection() : Object("IfcStructuralPointConnection") {} + Maybe< Lazy< IfcAxis2Placement3D > > ConditionCoordinateSystem; + }; + + // C++ wrapper for IfcStructuralPointReaction + struct IfcStructuralPointReaction : IfcStructuralReaction, ObjectHelper<IfcStructuralPointReaction,0> { IfcStructuralPointReaction() : Object("IfcStructuralPointReaction") {} + + }; + + // C++ wrapper for IfcStructuralResultGroup + struct IfcStructuralResultGroup : IfcGroup, ObjectHelper<IfcStructuralResultGroup,3> { IfcStructuralResultGroup() : Object("IfcStructuralResultGroup") {} + IfcAnalysisTheoryTypeEnum::Out TheoryType; + Maybe< Lazy< IfcStructuralLoadGroup > > ResultForLoadGroup; + IfcBoolean::Out IsLinear; + }; + + // C++ wrapper for IfcStructuralSurfaceConnection + struct IfcStructuralSurfaceConnection : IfcStructuralConnection, ObjectHelper<IfcStructuralSurfaceConnection,0> { IfcStructuralSurfaceConnection() : Object("IfcStructuralSurfaceConnection") {} + + }; + + // C++ wrapper for IfcStructuralSurfaceMember + struct IfcStructuralSurfaceMember : IfcStructuralMember, ObjectHelper<IfcStructuralSurfaceMember,2> { IfcStructuralSurfaceMember() : Object("IfcStructuralSurfaceMember") {} + IfcStructuralSurfaceMemberTypeEnum::Out PredefinedType; + Maybe< IfcPositiveLengthMeasure::Out > Thickness; + }; + + // C++ wrapper for IfcStructuralSurfaceMemberVarying + struct IfcStructuralSurfaceMemberVarying : IfcStructuralSurfaceMember, ObjectHelper<IfcStructuralSurfaceMemberVarying,0> { IfcStructuralSurfaceMemberVarying() : Object("IfcStructuralSurfaceMemberVarying") {} + + }; + + // C++ wrapper for IfcStructuralSurfaceReaction + struct IfcStructuralSurfaceReaction : IfcStructuralReaction, ObjectHelper<IfcStructuralSurfaceReaction,1> { IfcStructuralSurfaceReaction() : Object("IfcStructuralSurfaceReaction") {} + IfcStructuralSurfaceActivityTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcStyleModel + struct IfcStyleModel : IfcRepresentation, ObjectHelper<IfcStyleModel,0> { IfcStyleModel() : Object("IfcStyleModel") {} + + }; + + // C++ wrapper for IfcStyledItem + struct IfcStyledItem : IfcRepresentationItem, ObjectHelper<IfcStyledItem,3> { IfcStyledItem() : Object("IfcStyledItem") {} + Maybe< Lazy< IfcRepresentationItem > > Item; + ListOf< IfcStyleAssignmentSelect, 1, 0 >::Out Styles; + Maybe< IfcLabel::Out > Name; + }; + + // C++ wrapper for IfcStyledRepresentation + struct IfcStyledRepresentation : IfcStyleModel, ObjectHelper<IfcStyledRepresentation,0> { IfcStyledRepresentation() : Object("IfcStyledRepresentation") {} + + }; + + // C++ wrapper for IfcSubContractResource + struct IfcSubContractResource : IfcConstructionResource, ObjectHelper<IfcSubContractResource,1> { IfcSubContractResource() : Object("IfcSubContractResource") {} + Maybe< IfcSubContractResourceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcSubContractResourceType + struct IfcSubContractResourceType : IfcConstructionResourceType, ObjectHelper<IfcSubContractResourceType,1> { IfcSubContractResourceType() : Object("IfcSubContractResourceType") {} + IfcSubContractResourceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcSubedge + struct IfcSubedge : IfcEdge, ObjectHelper<IfcSubedge,1> { IfcSubedge() : Object("IfcSubedge") {} + Lazy< IfcEdge > ParentEdge; + }; + + // C++ wrapper for IfcSurfaceCurveSweptAreaSolid + struct IfcSurfaceCurveSweptAreaSolid : IfcSweptAreaSolid, ObjectHelper<IfcSurfaceCurveSweptAreaSolid,4> { IfcSurfaceCurveSweptAreaSolid() : Object("IfcSurfaceCurveSweptAreaSolid") {} + Lazy< IfcCurve > Directrix; + Maybe< IfcParameterValue::Out > StartParam; + Maybe< IfcParameterValue::Out > EndParam; + Lazy< IfcSurface > ReferenceSurface; + }; + + // C++ wrapper for IfcSurfaceFeature + struct IfcSurfaceFeature : IfcFeatureElement, ObjectHelper<IfcSurfaceFeature,1> { IfcSurfaceFeature() : Object("IfcSurfaceFeature") {} + Maybe< IfcSurfaceFeatureTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcSweptSurface + struct IfcSweptSurface : IfcSurface, ObjectHelper<IfcSweptSurface,2> { IfcSweptSurface() : Object("IfcSweptSurface") {} + Lazy< IfcProfileDef > SweptCurve; + Maybe< Lazy< IfcAxis2Placement3D > > Position; + }; + + // C++ wrapper for IfcSurfaceOfLinearExtrusion + struct IfcSurfaceOfLinearExtrusion : IfcSweptSurface, ObjectHelper<IfcSurfaceOfLinearExtrusion,2> { IfcSurfaceOfLinearExtrusion() : Object("IfcSurfaceOfLinearExtrusion") {} + Lazy< IfcDirection > ExtrudedDirection; + IfcLengthMeasure::Out Depth; + }; + + // C++ wrapper for IfcSurfaceOfRevolution + struct IfcSurfaceOfRevolution : IfcSweptSurface, ObjectHelper<IfcSurfaceOfRevolution,1> { IfcSurfaceOfRevolution() : Object("IfcSurfaceOfRevolution") {} + Lazy< IfcAxis1Placement > AxisPosition; + }; + + // C++ wrapper for IfcSurfaceStyle + struct IfcSurfaceStyle : IfcPresentationStyle, ObjectHelper<IfcSurfaceStyle,2> { IfcSurfaceStyle() : Object("IfcSurfaceStyle") {} + IfcSurfaceSide::Out Side; + ListOf< IfcSurfaceStyleElementSelect, 1, 5 >::Out Styles; + }; + + // C++ wrapper for IfcSurfaceStyleShading + struct IfcSurfaceStyleShading : IfcPresentationItem, ObjectHelper<IfcSurfaceStyleShading,2> { IfcSurfaceStyleShading() : Object("IfcSurfaceStyleShading") {} + Lazy< IfcColourRgb > SurfaceColour; + Maybe< IfcNormalisedRatioMeasure::Out > Transparency; + }; + + // C++ wrapper for IfcSurfaceStyleRendering + struct IfcSurfaceStyleRendering : IfcSurfaceStyleShading, ObjectHelper<IfcSurfaceStyleRendering,7> { IfcSurfaceStyleRendering() : Object("IfcSurfaceStyleRendering") {} + Maybe< IfcColourOrFactor::Out > DiffuseColour; + Maybe< IfcColourOrFactor::Out > TransmissionColour; + Maybe< IfcColourOrFactor::Out > DiffuseTransmissionColour; + Maybe< IfcColourOrFactor::Out > ReflectionColour; + Maybe< IfcColourOrFactor::Out > SpecularColour; + Maybe< IfcSpecularHighlightSelect::Out > SpecularHighlight; + IfcReflectanceMethodEnum::Out ReflectanceMethod; + }; + + // C++ wrapper for IfcSurfaceStyleWithTextures + struct IfcSurfaceStyleWithTextures : IfcPresentationItem, ObjectHelper<IfcSurfaceStyleWithTextures,1> { IfcSurfaceStyleWithTextures() : Object("IfcSurfaceStyleWithTextures") {} + ListOf< Lazy< NotImplemented >, 1, 0 > Textures; + }; + + // C++ wrapper for IfcSweptDiskSolid + struct IfcSweptDiskSolid : IfcSolidModel, ObjectHelper<IfcSweptDiskSolid,5> { IfcSweptDiskSolid() : Object("IfcSweptDiskSolid") {} + Lazy< IfcCurve > Directrix; + IfcPositiveLengthMeasure::Out Radius; + Maybe< IfcPositiveLengthMeasure::Out > InnerRadius; + Maybe< IfcParameterValue::Out > StartParam; + Maybe< IfcParameterValue::Out > EndParam; + }; + + // C++ wrapper for IfcSweptDiskSolidPolygonal + struct IfcSweptDiskSolidPolygonal : IfcSweptDiskSolid, ObjectHelper<IfcSweptDiskSolidPolygonal,1> { IfcSweptDiskSolidPolygonal() : Object("IfcSweptDiskSolidPolygonal") {} + Maybe< IfcPositiveLengthMeasure::Out > FilletRadius; + }; + + // C++ wrapper for IfcSwitchingDevice + struct IfcSwitchingDevice : IfcFlowController, ObjectHelper<IfcSwitchingDevice,1> { IfcSwitchingDevice() : Object("IfcSwitchingDevice") {} + Maybe< IfcSwitchingDeviceTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcSwitchingDeviceType + struct IfcSwitchingDeviceType : IfcFlowControllerType, ObjectHelper<IfcSwitchingDeviceType,1> { IfcSwitchingDeviceType() : Object("IfcSwitchingDeviceType") {} + IfcSwitchingDeviceTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcSystemFurnitureElement + struct IfcSystemFurnitureElement : IfcFurnishingElement, ObjectHelper<IfcSystemFurnitureElement,1> { IfcSystemFurnitureElement() : Object("IfcSystemFurnitureElement") {} + Maybe< IfcSystemFurnitureElementTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcSystemFurnitureElementType + struct IfcSystemFurnitureElementType : IfcFurnishingElementType, ObjectHelper<IfcSystemFurnitureElementType,1> { IfcSystemFurnitureElementType() : Object("IfcSystemFurnitureElementType") {} + Maybe< IfcSystemFurnitureElementTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcTShapeProfileDef + struct IfcTShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcTShapeProfileDef,9> { IfcTShapeProfileDef() : Object("IfcTShapeProfileDef") {} + IfcPositiveLengthMeasure::Out Depth; + IfcPositiveLengthMeasure::Out FlangeWidth; + IfcPositiveLengthMeasure::Out WebThickness; + IfcPositiveLengthMeasure::Out FlangeThickness; + Maybe< IfcNonNegativeLengthMeasure::Out > FilletRadius; + Maybe< IfcNonNegativeLengthMeasure::Out > FlangeEdgeRadius; + Maybe< IfcNonNegativeLengthMeasure::Out > WebEdgeRadius; + Maybe< IfcPlaneAngleMeasure::Out > WebSlope; + Maybe< IfcPlaneAngleMeasure::Out > FlangeSlope; + }; + + // C++ wrapper for IfcTank + struct IfcTank : IfcFlowStorageDevice, ObjectHelper<IfcTank,1> { IfcTank() : Object("IfcTank") {} + Maybe< IfcTankTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcTankType + struct IfcTankType : IfcFlowStorageDeviceType, ObjectHelper<IfcTankType,1> { IfcTankType() : Object("IfcTankType") {} + IfcTankTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcTask + struct IfcTask : IfcProcess, ObjectHelper<IfcTask,6> { IfcTask() : Object("IfcTask") {} + Maybe< IfcLabel::Out > Status; + Maybe< IfcLabel::Out > WorkMethod; + IfcBoolean::Out IsMilestone; + Maybe< IfcInteger::Out > Priority; + Maybe< Lazy< NotImplemented > > TaskTime; + Maybe< IfcTaskTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcTaskType + struct IfcTaskType : IfcTypeProcess, ObjectHelper<IfcTaskType,2> { IfcTaskType() : Object("IfcTaskType") {} + IfcTaskTypeEnum::Out PredefinedType; + Maybe< IfcLabel::Out > WorkMethod; + }; + + // C++ wrapper for IfcTendon + struct IfcTendon : IfcReinforcingElement, ObjectHelper<IfcTendon,8> { IfcTendon() : Object("IfcTendon") {} + Maybe< IfcTendonTypeEnum::Out > PredefinedType; + Maybe< IfcPositiveLengthMeasure::Out > NominalDiameter; + Maybe< IfcAreaMeasure::Out > CrossSectionArea; + Maybe< IfcForceMeasure::Out > TensionForce; + Maybe< IfcPressureMeasure::Out > PreStress; + Maybe< IfcNormalisedRatioMeasure::Out > FrictionCoefficient; + Maybe< IfcPositiveLengthMeasure::Out > AnchorageSlip; + Maybe< IfcPositiveLengthMeasure::Out > MinCurvatureRadius; + }; + + // C++ wrapper for IfcTendonAnchor + struct IfcTendonAnchor : IfcReinforcingElement, ObjectHelper<IfcTendonAnchor,1> { IfcTendonAnchor() : Object("IfcTendonAnchor") {} + Maybe< IfcTendonAnchorTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcTendonAnchorType + struct IfcTendonAnchorType : IfcReinforcingElementType, ObjectHelper<IfcTendonAnchorType,1> { IfcTendonAnchorType() : Object("IfcTendonAnchorType") {} + IfcTendonAnchorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcTendonType + struct IfcTendonType : IfcReinforcingElementType, ObjectHelper<IfcTendonType,4> { IfcTendonType() : Object("IfcTendonType") {} + IfcTendonTypeEnum::Out PredefinedType; + Maybe< IfcPositiveLengthMeasure::Out > NominalDiameter; + Maybe< IfcAreaMeasure::Out > CrossSectionArea; + Maybe< IfcPositiveLengthMeasure::Out > SheathDiameter; + }; + + // C++ wrapper for IfcTextLiteral + struct IfcTextLiteral : IfcGeometricRepresentationItem, ObjectHelper<IfcTextLiteral,3> { IfcTextLiteral() : Object("IfcTextLiteral") {} + IfcPresentableText::Out Literal; + IfcAxis2Placement::Out Placement; + IfcTextPath::Out Path; + }; + + // C++ wrapper for IfcTextLiteralWithExtent + struct IfcTextLiteralWithExtent : IfcTextLiteral, ObjectHelper<IfcTextLiteralWithExtent,2> { IfcTextLiteralWithExtent() : Object("IfcTextLiteralWithExtent") {} + Lazy< IfcPlanarExtent > Extent; + IfcBoxAlignment::Out BoxAlignment; + }; + + // C++ wrapper for IfcTopologyRepresentation + struct IfcTopologyRepresentation : IfcShapeModel, ObjectHelper<IfcTopologyRepresentation,0> { IfcTopologyRepresentation() : Object("IfcTopologyRepresentation") {} + + }; + + // C++ wrapper for IfcToroidalSurface + struct IfcToroidalSurface : IfcElementarySurface, ObjectHelper<IfcToroidalSurface,2> { IfcToroidalSurface() : Object("IfcToroidalSurface") {} + IfcPositiveLengthMeasure::Out MajorRadius; + IfcPositiveLengthMeasure::Out MinorRadius; + }; + + // C++ wrapper for IfcTransformer + struct IfcTransformer : IfcEnergyConversionDevice, ObjectHelper<IfcTransformer,1> { IfcTransformer() : Object("IfcTransformer") {} + Maybe< IfcTransformerTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcTransformerType + struct IfcTransformerType : IfcEnergyConversionDeviceType, ObjectHelper<IfcTransformerType,1> { IfcTransformerType() : Object("IfcTransformerType") {} + IfcTransformerTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcTransportElement + struct IfcTransportElement : IfcElement, ObjectHelper<IfcTransportElement,1> { IfcTransportElement() : Object("IfcTransportElement") {} + Maybe< IfcTransportElementTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcTransportElementType + struct IfcTransportElementType : IfcElementType, ObjectHelper<IfcTransportElementType,1> { IfcTransportElementType() : Object("IfcTransportElementType") {} + IfcTransportElementTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcTrapeziumProfileDef + struct IfcTrapeziumProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcTrapeziumProfileDef,4> { IfcTrapeziumProfileDef() : Object("IfcTrapeziumProfileDef") {} + IfcPositiveLengthMeasure::Out BottomXDim; + IfcPositiveLengthMeasure::Out TopXDim; + IfcPositiveLengthMeasure::Out YDim; + IfcLengthMeasure::Out TopXOffset; + }; + + // C++ wrapper for IfcTriangulatedFaceSet + struct IfcTriangulatedFaceSet : IfcTessellatedFaceSet, ObjectHelper<IfcTriangulatedFaceSet,2> { IfcTriangulatedFaceSet() : Object("IfcTriangulatedFaceSet") {} + Maybe< IfcBoolean::Out > Closed; + Maybe< ListOf< IfcPositiveInteger, 1, 0 >::Out > PnIndex; + }; + + // C++ wrapper for IfcTrimmedCurve + struct IfcTrimmedCurve : IfcBoundedCurve, ObjectHelper<IfcTrimmedCurve,5> { IfcTrimmedCurve() : Object("IfcTrimmedCurve") {} + Lazy< IfcCurve > BasisCurve; + ListOf< IfcTrimmingSelect, 1, 2 >::Out Trim1; + ListOf< IfcTrimmingSelect, 1, 2 >::Out Trim2; + IfcBoolean::Out SenseAgreement; + IfcTrimmingPreference::Out MasterRepresentation; + }; + + // C++ wrapper for IfcTubeBundle + struct IfcTubeBundle : IfcEnergyConversionDevice, ObjectHelper<IfcTubeBundle,1> { IfcTubeBundle() : Object("IfcTubeBundle") {} + Maybe< IfcTubeBundleTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcTubeBundleType + struct IfcTubeBundleType : IfcEnergyConversionDeviceType, ObjectHelper<IfcTubeBundleType,1> { IfcTubeBundleType() : Object("IfcTubeBundleType") {} + IfcTubeBundleTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcUShapeProfileDef + struct IfcUShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcUShapeProfileDef,7> { IfcUShapeProfileDef() : Object("IfcUShapeProfileDef") {} + IfcPositiveLengthMeasure::Out Depth; + IfcPositiveLengthMeasure::Out FlangeWidth; + IfcPositiveLengthMeasure::Out WebThickness; + IfcPositiveLengthMeasure::Out FlangeThickness; + Maybe< IfcNonNegativeLengthMeasure::Out > FilletRadius; + Maybe< IfcNonNegativeLengthMeasure::Out > EdgeRadius; + Maybe< IfcPlaneAngleMeasure::Out > FlangeSlope; + }; + + // C++ wrapper for IfcUnitAssignment + struct IfcUnitAssignment : ObjectHelper<IfcUnitAssignment,1> { IfcUnitAssignment() : Object("IfcUnitAssignment") {} + ListOf< IfcUnit, 1, 0 >::Out Units; + }; + + // C++ wrapper for IfcUnitaryControlElement + struct IfcUnitaryControlElement : IfcDistributionControlElement, ObjectHelper<IfcUnitaryControlElement,1> { IfcUnitaryControlElement() : Object("IfcUnitaryControlElement") {} + Maybe< IfcUnitaryControlElementTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcUnitaryControlElementType + struct IfcUnitaryControlElementType : IfcDistributionControlElementType, ObjectHelper<IfcUnitaryControlElementType,1> { IfcUnitaryControlElementType() : Object("IfcUnitaryControlElementType") {} + IfcUnitaryControlElementTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcUnitaryEquipment + struct IfcUnitaryEquipment : IfcEnergyConversionDevice, ObjectHelper<IfcUnitaryEquipment,1> { IfcUnitaryEquipment() : Object("IfcUnitaryEquipment") {} + Maybe< IfcUnitaryEquipmentTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcUnitaryEquipmentType + struct IfcUnitaryEquipmentType : IfcEnergyConversionDeviceType, ObjectHelper<IfcUnitaryEquipmentType,1> { IfcUnitaryEquipmentType() : Object("IfcUnitaryEquipmentType") {} + IfcUnitaryEquipmentTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcValve + struct IfcValve : IfcFlowController, ObjectHelper<IfcValve,1> { IfcValve() : Object("IfcValve") {} + Maybe< IfcValveTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcValveType + struct IfcValveType : IfcFlowControllerType, ObjectHelper<IfcValveType,1> { IfcValveType() : Object("IfcValveType") {} + IfcValveTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcVector + struct IfcVector : IfcGeometricRepresentationItem, ObjectHelper<IfcVector,2> { IfcVector() : Object("IfcVector") {} + Lazy< IfcDirection > Orientation; + IfcLengthMeasure::Out Magnitude; + }; + + // C++ wrapper for IfcVertex + struct IfcVertex : IfcTopologicalRepresentationItem, ObjectHelper<IfcVertex,0> { IfcVertex() : Object("IfcVertex") {} + + }; + + // C++ wrapper for IfcVertexLoop + struct IfcVertexLoop : IfcLoop, ObjectHelper<IfcVertexLoop,1> { IfcVertexLoop() : Object("IfcVertexLoop") {} + Lazy< IfcVertex > LoopVertex; + }; + + // C++ wrapper for IfcVertexPoint + struct IfcVertexPoint : IfcVertex, ObjectHelper<IfcVertexPoint,1> { IfcVertexPoint() : Object("IfcVertexPoint") {} + Lazy< IfcPoint > VertexGeometry; + }; + + // C++ wrapper for IfcVibrationIsolator + struct IfcVibrationIsolator : IfcElementComponent, ObjectHelper<IfcVibrationIsolator,1> { IfcVibrationIsolator() : Object("IfcVibrationIsolator") {} + Maybe< IfcVibrationIsolatorTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcVibrationIsolatorType + struct IfcVibrationIsolatorType : IfcElementComponentType, ObjectHelper<IfcVibrationIsolatorType,1> { IfcVibrationIsolatorType() : Object("IfcVibrationIsolatorType") {} + IfcVibrationIsolatorTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcVirtualElement + struct IfcVirtualElement : IfcElement, ObjectHelper<IfcVirtualElement,0> { IfcVirtualElement() : Object("IfcVirtualElement") {} + + }; + + // C++ wrapper for IfcVoidingFeature + struct IfcVoidingFeature : IfcFeatureElementSubtraction, ObjectHelper<IfcVoidingFeature,1> { IfcVoidingFeature() : Object("IfcVoidingFeature") {} + Maybe< IfcVoidingFeatureTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcWall + struct IfcWall : IfcBuildingElement, ObjectHelper<IfcWall,1> { IfcWall() : Object("IfcWall") {} + Maybe< IfcWallTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcWallElementedCase + struct IfcWallElementedCase : IfcWall, ObjectHelper<IfcWallElementedCase,0> { IfcWallElementedCase() : Object("IfcWallElementedCase") {} + + }; + + // C++ wrapper for IfcWallStandardCase + struct IfcWallStandardCase : IfcWall, ObjectHelper<IfcWallStandardCase,0> { IfcWallStandardCase() : Object("IfcWallStandardCase") {} + + }; + + // C++ wrapper for IfcWallType + struct IfcWallType : IfcBuildingElementType, ObjectHelper<IfcWallType,1> { IfcWallType() : Object("IfcWallType") {} + IfcWallTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcWasteTerminal + struct IfcWasteTerminal : IfcFlowTerminal, ObjectHelper<IfcWasteTerminal,1> { IfcWasteTerminal() : Object("IfcWasteTerminal") {} + Maybe< IfcWasteTerminalTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcWasteTerminalType + struct IfcWasteTerminalType : IfcFlowTerminalType, ObjectHelper<IfcWasteTerminalType,1> { IfcWasteTerminalType() : Object("IfcWasteTerminalType") {} + IfcWasteTerminalTypeEnum::Out PredefinedType; + }; + + // C++ wrapper for IfcWindow + struct IfcWindow : IfcBuildingElement, ObjectHelper<IfcWindow,5> { IfcWindow() : Object("IfcWindow") {} + Maybe< IfcPositiveLengthMeasure::Out > OverallHeight; + Maybe< IfcPositiveLengthMeasure::Out > OverallWidth; + Maybe< IfcWindowTypeEnum::Out > PredefinedType; + Maybe< IfcWindowTypePartitioningEnum::Out > PartitioningType; + Maybe< IfcLabel::Out > UserDefinedPartitioningType; + }; + + // C++ wrapper for IfcWindowStandardCase + struct IfcWindowStandardCase : IfcWindow, ObjectHelper<IfcWindowStandardCase,0> { IfcWindowStandardCase() : Object("IfcWindowStandardCase") {} + + }; + + // C++ wrapper for IfcWindowStyle + struct IfcWindowStyle : IfcTypeProduct, ObjectHelper<IfcWindowStyle,4> { IfcWindowStyle() : Object("IfcWindowStyle") {} + IfcWindowStyleConstructionEnum::Out ConstructionType; + IfcWindowStyleOperationEnum::Out OperationType; + IfcBoolean::Out ParameterTakesPrecedence; + IfcBoolean::Out Sizeable; + }; + + // C++ wrapper for IfcWindowType + struct IfcWindowType : IfcBuildingElementType, ObjectHelper<IfcWindowType,4> { IfcWindowType() : Object("IfcWindowType") {} + IfcWindowTypeEnum::Out PredefinedType; + IfcWindowTypePartitioningEnum::Out PartitioningType; + Maybe< IfcBoolean::Out > ParameterTakesPrecedence; + Maybe< IfcLabel::Out > UserDefinedPartitioningType; + }; + + // C++ wrapper for IfcWorkCalendar + struct IfcWorkCalendar : IfcControl, ObjectHelper<IfcWorkCalendar,3> { IfcWorkCalendar() : Object("IfcWorkCalendar") {} + Maybe< ListOf< Lazy< NotImplemented >, 1, 0 > > WorkingTimes; + Maybe< ListOf< Lazy< NotImplemented >, 1, 0 > > ExceptionTimes; + Maybe< IfcWorkCalendarTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcWorkControl + struct IfcWorkControl : IfcControl, ObjectHelper<IfcWorkControl,7> { IfcWorkControl() : Object("IfcWorkControl") {} + IfcDateTime::Out CreationDate; + Maybe< ListOf< Lazy< NotImplemented >, 1, 0 > > Creators; + Maybe< IfcLabel::Out > Purpose; + Maybe< IfcDuration::Out > Duration; + Maybe< IfcDuration::Out > TotalFloat; + IfcDateTime::Out StartTime; + Maybe< IfcDateTime::Out > FinishTime; + }; + + // C++ wrapper for IfcWorkPlan + struct IfcWorkPlan : IfcWorkControl, ObjectHelper<IfcWorkPlan,1> { IfcWorkPlan() : Object("IfcWorkPlan") {} + Maybe< IfcWorkPlanTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcWorkSchedule + struct IfcWorkSchedule : IfcWorkControl, ObjectHelper<IfcWorkSchedule,1> { IfcWorkSchedule() : Object("IfcWorkSchedule") {} + Maybe< IfcWorkScheduleTypeEnum::Out > PredefinedType; + }; + + // C++ wrapper for IfcZShapeProfileDef + struct IfcZShapeProfileDef : IfcParameterizedProfileDef, ObjectHelper<IfcZShapeProfileDef,6> { IfcZShapeProfileDef() : Object("IfcZShapeProfileDef") {} + IfcPositiveLengthMeasure::Out Depth; + IfcPositiveLengthMeasure::Out FlangeWidth; + IfcPositiveLengthMeasure::Out WebThickness; + IfcPositiveLengthMeasure::Out FlangeThickness; + Maybe< IfcNonNegativeLengthMeasure::Out > FilletRadius; + Maybe< IfcNonNegativeLengthMeasure::Out > EdgeRadius; + }; + + // C++ wrapper for IfcZone + struct IfcZone : IfcSystem, ObjectHelper<IfcZone,1> { IfcZone() : Object("IfcZone") {} + Maybe< IfcLabel::Out > LongName; + }; + + void GetSchema(EXPRESS::ConversionSchema& out); + +} //! IFC +namespace STEP { + + // ****************************************************************************** + // Converter stubs + // ****************************************************************************** + +#define DECL_CONV_STUB(type) template <> size_t GenericFill<IFC::type>(const STEP::DB& db, const EXPRESS::LIST& params, IFC::type* in) + + DECL_CONV_STUB( IfcRoot ); + DECL_CONV_STUB( IfcObjectDefinition ); + DECL_CONV_STUB( IfcObject ); + DECL_CONV_STUB( IfcControl ); + DECL_CONV_STUB( IfcActionRequest ); + DECL_CONV_STUB( IfcActor ); + DECL_CONV_STUB( IfcProduct ); + DECL_CONV_STUB( IfcElement ); + DECL_CONV_STUB( IfcDistributionElement ); + DECL_CONV_STUB( IfcDistributionControlElement ); + DECL_CONV_STUB( IfcActuator ); + DECL_CONV_STUB( IfcTypeObject ); + DECL_CONV_STUB( IfcTypeProduct ); + DECL_CONV_STUB( IfcElementType ); + DECL_CONV_STUB( IfcDistributionElementType ); + DECL_CONV_STUB( IfcDistributionControlElementType ); + DECL_CONV_STUB( IfcActuatorType ); + DECL_CONV_STUB( IfcRepresentationItem ); + DECL_CONV_STUB( IfcGeometricRepresentationItem ); + DECL_CONV_STUB( IfcSolidModel ); + DECL_CONV_STUB( IfcManifoldSolidBrep ); + DECL_CONV_STUB( IfcAdvancedBrep ); + DECL_CONV_STUB( IfcAdvancedBrepWithVoids ); + DECL_CONV_STUB( IfcTopologicalRepresentationItem ); + DECL_CONV_STUB( IfcFace ); + DECL_CONV_STUB( IfcFaceSurface ); + DECL_CONV_STUB( IfcAdvancedFace ); + DECL_CONV_STUB( IfcDistributionFlowElement ); + DECL_CONV_STUB( IfcFlowTerminal ); + DECL_CONV_STUB( IfcAirTerminal ); + DECL_CONV_STUB( IfcFlowController ); + DECL_CONV_STUB( IfcAirTerminalBox ); + DECL_CONV_STUB( IfcDistributionFlowElementType ); + DECL_CONV_STUB( IfcFlowControllerType ); + DECL_CONV_STUB( IfcAirTerminalBoxType ); + DECL_CONV_STUB( IfcFlowTerminalType ); + DECL_CONV_STUB( IfcAirTerminalType ); + DECL_CONV_STUB( IfcEnergyConversionDevice ); + DECL_CONV_STUB( IfcAirToAirHeatRecovery ); + DECL_CONV_STUB( IfcEnergyConversionDeviceType ); + DECL_CONV_STUB( IfcAirToAirHeatRecoveryType ); + DECL_CONV_STUB( IfcAlarm ); + DECL_CONV_STUB( IfcAlarmType ); + DECL_CONV_STUB( IfcAnnotation ); + DECL_CONV_STUB( IfcAnnotationFillArea ); + DECL_CONV_STUB( IfcProfileDef ); + DECL_CONV_STUB( IfcArbitraryClosedProfileDef ); + DECL_CONV_STUB( IfcArbitraryOpenProfileDef ); + DECL_CONV_STUB( IfcArbitraryProfileDefWithVoids ); + DECL_CONV_STUB( IfcGroup ); + DECL_CONV_STUB( IfcAsset ); + DECL_CONV_STUB( IfcParameterizedProfileDef ); + DECL_CONV_STUB( IfcAsymmetricIShapeProfileDef ); + DECL_CONV_STUB( IfcAudioVisualAppliance ); + DECL_CONV_STUB( IfcAudioVisualApplianceType ); + DECL_CONV_STUB( IfcPlacement ); + DECL_CONV_STUB( IfcAxis1Placement ); + DECL_CONV_STUB( IfcAxis2Placement2D ); + DECL_CONV_STUB( IfcAxis2Placement3D ); + DECL_CONV_STUB( IfcCurve ); + DECL_CONV_STUB( IfcBoundedCurve ); + DECL_CONV_STUB( IfcBSplineCurve ); + DECL_CONV_STUB( IfcBSplineCurveWithKnots ); + DECL_CONV_STUB( IfcSurface ); + DECL_CONV_STUB( IfcBoundedSurface ); + DECL_CONV_STUB( IfcBSplineSurface ); + DECL_CONV_STUB( IfcBSplineSurfaceWithKnots ); + DECL_CONV_STUB( IfcBuildingElement ); + DECL_CONV_STUB( IfcBeam ); + DECL_CONV_STUB( IfcBeamStandardCase ); + DECL_CONV_STUB( IfcBuildingElementType ); + DECL_CONV_STUB( IfcBeamType ); + DECL_CONV_STUB( IfcPresentationItem ); + DECL_CONV_STUB( IfcCsgPrimitive3D ); + DECL_CONV_STUB( IfcBlock ); + DECL_CONV_STUB( IfcBoiler ); + DECL_CONV_STUB( IfcBoilerType ); + DECL_CONV_STUB( IfcBooleanResult ); + DECL_CONV_STUB( IfcBooleanClippingResult ); + DECL_CONV_STUB( IfcCompositeCurve ); + DECL_CONV_STUB( IfcCompositeCurveOnSurface ); + DECL_CONV_STUB( IfcBoundaryCurve ); + DECL_CONV_STUB( IfcBoundingBox ); + DECL_CONV_STUB( IfcHalfSpaceSolid ); + DECL_CONV_STUB( IfcBoxedHalfSpace ); + DECL_CONV_STUB( IfcSpatialElement ); + DECL_CONV_STUB( IfcSpatialStructureElement ); + DECL_CONV_STUB( IfcBuilding ); + DECL_CONV_STUB( IfcElementComponent ); + DECL_CONV_STUB( IfcBuildingElementPart ); + DECL_CONV_STUB( IfcElementComponentType ); + DECL_CONV_STUB( IfcBuildingElementPartType ); + DECL_CONV_STUB( IfcBuildingElementProxy ); + DECL_CONV_STUB( IfcBuildingElementProxyType ); + DECL_CONV_STUB( IfcBuildingStorey ); + DECL_CONV_STUB( IfcSystem ); + DECL_CONV_STUB( IfcBuildingSystem ); + DECL_CONV_STUB( IfcBurner ); + DECL_CONV_STUB( IfcBurnerType ); + DECL_CONV_STUB( IfcCShapeProfileDef ); + DECL_CONV_STUB( IfcFlowFitting ); + DECL_CONV_STUB( IfcCableCarrierFitting ); + DECL_CONV_STUB( IfcFlowFittingType ); + DECL_CONV_STUB( IfcCableCarrierFittingType ); + DECL_CONV_STUB( IfcFlowSegment ); + DECL_CONV_STUB( IfcCableCarrierSegment ); + DECL_CONV_STUB( IfcFlowSegmentType ); + DECL_CONV_STUB( IfcCableCarrierSegmentType ); + DECL_CONV_STUB( IfcCableFitting ); + DECL_CONV_STUB( IfcCableFittingType ); + DECL_CONV_STUB( IfcCableSegment ); + DECL_CONV_STUB( IfcCableSegmentType ); + DECL_CONV_STUB( IfcPoint ); + DECL_CONV_STUB( IfcCartesianPoint ); + DECL_CONV_STUB( IfcCartesianPointList ); + DECL_CONV_STUB( IfcCartesianPointList2D ); + DECL_CONV_STUB( IfcCartesianPointList3D ); + DECL_CONV_STUB( IfcCartesianTransformationOperator ); + DECL_CONV_STUB( IfcCartesianTransformationOperator2D ); + DECL_CONV_STUB( IfcCartesianTransformationOperator2DnonUniform ); + DECL_CONV_STUB( IfcCartesianTransformationOperator3D ); + DECL_CONV_STUB( IfcCartesianTransformationOperator3DnonUniform ); + DECL_CONV_STUB( IfcCenterLineProfileDef ); + DECL_CONV_STUB( IfcChiller ); + DECL_CONV_STUB( IfcChillerType ); + DECL_CONV_STUB( IfcChimney ); + DECL_CONV_STUB( IfcChimneyType ); + DECL_CONV_STUB( IfcConic ); + DECL_CONV_STUB( IfcCircle ); + DECL_CONV_STUB( IfcCircleProfileDef ); + DECL_CONV_STUB( IfcCircleHollowProfileDef ); + DECL_CONV_STUB( IfcCivilElement ); + DECL_CONV_STUB( IfcCivilElementType ); + DECL_CONV_STUB( IfcConnectedFaceSet ); + DECL_CONV_STUB( IfcClosedShell ); + DECL_CONV_STUB( IfcCoil ); + DECL_CONV_STUB( IfcCoilType ); + DECL_CONV_STUB( IfcColourSpecification ); + DECL_CONV_STUB( IfcColourRgb ); + DECL_CONV_STUB( IfcColumn ); + DECL_CONV_STUB( IfcColumnStandardCase ); + DECL_CONV_STUB( IfcColumnType ); + DECL_CONV_STUB( IfcCommunicationsAppliance ); + DECL_CONV_STUB( IfcCommunicationsApplianceType ); + DECL_CONV_STUB( IfcPropertyAbstraction ); + DECL_CONV_STUB( IfcProperty ); + DECL_CONV_STUB( IfcComplexProperty ); + DECL_CONV_STUB( IfcPropertyDefinition ); + DECL_CONV_STUB( IfcCompositeCurveSegment ); + DECL_CONV_STUB( IfcCompositeProfileDef ); + DECL_CONV_STUB( IfcFlowMovingDevice ); + DECL_CONV_STUB( IfcCompressor ); + DECL_CONV_STUB( IfcFlowMovingDeviceType ); + DECL_CONV_STUB( IfcCompressorType ); + DECL_CONV_STUB( IfcCondenser ); + DECL_CONV_STUB( IfcCondenserType ); + DECL_CONV_STUB( IfcResource ); + DECL_CONV_STUB( IfcConstructionResource ); + DECL_CONV_STUB( IfcConstructionEquipmentResource ); + DECL_CONV_STUB( IfcTypeResource ); + DECL_CONV_STUB( IfcConstructionResourceType ); + DECL_CONV_STUB( IfcConstructionEquipmentResourceType ); + DECL_CONV_STUB( IfcConstructionMaterialResource ); + DECL_CONV_STUB( IfcConstructionMaterialResourceType ); + DECL_CONV_STUB( IfcConstructionProductResource ); + DECL_CONV_STUB( IfcConstructionProductResourceType ); + DECL_CONV_STUB( IfcContext ); + DECL_CONV_STUB( IfcNamedUnit ); + DECL_CONV_STUB( IfcContextDependentUnit ); + DECL_CONV_STUB( IfcController ); + DECL_CONV_STUB( IfcControllerType ); + DECL_CONV_STUB( IfcConversionBasedUnit ); + DECL_CONV_STUB( IfcConversionBasedUnitWithOffset ); + DECL_CONV_STUB( IfcCooledBeam ); + DECL_CONV_STUB( IfcCooledBeamType ); + DECL_CONV_STUB( IfcCoolingTower ); + DECL_CONV_STUB( IfcCoolingTowerType ); + DECL_CONV_STUB( IfcCostItem ); + DECL_CONV_STUB( IfcCostSchedule ); + DECL_CONV_STUB( IfcCovering ); + DECL_CONV_STUB( IfcCoveringType ); + DECL_CONV_STUB( IfcCrewResource ); + DECL_CONV_STUB( IfcCrewResourceType ); + DECL_CONV_STUB( IfcCsgSolid ); + DECL_CONV_STUB( IfcCurtainWall ); + DECL_CONV_STUB( IfcCurtainWallType ); + DECL_CONV_STUB( IfcCurveBoundedPlane ); + DECL_CONV_STUB( IfcCurveBoundedSurface ); + DECL_CONV_STUB( IfcPresentationStyle ); + DECL_CONV_STUB( IfcElementarySurface ); + DECL_CONV_STUB( IfcCylindricalSurface ); + DECL_CONV_STUB( IfcDamper ); + DECL_CONV_STUB( IfcDamperType ); + DECL_CONV_STUB( IfcDerivedProfileDef ); + DECL_CONV_STUB( IfcDirection ); + DECL_CONV_STUB( IfcDiscreteAccessory ); + DECL_CONV_STUB( IfcDiscreteAccessoryType ); + DECL_CONV_STUB( IfcDistributionChamberElement ); + DECL_CONV_STUB( IfcDistributionChamberElementType ); + DECL_CONV_STUB( IfcDistributionSystem ); + DECL_CONV_STUB( IfcDistributionCircuit ); + DECL_CONV_STUB( IfcPort ); + DECL_CONV_STUB( IfcDistributionPort ); + DECL_CONV_STUB( IfcDoor ); + DECL_CONV_STUB( IfcPropertySetDefinition ); + DECL_CONV_STUB( IfcDoorStandardCase ); + DECL_CONV_STUB( IfcDoorStyle ); + DECL_CONV_STUB( IfcDoorType ); + DECL_CONV_STUB( IfcDuctFitting ); + DECL_CONV_STUB( IfcDuctFittingType ); + DECL_CONV_STUB( IfcDuctSegment ); + DECL_CONV_STUB( IfcDuctSegmentType ); + DECL_CONV_STUB( IfcFlowTreatmentDevice ); + DECL_CONV_STUB( IfcDuctSilencer ); + DECL_CONV_STUB( IfcFlowTreatmentDeviceType ); + DECL_CONV_STUB( IfcDuctSilencerType ); + DECL_CONV_STUB( IfcEdge ); + DECL_CONV_STUB( IfcEdgeCurve ); + DECL_CONV_STUB( IfcLoop ); + DECL_CONV_STUB( IfcEdgeLoop ); + DECL_CONV_STUB( IfcElectricAppliance ); + DECL_CONV_STUB( IfcElectricApplianceType ); + DECL_CONV_STUB( IfcElectricDistributionBoard ); + DECL_CONV_STUB( IfcElectricDistributionBoardType ); + DECL_CONV_STUB( IfcFlowStorageDevice ); + DECL_CONV_STUB( IfcElectricFlowStorageDevice ); + DECL_CONV_STUB( IfcFlowStorageDeviceType ); + DECL_CONV_STUB( IfcElectricFlowStorageDeviceType ); + DECL_CONV_STUB( IfcElectricGenerator ); + DECL_CONV_STUB( IfcElectricGeneratorType ); + DECL_CONV_STUB( IfcElectricMotor ); + DECL_CONV_STUB( IfcElectricMotorType ); + DECL_CONV_STUB( IfcElectricTimeControl ); + DECL_CONV_STUB( IfcElectricTimeControlType ); + DECL_CONV_STUB( IfcElementAssembly ); + DECL_CONV_STUB( IfcElementAssemblyType ); + DECL_CONV_STUB( IfcQuantitySet ); + DECL_CONV_STUB( IfcElementQuantity ); + DECL_CONV_STUB( IfcEllipse ); + DECL_CONV_STUB( IfcEllipseProfileDef ); + DECL_CONV_STUB( IfcEngine ); + DECL_CONV_STUB( IfcEngineType ); + DECL_CONV_STUB( IfcEvaporativeCooler ); + DECL_CONV_STUB( IfcEvaporativeCoolerType ); + DECL_CONV_STUB( IfcEvaporator ); + DECL_CONV_STUB( IfcEvaporatorType ); + DECL_CONV_STUB( IfcProcess ); + DECL_CONV_STUB( IfcEvent ); + DECL_CONV_STUB( IfcTypeProcess ); + DECL_CONV_STUB( IfcEventType ); + DECL_CONV_STUB( IfcExternalSpatialStructureElement ); + DECL_CONV_STUB( IfcExternalSpatialElement ); + DECL_CONV_STUB( IfcSweptAreaSolid ); + DECL_CONV_STUB( IfcExtrudedAreaSolid ); + DECL_CONV_STUB( IfcExtrudedAreaSolidTapered ); + DECL_CONV_STUB( IfcFaceBasedSurfaceModel ); + DECL_CONV_STUB( IfcFaceBound ); + DECL_CONV_STUB( IfcFaceOuterBound ); + DECL_CONV_STUB( IfcFacetedBrep ); + DECL_CONV_STUB( IfcFacetedBrepWithVoids ); + DECL_CONV_STUB( IfcFan ); + DECL_CONV_STUB( IfcFanType ); + DECL_CONV_STUB( IfcFastener ); + DECL_CONV_STUB( IfcFastenerType ); + DECL_CONV_STUB( IfcFeatureElement ); + DECL_CONV_STUB( IfcFeatureElementAddition ); + DECL_CONV_STUB( IfcFeatureElementSubtraction ); + DECL_CONV_STUB( IfcFillAreaStyleHatching ); + DECL_CONV_STUB( IfcFillAreaStyleTiles ); + DECL_CONV_STUB( IfcFilter ); + DECL_CONV_STUB( IfcFilterType ); + DECL_CONV_STUB( IfcFireSuppressionTerminal ); + DECL_CONV_STUB( IfcFireSuppressionTerminalType ); + DECL_CONV_STUB( IfcFixedReferenceSweptAreaSolid ); + DECL_CONV_STUB( IfcFlowInstrument ); + DECL_CONV_STUB( IfcFlowInstrumentType ); + DECL_CONV_STUB( IfcFlowMeter ); + DECL_CONV_STUB( IfcFlowMeterType ); + DECL_CONV_STUB( IfcFooting ); + DECL_CONV_STUB( IfcFootingType ); + DECL_CONV_STUB( IfcFurnishingElement ); + DECL_CONV_STUB( IfcFurnishingElementType ); + DECL_CONV_STUB( IfcFurniture ); + DECL_CONV_STUB( IfcFurnitureType ); + DECL_CONV_STUB( IfcGeographicElement ); + DECL_CONV_STUB( IfcGeographicElementType ); + DECL_CONV_STUB( IfcGeometricSet ); + DECL_CONV_STUB( IfcGeometricCurveSet ); + DECL_CONV_STUB( IfcRepresentationContext ); + DECL_CONV_STUB( IfcGeometricRepresentationContext ); + DECL_CONV_STUB( IfcGeometricRepresentationSubContext ); + DECL_CONV_STUB( IfcGrid ); + DECL_CONV_STUB( IfcObjectPlacement ); + DECL_CONV_STUB( IfcGridPlacement ); + DECL_CONV_STUB( IfcHeatExchanger ); + DECL_CONV_STUB( IfcHeatExchangerType ); + DECL_CONV_STUB( IfcHumidifier ); + DECL_CONV_STUB( IfcHumidifierType ); + DECL_CONV_STUB( IfcIShapeProfileDef ); + DECL_CONV_STUB( IfcIndexedPolyCurve ); + DECL_CONV_STUB( IfcTessellatedItem ); + DECL_CONV_STUB( IfcIndexedPolygonalFace ); + DECL_CONV_STUB( IfcIndexedPolygonalFaceWithVoids ); + DECL_CONV_STUB( IfcInterceptor ); + DECL_CONV_STUB( IfcInterceptorType ); + DECL_CONV_STUB( IfcSurfaceCurve ); + DECL_CONV_STUB( IfcIntersectionCurve ); + DECL_CONV_STUB( IfcInventory ); + DECL_CONV_STUB( IfcJunctionBox ); + DECL_CONV_STUB( IfcJunctionBoxType ); + DECL_CONV_STUB( IfcLShapeProfileDef ); + DECL_CONV_STUB( IfcLaborResource ); + DECL_CONV_STUB( IfcLaborResourceType ); + DECL_CONV_STUB( IfcLamp ); + DECL_CONV_STUB( IfcLampType ); + DECL_CONV_STUB( IfcLightFixture ); + DECL_CONV_STUB( IfcLightFixtureType ); + DECL_CONV_STUB( IfcLightSource ); + DECL_CONV_STUB( IfcLightSourceAmbient ); + DECL_CONV_STUB( IfcLightSourceDirectional ); + DECL_CONV_STUB( IfcLightSourceGoniometric ); + DECL_CONV_STUB( IfcLightSourcePositional ); + DECL_CONV_STUB( IfcLightSourceSpot ); + DECL_CONV_STUB( IfcLine ); + DECL_CONV_STUB( IfcLocalPlacement ); + DECL_CONV_STUB( IfcMappedItem ); + DECL_CONV_STUB( IfcProductRepresentation ); + DECL_CONV_STUB( IfcMaterialDefinitionRepresentation ); + DECL_CONV_STUB( IfcMeasureWithUnit ); + DECL_CONV_STUB( IfcMechanicalFastener ); + DECL_CONV_STUB( IfcMechanicalFastenerType ); + DECL_CONV_STUB( IfcMedicalDevice ); + DECL_CONV_STUB( IfcMedicalDeviceType ); + DECL_CONV_STUB( IfcMember ); + DECL_CONV_STUB( IfcMemberStandardCase ); + DECL_CONV_STUB( IfcMemberType ); + DECL_CONV_STUB( IfcMirroredProfileDef ); + DECL_CONV_STUB( IfcMotorConnection ); + DECL_CONV_STUB( IfcMotorConnectionType ); + DECL_CONV_STUB( IfcOccupant ); + DECL_CONV_STUB( IfcOffsetCurve2D ); + DECL_CONV_STUB( IfcOffsetCurve3D ); + DECL_CONV_STUB( IfcOpenShell ); + DECL_CONV_STUB( IfcOpeningElement ); + DECL_CONV_STUB( IfcOpeningStandardCase ); + DECL_CONV_STUB( IfcOrientedEdge ); + DECL_CONV_STUB( IfcOuterBoundaryCurve ); + DECL_CONV_STUB( IfcOutlet ); + DECL_CONV_STUB( IfcOutletType ); + DECL_CONV_STUB( IfcPath ); + DECL_CONV_STUB( IfcPcurve ); + DECL_CONV_STUB( IfcPerformanceHistory ); + DECL_CONV_STUB( IfcPermit ); + DECL_CONV_STUB( IfcPile ); + DECL_CONV_STUB( IfcPileType ); + DECL_CONV_STUB( IfcPipeFitting ); + DECL_CONV_STUB( IfcPipeFittingType ); + DECL_CONV_STUB( IfcPipeSegment ); + DECL_CONV_STUB( IfcPipeSegmentType ); + DECL_CONV_STUB( IfcPlanarExtent ); + DECL_CONV_STUB( IfcPlanarBox ); + DECL_CONV_STUB( IfcPlane ); + DECL_CONV_STUB( IfcPlate ); + DECL_CONV_STUB( IfcPlateStandardCase ); + DECL_CONV_STUB( IfcPlateType ); + DECL_CONV_STUB( IfcPointOnCurve ); + DECL_CONV_STUB( IfcPointOnSurface ); + DECL_CONV_STUB( IfcPolyLoop ); + DECL_CONV_STUB( IfcPolygonalBoundedHalfSpace ); + DECL_CONV_STUB( IfcTessellatedFaceSet ); + DECL_CONV_STUB( IfcPolygonalFaceSet ); + DECL_CONV_STUB( IfcPolyline ); + DECL_CONV_STUB( IfcPresentationStyleAssignment ); + DECL_CONV_STUB( IfcProcedure ); + DECL_CONV_STUB( IfcProcedureType ); + DECL_CONV_STUB( IfcProductDefinitionShape ); + DECL_CONV_STUB( IfcProject ); + DECL_CONV_STUB( IfcProjectLibrary ); + DECL_CONV_STUB( IfcProjectOrder ); + DECL_CONV_STUB( IfcProjectionElement ); + DECL_CONV_STUB( IfcSimpleProperty ); + DECL_CONV_STUB( IfcPropertyBoundedValue ); + DECL_CONV_STUB( IfcPropertyEnumeratedValue ); + DECL_CONV_STUB( IfcPropertyListValue ); + DECL_CONV_STUB( IfcPropertyReferenceValue ); + DECL_CONV_STUB( IfcPropertySet ); + DECL_CONV_STUB( IfcPropertySingleValue ); + DECL_CONV_STUB( IfcPropertyTableValue ); + DECL_CONV_STUB( IfcProtectiveDevice ); + DECL_CONV_STUB( IfcProtectiveDeviceTrippingUnit ); + DECL_CONV_STUB( IfcProtectiveDeviceTrippingUnitType ); + DECL_CONV_STUB( IfcProtectiveDeviceType ); + DECL_CONV_STUB( IfcProxy ); + DECL_CONV_STUB( IfcPump ); + DECL_CONV_STUB( IfcPumpType ); + DECL_CONV_STUB( IfcRailing ); + DECL_CONV_STUB( IfcRailingType ); + DECL_CONV_STUB( IfcRamp ); + DECL_CONV_STUB( IfcRampFlight ); + DECL_CONV_STUB( IfcRampFlightType ); + DECL_CONV_STUB( IfcRampType ); + DECL_CONV_STUB( IfcRationalBSplineCurveWithKnots ); + DECL_CONV_STUB( IfcRationalBSplineSurfaceWithKnots ); + DECL_CONV_STUB( IfcRectangleProfileDef ); + DECL_CONV_STUB( IfcRectangleHollowProfileDef ); + DECL_CONV_STUB( IfcRectangularPyramid ); + DECL_CONV_STUB( IfcRectangularTrimmedSurface ); + DECL_CONV_STUB( IfcReinforcingElement ); + DECL_CONV_STUB( IfcReinforcingBar ); + DECL_CONV_STUB( IfcReinforcingElementType ); + DECL_CONV_STUB( IfcReinforcingBarType ); + DECL_CONV_STUB( IfcReinforcingMesh ); + DECL_CONV_STUB( IfcReinforcingMeshType ); + DECL_CONV_STUB( IfcRelationship ); + DECL_CONV_STUB( IfcRelDecomposes ); + DECL_CONV_STUB( IfcRelAggregates ); + DECL_CONV_STUB( IfcRelConnects ); + DECL_CONV_STUB( IfcRelContainedInSpatialStructure ); + DECL_CONV_STUB( IfcRelDefines ); + DECL_CONV_STUB( IfcRelDefinesByProperties ); + DECL_CONV_STUB( IfcRelFillsElement ); + DECL_CONV_STUB( IfcRelVoidsElement ); + DECL_CONV_STUB( IfcReparametrisedCompositeCurveSegment ); + DECL_CONV_STUB( IfcRepresentation ); + DECL_CONV_STUB( IfcRepresentationMap ); + DECL_CONV_STUB( IfcRevolvedAreaSolid ); + DECL_CONV_STUB( IfcRevolvedAreaSolidTapered ); + DECL_CONV_STUB( IfcRightCircularCone ); + DECL_CONV_STUB( IfcRightCircularCylinder ); + DECL_CONV_STUB( IfcRoof ); + DECL_CONV_STUB( IfcRoofType ); + DECL_CONV_STUB( IfcRoundedRectangleProfileDef ); + DECL_CONV_STUB( IfcSIUnit ); + DECL_CONV_STUB( IfcSanitaryTerminal ); + DECL_CONV_STUB( IfcSanitaryTerminalType ); + DECL_CONV_STUB( IfcSeamCurve ); + DECL_CONV_STUB( IfcSectionedSpine ); + DECL_CONV_STUB( IfcSensor ); + DECL_CONV_STUB( IfcSensorType ); + DECL_CONV_STUB( IfcShadingDevice ); + DECL_CONV_STUB( IfcShadingDeviceType ); + DECL_CONV_STUB( IfcShapeModel ); + DECL_CONV_STUB( IfcShapeRepresentation ); + DECL_CONV_STUB( IfcShellBasedSurfaceModel ); + DECL_CONV_STUB( IfcSite ); + DECL_CONV_STUB( IfcSlab ); + DECL_CONV_STUB( IfcSlabElementedCase ); + DECL_CONV_STUB( IfcSlabStandardCase ); + DECL_CONV_STUB( IfcSlabType ); + DECL_CONV_STUB( IfcSolarDevice ); + DECL_CONV_STUB( IfcSolarDeviceType ); + DECL_CONV_STUB( IfcSpace ); + DECL_CONV_STUB( IfcSpaceHeater ); + DECL_CONV_STUB( IfcSpaceHeaterType ); + DECL_CONV_STUB( IfcSpatialElementType ); + DECL_CONV_STUB( IfcSpatialStructureElementType ); + DECL_CONV_STUB( IfcSpaceType ); + DECL_CONV_STUB( IfcSpatialZone ); + DECL_CONV_STUB( IfcSpatialZoneType ); + DECL_CONV_STUB( IfcSphere ); + DECL_CONV_STUB( IfcSphericalSurface ); + DECL_CONV_STUB( IfcStackTerminal ); + DECL_CONV_STUB( IfcStackTerminalType ); + DECL_CONV_STUB( IfcStair ); + DECL_CONV_STUB( IfcStairFlight ); + DECL_CONV_STUB( IfcStairFlightType ); + DECL_CONV_STUB( IfcStairType ); + DECL_CONV_STUB( IfcStructuralActivity ); + DECL_CONV_STUB( IfcStructuralAction ); + DECL_CONV_STUB( IfcStructuralAnalysisModel ); + DECL_CONV_STUB( IfcStructuralItem ); + DECL_CONV_STUB( IfcStructuralConnection ); + DECL_CONV_STUB( IfcStructuralCurveAction ); + DECL_CONV_STUB( IfcStructuralCurveConnection ); + DECL_CONV_STUB( IfcStructuralMember ); + DECL_CONV_STUB( IfcStructuralCurveMember ); + DECL_CONV_STUB( IfcStructuralCurveMemberVarying ); + DECL_CONV_STUB( IfcStructuralReaction ); + DECL_CONV_STUB( IfcStructuralCurveReaction ); + DECL_CONV_STUB( IfcStructuralLinearAction ); + DECL_CONV_STUB( IfcStructuralLoadGroup ); + DECL_CONV_STUB( IfcStructuralLoadCase ); + DECL_CONV_STUB( IfcStructuralSurfaceAction ); + DECL_CONV_STUB( IfcStructuralPlanarAction ); + DECL_CONV_STUB( IfcStructuralPointAction ); + DECL_CONV_STUB( IfcStructuralPointConnection ); + DECL_CONV_STUB( IfcStructuralPointReaction ); + DECL_CONV_STUB( IfcStructuralResultGroup ); + DECL_CONV_STUB( IfcStructuralSurfaceConnection ); + DECL_CONV_STUB( IfcStructuralSurfaceMember ); + DECL_CONV_STUB( IfcStructuralSurfaceMemberVarying ); + DECL_CONV_STUB( IfcStructuralSurfaceReaction ); + DECL_CONV_STUB( IfcStyleModel ); + DECL_CONV_STUB( IfcStyledItem ); + DECL_CONV_STUB( IfcStyledRepresentation ); + DECL_CONV_STUB( IfcSubContractResource ); + DECL_CONV_STUB( IfcSubContractResourceType ); + DECL_CONV_STUB( IfcSubedge ); + DECL_CONV_STUB( IfcSurfaceCurveSweptAreaSolid ); + DECL_CONV_STUB( IfcSurfaceFeature ); + DECL_CONV_STUB( IfcSweptSurface ); + DECL_CONV_STUB( IfcSurfaceOfLinearExtrusion ); + DECL_CONV_STUB( IfcSurfaceOfRevolution ); + DECL_CONV_STUB( IfcSurfaceStyle ); + DECL_CONV_STUB( IfcSurfaceStyleShading ); + DECL_CONV_STUB( IfcSurfaceStyleRendering ); + DECL_CONV_STUB( IfcSurfaceStyleWithTextures ); + DECL_CONV_STUB( IfcSweptDiskSolid ); + DECL_CONV_STUB( IfcSweptDiskSolidPolygonal ); + DECL_CONV_STUB( IfcSwitchingDevice ); + DECL_CONV_STUB( IfcSwitchingDeviceType ); + DECL_CONV_STUB( IfcSystemFurnitureElement ); + DECL_CONV_STUB( IfcSystemFurnitureElementType ); + DECL_CONV_STUB( IfcTShapeProfileDef ); + DECL_CONV_STUB( IfcTank ); + DECL_CONV_STUB( IfcTankType ); + DECL_CONV_STUB( IfcTask ); + DECL_CONV_STUB( IfcTaskType ); + DECL_CONV_STUB( IfcTendon ); + DECL_CONV_STUB( IfcTendonAnchor ); + DECL_CONV_STUB( IfcTendonAnchorType ); + DECL_CONV_STUB( IfcTendonType ); + DECL_CONV_STUB( IfcTextLiteral ); + DECL_CONV_STUB( IfcTextLiteralWithExtent ); + DECL_CONV_STUB( IfcTopologyRepresentation ); + DECL_CONV_STUB( IfcToroidalSurface ); + DECL_CONV_STUB( IfcTransformer ); + DECL_CONV_STUB( IfcTransformerType ); + DECL_CONV_STUB( IfcTransportElement ); + DECL_CONV_STUB( IfcTransportElementType ); + DECL_CONV_STUB( IfcTrapeziumProfileDef ); + DECL_CONV_STUB( IfcTriangulatedFaceSet ); + DECL_CONV_STUB( IfcTrimmedCurve ); + DECL_CONV_STUB( IfcTubeBundle ); + DECL_CONV_STUB( IfcTubeBundleType ); + DECL_CONV_STUB( IfcUShapeProfileDef ); + DECL_CONV_STUB( IfcUnitAssignment ); + DECL_CONV_STUB( IfcUnitaryControlElement ); + DECL_CONV_STUB( IfcUnitaryControlElementType ); + DECL_CONV_STUB( IfcUnitaryEquipment ); + DECL_CONV_STUB( IfcUnitaryEquipmentType ); + DECL_CONV_STUB( IfcValve ); + DECL_CONV_STUB( IfcValveType ); + DECL_CONV_STUB( IfcVector ); + DECL_CONV_STUB( IfcVertex ); + DECL_CONV_STUB( IfcVertexLoop ); + DECL_CONV_STUB( IfcVertexPoint ); + DECL_CONV_STUB( IfcVibrationIsolator ); + DECL_CONV_STUB( IfcVibrationIsolatorType ); + DECL_CONV_STUB( IfcVirtualElement ); + DECL_CONV_STUB( IfcVoidingFeature ); + DECL_CONV_STUB( IfcWall ); + DECL_CONV_STUB( IfcWallElementedCase ); + DECL_CONV_STUB( IfcWallStandardCase ); + DECL_CONV_STUB( IfcWallType ); + DECL_CONV_STUB( IfcWasteTerminal ); + DECL_CONV_STUB( IfcWasteTerminalType ); + DECL_CONV_STUB( IfcWindow ); + DECL_CONV_STUB( IfcWindowStandardCase ); + DECL_CONV_STUB( IfcWindowStyle ); + DECL_CONV_STUB( IfcWindowType ); + DECL_CONV_STUB( IfcWorkCalendar ); + DECL_CONV_STUB( IfcWorkControl ); + DECL_CONV_STUB( IfcWorkPlan ); + DECL_CONV_STUB( IfcWorkSchedule ); + DECL_CONV_STUB( IfcZShapeProfileDef ); + DECL_CONV_STUB( IfcZone ); + + +#undef DECL_CONV_STUB + +} //! Schema_4 +} //! STEP +} //! Assimp + +#endif // INCLUDED_IFC_READER_GEN_H diff --git a/libs/assimp/code/AssetLib/IFC/IFCUtil.cpp b/libs/assimp/code/AssetLib/IFC/IFCUtil.cpp new file mode 100644 index 0000000..6b378c1 --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCUtil.cpp @@ -0,0 +1,701 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IFCUtil.cpp + * @brief Implementation of conversion routines for some common Ifc helper entities. + */ + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER + +#include "AssetLib/IFC/IFCUtil.h" +#include "Common/PolyTools.h" +#include "PostProcessing/ProcessHelper.h" + +namespace Assimp { +namespace IFC { + +// ------------------------------------------------------------------------------------------------ +void TempOpening::Transform(const IfcMatrix4& mat) { + if(profileMesh) { + profileMesh->Transform(mat); + } + if(profileMesh2D) { + profileMesh2D->Transform(mat); + } + extrusionDir *= IfcMatrix3(mat); +} + +// ------------------------------------------------------------------------------------------------ +aiMesh* TempMesh::ToMesh() +{ + ai_assert(mVerts.size() == std::accumulate(mVertcnt.begin(),mVertcnt.end(),size_t(0))); + + if (mVerts.empty()) { + return nullptr; + } + + std::unique_ptr<aiMesh> mesh(new aiMesh()); + + // copy vertices + mesh->mNumVertices = static_cast<unsigned int>(mVerts.size()); + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + std::copy(mVerts.begin(),mVerts.end(),mesh->mVertices); + + // and build up faces + mesh->mNumFaces = static_cast<unsigned int>(mVertcnt.size()); + mesh->mFaces = new aiFace[mesh->mNumFaces]; + + for(unsigned int i = 0,n=0, acc = 0; i < mesh->mNumFaces; ++n) { + aiFace& f = mesh->mFaces[i]; + if (!mVertcnt[n]) { + --mesh->mNumFaces; + continue; + } + + f.mNumIndices = mVertcnt[n]; + f.mIndices = new unsigned int[f.mNumIndices]; + for(unsigned int a = 0; a < f.mNumIndices; ++a) { + f.mIndices[a] = acc++; + } + + ++i; + } + + return mesh.release(); +} + +// ------------------------------------------------------------------------------------------------ +void TempMesh::Clear() +{ + mVerts.clear(); + mVertcnt.clear(); +} + +// ------------------------------------------------------------------------------------------------ +void TempMesh::Transform(const IfcMatrix4& mat) +{ + for(IfcVector3& v : mVerts) { + v *= mat; + } +} + +// ------------------------------------------------------------------------------ +IfcVector3 TempMesh::Center() const +{ + return (mVerts.size() == 0) ? IfcVector3(0.0f, 0.0f, 0.0f) : (std::accumulate(mVerts.begin(),mVerts.end(),IfcVector3()) / static_cast<IfcFloat>(mVerts.size())); +} + +// ------------------------------------------------------------------------------------------------ +void TempMesh::Append(const TempMesh& other) +{ + mVerts.insert(mVerts.end(),other.mVerts.begin(),other.mVerts.end()); + mVertcnt.insert(mVertcnt.end(),other.mVertcnt.begin(),other.mVertcnt.end()); +} + +// ------------------------------------------------------------------------------------------------ +void TempMesh::RemoveDegenerates() +{ + // The strategy is simple: walk the mesh and compute normals using + // Newell's algorithm. The length of the normals gives the area + // of the polygons, which is close to zero for lines. + + std::vector<IfcVector3> normals; + ComputePolygonNormals(normals, false); + + bool drop = false; + size_t inor = 0; + + std::vector<IfcVector3>::iterator vit = mVerts.begin(); + for (std::vector<unsigned int>::iterator it = mVertcnt.begin(); it != mVertcnt.end(); ++inor) { + const unsigned int pcount = *it; + + if (normals[inor].SquareLength() < 1e-10f) { + it = mVertcnt.erase(it); + vit = mVerts.erase(vit, vit + pcount); + + drop = true; + continue; + } + + vit += pcount; + ++it; + } + + if(drop) { + IFCImporter::LogVerboseDebug("removing degenerate faces"); + } +} + +// ------------------------------------------------------------------------------------------------ +IfcVector3 TempMesh::ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bool normalize) +{ + std::vector<IfcFloat> 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, static_cast<int>(cnt), &temp[0], &temp[1], &temp[2]); + return normalize ? nor.Normalize() : nor; +} + +// ------------------------------------------------------------------------------------------------ +void TempMesh::ComputePolygonNormals(std::vector<IfcVector3>& normals, + bool normalize, + size_t ofs) const +{ + size_t max_vcount = 0; + std::vector<unsigned int>::const_iterator begin = mVertcnt.begin()+ofs, end = mVertcnt.end(), iit; + for(iit = begin; iit != end; ++iit) { + max_vcount = std::max(max_vcount,static_cast<size_t>(*iit)); + } + + std::vector<IfcFloat> temp((max_vcount+2)*4); + normals.reserve( normals.size() + mVertcnt.size()-ofs ); + + // `NewellNormal()` currently has a relatively strange interface and need to + // re-structure things a bit to meet them. + size_t vidx = std::accumulate(mVertcnt.begin(),begin,0); + for(iit = begin; iit != end; vidx += *iit++) { + if (!*iit) { + normals.push_back(IfcVector3()); + continue; + } + for(size_t vofs = 0, cnt = 0; vofs < *iit; ++vofs) { + const IfcVector3& v = mVerts[vidx+vofs]; + temp[cnt++] = v.x; + temp[cnt++] = v.y; + temp[cnt++] = v.z; +#ifdef ASSIMP_BUILD_DEBUG + temp[cnt] = std::numeric_limits<IfcFloat>::quiet_NaN(); +#endif + ++cnt; + } + + normals.push_back(IfcVector3()); + NewellNormal<4,4,4>(normals.back(),*iit,&temp[0],&temp[1],&temp[2]); + } + + if(normalize) { + for(IfcVector3& n : normals) { + n.Normalize(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Compute the normal of the last polygon in the given mesh +IfcVector3 TempMesh::ComputeLastPolygonNormal(bool normalize) const { + return ComputePolygonNormal(&mVerts[mVerts.size() - mVertcnt.back()], mVertcnt.back(), normalize); +} + +struct CompareVector { + bool operator () (const IfcVector3& a, const IfcVector3& b) const { + IfcVector3 d = a - b; + IfcFloat eps = ai_epsilon; + 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(ai_epsilon)(p, v); + } +}; + +// ------------------------------------------------------------------------------------------------ +void TempMesh::FixupFaceOrientation() +{ + const IfcVector3 vavg = Center(); + + // create a list of start indices for all faces to allow random access to faces + std::vector<size_t> faceStartIndices(mVertcnt.size()); + for( size_t i = 0, a = 0; a < mVertcnt.size(); i += mVertcnt[a], ++a ) + faceStartIndices[a] = i; + + // list all faces on a vertex + std::map<IfcVector3, std::vector<size_t>, CompareVector> facesByVertex; + for( size_t a = 0; a < mVertcnt.size(); ++a ) + { + for( size_t b = 0; b < mVertcnt[a]; ++b ) + facesByVertex[mVerts[faceStartIndices[a] + b]].push_back(a); + } + // determine neighbourhood for all polys + std::vector<size_t> neighbour(mVerts.size(), SIZE_MAX); + std::vector<size_t> tempIntersect(10); + for( size_t a = 0; a < mVertcnt.size(); ++a ) + { + for( size_t b = 0; b < mVertcnt[a]; ++b ) + { + size_t ib = faceStartIndices[a] + b, nib = faceStartIndices[a] + (b + 1) % mVertcnt[a]; + const std::vector<size_t>& facesOnB = facesByVertex[mVerts[ib]]; + const std::vector<size_t>& facesOnNB = facesByVertex[mVerts[nib]]; + // there should be exactly one or two faces which appear in both lists. Our face and the other side + std::vector<size_t>::iterator sectstart = tempIntersect.begin(); + std::vector<size_t>::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<bool> faceDone(mVertcnt.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 < mVertcnt.size(); ++a ) + { + if( faceDone[a] ) + continue; + IfcVector3 faceCenter = std::accumulate(mVerts.begin() + faceStartIndices[a], + mVerts.begin() + faceStartIndices[a] + mVertcnt[a], IfcVector3(0.0)) / IfcFloat(mVertcnt[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(mVerts.data() + faceStartIndices[farthestIndex], mVertcnt[farthestIndex]); + IfcVector3 farthestCenter = std::accumulate(mVerts.begin() + faceStartIndices[farthestIndex], + mVerts.begin() + faceStartIndices[farthestIndex] + mVertcnt[farthestIndex], IfcVector3(0.0)) + / IfcFloat(mVertcnt[farthestIndex]); + // We accept 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 = mVertcnt[farthestIndex]; + std::reverse(mVerts.begin() + fsi, mVerts.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<size_t> 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 = mVertcnt[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 = mVerts[vsi + a]; + size_t nbvsi = faceStartIndices[nbi], nbvc = mVertcnt[nbi]; + std::vector<IfcVector3>::iterator it = std::find_if(mVerts.begin() + nbvsi, mVerts.begin() + nbvsi + nbvc, FindVector(vp)); + ai_assert(it != mVerts.begin() + nbvsi + nbvc); + size_t nb_vidx = std::distance(mVerts.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(ai_epsilon)(mVerts[vsi + oursideidx], mVerts[nbvsi + nb_vidx])) { + std::reverse(mVerts.begin() + nbvsi, mVerts.begin() + nbvsi + nbvc); + std::reverse(neighbour.begin() + nbvsi, neighbour.begin() + nbvsi + nbvc); + for (size_t aa = 0; aa < nbvc - 1; ++aa) { + std::swap(neighbour[nbvsi + aa], neighbour[nbvsi + aa + 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); + } + } + + // no more faces reachable from this part of the surface, start over with a disjunct part and its farthest face + } +} + +// ------------------------------------------------------------------------------------------------ +void TempMesh::RemoveAdjacentDuplicates() { + bool drop = false; + std::vector<IfcVector3>::iterator base = mVerts.begin(); + for(unsigned int& cnt : mVertcnt) { + if (cnt < 2){ + base += cnt; + continue; + } + + IfcVector3 vmin,vmax; + ArrayBounds(&*base, cnt ,vmin,vmax); + + + const IfcFloat epsilon = (vmax-vmin).SquareLength() / static_cast<IfcFloat>(1e9); + //const IfcFloat dotepsilon = 1e-9; + + //// look for vertices that lie directly on the line between their predecessor and their + //// successor and replace them with either of them. + + //for(size_t i = 0; i < cnt; ++i) { + // IfcVector3& v1 = *(base+i), &v0 = *(base+(i?i-1:cnt-1)), &v2 = *(base+(i+1)%cnt); + // const IfcVector3& d0 = (v1-v0), &d1 = (v2-v1); + // const IfcFloat l0 = d0.SquareLength(), l1 = d1.SquareLength(); + // if (!l0 || !l1) { + // continue; + // } + + // const IfcFloat d = (d0/std::sqrt(l0))*(d1/std::sqrt(l1)); + + // if ( d >= 1.f-dotepsilon ) { + // v1 = v0; + // } + // else if ( d < -1.f+dotepsilon ) { + // v2 = v1; + // continue; + // } + //} + + // drop any identical, adjacent vertices. this pass will collect the dropouts + // of the previous pass as a side-effect. + FuzzyVectorCompare fz(epsilon); + std::vector<IfcVector3>::iterator end = base+cnt, e = std::unique( base, end, fz ); + if (e != end) { + cnt -= static_cast<unsigned int>(std::distance(e, end)); + mVerts.erase(e,end); + drop = true; + } + + // check front and back vertices for this polygon + if (cnt > 1 && fz(*base,*(base+cnt-1))) { + mVerts.erase(base+ --cnt); + drop = true; + } + + // removing adjacent duplicates shouldn't erase everything :-) + ai_assert(cnt>0); + base += cnt; + } + if(drop) { + IFCImporter::LogVerboseDebug("removing duplicate vertices"); + } +} + +// ------------------------------------------------------------------------------------------------ +void TempMesh::Swap(TempMesh& other) +{ + mVertcnt.swap(other.mVertcnt); + mVerts.swap(other.mVerts); +} + +// ------------------------------------------------------------------------------------------------ +bool IsTrue(const ::Assimp::STEP::EXPRESS::BOOLEAN& in) +{ + return (std::string)in == "TRUE" || (std::string)in == "T"; +} + +// ------------------------------------------------------------------------------------------------ +IfcFloat ConvertSIPrefix(const std::string& prefix) +{ + if (prefix == "EXA") { + return 1e18f; + } + else if (prefix == "PETA") { + return 1e15f; + } + else if (prefix == "TERA") { + return 1e12f; + } + else if (prefix == "GIGA") { + return 1e9f; + } + else if (prefix == "MEGA") { + return 1e6f; + } + else if (prefix == "KILO") { + return 1e3f; + } + else if (prefix == "HECTO") { + return 1e2f; + } + else if (prefix == "DECA") { + return 1e-0f; + } + else if (prefix == "DECI") { + return 1e-1f; + } + else if (prefix == "CENTI") { + return 1e-2f; + } + else if (prefix == "MILLI") { + return 1e-3f; + } + else if (prefix == "MICRO") { + return 1e-6f; + } + else if (prefix == "NANO") { + return 1e-9f; + } + else if (prefix == "PICO") { + return 1e-12f; + } + else if (prefix == "FEMTO") { + return 1e-15f; + } + else if (prefix == "ATTO") { + return 1e-18f; + } + else { + IFCImporter::LogError("Unrecognized SI prefix: ", prefix); + return 1; + } +} + +// ------------------------------------------------------------------------------------------------ +void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in) +{ + out.r = static_cast<float>( in.Red ); + out.g = static_cast<float>( in.Green ); + out.b = static_cast<float>( in.Blue ); + out.a = static_cast<float>( 1.f ); +} + +// ------------------------------------------------------------------------------------------------ +void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourOrFactor& in,ConversionData& conv,const aiColor4D* base) +{ + if (const ::Assimp::STEP::EXPRESS::REAL* const r = in.ToPtr<::Assimp::STEP::EXPRESS::REAL>()) { + out.r = out.g = out.b = static_cast<float>(*r); + if(base) { + out.r *= static_cast<float>( base->r ); + out.g *= static_cast<float>( base->g ); + out.b *= static_cast<float>( base->b ); + out.a = static_cast<float>( base->a ); + } + else out.a = 1.0; + } + else if (const Schema_2x3::IfcColourRgb* const rgb = in.ResolveSelectPtr<Schema_2x3::IfcColourRgb>(conv.db)) { + ConvertColor(out,*rgb); + } + else { + IFCImporter::LogWarn("skipping unknown IfcColourOrFactor entity"); + } +} + +// ------------------------------------------------------------------------------------------------ +void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint& in) +{ + out = IfcVector3(); + for(size_t i = 0; i < in.Coordinates.size(); ++i) { + out[static_cast<unsigned int>(i)] = in.Coordinates[i]; + } +} + +// ------------------------------------------------------------------------------------------------ +void ConvertVector(IfcVector3& out, const Schema_2x3::IfcVector& in) +{ + ConvertDirection(out,in.Orientation); + out *= in.Magnitude; +} + +// ------------------------------------------------------------------------------------------------ +void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in) +{ + out = IfcVector3(); + for(size_t i = 0; i < in.DirectionRatios.size(); ++i) { + out[static_cast<unsigned int>(i)] = in.DirectionRatios[i]; + } + const IfcFloat len = out.Length(); + if (len < ai_epsilon) { + IFCImporter::LogWarn("direction vector magnitude too small, normalization would result in a division by zero"); + return; + } + out /= len; +} + +// ------------------------------------------------------------------------------------------------ +void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y, const IfcVector3& z) +{ + out.a1 = x.x; + out.b1 = x.y; + out.c1 = x.z; + + out.a2 = y.x; + out.b2 = y.y; + out.c2 = y.z; + + out.a3 = z.x; + out.b3 = z.y; + out.c3 = z.z; +} + +// ------------------------------------------------------------------------------------------------ +void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D& in) +{ + IfcVector3 loc; + ConvertCartesianPoint(loc,in.Location); + + IfcVector3 z(0.f,0.f,1.f),r(1.f,0.f,0.f),x; + + if (in.Axis) { + ConvertDirection(z,*in.Axis.Get()); + } + if (in.RefDirection) { + ConvertDirection(r,*in.RefDirection.Get()); + } + + IfcVector3 v = r.Normalize(); + IfcVector3 tmpx = z * (v*z); + + x = (v-tmpx).Normalize(); + IfcVector3 y = (z^x); + + IfcMatrix4::Translation(loc,out); + AssignMatrixAxes(out,x,y,z); +} + +// ------------------------------------------------------------------------------------------------ +void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D& in) +{ + IfcVector3 loc; + ConvertCartesianPoint(loc,in.Location); + + IfcVector3 x(1.f,0.f,0.f); + if (in.RefDirection) { + ConvertDirection(x,*in.RefDirection.Get()); + } + + const IfcVector3 y = IfcVector3(x.y,-x.x,0.f); + + IfcMatrix4::Translation(loc,out); + AssignMatrixAxes(out,x,y,IfcVector3(0.f,0.f,1.f)); +} + +// ------------------------------------------------------------------------------------------------ +void ConvertAxisPlacement(IfcVector3& axis, IfcVector3& pos, const Schema_2x3::IfcAxis1Placement& in) +{ + ConvertCartesianPoint(pos,in.Location); + if (in.Axis) { + ConvertDirection(axis,in.Axis.Get()); + } + else { + axis = IfcVector3(0.f,0.f,1.f); + } +} + +// ------------------------------------------------------------------------------------------------ +void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement& in, ConversionData& conv) +{ + if(const Schema_2x3::IfcAxis2Placement3D* pl3 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement3D>(conv.db)) { + ConvertAxisPlacement(out,*pl3); + } + else if(const Schema_2x3::IfcAxis2Placement2D* pl2 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement2D>(conv.db)) { + ConvertAxisPlacement(out,*pl2); + } + else { + IFCImporter::LogWarn("skipping unknown IfcAxis2Placement entity"); + } +} + +// ------------------------------------------------------------------------------------------------ +void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTransformationOperator& op) +{ + IfcVector3 loc; + ConvertCartesianPoint(loc,op.LocalOrigin); + + IfcVector3 x(1.f,0.f,0.f),y(0.f,1.f,0.f),z(0.f,0.f,1.f); + if (op.Axis1) { + ConvertDirection(x,*op.Axis1.Get()); + } + if (op.Axis2) { + ConvertDirection(y,*op.Axis2.Get()); + } + if (const Schema_2x3::IfcCartesianTransformationOperator3D* op2 = op.ToPtr<Schema_2x3::IfcCartesianTransformationOperator3D>()) { + if(op2->Axis3) { + ConvertDirection(z,*op2->Axis3.Get()); + } + } + + IfcMatrix4 locm; + IfcMatrix4::Translation(loc,locm); + AssignMatrixAxes(out,x,y,z); + + + IfcVector3 vscale; + if (const Schema_2x3::IfcCartesianTransformationOperator3DnonUniform* nuni = op.ToPtr<Schema_2x3::IfcCartesianTransformationOperator3DnonUniform>()) { + vscale.x = nuni->Scale?op.Scale.Get():1.f; + vscale.y = nuni->Scale2?nuni->Scale2.Get():1.f; + vscale.z = nuni->Scale3?nuni->Scale3.Get():1.f; + } + else { + const IfcFloat sc = op.Scale?op.Scale.Get():1.f; + vscale = IfcVector3(sc,sc,sc); + } + + IfcMatrix4 s; + IfcMatrix4::Scaling(vscale,s); + + out = locm * out * s; +} + + +} // ! IFC +} // ! Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/IFC/IFCUtil.h b/libs/assimp/code/AssetLib/IFC/IFCUtil.h new file mode 100644 index 0000000..b5f72a5 --- /dev/null +++ b/libs/assimp/code/AssetLib/IFC/IFCUtil.h @@ -0,0 +1,411 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IFC.cpp + * @brief Implementation of the Industry Foundation Classes loader. + */ + +#ifndef INCLUDED_IFCUTIL_H +#define INCLUDED_IFCUTIL_H + +#include "AssetLib/IFC/IFCReaderGen_2x3.h" +#include "AssetLib/IFC/IFCLoader.h" +#include "AssetLib/Step/STEPFile.h" + +#include <assimp/mesh.h> +#include <assimp/material.h> + +#include <utility> + +struct aiNode; + +namespace Assimp { +namespace IFC { + + typedef double IfcFloat; + + // IfcFloat-precision math data types + typedef aiVector2t<IfcFloat> IfcVector2; + typedef aiVector3t<IfcFloat> IfcVector3; + typedef aiMatrix4x4t<IfcFloat> IfcMatrix4; + typedef aiMatrix3x3t<IfcFloat> IfcMatrix3; + typedef aiColor4t<IfcFloat> IfcColor4; + + +// ------------------------------------------------------------------------------------------------ +// Helper for std::for_each to delete all heap-allocated items in a container +// ------------------------------------------------------------------------------------------------ +template<typename T> +struct delete_fun { + void operator()(T* del) { + delete del; + } +}; + + + +// ------------------------------------------------------------------------------------------------ +// Helper used during mesh construction. Aids at creating aiMesh'es out of relatively few polygons. +// ------------------------------------------------------------------------------------------------ +struct TempMesh { + std::vector<IfcVector3> mVerts; + std::vector<unsigned int> mVertcnt; + + // utilities + aiMesh* ToMesh(); + void Clear(); + void Transform(const IfcMatrix4& mat); + IfcVector3 Center() const; + void Append(const TempMesh& other); + bool IsEmpty() const; + void RemoveAdjacentDuplicates(); + 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<IfcVector3>& normals, bool normalize = true, size_t ofs = 0) const; + void Swap(TempMesh& other); +}; + +inline +bool TempMesh::IsEmpty() const { + return mVerts.empty() && mVertcnt.empty(); +} + + +// ------------------------------------------------------------------------------------------------ +// Temporary representation of an opening in a wall or a floor +// ------------------------------------------------------------------------------------------------ +struct TempOpening +{ + const IFC::Schema_2x3::IfcSolidModel *solid; + IfcVector3 extrusionDir; + + std::shared_ptr<TempMesh> profileMesh; + std::shared_ptr<TempMesh> profileMesh2D; + + // list of points generated for this opening. This is used to + // create connections between two opposing holes created + // from a single opening instance (two because walls tend to + // have two sides). If !empty(), the other side of the wall + // has already been processed. + std::vector<IfcVector3> wallPoints; + + // ------------------------------------------------------------------------------ + TempOpening() + : solid() + , extrusionDir() + , profileMesh() + { + } + + // ------------------------------------------------------------------------------ + TempOpening(const IFC::Schema_2x3::IfcSolidModel *solid, IfcVector3 extrusionDir, + std::shared_ptr<TempMesh> profileMesh, + std::shared_ptr<TempMesh> profileMesh2D) : + solid(solid), extrusionDir(extrusionDir), profileMesh(std::move(profileMesh)), profileMesh2D(std::move(profileMesh2D)) { + } + + // ------------------------------------------------------------------------------ + void Transform(const IfcMatrix4& mat); // defined later since TempMesh is not complete yet + + + + // ------------------------------------------------------------------------------ + // Helper to sort openings by distance from a given base point + struct DistanceSorter { + + DistanceSorter(const IfcVector3& base) : base(base) {} + + bool operator () (const TempOpening& a, const TempOpening& b) const { + return (a.profileMesh->Center()-base).SquareLength() < (b.profileMesh->Center()-base).SquareLength(); + } + + IfcVector3 base; + }; +}; + + +// ------------------------------------------------------------------------------------------------ +// Intermediate data storage during conversion. Keeps everything and a bit more. +// ------------------------------------------------------------------------------------------------ +struct ConversionData +{ + ConversionData(const STEP::DB& db, const IFC::Schema_2x3::IfcProject& proj, aiScene* out,const IFCImporter::Settings& settings) + : len_scale(1.0) + , angle_scale(-1.0) + , db(db) + , proj(proj) + , out(out) + , settings(settings) + , apply_openings() + , collect_openings() + {} + + ~ConversionData() { + std::for_each(meshes.begin(),meshes.end(),delete_fun<aiMesh>()); + std::for_each(materials.begin(),materials.end(),delete_fun<aiMaterial>()); + } + + IfcFloat len_scale, angle_scale; + bool plane_angle_in_radians; + + const STEP::DB& db; + const IFC::Schema_2x3::IfcProject& proj; + aiScene* out; + + IfcMatrix4 wcs; + std::vector<aiMesh*> meshes; + std::vector<aiMaterial*> materials; + + struct MeshCacheIndex { + const IFC::Schema_2x3::IfcRepresentationItem* item; unsigned int matindex; + MeshCacheIndex() : item(nullptr), matindex(0) { } + MeshCacheIndex(const IFC::Schema_2x3::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<MeshCacheIndex, std::set<unsigned int> > MeshCache; + MeshCache cached_meshes; + + typedef std::map<const IFC::Schema_2x3::IfcSurfaceStyle*, unsigned int> MaterialCache; + MaterialCache cached_materials; + + const IFCImporter::Settings& settings; + + // Intermediate arrays used to resolve openings in walls: only one of them + // can be given at a time. apply_openings if present if the current element + // is a wall and needs its openings to be poured into its geometry while + // collect_openings is present only if the current element is an + // IfcOpeningElement, for which all the geometry needs to be preserved + // for later processing by a parent, which is a wall. + std::vector<TempOpening>* apply_openings; + std::vector<TempOpening>* collect_openings; + + std::set<uint64_t> already_processed; +}; + + +// ------------------------------------------------------------------------------------------------ +// Binary predicate to compare vectors with a given, quadratic epsilon. +// ------------------------------------------------------------------------------------------------ +struct FuzzyVectorCompare { + + FuzzyVectorCompare(IfcFloat epsilon) : epsilon(epsilon) {} + bool operator()(const IfcVector3& a, const IfcVector3& b) { + return std::abs((a-b).SquareLength()) < epsilon; + } + + const IfcFloat epsilon; +}; + + +// ------------------------------------------------------------------------------------------------ +// Ordering predicate to totally order R^2 vectors first by x and then by y +// ------------------------------------------------------------------------------------------------ +struct XYSorter { + + // sort first by X coordinates, then by Y coordinates + bool operator () (const IfcVector2&a, const IfcVector2& b) const { + if (a.x == b.x) { + return a.y < b.y; + } + return a.x < b.x; + } +}; + + + +// conversion routines for common IFC entities, implemented in IFCUtil.cpp +void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in); +void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourOrFactor& in,ConversionData& conv,const aiColor4D* base); +void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint& in); +void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in); +void ConvertVector(IfcVector3& out, const Schema_2x3::IfcVector& in); +void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y, const IfcVector3& z); +void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D& in); +void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D& in); +void ConvertAxisPlacement(IfcVector3& axis, IfcVector3& pos, const IFC::Schema_2x3::IfcAxis1Placement& in); +void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement& in, ConversionData& conv); +void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTransformationOperator& op); +bool IsTrue(const Assimp::STEP::EXPRESS::BOOLEAN& in); +IfcFloat ConvertSIPrefix(const std::string& prefix); + + +// IFCProfile.cpp +bool ProcessProfile(const Schema_2x3::IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv); +bool ProcessCurve(const Schema_2x3::IfcCurve& curve, TempMesh& meshout, ConversionData& conv); + +// IFCMaterial.cpp +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 Schema_2x3::IfcRepresentationItem& item, unsigned int matid, std::set<unsigned int>& mesh_indices, ConversionData& conv); +void AssignAddedMeshes(std::set<unsigned int>& mesh_indices,aiNode* nd,ConversionData& /*conv*/); + +void ProcessSweptAreaSolid(const Schema_2x3::IfcSweptAreaSolid& swept, TempMesh& meshout, + ConversionData& conv); + +void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, TempMesh& result, + ConversionData& conv, bool collect_openings); + +// IFCBoolean.cpp + +void ProcessBoolean(const Schema_2x3::IfcBooleanResult& boolean, TempMesh& result, ConversionData& conv); +void ProcessBooleanHalfSpaceDifference(const Schema_2x3::IfcHalfSpaceSolid* hs, TempMesh& result, + const TempMesh& first_operand, + ConversionData& conv); + +void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const Schema_2x3::IfcPolygonalBoundedHalfSpace* hs, TempMesh& result, + const TempMesh& first_operand, + ConversionData& conv); +void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedAreaSolid* as, TempMesh& result, + const TempMesh& first_operand, + ConversionData& conv); + + +// IFCOpenings.cpp + +bool GenerateOpenings(std::vector<TempOpening>& openings, + TempMesh& curmesh, + bool check_intersection, + bool generate_connection_geometry, + const IfcVector3& wall_extrusion_axis = IfcVector3(0,1,0)); + + + +// IFCCurve.cpp + +// ------------------------------------------------------------------------------------------------ +// Custom exception for use by members of the Curve class +// ------------------------------------------------------------------------------------------------ +class CurveError { +public: + CurveError(const std::string& s) + : mStr(s) { + // empty + } + + std::string mStr; +}; + +// ------------------------------------------------------------------------------------------------ +// Temporary representation for an arbitrary sub-class of IfcCurve. Used to sample the curves +// to obtain a list of line segments. +// ------------------------------------------------------------------------------------------------ +class Curve { +protected: + Curve(const Schema_2x3::IfcCurve& base_entity, ConversionData& conv) + : base_entity(base_entity) + , conv(conv) { + // empty + } + +public: + typedef std::pair<IfcFloat, IfcFloat> ParamRange; + + virtual ~Curve() {} + + + // check if a curve is closed + virtual bool IsClosed() const = 0; + + // evaluate the curve at the given parametric position + virtual IfcVector3 Eval(IfcFloat p) const = 0; + + // try to match a point on the curve to a given parameter + // for self-intersecting curves, the result is not ambiguous and + // it is undefined which parameter is returned. + virtual bool ReverseEval(const IfcVector3& val, IfcFloat& paramOut) const; + + // get the range of the curve (both inclusive). + // +inf and -inf are valid return values, the curve is not bounded in such a case. + virtual std::pair<IfcFloat,IfcFloat> GetParametricRange() const = 0; + IfcFloat GetParametricRangeDelta() const; + + // estimate the number of sample points that this curve will require + virtual size_t EstimateSampleCount(IfcFloat start,IfcFloat end) const; + + // intelligently sample the curve based on the current settings + // and append the result to the mesh + virtual void SampleDiscrete(TempMesh& out,IfcFloat start,IfcFloat end) const; + +#ifdef ASSIMP_BUILD_DEBUG + // check if a particular parameter value lies within the well-defined range + bool InRange(IfcFloat) const; +#endif + static Curve* Convert(const IFC::Schema_2x3::IfcCurve&,ConversionData& conv); + +protected: + const Schema_2x3::IfcCurve& base_entity; + ConversionData& conv; +}; + + +// -------------------------------------------------------------------------------- +// A BoundedCurve always holds the invariant that GetParametricRange() +// never returns infinite values. +// -------------------------------------------------------------------------------- +class BoundedCurve : public Curve { +public: + BoundedCurve(const Schema_2x3::IfcBoundedCurve& entity, ConversionData& conv) + : Curve(entity,conv) + {} + +public: + + bool IsClosed() const; + +public: + + // sample the entire curve + void SampleDiscrete(TempMesh& out) const; + using Curve::SampleDiscrete; +}; + +// IfcProfile.cpp +bool ProcessCurve(const Schema_2x3::IfcCurve& curve, TempMesh& meshout, ConversionData& conv); +} +} + +#endif diff --git a/libs/assimp/code/AssetLib/IQM/IQMImporter.cpp b/libs/assimp/code/AssetLib/IQM/IQMImporter.cpp new file mode 100644 index 0000000..2bea66c --- /dev/null +++ b/libs/assimp/code/AssetLib/IQM/IQMImporter.cpp @@ -0,0 +1,322 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2021, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_IQM_IMPORTER + +#include <assimp/DefaultIOSystem.h> +#include <assimp/IOStreamBuffer.h> +#include <assimp/ai_assert.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> +#include <assimp/ByteSwapper.h> +#include <memory> +#include <numeric> + +#include "IQMImporter.h" +#include "iqm.h" + +// RESOURCES: +// http://sauerbraten.org/iqm/ +// https://github.com/lsalzman/iqm + + +inline void swap_block( uint32_t *block, size_t size ){ + (void)block; // suppress 'unreferenced formal parameter' MSVC warning + size >>= 2; + for ( size_t i = 0; i < size; ++i ) + AI_SWAP4( block[ i ] ); +} + +static const aiImporterDesc desc = { + "Inter-Quake Model Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "iqm" +}; + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +// Default constructor +IQMImporter::IQMImporter() : + mScene(nullptr) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns true, if file is a binary Inter-Quake Model file. +bool IQMImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const { + const std::string extension = GetExtension(pFile); + + if (extension == "iqm") + return true; + else if (!extension.length() || checkSig) { + if (!pIOHandler) { + return true; + } + /* + * don't use CheckMagicToken because that checks with swapped bytes too, leading to false + * positives. This magic is not uint32_t, but char[4], so memcmp is the best way + + const char* tokens[] = {"3DMO", "3dmo"}; + return CheckMagicToken(pIOHandler,pFile,tokens,2,0,4); + */ + std::unique_ptr<IOStream> pStream(pIOHandler->Open(pFile, "rb")); + unsigned char data[15]; + if (!pStream || 15 != pStream->Read(data, 1, 15)) { + return false; + } + return !memcmp(data, "INTERQUAKEMODEL", 15); + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc *IQMImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Model 3D import implementation +void IQMImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) { + // Read file into memory + std::unique_ptr<IOStream> pStream(pIOHandler->Open(file, "rb")); + if (!pStream.get()) { + throw DeadlyImportError("Failed to open file ", file, "."); + } + + // Get the file-size and validate it, throwing an exception when fails + const size_t fileSize = pStream->FileSize(); + if (fileSize < sizeof( iqmheader )) { + throw DeadlyImportError("IQM-file ", file, " is too small."); + } + std::vector<unsigned char> buffer(fileSize); + unsigned char *data = buffer.data(); + if (fileSize != pStream->Read(data, 1, fileSize)) { + throw DeadlyImportError("Failed to read the file ", file, "."); + } + + // get header + iqmheader &hdr = reinterpret_cast<iqmheader&>( *data ); + swap_block( &hdr.version, sizeof( iqmheader ) - sizeof( iqmheader::magic ) ); + + // extra check for header + if (memcmp(data, IQM_MAGIC, sizeof( IQM_MAGIC ) ) + || hdr.version != IQM_VERSION + || hdr.filesize != fileSize) { + throw DeadlyImportError("Bad binary header in file ", file, "."); + } + + ASSIMP_LOG_DEBUG("IQM: loading ", file); + + // create the root node + pScene->mRootNode = new aiNode( "<IQMRoot>" ); + // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system + pScene->mRootNode->mTransformation = aiMatrix4x4( + 1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f); + pScene->mRootNode->mNumMeshes = hdr.num_meshes; + pScene->mRootNode->mMeshes = new unsigned int[hdr.num_meshes]; + std::iota( pScene->mRootNode->mMeshes, pScene->mRootNode->mMeshes + pScene->mRootNode->mNumMeshes, 0 ); + + mScene = pScene; + + // Allocate output storage + pScene->mNumMeshes = 0; + pScene->mMeshes = new aiMesh *[hdr.num_meshes](); // Set arrays to zero to ensue proper destruction if an exception is raised + + pScene->mNumMaterials = 0; + pScene->mMaterials = new aiMaterial *[hdr.num_meshes](); + + // swap vertex arrays beforehand... + for( auto array = reinterpret_cast<iqmvertexarray*>( data + hdr.ofs_vertexarrays ), end = array + hdr.num_vertexarrays; array != end; ++array ) + { + swap_block( &array->type, sizeof( iqmvertexarray ) ); + } + + // Read all surfaces from the file + for( auto imesh = reinterpret_cast<iqmmesh*>( data + hdr.ofs_meshes ), end_ = imesh + hdr.num_meshes; imesh != end_; ++imesh ) + { + swap_block( &imesh->name, sizeof( iqmmesh ) ); + // Allocate output mesh & material + auto mesh = pScene->mMeshes[pScene->mNumMeshes++] = new aiMesh(); + mesh->mMaterialIndex = pScene->mNumMaterials; + auto mat = pScene->mMaterials[pScene->mNumMaterials++] = new aiMaterial(); + + { + auto text = reinterpret_cast<char*>( data + hdr.ofs_text ); + aiString name( text + imesh->material ); + mat->AddProperty( &name, AI_MATKEY_NAME ); + mat->AddProperty( &name, AI_MATKEY_TEXTURE_DIFFUSE(0) ); + } + + // Fill mesh information + mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + mesh->mNumFaces = 0; + mesh->mFaces = new aiFace[imesh->num_triangles]; + + // Fill in all triangles + for( auto tri = reinterpret_cast<iqmtriangle*>( data + hdr.ofs_triangles ) + imesh->first_triangle, end = tri + imesh->num_triangles; tri != end; ++tri ) + { + swap_block( tri->vertex, sizeof( tri->vertex ) ); + auto& face = mesh->mFaces[mesh->mNumFaces++]; + face.mNumIndices = 3; + face.mIndices = new unsigned int[3]{ tri->vertex[0] - imesh->first_vertex, + tri->vertex[2] - imesh->first_vertex, + tri->vertex[1] - imesh->first_vertex }; + } + + // Fill in all vertices + for( auto array = reinterpret_cast<const iqmvertexarray*>( data + hdr.ofs_vertexarrays ), end__ = array + hdr.num_vertexarrays; array != end__; ++array ) + { + const unsigned int nVerts = imesh->num_vertexes; + const unsigned int step = array->size; + + switch ( array->type ) + { + case IQM_POSITION: + if( array->format == IQM_FLOAT && step >= 3 ){ + mesh->mNumVertices = nVerts; + auto v = mesh->mVertices = new aiVector3D[nVerts]; + for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step, + end = f + nVerts * step; f != end; f += step, ++v ) + { + *v = { AI_BE( f[0] ), + AI_BE( f[1] ), + AI_BE( f[2] ) }; + } + } + break; + case IQM_TEXCOORD: + if( array->format == IQM_FLOAT && step >= 2) + { + auto v = mesh->mTextureCoords[0] = new aiVector3D[nVerts]; + mesh->mNumUVComponents[0] = 2; + for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step, + end = f + nVerts * step; f != end; f += step, ++v ) + { + *v = { AI_BE( f[0] ), + 1 - AI_BE( f[1] ), 0 }; + } + } + break; + case IQM_NORMAL: + if (array->format == IQM_FLOAT && step >= 3) + { + auto v = mesh->mNormals = new aiVector3D[nVerts]; + for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step, + end = f + nVerts * step; f != end; f += step, ++v ) + { + *v = { AI_BE( f[0] ), + AI_BE( f[1] ), + AI_BE( f[2] ) }; + } + } + break; + case IQM_COLOR: + if (array->format == IQM_UBYTE && step >= 3) + { + auto v = mesh->mColors[0] = new aiColor4D[nVerts]; + for( auto f = ( data + array->offset ) + imesh->first_vertex * step, + end = f + nVerts * step; f != end; f += step, ++v ) + { + *v = { ( f[0] ) / 255.f, + ( f[1] ) / 255.f, + ( f[2] ) / 255.f, + step == 3? 1 : ( f[3] ) / 255.f }; + } + } + else if (array->format == IQM_FLOAT && step >= 3) + { + auto v = mesh->mColors[0] = new aiColor4D[nVerts]; + for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step, + end = f + nVerts * step; f != end; f += step, ++v ) + { + *v = { AI_BE( f[0] ), + AI_BE( f[1] ), + AI_BE( f[2] ), + step == 3? 1 : AI_BE( f[3] ) }; + } + } + break; + case IQM_TANGENT: +#if 0 + if (array->format == IQM_FLOAT && step >= 3) + { + auto v = mesh->mTangents = new aiVector3D[nVerts]; + for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step, + end = f + nVerts * step; f != end; f += step, ++v ) + { + *v = { AI_BE( f[0] ), + AI_BE( f[1] ), + AI_BE( f[2] ) }; + } + } +#endif + break; + case IQM_BLENDINDEXES: + case IQM_BLENDWEIGHTS: + case IQM_CUSTOM: + break; // these attributes are not relevant. + + default: + break; + } + } + } +} + + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_IQM_IMPORTER diff --git a/libs/assimp/code/AssetLib/IQM/IQMImporter.h b/libs/assimp/code/AssetLib/IQM/IQMImporter.h new file mode 100644 index 0000000..fbeaff8 --- /dev/null +++ b/libs/assimp/code/AssetLib/IQM/IQMImporter.h @@ -0,0 +1,78 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2021, 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 IQMImporter.h +* @brief Declares the importer class to read a scene from an Inter-Quake Model file +*/ + +#pragma once + +#ifndef ASSIMP_BUILD_NO_IQM_IMPORTER + +#include <assimp/BaseImporter.h> +#include <assimp/material.h> + +namespace Assimp { + +class IQMImporter : public BaseImporter { +public: + /// \brief Default constructor + IQMImporter(); + ~IQMImporter() override {} + + /// \brief Returns whether the class can handle the format of the given file. + /// \remark See BaseImporter::CanRead() for details. + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + +protected: + //! \brief Appends the supported extension. + const aiImporterDesc *GetInfo() const override; + + //! \brief File import implementation. + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + +private: + aiScene *mScene = nullptr; // the scene to import to +}; + +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_IQM_IMPORTER diff --git a/libs/assimp/code/AssetLib/IQM/iqm.h b/libs/assimp/code/AssetLib/IQM/iqm.h new file mode 100644 index 0000000..a450504 --- /dev/null +++ b/libs/assimp/code/AssetLib/IQM/iqm.h @@ -0,0 +1,134 @@ +#ifndef __IQM_H__ +#define __IQM_H__ + +#define IQM_MAGIC "INTERQUAKEMODEL" +#define IQM_VERSION 2 + +struct iqmheader +{ + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; +}; + +struct iqmmesh +{ + unsigned int name; + unsigned int material; + unsigned int first_vertex, num_vertexes; + unsigned int first_triangle, num_triangles; +}; + +enum +{ + IQM_POSITION = 0, + IQM_TEXCOORD = 1, + IQM_NORMAL = 2, + IQM_TANGENT = 3, + IQM_BLENDINDEXES = 4, + IQM_BLENDWEIGHTS = 5, + IQM_COLOR = 6, + IQM_CUSTOM = 0x10 +}; + +enum +{ + IQM_BYTE = 0, + IQM_UBYTE = 1, + IQM_SHORT = 2, + IQM_USHORT = 3, + IQM_INT = 4, + IQM_UINT = 5, + IQM_HALF = 6, + IQM_FLOAT = 7, + IQM_DOUBLE = 8 +}; + +struct iqmtriangle +{ + unsigned int vertex[3]; +}; + +struct iqmadjacency +{ + unsigned int triangle[3]; +}; + +struct iqmjointv1 +{ + unsigned int name; + int parent; + float translate[3], rotate[3], scale[3]; +}; + +struct iqmjoint +{ + unsigned int name; + int parent; + float translate[3], rotate[4], scale[3]; +}; + +struct iqmposev1 +{ + int parent; + unsigned int mask; + float channeloffset[9]; + float channelscale[9]; +}; + +struct iqmpose +{ + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; +}; + +struct iqmanim +{ + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; +}; + +enum +{ + IQM_LOOP = 1<<0 +}; + +struct iqmvertexarray +{ + unsigned int type; + unsigned int flags; + unsigned int format; + unsigned int size; + unsigned int offset; +}; + +struct iqmbounds +{ + float bbmin[3], bbmax[3]; + float xyradius, radius; +}; + +struct iqmextension +{ + unsigned int name; + unsigned int num_data, ofs_data; + unsigned int ofs_extensions; // pointer to next extension +}; + +#endif + diff --git a/libs/assimp/code/AssetLib/Irr/IRRLoader.cpp b/libs/assimp/code/AssetLib/Irr/IRRLoader.cpp new file mode 100644 index 0000000..0061634 --- /dev/null +++ b/libs/assimp/code/AssetLib/Irr/IRRLoader.cpp @@ -0,0 +1,1359 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IRRLoader.cpp + * @brief Implementation of the Irr importer class + */ + +#ifndef ASSIMP_BUILD_NO_IRR_IMPORTER + +#include "AssetLib/Irr/IRRLoader.h" +#include "Common/Importer.h" + +#include <assimp/GenericProperty.h> +#include <assimp/MathFunctions.h> +#include <assimp/ParsingUtils.h> +#include <assimp/SceneCombiner.h> +#include <assimp/StandardShapes.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/material.h> +#include <assimp/mesh.h> +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> + +#include <memory> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Irrlicht Scene Reader", + "", + "", + "http://irrlicht.sourceforge.net/", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "irr xml" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +IRRImporter::IRRImporter() : + fps(), configSpeedFlag() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +IRRImporter::~IRRImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool IRRImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "irr_scene" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc *IRRImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +void IRRImporter::SetupProperties(const Importer *pImp) { + // read the output frame rate of all node animation channels + fps = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IRR_ANIM_FPS, 100); + if (fps < 10.) { + ASSIMP_LOG_ERROR("IRR: Invalid FPS configuration"); + fps = 100; + } + + // AI_CONFIG_FAVOUR_SPEED + configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED, 0)); +} + +// ------------------------------------------------------------------------------------------------ +// Build a mesh that consists of a single squad (a side of a skybox) +aiMesh *IRRImporter::BuildSingleQuadMesh(const SkyboxVertex &v1, + const SkyboxVertex &v2, + const SkyboxVertex &v3, + const SkyboxVertex &v4) { + // allocate and prepare the mesh + aiMesh *out = new aiMesh(); + + out->mPrimitiveTypes = aiPrimitiveType_POLYGON; + out->mNumFaces = 1; + + // build the face + out->mFaces = new aiFace[1]; + aiFace &face = out->mFaces[0]; + + face.mNumIndices = 4; + face.mIndices = new unsigned int[4]; + for (unsigned int i = 0; i < 4; ++i) + face.mIndices[i] = i; + + out->mNumVertices = 4; + + // copy vertex positions + aiVector3D *vec = out->mVertices = new aiVector3D[4]; + *vec++ = v1.position; + *vec++ = v2.position; + *vec++ = v3.position; + *vec = v4.position; + + // copy vertex normals + vec = out->mNormals = new aiVector3D[4]; + *vec++ = v1.normal; + *vec++ = v2.normal; + *vec++ = v3.normal; + *vec = v4.normal; + + // copy texture coordinates + vec = out->mTextureCoords[0] = new aiVector3D[4]; + *vec++ = v1.uv; + *vec++ = v2.uv; + *vec++ = v3.uv; + *vec = v4.uv; + return out; +} + +// ------------------------------------------------------------------------------------------------ +void IRRImporter::BuildSkybox(std::vector<aiMesh *> &meshes, std::vector<aiMaterial *> materials) { + // Update the material of the skybox - replace the name and disable shading for skyboxes. + for (unsigned int i = 0; i < 6; ++i) { + aiMaterial *out = (aiMaterial *)(*(materials.end() - (6 - i))); + + aiString s; + s.length = ::ai_snprintf(s.data, MAXLEN, "SkyboxSide_%u", i); + out->AddProperty(&s, AI_MATKEY_NAME); + + int shading = aiShadingMode_NoShading; + out->AddProperty(&shading, 1, AI_MATKEY_SHADING_MODEL); + } + + // Skyboxes are much more difficult. They are represented + // by six single planes with different textures, so we'll + // need to build six meshes. + + const ai_real l = 10.0; // the size used by Irrlicht + + // FRONT SIDE + meshes.push_back(BuildSingleQuadMesh( + SkyboxVertex(-l, -l, -l, 0, 0, 1, 1.0, 1.0), + SkyboxVertex(l, -l, -l, 0, 0, 1, 0.0, 1.0), + SkyboxVertex(l, l, -l, 0, 0, 1, 0.0, 0.0), + SkyboxVertex(-l, l, -l, 0, 0, 1, 1.0, 0.0))); + meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 6u); + + // LEFT SIDE + meshes.push_back(BuildSingleQuadMesh( + SkyboxVertex(l, -l, -l, -1, 0, 0, 1.0, 1.0), + SkyboxVertex(l, -l, l, -1, 0, 0, 0.0, 1.0), + SkyboxVertex(l, l, l, -1, 0, 0, 0.0, 0.0), + SkyboxVertex(l, l, -l, -1, 0, 0, 1.0, 0.0))); + meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 5u); + + // BACK SIDE + meshes.push_back(BuildSingleQuadMesh( + SkyboxVertex(l, -l, l, 0, 0, -1, 1.0, 1.0), + SkyboxVertex(-l, -l, l, 0, 0, -1, 0.0, 1.0), + SkyboxVertex(-l, l, l, 0, 0, -1, 0.0, 0.0), + SkyboxVertex(l, l, l, 0, 0, -1, 1.0, 0.0))); + meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 4u); + + // RIGHT SIDE + meshes.push_back(BuildSingleQuadMesh( + SkyboxVertex(-l, -l, l, 1, 0, 0, 1.0, 1.0), + SkyboxVertex(-l, -l, -l, 1, 0, 0, 0.0, 1.0), + SkyboxVertex(-l, l, -l, 1, 0, 0, 0.0, 0.0), + SkyboxVertex(-l, l, l, 1, 0, 0, 1.0, 0.0))); + meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 3u); + + // TOP SIDE + meshes.push_back(BuildSingleQuadMesh( + SkyboxVertex(l, l, -l, 0, -1, 0, 1.0, 1.0), + SkyboxVertex(l, l, l, 0, -1, 0, 0.0, 1.0), + SkyboxVertex(-l, l, l, 0, -1, 0, 0.0, 0.0), + SkyboxVertex(-l, l, -l, 0, -1, 0, 1.0, 0.0))); + meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 2u); + + // BOTTOM SIDE + meshes.push_back(BuildSingleQuadMesh( + SkyboxVertex(l, -l, l, 0, 1, 0, 0.0, 0.0), + SkyboxVertex(l, -l, -l, 0, 1, 0, 1.0, 0.0), + SkyboxVertex(-l, -l, -l, 0, 1, 0, 1.0, 1.0), + SkyboxVertex(-l, -l, l, 0, 1, 0, 0.0, 1.0))); + meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 1u); +} + +// ------------------------------------------------------------------------------------------------ +void IRRImporter::CopyMaterial(std::vector<aiMaterial *> &materials, + std::vector<std::pair<aiMaterial *, unsigned int>> &inmaterials, + unsigned int &defMatIdx, + aiMesh *mesh) { + if (inmaterials.empty()) { + // Do we have a default material? If not we need to create one + if (UINT_MAX == defMatIdx) { + defMatIdx = (unsigned int)materials.size(); + //TODO: add this materials to someone? + /*aiMaterial* mat = new aiMaterial(); + + aiString s; + s.Set(AI_DEFAULT_MATERIAL_NAME); + mat->AddProperty(&s,AI_MATKEY_NAME); + + aiColor3D c(0.6f,0.6f,0.6f); + mat->AddProperty(&c,1,AI_MATKEY_COLOR_DIFFUSE);*/ + } + mesh->mMaterialIndex = defMatIdx; + return; + } else if (inmaterials.size() > 1) { + ASSIMP_LOG_INFO("IRR: Skipping additional materials"); + } + + mesh->mMaterialIndex = (unsigned int)materials.size(); + materials.push_back(inmaterials[0].first); +} + +// ------------------------------------------------------------------------------------------------ +inline int ClampSpline(int idx, int size) { + return (idx < 0 ? size + idx : (idx >= size ? idx - size : idx)); +} + +// ------------------------------------------------------------------------------------------------ +inline void FindSuitableMultiple(int &angle) { + if (angle < 3) + angle = 3; + else if (angle < 10) + angle = 10; + else if (angle < 20) + angle = 20; + else if (angle < 30) + angle = 30; +} + +// ------------------------------------------------------------------------------------------------ +void IRRImporter::ComputeAnimations(Node *root, aiNode *real, std::vector<aiNodeAnim *> &anims) { + ai_assert(nullptr != root && nullptr != real); + + // XXX totally WIP - doesn't produce proper results, need to evaluate + // whether there's any use for Irrlicht's proprietary scene format + // outside Irrlicht ... + // This also applies to the above function of FindSuitableMultiple and ClampSpline which are + // solely used in this function + + if (root->animators.empty()) { + return; + } + unsigned int total(0); + for (std::list<Animator>::iterator it = root->animators.begin(); it != root->animators.end(); ++it) { + if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER) { + ASSIMP_LOG_WARN("IRR: Skipping unknown or unsupported animator"); + continue; + } + ++total; + } + if (!total) { + return; + } else if (1 == total) { + ASSIMP_LOG_WARN("IRR: Adding dummy nodes to simulate multiple animators"); + } + + // NOTE: 1 tick == i millisecond + + unsigned int cur = 0; + for (std::list<Animator>::iterator it = root->animators.begin(); + it != root->animators.end(); ++it) { + if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER) continue; + + Animator &in = *it; + aiNodeAnim *anim = new aiNodeAnim(); + + if (cur != total - 1) { + // Build a new name - a prefix instead of a suffix because it is + // easier to check against + anim->mNodeName.length = ::ai_snprintf(anim->mNodeName.data, MAXLEN, + "$INST_DUMMY_%i_%s", total - 1, + (root->name.length() ? root->name.c_str() : "")); + + // we'll also need to insert a dummy in the node hierarchy. + aiNode *dummy = new aiNode(); + + for (unsigned int i = 0; i < real->mParent->mNumChildren; ++i) + if (real->mParent->mChildren[i] == real) + real->mParent->mChildren[i] = dummy; + + dummy->mParent = real->mParent; + dummy->mName = anim->mNodeName; + + dummy->mNumChildren = 1; + dummy->mChildren = new aiNode *[dummy->mNumChildren]; + dummy->mChildren[0] = real; + + // the transformation matrix of the dummy node is the identity + + real->mParent = dummy; + } else + anim->mNodeName.Set(root->name); + ++cur; + + switch (in.type) { + case Animator::ROTATION: { + // ----------------------------------------------------- + // find out how long a full rotation will take + // This is the least common multiple of 360.f and all + // three euler angles. Although we'll surely find a + // possible multiple (haha) it could be somewhat large + // for our purposes. So we need to modify the angles + // here in order to get good results. + // ----------------------------------------------------- + int angles[3]; + angles[0] = (int)(in.direction.x * 100); + angles[1] = (int)(in.direction.y * 100); + angles[2] = (int)(in.direction.z * 100); + + angles[0] %= 360; + angles[1] %= 360; + angles[2] %= 360; + + if ((angles[0] * angles[1]) != 0 && (angles[1] * angles[2]) != 0) { + FindSuitableMultiple(angles[0]); + FindSuitableMultiple(angles[1]); + FindSuitableMultiple(angles[2]); + } + + int lcm = 360; + + if (angles[0]) + lcm = Math::lcm(lcm, angles[0]); + + if (angles[1]) + lcm = Math::lcm(lcm, angles[1]); + + if (angles[2]) + lcm = Math::lcm(lcm, angles[2]); + + if (360 == lcm) + break; + + + // find out how many time units we'll need for the finest + // track (in seconds) - this defines the number of output + // keys (fps * seconds) + float max = 0.f; + if (angles[0]) + max = (float)lcm / angles[0]; + if (angles[1]) + max = std::max(max, (float)lcm / angles[1]); + if (angles[2]) + max = std::max(max, (float)lcm / angles[2]); + + anim->mNumRotationKeys = (unsigned int)(max * fps); + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; + + // begin with a zero angle + aiVector3D angle; + for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { + // build the quaternion for the given euler angles + aiQuatKey &q = anim->mRotationKeys[i]; + + q.mValue = aiQuaternion(angle.x, angle.y, angle.z); + q.mTime = (double)i; + + // increase the angle + angle += in.direction; + } + + // This animation is repeated and repeated ... + anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT; + } break; + + case Animator::FLY_CIRCLE: { + // ----------------------------------------------------- + // Find out how much time we'll need to perform a + // full circle. + // ----------------------------------------------------- + const double seconds = (1. / in.speed) / 1000.; + const double tdelta = 1000. / fps; + + anim->mNumPositionKeys = (unsigned int)(fps * seconds); + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + + // from Irrlicht, what else should we do than copying it? + aiVector3D vecU, vecV; + if (in.direction.y) { + vecV = aiVector3D(50, 0, 0) ^ in.direction; + } else + vecV = aiVector3D(0, 50, 00) ^ in.direction; + vecV.Normalize(); + vecU = (vecV ^ in.direction).Normalize(); + + // build the output keys + for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { + aiVectorKey &key = anim->mPositionKeys[i]; + key.mTime = i * tdelta; + + const ai_real t = (ai_real)(in.speed * key.mTime); + key.mValue = in.circleCenter + in.circleRadius * ((vecU * std::cos(t)) + (vecV * std::sin(t))); + } + + // This animation is repeated and repeated ... + anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT; + } break; + + case Animator::FLY_STRAIGHT: { + anim->mPostState = anim->mPreState = (in.loop ? aiAnimBehaviour_REPEAT : aiAnimBehaviour_CONSTANT); + const double seconds = in.timeForWay / 1000.; + const double tdelta = 1000. / fps; + + anim->mNumPositionKeys = (unsigned int)(fps * seconds); + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + + aiVector3D diff = in.direction - in.circleCenter; + const ai_real lengthOfWay = diff.Length(); + diff.Normalize(); + + const double timeFactor = lengthOfWay / in.timeForWay; + + // build the output keys + for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { + aiVectorKey &key = anim->mPositionKeys[i]; + key.mTime = i * tdelta; + key.mValue = in.circleCenter + diff * ai_real(timeFactor * key.mTime); + } + } break; + + case Animator::FOLLOW_SPLINE: { + // repeat outside the defined time range + anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT; + const int size = (int)in.splineKeys.size(); + if (!size) { + // We have no point in the spline. That's bad. Really bad. + ASSIMP_LOG_WARN("IRR: Spline animators with no points defined"); + + delete anim; + anim = nullptr; + break; + } else if (size == 1) { + // We have just one point in the spline so we don't need the full calculation + anim->mNumPositionKeys = 1; + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + + anim->mPositionKeys[0].mValue = in.splineKeys[0].mValue; + anim->mPositionKeys[0].mTime = 0.f; + break; + } + + unsigned int ticksPerFull = 15; + anim->mNumPositionKeys = (unsigned int)(ticksPerFull * fps); + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + + for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { + aiVectorKey &key = anim->mPositionKeys[i]; + + const ai_real dt = (i * in.speed * ai_real(0.001)); + const ai_real u = dt - std::floor(dt); + const int idx = (int)std::floor(dt) % size; + + // get the 4 current points to evaluate the spline + const aiVector3D &p0 = in.splineKeys[ClampSpline(idx - 1, size)].mValue; + const aiVector3D &p1 = in.splineKeys[ClampSpline(idx + 0, size)].mValue; + const aiVector3D &p2 = in.splineKeys[ClampSpline(idx + 1, size)].mValue; + const aiVector3D &p3 = in.splineKeys[ClampSpline(idx + 2, size)].mValue; + + // compute polynomials + const ai_real u2 = u * u; + const ai_real u3 = u2 * 2; + + const ai_real h1 = ai_real(2.0) * u3 - ai_real(3.0) * u2 + ai_real(1.0); + const ai_real h2 = ai_real(-2.0) * u3 + ai_real(3.0) * u3; + const ai_real h3 = u3 - ai_real(2.0) * u3; + const ai_real h4 = u3 - u2; + + // compute the spline tangents + const aiVector3D t1 = (p2 - p0) * in.tightness; + aiVector3D t2 = (p3 - p1) * in.tightness; + + // and use them to get the interpolated point + t2 = (h1 * p1 + p2 * h2 + t1 * h3 + h4 * t2); + + // build a simple translation matrix from it + key.mValue = t2; + key.mTime = (double)i; + } + } break; + default: + // UNKNOWN , OTHER + break; + }; + if (anim) { + anims.push_back(anim); + ++total; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// This function is maybe more generic than we'd need it here +void SetupMapping(aiMaterial *mat, aiTextureMapping mode, const aiVector3D &axis = aiVector3D(0.f, 0.f, -1.f)) { + if (nullptr == mat) { + return; + } + + // Check whether there are texture properties defined - setup + // the desired texture mapping mode for all of them and ignore + // all UV settings we might encounter. WE HAVE NO UVS! + + std::vector<aiMaterialProperty *> p; + p.reserve(mat->mNumProperties + 1); + + for (unsigned int i = 0; i < mat->mNumProperties; ++i) { + aiMaterialProperty *prop = mat->mProperties[i]; + if (!::strcmp(prop->mKey.data, "$tex.file")) { + // Setup the mapping key + aiMaterialProperty *m = new aiMaterialProperty(); + m->mKey.Set("$tex.mapping"); + m->mIndex = prop->mIndex; + m->mSemantic = prop->mSemantic; + m->mType = aiPTI_Integer; + + m->mDataLength = 4; + m->mData = new char[4]; + *((int *)m->mData) = mode; + + p.push_back(prop); + p.push_back(m); + + // Setup the mapping axis + if (mode == aiTextureMapping_CYLINDER || mode == aiTextureMapping_PLANE || mode == aiTextureMapping_SPHERE) { + m = new aiMaterialProperty(); + m->mKey.Set("$tex.mapaxis"); + m->mIndex = prop->mIndex; + m->mSemantic = prop->mSemantic; + m->mType = aiPTI_Float; + + m->mDataLength = 12; + m->mData = new char[12]; + *((aiVector3D *)m->mData) = axis; + p.push_back(m); + } + } else if (!::strcmp(prop->mKey.data, "$tex.uvwsrc")) { + delete mat->mProperties[i]; + } else + p.push_back(prop); + } + + if (p.empty()) return; + + // rebuild the output array + if (p.size() > mat->mNumAllocated) { + delete[] mat->mProperties; + mat->mProperties = new aiMaterialProperty *[p.size() * 2]; + + mat->mNumAllocated = static_cast<unsigned int>(p.size() * 2); + } + mat->mNumProperties = (unsigned int)p.size(); + ::memcpy(mat->mProperties, &p[0], sizeof(void *) * mat->mNumProperties); +} + +// ------------------------------------------------------------------------------------------------ +void IRRImporter::GenerateGraph(Node *root, aiNode *rootOut, aiScene *scene, + BatchLoader &batch, + std::vector<aiMesh *> &meshes, + std::vector<aiNodeAnim *> &anims, + std::vector<AttachmentInfo> &attach, + std::vector<aiMaterial *> &materials, + unsigned int &defMatIdx) { + unsigned int oldMeshSize = (unsigned int)meshes.size(); + //unsigned int meshTrafoAssign = 0; + + // Now determine the type of the node + switch (root->type) { + case Node::ANIMMESH: + case Node::MESH: { + if (!root->meshPath.length()) + break; + + // Get the loaded mesh from the scene and add it to + // the list of all scenes to be attached to the + // graph we're currently building + aiScene *localScene = batch.GetImport(root->id); + if (!localScene) { + ASSIMP_LOG_ERROR("IRR: Unable to load external file: ", root->meshPath); + break; + } + attach.push_back(AttachmentInfo(localScene, rootOut)); + + // Now combine the material we've loaded for this mesh + // with the real materials we got from the file. As we + // don't execute any pp-steps on the file, the numbers + // should be equal. If they are not, we can impossibly + // do this ... + if (root->materials.size() != (unsigned int)localScene->mNumMaterials) { + ASSIMP_LOG_WARN("IRR: Failed to match imported materials " + "with the materials found in the IRR scene file"); + + break; + } + for (unsigned int i = 0; i < localScene->mNumMaterials; ++i) { + // Delete the old material, we don't need it anymore + delete localScene->mMaterials[i]; + + std::pair<aiMaterial *, unsigned int> &src = root->materials[i]; + localScene->mMaterials[i] = src.first; + } + + // NOTE: Each mesh should have exactly one material assigned, + // but we do it in a separate loop if this behavior changes + // in future. + for (unsigned int i = 0; i < localScene->mNumMeshes; ++i) { + // Process material flags + aiMesh *mesh = localScene->mMeshes[i]; + + // If "trans_vertex_alpha" mode is enabled, search all vertex colors + // and check whether they have a common alpha value. This is quite + // often the case so we can simply extract it to a shared oacity + // value. + std::pair<aiMaterial *, unsigned int> &src = root->materials[mesh->mMaterialIndex]; + aiMaterial *mat = (aiMaterial *)src.first; + + if (mesh->HasVertexColors(0) && src.second & AI_IRRMESH_MAT_trans_vertex_alpha) { + bool bdo = true; + for (unsigned int a = 1; a < mesh->mNumVertices; ++a) { + + if (mesh->mColors[0][a].a != mesh->mColors[0][a - 1].a) { + bdo = false; + break; + } + } + if (bdo) { + ASSIMP_LOG_INFO("IRR: Replacing mesh vertex alpha with common opacity"); + + for (unsigned int a = 0; a < mesh->mNumVertices; ++a) + mesh->mColors[0][a].a = 1.f; + + mat->AddProperty(&mesh->mColors[0][0].a, 1, AI_MATKEY_OPACITY); + } + } + + // If we have a second texture coordinate set and a second texture + // (either light-map, normal-map, 2layered material) we need to + // setup the correct UV index for it. The texture can either + // be diffuse (light-map & 2layer) or a normal map (normal & parallax) + if (mesh->HasTextureCoords(1)) { + + int idx = 1; + if (src.second & (AI_IRRMESH_MAT_solid_2layer | AI_IRRMESH_MAT_lightmap)) { + mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_DIFFUSE(0)); + } else if (src.second & AI_IRRMESH_MAT_normalmap_solid) { + mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_NORMALS(0)); + } + } + } + } break; + + case Node::LIGHT: + case Node::CAMERA: + + // We're already finished with lights and cameras + break; + + case Node::SPHERE: { + // Generate the sphere model. Our input parameter to + // the sphere generation algorithm is the number of + // subdivisions of each triangle - but here we have + // the number of polygons on a specific axis. Just + // use some hard-coded limits to approximate this ... + unsigned int mul = root->spherePolyCountX * root->spherePolyCountY; + if (mul < 100) + mul = 2; + else if (mul < 300) + mul = 3; + else + mul = 4; + + meshes.push_back(StandardShapes::MakeMesh(mul, + &StandardShapes::MakeSphere)); + + // Adjust scaling + root->scaling *= root->sphereRadius / 2; + + // Copy one output material + CopyMaterial(materials, root->materials, defMatIdx, meshes.back()); + + // Now adjust this output material - if there is a first texture + // set, setup spherical UV mapping around the Y axis. + SetupMapping((aiMaterial *)materials.back(), aiTextureMapping_SPHERE); + } break; + + case Node::CUBE: { + // Generate an unit cube first + meshes.push_back(StandardShapes::MakeMesh( + &StandardShapes::MakeHexahedron)); + + // Adjust scaling + root->scaling *= root->sphereRadius; + + // Copy one output material + CopyMaterial(materials, root->materials, defMatIdx, meshes.back()); + + // Now adjust this output material - if there is a first texture + // set, setup cubic UV mapping + SetupMapping((aiMaterial *)materials.back(), aiTextureMapping_BOX); + } break; + + case Node::SKYBOX: { + // A sky-box is defined by six materials + if (root->materials.size() < 6) { + ASSIMP_LOG_ERROR("IRR: There should be six materials for a skybox"); + break; + } + + // copy those materials and generate 6 meshes for our new sky-box + materials.reserve(materials.size() + 6); + for (unsigned int i = 0; i < 6; ++i) + materials.insert(materials.end(), root->materials[i].first); + + BuildSkybox(meshes, materials); + + // ************************************************************* + // Skyboxes will require a different code path for rendering, + // so there must be a way for the user to add special support + // for IRR skyboxes. We add a 'IRR.SkyBox_' prefix to the node. + // ************************************************************* + root->name = "IRR.SkyBox_" + root->name; + ASSIMP_LOG_INFO("IRR: Loading skybox, this will " + "require special handling to be displayed correctly"); + } break; + + case Node::TERRAIN: { + // to support terrains, we'd need to have a texture decoder + ASSIMP_LOG_ERROR("IRR: Unsupported node - TERRAIN"); + } break; + default: + // DUMMY + break; + }; + + // Check whether we added a mesh (or more than one ...). In this case + // we'll also need to attach it to the node + if (oldMeshSize != (unsigned int)meshes.size()) { + + rootOut->mNumMeshes = (unsigned int)meshes.size() - oldMeshSize; + rootOut->mMeshes = new unsigned int[rootOut->mNumMeshes]; + + for (unsigned int a = 0; a < rootOut->mNumMeshes; ++a) { + rootOut->mMeshes[a] = oldMeshSize + a; + } + } + + // Setup the name of this node + rootOut->mName.Set(root->name); + + // Now compute the final local transformation matrix of the + // node from the given translation, rotation and scaling values. + // (the rotation is given in Euler angles, XYZ order) + //std::swap((float&)root->rotation.z,(float&)root->rotation.y); + rootOut->mTransformation.FromEulerAnglesXYZ(AI_DEG_TO_RAD(root->rotation)); + + // apply scaling + aiMatrix4x4 &mat = rootOut->mTransformation; + mat.a1 *= root->scaling.x; + mat.b1 *= root->scaling.x; + mat.c1 *= root->scaling.x; + mat.a2 *= root->scaling.y; + mat.b2 *= root->scaling.y; + mat.c2 *= root->scaling.y; + mat.a3 *= root->scaling.z; + mat.b3 *= root->scaling.z; + mat.c3 *= root->scaling.z; + + // apply translation + mat.a4 += root->position.x; + mat.b4 += root->position.y; + mat.c4 += root->position.z; + + // now compute animations for the node + ComputeAnimations(root, rootOut, anims); + + // Add all children recursively. First allocate enough storage + // for them, then call us again + rootOut->mNumChildren = (unsigned int)root->children.size(); + if (rootOut->mNumChildren) { + + rootOut->mChildren = new aiNode *[rootOut->mNumChildren]; + for (unsigned int i = 0; i < rootOut->mNumChildren; ++i) { + + aiNode *node = rootOut->mChildren[i] = new aiNode(); + node->mParent = rootOut; + GenerateGraph(root->children[i], node, scene, batch, meshes, + anims, attach, materials, defMatIdx); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void IRRImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile)); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open IRR file ", pFile); + } + + // Construct the irrXML parser + XmlParser st; + if (!st.parse( file.get() )) { + throw DeadlyImportError("XML parse error while loading IRR file ", pFile); + } + pugi::xml_node rootElement = st.getRootNode(); + + // The root node of the scene + Node *root = new Node(Node::DUMMY); + root->parent = nullptr; + root->name = "<IRRSceneRoot>"; + + // Current node parent + Node *curParent = root; + + // Scene-graph node we're currently working on + Node *curNode = nullptr; + + // List of output cameras + std::vector<aiCamera *> cameras; + + // List of output lights + std::vector<aiLight *> lights; + + // Batch loader used to load external models + BatchLoader batch(pIOHandler); + // batch.SetBasePath(pFile); + + cameras.reserve(5); + lights.reserve(5); + + bool inMaterials = false, inAnimator = false; + unsigned int guessedAnimCnt = 0, guessedMeshCnt = 0, guessedMatCnt = 0; + + // Parse the XML file + + //while (reader->read()) { + for (pugi::xml_node child : rootElement.children()) + switch (child.type()) { + case pugi::node_element: + if (!ASSIMP_stricmp(child.name(), "node")) { + // *********************************************************************** + /* What we're going to do with the node depends + * on its type: + * + * "mesh" - Load a mesh from an external file + * "cube" - Generate a cube + * "skybox" - Generate a skybox + * "light" - A light source + * "sphere" - Generate a sphere mesh + * "animatedMesh" - Load an animated mesh from an external file + * and join its animation channels with ours. + * "empty" - A dummy node + * "camera" - A camera + * "terrain" - a terrain node (data comes from a heightmap) + * "billboard", "" + * + * Each of these nodes can be animated and all can have multiple + * materials assigned (except lights, cameras and dummies, of course). + */ + // *********************************************************************** + //const char *sz = reader->getAttributeValueSafe("type"); + pugi::xml_attribute attrib = child.attribute("type"); + Node *nd; + if (!ASSIMP_stricmp(attrib.name(), "mesh") || !ASSIMP_stricmp(attrib.name(), "octTree")) { + // OctTree's and meshes are treated equally + nd = new Node(Node::MESH); + } else if (!ASSIMP_stricmp(attrib.name(), "cube")) { + nd = new Node(Node::CUBE); + ++guessedMeshCnt; + } else if (!ASSIMP_stricmp(attrib.name(), "skybox")) { + nd = new Node(Node::SKYBOX); + guessedMeshCnt += 6; + } else if (!ASSIMP_stricmp(attrib.name(), "camera")) { + nd = new Node(Node::CAMERA); + + // Setup a temporary name for the camera + aiCamera *cam = new aiCamera(); + cam->mName.Set(nd->name); + cameras.push_back(cam); + } else if (!ASSIMP_stricmp(attrib.name(), "light")) { + nd = new Node(Node::LIGHT); + + // Setup a temporary name for the light + aiLight *cam = new aiLight(); + cam->mName.Set(nd->name); + lights.push_back(cam); + } else if (!ASSIMP_stricmp(attrib.name(), "sphere")) { + nd = new Node(Node::SPHERE); + ++guessedMeshCnt; + } else if (!ASSIMP_stricmp(attrib.name(), "animatedMesh")) { + nd = new Node(Node::ANIMMESH); + } else if (!ASSIMP_stricmp(attrib.name(), "empty")) { + nd = new Node(Node::DUMMY); + } else if (!ASSIMP_stricmp(attrib.name(), "terrain")) { + nd = new Node(Node::TERRAIN); + } else if (!ASSIMP_stricmp(attrib.name(), "billBoard")) { + // We don't support billboards, so ignore them + ASSIMP_LOG_ERROR("IRR: Billboards are not supported by Assimp"); + nd = new Node(Node::DUMMY); + } else { + ASSIMP_LOG_WARN("IRR: Found unknown node: ", attrib.name()); + + /* We skip the contents of nodes we don't know. + * We parse the transformation and all animators + * and skip the rest. + */ + nd = new Node(Node::DUMMY); + } + + /* Attach the newly created node to the scene-graph + */ + curNode = nd; + nd->parent = curParent; + curParent->children.push_back(nd); + } else if (!ASSIMP_stricmp(child.name(), "materials")) { + inMaterials = true; + } else if (!ASSIMP_stricmp(child.name(), "animators")) { + inAnimator = true; + } else if (!ASSIMP_stricmp(child.name(), "attributes")) { + // We should have a valid node here + // FIX: no ... the scene root node is also contained in an attributes block + if (!curNode) { + continue; + } + + Animator *curAnim = nullptr; + + // Materials can occur for nearly any type of node + if (inMaterials && curNode->type != Node::DUMMY) { + // This is a material description - parse it! + curNode->materials.push_back(std::pair<aiMaterial *, unsigned int>()); + std::pair<aiMaterial *, unsigned int> &p = curNode->materials.back(); + + p.first = ParseMaterial(p.second); + ++guessedMatCnt; + continue; + } else if (inAnimator) { + // This is an animation path - add a new animator + // to the list. + curNode->animators.push_back(Animator()); + curAnim = &curNode->animators.back(); + + ++guessedAnimCnt; + } + + /* Parse all elements in the attributes block + * and process them. + */ + // while (reader->read()) { + for (pugi::xml_node attrib : child.children()) { + if (attrib.type() == pugi::node_element) { + //if (reader->getNodeType() == EXN_ELEMENT) { + //if (!ASSIMP_stricmp(reader->getNodeName(), "vector3d")) { + if (!ASSIMP_stricmp(attrib.name(), "vector3d")) { + VectorProperty prop; + ReadVectorProperty(prop); + + if (inAnimator) { + if (curAnim->type == Animator::ROTATION && prop.name == "Rotation") { + // We store the rotation euler angles in 'direction' + curAnim->direction = prop.value; + } else if (curAnim->type == Animator::FOLLOW_SPLINE) { + // Check whether the vector follows the PointN naming scheme, + // here N is the ONE-based index of the point + if (prop.name.length() >= 6 && prop.name.substr(0, 5) == "Point") { + // Add a new key to the list + curAnim->splineKeys.push_back(aiVectorKey()); + aiVectorKey &key = curAnim->splineKeys.back(); + + // and parse its properties + key.mValue = prop.value; + key.mTime = strtoul10(&prop.name[5]); + } + } else if (curAnim->type == Animator::FLY_CIRCLE) { + if (prop.name == "Center") { + curAnim->circleCenter = prop.value; + } else if (prop.name == "Direction") { + curAnim->direction = prop.value; + + // From Irrlicht's source - a workaround for backward compatibility with Irrlicht 1.1 + if (curAnim->direction == aiVector3D()) { + curAnim->direction = aiVector3D(0.f, 1.f, 0.f); + } else + curAnim->direction.Normalize(); + } + } else if (curAnim->type == Animator::FLY_STRAIGHT) { + if (prop.name == "Start") { + // We reuse the field here + curAnim->circleCenter = prop.value; + } else if (prop.name == "End") { + // We reuse the field here + curAnim->direction = prop.value; + } + } + } else { + if (prop.name == "Position") { + curNode->position = prop.value; + } else if (prop.name == "Rotation") { + curNode->rotation = prop.value; + } else if (prop.name == "Scale") { + curNode->scaling = prop.value; + } else if (Node::CAMERA == curNode->type) { + aiCamera *cam = cameras.back(); + if (prop.name == "Target") { + cam->mLookAt = prop.value; + } else if (prop.name == "UpVector") { + cam->mUp = prop.value; + } + } + } + //} else if (!ASSIMP_stricmp(reader->getNodeName(), "bool")) { + } else if (!ASSIMP_stricmp(attrib.name(), "bool")) { + BoolProperty prop; + ReadBoolProperty(prop); + + if (inAnimator && curAnim->type == Animator::FLY_CIRCLE && prop.name == "Loop") { + curAnim->loop = prop.value; + } + //} else if (!ASSIMP_stricmp(reader->getNodeName(), "float")) { + } else if (!ASSIMP_stricmp(attrib.name(), "float")) { + FloatProperty prop; + ReadFloatProperty(prop); + + if (inAnimator) { + // The speed property exists for several animators + if (prop.name == "Speed") { + curAnim->speed = prop.value; + } else if (curAnim->type == Animator::FLY_CIRCLE && prop.name == "Radius") { + curAnim->circleRadius = prop.value; + } else if (curAnim->type == Animator::FOLLOW_SPLINE && prop.name == "Tightness") { + curAnim->tightness = prop.value; + } + } else { + if (prop.name == "FramesPerSecond" && Node::ANIMMESH == curNode->type) { + curNode->framesPerSecond = prop.value; + } else if (Node::CAMERA == curNode->type) { + /* This is the vertical, not the horizontal FOV. + * We need to compute the right FOV from the + * screen aspect which we don't know yet. + */ + if (prop.name == "Fovy") { + cameras.back()->mHorizontalFOV = prop.value; + } else if (prop.name == "Aspect") { + cameras.back()->mAspect = prop.value; + } else if (prop.name == "ZNear") { + cameras.back()->mClipPlaneNear = prop.value; + } else if (prop.name == "ZFar") { + cameras.back()->mClipPlaneFar = prop.value; + } + } else if (Node::LIGHT == curNode->type) { + /* Additional light information + */ + if (prop.name == "Attenuation") { + lights.back()->mAttenuationLinear = prop.value; + } else if (prop.name == "OuterCone") { + lights.back()->mAngleOuterCone = AI_DEG_TO_RAD(prop.value); + } else if (prop.name == "InnerCone") { + lights.back()->mAngleInnerCone = AI_DEG_TO_RAD(prop.value); + } + } + // radius of the sphere to be generated - + // or alternatively, size of the cube + else if ((Node::SPHERE == curNode->type && prop.name == "Radius") || (Node::CUBE == curNode->type && prop.name == "Size")) { + + curNode->sphereRadius = prop.value; + } + } + //} else if (!ASSIMP_stricmp(reader->getNodeName(), "int")) { + } else if (!ASSIMP_stricmp(attrib.name(), "int")) { + IntProperty prop; + ReadIntProperty(prop); + + if (inAnimator) { + if (curAnim->type == Animator::FLY_STRAIGHT && prop.name == "TimeForWay") { + curAnim->timeForWay = prop.value; + } + } else { + // sphere polygon numbers in each direction + if (Node::SPHERE == curNode->type) { + + if (prop.name == "PolyCountX") { + curNode->spherePolyCountX = prop.value; + } else if (prop.name == "PolyCountY") { + curNode->spherePolyCountY = prop.value; + } + } + } + //} else if (!ASSIMP_stricmp(reader->getNodeName(), "string") || !ASSIMP_stricmp(reader->getNodeName(), "enum")) { + } else if (!ASSIMP_stricmp(attrib.name(), "string") || !ASSIMP_stricmp(attrib.name(), "enum")) { + StringProperty prop; + ReadStringProperty(prop); + if (prop.value.length()) { + if (prop.name == "Name") { + curNode->name = prop.value; + + /* If we're either a camera or a light source + * we need to update the name in the aiLight/ + * aiCamera structure, too. + */ + if (Node::CAMERA == curNode->type) { + cameras.back()->mName.Set(prop.value); + } else if (Node::LIGHT == curNode->type) { + lights.back()->mName.Set(prop.value); + } + } else if (Node::LIGHT == curNode->type && "LightType" == prop.name) { + if (prop.value == "Spot") + lights.back()->mType = aiLightSource_SPOT; + else if (prop.value == "Point") + lights.back()->mType = aiLightSource_POINT; + else if (prop.value == "Directional") + lights.back()->mType = aiLightSource_DIRECTIONAL; + else { + // We won't pass the validation with aiLightSourceType_UNDEFINED, + // so we remove the light and replace it with a silly dummy node + delete lights.back(); + lights.pop_back(); + curNode->type = Node::DUMMY; + + ASSIMP_LOG_ERROR("Ignoring light of unknown type: ", prop.value); + } + } else if ((prop.name == "Mesh" && Node::MESH == curNode->type) || + Node::ANIMMESH == curNode->type) { + /* This is the file name of the mesh - either + * animated or not. We need to make sure we setup + * the correct post-processing settings here. + */ + unsigned int pp = 0; + BatchLoader::PropertyMap map; + + /* If the mesh is a static one remove all animations from the impor data + */ + if (Node::ANIMMESH != curNode->type) { + pp |= aiProcess_RemoveComponent; + SetGenericProperty<int>(map.ints, AI_CONFIG_PP_RVC_FLAGS, + aiComponent_ANIMATIONS | aiComponent_BONEWEIGHTS); + } + + /* TODO: maybe implement the protection against recursive + * loading calls directly in BatchLoader? The current + * implementation is not absolutely safe. A LWS and an IRR + * file referencing each other *could* cause the system to + * recurse forever. + */ + + const std::string extension = GetExtension(prop.value); + if ("irr" == extension) { + ASSIMP_LOG_ERROR("IRR: Can't load another IRR file recursively"); + } else { + curNode->id = batch.AddLoadRequest(prop.value, pp, &map); + curNode->meshPath = prop.value; + } + } else if (inAnimator && prop.name == "Type") { + // type of the animator + if (prop.value == "rotation") { + curAnim->type = Animator::ROTATION; + } else if (prop.value == "flyCircle") { + curAnim->type = Animator::FLY_CIRCLE; + } else if (prop.value == "flyStraight") { + curAnim->type = Animator::FLY_CIRCLE; + } else if (prop.value == "followSpline") { + curAnim->type = Animator::FOLLOW_SPLINE; + } else { + ASSIMP_LOG_WARN("IRR: Ignoring unknown animator: ", prop.value); + + curAnim->type = Animator::UNKNOWN; + } + } + } + } + //} else if (reader->getNodeType() == EXN_ELEMENT_END && !ASSIMP_stricmp(reader->getNodeName(), "attributes")) { + } else if (attrib.type() == pugi::node_null && !ASSIMP_stricmp(attrib.name(), "attributes")) { + break; + } + } + } + break; + + /*case EXN_ELEMENT_END: + + // If we reached the end of a node, we need to continue processing its parent + if (!ASSIMP_stricmp(reader->getNodeName(), "node")) { + if (!curNode) { + // currently is no node set. We need to go + // back in the node hierarchy + if (!curParent) { + curParent = root; + ASSIMP_LOG_ERROR("IRR: Too many closing <node> elements"); + } else + curParent = curParent->parent; + } else + curNode = nullptr; + } + // clear all flags + else if (!ASSIMP_stricmp(reader->getNodeName(), "materials")) { + inMaterials = false; + } else if (!ASSIMP_stricmp(reader->getNodeName(), "animators")) { + inAnimator = false; + } + break;*/ + + default: + // GCC complains that not all enumeration values are handled + break; + } + //} + + // Now iterate through all cameras and compute their final (horizontal) FOV + for (aiCamera *cam : cameras) { + // screen aspect could be missing + if (cam->mAspect) { + cam->mHorizontalFOV *= cam->mAspect; + } else { + ASSIMP_LOG_WARN("IRR: Camera aspect is not given, can't compute horizontal FOV"); + } + } + + batch.LoadAll(); + + // Allocate a temporary scene data structure + aiScene *tempScene = new aiScene(); + tempScene->mRootNode = new aiNode(); + tempScene->mRootNode->mName.Set("<IRRRoot>"); + + // Copy the cameras to the output array + if (!cameras.empty()) { + tempScene->mNumCameras = (unsigned int)cameras.size(); + tempScene->mCameras = new aiCamera *[tempScene->mNumCameras]; + ::memcpy(tempScene->mCameras, &cameras[0], sizeof(void *) * tempScene->mNumCameras); + } + + // Copy the light sources to the output array + if (!lights.empty()) { + tempScene->mNumLights = (unsigned int)lights.size(); + tempScene->mLights = new aiLight *[tempScene->mNumLights]; + ::memcpy(tempScene->mLights, &lights[0], sizeof(void *) * tempScene->mNumLights); + } + + // temporary data + std::vector<aiNodeAnim *> anims; + std::vector<aiMaterial *> materials; + std::vector<AttachmentInfo> attach; + std::vector<aiMesh *> meshes; + + // try to guess how much storage we'll need + anims.reserve(guessedAnimCnt + (guessedAnimCnt >> 2)); + meshes.reserve(guessedMeshCnt + (guessedMeshCnt >> 2)); + materials.reserve(guessedMatCnt + (guessedMatCnt >> 2)); + + // Now process our scene-graph recursively: generate final + // meshes and generate animation channels for all nodes. + unsigned int defMatIdx = UINT_MAX; + GenerateGraph(root, tempScene->mRootNode, tempScene, + batch, meshes, anims, attach, materials, defMatIdx); + + if (!anims.empty()) { + tempScene->mNumAnimations = 1; + tempScene->mAnimations = new aiAnimation *[tempScene->mNumAnimations]; + aiAnimation *an = tempScene->mAnimations[0] = new aiAnimation(); + + // *********************************************************** + // This is only the global animation channel of the scene. + // If there are animated models, they will have separate + // animation channels in the scene. To display IRR scenes + // correctly, users will need to combine the global anim + // channel with all the local animations they want to play + // *********************************************************** + an->mName.Set("Irr_GlobalAnimChannel"); + + // copy all node animation channels to the global channel + an->mNumChannels = (unsigned int)anims.size(); + an->mChannels = new aiNodeAnim *[an->mNumChannels]; + ::memcpy(an->mChannels, &anims[0], sizeof(void *) * an->mNumChannels); + } + if (!meshes.empty()) { + // copy all meshes to the temporary scene + tempScene->mNumMeshes = (unsigned int)meshes.size(); + tempScene->mMeshes = new aiMesh *[tempScene->mNumMeshes]; + ::memcpy(tempScene->mMeshes, &meshes[0], tempScene->mNumMeshes * sizeof(void *)); + } + + // Copy all materials to the output array + if (!materials.empty()) { + tempScene->mNumMaterials = (unsigned int)materials.size(); + tempScene->mMaterials = new aiMaterial *[tempScene->mNumMaterials]; + ::memcpy(tempScene->mMaterials, &materials[0], sizeof(void *) * tempScene->mNumMaterials); + } + + // Now merge all sub scenes and attach them to the correct + // attachment points in the scenegraph. + SceneCombiner::MergeScenes(&pScene, tempScene, attach, + AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | (!configSpeedFlag ? ( + AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY | AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) : + 0)); + + // If we have no meshes | no materials now set the INCOMPLETE + // scene flag. This is necessary if we failed to load all + // models from external files + if (!pScene->mNumMeshes || !pScene->mNumMaterials) { + ASSIMP_LOG_WARN("IRR: No meshes loaded, setting AI_SCENE_FLAGS_INCOMPLETE"); + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } + + // Finished ... everything destructs automatically and all + // temporary scenes have already been deleted by MergeScenes() + delete root; +} + +#endif // !! ASSIMP_BUILD_NO_IRR_IMPORTER diff --git a/libs/assimp/code/AssetLib/Irr/IRRLoader.h b/libs/assimp/code/AssetLib/Irr/IRRLoader.h new file mode 100644 index 0000000..d56c4ca --- /dev/null +++ b/libs/assimp/code/AssetLib/Irr/IRRLoader.h @@ -0,0 +1,284 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IRRLoader.h + * @brief Declaration of the .irrMesh (Irrlight Engine Mesh Format) + * importer class. + */ +#ifndef AI_IRRLOADER_H_INCLUDED +#define AI_IRRLOADER_H_INCLUDED + +#include "AssetLib/Irr/IRRShared.h" +#include "Common/Importer.h" + +#include <assimp/SceneCombiner.h> +#include <assimp/StringUtils.h> +#include <assimp/anim.h> + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** Irr importer class. + * + * Irr is the native scene file format of the Irrlight engine and its editor + * irrEdit. As IrrEdit itself is capable of importing quite many file formats, + * it might be a good file format for data exchange. + */ +class IRRImporter : public BaseImporter, public IrrlichtBase { +public: + IRRImporter(); + ~IRRImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool checkSig) const override; + +protected: + const aiImporterDesc* GetInfo () const override; + void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) override; + void SetupProperties(const Importer* pImp) override; + +private: + /** Data structure for a scene-graph node animator + */ + struct Animator { + // Type of the animator + enum AT { + UNKNOWN = 0x0, + ROTATION = 0x1, + FLY_CIRCLE = 0x2, + FLY_STRAIGHT = 0x3, + FOLLOW_SPLINE = 0x4, + OTHER = 0x5 + + } type; + + explicit Animator(AT t = UNKNOWN) + : type (t) + , speed ( ai_real( 0.001 ) ) + , direction ( ai_real( 0.0 ), ai_real( 1.0 ), ai_real( 0.0 ) ) + , circleRadius ( ai_real( 1.0) ) + , tightness ( ai_real( 0.5 ) ) + , loop (true) + , timeForWay (100) + { + } + + + // common parameters + ai_real speed; + aiVector3D direction; + + // FLY_CIRCLE + aiVector3D circleCenter; + ai_real circleRadius; + + // FOLLOW_SPLINE + ai_real tightness; + std::vector<aiVectorKey> splineKeys; + + // ROTATION (angles given in direction) + + // FLY STRAIGHT + // circleCenter = start, direction = end + bool loop; + int timeForWay; + }; + + /** Data structure for a scene-graph node in an IRR file + */ + struct Node + { + // Type of the node + enum ET + { + LIGHT, + CUBE, + MESH, + SKYBOX, + DUMMY, + CAMERA, + TERRAIN, + SPHERE, + ANIMMESH + } type; + + explicit Node(ET t) + : type (t) + , scaling (1.0,1.0,1.0) // assume uniform scaling by default + , parent() + , framesPerSecond (0.0) + , id() + , sphereRadius (1.0) + , spherePolyCountX (100) + , spherePolyCountY (100) + { + + // Generate a default name for the node + char buffer[128]; + static int cnt; + ai_snprintf(buffer, 128, "IrrNode_%i",cnt++); + name = std::string(buffer); + + // reserve space for up to 5 materials + materials.reserve(5); + + // reserve space for up to 5 children + children.reserve(5); + } + + // Transformation of the node + aiVector3D position, rotation, scaling; + + // Name of the node + std::string name; + + // List of all child nodes + std::vector<Node*> children; + + // Parent node + Node* parent; + + // Animated meshes: frames per second + // 0.f if not specified + ai_real framesPerSecond; + + // Meshes: path to the mesh to be loaded + std::string meshPath; + unsigned int id; + + // Meshes: List of materials to be assigned + // along with their corresponding material flags + std::vector< std::pair<aiMaterial*, unsigned int> > materials; + + // Spheres: radius of the sphere to be generates + ai_real sphereRadius; + + // Spheres: Number of polygons in the x,y direction + unsigned int spherePolyCountX,spherePolyCountY; + + // List of all animators assigned to the node + std::list<Animator> animators; + }; + + /** Data structure for a vertex in an IRR skybox + */ + struct SkyboxVertex + { + SkyboxVertex() + {} + + //! Construction from single vertex components + SkyboxVertex(ai_real px, ai_real py, ai_real pz, + ai_real nx, ai_real ny, ai_real nz, + ai_real uvx, ai_real uvy) + + : position (px,py,pz) + , normal (nx,ny,nz) + , uv (uvx,uvy,0.0) + {} + + aiVector3D position, normal, uv; + }; + + + // ------------------------------------------------------------------- + /// Fill the scene-graph recursively + void GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, + BatchLoader& batch, + std::vector<aiMesh*>& meshes, + std::vector<aiNodeAnim*>& anims, + std::vector<AttachmentInfo>& attach, + std::vector<aiMaterial*>& materials, + unsigned int& defaultMatIdx); + + // ------------------------------------------------------------------- + /// Generate a mesh that consists of just a single quad + aiMesh* BuildSingleQuadMesh(const SkyboxVertex& v1, + const SkyboxVertex& v2, + const SkyboxVertex& v3, + const SkyboxVertex& v4); + + // ------------------------------------------------------------------- + /// Build a sky-box + /// + /// @param meshes Receives 6 output meshes + /// @param materials The last 6 materials are assigned to the newly + /// created meshes. The names of the materials are adjusted. + void BuildSkybox(std::vector<aiMesh*>& meshes, + std::vector<aiMaterial*> materials); + + // ------------------------------------------------------------------- + /** Copy a material for a mesh to the output material list + * + * @param materials Receives an output material + * @param inmaterials List of input materials + * @param defMatIdx Default material index - UINT_MAX if not present + * @param mesh Mesh to work on + */ + void CopyMaterial(std::vector<aiMaterial*>& materials, + std::vector< std::pair<aiMaterial*, unsigned int> >& inmaterials, + unsigned int& defMatIdx, + aiMesh* mesh); + + // ------------------------------------------------------------------- + /** Compute animations for a specific node + * + * @param root Node to be processed + * @param anims The list of output animations + */ + void ComputeAnimations(Node* root, aiNode* real, + std::vector<aiNodeAnim*>& anims); + +private: + /// Configuration option: desired output FPS + double fps; + + /// Configuration option: speed flag was set? + bool configSpeedFlag; +}; + +} // end of namespace Assimp + +#endif // AI_IRRIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/Irr/IRRMeshLoader.cpp b/libs/assimp/code/AssetLib/Irr/IRRMeshLoader.cpp new file mode 100644 index 0000000..90b4d85 --- /dev/null +++ b/libs/assimp/code/AssetLib/Irr/IRRMeshLoader.cpp @@ -0,0 +1,502 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the IrrMesh importer class */ + +#ifndef ASSIMP_BUILD_NO_IRRMESH_IMPORTER + +#include "IRRMeshLoader.h" +#include <assimp/ParsingUtils.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/material.h> +#include <assimp/mesh.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <memory> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Irrlicht Mesh Reader", + "", + "", + "http://irrlicht.sourceforge.net/", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "xml irrmesh" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +IRRMeshImporter::IRRMeshImporter() : + BaseImporter(), + IrrlichtBase() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +IRRMeshImporter::~IRRMeshImporter() {} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool IRRMeshImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + /* NOTE: A simple check for the file extension is not enough + * here. Irrmesh and irr are easy, but xml is too generic + * and could be collada, too. So we need to open the file and + * search for typical tokens. + */ + static const char *tokens[] = { "irrmesh" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Get a list of all file extensions which are handled by this class +const aiImporterDesc *IRRMeshImporter::GetInfo() const { + return &desc; +} + +static void releaseMaterial(aiMaterial **mat) { + if (*mat != nullptr) { + delete *mat; + *mat = nullptr; + } +} + +static void releaseMesh(aiMesh **mesh) { + if (*mesh != nullptr) { + delete *mesh; + *mesh = nullptr; + } +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void IRRMeshImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile)); + + // Check whether we can read from the file + if (file.get() == NULL) + throw DeadlyImportError("Failed to open IRRMESH file ", pFile); + + // Construct the irrXML parser + XmlParser parser; + if (!parser.parse( file.get() )) { + throw DeadlyImportError("XML parse error while loading IRRMESH file ", pFile); + } + XmlNode root = parser.getRootNode(); + + // final data + std::vector<aiMaterial *> materials; + std::vector<aiMesh *> meshes; + materials.reserve(5); + meshes.reserve(5); + + // temporary data - current mesh buffer + aiMaterial *curMat = nullptr; + aiMesh *curMesh = nullptr; + unsigned int curMatFlags = 0; + + std::vector<aiVector3D> curVertices, curNormals, curTangents, curBitangents; + std::vector<aiColor4D> curColors; + std::vector<aiVector3D> curUVs, curUV2s; + + // some temporary variables + int textMeaning = 0; + int vertexFormat = 0; // 0 = normal; 1 = 2 tcoords, 2 = tangents + bool useColors = false; + + // Parse the XML file + for (pugi::xml_node child : root.children()) { + if (child.type() == pugi::node_element) { + if (!ASSIMP_stricmp(child.name(), "buffer") && (curMat || curMesh)) { + // end of previous buffer. A material and a mesh should be there + if (!curMat || !curMesh) { + ASSIMP_LOG_ERROR("IRRMESH: A buffer must contain a mesh and a material"); + releaseMaterial(&curMat); + releaseMesh(&curMesh); + } else { + materials.push_back(curMat); + meshes.push_back(curMesh); + } + curMat = nullptr; + curMesh = nullptr; + + curVertices.clear(); + curColors.clear(); + curNormals.clear(); + curUV2s.clear(); + curUVs.clear(); + curTangents.clear(); + curBitangents.clear(); + } + + if (!ASSIMP_stricmp(child.name(), "material")) { + if (curMat) { + ASSIMP_LOG_WARN("IRRMESH: Only one material description per buffer, please"); + releaseMaterial(&curMat); + } + curMat = ParseMaterial(curMatFlags); + } + /* no else here! */ if (!ASSIMP_stricmp(child.name(), "vertices")) { + pugi::xml_attribute attr = child.attribute("vertexCount"); + int num = attr.as_int(); + //int num = reader->getAttributeValueAsInt("vertexCount"); + + if (!num) { + // This is possible ... remove the mesh from the list and skip further reading + ASSIMP_LOG_WARN("IRRMESH: Found mesh with zero vertices"); + + releaseMaterial(&curMat); + releaseMesh(&curMesh); + textMeaning = 0; + continue; + } + + curVertices.reserve(num); + curNormals.reserve(num); + curColors.reserve(num); + curUVs.reserve(num); + + // Determine the file format + //const char *t = reader->getAttributeValueSafe("type"); + pugi::xml_attribute t = child.attribute("type"); + if (!ASSIMP_stricmp("2tcoords", t.name())) { + curUV2s.reserve(num); + vertexFormat = 1; + + if (curMatFlags & AI_IRRMESH_EXTRA_2ND_TEXTURE) { + // ********************************************************* + // We have a second texture! So use this UV channel + // for it. The 2nd texture can be either a normal + // texture (solid_2layer or lightmap_xxx) or a normal + // map (normal_..., parallax_...) + // ********************************************************* + int idx = 1; + aiMaterial *mat = (aiMaterial *)curMat; + + if (curMatFlags & AI_IRRMESH_MAT_lightmap) { + mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_LIGHTMAP(0)); + } else if (curMatFlags & AI_IRRMESH_MAT_normalmap_solid) { + mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_NORMALS(0)); + } else if (curMatFlags & AI_IRRMESH_MAT_solid_2layer) { + mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_DIFFUSE(1)); + } + } + } else if (!ASSIMP_stricmp("tangents", t.name())) { + curTangents.reserve(num); + curBitangents.reserve(num); + vertexFormat = 2; + } else if (ASSIMP_stricmp("standard", t.name())) { + releaseMaterial(&curMat); + ASSIMP_LOG_WARN("IRRMESH: Unknown vertex format"); + } else + vertexFormat = 0; + textMeaning = 1; + } else if (!ASSIMP_stricmp(child.name(), "indices")) { + if (curVertices.empty() && curMat) { + releaseMaterial(&curMat); + throw DeadlyImportError("IRRMESH: indices must come after vertices"); + } + + textMeaning = 2; + + // start a new mesh + curMesh = new aiMesh(); + + // allocate storage for all faces + pugi::xml_attribute attr = child.attribute("indexCount"); + curMesh->mNumVertices = attr.as_int(); + if (!curMesh->mNumVertices) { + // This is possible ... remove the mesh from the list and skip further reading + ASSIMP_LOG_WARN("IRRMESH: Found mesh with zero indices"); + + // mesh - away + releaseMesh(&curMesh); + + // material - away + releaseMaterial(&curMat); + + textMeaning = 0; + continue; + } + + if (curMesh->mNumVertices % 3) { + ASSIMP_LOG_WARN("IRRMESH: Number if indices isn't divisible by 3"); + } + + curMesh->mNumFaces = curMesh->mNumVertices / 3; + curMesh->mFaces = new aiFace[curMesh->mNumFaces]; + + // setup some members + curMesh->mMaterialIndex = (unsigned int)materials.size(); + curMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + // allocate storage for all vertices + curMesh->mVertices = new aiVector3D[curMesh->mNumVertices]; + + if (curNormals.size() == curVertices.size()) { + curMesh->mNormals = new aiVector3D[curMesh->mNumVertices]; + } + if (curTangents.size() == curVertices.size()) { + curMesh->mTangents = new aiVector3D[curMesh->mNumVertices]; + } + if (curBitangents.size() == curVertices.size()) { + curMesh->mBitangents = new aiVector3D[curMesh->mNumVertices]; + } + if (curColors.size() == curVertices.size() && useColors) { + curMesh->mColors[0] = new aiColor4D[curMesh->mNumVertices]; + } + if (curUVs.size() == curVertices.size()) { + curMesh->mTextureCoords[0] = new aiVector3D[curMesh->mNumVertices]; + } + if (curUV2s.size() == curVertices.size()) { + curMesh->mTextureCoords[1] = new aiVector3D[curMesh->mNumVertices]; + } + } + //break; + + //case EXN_TEXT: { + const char *sz = child.child_value(); + if (textMeaning == 1) { + textMeaning = 0; + + // read vertices + do { + SkipSpacesAndLineEnd(&sz); + aiVector3D temp; + aiColor4D c; + + // Read the vertex position + sz = fast_atoreal_move<float>(sz, (float &)temp.x); + SkipSpaces(&sz); + + sz = fast_atoreal_move<float>(sz, (float &)temp.y); + SkipSpaces(&sz); + + sz = fast_atoreal_move<float>(sz, (float &)temp.z); + SkipSpaces(&sz); + curVertices.push_back(temp); + + // Read the vertex normals + sz = fast_atoreal_move<float>(sz, (float &)temp.x); + SkipSpaces(&sz); + + sz = fast_atoreal_move<float>(sz, (float &)temp.y); + SkipSpaces(&sz); + + sz = fast_atoreal_move<float>(sz, (float &)temp.z); + SkipSpaces(&sz); + curNormals.push_back(temp); + + // read the vertex colors + uint32_t clr = strtoul16(sz, &sz); + ColorFromARGBPacked(clr, c); + + if (!curColors.empty() && c != *(curColors.end() - 1)) + useColors = true; + + curColors.push_back(c); + SkipSpaces(&sz); + + // read the first UV coordinate set + sz = fast_atoreal_move<float>(sz, (float &)temp.x); + SkipSpaces(&sz); + + sz = fast_atoreal_move<float>(sz, (float &)temp.y); + SkipSpaces(&sz); + temp.z = 0.f; + temp.y = 1.f - temp.y; // DX to OGL + curUVs.push_back(temp); + + // read the (optional) second UV coordinate set + if (vertexFormat == 1) { + sz = fast_atoreal_move<float>(sz, (float &)temp.x); + SkipSpaces(&sz); + + sz = fast_atoreal_move<float>(sz, (float &)temp.y); + temp.y = 1.f - temp.y; // DX to OGL + curUV2s.push_back(temp); + } + // read optional tangent and bitangent vectors + else if (vertexFormat == 2) { + // tangents + sz = fast_atoreal_move<float>(sz, (float &)temp.x); + SkipSpaces(&sz); + + sz = fast_atoreal_move<float>(sz, (float &)temp.z); + SkipSpaces(&sz); + + sz = fast_atoreal_move<float>(sz, (float &)temp.y); + SkipSpaces(&sz); + temp.y *= -1.0f; + curTangents.push_back(temp); + + // bitangents + sz = fast_atoreal_move<float>(sz, (float &)temp.x); + SkipSpaces(&sz); + + sz = fast_atoreal_move<float>(sz, (float &)temp.z); + SkipSpaces(&sz); + + sz = fast_atoreal_move<float>(sz, (float &)temp.y); + SkipSpaces(&sz); + temp.y *= -1.0f; + curBitangents.push_back(temp); + } + } + + /* IMPORTANT: We assume that each vertex is specified in one + line. So we can skip the rest of the line - unknown vertex + elements are ignored. + */ + + while (SkipLine(&sz)); + } else if (textMeaning == 2) { + textMeaning = 0; + + // read indices + aiFace *curFace = curMesh->mFaces; + aiFace *const faceEnd = curMesh->mFaces + curMesh->mNumFaces; + + aiVector3D *pcV = curMesh->mVertices; + aiVector3D *pcN = curMesh->mNormals; + aiVector3D *pcT = curMesh->mTangents; + aiVector3D *pcB = curMesh->mBitangents; + aiColor4D *pcC0 = curMesh->mColors[0]; + aiVector3D *pcT0 = curMesh->mTextureCoords[0]; + aiVector3D *pcT1 = curMesh->mTextureCoords[1]; + + unsigned int curIdx = 0; + unsigned int total = 0; + while (SkipSpacesAndLineEnd(&sz)) { + if (curFace >= faceEnd) { + ASSIMP_LOG_ERROR("IRRMESH: Too many indices"); + break; + } + if (!curIdx) { + curFace->mNumIndices = 3; + curFace->mIndices = new unsigned int[3]; + } + + unsigned int idx = strtoul10(sz, &sz); + if (idx >= curVertices.size()) { + ASSIMP_LOG_ERROR("IRRMESH: Index out of range"); + idx = 0; + } + + curFace->mIndices[curIdx] = total++; + + *pcV++ = curVertices[idx]; + if (pcN) *pcN++ = curNormals[idx]; + if (pcT) *pcT++ = curTangents[idx]; + if (pcB) *pcB++ = curBitangents[idx]; + if (pcC0) *pcC0++ = curColors[idx]; + if (pcT0) *pcT0++ = curUVs[idx]; + if (pcT1) *pcT1++ = curUV2s[idx]; + + if (++curIdx == 3) { + ++curFace; + curIdx = 0; + } + } + + if (curFace != faceEnd) + ASSIMP_LOG_ERROR("IRRMESH: Not enough indices"); + + // Finish processing the mesh - do some small material workarounds + if (curMatFlags & AI_IRRMESH_MAT_trans_vertex_alpha && !useColors) { + // Take the opacity value of the current material + // from the common vertex color alpha + aiMaterial *mat = (aiMaterial *)curMat; + mat->AddProperty(&curColors[0].a, 1, AI_MATKEY_OPACITY); + } + } + } + } + + // End of the last buffer. A material and a mesh should be there + if (curMat || curMesh) { + if (!curMat || !curMesh) { + ASSIMP_LOG_ERROR("IRRMESH: A buffer must contain a mesh and a material"); + releaseMaterial(&curMat); + releaseMesh(&curMesh); + } else { + materials.push_back(curMat); + meshes.push_back(curMesh); + } + } + + if (materials.empty()) { + throw DeadlyImportError("IRRMESH: Unable to read a mesh from this file"); + } + + // now generate the output scene + pScene->mNumMeshes = (unsigned int)meshes.size(); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + pScene->mMeshes[i] = meshes[i]; + + // clean this value ... + pScene->mMeshes[i]->mNumUVComponents[3] = 0; + } + + pScene->mNumMaterials = (unsigned int)materials.size(); + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + ::memcpy(pScene->mMaterials, &materials[0], sizeof(void *) * pScene->mNumMaterials); + + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mName.Set("<IRRMesh>"); + pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; + pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; + + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + pScene->mRootNode->mMeshes[i] = i; + } +} + +#endif // !! ASSIMP_BUILD_NO_IRRMESH_IMPORTER diff --git a/libs/assimp/code/AssetLib/Irr/IRRMeshLoader.h b/libs/assimp/code/AssetLib/Irr/IRRMeshLoader.h new file mode 100644 index 0000000..79c1e48 --- /dev/null +++ b/libs/assimp/code/AssetLib/Irr/IRRMeshLoader.h @@ -0,0 +1,94 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IRRMeshLoader.h + * @brief Declaration of the .irrMesh (Irrlight Engine Mesh Format) + * importer class. + */ +#ifndef AI_IRRMESHLOADER_H_INCLUDED +#define AI_IRRMESHLOADER_H_INCLUDED + +#include "IRRShared.h" +#include <assimp/BaseImporter.h> + +#ifndef ASSIMP_BUILD_NO_IRRMESH_IMPORTER + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** IrrMesh importer class. + * + * IrrMesh is the native file format of the Irrlight engine and its editor + * irrEdit. As IrrEdit itself is capable of importing quite many file formats, + * it might be a good file format for data exchange. + */ +class IRRMeshImporter : public BaseImporter, public IrrlichtBase { +public: + IRRMeshImporter(); + ~IRRMeshImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; +}; + +} // end of namespace Assimp + +#endif // ASSIMP_BUILD_NO_IRRMESH_IMPORTER + +#endif // AI_IRRMESHIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/Irr/IRRShared.cpp b/libs/assimp/code/AssetLib/Irr/IRRShared.cpp new file mode 100644 index 0000000..8763b63 --- /dev/null +++ b/libs/assimp/code/AssetLib/Irr/IRRShared.cpp @@ -0,0 +1,387 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 IRRShared.cpp + * @brief Shared utilities for the IRR and IRRMESH loaders + */ + +//This section should be excluded only if both the Irrlicht AND the Irrlicht Mesh importers were omitted. +#if !(defined(ASSIMP_BUILD_NO_IRR_IMPORTER) && defined(ASSIMP_BUILD_NO_IRRMESH_IMPORTER)) + +#include "IRRShared.h" +#include <assimp/ParsingUtils.h> +#include <assimp/fast_atof.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/material.h> + +using namespace Assimp; + +// Transformation matrix to convert from Assimp to IRR space +const aiMatrix4x4 Assimp::AI_TO_IRR_MATRIX = aiMatrix4x4 ( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + +// ------------------------------------------------------------------------------------------------ +// read a property in hexadecimal format (i.e. ffffffff) +void IrrlichtBase::ReadHexProperty(HexProperty &out ) { + for (pugi::xml_attribute attrib : mNode->attributes()) { + if (!ASSIMP_stricmp(attrib.name(), "name")) { + out.name = std::string( attrib.value() ); + } else if (!ASSIMP_stricmp(attrib.name(),"value")) { + // parse the hexadecimal value + out.value = strtoul16(attrib.name()); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// read a decimal property +void IrrlichtBase::ReadIntProperty(IntProperty & out) { + for (pugi::xml_attribute attrib : mNode->attributes()) { + if (!ASSIMP_stricmp(attrib.name(), "name")) { + out.name = std::string(attrib.value()); + } else if (!ASSIMP_stricmp(attrib.value(),"value")) { + // parse the int value + out.value = strtol10(attrib.name()); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// read a string property +void IrrlichtBase::ReadStringProperty( StringProperty& out) { + for (pugi::xml_attribute attrib : mNode->attributes()) { + if (!ASSIMP_stricmp(attrib.name(), "name")) { + out.name = std::string(attrib.value()); + } else if (!ASSIMP_stricmp(attrib.name(), "value")) { + // simple copy the string + out.value = std::string(attrib.value()); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// read a boolean property +void IrrlichtBase::ReadBoolProperty(BoolProperty &out) { + for (pugi::xml_attribute attrib : mNode->attributes()) { + if (!ASSIMP_stricmp(attrib.name(), "name")){ + out.name = std::string(attrib.value()); + } else if (!ASSIMP_stricmp(attrib.name(), "value")) { + // true or false, case insensitive + out.value = (ASSIMP_stricmp(attrib.value(), "true") ? false : true); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// read a float property +void IrrlichtBase::ReadFloatProperty(FloatProperty &out) { + for (pugi::xml_attribute attrib : mNode->attributes()) { + if (!ASSIMP_stricmp(attrib.name(), "name")) { + out.name = std::string(attrib.value()); + } else if (!ASSIMP_stricmp(attrib.name(), "value")) { + // just parse the float + out.value = fast_atof(attrib.value()); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// read a vector property +void IrrlichtBase::ReadVectorProperty( VectorProperty &out ) { + for (pugi::xml_attribute attrib : mNode->attributes()) { + if (!ASSIMP_stricmp(attrib.name(), "name")) { + out.name = std::string(attrib.value()); + } else if (!ASSIMP_stricmp(attrib.name(), "value")) { + // three floats, separated with commas + const char *ptr = attrib.value(); + + SkipSpaces(&ptr); + ptr = fast_atoreal_move<float>( ptr,(float&)out.value.x ); + SkipSpaces(&ptr); + if (',' != *ptr) { + ASSIMP_LOG_ERROR("IRR(MESH): Expected comma in vector definition"); + } else { + SkipSpaces(ptr + 1, &ptr); + } + ptr = fast_atoreal_move<float>( ptr,(float&)out.value.y ); + SkipSpaces(&ptr); + if (',' != *ptr) { + ASSIMP_LOG_ERROR("IRR(MESH): Expected comma in vector definition"); + } else { + SkipSpaces(ptr + 1, &ptr); + } + ptr = fast_atoreal_move<float>( ptr,(float&)out.value.z ); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Convert a string to a proper aiMappingMode +int ConvertMappingMode(const std::string& mode) { + if (mode == "texture_clamp_repeat") { + return aiTextureMapMode_Wrap; + } else if (mode == "texture_clamp_mirror") { + return aiTextureMapMode_Mirror; + } + + return aiTextureMapMode_Clamp; +} + +// ------------------------------------------------------------------------------------------------ +// Parse a material from the XML file +aiMaterial* IrrlichtBase::ParseMaterial(unsigned int& matFlags) { + aiMaterial* mat = new aiMaterial(); + aiColor4D clr; + aiString s; + + matFlags = 0; // zero output flags + int cnt = 0; // number of used texture channels + unsigned int nd = 0; + + for (pugi::xml_node child : mNode->children()) { + if (!ASSIMP_stricmp(child.name(), "color")) { // Hex properties + HexProperty prop; + ReadHexProperty(prop); + if (prop.name == "Diffuse") { + ColorFromARGBPacked(prop.value, clr); + mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); + } else if (prop.name == "Ambient") { + ColorFromARGBPacked(prop.value, clr); + mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_AMBIENT); + } else if (prop.name == "Specular") { + ColorFromARGBPacked(prop.value, clr); + mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_SPECULAR); + } + + // NOTE: The 'emissive' property causes problems. It is + // often != 0, even if there is obviously no light + // emitted by the described surface. In fact I think + // IRRLICHT ignores this property, too. +#if 0 + else if (prop.name == "Emissive") { + ColorFromARGBPacked(prop.value,clr); + mat->AddProperty(&clr,1,AI_MATKEY_COLOR_EMISSIVE); + } +#endif + } else if (!ASSIMP_stricmp(child.name(), "float")) { // Float properties + FloatProperty prop; + ReadFloatProperty(prop); + if (prop.name == "Shininess") { + mat->AddProperty(&prop.value, 1, AI_MATKEY_SHININESS); + } + } else if (!ASSIMP_stricmp(child.name(), "bool")) { // Bool properties + BoolProperty prop; + ReadBoolProperty(prop); + if (prop.name == "Wireframe") { + int val = (prop.value ? true : false); + mat->AddProperty(&val, 1, AI_MATKEY_ENABLE_WIREFRAME); + } else if (prop.name == "GouraudShading") { + int val = (prop.value ? aiShadingMode_Gouraud : aiShadingMode_NoShading); + mat->AddProperty(&val, 1, AI_MATKEY_SHADING_MODEL); + } else if (prop.name == "BackfaceCulling") { + int val = (!prop.value); + mat->AddProperty(&val, 1, AI_MATKEY_TWOSIDED); + } + } else if (!ASSIMP_stricmp(child.name(), "texture") || + !ASSIMP_stricmp(child.name(), "enum")) { // String properties - textures and texture related properties + StringProperty prop; + ReadStringProperty(prop); + if (prop.value.length()) { + // material type (shader) + if (prop.name == "Type") { + if (prop.value == "solid") { + // default material ... + } else if (prop.value == "trans_vertex_alpha") { + matFlags = AI_IRRMESH_MAT_trans_vertex_alpha; + } else if (prop.value == "lightmap") { + matFlags = AI_IRRMESH_MAT_lightmap; + } else if (prop.value == "solid_2layer") { + matFlags = AI_IRRMESH_MAT_solid_2layer; + } else if (prop.value == "lightmap_m2") { + matFlags = AI_IRRMESH_MAT_lightmap_m2; + } else if (prop.value == "lightmap_m4") { + matFlags = AI_IRRMESH_MAT_lightmap_m4; + } else if (prop.value == "lightmap_light") { + matFlags = AI_IRRMESH_MAT_lightmap_light; + } else if (prop.value == "lightmap_light_m2") { + matFlags = AI_IRRMESH_MAT_lightmap_light_m2; + } else if (prop.value == "lightmap_light_m4") { + matFlags = AI_IRRMESH_MAT_lightmap_light_m4; + } else if (prop.value == "lightmap_add") { + matFlags = AI_IRRMESH_MAT_lightmap_add; + } else if (prop.value == "normalmap_solid" || + prop.value == "parallaxmap_solid") { // Normal and parallax maps are treated equally + matFlags = AI_IRRMESH_MAT_normalmap_solid; + } else if (prop.value == "normalmap_trans_vertex_alpha" || + prop.value == "parallaxmap_trans_vertex_alpha") { + matFlags = AI_IRRMESH_MAT_normalmap_tva; + } else if (prop.value == "normalmap_trans_add" || + prop.value == "parallaxmap_trans_add") { + matFlags = AI_IRRMESH_MAT_normalmap_ta; + } else { + ASSIMP_LOG_WARN("IRRMat: Unrecognized material type: ", prop.value); + } + } + + // Up to 4 texture channels are supported + if (prop.name == "Texture1") { + // Always accept the primary texture channel + ++cnt; + s.Set(prop.value); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0)); + } else if (prop.name == "Texture2" && cnt == 1) { + // 2-layer material lightmapped? + if (matFlags & AI_IRRMESH_MAT_lightmap) { + ++cnt; + s.Set(prop.value); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_LIGHTMAP(0)); + + // set the corresponding material flag + matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE; + } else if (matFlags & AI_IRRMESH_MAT_normalmap_solid) { // alternatively: normal or parallax mapping + ++cnt; + s.Set(prop.value); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_NORMALS(0)); + + // set the corresponding material flag + matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE; + } else if (matFlags & AI_IRRMESH_MAT_solid_2layer) { // or just as second diffuse texture + ++cnt; + s.Set(prop.value); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(1)); + ++nd; + + // set the corresponding material flag + matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE; + } else { + ASSIMP_LOG_WARN("IRRmat: Skipping second texture"); + } + } else if (prop.name == "Texture3" && cnt == 2) { + // Irrlicht does not seem to use these channels. + ++cnt; + s.Set(prop.value); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(nd + 1)); + } else if (prop.name == "Texture4" && cnt == 3) { + // Irrlicht does not seem to use these channels. + ++cnt; + s.Set(prop.value); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(nd + 2)); + } + + // Texture mapping options + if (prop.name == "TextureWrap1" && cnt >= 1) { + int map = ConvertMappingMode(prop.value); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0)); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0)); + } else if (prop.name == "TextureWrap2" && cnt >= 2) { + int map = ConvertMappingMode(prop.value); + if (matFlags & AI_IRRMESH_MAT_lightmap) { + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_LIGHTMAP(0)); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_LIGHTMAP(0)); + } else if (matFlags & (AI_IRRMESH_MAT_normalmap_solid)) { + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_NORMALS(0)); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_NORMALS(0)); + } else if (matFlags & AI_IRRMESH_MAT_solid_2layer) { + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(1)); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(1)); + } + } else if (prop.name == "TextureWrap3" && cnt >= 3) { + int map = ConvertMappingMode(prop.value); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(nd + 1)); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(nd + 1)); + } else if (prop.name == "TextureWrap4" && cnt >= 4) { + int map = ConvertMappingMode(prop.value); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(nd + 2)); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(nd + 2)); + } + } + } + //break; + /*case EXN_ELEMENT_END: + + // Assume there are no further nested nodes in <material> elements + if ( !ASSIMP_stricmp(reader->getNodeName(),"material") || + !ASSIMP_stricmp(reader->getNodeName(),"attributes")) + { + // Now process lightmapping flags + // We should have at least one textur to do that .. + if (cnt && matFlags & AI_IRRMESH_MAT_lightmap) + { + float f = 1.f; + unsigned int unmasked = matFlags&~AI_IRRMESH_MAT_lightmap; + + // Additive lightmap? + int op = (unmasked & AI_IRRMESH_MAT_lightmap_add + ? aiTextureOp_Add : aiTextureOp_Multiply); + + // Handle Irrlicht's lightmapping scaling factor + if (unmasked & AI_IRRMESH_MAT_lightmap_m2 || + unmasked & AI_IRRMESH_MAT_lightmap_light_m2) + { + f = 2.f; + } + else if (unmasked & AI_IRRMESH_MAT_lightmap_m4 || + unmasked & AI_IRRMESH_MAT_lightmap_light_m4) + { + f = 4.f; + } + mat->AddProperty( &f, 1, AI_MATKEY_TEXBLEND_LIGHTMAP(0)); + mat->AddProperty( &op,1, AI_MATKEY_TEXOP_LIGHTMAP(0)); + } + + return mat; + } + default: + + // GCC complains here ... + break; + } + }*/ + } + ASSIMP_LOG_ERROR("IRRMESH: Unexpected end of file. Material is not complete"); + + return mat; +} + +#endif // !(defined(ASSIMP_BUILD_NO_IRR_IMPORTER) && defined(ASSIMP_BUILD_NO_IRRMESH_IMPORTER)) diff --git a/libs/assimp/code/AssetLib/Irr/IRRShared.h b/libs/assimp/code/AssetLib/Irr/IRRShared.h new file mode 100644 index 0000000..90e212d --- /dev/null +++ b/libs/assimp/code/AssetLib/Irr/IRRShared.h @@ -0,0 +1,119 @@ + + +/** @file IRRShared.h + * @brief Shared utilities for the IRR and IRRMESH loaders + */ + +#ifndef INCLUDED_AI_IRRSHARED_H +#define INCLUDED_AI_IRRSHARED_H + +#include <assimp/BaseImporter.h> +#include <assimp/XmlParser.h> +#include <cstdint> + +struct aiMaterial; + +namespace Assimp { + +/** @brief Matrix to convert from Assimp to IRR and backwards + */ +extern const aiMatrix4x4 AI_TO_IRR_MATRIX; + +// Default: 0 = solid, one texture +#define AI_IRRMESH_MAT_solid_2layer 0x10000 + +// Transparency flags +#define AI_IRRMESH_MAT_trans_vertex_alpha 0x1 +#define AI_IRRMESH_MAT_trans_add 0x2 + +// Lightmapping flags +#define AI_IRRMESH_MAT_lightmap 0x2 +#define AI_IRRMESH_MAT_lightmap_m2 (AI_IRRMESH_MAT_lightmap | 0x4) +#define AI_IRRMESH_MAT_lightmap_m4 (AI_IRRMESH_MAT_lightmap | 0x8) +#define AI_IRRMESH_MAT_lightmap_light (AI_IRRMESH_MAT_lightmap | 0x10) +#define AI_IRRMESH_MAT_lightmap_light_m2 (AI_IRRMESH_MAT_lightmap | 0x20) +#define AI_IRRMESH_MAT_lightmap_light_m4 (AI_IRRMESH_MAT_lightmap | 0x40) +#define AI_IRRMESH_MAT_lightmap_add (AI_IRRMESH_MAT_lightmap | 0x80) + +// Standard NormalMap (or Parallax map, they're treated equally) +#define AI_IRRMESH_MAT_normalmap_solid (0x100) + +// Normal map combined with vertex alpha +#define AI_IRRMESH_MAT_normalmap_tva \ + (AI_IRRMESH_MAT_normalmap_solid | AI_IRRMESH_MAT_trans_vertex_alpha) + +// Normal map combined with additive transparency +#define AI_IRRMESH_MAT_normalmap_ta \ + (AI_IRRMESH_MAT_normalmap_solid | AI_IRRMESH_MAT_trans_add) + +// Special flag. It indicates a second texture has been found +// Its type depends ... either a normal textue or a normal map +#define AI_IRRMESH_EXTRA_2ND_TEXTURE 0x100000 + +// --------------------------------------------------------------------------- +/** Base class for the Irr and IrrMesh importers. + * + * Declares some irrlight-related xml parsing utilities and provides tools + * to load materials from IRR and IRRMESH files. + */ +class IrrlichtBase { +protected: + IrrlichtBase() : + mNode(nullptr) { + // empty + } + + ~IrrlichtBase() { + // empty + } + + /** @brief Data structure for a simple name-value property + */ + template <class T> + struct Property { + std::string name; + T value; + }; + + typedef Property<uint32_t> HexProperty; + typedef Property<std::string> StringProperty; + typedef Property<bool> BoolProperty; + typedef Property<float> FloatProperty; + typedef Property<aiVector3D> VectorProperty; + typedef Property<int> IntProperty; + + /// XML reader instance + XmlParser mParser; + pugi::xml_node *mNode; + + // ------------------------------------------------------------------- + /** Parse a material description from the XML + * @return The created material + * @param matFlags Receives AI_IRRMESH_MAT_XX flags + */ + aiMaterial *ParseMaterial(unsigned int &matFlags); + + // ------------------------------------------------------------------- + /** Read a property of the specified type from the current XML element. + * @param out Receives output data + */ + void ReadHexProperty(HexProperty &out); + void ReadStringProperty(StringProperty &out); + void ReadBoolProperty(BoolProperty &out); + void ReadFloatProperty(FloatProperty &out); + void ReadVectorProperty(VectorProperty &out); + void ReadIntProperty(IntProperty &out); +}; + +// ------------------------------------------------------------------------------------------------ +// Unpack a hex color, e.g. 0xdcdedfff +inline void ColorFromARGBPacked(uint32_t in, aiColor4D &clr) { + clr.a = ((in >> 24) & 0xff) / 255.f; + clr.r = ((in >> 16) & 0xff) / 255.f; + clr.g = ((in >> 8) & 0xff) / 255.f; + clr.b = ((in)&0xff) / 255.f; +} + +} // end namespace Assimp + +#endif // !! INCLUDED_AI_IRRSHARED_H diff --git a/libs/assimp/code/AssetLib/LWO/LWOAnimation.cpp b/libs/assimp/code/AssetLib/LWO/LWOAnimation.cpp new file mode 100644 index 0000000..c2ee2d9 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOAnimation.cpp @@ -0,0 +1,609 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 LWOAnimation.cpp + * @brief LWOAnimationResolver utility class + * + * It's a very generic implementation of LightWave's system of + * component-wise-animated stuff. The one and only fully free + * implementation of LightWave envelopes of which I know. +*/ + +#if (!defined ASSIMP_BUILD_NO_LWO_IMPORTER) && (!defined ASSIMP_BUILD_NO_LWS_IMPORTER) + +#include <functional> + +// internal headers +#include "LWOFileData.h" +#include <assimp/anim.h> + +using namespace Assimp; +using namespace Assimp::LWO; + +// ------------------------------------------------------------------------------------------------ +// Construct an animation resolver from a given list of envelopes +AnimResolver::AnimResolver(std::list<Envelope> &_envelopes, double tick) : + envelopes(_envelopes), + sample_rate(0.), + envl_x(), + envl_y(), + envl_z(), + end_x(), + end_y(), + end_z(), + flags(), + sample_delta() { + trans_x = trans_y = trans_z = nullptr; + rotat_x = rotat_y = rotat_z = nullptr; + scale_x = scale_y = scale_z = nullptr; + + first = last = 150392.; + + // find transformation envelopes + for (std::list<LWO::Envelope>::iterator it = envelopes.begin(); it != envelopes.end(); ++it) { + + (*it).old_first = 0; + (*it).old_last = (*it).keys.size() - 1; + + if ((*it).keys.empty()) { + continue; + } + if ((int)(*it).type < 1 || (int)(*it).type>EnvelopeType_Unknown) { + continue; + } + switch ((*it).type) { + // translation + case LWO::EnvelopeType_Position_X: + trans_x = &*it; + break; + case LWO::EnvelopeType_Position_Y: + trans_y = &*it; + break; + case LWO::EnvelopeType_Position_Z: + trans_z = &*it; + break; + + // rotation + case LWO::EnvelopeType_Rotation_Heading: + rotat_x = &*it; + break; + case LWO::EnvelopeType_Rotation_Pitch: + rotat_y = &*it; + break; + case LWO::EnvelopeType_Rotation_Bank: + rotat_z = &*it; + break; + + // scaling + case LWO::EnvelopeType_Scaling_X: + scale_x = &*it; + break; + case LWO::EnvelopeType_Scaling_Y: + scale_y = &*it; + break; + case LWO::EnvelopeType_Scaling_Z: + scale_z = &*it; + break; + default: + continue; + }; + + // convert from seconds to ticks + for (std::vector<LWO::Key>::iterator d = (*it).keys.begin(); d != (*it).keys.end(); ++d) + (*d).time *= tick; + + // set default animation range (minimum and maximum time value for which we have a keyframe) + first = std::min(first, (*it).keys.front().time); + last = std::max(last, (*it).keys.back().time); + } + + // deferred setup of animation range to increase performance. + // typically the application will want to specify its own. + need_to_setup = true; +} + +// ------------------------------------------------------------------------------------------------ +// Reset all envelopes to their original contents +void AnimResolver::ClearAnimRangeSetup() { + for (std::list<LWO::Envelope>::iterator it = envelopes.begin(); it != envelopes.end(); ++it) { + + (*it).keys.erase((*it).keys.begin(), (*it).keys.begin() + (*it).old_first); + (*it).keys.erase((*it).keys.begin() + (*it).old_last + 1, (*it).keys.end()); + } +} + +// ------------------------------------------------------------------------------------------------ +// Insert additional keys to match LWO's pre& post behaviors. +void AnimResolver::UpdateAnimRangeSetup() { + // XXX doesn't work yet (hangs if more than one envelope channels needs to be interpolated) + + for (std::list<LWO::Envelope>::iterator it = envelopes.begin(); it != envelopes.end(); ++it) { + if ((*it).keys.empty()) continue; + + const double my_first = (*it).keys.front().time; + const double my_last = (*it).keys.back().time; + + const double delta = my_last - my_first; + const size_t old_size = (*it).keys.size(); + + const float value_delta = (*it).keys.back().value - (*it).keys.front().value; + + // NOTE: We won't handle reset, linear and constant here. + // See DoInterpolation() for their implementation. + + // process pre behavior + switch ((*it).pre) { + case LWO::PrePostBehaviour_OffsetRepeat: + case LWO::PrePostBehaviour_Repeat: + case LWO::PrePostBehaviour_Oscillate: { + const double start_time = delta - std::fmod(my_first - first, delta); + std::vector<LWO::Key>::iterator n = std::find_if((*it).keys.begin(), (*it).keys.end(), + [start_time](double t) { return start_time > t; }), + m; + + size_t ofs = 0; + if (n != (*it).keys.end()) { + // copy from here - don't use iterators, insert() would invalidate them + ofs = (*it).keys.end() - n; + (*it).keys.insert((*it).keys.begin(), ofs, LWO::Key()); + + std::copy((*it).keys.end() - ofs, (*it).keys.end(), (*it).keys.begin()); + } + + // do full copies. again, no iterators + const unsigned int num = (unsigned int)((my_first - first) / delta); + (*it).keys.resize((*it).keys.size() + num * old_size); + + n = (*it).keys.begin() + ofs; + bool reverse = false; + for (unsigned int i = 0; i < num; ++i) { + m = n + old_size * (i + 1); + std::copy(n, n + old_size, m); + const bool res = ((*it).pre == LWO::PrePostBehaviour_Oscillate); + reverse = !reverse; + if (res && reverse) { + std::reverse(m, m + old_size - 1); + } + } + + // update time values + n = (*it).keys.end() - (old_size + 1); + double cur_minus = delta; + unsigned int tt = 1; + for (const double tmp = delta * (num + 1); cur_minus <= tmp; cur_minus += delta, ++tt) { + m = (delta == tmp ? (*it).keys.begin() : n - (old_size + 1)); + for (; m != n; --n) { + (*n).time -= cur_minus; + + // offset repeat? add delta offset to key value + if ((*it).pre == LWO::PrePostBehaviour_OffsetRepeat) { + (*n).value += tt * value_delta; + } + } + } + break; + } + default: + // silence compiler warning + break; + } + + // process post behavior + switch ((*it).post) { + + case LWO::PrePostBehaviour_OffsetRepeat: + case LWO::PrePostBehaviour_Repeat: + case LWO::PrePostBehaviour_Oscillate: + + break; + + default: + // silence compiler warning + break; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Extract bind pose matrix +void AnimResolver::ExtractBindPose(aiMatrix4x4 &out) { + // If we have no envelopes, return identity + if (envelopes.empty()) { + out = aiMatrix4x4(); + return; + } + aiVector3D angles, scaling(1.f, 1.f, 1.f), translation; + + if (trans_x) translation.x = trans_x->keys[0].value; + if (trans_y) translation.y = trans_y->keys[0].value; + if (trans_z) translation.z = trans_z->keys[0].value; + + if (rotat_x) angles.x = rotat_x->keys[0].value; + if (rotat_y) angles.y = rotat_y->keys[0].value; + if (rotat_z) angles.z = rotat_z->keys[0].value; + + if (scale_x) scaling.x = scale_x->keys[0].value; + if (scale_y) scaling.y = scale_y->keys[0].value; + if (scale_z) scaling.z = scale_z->keys[0].value; + + // build the final matrix + aiMatrix4x4 s, rx, ry, rz, t; + aiMatrix4x4::RotationZ(angles.z, rz); + aiMatrix4x4::RotationX(angles.y, rx); + aiMatrix4x4::RotationY(angles.x, ry); + aiMatrix4x4::Translation(translation, t); + aiMatrix4x4::Scaling(scaling, s); + out = t * ry * rx * rz * s; +} + +// ------------------------------------------------------------------------------------------------ +// Do a single interpolation on a channel +void AnimResolver::DoInterpolation(std::vector<LWO::Key>::const_iterator cur, + LWO::Envelope *envl, double time, float &fill) { + if (envl->keys.size() == 1) { + fill = envl->keys[0].value; + return; + } + + // check whether we're at the beginning of the animation track + if (cur == envl->keys.begin()) { + + // ok ... this depends on pre behaviour now + // we don't need to handle repeat&offset repeat&oszillate here, see UpdateAnimRangeSetup() + switch (envl->pre) { + case LWO::PrePostBehaviour_Linear: + DoInterpolation2(cur, cur + 1, time, fill); + return; + + case LWO::PrePostBehaviour_Reset: + fill = 0.f; + return; + + default: //case LWO::PrePostBehaviour_Constant: + fill = (*cur).value; + return; + } + } + // check whether we're at the end of the animation track + else if (cur == envl->keys.end() - 1 && time > envl->keys.rbegin()->time) { + // ok ... this depends on post behaviour now + switch (envl->post) { + case LWO::PrePostBehaviour_Linear: + DoInterpolation2(cur, cur - 1, time, fill); + return; + + case LWO::PrePostBehaviour_Reset: + fill = 0.f; + return; + + default: //case LWO::PrePostBehaviour_Constant: + fill = (*cur).value; + return; + } + } + + // Otherwise do a simple interpolation + DoInterpolation2(cur - 1, cur, time, fill); +} + +// ------------------------------------------------------------------------------------------------ +// Almost the same, except we won't handle pre/post conditions here +void AnimResolver::DoInterpolation2(std::vector<LWO::Key>::const_iterator beg, + std::vector<LWO::Key>::const_iterator end, double time, float &fill) { + switch ((*end).inter) { + + case LWO::IT_STEP: + // no interpolation at all - take the value of the last key + fill = (*beg).value; + return; + default: + + // silence compiler warning + break; + } + // linear interpolation - default + double duration = (*end).time - (*beg).time; + if (duration > 0.0) { + fill = (*beg).value + ((*end).value - (*beg).value) * (float)(((time - (*beg).time) / duration)); + } else { + fill = (*beg).value; + } +} + +// ------------------------------------------------------------------------------------------------ +// Subsample animation track by given key values +void AnimResolver::SubsampleAnimTrack(std::vector<aiVectorKey> & /*out*/, + double /*time*/, double /*sample_delta*/) { + //ai_assert(out.empty() && sample_delta); + + //const double time_start = out.back().mTime; + // for () +} + +// ------------------------------------------------------------------------------------------------ +// Track interpolation +void AnimResolver::InterpolateTrack(std::vector<aiVectorKey> &out, aiVectorKey &fill, double time) { + // subsample animation track? + if (flags & AI_LWO_ANIM_FLAG_SAMPLE_ANIMS) { + SubsampleAnimTrack(out, time, sample_delta); + } + + fill.mTime = time; + + // get x + if ((*cur_x).time == time) { + fill.mValue.x = (*cur_x).value; + + if (cur_x != envl_x->keys.end() - 1) /* increment x */ + ++cur_x; + else + end_x = true; + } else + DoInterpolation(cur_x, envl_x, time, (float &)fill.mValue.x); + + // get y + if ((*cur_y).time == time) { + fill.mValue.y = (*cur_y).value; + + if (cur_y != envl_y->keys.end() - 1) /* increment y */ + ++cur_y; + else + end_y = true; + } else + DoInterpolation(cur_y, envl_y, time, (float &)fill.mValue.y); + + // get z + if ((*cur_z).time == time) { + fill.mValue.z = (*cur_z).value; + + if (cur_z != envl_z->keys.end() - 1) /* increment z */ + ++cur_z; + else + end_x = true; + } else + DoInterpolation(cur_z, envl_z, time, (float &)fill.mValue.z); +} + +// ------------------------------------------------------------------------------------------------ +// Build linearly subsampled keys from three single envelopes, one for each component (x,y,z) +void AnimResolver::GetKeys(std::vector<aiVectorKey> &out, + LWO::Envelope *_envl_x, + LWO::Envelope *_envl_y, + LWO::Envelope *_envl_z, + unsigned int _flags) { + envl_x = _envl_x; + envl_y = _envl_y; + envl_z = _envl_z; + flags = _flags; + + // generate default channels if none are given + LWO::Envelope def_x, def_y, def_z; + LWO::Key key_dummy; + key_dummy.time = 0.f; + if ((envl_x && envl_x->type == LWO::EnvelopeType_Scaling_X) || + (envl_y && envl_y->type == LWO::EnvelopeType_Scaling_Y) || + (envl_z && envl_z->type == LWO::EnvelopeType_Scaling_Z)) { + key_dummy.value = 1.f; + } else + key_dummy.value = 0.f; + + if (!envl_x) { + envl_x = &def_x; + envl_x->keys.push_back(key_dummy); + } + if (!envl_y) { + envl_y = &def_y; + envl_y->keys.push_back(key_dummy); + } + if (!envl_z) { + envl_z = &def_z; + envl_z->keys.push_back(key_dummy); + } + + // guess how many keys we'll get + size_t reserve; + double sr = 1.; + if (flags & AI_LWO_ANIM_FLAG_SAMPLE_ANIMS) { + if (!sample_rate) + sr = 100.f; + else + sr = sample_rate; + sample_delta = 1.f / sr; + + reserve = (size_t)( + std::max(envl_x->keys.rbegin()->time, + std::max(envl_y->keys.rbegin()->time, envl_z->keys.rbegin()->time)) * + sr); + } else + reserve = std::max(envl_x->keys.size(), std::max(envl_x->keys.size(), envl_z->keys.size())); + out.reserve(reserve + (reserve >> 1)); + + // Iterate through all three arrays at once - it's tricky, but + // rather interesting to implement. + cur_x = envl_x->keys.begin(); + cur_y = envl_y->keys.begin(); + cur_z = envl_z->keys.begin(); + + end_x = end_y = end_z = false; + while (1) { + + aiVectorKey fill; + + if ((*cur_x).time == (*cur_y).time && (*cur_x).time == (*cur_z).time) { + + // we have a keyframe for all of them defined .. this means + // we don't need to interpolate here. + fill.mTime = (*cur_x).time; + + fill.mValue.x = (*cur_x).value; + fill.mValue.y = (*cur_y).value; + fill.mValue.z = (*cur_z).value; + + // subsample animation track + if (flags & AI_LWO_ANIM_FLAG_SAMPLE_ANIMS) { + //SubsampleAnimTrack(out,cur_x, cur_y, cur_z, d, sample_delta); + } + } + + // Find key with lowest time value + else if ((*cur_x).time <= (*cur_y).time && !end_x) { + + if ((*cur_z).time <= (*cur_x).time && !end_z) { + InterpolateTrack(out, fill, (*cur_z).time); + } else { + InterpolateTrack(out, fill, (*cur_x).time); + } + } else if ((*cur_z).time <= (*cur_y).time && !end_y) { + InterpolateTrack(out, fill, (*cur_y).time); + } else if (!end_y) { + // welcome on the server, y + InterpolateTrack(out, fill, (*cur_y).time); + } else { + // we have reached the end of at least 2 channels, + // only one is remaining. Extrapolate the 2. + if (end_y) { + InterpolateTrack(out, fill, (end_x ? (*cur_z) : (*cur_x)).time); + } else if (end_x) { + InterpolateTrack(out, fill, (end_z ? (*cur_y) : (*cur_z)).time); + } else { // if (end_z) + InterpolateTrack(out, fill, (end_y ? (*cur_x) : (*cur_y)).time); + } + } + double lasttime = fill.mTime; + out.push_back(fill); + + if (lasttime >= (*cur_x).time) { + if (cur_x != envl_x->keys.end() - 1) + ++cur_x; + else + end_x = true; + } + if (lasttime >= (*cur_y).time) { + if (cur_y != envl_y->keys.end() - 1) + ++cur_y; + else + end_y = true; + } + if (lasttime >= (*cur_z).time) { + if (cur_z != envl_z->keys.end() - 1) + ++cur_z; + else + end_z = true; + } + + if (end_x && end_y && end_z) /* finished? */ + break; + } + + if (flags & AI_LWO_ANIM_FLAG_START_AT_ZERO) { + for (std::vector<aiVectorKey>::iterator it = out.begin(); it != out.end(); ++it) + (*it).mTime -= first; + } +} + +// ------------------------------------------------------------------------------------------------ +// Extract animation channel +void AnimResolver::ExtractAnimChannel(aiNodeAnim **out, unsigned int /*= 0*/) { + *out = nullptr; + + //FIXME: crashes if more than one component is animated at different timings, to be resolved. + + // If we have no envelopes, return nullptr + if (envelopes.empty()) { + return; + } + + // We won't spawn an animation channel if we don't have at least one envelope with more than one keyframe defined. + const bool trans = ((trans_x && trans_x->keys.size() > 1) || (trans_y && trans_y->keys.size() > 1) || (trans_z && trans_z->keys.size() > 1)); + const bool rotat = ((rotat_x && rotat_x->keys.size() > 1) || (rotat_y && rotat_y->keys.size() > 1) || (rotat_z && rotat_z->keys.size() > 1)); + const bool scale = ((scale_x && scale_x->keys.size() > 1) || (scale_y && scale_y->keys.size() > 1) || (scale_z && scale_z->keys.size() > 1)); + if (!trans && !rotat && !scale) + return; + + // Allocate the output animation + aiNodeAnim *anim = *out = new aiNodeAnim(); + + // Setup default animation setup if necessary + if (need_to_setup) { + UpdateAnimRangeSetup(); + need_to_setup = false; + } + + // copy translation keys + if (trans) { + std::vector<aiVectorKey> keys; + GetKeys(keys, trans_x, trans_y, trans_z, flags); + + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys = static_cast<unsigned int>(keys.size())]; + std::copy(keys.begin(), keys.end(), anim->mPositionKeys); + } + + // copy rotation keys + if (rotat) { + std::vector<aiVectorKey> keys; + GetKeys(keys, rotat_x, rotat_y, rotat_z, flags); + + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys = static_cast<unsigned int>(keys.size())]; + + // convert heading, pitch, bank to quaternion + // mValue.x=Heading=Rot(Y), mValue.y=Pitch=Rot(X), mValue.z=Bank=Rot(Z) + // Lightwave's rotation order is ZXY + aiVector3D X(1.0, 0.0, 0.0); + aiVector3D Y(0.0, 1.0, 0.0); + aiVector3D Z(0.0, 0.0, 1.0); + for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { + aiQuatKey &qk = anim->mRotationKeys[i]; + qk.mTime = keys[i].mTime; + qk.mValue = aiQuaternion(Y, keys[i].mValue.x) * aiQuaternion(X, keys[i].mValue.y) * aiQuaternion(Z, keys[i].mValue.z); + } + } + + // copy scaling keys + if (scale) { + std::vector<aiVectorKey> keys; + GetKeys(keys, scale_x, scale_y, scale_z, flags); + + anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys = static_cast<unsigned int>(keys.size())]; + std::copy(keys.begin(), keys.end(), anim->mScalingKeys); + } +} + +#endif // no lwo or no lws diff --git a/libs/assimp/code/AssetLib/LWO/LWOAnimation.h b/libs/assimp/code/AssetLib/LWO/LWOAnimation.h new file mode 100644 index 0000000..1e419d4 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOAnimation.h @@ -0,0 +1,346 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 LWOAnimation.h + * @brief LWOAnimationResolver utility class + * + * This is for all lightwave-related file format, not only LWO. + * LWS isthe main purpose. +*/ +#ifndef AI_LWO_ANIMATION_INCLUDED +#define AI_LWO_ANIMATION_INCLUDED + +// +#include <vector> +#include <list> + +struct aiNodeAnim; +struct aiVectorKey; + +namespace Assimp { +namespace LWO { + +// --------------------------------------------------------------------------- +/** \brief List of recognized LWO envelopes + */ +enum EnvelopeType +{ + EnvelopeType_Position_X = 0x1, + EnvelopeType_Position_Y = 0x2, + EnvelopeType_Position_Z = 0x3, + + EnvelopeType_Rotation_Heading = 0x4, + EnvelopeType_Rotation_Pitch = 0x5, + EnvelopeType_Rotation_Bank = 0x6, + + EnvelopeType_Scaling_X = 0x7, + EnvelopeType_Scaling_Y = 0x8, + EnvelopeType_Scaling_Z = 0x9, + + // -- currently not yet handled + EnvelopeType_Color_R = 0xa, + EnvelopeType_Color_G = 0xb, + EnvelopeType_Color_B = 0xc, + + EnvelopeType_Falloff_X = 0xd, + EnvelopeType_Falloff_Y = 0xe, + EnvelopeType_Falloff_Z = 0xf, + + EnvelopeType_Unknown +}; + +// --------------------------------------------------------------------------- +/** \brief List of recognized LWO interpolation modes + */ +enum InterpolationType +{ + IT_STEP, IT_LINE, IT_TCB, IT_HERM, IT_BEZI, IT_BEZ2 +}; + + +// --------------------------------------------------------------------------- +/** \brief List of recognized LWO pre or post range behaviours + */ +enum PrePostBehaviour +{ + PrePostBehaviour_Reset = 0x0, + PrePostBehaviour_Constant = 0x1, + PrePostBehaviour_Repeat = 0x2, + PrePostBehaviour_Oscillate = 0x3, + PrePostBehaviour_OffsetRepeat = 0x4, + PrePostBehaviour_Linear = 0x5 +}; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a LWO animation keyframe + */ +struct Key { + Key() AI_NO_EXCEPT + : time() + , value() + , inter(IT_LINE) + , params() { + // empty + } + + //! Current time + double time; + + //! Current value + float value; + + //! How to interpolate this key with previous key? + InterpolationType inter; + + //! Interpolation parameters + float params[5]; + + + // for std::find() + operator double () { + return time; + } +}; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a LWO animation envelope + */ +struct Envelope { + Envelope() AI_NO_EXCEPT + : index() + , type(EnvelopeType_Unknown) + , pre(PrePostBehaviour_Constant) + , post(PrePostBehaviour_Constant) + , old_first(0) + , old_last(0) { + // empty + } + + //! Index of this envelope + unsigned int index; + + //! Type of envelope + EnvelopeType type; + + //! Pre- and post-behavior + PrePostBehaviour pre,post; + + //! Keyframes for this envelope + std::vector<Key> keys; + + // temporary data for AnimResolver + size_t old_first,old_last; +}; + +// --------------------------------------------------------------------------- +//! @def AI_LWO_ANIM_FLAG_SAMPLE_ANIMS +//! Flag for AnimResolver, subsamples the input data with the rate specified +//! by AnimResolver::SetSampleRate(). +#define AI_LWO_ANIM_FLAG_SAMPLE_ANIMS 0x1 + + +// --------------------------------------------------------------------------- +//! @def AI_LWO_ANIM_FLAG_START_AT_ZERO +//! Flag for AnimResolver, ensures that the animations starts at zero. +#define AI_LWO_ANIM_FLAG_START_AT_ZERO 0x2 + +// --------------------------------------------------------------------------- +/** @brief Utility class to build Assimp animations from LWO envelopes. + * + * Used for both LWO and LWS (MOT also). + */ +class AnimResolver +{ +public: + + // ------------------------------------------------------------------ + /** @brief Construct an AnimResolver from a given list of envelopes + * @param envelopes Input envelopes. May be empty. + * @param Output tick rate, per second + * @note The input envelopes are possibly modified. + */ + AnimResolver(std::list<Envelope>& envelopes, double tick); + +public: + + // ------------------------------------------------------------------ + /** @brief Extract the bind-pose transformation matrix. + * @param out Receives bind-pose transformation matrix + */ + void ExtractBindPose(aiMatrix4x4& out); + + // ------------------------------------------------------------------ + /** @brief Extract a node animation channel + * @param out Receives a pointer to a newly allocated node anim. + * If there's just one keyframe defined, *out is set to nullptr and + * no animation channel is computed. + * @param flags Any combination of the AI_LWO_ANIM_FLAG_XXX flags. + */ + void ExtractAnimChannel(aiNodeAnim** out, unsigned int flags = 0); + + + // ------------------------------------------------------------------ + /** @brief Set the sampling rate for ExtractAnimChannel(). + * + * Non-linear interpolations are subsampled with this rate (keys + * per second). Closer sampling positions, if existent, are kept. + * The sampling rate defaults to 0, if this value is not changed and + * AI_LWO_ANIM_FLAG_SAMPLE_ANIMS is specified for ExtractAnimChannel(), + * the class finds a suitable sample rate by itself. + */ + void SetSampleRate(double sr) { + sample_rate = sr; + } + + // ------------------------------------------------------------------ + /** @brief Getter for SetSampleRate() + */ + double GetSampleRate() const { + return sample_rate; + } + + // ------------------------------------------------------------------ + /** @brief Set the animation time range + * + * @param first Time where the animation starts, in ticks + * @param last Time where the animation ends, in ticks + */ + void SetAnimationRange(double _first, double _last) { + first = _first; + last = _last; + + ClearAnimRangeSetup(); + UpdateAnimRangeSetup(); + } + +protected: + + // ------------------------------------------------------------------ + /** @brief Build linearly subsampled keys from 3 single envelopes + * @param out Receives output keys + * @param envl_x X-component envelope + * @param envl_y Y-component envelope + * @param envl_z Z-component envelope + * @param flags Any combination of the AI_LWO_ANIM_FLAG_XXX flags. + * @note Up to two input envelopes may be nullptr + */ + void GetKeys(std::vector<aiVectorKey>& out, + LWO::Envelope* envl_x, + LWO::Envelope* envl_y, + LWO::Envelope* envl_z, + unsigned int flags); + + // ------------------------------------------------------------------ + /** @brief Resolve a single animation key by applying the right + * interpolation to it. + * @param cur Current key + * @param envl Envelope working on + * @param time time to be interpolated + * @param fill Receives the interpolated output value. + */ + void DoInterpolation(std::vector<LWO::Key>::const_iterator cur, + LWO::Envelope* envl,double time, float& fill); + + // ------------------------------------------------------------------ + /** @brief Almost the same, except we won't handle pre/post + * conditions here. + * @see DoInterpolation + */ + void DoInterpolation2(std::vector<LWO::Key>::const_iterator beg, + std::vector<LWO::Key>::const_iterator end,double time, float& fill); + + // ------------------------------------------------------------------ + /** @brief Interpolate 2 tracks if one is given + * + * @param out Receives extra output keys + * @param key_out Primary output key + * @param time Time to interpolate for + */ + void InterpolateTrack(std::vector<aiVectorKey>& out, + aiVectorKey& key_out,double time); + + // ------------------------------------------------------------------ + /** @brief Subsample an animation track by a given sampling rate + * + * @param out Receives output keys. Last key at input defines the + * time where subsampling starts. + * @param time Time to end subsampling at + * @param sample_delta Time delta between two samples + */ + void SubsampleAnimTrack(std::vector<aiVectorKey>& out, + double time,double sample_delta); + + // ------------------------------------------------------------------ + /** @brief Delete all keys which we inserted to match anim setup + */ + void ClearAnimRangeSetup(); + + // ------------------------------------------------------------------ + /** @brief Insert extra keys to match LWO's pre and post behaviours + * in a given time range [first...last] + */ + void UpdateAnimRangeSetup(); + +private: + std::list<Envelope>& envelopes; + double sample_rate; + + LWO::Envelope* trans_x, *trans_y, *trans_z; + LWO::Envelope* rotat_x, *rotat_y, *rotat_z; + LWO::Envelope* scale_x, *scale_y, *scale_z; + + double first, last; + bool need_to_setup; + + // temporary storage + LWO::Envelope* envl_x, * envl_y, * envl_z; + std::vector<LWO::Key>::const_iterator cur_x,cur_y,cur_z; + bool end_x, end_y, end_z; + + unsigned int flags; + double sample_delta; +}; + +} // end namespace LWO +} // end namespace Assimp + +#endif // !! AI_LWO_ANIMATION_INCLUDED diff --git a/libs/assimp/code/AssetLib/LWO/LWOBLoader.cpp b/libs/assimp/code/AssetLib/LWO/LWOBLoader.cpp new file mode 100644 index 0000000..1fbd9b9 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOBLoader.cpp @@ -0,0 +1,428 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the LWO importer class for the older LWOB + file formats, including materials */ + + +#ifndef ASSIMP_BUILD_NO_LWO_IMPORTER + +// Internal headers +#include "LWOLoader.h" +using namespace Assimp; + + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWOBFile() +{ + LE_NCONST uint8_t* const end = mFileBuffer + fileSize; + bool running = true; + while (running) + { + if (mFileBuffer + sizeof(IFF::ChunkHeader) > end)break; + const IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) + { + throw DeadlyImportError("LWOB: Invalid chunk length"); + break; + } + uint8_t* const next = mFileBuffer+head.length; + switch (head.type) + { + // vertex list + case AI_LWO_PNTS: + { + if (!mCurLayer->mTempPoints.empty()) + ASSIMP_LOG_WARN("LWO: PNTS chunk encountered twice"); + else LoadLWOPoints(head.length); + break; + } + // face list + case AI_LWO_POLS: + { + + if (!mCurLayer->mFaces.empty()) + ASSIMP_LOG_WARN("LWO: POLS chunk encountered twice"); + else LoadLWOBPolygons(head.length); + break; + } + // list of tags + case AI_LWO_SRFS: + { + if (!mTags->empty()) + ASSIMP_LOG_WARN("LWO: SRFS chunk encountered twice"); + else LoadLWOTags(head.length); + break; + } + + // surface chunk + case AI_LWO_SURF: + { + LoadLWOBSurface(head.length); + break; + } + } + mFileBuffer = next; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWOBPolygons(unsigned int length) +{ + // first find out how many faces and vertices we'll finally need + LE_NCONST uint16_t* const end = (LE_NCONST uint16_t*)(mFileBuffer+length); + LE_NCONST uint16_t* cursor = (LE_NCONST uint16_t*)mFileBuffer; + + // perform endianness conversions +#ifndef AI_BUILD_BIG_ENDIAN + while (cursor < end)ByteSwap::Swap2(cursor++); + cursor = (LE_NCONST uint16_t*)mFileBuffer; +#endif + + unsigned int iNumFaces = 0,iNumVertices = 0; + CountVertsAndFacesLWOB(iNumVertices,iNumFaces,cursor,end); + + // allocate the output array and copy face indices + if (iNumFaces) + { + cursor = (LE_NCONST uint16_t*)mFileBuffer; + + mCurLayer->mFaces.resize(iNumFaces); + FaceList::iterator it = mCurLayer->mFaces.begin(); + CopyFaceIndicesLWOB(it,cursor,end); + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::CountVertsAndFacesLWOB(unsigned int& verts, unsigned int& faces, + LE_NCONST uint16_t*& cursor, const uint16_t* const end, unsigned int max) +{ + while (cursor < end && max--) + { + uint16_t numIndices; + // must have 2 shorts left for numIndices and surface + if (end - cursor < 2) { + throw DeadlyImportError("LWOB: Unexpected end of file"); + } + ::memcpy(&numIndices, cursor++, 2); + // must have enough left for indices and surface + if (end - cursor < (1 + numIndices)) { + throw DeadlyImportError("LWOB: Unexpected end of file"); + } + verts += numIndices; + faces++; + cursor += numIndices; + int16_t surface; + ::memcpy(&surface, cursor++, 2); + if (surface < 0) + { + // there are detail polygons + ::memcpy(&numIndices, cursor++, 2); + CountVertsAndFacesLWOB(verts,faces,cursor,end,numIndices); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::CopyFaceIndicesLWOB(FaceList::iterator& it, + LE_NCONST uint16_t*& cursor, + const uint16_t* const end, + unsigned int max) +{ + while (cursor < end && max--) + { + LWO::Face& face = *it;++it; + uint16_t numIndices; + ::memcpy(&numIndices, cursor++, 2); + face.mNumIndices = numIndices; + if(face.mNumIndices) + { + if (cursor + face.mNumIndices >= end) + { + break; + } + face.mIndices = new unsigned int[face.mNumIndices]; + for (unsigned int i = 0; i < face.mNumIndices;++i) { + unsigned int & mi = face.mIndices[i]; + uint16_t index; + ::memcpy(&index, cursor++, 2); + mi = index; + if (mi > mCurLayer->mTempPoints.size()) + { + ASSIMP_LOG_WARN("LWOB: face index is out of range"); + mi = (unsigned int)mCurLayer->mTempPoints.size()-1; + } + } + } else { + ASSIMP_LOG_WARN("LWOB: Face has 0 indices"); + } + int16_t surface; + ::memcpy(&surface, cursor++, 2); + if (surface < 0) + { + surface = -surface; + + // there are detail polygons. + uint16_t numPolygons; + ::memcpy(&numPolygons, cursor++, 2); + if (cursor < end) + { + CopyFaceIndicesLWOB(it,cursor,end,numPolygons); + } + } + face.surfaceIndex = surface-1; + } +} + +// ------------------------------------------------------------------------------------------------ +LWO::Texture* LWOImporter::SetupNewTextureLWOB(LWO::TextureList& list,unsigned int size) +{ + list.push_back(LWO::Texture()); + LWO::Texture* tex = &list.back(); + + std::string type; + GetS0(type,size); + const char* s = type.c_str(); + + if(strstr(s, "Image Map")) + { + // Determine mapping type + if(strstr(s, "Planar")) + tex->mapMode = LWO::Texture::Planar; + else if(strstr(s, "Cylindrical")) + tex->mapMode = LWO::Texture::Cylindrical; + else if(strstr(s, "Spherical")) + tex->mapMode = LWO::Texture::Spherical; + else if(strstr(s, "Cubic")) + tex->mapMode = LWO::Texture::Cubic; + else if(strstr(s, "Front")) + tex->mapMode = LWO::Texture::FrontProjection; + } + else + { + // procedural or gradient, not supported + ASSIMP_LOG_ERROR("LWOB: Unsupported legacy texture: ", type); + } + + return tex; +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWOBSurface(unsigned int size) +{ + LE_NCONST uint8_t* const end = mFileBuffer + size; + + mSurfaces->push_back( LWO::Surface () ); + LWO::Surface& surf = mSurfaces->back(); + LWO::Texture *pTex = nullptr; + + GetS0(surf.mName,size); + bool running = true; + while (running) { + if (mFileBuffer + 6 >= end) + break; + + IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + + /* A single test file (sonycam.lwo) seems to have invalid surface chunks. + * I'm assuming it's the fault of a single, unknown exporter so there are + * probably THOUSANDS of them. Here's a dirty workaround: + * + * We don't break if the chunk limit is exceeded. Instead, we're computing + * how much storage is actually left and work with this value from now on. + */ + if (mFileBuffer + head.length > end) { + ASSIMP_LOG_ERROR("LWOB: Invalid surface chunk length. Trying to continue."); + head.length = (uint16_t) (end - mFileBuffer); + } + + uint8_t* const next = mFileBuffer+head.length; + switch (head.type) + { + // diffuse color + case AI_LWO_COLR: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,COLR,3); + surf.mColor.r = GetU1() / 255.0f; + surf.mColor.g = GetU1() / 255.0f; + surf.mColor.b = GetU1() / 255.0f; + break; + } + // diffuse strength ... + case AI_LWO_DIFF: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,DIFF,2); + surf.mDiffuseValue = GetU2() / 255.0f; + break; + } + // specular strength ... + case AI_LWO_SPEC: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,SPEC,2); + surf.mSpecularValue = GetU2() / 255.0f; + break; + } + // luminosity ... + case AI_LWO_LUMI: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,LUMI,2); + surf.mLuminosity = GetU2() / 255.0f; + break; + } + // transparency + case AI_LWO_TRAN: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,TRAN,2); + surf.mTransparency = GetU2() / 255.0f; + break; + } + // surface flags + case AI_LWO_FLAG: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,FLAG,2); + uint16_t flag = GetU2(); + if (flag & 0x4 ) surf.mMaximumSmoothAngle = 1.56207f; + if (flag & 0x8 ) surf.mColorHighlights = 1.f; + if (flag & 0x100) surf.bDoubleSided = true; + break; + } + // maximum smoothing angle + case AI_LWO_SMAN: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,SMAN,4); + surf.mMaximumSmoothAngle = std::fabs( GetF4() ); + break; + } + // glossiness + case AI_LWO_GLOS: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,GLOS,2); + surf.mGlossiness = (float)GetU2(); + break; + } + // color texture + case AI_LWO_CTEX: + { + pTex = SetupNewTextureLWOB(surf.mColorTextures, + head.length); + break; + } + // diffuse texture + case AI_LWO_DTEX: + { + pTex = SetupNewTextureLWOB(surf.mDiffuseTextures, + head.length); + break; + } + // specular texture + case AI_LWO_STEX: + { + pTex = SetupNewTextureLWOB(surf.mSpecularTextures, + head.length); + break; + } + // bump texture + case AI_LWO_BTEX: + { + pTex = SetupNewTextureLWOB(surf.mBumpTextures, + head.length); + break; + } + // transparency texture + case AI_LWO_TTEX: + { + pTex = SetupNewTextureLWOB(surf.mOpacityTextures, + head.length); + break; + } + // texture path + case AI_LWO_TIMG: + { + if (pTex) { + GetS0(pTex->mFileName,head.length); + } else { + ASSIMP_LOG_WARN("LWOB: Unexpected TIMG chunk"); + } + break; + } + // texture strength + case AI_LWO_TVAL: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,TVAL,1); + if (pTex) { + pTex->mStrength = (float)GetU1()/ 255.f; + } else { + ASSIMP_LOG_ERROR("LWOB: Unexpected TVAL chunk"); + } + break; + } + // texture flags + case AI_LWO_TFLG: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,TFLG,2); + + if (nullptr != pTex) { + const uint16_t s = GetU2(); + if (s & 1) + pTex->majorAxis = LWO::Texture::AXIS_X; + else if (s & 2) + pTex->majorAxis = LWO::Texture::AXIS_Y; + else if (s & 4) + pTex->majorAxis = LWO::Texture::AXIS_Z; + + if (s & 16) { + ASSIMP_LOG_WARN("LWOB: Ignoring \'negate\' flag on texture"); + } + } + else { + ASSIMP_LOG_WARN("LWOB: Unexpected TFLG chunk"); + } + break; + } + } + mFileBuffer = next; + } +} + +#endif // !! ASSIMP_BUILD_NO_LWO_IMPORTER diff --git a/libs/assimp/code/AssetLib/LWO/LWOFileData.h b/libs/assimp/code/AssetLib/LWO/LWOFileData.h new file mode 100644 index 0000000..f477f58 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOFileData.h @@ -0,0 +1,638 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 LWOFileData.h + * @brief Defines chunk constants used by the LWO file format + +The chunks are taken from the official LightWave SDK headers. + +*/ +#ifndef AI_LWO_FILEDATA_INCLUDED +#define AI_LWO_FILEDATA_INCLUDED + +// STL headers +#include <list> +#include <vector> + +// public ASSIMP headers +#include <assimp/mesh.h> + +// internal headers +#include "AssetLib/LWO/LWOAnimation.h" +#include "Common/IFF.h" + +namespace Assimp { +namespace LWO { + +#define AI_LWO_FOURCC_LWOB AI_IFF_FOURCC('L', 'W', 'O', 'B') +#define AI_LWO_FOURCC_LWO2 AI_IFF_FOURCC('L', 'W', 'O', '2') +#define AI_LWO_FOURCC_LXOB AI_IFF_FOURCC('L', 'X', 'O', 'B') + +// chunks specific to the LWOB format +#define AI_LWO_SRFS AI_IFF_FOURCC('S', 'R', 'F', 'S') +#define AI_LWO_FLAG AI_IFF_FOURCC('F', 'L', 'A', 'G') +#define AI_LWO_VLUM AI_IFF_FOURCC('V', 'L', 'U', 'M') +#define AI_LWO_VDIF AI_IFF_FOURCC('V', 'D', 'I', 'F') +#define AI_LWO_VSPC AI_IFF_FOURCC('V', 'S', 'P', 'C') +#define AI_LWO_RFLT AI_IFF_FOURCC('R', 'F', 'L', 'T') +#define AI_LWO_BTEX AI_IFF_FOURCC('B', 'T', 'E', 'X') +#define AI_LWO_CTEX AI_IFF_FOURCC('C', 'T', 'E', 'X') +#define AI_LWO_DTEX AI_IFF_FOURCC('D', 'T', 'E', 'X') +#define AI_LWO_LTEX AI_IFF_FOURCC('L', 'T', 'E', 'X') +#define AI_LWO_RTEX AI_IFF_FOURCC('R', 'T', 'E', 'X') +#define AI_LWO_STEX AI_IFF_FOURCC('S', 'T', 'E', 'X') +#define AI_LWO_TTEX AI_IFF_FOURCC('T', 'T', 'E', 'X') +#define AI_LWO_TFLG AI_IFF_FOURCC('T', 'F', 'L', 'G') +#define AI_LWO_TSIZ AI_IFF_FOURCC('T', 'S', 'I', 'Z') +#define AI_LWO_TCTR AI_IFF_FOURCC('T', 'C', 'T', 'R') +#define AI_LWO_TFAL AI_IFF_FOURCC('T', 'F', 'A', 'L') +#define AI_LWO_TVEL AI_IFF_FOURCC('T', 'V', 'E', 'L') +#define AI_LWO_TCLR AI_IFF_FOURCC('T', 'C', 'L', 'R') +#define AI_LWO_TVAL AI_IFF_FOURCC('T', 'V', 'A', 'L') +#define AI_LWO_TAMP AI_IFF_FOURCC('T', 'A', 'M', 'P') +#define AI_LWO_TIMG AI_IFF_FOURCC('T', 'I', 'M', 'G') +#define AI_LWO_TAAS AI_IFF_FOURCC('T', 'A', 'A', 'S') +#define AI_LWO_TREF AI_IFF_FOURCC('T', 'R', 'E', 'F') +#define AI_LWO_TOPC AI_IFF_FOURCC('T', 'O', 'P', 'C') +#define AI_LWO_SDAT AI_IFF_FOURCC('S', 'D', 'A', 'T') +#define AI_LWO_TFP0 AI_IFF_FOURCC('T', 'F', 'P', '0') +#define AI_LWO_TFP1 AI_IFF_FOURCC('T', 'F', 'P', '1') + +/* top-level chunks */ +#define AI_LWO_LAYR AI_IFF_FOURCC('L', 'A', 'Y', 'R') +#define AI_LWO_TAGS AI_IFF_FOURCC('T', 'A', 'G', 'S') +#define AI_LWO_PNTS AI_IFF_FOURCC('P', 'N', 'T', 'S') +#define AI_LWO_BBOX AI_IFF_FOURCC('B', 'B', 'O', 'X') +#define AI_LWO_VMAP AI_IFF_FOURCC('V', 'M', 'A', 'P') +#define AI_LWO_VMAD AI_IFF_FOURCC('V', 'M', 'A', 'D') +#define AI_LWO_POLS AI_IFF_FOURCC('P', 'O', 'L', 'S') +#define AI_LWO_PTAG AI_IFF_FOURCC('P', 'T', 'A', 'G') +#define AI_LWO_ENVL AI_IFF_FOURCC('E', 'N', 'V', 'L') +#define AI_LWO_CLIP AI_IFF_FOURCC('C', 'L', 'I', 'P') +#define AI_LWO_SURF AI_IFF_FOURCC('S', 'U', 'R', 'F') +#define AI_LWO_DESC AI_IFF_FOURCC('D', 'E', 'S', 'C') +#define AI_LWO_TEXT AI_IFF_FOURCC('T', 'E', 'X', 'T') +#define AI_LWO_ICON AI_IFF_FOURCC('I', 'C', 'O', 'N') + +/* polygon types */ +#define AI_LWO_FACE AI_IFF_FOURCC('F', 'A', 'C', 'E') +#define AI_LWO_CURV AI_IFF_FOURCC('C', 'U', 'R', 'V') +#define AI_LWO_PTCH AI_IFF_FOURCC('P', 'T', 'C', 'H') +#define AI_LWO_MBAL AI_IFF_FOURCC('M', 'B', 'A', 'L') +#define AI_LWO_BONE AI_IFF_FOURCC('B', 'O', 'N', 'E') +#define AI_LWO_SUBD AI_IFF_FOURCC('S', 'U', 'B', 'D') + +/* polygon tags */ +#define AI_LWO_SURF AI_IFF_FOURCC('S', 'U', 'R', 'F') +#define AI_LWO_PART AI_IFF_FOURCC('P', 'A', 'R', 'T') +#define AI_LWO_SMGP AI_IFF_FOURCC('S', 'M', 'G', 'P') + +/* envelopes */ +#define AI_LWO_PRE AI_IFF_FOURCC('P', 'R', 'E', ' ') +#define AI_LWO_POST AI_IFF_FOURCC('P', 'O', 'S', 'T') +#define AI_LWO_KEY AI_IFF_FOURCC('K', 'E', 'Y', ' ') +#define AI_LWO_SPAN AI_IFF_FOURCC('S', 'P', 'A', 'N') +#define AI_LWO_TCB AI_IFF_FOURCC('T', 'C', 'B', ' ') +#define AI_LWO_HERM AI_IFF_FOURCC('H', 'E', 'R', 'M') +#define AI_LWO_BEZI AI_IFF_FOURCC('B', 'E', 'Z', 'I') +#define AI_LWO_BEZ2 AI_IFF_FOURCC('B', 'E', 'Z', '2') +#define AI_LWO_LINE AI_IFF_FOURCC('L', 'I', 'N', 'E') +#define AI_LWO_STEP AI_IFF_FOURCC('S', 'T', 'E', 'P') + +/* clips */ +#define AI_LWO_STIL AI_IFF_FOURCC('S', 'T', 'I', 'L') +#define AI_LWO_ISEQ AI_IFF_FOURCC('I', 'S', 'E', 'Q') +#define AI_LWO_ANIM AI_IFF_FOURCC('A', 'N', 'I', 'M') +#define AI_LWO_XREF AI_IFF_FOURCC('X', 'R', 'E', 'F') +#define AI_LWO_STCC AI_IFF_FOURCC('S', 'T', 'C', 'C') +#define AI_LWO_TIME AI_IFF_FOURCC('T', 'I', 'M', 'E') +#define AI_LWO_CONT AI_IFF_FOURCC('C', 'O', 'N', 'T') +#define AI_LWO_BRIT AI_IFF_FOURCC('B', 'R', 'I', 'T') +#define AI_LWO_SATR AI_IFF_FOURCC('S', 'A', 'T', 'R') +#define AI_LWO_HUE AI_IFF_FOURCC('H', 'U', 'E', ' ') +#define AI_LWO_GAMM AI_IFF_FOURCC('G', 'A', 'M', 'M') +#define AI_LWO_NEGA AI_IFF_FOURCC('N', 'E', 'G', 'A') +#define AI_LWO_IFLT AI_IFF_FOURCC('I', 'F', 'L', 'T') +#define AI_LWO_PFLT AI_IFF_FOURCC('P', 'F', 'L', 'T') + +/* surfaces */ +#define AI_LWO_COLR AI_IFF_FOURCC('C', 'O', 'L', 'R') +#define AI_LWO_LUMI AI_IFF_FOURCC('L', 'U', 'M', 'I') +#define AI_LWO_DIFF AI_IFF_FOURCC('D', 'I', 'F', 'F') +#define AI_LWO_SPEC AI_IFF_FOURCC('S', 'P', 'E', 'C') +#define AI_LWO_GLOS AI_IFF_FOURCC('G', 'L', 'O', 'S') +#define AI_LWO_REFL AI_IFF_FOURCC('R', 'E', 'F', 'L') +#define AI_LWO_RFOP AI_IFF_FOURCC('R', 'F', 'O', 'P') +#define AI_LWO_RIMG AI_IFF_FOURCC('R', 'I', 'M', 'G') +#define AI_LWO_RSAN AI_IFF_FOURCC('R', 'S', 'A', 'N') +#define AI_LWO_TRAN AI_IFF_FOURCC('T', 'R', 'A', 'N') +#define AI_LWO_TROP AI_IFF_FOURCC('T', 'R', 'O', 'P') +#define AI_LWO_TIMG AI_IFF_FOURCC('T', 'I', 'M', 'G') +#define AI_LWO_RIND AI_IFF_FOURCC('R', 'I', 'N', 'D') +#define AI_LWO_TRNL AI_IFF_FOURCC('T', 'R', 'N', 'L') +#define AI_LWO_BUMP AI_IFF_FOURCC('B', 'U', 'M', 'P') +#define AI_LWO_SMAN AI_IFF_FOURCC('S', 'M', 'A', 'N') +#define AI_LWO_SIDE AI_IFF_FOURCC('S', 'I', 'D', 'E') +#define AI_LWO_CLRH AI_IFF_FOURCC('C', 'L', 'R', 'H') +#define AI_LWO_CLRF AI_IFF_FOURCC('C', 'L', 'R', 'F') +#define AI_LWO_ADTR AI_IFF_FOURCC('A', 'D', 'T', 'R') +#define AI_LWO_SHRP AI_IFF_FOURCC('S', 'H', 'R', 'P') +#define AI_LWO_LINE AI_IFF_FOURCC('L', 'I', 'N', 'E') +#define AI_LWO_LSIZ AI_IFF_FOURCC('L', 'S', 'I', 'Z') +#define AI_LWO_ALPH AI_IFF_FOURCC('A', 'L', 'P', 'H') +#define AI_LWO_AVAL AI_IFF_FOURCC('A', 'V', 'A', 'L') +#define AI_LWO_GVAL AI_IFF_FOURCC('G', 'V', 'A', 'L') +#define AI_LWO_BLOK AI_IFF_FOURCC('B', 'L', 'O', 'K') +#define AI_LWO_VCOL AI_IFF_FOURCC('V', 'C', 'O', 'L') + +/* texture layer */ +#define AI_LWO_TYPE AI_IFF_FOURCC('T', 'Y', 'P', 'E') +#define AI_LWO_CHAN AI_IFF_FOURCC('C', 'H', 'A', 'N') +#define AI_LWO_NAME AI_IFF_FOURCC('N', 'A', 'M', 'E') +#define AI_LWO_ENAB AI_IFF_FOURCC('E', 'N', 'A', 'B') +#define AI_LWO_OPAC AI_IFF_FOURCC('O', 'P', 'A', 'C') +#define AI_LWO_FLAG AI_IFF_FOURCC('F', 'L', 'A', 'G') +#define AI_LWO_PROJ AI_IFF_FOURCC('P', 'R', 'O', 'J') +#define AI_LWO_STCK AI_IFF_FOURCC('S', 'T', 'C', 'K') +#define AI_LWO_TAMP AI_IFF_FOURCC('T', 'A', 'M', 'P') + +/* texture coordinates */ +#define AI_LWO_TMAP AI_IFF_FOURCC('T', 'M', 'A', 'P') +#define AI_LWO_AXIS AI_IFF_FOURCC('A', 'X', 'I', 'S') +#define AI_LWO_CNTR AI_IFF_FOURCC('C', 'N', 'T', 'R') +#define AI_LWO_SIZE AI_IFF_FOURCC('S', 'I', 'Z', 'E') +#define AI_LWO_ROTA AI_IFF_FOURCC('R', 'O', 'T', 'A') +#define AI_LWO_OREF AI_IFF_FOURCC('O', 'R', 'E', 'F') +#define AI_LWO_FALL AI_IFF_FOURCC('F', 'A', 'L', 'L') +#define AI_LWO_CSYS AI_IFF_FOURCC('C', 'S', 'Y', 'S') + +/* image map */ +#define AI_LWO_IMAP AI_IFF_FOURCC('I', 'M', 'A', 'P') +#define AI_LWO_IMAG AI_IFF_FOURCC('I', 'M', 'A', 'G') +#define AI_LWO_WRAP AI_IFF_FOURCC('W', 'R', 'A', 'P') +#define AI_LWO_WRPW AI_IFF_FOURCC('W', 'R', 'P', 'W') +#define AI_LWO_WRPH AI_IFF_FOURCC('W', 'R', 'P', 'H') +#define AI_LWO_VMAP AI_IFF_FOURCC('V', 'M', 'A', 'P') +#define AI_LWO_AAST AI_IFF_FOURCC('A', 'A', 'S', 'T') +#define AI_LWO_PIXB AI_IFF_FOURCC('P', 'I', 'X', 'B') + +/* procedural */ +#define AI_LWO_PROC AI_IFF_FOURCC('P', 'R', 'O', 'C') +#define AI_LWO_COLR AI_IFF_FOURCC('C', 'O', 'L', 'R') +#define AI_LWO_VALU AI_IFF_FOURCC('V', 'A', 'L', 'U') +#define AI_LWO_FUNC AI_IFF_FOURCC('F', 'U', 'N', 'C') +#define AI_LWO_FTPS AI_IFF_FOURCC('F', 'T', 'P', 'S') +#define AI_LWO_ITPS AI_IFF_FOURCC('I', 'T', 'P', 'S') +#define AI_LWO_ETPS AI_IFF_FOURCC('E', 'T', 'P', 'S') + +/* gradient */ +#define AI_LWO_GRAD AI_IFF_FOURCC('G', 'R', 'A', 'D') +#define AI_LWO_GRST AI_IFF_FOURCC('G', 'R', 'S', 'T') +#define AI_LWO_GREN AI_IFF_FOURCC('G', 'R', 'E', 'N') +#define AI_LWO_PNAM AI_IFF_FOURCC('P', 'N', 'A', 'M') +#define AI_LWO_INAM AI_IFF_FOURCC('I', 'N', 'A', 'M') +#define AI_LWO_GRPT AI_IFF_FOURCC('G', 'R', 'P', 'T') +#define AI_LWO_FKEY AI_IFF_FOURCC('F', 'K', 'E', 'Y') +#define AI_LWO_IKEY AI_IFF_FOURCC('I', 'K', 'E', 'Y') + +/* shader */ +#define AI_LWO_SHDR AI_IFF_FOURCC('S', 'H', 'D', 'R') +#define AI_LWO_DATA AI_IFF_FOURCC('D', 'A', 'T', 'A') + +/* VMAP types */ +#define AI_LWO_TXUV AI_IFF_FOURCC('T', 'X', 'U', 'V') +#define AI_LWO_RGB AI_IFF_FOURCC('R', 'G', 'B', ' ') +#define AI_LWO_RGBA AI_IFF_FOURCC('R', 'G', 'B', 'A') +#define AI_LWO_WGHT AI_IFF_FOURCC('W', 'G', 'H', 'T') + +#define AI_LWO_MNVW AI_IFF_FOURCC('M', 'N', 'V', 'W') +#define AI_LWO_MORF AI_IFF_FOURCC('M', 'O', 'R', 'F') +#define AI_LWO_SPOT AI_IFF_FOURCC('S', 'P', 'O', 'T') +#define AI_LWO_PICK AI_IFF_FOURCC('P', 'I', 'C', 'K') + +// MODO extension - per-vertex normal vectors +#define AI_LWO_MODO_NORM AI_IFF_FOURCC('N', 'O', 'R', 'M') + +// --------------------------------------------------------------------------- +/** \brief Data structure for a face in a LWO file + * + * \note We can't use the code in SmoothingGroups.inl here - the mesh + * structures of 3DS/ASE and LWO are too different. + */ +struct Face : public aiFace { + //! Default construction + Face() AI_NO_EXCEPT + : surfaceIndex(0), + smoothGroup(0), + type(AI_LWO_FACE) { + // empty + } + + //! Construction from given type + explicit Face(uint32_t _type) : + surfaceIndex(0), smoothGroup(0), type(_type) {} + + //! Copy construction + Face(const Face &f) : + aiFace() { + *this = f; + } + + //! Zero-based index into tags chunk + unsigned int surfaceIndex; + + //! Smooth group this face is assigned to + unsigned int smoothGroup; + + //! Type of face + uint32_t type; + + //! Assignment operator + Face &operator=(const LWO::Face &f) { + aiFace::operator=(f); + surfaceIndex = f.surfaceIndex; + smoothGroup = f.smoothGroup; + type = f.type; + return *this; + } +}; + +// --------------------------------------------------------------------------- +/** \brief Base structure for all vertex map representations + */ +struct VMapEntry { + explicit VMapEntry(unsigned int _dims) : + dims(_dims) {} + + virtual ~VMapEntry() {} + + //! allocates memory for the vertex map + virtual void Allocate(unsigned int num) { + if (!rawData.empty()) + return; // return if already allocated + + const unsigned int m = num * dims; + rawData.reserve(m + (m >> 2u)); // 25% as extra storage for VMADs + rawData.resize(m, 0.f); + abAssigned.resize(num, false); + } + + std::string name; + unsigned int dims; + + std::vector<float> rawData; + std::vector<bool> abAssigned; +}; + +// --------------------------------------------------------------------------- +/** \brief Represents an extra vertex color channel + */ +struct VColorChannel : public VMapEntry { + VColorChannel() : + VMapEntry(4) {} + + //! need to overwrite this function - the alpha channel must + //! be initialized to 1.0 by default + virtual void Allocate(unsigned int num) { + if (!rawData.empty()) + return; // return if already allocated + + unsigned int m = num * dims; + rawData.reserve(m + (m >> 2u)); // 25% as extra storage for VMADs + rawData.resize(m); + + for (aiColor4D *p = (aiColor4D *)&rawData[0]; p < (aiColor4D *)&rawData[m - 1]; ++p) + p->a = 1.f; + + abAssigned.resize(num, false); + } +}; + +// --------------------------------------------------------------------------- +/** \brief Represents an extra vertex UV channel + */ +struct UVChannel : public VMapEntry { + UVChannel() : + VMapEntry(2) {} +}; + +// --------------------------------------------------------------------------- +/** \brief Represents a weight map + */ +struct WeightChannel : public VMapEntry { + WeightChannel() : + VMapEntry(1) {} +}; + +// --------------------------------------------------------------------------- +/** \brief Represents a vertex-normals channel (MODO extension) + */ +struct NormalChannel : public VMapEntry { + NormalChannel() : + VMapEntry(3) {} +}; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a LWO file texture + */ +struct Texture { + // we write the enum values out here to make debugging easier ... + enum BlendType { + Normal = 0, + Subtractive = 1, + Difference = 2, + Multiply = 3, + Divide = 4, + Alpha = 5, + TextureDispl = 6, + Additive = 7 + }; + + enum MappingMode { + Planar = 0, + Cylindrical = 1, + Spherical = 2, + Cubic = 3, + FrontProjection = 4, + UV = 5 + }; + + enum Axes { + AXIS_X = 0, + AXIS_Y = 1, + AXIS_Z = 2 + }; + + enum Wrap { + RESET = 0, + REPEAT = 1, + MIRROR = 2, + EDGE = 3 + }; + + Texture() : + mClipIdx(UINT_MAX), mStrength(1.0f), type(), mUVChannelIndex("unknown"), mRealUVIndex(UINT_MAX), enabled(true), blendType(Additive), bCanUse(true), mapMode(UV), majorAxis(AXIS_X), wrapAmountH(1.0f), wrapAmountW(1.0f), wrapModeWidth(REPEAT), wrapModeHeight(REPEAT), ordinal("\x00") {} + + //! File name of the texture + std::string mFileName; + + //! Clip index + unsigned int mClipIdx; + + //! Strength of the texture - blend factor + float mStrength; + + uint32_t type; // type of the texture + + //! Name of the corresponding UV channel + std::string mUVChannelIndex; + unsigned int mRealUVIndex; + + //! is the texture enabled? + bool enabled; + + //! blend type + BlendType blendType; + + //! are we able to use the texture? + bool bCanUse; + + //! mapping mode + MappingMode mapMode; + + //! major axis for planar, cylindrical, spherical projections + Axes majorAxis; + + //! wrap amount for cylindrical and spherical projections + float wrapAmountH, wrapAmountW; + + //! wrapping mode for the texture + Wrap wrapModeWidth, wrapModeHeight; + + //! ordinal string of the texture + std::string ordinal; +}; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a LWO file clip + */ +struct Clip { + enum Type { + STILL, + SEQ, + REF, + UNSUPPORTED + } type; + + Clip() : + type(UNSUPPORTED), clipRef(), idx(0), negate(false) {} + + //! path to the base texture - + std::string path; + + //! reference to another CLIP + unsigned int clipRef; + + //! index of the clip + unsigned int idx; + + //! Negate the clip? + bool negate; +}; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a LWO file shader + * + * Later + */ +struct Shader { + Shader() : + ordinal("\x00"), functionName("unknown"), enabled(true) {} + + std::string ordinal; + std::string functionName; + bool enabled; +}; + +typedef std::list<Texture> TextureList; +typedef std::list<Shader> ShaderList; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a LWO file surface (= material) + */ +struct Surface { + Surface() : + mColor(0.78431f, 0.78431f, 0.78431f), bDoubleSided(false), mDiffuseValue(1.f), mSpecularValue(0.f), mTransparency(0.f), mGlossiness(0.4f), mLuminosity(0.f), mColorHighlights(0.f), mMaximumSmoothAngle(0.f) // 0 == not specified, no smoothing + , + mVCMap(), + mVCMapType(AI_LWO_RGBA), + mIOR(1.f) // vakuum + , + mBumpIntensity(1.f), + mWireframe(false), + mAdditiveTransparency(0.f) {} + + //! Name of the surface + std::string mName; + + //! Color of the surface + aiColor3D mColor; + + //! true for two-sided materials + bool bDoubleSided; + + //! Various material parameters + float mDiffuseValue, mSpecularValue, mTransparency, mGlossiness, mLuminosity, mColorHighlights; + + //! Maximum angle between two adjacent triangles + //! that they can be smoothed - in degrees + float mMaximumSmoothAngle; + + //! Vertex color map to be used to color the surface + std::string mVCMap; + uint32_t mVCMapType; + + //! Names of the special shaders to be applied to the surface + ShaderList mShaders; + + //! Textures - the first entry in the list is evaluated first + TextureList mColorTextures, // color textures are added to both diffuse and specular texture stacks + mDiffuseTextures, + mSpecularTextures, + mOpacityTextures, + mBumpTextures, + mGlossinessTextures, + mReflectionTextures; + + //! Index of refraction + float mIOR; + + //! Bump intensity scaling + float mBumpIntensity; + + //! Wireframe flag + bool mWireframe; + + //! Intensity of additive blending + float mAdditiveTransparency; +}; + +// --------------------------------------------------------------------------- +#define AI_LWO_VALIDATE_CHUNK_LENGTH(length, name, size) \ + if (length < size) { \ + throw DeadlyImportError("LWO: " #name " chunk is too small"); \ + } + +// some typedefs ... to make life with loader monsters like this easier +typedef std::vector<aiVector3D> PointList; +typedef std::vector<LWO::Face> FaceList; +typedef std::vector<LWO::Surface> SurfaceList; +typedef std::vector<std::string> TagList; +typedef std::vector<unsigned int> TagMappingTable; +typedef std::vector<unsigned int> ReferrerList; +typedef std::vector<WeightChannel> WeightChannelList; +typedef std::vector<VColorChannel> VColorChannelList; +typedef std::vector<UVChannel> UVChannelList; +typedef std::vector<Clip> ClipList; +typedef std::vector<Envelope> EnvelopeList; +typedef std::vector<unsigned int> SortedRep; + +// --------------------------------------------------------------------------- +/** \brief Represents a layer in the file + */ +struct Layer { + Layer() : + mFaceIDXOfs(0), mPointIDXOfs(0), mParent(0x0), mIndex(0xffff), skip(false) {} + + /** Temporary point list from the file */ + PointList mTempPoints; + + /** Lists for every point the index of another point + that has been copied from *this* point or UINT_MAX if + no copy of the point has been made */ + ReferrerList mPointReferrers; + + /** Weight channel list from the file */ + WeightChannelList mWeightChannels; + + /** Subdivision weight channel list from the file */ + WeightChannelList mSWeightChannels; + + /** Vertex color list from the file */ + VColorChannelList mVColorChannels; + + /** UV channel list from the file */ + UVChannelList mUVChannels; + + /** Normal vector channel from the file */ + NormalChannel mNormals; + + /** Temporary face list from the file*/ + FaceList mFaces; + + /** Current face indexing offset from the beginning of the buffers*/ + unsigned int mFaceIDXOfs; + + /** Current point indexing offset from the beginning of the buffers*/ + unsigned int mPointIDXOfs; + + /** Parent index */ + uint16_t mParent; + + /** Index of the layer */ + uint16_t mIndex; + + /** Name of the layer */ + std::string mName; + + /** Pivot point of the layer */ + aiVector3D mPivot; + + /** Skip this layer? */ + bool skip; +}; + +typedef std::list<LWO::Layer> LayerList; + +} // namespace LWO +} // namespace Assimp + +#endif // !! AI_LWO_FILEDATA_INCLUDED diff --git a/libs/assimp/code/AssetLib/LWO/LWOLoader.cpp b/libs/assimp/code/AssetLib/LWO/LWOLoader.cpp new file mode 100644 index 0000000..7410fb6 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOLoader.cpp @@ -0,0 +1,1422 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 LWOLoader.cpp + * @brief Implementation of the LWO importer class + */ + +#ifndef ASSIMP_BUILD_NO_LWO_IMPORTER + +// internal headers +#include "AssetLib/LWO/LWOLoader.h" +#include "PostProcessing/ConvertToLHProcess.h" +#include "PostProcessing/ProcessHelper.h" + +#include <assimp/ByteSwapper.h> +#include <assimp/SGSpatialSort.h> +#include <assimp/StringComparison.h> +#include <assimp/importerdesc.h> +#include <assimp/IOSystem.hpp> + +#include <iomanip> +#include <map> +#include <memory> +#include <sstream> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "LightWave/Modo Object Importer", + "", + "", + "https://www.lightwave3d.com/lightwave_sdk/", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "lwo lxo" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +LWOImporter::LWOImporter() : + mIsLWO2(), + mIsLXOB(), + mLayers(), + mCurLayer(), + mTags(), + mMapping(), + mSurfaces(), + mFileBuffer(), + fileSize(), + mScene(nullptr), + configSpeedFlag(), + configLayerIndex(), + hasNamedLayer() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +LWOImporter::~LWOImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool LWOImporter::CanRead(const std::string &file, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const uint32_t tokens[] = { + AI_LWO_FOURCC_LWOB, + AI_LWO_FOURCC_LWO2, + AI_LWO_FOURCC_LXOB + }; + return CheckMagicToken(pIOHandler, file, tokens, AI_COUNT_OF(tokens), 8); +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties +void LWOImporter::SetupProperties(const Importer *pImp) { + configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED, 0) ? true : false); + configLayerIndex = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_LWO_ONE_LAYER_ONLY, UINT_MAX); + configLayerName = pImp->GetPropertyString(AI_CONFIG_IMPORT_LWO_ONE_LAYER_ONLY, ""); +} + +// ------------------------------------------------------------------------------------------------ +// Get list of file extensions +const aiImporterDesc *LWOImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void LWOImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler) { + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb")); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open LWO file ", pFile, "."); + } + + if ((this->fileSize = (unsigned int)file->FileSize()) < 12) { + throw DeadlyImportError("LWO: The file is too small to contain the IFF header"); + } + + // Allocate storage and copy the contents of the file to a memory buffer + std::vector<uint8_t> mBuffer(fileSize); + file->Read(&mBuffer[0], 1, fileSize); + mScene = pScene; + + // Determine the type of the file + uint32_t fileType; + const char *sz = IFF::ReadHeader(&mBuffer[0], fileType); + if (sz) { + throw DeadlyImportError(sz); + } + + mFileBuffer = &mBuffer[0] + 12; + fileSize -= 12; + + // Initialize some members with their default values + hasNamedLayer = false; + + // Create temporary storage on the stack but store pointers to it in the class + // instance. Therefore everything will be destructed properly if an exception + // is thrown and we needn't take care of that. + LayerList _mLayers; + SurfaceList _mSurfaces; + TagList _mTags; + TagMappingTable _mMapping; + + mLayers = &_mLayers; + mTags = &_mTags; + mMapping = &_mMapping; + mSurfaces = &_mSurfaces; + + // Allocate a default layer (layer indices are 1-based from now) + mLayers->push_back(Layer()); + mCurLayer = &mLayers->back(); + mCurLayer->mName = "<LWODefault>"; + mCurLayer->mIndex = (uint16_t) -1; + + // old lightwave file format (prior to v6) + if (AI_LWO_FOURCC_LWOB == fileType) { + ASSIMP_LOG_INFO("LWO file format: LWOB (<= LightWave 5.5)"); + + mIsLWO2 = false; + mIsLXOB = false; + LoadLWOBFile(); + } else if (AI_LWO_FOURCC_LWO2 == fileType) { + // New lightwave format + mIsLXOB = false; + ASSIMP_LOG_INFO("LWO file format: LWO2 (>= LightWave 6)"); + } else if (AI_LWO_FOURCC_LXOB == fileType) { + // MODO file format + mIsLXOB = true; + ASSIMP_LOG_INFO("LWO file format: LXOB (Modo)"); + } + else { + char szBuff[5]; + szBuff[0] = (char)(fileType >> 24u); + szBuff[1] = (char)(fileType >> 16u); + szBuff[2] = (char)(fileType >> 8u); + szBuff[3] = (char)(fileType); + szBuff[4] = '\0'; + throw DeadlyImportError("Unknown LWO sub format: ", szBuff); + } + + if (AI_LWO_FOURCC_LWOB != fileType) { + mIsLWO2 = true; + LoadLWO2File(); + + // The newer lightwave format allows the user to configure the + // loader that just one layer is used. If this is the case + // we need to check now whether the requested layer has been found. + if (UINT_MAX != configLayerIndex) { + unsigned int layerCount = 0; + for (std::list<LWO::Layer>::iterator itLayers = mLayers->begin(); itLayers != mLayers->end(); ++itLayers) + if (!itLayers->skip) + layerCount++; + if (layerCount != 2) + throw DeadlyImportError("LWO2: The requested layer was not found"); + } + + if (configLayerName.length() && !hasNamedLayer) { + throw DeadlyImportError("LWO2: Unable to find the requested layer: ", configLayerName); + } + } + + // now, as we have loaded all data, we can resolve cross-referenced tags and clips + ResolveTags(); + ResolveClips(); + + // now process all layers and build meshes and nodes + std::vector<aiMesh *> apcMeshes; + std::map<uint16_t, aiNode *> apcNodes; + + apcMeshes.reserve(mLayers->size() * std::min(((unsigned int)mSurfaces->size() / 2u), 1u)); + + unsigned int iDefaultSurface = UINT_MAX; // index of the default surface + for (LWO::Layer &layer : *mLayers) { + if (layer.skip) + continue; + + // I don't know whether there could be dummy layers, but it would be possible + const unsigned int meshStart = (unsigned int)apcMeshes.size(); + if (!layer.mFaces.empty() && !layer.mTempPoints.empty()) { + + // now sort all faces by the surfaces assigned to them + std::vector<SortedRep> pSorted(mSurfaces->size() + 1); + + unsigned int i = 0; + for (FaceList::iterator it = layer.mFaces.begin(), end = layer.mFaces.end(); it != end; ++it, ++i) { + // Check whether we support this face's type + if ((*it).type != AI_LWO_FACE && (*it).type != AI_LWO_PTCH && + (*it).type != AI_LWO_BONE && (*it).type != AI_LWO_SUBD) { + continue; + } + + unsigned int idx = (*it).surfaceIndex; + if (idx >= mTags->size()) { + ASSIMP_LOG_WARN("LWO: Invalid face surface index"); + idx = UINT_MAX; + } + if (UINT_MAX == idx || UINT_MAX == (idx = _mMapping[idx])) { + if (UINT_MAX == iDefaultSurface) { + iDefaultSurface = (unsigned int)mSurfaces->size(); + mSurfaces->push_back(LWO::Surface()); + LWO::Surface &surf = mSurfaces->back(); + surf.mColor.r = surf.mColor.g = surf.mColor.b = 0.6f; + surf.mName = "LWODefaultSurface"; + } + idx = iDefaultSurface; + } + pSorted[idx].push_back(i); + } + if (UINT_MAX == iDefaultSurface) { + pSorted.erase(pSorted.end() - 1); + } + for (unsigned int p = 0, j = 0; j < mSurfaces->size(); ++j) { + SortedRep &sorted = pSorted[j]; + if (sorted.empty()) + continue; + + // generate the mesh + aiMesh *mesh = new aiMesh(); + apcMeshes.push_back(mesh); + mesh->mNumFaces = (unsigned int)sorted.size(); + + // count the number of vertices + SortedRep::const_iterator it = sorted.begin(), end = sorted.end(); + for (; it != end; ++it) { + mesh->mNumVertices += layer.mFaces[*it].mNumIndices; + } + + aiVector3D *nrm = nullptr, *pv = mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + aiFace *pf = mesh->mFaces = new aiFace[mesh->mNumFaces]; + mesh->mMaterialIndex = j; + + // find out which vertex color channels and which texture coordinate + // channels are really required by the material attached to this mesh + unsigned int vUVChannelIndices[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + unsigned int vVColorIndices[AI_MAX_NUMBER_OF_COLOR_SETS]; + +#ifdef ASSIMP_BUILD_DEBUG + for (unsigned int mui = 0; mui < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++mui) { + vUVChannelIndices[mui] = UINT_MAX; + } + for (unsigned int mui = 0; mui < AI_MAX_NUMBER_OF_COLOR_SETS; ++mui) { + vVColorIndices[mui] = UINT_MAX; + } +#endif + + FindUVChannels(_mSurfaces[j], sorted, layer, vUVChannelIndices); + FindVCChannels(_mSurfaces[j], sorted, layer, vVColorIndices); + + // allocate storage for UV and CV channels + aiVector3D *pvUV[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + for (unsigned int mui = 0; mui < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++mui) { + if (UINT_MAX == vUVChannelIndices[mui]) { + break; + } + + pvUV[mui] = mesh->mTextureCoords[mui] = new aiVector3D[mesh->mNumVertices]; + + // LightWave doesn't support more than 2 UV components (?) + mesh->mNumUVComponents[0] = 2; + } + + if (layer.mNormals.name.length()) { + nrm = mesh->mNormals = new aiVector3D[mesh->mNumVertices]; + } + + aiColor4D *pvVC[AI_MAX_NUMBER_OF_COLOR_SETS]; + for (unsigned int mui = 0; mui < AI_MAX_NUMBER_OF_COLOR_SETS; ++mui) { + if (UINT_MAX == vVColorIndices[mui]) { + break; + } + pvVC[mui] = mesh->mColors[mui] = new aiColor4D[mesh->mNumVertices]; + } + + // we would not need this extra array, but the code is much cleaner if we use it + std::vector<unsigned int> &smoothingGroups = layer.mPointReferrers; + smoothingGroups.erase(smoothingGroups.begin(), smoothingGroups.end()); + smoothingGroups.resize(mesh->mNumFaces, 0); + + // now convert all faces + unsigned int vert = 0; + std::vector<unsigned int>::iterator outIt = smoothingGroups.begin(); + for (it = sorted.begin(); it != end; ++it, ++outIt) { + const LWO::Face &face = layer.mFaces[*it]; + *outIt = face.smoothGroup; + + // copy all vertices + for (unsigned int q = 0; q < face.mNumIndices; ++q, ++vert) { + unsigned int idx = face.mIndices[q]; + *pv++ = layer.mTempPoints[idx] /*- layer.mPivot*/; + + // process UV coordinates + for (unsigned int w = 0; w < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++w) { + if (UINT_MAX == vUVChannelIndices[w]) { + break; + } + aiVector3D *&pp = pvUV[w]; + const aiVector2D &src = ((aiVector2D *)&layer.mUVChannels[vUVChannelIndices[w]].rawData[0])[idx]; + pp->x = src.x; + pp->y = src.y; + pp++; + } + + // process normals (MODO extension) + if (nrm) { + *nrm = ((aiVector3D *)&layer.mNormals.rawData[0])[idx]; + nrm->z *= -1.f; + ++nrm; + } + + // process vertex colors + for (unsigned int w = 0; w < AI_MAX_NUMBER_OF_COLOR_SETS; ++w) { + if (UINT_MAX == vVColorIndices[w]) { + break; + } + *pvVC[w] = ((aiColor4D *)&layer.mVColorChannels[vVColorIndices[w]].rawData[0])[idx]; + + // If a RGB color map is explicitly requested delete the + // alpha channel - it could theoretically be != 1. + if (_mSurfaces[j].mVCMapType == AI_LWO_RGB) + pvVC[w]->a = 1.f; + + pvVC[w]++; + } + +#if 0 + // process vertex weights. We can't properly reconstruct the whole skeleton for now, + // but we can create dummy bones for all weight channels which we have. + for (unsigned int w = 0; w < layer.mWeightChannels.size();++w) + { + } +#endif + + face.mIndices[q] = vert; + } + pf->mIndices = face.mIndices; + pf->mNumIndices = face.mNumIndices; + unsigned int **facePtr = (unsigned int **)&face.mIndices; + *facePtr = nullptr; // HACK: make sure it won't be deleted + pf++; + } + + if (!mesh->mNormals) { + // Compute normal vectors for the mesh - we can't use our GenSmoothNormal- + // Step here since it wouldn't handle smoothing groups correctly for LWO. + // So we use a separate implementation. + ComputeNormals(mesh, smoothingGroups, _mSurfaces[j]); + } else { + ASSIMP_LOG_VERBOSE_DEBUG("LWO2: No need to compute normals, they're already there"); + } + ++p; + } + } + + // Generate nodes to render the mesh. Store the source layer in the mParent member of the nodes + unsigned int num = static_cast<unsigned int>(apcMeshes.size() - meshStart); + if (layer.mName != "<LWODefault>" || num > 0) { + aiNode *pcNode = new aiNode(); + pcNode->mName.Set(layer.mName); + pcNode->mParent = (aiNode *)&layer; + pcNode->mNumMeshes = num; + + if (pcNode->mNumMeshes) { + pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes]; + for (unsigned int p = 0; p < pcNode->mNumMeshes; ++p) + pcNode->mMeshes[p] = p + meshStart; + } + apcNodes[layer.mIndex] = pcNode; + } + } + + if (apcNodes.empty() || apcMeshes.empty()) + throw DeadlyImportError("LWO: No meshes loaded"); + + // The RemoveRedundantMaterials step will clean this up later + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials = (unsigned int)mSurfaces->size()]; + for (unsigned int mat = 0; mat < pScene->mNumMaterials; ++mat) { + aiMaterial *pcMat = new aiMaterial(); + pScene->mMaterials[mat] = pcMat; + ConvertMaterial((*mSurfaces)[mat], pcMat); + } + + // copy the meshes to the output structure + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes = (unsigned int)apcMeshes.size()]; + ::memcpy(pScene->mMeshes, &apcMeshes[0], pScene->mNumMeshes * sizeof(void *)); + + // generate the final node graph + GenerateNodeGraph(apcNodes); +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::ComputeNormals(aiMesh *mesh, const std::vector<unsigned int> &smoothingGroups, + const LWO::Surface &surface) { + // Allocate output storage + mesh->mNormals = new aiVector3D[mesh->mNumVertices]; + + // First generate per-face normals + aiVector3D *out; + std::vector<aiVector3D> faceNormals; + + // ... in some cases that's already enough + if (!surface.mMaximumSmoothAngle) + out = mesh->mNormals; + else { + faceNormals.resize(mesh->mNumVertices); + out = &faceNormals[0]; + } + + aiFace *begin = mesh->mFaces, *const end = mesh->mFaces + mesh->mNumFaces; + for (; begin != end; ++begin) { + aiFace &face = *begin; + + if (face.mNumIndices < 3) { + continue; + } + + // LWO doc: "the normal is defined as the cross product of the first and last edges" + aiVector3D *pV1 = mesh->mVertices + face.mIndices[0]; + aiVector3D *pV2 = mesh->mVertices + face.mIndices[1]; + aiVector3D *pV3 = mesh->mVertices + face.mIndices[face.mNumIndices - 1]; + + aiVector3D vNor = ((*pV2 - *pV1) ^ (*pV3 - *pV1)).Normalize(); + for (unsigned int i = 0; i < face.mNumIndices; ++i) + out[face.mIndices[i]] = vNor; + } + if (!surface.mMaximumSmoothAngle) return; + const float posEpsilon = ComputePositionEpsilon(mesh); + + // Now generate the spatial sort tree + SGSpatialSort sSort; + std::vector<unsigned int>::const_iterator it = smoothingGroups.begin(); + for (begin = mesh->mFaces; begin != end; ++begin, ++it) { + aiFace &face = *begin; + for (unsigned int i = 0; i < face.mNumIndices; ++i) { + unsigned int tt = face.mIndices[i]; + sSort.Add(mesh->mVertices[tt], tt, *it); + } + } + // Sort everything - this takes O(nlogn) time + sSort.Prepare(); + std::vector<unsigned int> poResult; + poResult.reserve(20); + + // Generate vertex normals. We have O(logn) for the binary lookup, which we need + // for n elements, thus the EXPECTED complexity is O(nlogn) + if (surface.mMaximumSmoothAngle < 3.f && !configSpeedFlag) { + const float fLimit = std::cos(surface.mMaximumSmoothAngle); + + for (begin = mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it) { + const aiFace &face = *begin; + unsigned int *beginIdx = face.mIndices, *const endIdx = face.mIndices + face.mNumIndices; + for (; beginIdx != endIdx; ++beginIdx) { + unsigned int idx = *beginIdx; + sSort.FindPositions(mesh->mVertices[idx], *it, posEpsilon, poResult, true); + + aiVector3D vNormals; + for (std::vector<unsigned int>::const_iterator a = poResult.begin(); a != poResult.end(); ++a) { + const aiVector3D &v = faceNormals[*a]; + if (v * faceNormals[idx] < fLimit) + continue; + vNormals += v; + } + mesh->mNormals[idx] = vNormals.Normalize(); + } + } + } + // faster code path in case there is no smooth angle + else { + std::vector<bool> vertexDone(mesh->mNumVertices, false); + for (begin = mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it) { + const aiFace &face = *begin; + unsigned int *beginIdx = face.mIndices, *const endIdx = face.mIndices + face.mNumIndices; + for (; beginIdx != endIdx; ++beginIdx) { + unsigned int idx = *beginIdx; + if (vertexDone[idx]) + continue; + sSort.FindPositions(mesh->mVertices[idx], *it, posEpsilon, poResult, true); + + aiVector3D vNormals; + for (std::vector<unsigned int>::const_iterator a = poResult.begin(); a != poResult.end(); ++a) { + const aiVector3D &v = faceNormals[*a]; + vNormals += v; + } + vNormals.Normalize(); + for (std::vector<unsigned int>::const_iterator a = poResult.begin(); a != poResult.end(); ++a) { + mesh->mNormals[*a] = vNormals; + vertexDone[*a] = true; + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::GenerateNodeGraph(std::map<uint16_t, aiNode *> &apcNodes) { + // now generate the final nodegraph - generate a root node and attach children + aiNode *root = mScene->mRootNode = new aiNode(); + root->mName.Set("<LWORoot>"); + + //Set parent of all children, inserting pivots + std::map<uint16_t, aiNode *> mapPivot; + for (auto itapcNodes = apcNodes.begin(); itapcNodes != apcNodes.end(); ++itapcNodes) { + + //Get the parent index + LWO::Layer *nodeLayer = (LWO::Layer *)(itapcNodes->second->mParent); + uint16_t parentIndex = nodeLayer->mParent; + + //Create pivot node, store it into the pivot map, and set the parent as the pivot + aiNode *pivotNode = new aiNode(); + pivotNode->mName.Set("Pivot-" + std::string(itapcNodes->second->mName.data)); + itapcNodes->second->mParent = pivotNode; + + //Look for the parent node to attach the pivot to + if (apcNodes.find(parentIndex) != apcNodes.end()) { + pivotNode->mParent = apcNodes[parentIndex]; + } else { + //If not, attach to the root node + pivotNode->mParent = root; + } + + //Set the node and the pivot node transformation + itapcNodes->second->mTransformation.a4 = -nodeLayer->mPivot.x; + itapcNodes->second->mTransformation.b4 = -nodeLayer->mPivot.y; + itapcNodes->second->mTransformation.c4 = -nodeLayer->mPivot.z; + pivotNode->mTransformation.a4 = nodeLayer->mPivot.x; + pivotNode->mTransformation.b4 = nodeLayer->mPivot.y; + pivotNode->mTransformation.c4 = nodeLayer->mPivot.z; + mapPivot[-(itapcNodes->first + 2)] = pivotNode; + } + + //Merge pivot map into node map + for (auto itMapPivot = mapPivot.begin(); itMapPivot != mapPivot.end(); ++itMapPivot) { + apcNodes[itMapPivot->first] = itMapPivot->second; + } + + //Set children of all parents + apcNodes[(uint16_t)-1] = root; + for (auto itMapParentNodes = apcNodes.begin(); itMapParentNodes != apcNodes.end(); ++itMapParentNodes) { + for (auto itMapChildNodes = apcNodes.begin(); itMapChildNodes != apcNodes.end(); ++itMapChildNodes) { + if ((itMapParentNodes->first != itMapChildNodes->first) && (itMapParentNodes->second == itMapChildNodes->second->mParent)) { + ++(itMapParentNodes->second->mNumChildren); + } + } + if (itMapParentNodes->second->mNumChildren) { + itMapParentNodes->second->mChildren = new aiNode *[itMapParentNodes->second->mNumChildren]; + uint16_t p = 0; + for (auto itMapChildNodes = apcNodes.begin(); itMapChildNodes != apcNodes.end(); ++itMapChildNodes) { + if ((itMapParentNodes->first != itMapChildNodes->first) && (itMapParentNodes->second == itMapChildNodes->second->mParent)) { + itMapParentNodes->second->mChildren[p++] = itMapChildNodes->second; + } + } + } + } + + if (!mScene->mRootNode->mNumChildren) + throw DeadlyImportError("LWO: Unable to build a valid node graph"); + + // Remove a single root node with no meshes assigned to it ... + if (1 == mScene->mRootNode->mNumChildren) { + aiNode *pc = mScene->mRootNode->mChildren[0]; + pc->mParent = mScene->mRootNode->mChildren[0] = nullptr; + delete mScene->mRootNode; + mScene->mRootNode = pc; + } + + // convert the whole stuff to RH with CCW winding + MakeLeftHandedProcess maker; + maker.Execute(mScene); + + FlipWindingOrderProcess flipper; + flipper.Execute(mScene); +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::ResolveTags() { + // --- this function is used for both LWO2 and LWOB + mMapping->resize(mTags->size(), UINT_MAX); + for (unsigned int a = 0; a < mTags->size(); ++a) { + + const std::string &c = (*mTags)[a]; + for (unsigned int i = 0; i < mSurfaces->size(); ++i) { + + const std::string &d = (*mSurfaces)[i].mName; + if (!ASSIMP_stricmp(c, d)) { + + (*mMapping)[a] = i; + break; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::ResolveClips() { + for (unsigned int i = 0; i < mClips.size(); ++i) { + + Clip &clip = mClips[i]; + if (Clip::REF == clip.type) { + + if (clip.clipRef >= mClips.size()) { + ASSIMP_LOG_ERROR("LWO2: Clip referrer index is out of range"); + clip.clipRef = 0; + } + + Clip &dest = mClips[clip.clipRef]; + if (Clip::REF == dest.type) { + ASSIMP_LOG_ERROR("LWO2: Clip references another clip reference"); + clip.type = Clip::UNSUPPORTED; + } + + else { + clip.path = dest.path; + clip.type = dest.type; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::AdjustTexturePath(std::string &out) { + // --- this function is used for both LWO2 and LWOB + if (!mIsLWO2 && ::strstr(out.c_str(), "(sequence)")) { + + // remove the (sequence) and append 000 + ASSIMP_LOG_INFO("LWOB: Sequence of animated texture found. It will be ignored"); + out = out.substr(0, out.length() - 10) + "000"; + } + + // format: drive:path/file - we just need to insert a slash after the drive + std::string::size_type n = out.find_first_of(':'); + if (std::string::npos != n) { + out.insert(n + 1, "/"); + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWOTags(unsigned int size) { + // --- this function is used for both LWO2 and LWOB + + const char *szCur = (const char *)mFileBuffer, *szLast = szCur; + const char *const szEnd = szLast + size; + while (szCur < szEnd) { + if (!(*szCur)) { + const size_t len = (size_t)(szCur - szLast); + // FIX: skip empty-sized tags + if (len) + mTags->push_back(std::string(szLast, len)); + szCur += (len & 0x1 ? 1 : 2); + szLast = szCur; + } + szCur++; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWOPoints(unsigned int length) { + // --- this function is used for both LWO2 and LWOB but for + // LWO2 we need to allocate 25% more storage - it could be we'll + // need to duplicate some points later. + const size_t vertexLen = 12; + if ((length % vertexLen) != 0) { + throw DeadlyImportError("LWO2: Points chunk length is not multiple of vertexLen (12)"); + } + unsigned int regularSize = (unsigned int)mCurLayer->mTempPoints.size() + length / 12; + if (mIsLWO2) { + mCurLayer->mTempPoints.reserve(regularSize + (regularSize >> 2u)); + mCurLayer->mTempPoints.resize(regularSize); + + // initialize all point referrers with the default values + mCurLayer->mPointReferrers.reserve(regularSize + (regularSize >> 2u)); + mCurLayer->mPointReferrers.resize(regularSize, UINT_MAX); + } else + mCurLayer->mTempPoints.resize(regularSize); + + // perform endianness conversions +#ifndef AI_BUILD_BIG_ENDIAN + for (unsigned int i = 0; i<length >> 2; ++i) + ByteSwap::Swap4(mFileBuffer + (i << 2)); +#endif + ::memcpy(&mCurLayer->mTempPoints[0], mFileBuffer, length); +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2Polygons(unsigned int length) { + LE_NCONST uint16_t *const end = (LE_NCONST uint16_t *)(mFileBuffer + length); + const uint32_t type = GetU4(); + + // Determine the type of the polygons + switch (type) { + // read unsupported stuff too (although we won't process it) + case AI_LWO_MBAL: + ASSIMP_LOG_WARN("LWO2: Encountered unsupported primitive chunk (METABALL)"); + break; + case AI_LWO_CURV: + ASSIMP_LOG_WARN("LWO2: Encountered unsupported primitive chunk (SPLINE)"); + ; + break; + + // These are ok with no restrictions + case AI_LWO_PTCH: + case AI_LWO_FACE: + case AI_LWO_BONE: + case AI_LWO_SUBD: + break; + default: + + // hm!? wtf is this? ok ... + ASSIMP_LOG_ERROR("LWO2: Ignoring unknown polygon type."); + break; + } + + // first find out how many faces and vertices we'll finally need + uint16_t *cursor = (uint16_t *)mFileBuffer; + + unsigned int iNumFaces = 0, iNumVertices = 0; + CountVertsAndFacesLWO2(iNumVertices, iNumFaces, cursor, end); + + // allocate the output array and copy face indices + if (iNumFaces) { + cursor = (uint16_t *)mFileBuffer; + + mCurLayer->mFaces.resize(iNumFaces, LWO::Face(type)); + FaceList::iterator it = mCurLayer->mFaces.begin(); + CopyFaceIndicesLWO2(it, cursor, end); + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::CountVertsAndFacesLWO2(unsigned int &verts, unsigned int &faces, + uint16_t *&cursor, const uint16_t *const end, unsigned int max) { + while (cursor < end && max--) { + uint16_t numIndices; + ::memcpy(&numIndices, cursor++, 2); + AI_LSWAP2(numIndices); + numIndices &= 0x03FF; + + verts += numIndices; + ++faces; + + for (uint16_t i = 0; i < numIndices; i++) { + ReadVSizedIntLWO2((uint8_t *&)cursor); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::CopyFaceIndicesLWO2(FaceList::iterator &it, + uint16_t *&cursor, + const uint16_t *const end) { + while (cursor < end) { + LWO::Face &face = *it++; + uint16_t numIndices; + ::memcpy(&numIndices, cursor++, 2); + AI_LSWAP2(numIndices); + face.mNumIndices = numIndices & 0x03FF; + + if (face.mNumIndices) /* byte swapping has already been done */ + { + face.mIndices = new unsigned int[face.mNumIndices]; + for (unsigned int i = 0; i < face.mNumIndices; i++) { + face.mIndices[i] = ReadVSizedIntLWO2((uint8_t *&)cursor) + mCurLayer->mPointIDXOfs; + if (face.mIndices[i] > mCurLayer->mTempPoints.size()) { + ASSIMP_LOG_WARN("LWO2: Failure evaluating face record, index is out of range"); + face.mIndices[i] = (unsigned int)mCurLayer->mTempPoints.size() - 1; + } + } + } else + throw DeadlyImportError("LWO2: Encountered invalid face record with zero indices"); + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2PolygonTags(unsigned int length) { + LE_NCONST uint8_t *const end = mFileBuffer + length; + + AI_LWO_VALIDATE_CHUNK_LENGTH(length, PTAG, 4); + uint32_t type = GetU4(); + + if (type != AI_LWO_SURF && type != AI_LWO_SMGP) + return; + + while (mFileBuffer < end) { + unsigned int i = ReadVSizedIntLWO2(mFileBuffer) + mCurLayer->mFaceIDXOfs; + unsigned int j = GetU2(); + + if (i >= mCurLayer->mFaces.size()) { + ASSIMP_LOG_WARN("LWO2: face index in PTAG is out of range"); + continue; + } + + switch (type) { + + case AI_LWO_SURF: + mCurLayer->mFaces[i].surfaceIndex = j; + break; + case AI_LWO_SMGP: /* is that really used? */ + mCurLayer->mFaces[i].smoothGroup = j; + break; + }; + } +} + +// ------------------------------------------------------------------------------------------------ +template <class T> +VMapEntry *FindEntry(std::vector<T> &list, const std::string &name, bool perPoly) { + for (auto &elem : list) { + if (elem.name == name) { + if (!perPoly) { + ASSIMP_LOG_WARN("LWO2: Found two VMAP sections with equal names"); + } + return &elem; + } + } + list.push_back(T()); + VMapEntry *p = &list.back(); + p->name = name; + return p; +} + +// ------------------------------------------------------------------------------------------------ +template <class T> +inline void CreateNewEntry(T &chan, unsigned int srcIdx) { + if (!chan.name.length()) + return; + + chan.abAssigned[srcIdx] = true; + chan.abAssigned.resize(chan.abAssigned.size() + 1, false); + + for (unsigned int a = 0; a < chan.dims; ++a) + chan.rawData.push_back(chan.rawData[srcIdx * chan.dims + a]); +} + +// ------------------------------------------------------------------------------------------------ +template <class T> +inline void CreateNewEntry(std::vector<T> &list, unsigned int srcIdx) { + for (auto &elem : list) { + CreateNewEntry(elem, srcIdx); + } +} + +// ------------------------------------------------------------------------------------------------ +inline void LWOImporter::DoRecursiveVMAPAssignment(VMapEntry *base, unsigned int numRead, + unsigned int idx, float *data) { + ai_assert(nullptr != data); + LWO::ReferrerList &refList = mCurLayer->mPointReferrers; + unsigned int i; + + if (idx >= base->abAssigned.size()) { + throw DeadlyImportError("Bad index"); + } + base->abAssigned[idx] = true; + for (i = 0; i < numRead; ++i) { + base->rawData[idx * base->dims + i] = data[i]; + } + + if (UINT_MAX != (i = refList[idx])) { + DoRecursiveVMAPAssignment(base, numRead, i, data); + } +} + +// ------------------------------------------------------------------------------------------------ +inline void AddToSingleLinkedList(ReferrerList &refList, unsigned int srcIdx, unsigned int destIdx) { + if (UINT_MAX == refList[srcIdx]) { + refList[srcIdx] = destIdx; + return; + } + AddToSingleLinkedList(refList, refList[srcIdx], destIdx); +} + +// ------------------------------------------------------------------------------------------------ +// Load LWO2 vertex map +void LWOImporter::LoadLWO2VertexMap(unsigned int length, bool perPoly) { + LE_NCONST uint8_t *const end = mFileBuffer + length; + + AI_LWO_VALIDATE_CHUNK_LENGTH(length, VMAP, 6); + unsigned int type = GetU4(); + unsigned int dims = GetU2(); + + VMapEntry *base; + + // read the name of the vertex map + std::string name; + GetS0(name, length); + + switch (type) { + case AI_LWO_TXUV: + if (dims != 2) { + ASSIMP_LOG_WARN("LWO2: Skipping UV channel \'", name, "\' with !2 components"); + return; + } + base = FindEntry(mCurLayer->mUVChannels, name, perPoly); + break; + case AI_LWO_WGHT: + case AI_LWO_MNVW: + if (dims != 1) { + ASSIMP_LOG_WARN("LWO2: Skipping Weight Channel \'", name, "\' with !1 components"); + return; + } + base = FindEntry((type == AI_LWO_WGHT ? mCurLayer->mWeightChannels : mCurLayer->mSWeightChannels), name, perPoly); + break; + case AI_LWO_RGB: + case AI_LWO_RGBA: + if (dims != 3 && dims != 4) { + ASSIMP_LOG_WARN("LWO2: Skipping Color Map \'", name, "\' with a dimension > 4 or < 3"); + return; + } + base = FindEntry(mCurLayer->mVColorChannels, name, perPoly); + break; + + case AI_LWO_MODO_NORM: + /* This is a non-standard extension chunk used by Luxology's MODO. + * It stores per-vertex normals. This VMAP exists just once, has + * 3 dimensions and is btw extremely beautiful. + */ + if (name != "vert_normals" || dims != 3 || mCurLayer->mNormals.name.length()) + return; + + ASSIMP_LOG_INFO("Processing non-standard extension: MODO VMAP.NORM.vert_normals"); + + mCurLayer->mNormals.name = name; + base = &mCurLayer->mNormals; + break; + + case AI_LWO_PICK: /* these VMAPs are just silently dropped */ + case AI_LWO_MORF: + case AI_LWO_SPOT: + return; + + default: + if (name == "APS.Level") { + // XXX handle this (seems to be subdivision-related). + } + ASSIMP_LOG_WARN("LWO2: Skipping unknown VMAP/VMAD channel \'", name, "\'"); + return; + }; + base->Allocate((unsigned int)mCurLayer->mTempPoints.size()); + + // now read all entries in the map + type = std::min(dims, base->dims); + const unsigned int diff = (dims - type) << 2u; + + LWO::FaceList &list = mCurLayer->mFaces; + LWO::PointList &pointList = mCurLayer->mTempPoints; + LWO::ReferrerList &refList = mCurLayer->mPointReferrers; + + const unsigned int numPoints = (unsigned int)pointList.size(); + const unsigned int numFaces = (unsigned int)list.size(); + + while (mFileBuffer < end) { + + unsigned int idx = ReadVSizedIntLWO2(mFileBuffer) + mCurLayer->mPointIDXOfs; + if (idx >= numPoints) { + ASSIMP_LOG_WARN("LWO2: Failure evaluating VMAP/VMAD entry \'", name, "\', vertex index is out of range"); + mFileBuffer += base->dims << 2u; + continue; + } + if (perPoly) { + unsigned int polyIdx = ReadVSizedIntLWO2(mFileBuffer) + mCurLayer->mFaceIDXOfs; + if (base->abAssigned[idx]) { + // we have already a VMAP entry for this vertex - thus + // we need to duplicate the corresponding polygon. + if (polyIdx >= numFaces) { + ASSIMP_LOG_WARN("LWO2: Failure evaluating VMAD entry \'", name, "\', polygon index is out of range"); + mFileBuffer += base->dims << 2u; + continue; + } + + LWO::Face &src = list[polyIdx]; + + // generate a new unique vertex for the corresponding index - but only + // if we can find the index in the face + bool had = false; + for (unsigned int i = 0; i < src.mNumIndices; ++i) { + + unsigned int srcIdx = src.mIndices[i], tmp = idx; + do { + if (tmp == srcIdx) + break; + } while ((tmp = refList[tmp]) != UINT_MAX); + if (tmp == UINT_MAX) { + continue; + } + + had = true; + refList.resize(refList.size() + 1, UINT_MAX); + + idx = (unsigned int)pointList.size(); + src.mIndices[i] = (unsigned int)pointList.size(); + + // store the index of the new vertex in the old vertex + // so we get a single linked list we can traverse in + // only one direction + AddToSingleLinkedList(refList, srcIdx, src.mIndices[i]); + pointList.push_back(pointList[srcIdx]); + + CreateNewEntry(mCurLayer->mVColorChannels, srcIdx); + CreateNewEntry(mCurLayer->mUVChannels, srcIdx); + CreateNewEntry(mCurLayer->mWeightChannels, srcIdx); + CreateNewEntry(mCurLayer->mSWeightChannels, srcIdx); + CreateNewEntry(mCurLayer->mNormals, srcIdx); + } + if (!had) { + ASSIMP_LOG_WARN("LWO2: Failure evaluating VMAD entry \'", name, "\', vertex index wasn't found in that polygon"); + ai_assert(had); + } + } + } + + std::unique_ptr<float[]> temp(new float[type]); + for (unsigned int l = 0; l < type; ++l) + temp[l] = GetF4(); + + DoRecursiveVMAPAssignment(base, type, idx, temp.get()); + mFileBuffer += diff; + } +} + +// ------------------------------------------------------------------------------------------------ +// Load LWO2 clip +void LWOImporter::LoadLWO2Clip(unsigned int length) { + AI_LWO_VALIDATE_CHUNK_LENGTH(length, CLIP, 10); + + mClips.push_back(LWO::Clip()); + LWO::Clip &clip = mClips.back(); + + // first - get the index of the clip + clip.idx = GetU4(); + + IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + switch (head.type) { + case AI_LWO_STIL: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, STIL, 1); + + // "Normal" texture + GetS0(clip.path, head.length); + clip.type = Clip::STILL; + break; + + case AI_LWO_ISEQ: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, ISEQ, 16); + // Image sequence. We'll later take the first. + { + uint8_t digits = GetU1(); + mFileBuffer++; + int16_t offset = GetU2(); + mFileBuffer += 4; + int16_t start = GetU2(); + mFileBuffer += 4; + + std::string s; + std::ostringstream ss; + GetS0(s, head.length); + + head.length -= (uint16_t)s.length() + 1; + ss << s; + ss << std::setw(digits) << offset + start; + GetS0(s, head.length); + ss << s; + clip.path = ss.str(); + clip.type = Clip::SEQ; + } + break; + + case AI_LWO_STCC: + ASSIMP_LOG_WARN("LWO2: Color shifted images are not supported"); + break; + + case AI_LWO_ANIM: + ASSIMP_LOG_WARN("LWO2: Animated textures are not supported"); + break; + + case AI_LWO_XREF: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, XREF, 4); + + // Just a cross-reference to another CLIp + clip.type = Clip::REF; + clip.clipRef = GetU4(); + break; + + case AI_LWO_NEGA: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, NEGA, 2); + clip.negate = (0 != GetU2()); + break; + + default: + ASSIMP_LOG_WARN("LWO2: Encountered unknown CLIP sub-chunk"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Load envelope description +void LWOImporter::LoadLWO2Envelope(unsigned int length) { + LE_NCONST uint8_t *const end = mFileBuffer + length; + AI_LWO_VALIDATE_CHUNK_LENGTH(length, ENVL, 4); + + mEnvelopes.push_back(LWO::Envelope()); + LWO::Envelope &envelope = mEnvelopes.back(); + + // Get the index of the envelope + envelope.index = ReadVSizedIntLWO2(mFileBuffer); + + // It looks like there might be an extra U4 right after the index, + // at least in modo (LXOB) files: we'll ignore it if it's zero, + // otherwise it represents the start of a subchunk, so we backtrack. + if (mIsLXOB) { + uint32_t extra = GetU4(); + if (extra) { + mFileBuffer -= 4; + } + } + + // ... and read all subchunks + while (true) { + if (mFileBuffer + 6 >= end) break; + LE_NCONST IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) + throw DeadlyImportError("LWO2: Invalid envelope chunk length"); + + uint8_t *const next = mFileBuffer + head.length; + switch (head.type) { + // Type & representation of the envelope + case AI_LWO_TYPE: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, TYPE, 2); + mFileBuffer++; // skip user format + + // Determine type of envelope + envelope.type = (LWO::EnvelopeType)*mFileBuffer; + ++mFileBuffer; + break; + + // precondition + case AI_LWO_PRE: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, PRE, 2); + envelope.pre = (LWO::PrePostBehaviour)GetU2(); + break; + + // postcondition + case AI_LWO_POST: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, POST, 2); + envelope.post = (LWO::PrePostBehaviour)GetU2(); + break; + + // keyframe + case AI_LWO_KEY: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, KEY, 8); + + envelope.keys.push_back(LWO::Key()); + LWO::Key &key = envelope.keys.back(); + + key.time = GetF4(); + key.value = GetF4(); + break; + } + + // interval interpolation + case AI_LWO_SPAN: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SPAN, 4); + if (envelope.keys.size() < 2) + ASSIMP_LOG_WARN("LWO2: Unexpected SPAN chunk"); + else { + LWO::Key &key = envelope.keys.back(); + switch (GetU4()) { + case AI_LWO_STEP: + key.inter = LWO::IT_STEP; + break; + case AI_LWO_LINE: + key.inter = LWO::IT_LINE; + break; + case AI_LWO_TCB: + key.inter = LWO::IT_TCB; + break; + case AI_LWO_HERM: + key.inter = LWO::IT_HERM; + break; + case AI_LWO_BEZI: + key.inter = LWO::IT_BEZI; + break; + case AI_LWO_BEZ2: + key.inter = LWO::IT_BEZ2; + break; + default: + ASSIMP_LOG_WARN("LWO2: Unknown interval interpolation mode"); + }; + + // todo ... read params + } + break; + } + + default: + ASSIMP_LOG_WARN("LWO2: Encountered unknown ENVL subchunk"); + break; + } + // regardless how much we did actually read, go to the next chunk + mFileBuffer = next; + } +} + +// ------------------------------------------------------------------------------------------------ +// Load file - master function +void LWOImporter::LoadLWO2File() { + bool skip = false; + + LE_NCONST uint8_t *const end = mFileBuffer + fileSize; + unsigned int iUnnamed = 0; + while (true) { + if (mFileBuffer + sizeof(IFF::ChunkHeader) > end) break; + const IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) { + throw DeadlyImportError("LWO2: Chunk length points behind the file"); + break; + } + uint8_t *const next = mFileBuffer + head.length; + + if (!head.length) { + mFileBuffer = next; + continue; + } + + switch (head.type) { + // new layer + case AI_LWO_LAYR: { + // add a new layer to the list .... + mLayers->push_back(LWO::Layer()); + LWO::Layer &layer = mLayers->back(); + mCurLayer = &layer; + + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, LAYR, 16); + + // layer index. + layer.mIndex = GetU2(); + + // Continue loading this layer or ignore it? Check the layer index property + if (UINT_MAX != configLayerIndex && (configLayerIndex - 1) != layer.mIndex) { + skip = true; + } else + skip = false; + + // pivot point + mFileBuffer += 2; /* unknown */ + mCurLayer->mPivot.x = GetF4(); + mCurLayer->mPivot.y = GetF4(); + mCurLayer->mPivot.z = GetF4(); + GetS0(layer.mName, head.length - 16); + + // if the name is empty, generate a default name + if (layer.mName.empty()) { + char buffer[128]; // should be sufficiently large + ::ai_snprintf(buffer, 128, "Layer_%i", iUnnamed++); + layer.mName = buffer; + } + + // load this layer or ignore it? Check the layer name property + if (configLayerName.length() && configLayerName != layer.mName) { + skip = true; + } else + hasNamedLayer = true; + + // optional: parent of this layer + if (mFileBuffer + 2 <= next) + layer.mParent = GetU2(); + else + layer.mParent = (uint16_t) -1; + + // Set layer skip parameter + layer.skip = skip; + + break; + } + + // vertex list + case AI_LWO_PNTS: { + if (skip) + break; + + unsigned int old = (unsigned int)mCurLayer->mTempPoints.size(); + LoadLWOPoints(head.length); + mCurLayer->mPointIDXOfs = old; + break; + } + // vertex tags + case AI_LWO_VMAD: + if (mCurLayer->mFaces.empty()) { + ASSIMP_LOG_WARN("LWO2: Unexpected VMAD chunk"); + break; + } + // --- intentionally no break here + case AI_LWO_VMAP: { + if (skip) + break; + + if (mCurLayer->mTempPoints.empty()) + ASSIMP_LOG_WARN("LWO2: Unexpected VMAP chunk"); + else + LoadLWO2VertexMap(head.length, head.type == AI_LWO_VMAD); + break; + } + // face list + case AI_LWO_POLS: { + if (skip) + break; + + unsigned int old = (unsigned int)mCurLayer->mFaces.size(); + LoadLWO2Polygons(head.length); + mCurLayer->mFaceIDXOfs = old; + break; + } + // polygon tags + case AI_LWO_PTAG: { + if (skip) + break; + + if (mCurLayer->mFaces.empty()) { + ASSIMP_LOG_WARN("LWO2: Unexpected PTAG"); + } else { + LoadLWO2PolygonTags(head.length); + } + break; + } + // list of tags + case AI_LWO_TAGS: { + if (!mTags->empty()) { + ASSIMP_LOG_WARN("LWO2: SRFS chunk encountered twice"); + } else { + LoadLWOTags(head.length); + } + break; + } + + // surface chunk + case AI_LWO_SURF: { + LoadLWO2Surface(head.length); + break; + } + + // clip chunk + case AI_LWO_CLIP: { + LoadLWO2Clip(head.length); + break; + } + + // envelope chunk + case AI_LWO_ENVL: { + LoadLWO2Envelope(head.length); + break; + } + } + mFileBuffer = next; + } +} + +#endif // !! ASSIMP_BUILD_NO_LWO_IMPORTER diff --git a/libs/assimp/code/AssetLib/LWO/LWOLoader.h b/libs/assimp/code/AssetLib/LWO/LWOLoader.h new file mode 100644 index 0000000..f3add53 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOLoader.h @@ -0,0 +1,468 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Declaration of the LWO importer class. */ +#pragma once +#ifndef AI_LWOLOADER_H_INCLUDED +#define AI_LWOLOADER_H_INCLUDED + +#include "LWOFileData.h" +#include <assimp/BaseImporter.h> +#include <assimp/material.h> +#include <assimp/DefaultLogger.hpp> + +#include <map> + +struct aiTexture; +struct aiNode; +struct aiMaterial; + +namespace Assimp { +using namespace LWO; + +// --------------------------------------------------------------------------- +/** Class to load LWO files. + * + * @note Methods named "xxxLWO2[xxx]" are used with the newer LWO2 format. + * Methods named "xxxLWOB[xxx]" are used with the older LWOB format. + * Methods named "xxxLWO[xxx]" are used with both formats. + * Methods named "xxx" are used to preprocess the loaded data - + * they aren't specific to one format version +*/ +// --------------------------------------------------------------------------- +class LWOImporter : public BaseImporter { +public: + LWOImporter(); + ~LWOImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer *pImp) override; + +protected: + // ------------------------------------------------------------------- + // Get list of supported extensions + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + +private: + // ------------------------------------------------------------------- + /** Loads a LWO file in the older LWOB format (LW < 6) + */ + void LoadLWOBFile(); + + // ------------------------------------------------------------------- + /** Loads a LWO file in the newer LWO2 format (LW >= 6) + */ + void LoadLWO2File(); + + // ------------------------------------------------------------------- + /** Parsing functions used for all file format versions + */ + inline void GetS0(std::string &out, unsigned int max); + inline float GetF4(); + inline uint32_t GetU4(); + inline uint16_t GetU2(); + inline uint8_t GetU1(); + + // ------------------------------------------------------------------- + /** Loads a surface chunk from an LWOB file + * @param size Maximum size to be read, in bytes. + */ + void LoadLWOBSurface(unsigned int size); + + // ------------------------------------------------------------------- + /** Loads a surface chunk from an LWO2 file + * @param size Maximum size to be read, in bytes. + */ + void LoadLWO2Surface(unsigned int size); + + // ------------------------------------------------------------------- + /** Loads a texture block from a LWO2 file. + * @param size Maximum size to be read, in bytes. + * @param head Header of the SUF.BLOK header + */ + void LoadLWO2TextureBlock(LE_NCONST IFF::SubChunkHeader *head, + unsigned int size); + + // ------------------------------------------------------------------- + /** Loads a shader block from a LWO2 file. + * @param size Maximum size to be read, in bytes. + * @param head Header of the SUF.BLOK header + */ + void LoadLWO2ShaderBlock(LE_NCONST IFF::SubChunkHeader *head, + unsigned int size); + + // ------------------------------------------------------------------- + /** Loads an image map from a LWO2 file + * @param size Maximum size to be read, in bytes. + * @param tex Texture object to be filled + */ + void LoadLWO2ImageMap(unsigned int size, LWO::Texture &tex); + void LoadLWO2Gradient(unsigned int size, LWO::Texture &tex); + void LoadLWO2Procedural(unsigned int size, LWO::Texture &tex); + + // loads the header - used by thethree functions above + void LoadLWO2TextureHeader(unsigned int size, LWO::Texture &tex); + + // ------------------------------------------------------------------- + /** Loads the LWO tag list from the file + * @param size Maximum size to be read, in bytes. + */ + void LoadLWOTags(unsigned int size); + + // ------------------------------------------------------------------- + /** Load polygons from a POLS chunk + * @param length Size of the chunk + */ + void LoadLWO2Polygons(unsigned int length); + void LoadLWOBPolygons(unsigned int length); + + // ------------------------------------------------------------------- + /** Load polygon tags from a PTAG chunk + * @param length Size of the chunk + */ + void LoadLWO2PolygonTags(unsigned int length); + + // ------------------------------------------------------------------- + /** Load a vertex map from a VMAP/VMAD chunk + * @param length Size of the chunk + * @param perPoly Operate on per-polygon base? + */ + void LoadLWO2VertexMap(unsigned int length, bool perPoly); + + // ------------------------------------------------------------------- + /** Load polygons from a PNTS chunk + * @param length Size of the chunk + */ + void LoadLWOPoints(unsigned int length); + + // ------------------------------------------------------------------- + /** Load a clip from a CLIP chunk + * @param length Size of the chunk + */ + void LoadLWO2Clip(unsigned int length); + + // ------------------------------------------------------------------- + /** Load an envelope from an EVL chunk + * @param length Size of the chunk + */ + void LoadLWO2Envelope(unsigned int length); + + // ------------------------------------------------------------------- + /** Count vertices and faces in a LWOB/LWO2 file + */ + void CountVertsAndFacesLWO2(unsigned int &verts, + unsigned int &faces, + uint16_t *&cursor, + const uint16_t *const end, + unsigned int max = UINT_MAX); + + void CountVertsAndFacesLWOB(unsigned int &verts, + unsigned int &faces, + LE_NCONST uint16_t *&cursor, + const uint16_t *const end, + unsigned int max = UINT_MAX); + + // ------------------------------------------------------------------- + /** Read vertices and faces in a LWOB/LWO2 file + */ + void CopyFaceIndicesLWO2(LWO::FaceList::iterator &it, + uint16_t *&cursor, + const uint16_t *const end); + + // ------------------------------------------------------------------- + void CopyFaceIndicesLWOB(LWO::FaceList::iterator &it, + LE_NCONST uint16_t *&cursor, + const uint16_t *const end, + unsigned int max = UINT_MAX); + + // ------------------------------------------------------------------- + /** Resolve the tag and surface lists that have been loaded. + * Generates the mMapping table. + */ + void ResolveTags(); + + // ------------------------------------------------------------------- + /** Resolve the clip list that has been loaded. + * Replaces clip references with real clips. + */ + void ResolveClips(); + + // ------------------------------------------------------------------- + /** Add a texture list to an output material description. + * + * @param pcMat Output material + * @param in Input texture list + * @param type Type identifier of the texture list + */ + bool HandleTextures(aiMaterial *pcMat, const TextureList &in, + aiTextureType type); + + // ------------------------------------------------------------------- + /** Adjust a texture path + */ + void AdjustTexturePath(std::string &out); + + // ------------------------------------------------------------------- + /** Convert a LWO surface description to an ASSIMP material + */ + void ConvertMaterial(const LWO::Surface &surf, aiMaterial *pcMat); + + // ------------------------------------------------------------------- + /** Get a list of all UV/VC channels required by a specific surface. + * + * @param surf Working surface + * @param layer Working layer + * @param out Output list. The members are indices into the + * UV/VC channel lists of the layer + */ + void FindUVChannels(/*const*/ LWO::Surface &surf, + LWO::SortedRep &sorted, + /*const*/ LWO::Layer &layer, + unsigned int out[AI_MAX_NUMBER_OF_TEXTURECOORDS]); + + // ------------------------------------------------------------------- + char FindUVChannels(LWO::TextureList &list, + LWO::Layer &layer, LWO::UVChannel &uv, unsigned int next); + + // ------------------------------------------------------------------- + void FindVCChannels(const LWO::Surface &surf, + LWO::SortedRep &sorted, + const LWO::Layer &layer, + unsigned int out[AI_MAX_NUMBER_OF_COLOR_SETS]); + + // ------------------------------------------------------------------- + /** Generate the final node graph + * Unused nodes are deleted. + * @param apcNodes Flat list of nodes + */ + void GenerateNodeGraph(std::map<uint16_t, aiNode *> &apcNodes); + + // ------------------------------------------------------------------- + /** Add children to a node + * @param node Node to become a father + * @param parent Index of the node + * @param apcNodes Flat list of nodes - used nodes are set to nullptr. + */ + void AddChildren(aiNode *node, uint16_t parent, + std::vector<aiNode *> &apcNodes); + + // ------------------------------------------------------------------- + /** Read a variable sized integer + * @param inout Input and output buffer + */ + int ReadVSizedIntLWO2(uint8_t *&inout); + + // ------------------------------------------------------------------- + /** Assign a value from a VMAP to a vertex and all vertices + * attached to it. + * @param base VMAP destination data + * @param numRead Number of float's to be read + * @param idx Absolute index of the first vertex + * @param data Value of the VMAP to be assigned - read numRead + * floats from this array. + */ + void DoRecursiveVMAPAssignment(VMapEntry *base, unsigned int numRead, + unsigned int idx, float *data); + + // ------------------------------------------------------------------- + /** Compute normal vectors for a mesh + * @param mesh Input mesh + * @param smoothingGroups Smoothing-groups-per-face array + * @param surface Surface for the mesh + */ + void ComputeNormals(aiMesh *mesh, const std::vector<unsigned int> &smoothingGroups, + const LWO::Surface &surface); + + // ------------------------------------------------------------------- + /** Setup a new texture after the corresponding chunk was + * encountered in the file. + * @param list Texture list + * @param size Maximum number of bytes to be read + * @return Pointer to new texture + */ + LWO::Texture *SetupNewTextureLWOB(LWO::TextureList &list, + unsigned int size); + +protected: + /** true if the file is a LWO2 file*/ + bool mIsLWO2; + + /** true if the file is a LXOB file*/ + bool mIsLXOB; + + /** Temporary list of layers from the file */ + LayerList *mLayers; + + /** Pointer to the current layer */ + LWO::Layer *mCurLayer; + + /** Temporary tag list from the file */ + TagList *mTags; + + /** Mapping table to convert from tag to surface indices. + UINT_MAX indicates that a no corresponding surface is available */ + TagMappingTable *mMapping; + + /** Temporary surface list from the file */ + SurfaceList *mSurfaces; + + /** Temporary clip list from the file */ + ClipList mClips; + + /** Temporary envelope list from the file */ + EnvelopeList mEnvelopes; + + /** file buffer */ + uint8_t *mFileBuffer; + + /** Size of the file, in bytes */ + unsigned int fileSize; + + /** Output scene */ + aiScene *mScene; + + /** Configuration option: speed flag set? */ + bool configSpeedFlag; + + /** Configuration option: index of layer to be loaded */ + unsigned int configLayerIndex; + + /** Configuration option: name of layer to be loaded */ + std::string configLayerName; + + /** True if we have a named layer */ + bool hasNamedLayer; +}; + +// ------------------------------------------------------------------------------------------------ +inline float LWOImporter::GetF4() { + float f; + ::memcpy(&f, mFileBuffer, 4); + mFileBuffer += 4; + AI_LSWAP4(f); + return f; +} + +// ------------------------------------------------------------------------------------------------ +inline uint32_t LWOImporter::GetU4() { + uint32_t f; + ::memcpy(&f, mFileBuffer, 4); + mFileBuffer += 4; + AI_LSWAP4(f); + return f; +} + +// ------------------------------------------------------------------------------------------------ +inline uint16_t LWOImporter::GetU2() { + uint16_t f; + ::memcpy(&f, mFileBuffer, 2); + mFileBuffer += 2; + AI_LSWAP2(f); + return f; +} + +// ------------------------------------------------------------------------------------------------ +inline uint8_t LWOImporter::GetU1() { + return *mFileBuffer++; +} + +// ------------------------------------------------------------------------------------------------ +inline int LWOImporter::ReadVSizedIntLWO2(uint8_t *&inout) { + int i; + int c = *inout; + inout++; + if (c != 0xFF) { + i = c << 8; + c = *inout; + inout++; + i |= c; + } else { + c = *inout; + inout++; + i = c << 16; + c = *inout; + inout++; + i |= c << 8; + c = *inout; + inout++; + i |= c; + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +inline void LWOImporter::GetS0(std::string &out, unsigned int max) { + unsigned int iCursor = 0; + const char *sz = (const char *)mFileBuffer; + while (*mFileBuffer) { + if (++iCursor > max) { + ASSIMP_LOG_WARN("LWO: Invalid file, string is is too long"); + break; + } + ++mFileBuffer; + } + size_t len = (size_t)((const char *)mFileBuffer - sz); + out = std::string(sz, len); + mFileBuffer += (len & 0x1 ? 1 : 2); +} + +} // end of namespace Assimp + +#endif // AI_LWOIMPORTER_H_INCLUDED diff --git a/libs/assimp/code/AssetLib/LWO/LWOMaterial.cpp b/libs/assimp/code/AssetLib/LWO/LWOMaterial.cpp new file mode 100644 index 0000000..b6f0bcc --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOMaterial.cpp @@ -0,0 +1,844 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the material oart of the LWO importer class */ + +#ifndef ASSIMP_BUILD_NO_LWO_IMPORTER + +// internal headers +#include "LWOLoader.h" +#include <assimp/ByteSwapper.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +template <class T> +T lerp(const T &one, const T &two, float val) { + return one + (two - one) * val; +} + +// ------------------------------------------------------------------------------------------------ +// Convert a lightwave mapping mode to our's +inline aiTextureMapMode GetMapMode(LWO::Texture::Wrap in) { + switch (in) { + case LWO::Texture::REPEAT: + return aiTextureMapMode_Wrap; + + case LWO::Texture::MIRROR: + return aiTextureMapMode_Mirror; + + case LWO::Texture::RESET: + ASSIMP_LOG_WARN("LWO2: Unsupported texture map mode: RESET"); + + // fall though here + case LWO::Texture::EDGE: + return aiTextureMapMode_Clamp; + } + return (aiTextureMapMode)0; +} + +// ------------------------------------------------------------------------------------------------ +bool LWOImporter::HandleTextures(aiMaterial *pcMat, const TextureList &in, aiTextureType type) { + ai_assert(nullptr != pcMat); + + unsigned int cur = 0, temp = 0; + aiString s; + bool ret = false; + + for (const auto &texture : in) { + if (!texture.enabled || !texture.bCanUse) + continue; + ret = true; + + // Convert lightwave's mapping modes to ours. We let them + // as they are, the GenUVcoords step will compute UV + // channels if they're not there. + + aiTextureMapping mapping = aiTextureMapping_OTHER; + switch (texture.mapMode) { + case LWO::Texture::Planar: + mapping = aiTextureMapping_PLANE; + break; + case LWO::Texture::Cylindrical: + mapping = aiTextureMapping_CYLINDER; + break; + case LWO::Texture::Spherical: + mapping = aiTextureMapping_SPHERE; + break; + case LWO::Texture::Cubic: + mapping = aiTextureMapping_BOX; + break; + case LWO::Texture::FrontProjection: + ASSIMP_LOG_ERROR("LWO2: Unsupported texture mapping: FrontProjection"); + mapping = aiTextureMapping_OTHER; + break; + case LWO::Texture::UV: { + if (UINT_MAX == texture.mRealUVIndex) { + // We have no UV index for this texture, so we can't display it + continue; + } + + // add the UV source index + temp = texture.mRealUVIndex; + pcMat->AddProperty<int>((int *)&temp, 1, AI_MATKEY_UVWSRC(type, cur)); + + mapping = aiTextureMapping_UV; + } break; + default: + ai_assert(false); + }; + + if (mapping != aiTextureMapping_UV) { + // Setup the main axis + aiVector3D v; + switch (texture.majorAxis) { + case Texture::AXIS_X: + v = aiVector3D(1.0, 0.0, 0.0); + break; + case Texture::AXIS_Y: + v = aiVector3D(0.0, 1.0, 0.0); + break; + default: // case Texture::AXIS_Z: + v = aiVector3D(0.0, 0.0, 1.0); + break; + } + + pcMat->AddProperty(&v, 1, AI_MATKEY_TEXMAP_AXIS(type, cur)); + + // Setup UV scalings for cylindric and spherical projections + if (mapping == aiTextureMapping_CYLINDER || mapping == aiTextureMapping_SPHERE) { + aiUVTransform trafo; + trafo.mScaling.x = texture.wrapAmountW; + trafo.mScaling.y = texture.wrapAmountH; + + static_assert(sizeof(aiUVTransform) / sizeof(ai_real) == 5, "sizeof(aiUVTransform)/sizeof(ai_real) == 5"); + pcMat->AddProperty(&trafo, 1, AI_MATKEY_UVTRANSFORM(type, cur)); + } + ASSIMP_LOG_VERBOSE_DEBUG("LWO2: Setting up non-UV mapping"); + } + + // The older LWOB format does not use indirect references to clips. + // The file name of a texture is directly specified in the tex chunk. + if (mIsLWO2) { + // find the corresponding clip (take the last one if multiple + // share the same index) + ClipList::iterator end = mClips.end(), candidate = end; + temp = texture.mClipIdx; + for (ClipList::iterator clip = mClips.begin(); clip != end; ++clip) { + if ((*clip).idx == temp) { + candidate = clip; + } + } + if (candidate == end) { + ASSIMP_LOG_ERROR("LWO2: Clip index is out of bounds"); + temp = 0; + + // fixme: apparently some LWO files shipping with Doom3 don't + // have clips at all ... check whether that's true or whether + // it's a bug in the loader. + + s.Set("$texture.png"); + + //continue; + } else { + if (Clip::UNSUPPORTED == (*candidate).type) { + ASSIMP_LOG_ERROR("LWO2: Clip type is not supported"); + continue; + } + AdjustTexturePath((*candidate).path); + s.Set((*candidate).path); + + // Additional image settings + int flags = 0; + if ((*candidate).negate) { + flags |= aiTextureFlags_Invert; + } + pcMat->AddProperty(&flags, 1, AI_MATKEY_TEXFLAGS(type, cur)); + } + } else { + std::string ss = texture.mFileName; + if (!ss.length()) { + ASSIMP_LOG_WARN("LWOB: Empty file name"); + continue; + } + AdjustTexturePath(ss); + s.Set(ss); + } + pcMat->AddProperty(&s, AI_MATKEY_TEXTURE(type, cur)); + + // add the blend factor + pcMat->AddProperty<float>(&texture.mStrength, 1, AI_MATKEY_TEXBLEND(type, cur)); + + // add the blend operation + switch (texture.blendType) { + case LWO::Texture::Normal: + case LWO::Texture::Multiply: + temp = (unsigned int)aiTextureOp_Multiply; + break; + + case LWO::Texture::Subtractive: + case LWO::Texture::Difference: + temp = (unsigned int)aiTextureOp_Subtract; + break; + + case LWO::Texture::Divide: + temp = (unsigned int)aiTextureOp_Divide; + break; + + case LWO::Texture::Additive: + temp = (unsigned int)aiTextureOp_Add; + break; + + default: + temp = (unsigned int)aiTextureOp_Multiply; + ASSIMP_LOG_WARN("LWO2: Unsupported texture blend mode: alpha or displacement"); + } + // Setup texture operation + pcMat->AddProperty<int>((int *)&temp, 1, AI_MATKEY_TEXOP(type, cur)); + + // setup the mapping mode + int mapping_ = static_cast<int>(mapping); + pcMat->AddProperty<int>(&mapping_, 1, AI_MATKEY_MAPPING(type, cur)); + + // add the u-wrapping + temp = (unsigned int)GetMapMode(texture.wrapModeWidth); + pcMat->AddProperty<int>((int *)&temp, 1, AI_MATKEY_MAPPINGMODE_U(type, cur)); + + // add the v-wrapping + temp = (unsigned int)GetMapMode(texture.wrapModeHeight); + pcMat->AddProperty<int>((int *)&temp, 1, AI_MATKEY_MAPPINGMODE_V(type, cur)); + + ++cur; + } + return ret; +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::ConvertMaterial(const LWO::Surface &surf, aiMaterial *pcMat) { + // copy the name of the surface + aiString st; + st.Set(surf.mName); + pcMat->AddProperty(&st, AI_MATKEY_NAME); + + const int i = surf.bDoubleSided ? 1 : 0; + pcMat->AddProperty(&i, 1, AI_MATKEY_TWOSIDED); + + // add the refraction index and the bump intensity + pcMat->AddProperty(&surf.mIOR, 1, AI_MATKEY_REFRACTI); + pcMat->AddProperty(&surf.mBumpIntensity, 1, AI_MATKEY_BUMPSCALING); + + aiShadingMode m; + if (surf.mSpecularValue && surf.mGlossiness) { + float fGloss; + if (mIsLWO2) { + fGloss = std::pow(surf.mGlossiness * ai_real(10.0) + ai_real(2.0), ai_real(2.0)); + } else { + if (16.0 >= surf.mGlossiness) + fGloss = 6.0; + else if (64.0 >= surf.mGlossiness) + fGloss = 20.0; + else if (256.0 >= surf.mGlossiness) + fGloss = 50.0; + else + fGloss = 80.0; + } + + pcMat->AddProperty(&surf.mSpecularValue, 1, AI_MATKEY_SHININESS_STRENGTH); + pcMat->AddProperty(&fGloss, 1, AI_MATKEY_SHININESS); + m = aiShadingMode_Phong; + } else + m = aiShadingMode_Gouraud; + + // specular color + aiColor3D clr = lerp(aiColor3D(1.0, 1.0, 1.0), surf.mColor, surf.mColorHighlights); + pcMat->AddProperty(&clr, 1, AI_MATKEY_COLOR_SPECULAR); + pcMat->AddProperty(&surf.mSpecularValue, 1, AI_MATKEY_SHININESS_STRENGTH); + + // emissive color + // luminosity is not really the same but it affects the surface in a similar way. Some scaling looks good. + clr.g = clr.b = clr.r = surf.mLuminosity * ai_real(0.8); + pcMat->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_EMISSIVE); + + // opacity ... either additive or default-blended, please + if (0.0 != surf.mAdditiveTransparency) { + const int add = aiBlendMode_Additive; + pcMat->AddProperty(&surf.mAdditiveTransparency, 1, AI_MATKEY_OPACITY); + pcMat->AddProperty(&add, 1, AI_MATKEY_BLEND_FUNC); + } else if (10e10f != surf.mTransparency) { + const int def = aiBlendMode_Default; + const float f = 1.0f - surf.mTransparency; + pcMat->AddProperty(&f, 1, AI_MATKEY_OPACITY); + pcMat->AddProperty(&def, 1, AI_MATKEY_BLEND_FUNC); + } + + // ADD TEXTURES to the material + // TODO: find out how we can handle COLOR textures correctly... + bool b = HandleTextures(pcMat, surf.mColorTextures, aiTextureType_DIFFUSE); + b = (b || HandleTextures(pcMat, surf.mDiffuseTextures, aiTextureType_DIFFUSE)); + HandleTextures(pcMat, surf.mSpecularTextures, aiTextureType_SPECULAR); + HandleTextures(pcMat, surf.mGlossinessTextures, aiTextureType_SHININESS); + HandleTextures(pcMat, surf.mBumpTextures, aiTextureType_HEIGHT); + HandleTextures(pcMat, surf.mOpacityTextures, aiTextureType_OPACITY); + HandleTextures(pcMat, surf.mReflectionTextures, aiTextureType_REFLECTION); + + // Now we need to know which shader to use .. iterate through the shader list of + // the surface and search for a name which we know ... + for (const auto &shader : surf.mShaders) { + if (shader.functionName == "LW_SuperCelShader" || shader.functionName == "AH_CelShader") { + ASSIMP_LOG_INFO("LWO2: Mapping LW_SuperCelShader/AH_CelShader to aiShadingMode_Toon"); + + m = aiShadingMode_Toon; + break; + } else if (shader.functionName == "LW_RealFresnel" || shader.functionName == "LW_FastFresnel") { + ASSIMP_LOG_INFO("LWO2: Mapping LW_RealFresnel/LW_FastFresnel to aiShadingMode_Fresnel"); + + m = aiShadingMode_Fresnel; + break; + } else { + ASSIMP_LOG_WARN("LWO2: Unknown surface shader: ", shader.functionName); + } + } + if (surf.mMaximumSmoothAngle <= 0.0) + m = aiShadingMode_Flat; + int m_ = static_cast<int>(m); + pcMat->AddProperty(&m_, 1, AI_MATKEY_SHADING_MODEL); + + // (the diffuse value is just a scaling factor) + // If a diffuse texture is set, we set this value to 1.0 + clr = (b && false ? aiColor3D(1.0, 1.0, 1.0) : surf.mColor); + clr.r *= surf.mDiffuseValue; + clr.g *= surf.mDiffuseValue; + clr.b *= surf.mDiffuseValue; + pcMat->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); +} + +// ------------------------------------------------------------------------------------------------ +char LWOImporter::FindUVChannels(LWO::TextureList &list, + LWO::Layer & /*layer*/, LWO::UVChannel &uv, unsigned int next) { + char ret = 0; + for (auto &texture : list) { + + // Ignore textures with non-UV mappings for the moment. + if (!texture.enabled || !texture.bCanUse || texture.mapMode != LWO::Texture::UV) { + continue; + } + + if (texture.mUVChannelIndex == uv.name) { + ret = 1; + + // got it. + if (texture.mRealUVIndex == UINT_MAX || texture.mRealUVIndex == next) { + texture.mRealUVIndex = next; + } else { + // channel mismatch. need to duplicate the material. + ASSIMP_LOG_WARN("LWO: Channel mismatch, would need to duplicate surface [design bug]"); + + // TODO + } + } + } + return ret; +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::FindUVChannels(LWO::Surface &surf, + LWO::SortedRep &sorted, LWO::Layer &layer, + unsigned int out[AI_MAX_NUMBER_OF_TEXTURECOORDS]) { + unsigned int next = 0, extra = 0, num_extra = 0; + + // Check whether we have an UV entry != 0 for one of the faces in 'sorted' + for (unsigned int i = 0; i < layer.mUVChannels.size(); ++i) { + LWO::UVChannel &uv = layer.mUVChannels[i]; + + for (LWO::SortedRep::const_iterator it = sorted.begin(); it != sorted.end(); ++it) { + + LWO::Face &face = layer.mFaces[*it]; + + for (unsigned int n = 0; n < face.mNumIndices; ++n) { + unsigned int idx = face.mIndices[n]; + + if (uv.abAssigned[idx] && ((aiVector2D *)&uv.rawData[0])[idx] != aiVector2D()) { + + if (extra >= AI_MAX_NUMBER_OF_TEXTURECOORDS) { + + ASSIMP_LOG_ERROR("LWO: Maximum number of UV channels for " + "this mesh reached. Skipping channel \'" + + uv.name + "\'"); + + } else { + // Search through all textures assigned to 'surf' and look for this UV channel + char had = 0; + had |= FindUVChannels(surf.mColorTextures, layer, uv, next); + had |= FindUVChannels(surf.mDiffuseTextures, layer, uv, next); + had |= FindUVChannels(surf.mSpecularTextures, layer, uv, next); + had |= FindUVChannels(surf.mGlossinessTextures, layer, uv, next); + had |= FindUVChannels(surf.mOpacityTextures, layer, uv, next); + had |= FindUVChannels(surf.mBumpTextures, layer, uv, next); + had |= FindUVChannels(surf.mReflectionTextures, layer, uv, next); + + // We have a texture referencing this UV channel so we have to take special care + // and are willing to drop unreferenced channels in favour of it. + if (had != 0) { + if (num_extra) { + + for (unsigned int a = next; a < std::min(extra, AI_MAX_NUMBER_OF_TEXTURECOORDS - 1u); ++a) { + out[a + 1] = out[a]; + } + } + ++extra; + out[next++] = i; + } + // Bah ... seems not to be used at all. Push to end if enough space is available. + else { + out[extra++] = i; + ++num_extra; + } + } + it = sorted.end() - 1; + break; + } + } + } + } + if (extra < AI_MAX_NUMBER_OF_TEXTURECOORDS) { + out[extra] = UINT_MAX; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::FindVCChannels(const LWO::Surface &surf, LWO::SortedRep &sorted, const LWO::Layer &layer, + unsigned int out[AI_MAX_NUMBER_OF_COLOR_SETS]) { + unsigned int next = 0; + + // Check whether we have an vc entry != 0 for one of the faces in 'sorted' + for (unsigned int i = 0; i < layer.mVColorChannels.size(); ++i) { + const LWO::VColorChannel &vc = layer.mVColorChannels[i]; + + if (surf.mVCMap == vc.name) { + // The vertex color map is explicitly requested by the surface so we need to take special care of it + for (unsigned int a = 0; a < std::min(next, AI_MAX_NUMBER_OF_COLOR_SETS - 1u); ++a) { + out[a + 1] = out[a]; + } + out[0] = i; + ++next; + } else { + + for (LWO::SortedRep::iterator it = sorted.begin(); it != sorted.end(); ++it) { + const LWO::Face &face = layer.mFaces[*it]; + + for (unsigned int n = 0; n < face.mNumIndices; ++n) { + unsigned int idx = face.mIndices[n]; + + if (vc.abAssigned[idx] && ((aiColor4D *)&vc.rawData[0])[idx] != aiColor4D(0.0, 0.0, 0.0, 1.0)) { + if (next >= AI_MAX_NUMBER_OF_COLOR_SETS) { + + ASSIMP_LOG_ERROR("LWO: Maximum number of vertex color channels for " + "this mesh reached. Skipping channel \'" + + vc.name + "\'"); + + } else { + out[next++] = i; + } + it = sorted.end() - 1; + break; + } + } + } + } + } + if (next != AI_MAX_NUMBER_OF_COLOR_SETS) { + out[next] = UINT_MAX; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2ImageMap(unsigned int size, LWO::Texture &tex) { + LE_NCONST uint8_t *const end = mFileBuffer + size; + while (true) { + if (mFileBuffer + 6 >= end) break; + LE_NCONST IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) + throw DeadlyImportError("LWO2: Invalid SURF.BLOCK chunk length"); + + uint8_t *const next = mFileBuffer + head.length; + switch (head.type) { + case AI_LWO_PROJ: + tex.mapMode = (Texture::MappingMode)GetU2(); + break; + case AI_LWO_WRAP: + tex.wrapModeWidth = (Texture::Wrap)GetU2(); + tex.wrapModeHeight = (Texture::Wrap)GetU2(); + break; + case AI_LWO_AXIS: + tex.majorAxis = (Texture::Axes)GetU2(); + break; + case AI_LWO_IMAG: + tex.mClipIdx = GetU2(); + break; + case AI_LWO_VMAP: + GetS0(tex.mUVChannelIndex, head.length); + break; + case AI_LWO_WRPH: + tex.wrapAmountH = GetF4(); + break; + case AI_LWO_WRPW: + tex.wrapAmountW = GetF4(); + break; + } + mFileBuffer = next; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2Procedural(unsigned int /*size*/, LWO::Texture &tex) { + // --- not supported at the moment + ASSIMP_LOG_ERROR("LWO2: Found procedural texture, this is not supported"); + tex.bCanUse = false; +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2Gradient(unsigned int /*size*/, LWO::Texture &tex) { + // --- not supported at the moment + ASSIMP_LOG_ERROR("LWO2: Found gradient texture, this is not supported"); + tex.bCanUse = false; +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2TextureHeader(unsigned int size, LWO::Texture &tex) { + LE_NCONST uint8_t *const end = mFileBuffer + size; + + // get the ordinal string + GetS0(tex.ordinal, size); + + // we could crash later if this is an empty string ... + if (!tex.ordinal.length()) { + ASSIMP_LOG_ERROR("LWO2: Ill-formed SURF.BLOK ordinal string"); + tex.ordinal = "\x00"; + } + while (true) { + if (mFileBuffer + 6 >= end) break; + const IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) + throw DeadlyImportError("LWO2: Invalid texture header chunk length"); + + uint8_t *const next = mFileBuffer + head.length; + switch (head.type) { + case AI_LWO_CHAN: + tex.type = GetU4(); + break; + case AI_LWO_ENAB: + tex.enabled = GetU2() ? true : false; + break; + case AI_LWO_OPAC: + tex.blendType = (Texture::BlendType)GetU2(); + tex.mStrength = GetF4(); + break; + } + mFileBuffer = next; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2TextureBlock(LE_NCONST IFF::SubChunkHeader *head, unsigned int size) { + ai_assert(!mSurfaces->empty()); + LWO::Surface &surf = mSurfaces->back(); + LWO::Texture tex; + + // load the texture header + LoadLWO2TextureHeader(head->length, tex); + size -= head->length + 6; + + // now get the exact type of the texture + switch (head->type) { + case AI_LWO_PROC: + LoadLWO2Procedural(size, tex); + break; + case AI_LWO_GRAD: + LoadLWO2Gradient(size, tex); + break; + case AI_LWO_IMAP: + LoadLWO2ImageMap(size, tex); + } + + // get the destination channel + TextureList *listRef = nullptr; + switch (tex.type) { + case AI_LWO_COLR: + listRef = &surf.mColorTextures; + break; + case AI_LWO_DIFF: + listRef = &surf.mDiffuseTextures; + break; + case AI_LWO_SPEC: + listRef = &surf.mSpecularTextures; + break; + case AI_LWO_GLOS: + listRef = &surf.mGlossinessTextures; + break; + case AI_LWO_BUMP: + listRef = &surf.mBumpTextures; + break; + case AI_LWO_TRAN: + listRef = &surf.mOpacityTextures; + break; + case AI_LWO_REFL: + listRef = &surf.mReflectionTextures; + break; + default: + ASSIMP_LOG_WARN("LWO2: Encountered unknown texture type"); + return; + } + + // now attach the texture to the parent surface - sort by ordinal string + for (TextureList::iterator it = listRef->begin(); it != listRef->end(); ++it) { + if (::strcmp(tex.ordinal.c_str(), (*it).ordinal.c_str()) < 0) { + listRef->insert(it, tex); + return; + } + } + listRef->push_back(tex); +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2ShaderBlock(LE_NCONST IFF::SubChunkHeader * /*head*/, unsigned int size) { + LE_NCONST uint8_t *const end = mFileBuffer + size; + + ai_assert(!mSurfaces->empty()); + LWO::Surface &surf = mSurfaces->back(); + LWO::Shader shader; + + // get the ordinal string + GetS0(shader.ordinal, size); + + // we could crash later if this is an empty string ... + if (!shader.ordinal.length()) { + ASSIMP_LOG_ERROR("LWO2: Ill-formed SURF.BLOK ordinal string"); + shader.ordinal = "\x00"; + } + + // read the header + while (true) { + if (mFileBuffer + 6 >= end) break; + const IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) + throw DeadlyImportError("LWO2: Invalid shader header chunk length"); + + uint8_t *const next = mFileBuffer + head.length; + switch (head.type) { + case AI_LWO_ENAB: + shader.enabled = GetU2() ? true : false; + break; + + case AI_LWO_FUNC: + GetS0(shader.functionName, head.length); + } + mFileBuffer = next; + } + + // now attach the shader to the parent surface - sort by ordinal string + for (ShaderList::iterator it = surf.mShaders.begin(); it != surf.mShaders.end(); ++it) { + if (::strcmp(shader.ordinal.c_str(), (*it).ordinal.c_str()) < 0) { + surf.mShaders.insert(it, shader); + return; + } + } + surf.mShaders.push_back(shader); +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2Surface(unsigned int size) { + LE_NCONST uint8_t *const end = mFileBuffer + size; + + mSurfaces->push_back(LWO::Surface()); + LWO::Surface &surf = mSurfaces->back(); + + GetS0(surf.mName, size); + + // check whether this surface was derived from any other surface + std::string derived; + GetS0(derived, (unsigned int)(end - mFileBuffer)); + if (derived.length()) { + // yes, find this surface + for (SurfaceList::iterator it = mSurfaces->begin(), itEnd = mSurfaces->end() - 1; it != itEnd; ++it) { + if ((*it).mName == derived) { + // we have it ... + surf = *it; + derived.clear(); + break; + } + } + if (derived.size()) { + ASSIMP_LOG_WARN("LWO2: Unable to find source surface: ", derived); + } + } + + while (true) { + if (mFileBuffer + 6 >= end) + break; + const IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) + throw DeadlyImportError("LWO2: Invalid surface chunk length"); + + uint8_t *const next = mFileBuffer + head.length; + switch (head.type) { + // diffuse color + case AI_LWO_COLR: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, COLR, 12); + surf.mColor.r = GetF4(); + surf.mColor.g = GetF4(); + surf.mColor.b = GetF4(); + break; + } + // diffuse strength ... hopefully + case AI_LWO_DIFF: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, DIFF, 4); + surf.mDiffuseValue = GetF4(); + break; + } + // specular strength ... hopefully + case AI_LWO_SPEC: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SPEC, 4); + surf.mSpecularValue = GetF4(); + break; + } + // transparency + case AI_LWO_TRAN: { + // transparency explicitly disabled? + if (surf.mTransparency == 10e10f) + break; + + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, TRAN, 4); + surf.mTransparency = GetF4(); + break; + } + // additive transparency + case AI_LWO_ADTR: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, ADTR, 4); + surf.mAdditiveTransparency = GetF4(); + break; + } + // wireframe mode + case AI_LWO_LINE: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, LINE, 2); + if (GetU2() & 0x1) + surf.mWireframe = true; + break; + } + // glossiness + case AI_LWO_GLOS: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, GLOS, 4); + surf.mGlossiness = GetF4(); + break; + } + // bump intensity + case AI_LWO_BUMP: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, BUMP, 4); + surf.mBumpIntensity = GetF4(); + break; + } + // color highlights + case AI_LWO_CLRH: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, CLRH, 4); + surf.mColorHighlights = GetF4(); + break; + } + // index of refraction + case AI_LWO_RIND: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, RIND, 4); + surf.mIOR = GetF4(); + break; + } + // polygon sidedness + case AI_LWO_SIDE: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SIDE, 2); + surf.bDoubleSided = (3 == GetU2()); + break; + } + // maximum smoothing angle + case AI_LWO_SMAN: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SMAN, 4); + surf.mMaximumSmoothAngle = std::fabs(GetF4()); + break; + } + // vertex color channel to be applied to the surface + case AI_LWO_VCOL: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, VCOL, 12); + surf.mDiffuseValue *= GetF4(); // strength + ReadVSizedIntLWO2(mFileBuffer); // skip envelope + surf.mVCMapType = GetU4(); // type of the channel + + // name of the channel + GetS0(surf.mVCMap, (unsigned int)(next - mFileBuffer)); + break; + } + // surface bock entry + case AI_LWO_BLOK: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, BLOK, 4); + IFF::SubChunkHeader head2 = IFF::LoadSubChunk(mFileBuffer); + + switch (head2.type) { + case AI_LWO_PROC: + case AI_LWO_GRAD: + case AI_LWO_IMAP: + LoadLWO2TextureBlock(&head2, head.length); + break; + case AI_LWO_SHDR: + LoadLWO2ShaderBlock(&head2, head.length); + break; + + default: + ASSIMP_LOG_WARN("LWO2: Found an unsupported surface BLOK"); + }; + + break; + } + } + mFileBuffer = next; + } +} + +#endif // !! ASSIMP_BUILD_NO_X_IMPORTER diff --git a/libs/assimp/code/AssetLib/LWS/LWSLoader.cpp b/libs/assimp/code/AssetLib/LWS/LWSLoader.cpp new file mode 100644 index 0000000..951dbe1 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWS/LWSLoader.cpp @@ -0,0 +1,929 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 LWSLoader.cpp + * @brief Implementation of the LWS importer class + */ + +#ifndef ASSIMP_BUILD_NO_LWS_IMPORTER + +#include "AssetLib/LWS/LWSLoader.h" +#include "Common/Importer.h" +#include "PostProcessing/ConvertToLHProcess.h" + +#include <assimp/GenericProperty.h> +#include <assimp/ParsingUtils.h> +#include <assimp/SceneCombiner.h> +#include <assimp/SkeletonMeshBuilder.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> + +#include <memory> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "LightWave Scene Importer", + "", + "", + "http://www.newtek.com/lightwave.html=", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "lws mot" +}; + +// ------------------------------------------------------------------------------------------------ +// Recursive parsing of LWS files +void LWS::Element::Parse(const char *&buffer) { + for (; SkipSpacesAndLineEnd(&buffer); SkipLine(&buffer)) { + + // begin of a new element with children + bool sub = false; + if (*buffer == '{') { + ++buffer; + SkipSpaces(&buffer); + sub = true; + } else if (*buffer == '}') + return; + + children.push_back(Element()); + + // copy data line - read token per token + + const char *cur = buffer; + while (!IsSpaceOrNewLine(*buffer)) + ++buffer; + children.back().tokens[0] = std::string(cur, (size_t)(buffer - cur)); + SkipSpaces(&buffer); + + if (children.back().tokens[0] == "Plugin") { + ASSIMP_LOG_VERBOSE_DEBUG("LWS: Skipping over plugin-specific data"); + + // strange stuff inside Plugin/Endplugin blocks. Needn't + // follow LWS syntax, so we skip over it + for (; SkipSpacesAndLineEnd(&buffer); SkipLine(&buffer)) { + if (!::strncmp(buffer, "EndPlugin", 9)) { + //SkipLine(&buffer); + break; + } + } + continue; + } + + cur = buffer; + while (!IsLineEnd(*buffer)) { + ++buffer; + } + children.back().tokens[1] = std::string(cur, (size_t)(buffer - cur)); + + // parse more elements recursively + if (sub) { + children.back().Parse(buffer); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +LWSImporter::LWSImporter() : + configSpeedFlag(), + io(), + first(), + last(), + fps(), + noSkeletonMesh() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +LWSImporter::~LWSImporter() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool LWSImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const uint32_t tokens[] = { + AI_MAKE_MAGIC("LWSC"), + AI_MAKE_MAGIC("LWMO") + }; + return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Get list of file extensions +const aiImporterDesc *LWSImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties +void LWSImporter::SetupProperties(const Importer *pImp) { + // AI_CONFIG_FAVOUR_SPEED + configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED, 0)); + + // AI_CONFIG_IMPORT_LWS_ANIM_START + first = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_LWS_ANIM_START, + 150392 /* magic hack */); + + // AI_CONFIG_IMPORT_LWS_ANIM_END + last = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_LWS_ANIM_END, + 150392 /* magic hack */); + + if (last < first) { + std::swap(last, first); + } + + noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, 0) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Read an envelope description +void LWSImporter::ReadEnvelope(const LWS::Element &dad, LWO::Envelope &fill) { + if (dad.children.empty()) { + ASSIMP_LOG_ERROR("LWS: Envelope descriptions must not be empty"); + return; + } + + // reserve enough storage + std::list<LWS::Element>::const_iterator it = dad.children.begin(); + + fill.keys.reserve(strtoul10(it->tokens[1].c_str())); + + for (++it; it != dad.children.end(); ++it) { + const char *c = (*it).tokens[1].c_str(); + + if ((*it).tokens[0] == "Key") { + fill.keys.push_back(LWO::Key()); + LWO::Key &key = fill.keys.back(); + + float f; + SkipSpaces(&c); + c = fast_atoreal_move<float>(c, key.value); + SkipSpaces(&c); + c = fast_atoreal_move<float>(c, f); + + key.time = f; + + unsigned int span = strtoul10(c, &c), num = 0; + switch (span) { + case 0: + key.inter = LWO::IT_TCB; + num = 5; + break; + case 1: + case 2: + key.inter = LWO::IT_HERM; + num = 5; + break; + case 3: + key.inter = LWO::IT_LINE; + num = 0; + break; + case 4: + key.inter = LWO::IT_STEP; + num = 0; + break; + case 5: + key.inter = LWO::IT_BEZ2; + num = 4; + break; + default: + ASSIMP_LOG_ERROR("LWS: Unknown span type"); + } + for (unsigned int i = 0; i < num; ++i) { + SkipSpaces(&c); + c = fast_atoreal_move<float>(c, key.params[i]); + } + } else if ((*it).tokens[0] == "Behaviors") { + SkipSpaces(&c); + fill.pre = (LWO::PrePostBehaviour)strtoul10(c, &c); + SkipSpaces(&c); + fill.post = (LWO::PrePostBehaviour)strtoul10(c, &c); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Read animation channels in the old LightWave animation format +void LWSImporter::ReadEnvelope_Old( + std::list<LWS::Element>::const_iterator &it, + const std::list<LWS::Element>::const_iterator &end, + LWS::NodeDesc &nodes, + unsigned int /*version*/) { + unsigned int num, sub_num; + if (++it == end) goto unexpected_end; + + num = strtoul10((*it).tokens[0].c_str()); + for (unsigned int i = 0; i < num; ++i) { + + nodes.channels.push_back(LWO::Envelope()); + LWO::Envelope &envl = nodes.channels.back(); + + envl.index = i; + envl.type = (LWO::EnvelopeType)(i + 1); + + if (++it == end) { + goto unexpected_end; + } + sub_num = strtoul10((*it).tokens[0].c_str()); + + for (unsigned int n = 0; n < sub_num; ++n) { + + if (++it == end) goto unexpected_end; + + // parse value and time, skip the rest for the moment. + LWO::Key key; + const char *c = fast_atoreal_move<float>((*it).tokens[0].c_str(), key.value); + SkipSpaces(&c); + float f; + fast_atoreal_move<float>((*it).tokens[0].c_str(), f); + key.time = f; + + envl.keys.push_back(key); + } + } + return; + +unexpected_end: + ASSIMP_LOG_ERROR("LWS: Encountered unexpected end of file while parsing object motion"); +} + +// ------------------------------------------------------------------------------------------------ +// Setup a nice name for a node +void LWSImporter::SetupNodeName(aiNode *nd, LWS::NodeDesc &src) { + const unsigned int combined = src.number | ((unsigned int)src.type) << 28u; + + // the name depends on the type. We break LWS's strange naming convention + // and return human-readable, but still machine-parsable and unique, strings. + if (src.type == LWS::NodeDesc::OBJECT) { + + if (src.path.length()) { + std::string::size_type s = src.path.find_last_of("\\/"); + if (s == std::string::npos) { + s = 0; + } else { + ++s; + } + std::string::size_type t = src.path.substr(s).find_last_of('.'); + + nd->mName.length = ::ai_snprintf(nd->mName.data, MAXLEN, "%s_(%08X)", src.path.substr(s).substr(0, t).c_str(), combined); + return; + } + } + nd->mName.length = ::ai_snprintf(nd->mName.data, MAXLEN, "%s_(%08X)", src.name, combined); +} + +// ------------------------------------------------------------------------------------------------ +// Recursively build the scene-graph +void LWSImporter::BuildGraph(aiNode *nd, LWS::NodeDesc &src, std::vector<AttachmentInfo> &attach, + BatchLoader &batch, + aiCamera **&camOut, + aiLight **&lightOut, + std::vector<aiNodeAnim *> &animOut) { + // Setup a very cryptic name for the node, we want the user to be happy + SetupNodeName(nd, src); + aiNode *ndAnim = nd; + + // If the node is an object + if (src.type == LWS::NodeDesc::OBJECT) { + + // If the object is from an external file, get it + aiScene *obj = nullptr; + if (src.path.length()) { + obj = batch.GetImport(src.id); + if (!obj) { + ASSIMP_LOG_ERROR("LWS: Failed to read external file ", src.path); + } else { + if (obj->mRootNode->mNumChildren == 1) { + + //If the pivot is not set for this layer, get it from the external object + if (!src.isPivotSet) { + src.pivotPos.x = +obj->mRootNode->mTransformation.a4; + src.pivotPos.y = +obj->mRootNode->mTransformation.b4; + src.pivotPos.z = -obj->mRootNode->mTransformation.c4; //The sign is the RH to LH back conversion + } + + //Remove first node from obj (the old pivot), reset transform of second node (the mesh node) + aiNode *newRootNode = obj->mRootNode->mChildren[0]; + obj->mRootNode->mChildren[0] = nullptr; + delete obj->mRootNode; + + obj->mRootNode = newRootNode; + obj->mRootNode->mTransformation.a4 = 0.0; + obj->mRootNode->mTransformation.b4 = 0.0; + obj->mRootNode->mTransformation.c4 = 0.0; + } + } + } + + //Setup the pivot node (also the animation node), the one we received + nd->mName = std::string("Pivot:") + nd->mName.data; + ndAnim = nd; + + //Add the attachment node to it + nd->mNumChildren = 1; + nd->mChildren = new aiNode *[1]; + nd->mChildren[0] = new aiNode(); + nd->mChildren[0]->mParent = nd; + nd->mChildren[0]->mTransformation.a4 = -src.pivotPos.x; + nd->mChildren[0]->mTransformation.b4 = -src.pivotPos.y; + nd->mChildren[0]->mTransformation.c4 = -src.pivotPos.z; + SetupNodeName(nd->mChildren[0], src); + + //Update the attachment node + nd = nd->mChildren[0]; + + //Push attachment, if the object came from an external file + if (obj) { + attach.push_back(AttachmentInfo(obj, nd)); + } + } + + // If object is a light source - setup a corresponding ai structure + else if (src.type == LWS::NodeDesc::LIGHT) { + aiLight *lit = *lightOut++ = new aiLight(); + + // compute final light color + lit->mColorDiffuse = lit->mColorSpecular = src.lightColor * src.lightIntensity; + + // name to attach light to node -> unique due to LWs indexing system + lit->mName = nd->mName; + + // determine light type and setup additional members + if (src.lightType == 2) { /* spot light */ + + lit->mType = aiLightSource_SPOT; + lit->mAngleInnerCone = (float)AI_DEG_TO_RAD(src.lightConeAngle); + lit->mAngleOuterCone = lit->mAngleInnerCone + (float)AI_DEG_TO_RAD(src.lightEdgeAngle); + + } else if (src.lightType == 1) { /* directional light source */ + lit->mType = aiLightSource_DIRECTIONAL; + } else { + lit->mType = aiLightSource_POINT; + } + + // fixme: no proper handling of light falloffs yet + if (src.lightFalloffType == 1) { + lit->mAttenuationConstant = 1.f; + } else if (src.lightFalloffType == 2) { + lit->mAttenuationLinear = 1.f; + } else { + lit->mAttenuationQuadratic = 1.f; + } + } else if (src.type == LWS::NodeDesc::CAMERA) { // If object is a camera - setup a corresponding ai structure + aiCamera *cam = *camOut++ = new aiCamera(); + + // name to attach cam to node -> unique due to LWs indexing system + cam->mName = nd->mName; + } + + // Get the node transformation from the LWO key + LWO::AnimResolver resolver(src.channels, fps); + resolver.ExtractBindPose(ndAnim->mTransformation); + + // .. and construct animation channels + aiNodeAnim *anim = nullptr; + + if (first != last) { + resolver.SetAnimationRange(first, last); + resolver.ExtractAnimChannel(&anim, AI_LWO_ANIM_FLAG_SAMPLE_ANIMS | AI_LWO_ANIM_FLAG_START_AT_ZERO); + if (anim) { + anim->mNodeName = ndAnim->mName; + animOut.push_back(anim); + } + } + + // Add children + if (!src.children.empty()) { + nd->mChildren = new aiNode *[src.children.size()]; + for (std::list<LWS::NodeDesc *>::iterator it = src.children.begin(); it != src.children.end(); ++it) { + aiNode *ndd = nd->mChildren[nd->mNumChildren++] = new aiNode(); + ndd->mParent = nd; + + BuildGraph(ndd, **it, attach, batch, camOut, lightOut, animOut); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Determine the exact location of a LWO file +std::string LWSImporter::FindLWOFile(const std::string &in) { + // insert missing directory separator if necessary + std::string tmp(in); + if (in.length() > 3 && in[1] == ':' && in[2] != '\\' && in[2] != '/') { + tmp = in[0] + (std::string(":\\") + in.substr(2)); + } + + if (io->Exists(tmp)) { + return in; + } + + // file is not accessible for us ... maybe it's packed by + // LightWave's 'Package Scene' command? + + // Relevant for us are the following two directories: + // <folder>\Objects\<hh>\<*>.lwo + // <folder>\Scenes\<hh>\<*>.lws + // where <hh> is optional. + + std::string test = std::string("..") + (io->getOsSeparator() + tmp); + if (io->Exists(test)) { + return test; + } + + test = std::string("..") + (io->getOsSeparator() + test); + if (io->Exists(test)) { + return test; + } + + // return original path, maybe the IOsystem knows better + return tmp; +} + +// ------------------------------------------------------------------------------------------------ +// Read file into given scene data structure +void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + io = pIOHandler; + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb")); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open LWS file ", pFile, "."); + } + + // Allocate storage and copy the contents of the file to a memory buffer + std::vector<char> mBuffer; + TextFileToBuffer(file.get(), mBuffer); + + // Parse the file structure + LWS::Element root; + const char *dummy = &mBuffer[0]; + root.Parse(dummy); + + // Construct a Batch-importer to read more files recursively + BatchLoader batch(pIOHandler); + + // Construct an array to receive the flat output graph + std::list<LWS::NodeDesc> nodes; + + unsigned int cur_light = 0, cur_camera = 0, cur_object = 0; + unsigned int num_light = 0, num_camera = 0, num_object = 0; + + // check magic identifier, 'LWSC' + bool motion_file = false; + std::list<LWS::Element>::const_iterator it = root.children.begin(); + + if ((*it).tokens[0] == "LWMO") { + motion_file = true; + } + + if ((*it).tokens[0] != "LWSC" && !motion_file) { + throw DeadlyImportError("LWS: Not a LightWave scene, magic tag LWSC not found"); + } + + // get file format version and print to log + ++it; + + if (it == root.children.end() || (*it).tokens[0].empty()) { + ASSIMP_LOG_ERROR("Invalid LWS file detectedm abort import."); + return; + } + unsigned int version = strtoul10((*it).tokens[0].c_str()); + ASSIMP_LOG_INFO("LWS file format version is ", (*it).tokens[0]); + first = 0.; + last = 60.; + fps = 25.; // seems to be a good default frame rate + + // Now read all elements in a very straightforward manner + for (; it != root.children.end(); ++it) { + const char *c = (*it).tokens[1].c_str(); + + // 'FirstFrame': begin of animation slice + if ((*it).tokens[0] == "FirstFrame") { + // see SetupProperties() + if (150392. != first ) { + first = strtoul10(c, &c) - 1.; // we're zero-based + } + } else if ((*it).tokens[0] == "LastFrame") { // 'LastFrame': end of animation slice + // see SetupProperties() + if (150392. != last ) { + last = strtoul10(c, &c) - 1.; // we're zero-based + } + } else if ((*it).tokens[0] == "FramesPerSecond") { // 'FramesPerSecond': frames per second + fps = strtoul10(c, &c); + } else if ((*it).tokens[0] == "LoadObjectLayer") { // 'LoadObjectLayer': load a layer of a specific LWO file + + // get layer index + const int layer = strtoul10(c, &c); + + // setup the layer to be loaded + BatchLoader::PropertyMap props; + SetGenericProperty(props.ints, AI_CONFIG_IMPORT_LWO_ONE_LAYER_ONLY, layer); + + // add node to list + LWS::NodeDesc d; + d.type = LWS::NodeDesc::OBJECT; + if (version >= 4) { // handle LWSC 4 explicit ID + SkipSpaces(&c); + d.number = strtoul16(c, &c) & AI_LWS_MASK; + } else { + d.number = cur_object++; + } + + // and add the file to the import list + SkipSpaces(&c); + std::string path = FindLWOFile(c); + d.path = path; + d.id = batch.AddLoadRequest(path, 0, &props); + + nodes.push_back(d); + ++num_object; + } else if ((*it).tokens[0] == "LoadObject") { // 'LoadObject': load a LWO file into the scene-graph + + // add node to list + LWS::NodeDesc d; + d.type = LWS::NodeDesc::OBJECT; + + if (version >= 4) { // handle LWSC 4 explicit ID + d.number = strtoul16(c, &c) & AI_LWS_MASK; + SkipSpaces(&c); + } else { + d.number = cur_object++; + } + std::string path = FindLWOFile(c); + d.id = batch.AddLoadRequest(path, 0, nullptr); + + d.path = path; + nodes.push_back(d); + ++num_object; + } else if ((*it).tokens[0] == "AddNullObject") { // 'AddNullObject': add a dummy node to the hierarchy + + // add node to list + LWS::NodeDesc d; + d.type = LWS::NodeDesc::OBJECT; + if (version >= 4) { // handle LWSC 4 explicit ID + d.number = strtoul16(c, &c) & AI_LWS_MASK; + SkipSpaces(&c); + } else { + d.number = cur_object++; + } + d.name = c; + nodes.push_back(d); + + num_object++; + } + // 'NumChannels': Number of envelope channels assigned to last layer + else if ((*it).tokens[0] == "NumChannels") { + // ignore for now + } + // 'Channel': preceedes any envelope description + else if ((*it).tokens[0] == "Channel") { + if (nodes.empty()) { + if (motion_file) { + + // LightWave motion file. Add dummy node + LWS::NodeDesc d; + d.type = LWS::NodeDesc::OBJECT; + d.name = c; + d.number = cur_object++; + nodes.push_back(d); + } + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'Channel\'"); + } + + // important: index of channel + nodes.back().channels.push_back(LWO::Envelope()); + LWO::Envelope &env = nodes.back().channels.back(); + + env.index = strtoul10(c); + + // currently we can just interpret the standard channels 0...9 + // (hack) assume that index-i yields the binary channel type from LWO + env.type = (LWO::EnvelopeType)(env.index + 1); + + } + // 'Envelope': a single animation channel + else if ((*it).tokens[0] == "Envelope") { + if (nodes.empty() || nodes.back().channels.empty()) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'Envelope\'"); + else { + ReadEnvelope((*it), nodes.back().channels.back()); + } + } + // 'ObjectMotion': animation information for older lightwave formats + else if (version < 3 && ((*it).tokens[0] == "ObjectMotion" || + (*it).tokens[0] == "CameraMotion" || + (*it).tokens[0] == "LightMotion")) { + + if (nodes.empty()) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'<Light|Object|Camera>Motion\'"); + else { + ReadEnvelope_Old(it, root.children.end(), nodes.back(), version); + } + } + // 'Pre/PostBehavior': pre/post animation behaviour for LWSC 2 + else if (version == 2 && (*it).tokens[0] == "Pre/PostBehavior") { + if (nodes.empty()) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'Pre/PostBehavior'"); + else { + for (std::list<LWO::Envelope>::iterator envelopeIt = nodes.back().channels.begin(); envelopeIt != nodes.back().channels.end(); ++envelopeIt) { + // two ints per envelope + LWO::Envelope &env = *envelopeIt; + env.pre = (LWO::PrePostBehaviour)strtoul10(c, &c); + SkipSpaces(&c); + env.post = (LWO::PrePostBehaviour)strtoul10(c, &c); + SkipSpaces(&c); + } + } + } + // 'ParentItem': specifies the parent of the current element + else if ((*it).tokens[0] == "ParentItem") { + if (nodes.empty()) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'ParentItem\'"); + + else + nodes.back().parent = strtoul16(c, &c); + } + // 'ParentObject': deprecated one for older formats + else if (version < 3 && (*it).tokens[0] == "ParentObject") { + if (nodes.empty()) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'ParentObject\'"); + + else { + nodes.back().parent = strtoul10(c, &c) | (1u << 28u); + } + } + // 'AddCamera': add a camera to the scenegraph + else if ((*it).tokens[0] == "AddCamera") { + + // add node to list + LWS::NodeDesc d; + d.type = LWS::NodeDesc::CAMERA; + + if (version >= 4) { // handle LWSC 4 explicit ID + d.number = strtoul16(c, &c) & AI_LWS_MASK; + } else + d.number = cur_camera++; + nodes.push_back(d); + + num_camera++; + } + // 'CameraName': set name of currently active camera + else if ((*it).tokens[0] == "CameraName") { + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::CAMERA) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'CameraName\'"); + + else + nodes.back().name = c; + } + // 'AddLight': add a light to the scenegraph + else if ((*it).tokens[0] == "AddLight") { + + // add node to list + LWS::NodeDesc d; + d.type = LWS::NodeDesc::LIGHT; + + if (version >= 4) { // handle LWSC 4 explicit ID + d.number = strtoul16(c, &c) & AI_LWS_MASK; + } else + d.number = cur_light++; + nodes.push_back(d); + + num_light++; + } + // 'LightName': set name of currently active light + else if ((*it).tokens[0] == "LightName") { + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightName\'"); + + else + nodes.back().name = c; + } + // 'LightIntensity': set intensity of currently active light + else if ((*it).tokens[0] == "LightIntensity" || (*it).tokens[0] == "LgtIntensity") { + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) { + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightIntensity\'"); + } else { + const std::string env = "(envelope)"; + if (0 == strncmp(c, env.c_str(), env.size())) { + ASSIMP_LOG_ERROR("LWS: envelopes for LightIntensity not supported, set to 1.0"); + nodes.back().lightIntensity = (ai_real)1.0; + } else { + fast_atoreal_move<float>(c, nodes.back().lightIntensity); + } + } + } + // 'LightType': set type of currently active light + else if ((*it).tokens[0] == "LightType") { + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightType\'"); + + else + nodes.back().lightType = strtoul10(c); + + } + // 'LightFalloffType': set falloff type of currently active light + else if ((*it).tokens[0] == "LightFalloffType") { + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightFalloffType\'"); + else + nodes.back().lightFalloffType = strtoul10(c); + + } + // 'LightConeAngle': set cone angle of currently active light + else if ((*it).tokens[0] == "LightConeAngle") { + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightConeAngle\'"); + + else + nodes.back().lightConeAngle = fast_atof(c); + + } + // 'LightEdgeAngle': set area where we're smoothing from min to max intensity + else if ((*it).tokens[0] == "LightEdgeAngle") { + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightEdgeAngle\'"); + + else + nodes.back().lightEdgeAngle = fast_atof(c); + + } + // 'LightColor': set color of currently active light + else if ((*it).tokens[0] == "LightColor") { + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightColor\'"); + + else { + c = fast_atoreal_move<float>(c, (float &)nodes.back().lightColor.r); + SkipSpaces(&c); + c = fast_atoreal_move<float>(c, (float &)nodes.back().lightColor.g); + SkipSpaces(&c); + c = fast_atoreal_move<float>(c, (float &)nodes.back().lightColor.b); + } + } + + // 'PivotPosition': position of local transformation origin + else if ((*it).tokens[0] == "PivotPosition" || (*it).tokens[0] == "PivotPoint") { + if (nodes.empty()) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'PivotPosition\'"); + else { + c = fast_atoreal_move<float>(c, (float &)nodes.back().pivotPos.x); + SkipSpaces(&c); + c = fast_atoreal_move<float>(c, (float &)nodes.back().pivotPos.y); + SkipSpaces(&c); + c = fast_atoreal_move<float>(c, (float &)nodes.back().pivotPos.z); + // Mark pivotPos as set + nodes.back().isPivotSet = true; + } + } + } + + // resolve parenting + for (std::list<LWS::NodeDesc>::iterator ndIt = nodes.begin(); ndIt != nodes.end(); ++ndIt) { + + // check whether there is another node which calls us a parent + for (std::list<LWS::NodeDesc>::iterator dit = nodes.begin(); dit != nodes.end(); ++dit) { + if (dit != ndIt && *ndIt == (*dit).parent) { + if ((*dit).parent_resolved) { + // fixme: it's still possible to produce an overflow due to cross references .. + ASSIMP_LOG_ERROR("LWS: Found cross reference in scene-graph"); + continue; + } + + ndIt->children.push_back(&*dit); + (*dit).parent_resolved = &*ndIt; + } + } + } + + // find out how many nodes have no parent yet + unsigned int no_parent = 0; + for (std::list<LWS::NodeDesc>::iterator ndIt = nodes.begin(); ndIt != nodes.end(); ++ndIt) { + if (!ndIt->parent_resolved) { + ++no_parent; + } + } + if (!no_parent) { + throw DeadlyImportError("LWS: Unable to find scene root node"); + } + + // Load all subsequent files + batch.LoadAll(); + + // and build the final output graph by attaching the loaded external + // files to ourselves. first build a master graph + aiScene *master = new aiScene(); + aiNode *nd = master->mRootNode = new aiNode(); + + // allocate storage for cameras&lights + if (num_camera) { + master->mCameras = new aiCamera *[master->mNumCameras = num_camera]; + } + aiCamera **cams = master->mCameras; + if (num_light) { + master->mLights = new aiLight *[master->mNumLights = num_light]; + } + aiLight **lights = master->mLights; + + std::vector<AttachmentInfo> attach; + std::vector<aiNodeAnim *> anims; + + nd->mName.Set("<LWSRoot>"); + nd->mChildren = new aiNode *[no_parent]; + for (std::list<LWS::NodeDesc>::iterator ndIt = nodes.begin(); ndIt != nodes.end(); ++ndIt) { + if (!ndIt->parent_resolved) { + aiNode *ro = nd->mChildren[nd->mNumChildren++] = new aiNode(); + ro->mParent = nd; + + // ... and build the scene graph. If we encounter object nodes, + // add then to our attachment table. + BuildGraph(ro, *ndIt, attach, batch, cams, lights, anims); + } + } + + // create a master animation channel for us + if (anims.size()) { + master->mAnimations = new aiAnimation *[master->mNumAnimations = 1]; + aiAnimation *anim = master->mAnimations[0] = new aiAnimation(); + anim->mName.Set("LWSMasterAnim"); + + // LWS uses seconds as time units, but we convert to frames + anim->mTicksPerSecond = fps; + anim->mDuration = last - (first - 1); /* fixme ... zero or one-based?*/ + + anim->mChannels = new aiNodeAnim *[anim->mNumChannels = static_cast<unsigned int>(anims.size())]; + std::copy(anims.begin(), anims.end(), anim->mChannels); + } + + // convert the master scene to RH + MakeLeftHandedProcess monster_cheat; + monster_cheat.Execute(master); + + // .. ccw + FlipWindingOrderProcess flipper; + flipper.Execute(master); + + // OK ... finally build the output graph + SceneCombiner::MergeScenes(&pScene, master, attach, + AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | (!configSpeedFlag ? ( + AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY | AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) : + 0)); + + // Check flags + if (!pScene->mNumMeshes || !pScene->mNumMaterials) { + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + + if (pScene->mNumAnimations && !noSkeletonMesh) { + // construct skeleton mesh + SkeletonMeshBuilder builder(pScene); + } + } +} + +#endif // !! ASSIMP_BUILD_NO_LWS_IMPORTER diff --git a/libs/assimp/code/AssetLib/LWS/LWSLoader.h b/libs/assimp/code/AssetLib/LWS/LWSLoader.h new file mode 100644 index 0000000..225186f --- /dev/null +++ b/libs/assimp/code/AssetLib/LWS/LWSLoader.h @@ -0,0 +1,237 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 LWSLoader.h + * @brief Declaration of the LightWave scene importer class. + */ +#pragma once +#ifndef AI_LWSLOADER_H_INCLUDED +#define AI_LWSLOADER_H_INCLUDED + +#include "AssetLib/LWO/LWOFileData.h" + +#include <assimp/BaseImporter.h> +#include <assimp/SceneCombiner.h> + +struct aiImporterDesc; + +namespace Assimp { + +class BatchLoader; +class Importer; +class IOSystem; + +namespace LWS { + +// --------------------------------------------------------------------------- +/** Represents an element in a LWS file. + * + * This can either be a single data line - <name> <value> or a data + * group - { name <data_line0> ... n } + */ +class Element { +public: + Element() {} + + // first: name, second: rest + std::string tokens[2]; + std::list<Element> children; + + //! Recursive parsing function + void Parse(const char *&buffer); +}; + +#define AI_LWS_MASK (0xffffffff >> 4u) + +// --------------------------------------------------------------------------- +/** Represents a LWS scenegraph element + */ +struct NodeDesc { + NodeDesc() : + type(), + id(), + number(0), + parent(0), + name(), + isPivotSet(false), + lightColor(1.f, 1.f, 1.f), + lightIntensity(1.f), + lightType(0), + lightFalloffType(0), + lightConeAngle(45.f), + lightEdgeAngle(), + parent_resolved(nullptr) {} + + enum { + + OBJECT = 1, + LIGHT = 2, + CAMERA = 3, + BONE = 4 + } type; // type of node + + // if object: path + std::string path; + unsigned int id; + + // number of object + unsigned int number; + + // index of parent index + unsigned int parent; + + // lights & cameras & dummies: name + const char *name; + + // animation channels + std::list<LWO::Envelope> channels; + + // position of pivot point + aiVector3D pivotPos; + bool isPivotSet; + + // color of light source + aiColor3D lightColor; + + // intensity of light source + float lightIntensity; + + // type of light source + unsigned int lightType; + + // falloff type of light source + unsigned int lightFalloffType; + + // cone angle of (spot) light source + float lightConeAngle; + + // soft cone angle of (spot) light source + float lightEdgeAngle; + + // list of resolved children + std::list<NodeDesc *> children; + + // resolved parent node + NodeDesc *parent_resolved; + + // for std::find() + bool operator==(unsigned int num) const { + if (!num) + return false; + unsigned int _type = num >> 28u; + + return _type == static_cast<unsigned int>(type) && (num & AI_LWS_MASK) == number; + } +}; + +} // end namespace LWS + +// --------------------------------------------------------------------------- +/** LWS (LightWave Scene Format) importer class. + * + * This class does heavily depend on the LWO importer class. LWS files + * contain mainly descriptions how LWO objects are composed together + * in a scene. +*/ +class LWSImporter : public BaseImporter { +public: + LWSImporter(); + ~LWSImporter() override; + + // ------------------------------------------------------------------- + // Check whether we can read a specific file + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + // Get list of supported extensions + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + // Import file into given scene data structure + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + + // ------------------------------------------------------------------- + // Setup import properties + void SetupProperties(const Importer *pImp) override; + +private: + // ------------------------------------------------------------------- + // Read an envelope description + void ReadEnvelope(const LWS::Element &dad, LWO::Envelope &out); + + // ------------------------------------------------------------------- + // Read an envelope description for the older LW file format + void ReadEnvelope_Old(std::list<LWS::Element>::const_iterator &it, + const std::list<LWS::Element>::const_iterator &end, + LWS::NodeDesc &nodes, + unsigned int version); + + // ------------------------------------------------------------------- + // Setup a nice name for a node + void SetupNodeName(aiNode *nd, LWS::NodeDesc &src); + + // ------------------------------------------------------------------- + // Recursively build the scenegraph + void BuildGraph(aiNode *nd, + LWS::NodeDesc &src, + std::vector<AttachmentInfo> &attach, + BatchLoader &batch, + aiCamera **&camOut, + aiLight **&lightOut, + std::vector<aiNodeAnim *> &animOut); + + // ------------------------------------------------------------------- + // Try several dirs until we find the right location of a LWS file. + std::string FindLWOFile(const std::string &in); + +private: + bool configSpeedFlag; + IOSystem *io; + double first, last, fps; + bool noSkeletonMesh; +}; + +} // end of namespace Assimp + +#endif // AI_LWSIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/M3D/M3DExporter.cpp b/libs/assimp/code/AssetLib/M3D/M3DExporter.cpp new file mode 100644 index 0000000..cf87b62 --- /dev/null +++ b/libs/assimp/code/AssetLib/M3D/M3DExporter.cpp @@ -0,0 +1,442 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team +Copyright (c) 2019 bzt + +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. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER + +#define M3D_IMPLEMENTATION +#define M3D_NOIMPORTER +#define M3D_EXPORTER +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER +#define M3D_NODUP + + +// Header files, standard library. +#include <memory> // shared_ptr +#include <string> +#include <vector> + +#include <assimp/Exceptional.h> // DeadlyExportError +#include <assimp/StreamWriter.h> // StreamWriterLE +#include <assimp/material.h> // aiTextureType +#include <assimp/mesh.h> +#include <assimp/scene.h> +#include <assimp/version.h> // aiGetVersion +#include <assimp/DefaultLogger.hpp> +#include <assimp/Exporter.hpp> +#include <assimp/IOSystem.hpp> + +#include "M3DExporter.h" +#include "M3DMaterials.h" +#include "M3DWrapper.h" + +// RESOURCES: +// https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md +// https://gitlab.com/bztsrc/model3d/blob/master/docs/a3d_format.md + +/* + * Currently supports static meshes, vertex colors, materials, textures + * + * For animation, it would require the following conversions: + * - aiNode (bones) -> m3d_t.bone (with parent id, position vector and orientation quaternion) + * - aiMesh.aiBone -> m3d_t.skin (per vertex, with bone id, weight pairs) + * - aiAnimation -> m3d_action (frame with timestamp and list of bone id, position, orientation + * triplets, instead of per bone timestamp + lists) + */ + +// ------------------------------------------------------------------------------------------------ +// Conversion functions +// ------------------------------------------------------------------------------------------------ +// helper to add a vertex (private to NodeWalk) +m3dv_t *AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx) { + if (v->x == (M3D_FLOAT)-0.0) v->x = (M3D_FLOAT)0.0; + if (v->y == (M3D_FLOAT)-0.0) v->y = (M3D_FLOAT)0.0; + if (v->z == (M3D_FLOAT)-0.0) v->z = (M3D_FLOAT)0.0; + if (v->w == (M3D_FLOAT)-0.0) v->w = (M3D_FLOAT)0.0; + vrtx = (m3dv_t *)M3D_REALLOC(vrtx, ((*numvrtx) + 1) * sizeof(m3dv_t)); + memcpy(&vrtx[*numvrtx], v, sizeof(m3dv_t)); + *idx = *numvrtx; + (*numvrtx)++; + return vrtx; +} + +// ------------------------------------------------------------------------------------------------ +// helper to add a tmap (private to NodeWalk) +m3dti_t *AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx) { + tmap = (m3dti_t *)M3D_REALLOC(tmap, ((*numtmap) + 1) * sizeof(m3dti_t)); + memcpy(&tmap[*numtmap], ti, sizeof(m3dti_t)); + *idx = *numtmap; + (*numtmap)++; + return tmap; +} + +// ------------------------------------------------------------------------------------------------ +// convert aiColor4D into uint32_t +uint32_t mkColor(aiColor4D *c) { + return ((uint8_t)(c->a * 255) << 24L) | + ((uint8_t)(c->b * 255) << 16L) | + ((uint8_t)(c->g * 255) << 8L) | + ((uint8_t)(c->r * 255) << 0L); +} + +// ------------------------------------------------------------------------------------------------ +// add a material property to the output +void addProp(m3dm_t *m, uint8_t type, uint32_t value) { + unsigned int i; + i = m->numprop++; + m->prop = (m3dp_t *)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t)); + if (!m->prop) { + throw DeadlyExportError("memory allocation error"); + } + m->prop[i].type = type; + m->prop[i].value.num = value; +} + +// ------------------------------------------------------------------------------------------------ +// convert aiString to identifier safe C string. This is a duplication of _m3d_safestr +char *SafeStr(aiString str, bool isStrict) { + char *s = (char *)&str.data; + char *d, *ret; + int i, len; + + for (len = str.length + 1; *s && (*s == ' ' || *s == '\t'); s++, len--) + ; + if (len > 255) len = 255; + ret = (char *)M3D_MALLOC(len + 1); + if (!ret) { + throw DeadlyExportError("memory allocation error"); + } + for (i = 0, d = ret; i < len && *s && *s != '\r' && *s != '\n'; s++, d++, i++) { + *d = isStrict && (*s == ' ' || *s == '\t' || *s == '/' || *s == '\\') ? '_' : (*s == '\t' ? ' ' : *s); + } + for (; d > ret && (*(d - 1) == ' ' || *(d - 1) == '\t'); d--) + ; + *d = 0; + return ret; +} + +// ------------------------------------------------------------------------------------------------ +// add a material to the output +M3D_INDEX addMaterial(const Assimp::M3DWrapper &m3d, const aiMaterial *mat) { + unsigned int mi = M3D_NOTDEFINED; + aiColor4D c; + aiString name; + ai_real f; + char *fn; + + if (mat && mat->Get(AI_MATKEY_NAME, name) == AI_SUCCESS && name.length && + strcmp((char *)&name.data, AI_DEFAULT_MATERIAL_NAME)) { + // check if we have saved a material by this name. This has to be done + // because only the referenced materials should be added to the output + for (unsigned int i = 0; i < m3d->nummaterial; i++) + if (!strcmp((char *)&name.data, m3d->material[i].name)) { + mi = i; + break; + } + // if not found, add the material to the output + if (mi == M3D_NOTDEFINED) { + unsigned int k; + mi = m3d->nummaterial++; + m3d->material = (m3dm_t *)M3D_REALLOC(m3d->material, m3d->nummaterial * sizeof(m3dm_t)); + if (!m3d->material) { + throw DeadlyExportError("memory allocation error"); + } + m3d->material[mi].name = SafeStr(name, true); + m3d->material[mi].numprop = 0; + m3d->material[mi].prop = nullptr; + // iterate through the material property table and see what we got + for (k = 0; k < 15; k++) { + unsigned int j; + if (m3d_propertytypes[k].format == m3dpf_map) + continue; + if (aiProps[k].pKey) { + switch (m3d_propertytypes[k].format) { + case m3dpf_color: + if (mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, c) == AI_SUCCESS) + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, mkColor(&c)); + break; + case m3dpf_float: + if (mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, f) == AI_SUCCESS) { + uint32_t f_uint32; + memcpy(&f_uint32, &f, sizeof(uint32_t)); + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, + /* not (uint32_t)f, because we don't want to convert + * it, we want to see it as 32 bits of memory */ + f_uint32); + } + break; + case m3dpf_uint8: + if (mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, j) == AI_SUCCESS) { + // special conversion for illumination model property + if (m3d_propertytypes[k].id == m3dp_il) { + switch (j) { + case aiShadingMode_NoShading: j = 0; break; + case aiShadingMode_Phong: j = 2; break; + default: j = 1; break; + } + } + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, j); + } + break; + default: + if (mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, j) == AI_SUCCESS) + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, j); + break; + } + } + if (aiTxProps[k].pKey && + mat->GetTexture((aiTextureType)aiTxProps[k].type, + aiTxProps[k].index, &name, nullptr, nullptr, nullptr, + nullptr, nullptr) == AI_SUCCESS) { + unsigned int i; + for (j = name.length - 1; j > 0 && name.data[j] != '.'; j++) + ; + if (j && name.data[j] == '.' && + (name.data[j + 1] == 'p' || name.data[j + 1] == 'P') && + (name.data[j + 1] == 'n' || name.data[j + 1] == 'N') && + (name.data[j + 1] == 'g' || name.data[j + 1] == 'G')) + name.data[j] = 0; + // do we have this texture saved already? + fn = SafeStr(name, true); + for (j = 0, i = M3D_NOTDEFINED; j < m3d->numtexture; j++) + if (!strcmp(fn, m3d->texture[j].name)) { + i = j; + free(fn); + break; + } + if (i == M3D_NOTDEFINED) { + i = m3d->numtexture++; + m3d->texture = (m3dtx_t *)M3D_REALLOC( + m3d->texture, + m3d->numtexture * sizeof(m3dtx_t)); + if (!m3d->texture) { + throw DeadlyExportError("memory allocation error"); + } + // we don't need the texture itself, only its name + m3d->texture[i].name = fn; + m3d->texture[i].w = 0; + m3d->texture[i].h = 0; + m3d->texture[i].d = nullptr; + } + addProp(&m3d->material[mi], + m3d_propertytypes[k].id + 128, i); + } + } + } + } + return mi; +} + +namespace Assimp { + +// --------------------------------------------------------------------- +// Worker function for exporting a scene to binary M3D. +// Prototyped and registered in Exporter.cpp +void ExportSceneM3D( + const char *pFile, + IOSystem *pIOSystem, + const aiScene *pScene, + const ExportProperties *pProperties) { + // initialize the exporter + M3DExporter exporter(pScene, pProperties); + + // perform binary export + exporter.doExport(pFile, pIOSystem, false); +} + +// --------------------------------------------------------------------- +// Worker function for exporting a scene to ASCII A3D. +// Prototyped and registered in Exporter.cpp +void ExportSceneM3DA( + const char *pFile, + IOSystem *pIOSystem, + const aiScene *pScene, + const ExportProperties *pProperties + +) { + // initialize the exporter + M3DExporter exporter(pScene, pProperties); + + // perform ascii export + exporter.doExport(pFile, pIOSystem, true); +} + +// ------------------------------------------------------------------------------------------------ +M3DExporter::M3DExporter(const aiScene *pScene, const ExportProperties *pProperties) : + mScene(pScene), + mProperties(pProperties), + outfile() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +void M3DExporter::doExport( + const char *pFile, + IOSystem *pIOSystem, + bool toAscii) { + // TODO: convert mProperties into M3D_EXP_* flags + (void)mProperties; + + // open the indicated file for writing (in binary / ASCII mode) + outfile.reset(pIOSystem->Open(pFile, toAscii ? "wt" : "wb")); + if (!outfile) { + throw DeadlyExportError("could not open output .m3d file: " + std::string(pFile)); + } + + M3DWrapper m3d; + if (!m3d) { + throw DeadlyExportError("memory allocation error"); + } + m3d->name = SafeStr(mScene->mRootNode->mName, false); + + // Create a model from assimp structures + aiMatrix4x4 m; + NodeWalk(m3d, mScene->mRootNode, m); + + // serialize the structures + unsigned int size; + unsigned char *output = m3d.Save(M3D_EXP_FLOAT, M3D_EXP_EXTRA | (toAscii ? M3D_EXP_ASCII : 0), size); + + if (!output || size < 8) { + throw DeadlyExportError("unable to serialize into Model 3D"); + } + + // Write out serialized model + outfile->Write(output, size, 1); + + // explicitly release file pointer, + // so we don't have to rely on class destruction. + outfile.reset(); + + M3D_FREE(m3d->name); + m3d->name = nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// recursive node walker +void M3DExporter::NodeWalk(const M3DWrapper &m3d, const aiNode *pNode, aiMatrix4x4 m) { + aiMatrix4x4 nm = m * pNode->mTransformation; + + for (unsigned int i = 0; i < pNode->mNumMeshes; i++) { + const aiMesh *mesh = mScene->mMeshes[pNode->mMeshes[i]]; + unsigned int mi = M3D_NOTDEFINED; + if (mScene->mMaterials) { + // get the material for this mesh + mi = addMaterial(m3d, mScene->mMaterials[mesh->mMaterialIndex]); + } + // iterate through the mesh faces + for (unsigned int j = 0; j < mesh->mNumFaces; j++) { + unsigned int n; + const aiFace *face = &(mesh->mFaces[j]); + // only triangle meshes supported for now + if (face->mNumIndices != 3) { + throw DeadlyExportError("use aiProcess_Triangulate before export"); + } + // add triangle to the output + n = m3d->numface++; + m3d->face = (m3df_t *)M3D_REALLOC(m3d->face, + m3d->numface * sizeof(m3df_t)); + if (!m3d->face) { + throw DeadlyExportError("memory allocation error"); + } + /* set all index to -1 by default */ + m3d->face[n].vertex[0] = m3d->face[n].vertex[1] = m3d->face[n].vertex[2] = + m3d->face[n].normal[0] = m3d->face[n].normal[1] = m3d->face[n].normal[2] = + m3d->face[n].texcoord[0] = m3d->face[n].texcoord[1] = m3d->face[n].texcoord[2] = M3D_UNDEF; + m3d->face[n].materialid = mi; + for (unsigned int k = 0; k < face->mNumIndices; k++) { + // get the vertex's index + unsigned int l = face->mIndices[k]; + unsigned int idx; + m3dv_t vertex; + m3dti_t ti; + // multiply the position vector by the transformation matrix + aiVector3D v = mesh->mVertices[l]; + v *= nm; + vertex.x = v.x; + vertex.y = v.y; + vertex.z = v.z; + vertex.w = 1.0; + vertex.color = 0; + vertex.skinid = M3D_UNDEF; + // add color if defined + if (mesh->HasVertexColors(0)) + vertex.color = mkColor(&mesh->mColors[0][l]); + // save the vertex to the output + m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, + &vertex, &idx); + m3d->face[n].vertex[k] = (M3D_INDEX)idx; + // do we have texture coordinates? + if (mesh->HasTextureCoords(0)) { + ti.u = mesh->mTextureCoords[0][l].x; + ti.v = mesh->mTextureCoords[0][l].y; + m3d->tmap = AddTmap(m3d->tmap, &m3d->numtmap, &ti, &idx); + m3d->face[n].texcoord[k] = (M3D_INDEX)idx; + } + // do we have normal vectors? + if (mesh->HasNormals()) { + vertex.x = mesh->mNormals[l].x; + vertex.y = mesh->mNormals[l].y; + vertex.z = mesh->mNormals[l].z; + vertex.color = 0; + m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, &vertex, &idx); + m3d->face[n].normal[k] = (M3D_INDEX)idx; + } + } + } + } + // repeat for the children nodes + for (unsigned int i = 0; i < pNode->mNumChildren; i++) { + NodeWalk(m3d, pNode->mChildren[i], nm); + } +} +} // namespace Assimp +#endif +#endif // ASSIMP_BUILD_NO_M3D_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/M3D/M3DExporter.h b/libs/assimp/code/AssetLib/M3D/M3DExporter.h new file mode 100644 index 0000000..d77743f --- /dev/null +++ b/libs/assimp/code/AssetLib/M3D/M3DExporter.h @@ -0,0 +1,93 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team +Copyright (c) 2019 bzt + +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 M3DExporter.h +* @brief Declares the exporter class to write a scene to a Model 3D file +*/ +#ifndef AI_M3DEXPORTER_H_INC +#define AI_M3DEXPORTER_H_INC + +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER +#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER + +#include <assimp/types.h> +#include <assimp/StreamWriter.h> // StreamWriterLE +#include <assimp/Exceptional.h> // DeadlyExportError + +#include <memory> // shared_ptr + +struct aiScene; +struct aiNode; +struct aiMaterial; +struct aiFace; + +namespace Assimp { + class IOSystem; + class IOStream; + class ExportProperties; + + class M3DWrapper; + + // --------------------------------------------------------------------- + /** Helper class to export a given scene to an M3D file. */ + // --------------------------------------------------------------------- + class M3DExporter { + public: + /// Constructor for a specific scene to export + M3DExporter(const aiScene* pScene, const ExportProperties* pProperties); + // call this to do the actual export + void doExport(const char* pFile, IOSystem* pIOSystem, bool toAscii); + + private: + const aiScene* mScene; // the scene to export + const ExportProperties* mProperties; // currently unused + std::shared_ptr<IOStream> outfile; // file to write to + + // helper to do the recursive walking + void NodeWalk(const M3DWrapper &m3d, const aiNode* pNode, aiMatrix4x4 m); + }; +} + +#endif // #ifndef ASSIMP_BUILD_NO_M3D_IMPORTER +#endif // ASSIMP_BUILD_NO_M3D_EXPORTER + +#endif // AI_M3DEXPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/M3D/M3DImporter.cpp b/libs/assimp/code/AssetLib/M3D/M3DImporter.cpp new file mode 100644 index 0000000..895b2bf --- /dev/null +++ b/libs/assimp/code/AssetLib/M3D/M3DImporter.cpp @@ -0,0 +1,789 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team +Copyright (c) 2019 bzt + +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. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER + +#define M3D_IMPLEMENTATION +#define M3D_NONORMALS /* leave the post-processing to Assimp */ +#define M3D_NOWEIGHTS +#define M3D_NOANIMATION + +#include <assimp/DefaultIOSystem.h> +#include <assimp/IOStreamBuffer.h> +#include <assimp/ai_assert.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> +#include <memory> + +#include "M3DImporter.h" +#include "M3DMaterials.h" +#include "M3DWrapper.h" + +// RESOURCES: +// https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md +// https://gitlab.com/bztsrc/model3d/blob/master/docs/a3d_format.md + +/* + Unfortunately aiNode has bone structures and meshes too, yet we can't assign + the mesh to a bone aiNode as a skin may refer to several aiNodes. Therefore + I've decided to import into this structure: + + aiScene->mRootNode + | |->mMeshes (all the meshes) + | \->children (empty if there's no skeleton imported, no meshes) + | \->skeleton root aiNode* + | |->bone aiNode + | | \->subbone aiNode + | |->bone aiNode + | | ... + | \->bone aiNode + \->mMeshes[] + \->aiBone, referencing mesh-less aiNodes from above + + * - normally one, but if a model has several skeleton roots, then all of them + are listed in aiScene->mRootNode->children, but all without meshes +*/ + +static const aiImporterDesc desc = { + "Model 3D Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "m3d a3d" +}; + +namespace Assimp { + +using namespace std; + +// ------------------------------------------------------------------------------------------------ +// Default constructor +M3DImporter::M3DImporter() : + mScene(nullptr) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns true, if file is a binary or ASCII Model 3D file. +bool M3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + // don't use CheckMagicToken because that checks with swapped bytes too, leading to false + // positives. This magic is not uint32_t, but char[4], so memcmp is the best way + std::unique_ptr<IOStream> pStream(pIOHandler->Open(pFile, "rb")); + unsigned char data[4]; + if (4 != pStream->Read(data, 1, 4)) { + return false; + } + return !memcmp(data, "3DMO", 4) /* bin */ +#ifdef M3D_ASCII + || !memcmp(data, "3dmo", 4) /* ASCII */ +#endif + ; +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc *M3DImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Model 3D import implementation +void M3DImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) { + // Read file into memory + std::unique_ptr<IOStream> pStream(pIOHandler->Open(file, "rb")); + if (!pStream.get()) { + throw DeadlyImportError("Failed to open file ", file, "."); + } + + // Get the file-size and validate it, throwing an exception when fails + size_t fileSize = pStream->FileSize(); + if (fileSize < 8) { + throw DeadlyImportError("M3D-file ", file, " is too small."); + } + std::vector<unsigned char> buffer(fileSize); + if (fileSize != pStream->Read(buffer.data(), 1, fileSize)) { + throw DeadlyImportError("Failed to read the file ", file, "."); + } + // extra check for binary format's first 8 bytes. Not done for the ASCII variant + if (!memcmp(buffer.data(), "3DMO", 4) && memcmp(buffer.data() + 4, &fileSize, 4)) { + throw DeadlyImportError("Bad binary header in file ", file, "."); + } + // make sure there's a terminator zero character, as input must be ASCIIZ + if (!memcmp(buffer.data(), "3dmo", 4)) { + buffer.push_back(0); + } + + // Get the path for external assets + std::string folderName("./"); + std::string::size_type pos = file.find_last_of("\\/"); + if (pos != std::string::npos) { + folderName = file.substr(0, pos); + if (!folderName.empty()) { + pIOHandler->PushDirectory(folderName); + } + } + + //DefaultLogger::create("/dev/stderr", Logger::VERBOSE); + ASSIMP_LOG_DEBUG("M3D: loading ", file); + + // let the C SDK do the hard work for us + M3DWrapper m3d(pIOHandler, buffer); + + if (!m3d) { + throw DeadlyImportError("Unable to parse ", file, " as M3D."); + } + + // create the root node + pScene->mRootNode = new aiNode; + pScene->mRootNode->mName = aiString(m3d.Name()); + pScene->mRootNode->mTransformation = aiMatrix4x4(); + pScene->mRootNode->mNumChildren = 0; + mScene = pScene; + + ASSIMP_LOG_DEBUG("M3D: root node ", m3d.Name()); + + // now we just have to fill up the Assimp structures in pScene + importMaterials(m3d); + importTextures(m3d); + importBones(m3d, M3D_NOTDEFINED, pScene->mRootNode); + importMeshes(m3d); + importAnimations(m3d); + + // Pop directory stack + if (pIOHandler->StackSize() > 0) { + pIOHandler->PopDirectory(); + } +} + +// ------------------------------------------------------------------------------------------------ +// convert materials. properties are converted using a static table in M3DMaterials.h +void M3DImporter::importMaterials(const M3DWrapper &m3d) { + unsigned int i, j, k, l, n; + m3dm_t *m; + aiString name = aiString(AI_DEFAULT_MATERIAL_NAME); + aiColor4D c; + ai_real f; + + ai_assert(mScene != nullptr); + ai_assert(m3d); + + mScene->mNumMaterials = m3d->nummaterial + 1; + mScene->mMaterials = new aiMaterial *[mScene->mNumMaterials]; + + ASSIMP_LOG_DEBUG("M3D: importMaterials ", mScene->mNumMaterials); + + // add a default material as first + aiMaterial *defaultMat = new aiMaterial; + defaultMat->AddProperty(&name, AI_MATKEY_NAME); + c.a = 1.0f; + c.b = c.g = c.r = 0.6f; + defaultMat->AddProperty(&c, 1, AI_MATKEY_COLOR_DIFFUSE); + mScene->mMaterials[0] = defaultMat; + + if (!m3d->nummaterial || !m3d->material) { + return; + } + + for (i = 0; i < m3d->nummaterial; i++) { + m = &m3d->material[i]; + aiMaterial *newMat = new aiMaterial; + name.Set(std::string(m->name)); + newMat->AddProperty(&name, AI_MATKEY_NAME); + for (j = 0; j < m->numprop; j++) { + // look up property type + // 0 - 127 scalar values, + // 128 - 255 the same properties but for texture maps + k = 256; + for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++) + if (m->prop[j].type == m3d_propertytypes[l].id || + m->prop[j].type == m3d_propertytypes[l].id + 128) { + k = l; + break; + } + // should never happen, but be safe than sorry + if (k == 256) + continue; + + // scalar properties + if (m->prop[j].type < 128 && aiProps[k].pKey) { + switch (m3d_propertytypes[k].format) { + case m3dpf_color: + c = mkColor(m->prop[j].value.color); + newMat->AddProperty(&c, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); + break; + case m3dpf_float: + f = m->prop[j].value.fnum; + newMat->AddProperty(&f, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); + break; + default: + n = m->prop[j].value.num; + if (m->prop[j].type == m3dp_il) { + switch (n) { + case 0: + n = aiShadingMode_NoShading; + break; + case 2: + n = aiShadingMode_Phong; + break; + default: + n = aiShadingMode_Gouraud; + break; + } + } + newMat->AddProperty(&n, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); + break; + } + } + // texture map properties + if (m->prop[j].type >= 128 && aiTxProps[k].pKey && + // extra check, should never happen, do we have the referred texture? + m->prop[j].value.textureid < m3d->numtexture && + m3d->texture[m->prop[j].value.textureid].name) { + name.Set(std::string(std::string(m3d->texture[m->prop[j].value.textureid].name) + ".png")); + newMat->AddProperty(&name, aiTxProps[k].pKey, aiTxProps[k].type, aiTxProps[k].index); + n = 0; + newMat->AddProperty(&n, 1, _AI_MATKEY_UVWSRC_BASE, aiProps[k].type, aiProps[k].index); + } + } + mScene->mMaterials[i + 1] = newMat; + } +} + +// ------------------------------------------------------------------------------------------------ +// import textures, this is the simplest of all +void M3DImporter::importTextures(const M3DWrapper &m3d) { + unsigned int i; + const char *formatHint[] = { + "rgba0800", + "rgba0808", + "rgba8880", + "rgba8888" + }; + m3dtx_t *t; + + ai_assert(mScene != nullptr); + ai_assert(m3d); + + mScene->mNumTextures = m3d->numtexture; + ASSIMP_LOG_DEBUG("M3D: importTextures ", mScene->mNumTextures); + + if (!m3d->numtexture || !m3d->texture) { + return; + } + + mScene->mTextures = new aiTexture *[m3d->numtexture]; + for (i = 0; i < m3d->numtexture; i++) { + unsigned int j, k; + t = &m3d->texture[i]; + aiTexture *tx = new aiTexture; + tx->mFilename = aiString(std::string(t->name) + ".png"); + if (!t->w || !t->h || !t->f || !t->d) { + /* without ASSIMP_USE_M3D_READFILECB, we only have the filename, but no texture data ever */ + tx->mWidth = 0; + tx->mHeight = 0; + memcpy(tx->achFormatHint, "png\000", 4); + tx->pcData = nullptr; + } else { + /* if we have the texture loaded, set format hint and pcData too */ + tx->mWidth = t->w; + tx->mHeight = t->h; + strcpy(tx->achFormatHint, formatHint[t->f - 1]); + tx->pcData = new aiTexel[tx->mWidth * tx->mHeight]; + for (j = k = 0; j < tx->mWidth * tx->mHeight; j++) { + switch (t->f) { + case 1: tx->pcData[j].g = t->d[k++]; break; + case 2: + tx->pcData[j].g = t->d[k++]; + tx->pcData[j].a = t->d[k++]; + break; + case 3: + tx->pcData[j].r = t->d[k++]; + tx->pcData[j].g = t->d[k++]; + tx->pcData[j].b = t->d[k++]; + tx->pcData[j].a = 255; + break; + case 4: + tx->pcData[j].r = t->d[k++]; + tx->pcData[j].g = t->d[k++]; + tx->pcData[j].b = t->d[k++]; + tx->pcData[j].a = t->d[k++]; + break; + } + } + } + mScene->mTextures[i] = tx; + } +} + +// ------------------------------------------------------------------------------------------------ +// this is tricky. M3D has a global vertex and UV list, and faces are indexing them +// individually. In assimp there're per mesh vertex and UV lists, and they must be +// indexed simultaneously. +void M3DImporter::importMeshes(const M3DWrapper &m3d) { + ASSIMP_LOG_DEBUG("M3D: importMeshes ", m3d->numface); + + if (!m3d->numface || !m3d->face || !m3d->numvertex || !m3d->vertex) { + return; + } + + unsigned int i, j, k, l, numpoly = 3, lastMat = M3D_INDEXMAX; + std::vector<aiMesh *> *meshes = new std::vector<aiMesh *>(); + std::vector<aiFace> *faces = nullptr; + std::vector<aiVector3D> *vertices = nullptr; + std::vector<aiVector3D> *normals = nullptr; + std::vector<aiVector3D> *texcoords = nullptr; + std::vector<aiColor4D> *colors = nullptr; + std::vector<unsigned int> *vertexids = nullptr; + aiMesh *pMesh = nullptr; + + ai_assert(mScene != nullptr); + ai_assert(m3d); + ai_assert(mScene->mRootNode != nullptr); + + for (i = 0; i < m3d->numface; i++) { + // we must switch mesh if material changes + if (lastMat != m3d->face[i].materialid) { + lastMat = m3d->face[i].materialid; + if (pMesh && vertices && vertices->size() && faces && faces->size()) { + populateMesh(m3d, pMesh, faces, vertices, normals, texcoords, colors, vertexids); + meshes->push_back(pMesh); + delete faces; + delete vertices; + delete normals; + delete texcoords; + delete colors; + delete vertexids; // this is not stored in pMesh, just to collect bone vertices + } + pMesh = new aiMesh; + pMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + pMesh->mMaterialIndex = lastMat + 1; + faces = new std::vector<aiFace>(); + vertices = new std::vector<aiVector3D>(); + normals = new std::vector<aiVector3D>(); + texcoords = new std::vector<aiVector3D>(); + colors = new std::vector<aiColor4D>(); + vertexids = new std::vector<unsigned int>(); + } + // add a face to temporary vector + aiFace *pFace = new aiFace; + pFace->mNumIndices = numpoly; + pFace->mIndices = new unsigned int[numpoly]; + for (j = 0; j < numpoly; j++) { + aiVector3D pos, uv, norm; + k = static_cast<unsigned int>(vertices->size()); + pFace->mIndices[j] = k; + l = m3d->face[i].vertex[j]; + if (l >= m3d->numvertex) continue; + pos.x = m3d->vertex[l].x; + pos.y = m3d->vertex[l].y; + pos.z = m3d->vertex[l].z; + vertices->push_back(pos); + colors->push_back(mkColor(m3d->vertex[l].color)); + // add a bone to temporary vector + if (m3d->vertex[l].skinid != M3D_UNDEF && m3d->vertex[l].skinid != M3D_INDEXMAX && m3d->skin && m3d->bone) { + // this is complicated, because M3D stores a list of bone id / weight pairs per + // vertex but assimp uses lists of local vertex id/weight pairs per local bone list + vertexids->push_back(l); + } + l = m3d->face[i].texcoord[j]; + if (l != M3D_UNDEF && l < m3d->numtmap) { + uv.x = m3d->tmap[l].u; + uv.y = m3d->tmap[l].v; + uv.z = 0.0; + texcoords->push_back(uv); + } + l = m3d->face[i].normal[j]; + if (l != M3D_UNDEF && l < m3d->numvertex) { + norm.x = m3d->vertex[l].x; + norm.y = m3d->vertex[l].y; + norm.z = m3d->vertex[l].z; + normals->push_back(norm); + } + } + faces->push_back(*pFace); + delete pFace; + } + // if there's data left in the temporary vectors, flush them + if (pMesh && vertices->size() && faces->size()) { + populateMesh(m3d, pMesh, faces, vertices, normals, texcoords, colors, vertexids); + meshes->push_back(pMesh); + } + + // create global mesh list in scene + mScene->mNumMeshes = static_cast<unsigned int>(meshes->size()); + mScene->mMeshes = new aiMesh *[mScene->mNumMeshes]; + std::copy(meshes->begin(), meshes->end(), mScene->mMeshes); + + // create mesh indices in root node + mScene->mRootNode->mNumMeshes = static_cast<unsigned int>(meshes->size()); + mScene->mRootNode->mMeshes = new unsigned int[meshes->size()]; + for (i = 0; i < meshes->size(); i++) { + mScene->mRootNode->mMeshes[i] = i; + } + + delete meshes; + if (faces) delete faces; + if (vertices) delete vertices; + if (normals) delete normals; + if (texcoords) delete texcoords; + if (colors) delete colors; + if (vertexids) delete vertexids; +} + +// ------------------------------------------------------------------------------------------------ +// a reentrant node parser. Otherwise this is simple +void M3DImporter::importBones(const M3DWrapper &m3d, unsigned int parentid, aiNode *pParent) { + unsigned int i, n; + + ai_assert(pParent != nullptr); + ai_assert(mScene != nullptr); + ai_assert(m3d); + + ASSIMP_LOG_DEBUG("M3D: importBones ", m3d->numbone, " parentid ", (int)parentid); + + if (!m3d->numbone || !m3d->bone) { + return; + } + + for (n = 0, i = parentid + 1; i < m3d->numbone; i++) { + if (m3d->bone[i].parent == parentid) { + n++; + } + } + pParent->mChildren = new aiNode *[n]; + + for (i = parentid + 1; i < m3d->numbone; i++) { + if (m3d->bone[i].parent == parentid) { + aiNode *pChild = new aiNode; + pChild->mParent = pParent; + pChild->mName = aiString(std::string(m3d->bone[i].name)); + convertPose(m3d, &pChild->mTransformation, m3d->bone[i].pos, m3d->bone[i].ori); + pChild->mNumChildren = 0; + pParent->mChildren[pParent->mNumChildren] = pChild; + pParent->mNumChildren++; + importBones(m3d, i, pChild); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// this is another headache. M3D stores list of changed bone id/position/orientation triplets and +// a timestamp per frame, but assimp needs timestamp and lists of position, orientation lists per +// bone, so we have to convert between the two conceptually different representation forms +void M3DImporter::importAnimations(const M3DWrapper &m3d) { + unsigned int i, j, k, l, pos, ori; + double t; + m3da_t *a; + + ai_assert(mScene != nullptr); + ai_assert(m3d); + + mScene->mNumAnimations = m3d->numaction; + + ASSIMP_LOG_DEBUG("M3D: importAnimations ", mScene->mNumAnimations); + + if (!m3d->numaction || !m3d->action || !m3d->numbone || !m3d->bone || !m3d->vertex) { + return; + } + + mScene->mAnimations = new aiAnimation *[m3d->numaction]; + for (i = 0; i < m3d->numaction; i++) { + a = &m3d->action[i]; + aiAnimation *pAnim = new aiAnimation; + pAnim->mName = aiString(std::string(a->name)); + pAnim->mDuration = ((double)a->durationmsec) / 10; + pAnim->mTicksPerSecond = 100; + // now we know how many bones are referenced in this animation + pAnim->mNumChannels = m3d->numbone; + pAnim->mChannels = new aiNodeAnim *[pAnim->mNumChannels]; + for (l = 0; l < m3d->numbone; l++) { + unsigned int n; + pAnim->mChannels[l] = new aiNodeAnim; + pAnim->mChannels[l]->mNodeName = aiString(std::string(m3d->bone[l].name)); + // now n is the size of positions / orientations arrays + pAnim->mChannels[l]->mNumPositionKeys = pAnim->mChannels[l]->mNumRotationKeys = a->numframe; + pAnim->mChannels[l]->mPositionKeys = new aiVectorKey[a->numframe]; + pAnim->mChannels[l]->mRotationKeys = new aiQuatKey[a->numframe]; + pos = m3d->bone[l].pos; + ori = m3d->bone[l].ori; + for (j = n = 0; j < a->numframe; j++) { + t = ((double)a->frame[j].msec) / 10; + for (k = 0; k < a->frame[j].numtransform; k++) { + if (a->frame[j].transform[k].boneid == l) { + pos = a->frame[j].transform[k].pos; + ori = a->frame[j].transform[k].ori; + } + } + if (pos >= m3d->numvertex || ori >= m3d->numvertex) continue; + m3dv_t *v = &m3d->vertex[pos]; + m3dv_t *q = &m3d->vertex[ori]; + pAnim->mChannels[l]->mPositionKeys[j].mTime = t; + pAnim->mChannels[l]->mPositionKeys[j].mValue.x = v->x; + pAnim->mChannels[l]->mPositionKeys[j].mValue.y = v->y; + pAnim->mChannels[l]->mPositionKeys[j].mValue.z = v->z; + pAnim->mChannels[l]->mRotationKeys[j].mTime = t; + pAnim->mChannels[l]->mRotationKeys[j].mValue.w = q->w; + pAnim->mChannels[l]->mRotationKeys[j].mValue.x = q->x; + pAnim->mChannels[l]->mRotationKeys[j].mValue.y = q->y; + pAnim->mChannels[l]->mRotationKeys[j].mValue.z = q->z; + } // foreach frame + } // foreach bones + mScene->mAnimations[i] = pAnim; + } +} + +// ------------------------------------------------------------------------------------------------ +// convert uint32_t into aiColor4D +aiColor4D M3DImporter::mkColor(uint32_t c) { + aiColor4D color; + color.a = ((float)((c >> 24) & 0xff)) / 255; + color.b = ((float)((c >> 16) & 0xff)) / 255; + color.g = ((float)((c >> 8) & 0xff)) / 255; + color.r = ((float)((c >> 0) & 0xff)) / 255; + return color; +} + +// ------------------------------------------------------------------------------------------------ +// convert a position id and orientation id into a 4 x 4 transformation matrix +void M3DImporter::convertPose(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned int posid, unsigned int orientid) { + ai_assert(m != nullptr); + ai_assert(m3d); + ai_assert(posid != M3D_UNDEF); + ai_assert(posid < m3d->numvertex); + ai_assert(orientid != M3D_UNDEF); + ai_assert(orientid < m3d->numvertex); + if (!m3d->numvertex || !m3d->vertex) + return; + m3dv_t *p = &m3d->vertex[posid]; + m3dv_t *q = &m3d->vertex[orientid]; + + /* quaternion to matrix. Do NOT use aiQuaternion to aiMatrix3x3, gives bad results */ + if (q->x == 0.0 && q->y == 0.0 && q->z >= 0.7071065 && q->z <= 0.7071075 && q->w == 0.0) { + m->a2 = m->a3 = m->b1 = m->b3 = m->c1 = m->c2 = 0.0; + m->a1 = m->b2 = m->c3 = -1.0; + } else { + m->a1 = 1 - 2 * (q->y * q->y + q->z * q->z); + if (m->a1 > -M3D_EPSILON && m->a1 < M3D_EPSILON) m->a1 = 0.0; + m->a2 = 2 * (q->x * q->y - q->z * q->w); + if (m->a2 > -M3D_EPSILON && m->a2 < M3D_EPSILON) m->a2 = 0.0; + m->a3 = 2 * (q->x * q->z + q->y * q->w); + if (m->a3 > -M3D_EPSILON && m->a3 < M3D_EPSILON) m->a3 = 0.0; + m->b1 = 2 * (q->x * q->y + q->z * q->w); + if (m->b1 > -M3D_EPSILON && m->b1 < M3D_EPSILON) m->b1 = 0.0; + m->b2 = 1 - 2 * (q->x * q->x + q->z * q->z); + if (m->b2 > -M3D_EPSILON && m->b2 < M3D_EPSILON) m->b2 = 0.0; + m->b3 = 2 * (q->y * q->z - q->x * q->w); + if (m->b3 > -M3D_EPSILON && m->b3 < M3D_EPSILON) m->b3 = 0.0; + m->c1 = 2 * (q->x * q->z - q->y * q->w); + if (m->c1 > -M3D_EPSILON && m->c1 < M3D_EPSILON) m->c1 = 0.0; + m->c2 = 2 * (q->y * q->z + q->x * q->w); + if (m->c2 > -M3D_EPSILON && m->c2 < M3D_EPSILON) m->c2 = 0.0; + m->c3 = 1 - 2 * (q->x * q->x + q->y * q->y); + if (m->c3 > -M3D_EPSILON && m->c3 < M3D_EPSILON) m->c3 = 0.0; + } + + /* set translation */ + m->a4 = p->x; + m->b4 = p->y; + m->c4 = p->z; + + m->d1 = 0; + m->d2 = 0; + m->d3 = 0; + m->d4 = 1; +} + +// ------------------------------------------------------------------------------------------------ +// find a node by name +aiNode *M3DImporter::findNode(aiNode *pNode, const aiString &name) { + ai_assert(pNode != nullptr); + ai_assert(mScene != nullptr); + + if (pNode->mName == name) { + return pNode; + } + + for (unsigned int i = 0; i < pNode->mNumChildren; i++) { + aiNode *pChild = findNode(pNode->mChildren[i], name); + if (pChild) { + return pChild; + } + } + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// fills up offsetmatrix in mBones +void M3DImporter::calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m) { + ai_assert(pNode != nullptr); + ai_assert(mScene != nullptr); + + if (pNode->mParent) { + calculateOffsetMatrix(pNode->mParent, m); + *m *= pNode->mTransformation; + } else { + *m = pNode->mTransformation; + } +} + +// ------------------------------------------------------------------------------------------------ +// because M3D has a global mesh, global vertex ids and stores materialid on the face, we need +// temporary lists to collect data for an aiMesh, which requires local arrays and local indices +// this function fills up an aiMesh with those temporary lists +void M3DImporter::populateMesh(const M3DWrapper &m3d, aiMesh *pMesh, std::vector<aiFace> *faces, std::vector<aiVector3D> *vertices, + std::vector<aiVector3D> *normals, std::vector<aiVector3D> *texcoords, std::vector<aiColor4D> *colors, + std::vector<unsigned int> *vertexids) { + + ai_assert(pMesh != nullptr); + ai_assert(faces != nullptr); + ai_assert(vertices != nullptr); + ai_assert(normals != nullptr); + ai_assert(texcoords != nullptr); + ai_assert(colors != nullptr); + ai_assert(vertexids != nullptr); + ai_assert(m3d); + + ASSIMP_LOG_DEBUG("M3D: populateMesh numvertices ", vertices->size(), " numfaces ", faces->size(), + " numnormals ", normals->size(), " numtexcoord ", texcoords->size(), " numbones ", m3d->numbone); + + if (vertices->size() && faces->size()) { + pMesh->mNumFaces = static_cast<unsigned int>(faces->size()); + pMesh->mFaces = new aiFace[pMesh->mNumFaces]; + std::copy(faces->begin(), faces->end(), pMesh->mFaces); + pMesh->mNumVertices = static_cast<unsigned int>(vertices->size()); + pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; + std::copy(vertices->begin(), vertices->end(), pMesh->mVertices); + if (normals->size() == vertices->size()) { + pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + std::copy(normals->begin(), normals->end(), pMesh->mNormals); + } + if (texcoords->size() == vertices->size()) { + pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices]; + std::copy(texcoords->begin(), texcoords->end(), pMesh->mTextureCoords[0]); + pMesh->mNumUVComponents[0] = 2; + } + if (colors->size() == vertices->size()) { + pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices]; + std::copy(colors->begin(), colors->end(), pMesh->mColors[0]); + } + // this is complicated, because M3D stores a list of bone id / weight pairs per + // vertex but assimp uses lists of local vertex id/weight pairs per local bone list + pMesh->mNumBones = m3d->numbone; + // we need aiBone with mOffsetMatrix for bones without weights as well + if (pMesh->mNumBones && m3d->numbone && m3d->bone) { + pMesh->mBones = new aiBone *[pMesh->mNumBones]; + for (unsigned int i = 0; i < m3d->numbone; i++) { + aiNode *pNode; + pMesh->mBones[i] = new aiBone; + pMesh->mBones[i]->mName = aiString(std::string(m3d->bone[i].name)); + pMesh->mBones[i]->mNumWeights = 0; + pNode = findNode(mScene->mRootNode, pMesh->mBones[i]->mName); + if (pNode) { + calculateOffsetMatrix(pNode, &pMesh->mBones[i]->mOffsetMatrix); + pMesh->mBones[i]->mOffsetMatrix.Inverse(); + } else + pMesh->mBones[i]->mOffsetMatrix = aiMatrix4x4(); + } + if (vertexids->size() && m3d->numvertex && m3d->vertex && m3d->numskin && m3d->skin) { + unsigned int i, j; + // first count how many vertices we have per bone + for (i = 0; i < vertexids->size(); i++) { + if (vertexids->at(i) >= m3d->numvertex) { + continue; + } + unsigned int s = m3d->vertex[vertexids->at(i)].skinid; + if (s != M3D_UNDEF && s != M3D_INDEXMAX) { + for (unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) { + aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name)); + for (j = 0; j < pMesh->mNumBones; j++) { + if (pMesh->mBones[j]->mName == name) { + pMesh->mBones[j]->mNumWeights++; + break; + } + } + } + } + } + // allocate mWeights + for (j = 0; j < pMesh->mNumBones; j++) { + aiBone *pBone = pMesh->mBones[j]; + if (pBone->mNumWeights) { + pBone->mWeights = new aiVertexWeight[pBone->mNumWeights]; + pBone->mNumWeights = 0; + } + } + // fill up with data + for (i = 0; i < vertexids->size(); i++) { + if (vertexids->at(i) >= m3d->numvertex) continue; + unsigned int s = m3d->vertex[vertexids->at(i)].skinid; + if (s != M3D_UNDEF && s != M3D_INDEXMAX && s < m3d->numskin) { + for (unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) { + if (m3d->skin[s].boneid[k] >= m3d->numbone) continue; + aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name)); + for (j = 0; j < pMesh->mNumBones; j++) { + if (pMesh->mBones[j]->mName == name) { + aiBone *pBone = pMesh->mBones[j]; + pBone->mWeights[pBone->mNumWeights].mVertexId = i; + pBone->mWeights[pBone->mNumWeights].mWeight = m3d->skin[s].weight[k]; + pBone->mNumWeights++; + break; + } + } + } // foreach skin + } + } // foreach vertexids + } + } + } +} + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_M3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/M3D/M3DImporter.h b/libs/assimp/code/AssetLib/M3D/M3DImporter.h new file mode 100644 index 0000000..5d3fcaa --- /dev/null +++ b/libs/assimp/code/AssetLib/M3D/M3DImporter.h @@ -0,0 +1,105 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team +Copyright (c) 2019 bzt + +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 M3DImporter.h +* @brief Declares the importer class to read a scene from a Model 3D file +*/ +#ifndef AI_M3DIMPORTER_H_INC +#define AI_M3DIMPORTER_H_INC + +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER + +#include <assimp/BaseImporter.h> +#include <assimp/material.h> +#include <vector> + +struct aiMesh; +struct aiNode; +struct aiMaterial; +struct aiFace; + +namespace Assimp { + +class M3DWrapper; + +class M3DImporter : public BaseImporter { +public: + /// \brief Default constructor + M3DImporter(); + ~M3DImporter() override {} + + /// \brief Returns whether the class can handle the format of the given file. + /// \remark See BaseImporter::CanRead() for details. + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + +protected: + //! \brief Appends the supported extension. + const aiImporterDesc *GetInfo() const override; + + //! \brief File import implementation. + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + +private: + void importMaterials(const M3DWrapper &m3d); + void importTextures(const M3DWrapper &m3d); + void importMeshes(const M3DWrapper &m3d); + void importBones(const M3DWrapper &m3d, unsigned int parentid, aiNode *pParent); + void importAnimations(const M3DWrapper &m3d); + + // helper functions + aiColor4D mkColor(uint32_t c); + void convertPose(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned int posid, unsigned int orientid); + aiNode *findNode(aiNode *pNode, const aiString &name); + void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m); + void populateMesh(const M3DWrapper &m3d, aiMesh *pMesh, std::vector<aiFace> *faces, std::vector<aiVector3D> *verteces, + std::vector<aiVector3D> *normals, std::vector<aiVector3D> *texcoords, std::vector<aiColor4D> *colors, + std::vector<unsigned int> *vertexids); + +private: + aiScene *mScene = nullptr; // the scene to import to +}; + +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_M3D_IMPORTER + +#endif // AI_M3DIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/M3D/M3DMaterials.h b/libs/assimp/code/AssetLib/M3D/M3DMaterials.h new file mode 100644 index 0000000..a1b0fd7 --- /dev/null +++ b/libs/assimp/code/AssetLib/M3D/M3DMaterials.h @@ -0,0 +1,106 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team +Copyright (c) 2019 bzt + +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 M3DMaterials.h +* @brief Declares the Assimp and Model 3D file material type relations +*/ +#ifndef AI_M3DMATERIALS_H_INC +#define AI_M3DMATERIALS_H_INC + +/* + * In the m3d.h header, there's a static array which defines the material + * properties, called m3d_propertytypes. These must have the same size, and + * list the matching Assimp materials for those properties. Used by both the + * M3DImporter and the M3DExporter, so you have to define these relations + * only once. D.R.Y. and K.I.S.S. + */ +typedef struct { + const char *pKey; + unsigned int type; + unsigned int index; +} aiMatProp; + +/* --- Scalar Properties --- !!!!! must match m3d_propertytypes !!!!! */ +static const aiMatProp aiProps[] = { + { AI_MATKEY_COLOR_DIFFUSE }, /* m3dp_Kd */ + { AI_MATKEY_COLOR_AMBIENT }, /* m3dp_Ka */ + { AI_MATKEY_COLOR_SPECULAR }, /* m3dp_Ks */ + { AI_MATKEY_SHININESS }, /* m3dp_Ns */ + { AI_MATKEY_COLOR_EMISSIVE }, /* m3dp_Ke */ + { AI_MATKEY_COLOR_REFLECTIVE }, /* m3dp_Tf */ + { AI_MATKEY_BUMPSCALING }, /* m3dp_Km */ + { AI_MATKEY_OPACITY }, /* m3dp_d */ + { AI_MATKEY_SHADING_MODEL }, /* m3dp_il */ + + { nullptr, 0, 0 }, /* m3dp_Pr */ + { AI_MATKEY_REFLECTIVITY }, /* m3dp_Pm */ + { nullptr, 0, 0 }, /* m3dp_Ps */ + { AI_MATKEY_REFRACTI }, /* m3dp_Ni */ + { nullptr, 0, 0 }, /* m3dp_Nt */ + { nullptr, 0, 0 }, + { nullptr, 0, 0 }, + { nullptr, 0, 0 } +}; + +/* --- Texture Map Properties --- !!!!! must match m3d_propertytypes !!!!! */ +static const aiMatProp aiTxProps[] = { + { AI_MATKEY_TEXTURE_DIFFUSE(0) }, /* m3dp_map_Kd */ + { AI_MATKEY_TEXTURE(aiTextureType_AMBIENT_OCCLUSION,0) },/* m3dp_map_Ka */ + { AI_MATKEY_TEXTURE_SPECULAR(0) }, /* m3dp_map_Ks */ + { AI_MATKEY_TEXTURE_SHININESS(0) }, /* m3dp_map_Ns */ + { AI_MATKEY_TEXTURE_EMISSIVE(0) }, /* m3dp_map_Ke */ + { nullptr, 0, 0 }, /* m3dp_map_Tf */ + { AI_MATKEY_TEXTURE_HEIGHT(0) }, /* m3dp_bump */ + { AI_MATKEY_TEXTURE_OPACITY(0) }, /* m3dp_map_d */ + { AI_MATKEY_TEXTURE_NORMALS(0) }, /* m3dp_map_N */ + + { AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE_ROUGHNESS,0) },/* m3dp_map_Pr */ + { AI_MATKEY_TEXTURE(aiTextureType_METALNESS,0) }, /* m3dp_map_Pm */ + { nullptr, 0, 0 }, /* m3dp_map_Ps */ + { AI_MATKEY_TEXTURE(aiTextureType_REFLECTION,0) }, /* m3dp_map_Ni */ + { nullptr, 0, 0 }, /* m3dp_map_Nt */ + { nullptr, 0, 0 }, + { nullptr, 0, 0 }, + { nullptr, 0, 0 } +}; + +#endif // AI_M3DMATERIALS_H_INC diff --git a/libs/assimp/code/AssetLib/M3D/M3DWrapper.cpp b/libs/assimp/code/AssetLib/M3D/M3DWrapper.cpp new file mode 100644 index 0000000..30452c7 --- /dev/null +++ b/libs/assimp/code/AssetLib/M3D/M3DWrapper.cpp @@ -0,0 +1,152 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team +Copyright (c) 2019 bzt + +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. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER +#if !(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER) + +#include "M3DWrapper.h" + +#include <assimp/DefaultIOSystem.h> +#include <assimp/IOStreamBuffer.h> +#include <assimp/ai_assert.h> + +#ifdef ASSIMP_USE_M3D_READFILECB + +#if (__cplusplus >= 201103L) || !defined(_MSC_VER) || (_MSC_VER >= 1900) // C++11 and MSVC 2015 onwards +#define threadlocal thread_local +#else +#if defined(_MSC_VER) && (_MSC_VER >= 1800) // there's an alternative for MSVC 2013 +#define threadlocal __declspec(thread) +#else +#define threadlocal +#endif +#endif + +extern "C" { + +// workaround: the M3D SDK expects a C callback, but we want to use Assimp::IOSystem to implement that +threadlocal void *m3dimporter_pIOHandler; + +unsigned char *m3dimporter_readfile(char *fn, unsigned int *size) { + ai_assert(nullptr != fn); + ai_assert(nullptr != size); + std::string file(fn); + std::unique_ptr<Assimp::IOStream> pStream( + (reinterpret_cast<Assimp::IOSystem *>(m3dimporter_pIOHandler))->Open(file, "rb")); + size_t fileSize = 0; + unsigned char *data = nullptr; + // sometimes pStream is nullptr in a single-threaded scenario too for some reason + // (should be an empty object returning nothing I guess) + if (pStream) { + fileSize = pStream->FileSize(); + // should be allocated with malloc(), because the library will call free() to deallocate + data = (unsigned char *)malloc(fileSize); + if (!data || !pStream.get() || !fileSize || fileSize != pStream->Read(data, 1, fileSize)) { + pStream.reset(); + *size = 0; + // don't throw a deadly exception, it's not fatal if we can't read an external asset + return nullptr; + } + pStream.reset(); + } + *size = (int)fileSize; + return data; +} +} +#endif + +namespace Assimp { +M3DWrapper::M3DWrapper() { + // use malloc() here because m3d_free() will call free() + m3d_ = (m3d_t *)calloc(1, sizeof(m3d_t)); +} + +M3DWrapper::M3DWrapper(IOSystem *pIOHandler, const std::vector<unsigned char> &buffer) { + if (nullptr == pIOHandler) { + ai_assert(nullptr != pIOHandler); + } + +#ifdef ASSIMP_USE_M3D_READFILECB + // pass this IOHandler to the C callback in a thread-local pointer + m3dimporter_pIOHandler = pIOHandler; + m3d_ = m3d_load(const_cast<unsigned char *>(buffer.data()), m3dimporter_readfile, free, nullptr); + // Clear the C callback + m3dimporter_pIOHandler = nullptr; +#else + m3d_ = m3d_load(const_cast<unsigned char *>(buffer.data()), nullptr, nullptr, nullptr); +#endif +} + +M3DWrapper::~M3DWrapper() { + reset(); +} + +void M3DWrapper::reset() { + ClearSave(); + if (m3d_) { + m3d_free(m3d_); + } + m3d_ = nullptr; +} + +unsigned char *M3DWrapper::Save(int quality, int flags, unsigned int &size) { +#if (!(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER)) + ClearSave(); + saved_output_ = m3d_save(m3d_, quality, flags, &size); + return saved_output_; +#else + (void)quality; + (void)flags; + (void)size; + return nullptr; +#endif +} + +void M3DWrapper::ClearSave() { + if (saved_output_) { + M3D_FREE(saved_output_); + } + saved_output_ = nullptr; +} +} // namespace Assimp + +#endif +#endif diff --git a/libs/assimp/code/AssetLib/M3D/M3DWrapper.h b/libs/assimp/code/AssetLib/M3D/M3DWrapper.h new file mode 100644 index 0000000..7cc2d0e --- /dev/null +++ b/libs/assimp/code/AssetLib/M3D/M3DWrapper.h @@ -0,0 +1,134 @@ +#pragma once +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team +Copyright (c) 2019 bzt + +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 M3DWrapper.h +* @brief Declares a class to wrap the C m3d SDK +*/ +#ifndef AI_M3DWRAPPER_H_INC +#define AI_M3DWRAPPER_H_INC + +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER +#if !(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER) + +#include <memory> +#include <vector> +#include <string> + +// Assimp specific M3D configuration. Comment out these defines to remove functionality +//#define ASSIMP_USE_M3D_READFILECB + +// Share stb_image's PNG loader with other importers/exporters instead of bringing our own copy. +#define STBI_ONLY_PNG +#include <stb/stb_image.h> + +#include "m3d.h" + +namespace Assimp { + +class IOSystem; + +/// brief The M3D-Wrapper, provudes c++ access to the data. +class M3DWrapper { +public: + /// Construct an empty M3D model + explicit M3DWrapper(); + + /// Construct an M3D model from provided buffer + /// @note The m3d.h SDK function does not mark the data as const. Have assumed it does not write. + /// BUG: SECURITY: The m3d.h SDK cannot be informed of the buffer size. BUFFER OVERFLOW IS CERTAIN + explicit M3DWrapper(IOSystem *pIOHandler, const std::vector<unsigned char> &buffer); + + /// Theclasss destructor. + ~M3DWrapper(); + + /// Will reset the wrapper, all data will become nullptr. + void reset(); + + // The Name access, empty string returned when no m3d instance. + std::string Name() const; + + /// Executes a save. + unsigned char *Save(int quality, int flags, unsigned int &size); + + /// Clearer + void ClearSave(); + + /// True for m3d instance exists. + explicit operator bool() const; + + // Allow direct access to M3D API + m3d_t *operator->() const; + m3d_t *M3D() const; + +private: + m3d_t *m3d_ = nullptr; + unsigned char *saved_output_ = nullptr; +}; + +inline std::string M3DWrapper::Name() const { + if (nullptr != m3d_) { + if (nullptr != m3d_->name) { + return std::string(m3d_->name); + } + } + return std::string(); +} + +inline M3DWrapper::operator bool() const { + return m3d_ != nullptr; +} + +inline m3d_t *M3DWrapper::operator->() const { + return m3d_; +} + +inline m3d_t *M3DWrapper::M3D() const { + return m3d_; +} + +} // namespace Assimp + +#endif +#endif // ASSIMP_BUILD_NO_M3D_IMPORTER + +#endif // AI_M3DWRAPPER_H_INC diff --git a/libs/assimp/code/AssetLib/M3D/m3d.h b/libs/assimp/code/AssetLib/M3D/m3d.h new file mode 100644 index 0000000..875007e --- /dev/null +++ b/libs/assimp/code/AssetLib/M3D/m3d.h @@ -0,0 +1,4902 @@ +/* + * m3d.h + * + * Copyright (C) 2019 bzt (bztsrc@gitlab) + * + * 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. + * + * @brief ANSI C89 / C++11 single header importer / exporter SDK for the Model 3D (.M3D) format + * https://gitlab.com/bztsrc/model3d + * + * PNG decompressor included from (with minor modifications to make it C89 valid): + * stb_image - v2.13 - public domain image loader - http://nothings.org/stb_image.h + * + * @version: 1.0.0 + */ + +#ifndef _M3D_H_ +#define _M3D_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> + +/*** configuration ***/ +#ifndef M3D_MALLOC +#define M3D_MALLOC(sz) malloc(sz) +#endif +#ifndef M3D_REALLOC +#define M3D_REALLOC(p, nsz) realloc(p, nsz) +#endif +#ifndef M3D_FREE +#define M3D_FREE(p) free(p) +#endif +#ifndef M3D_LOG +#define M3D_LOG(x) +#endif +#ifndef M3D_APIVERSION +#define M3D_APIVERSION 0x0100 +#ifndef M3D_DOUBLE +typedef float M3D_FLOAT; +#ifndef M3D_EPSILON +/* carefully chosen for IEEE 754 don't change */ +#define M3D_EPSILON ((M3D_FLOAT)1e-7) +#endif +#else +typedef double M3D_FLOAT; +#ifndef M3D_EPSILON +#define M3D_EPSILON ((M3D_FLOAT)1e-14) +#endif +#endif +#if !defined(M3D_SMALLINDEX) +typedef uint32_t M3D_INDEX; +#define M3D_UNDEF 0xffffffff +#define M3D_INDEXMAX 0xfffffffe +#else +typedef uint16_t M3D_INDEX; +#define M3D_UNDEF 0xffff +#define M3D_INDEXMAX 0xfffe +#endif +#define M3D_NOTDEFINED 0xffffffff +#ifndef M3D_NUMBONE +#define M3D_NUMBONE 4 +#endif +#ifndef M3D_BONEMAXLEVEL +#define M3D_BONEMAXLEVEL 8 +#endif +#if !defined(_MSC_VER) || defined(__clang__) +#ifndef _inline +#define _inline __inline__ +#endif +#define _pack __attribute__((packed)) +#define _unused __attribute__((unused)) +#else +#define _inline +#define _pack +#define _unused __pragma(warning(suppress : 4100)) +#endif +#ifndef __cplusplus +#define _register register +#else +#define _register +#endif + +#if _MSC_VER > 1920 && !defined(__clang__) +# pragma warning(push) +# pragma warning(disable : 4100 4127 4189 4505 4244 4403 4701 4703) +# if (_MSC_VER > 1800 ) +# pragma warning(disable : 5573 5744) +# endif +#endif // _MSC_VER + +/*** File format structures ***/ + +/** + * M3D file format structure + * 3DMO m3dchunk_t file header chunk, may followed by compressed data + * HEAD m3dhdr_t model header chunk + * n x m3dchunk_t more chunks follow + * PRVW preview chunk (optional) + * CMAP color map chunk (optional) + * TMAP texture map chunk (optional) + * VRTS vertex data chunk (optional if it's a material library) + * BONE bind-pose skeleton, bone hierarchy chunk (optional) + * n x m3db_t contains probably more, but at least one bone + * n x m3ds_t skin group records + * MTRL* material chunk(s), can be more (optional) + * n x m3dp_t each material contains propapbly more, but at least one property + * the properties are configurable with a static array, see m3d_propertytypes + * n x m3dchunk_t at least one, but maybe more face chunks + * PROC* procedural face, or + * MESH* triangle mesh (vertex index list) or + * SHPE* mathematical shapes like parameterized surfaces + * LBLS* annotation label chunks, can be more (optional) + * ACTN* action chunk(s), animation-pose skeletons, can be more (optional) + * n x m3dfr_t each action contains probably more, but at least one frame + * n x m3dtr_t each frame contains probably more, but at least one transformation + * ASET* inlined asset chunk(s), can be more (optional) + * OMD3 end chunk + * + * Typical chunks for a game engine: 3DMO, HEAD, CMAP, TMAP, VRTS, BONE, MTRL, MESH, ACTN, OMD3 + * Typical chunks for CAD software: 3DMO, HEAD, PRVW, CMAP, TMAP, VRTS, MTRL, SHPE, LBLS, OMD3 + */ +#ifdef _MSC_VER +#pragma pack(push) +#pragma pack(1) +#endif + +typedef struct { + char magic[4]; + uint32_t length; + float scale; /* deliberately not M3D_FLOAT */ + uint32_t types; +} _pack m3dhdr_t; + +typedef struct { + char magic[4]; + uint32_t length; +} _pack m3dchunk_t; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif + +/*** in-memory model structure ***/ + +/* textmap entry */ +typedef struct { + M3D_FLOAT u; + M3D_FLOAT v; +} m3dti_t; +#define m3d_textureindex_t m3dti_t + +/* texture */ +typedef struct { + char *name; /* texture name */ + uint8_t *d; /* pixels data */ + uint16_t w; /* width */ + uint16_t h; /* height */ + uint8_t f; /* format, 1 = grayscale, 2 = grayscale+alpha, 3 = rgb, 4 = rgba */ +} m3dtx_t; +#define m3d_texturedata_t m3dtx_t + +typedef struct { + M3D_INDEX vertexid; + M3D_FLOAT weight; +} m3dw_t; +#define m3d_weight_t m3dw_t + +/* bone entry */ +typedef struct { + M3D_INDEX parent; /* parent bone index */ + char *name; /* name for this bone */ + M3D_INDEX pos; /* vertex index position */ + M3D_INDEX ori; /* vertex index orientation (quaternion) */ + M3D_INDEX numweight; /* number of controlled vertices */ + m3dw_t *weight; /* weights for those vertices */ + M3D_FLOAT mat4[16]; /* transformation matrix */ +} m3db_t; +#define m3d_bone_t m3db_t + +/* skin: bone per vertex entry */ +typedef struct { + M3D_INDEX boneid[M3D_NUMBONE]; + M3D_FLOAT weight[M3D_NUMBONE]; +} m3ds_t; +#define m3d_skin_t m3ds_t + +/* vertex entry */ +typedef struct { + M3D_FLOAT x; /* 3D coordinates and weight */ + M3D_FLOAT y; + M3D_FLOAT z; + M3D_FLOAT w; + uint32_t color; /* default vertex color */ + M3D_INDEX skinid; /* skin index */ +#ifdef M3D_VERTEXTYPE + uint8_t type; +#endif +} m3dv_t; +#define m3d_vertex_t m3dv_t + +/* material property formats */ +enum { + m3dpf_color, + m3dpf_uint8, + m3dpf_uint16, + m3dpf_uint32, + m3dpf_float, + m3dpf_map +}; +typedef struct { + uint8_t format; + uint8_t id; +#define M3D_PROPERTYDEF(f, i, n) \ + { (f), (i), (char *)(n) } + char *key; +} m3dpd_t; + +/* material property types */ +/* You shouldn't change the first 8 display and first 4 physical property. Assign the rest as you like. */ +enum { + m3dp_Kd = 0, /* scalar display properties */ + m3dp_Ka, + m3dp_Ks, + m3dp_Ns, + m3dp_Ke, + m3dp_Tf, + m3dp_Km, + m3dp_d, + m3dp_il, + + m3dp_Pr = 64, /* scalar physical properties */ + m3dp_Pm, + m3dp_Ps, + m3dp_Ni, + m3dp_Nt, + + m3dp_map_Kd = 128, /* textured display map properties */ + m3dp_map_Ka, + m3dp_map_Ks, + m3dp_map_Ns, + m3dp_map_Ke, + m3dp_map_Tf, + m3dp_map_Km, /* bump map */ + m3dp_map_D, + m3dp_map_N, /* normal map */ + + m3dp_map_Pr = 192, /* textured physical map properties */ + m3dp_map_Pm, + m3dp_map_Ps, + m3dp_map_Ni, + m3dp_map_Nt +}; +enum { /* aliases */ + m3dp_bump = m3dp_map_Km, + m3dp_map_il = m3dp_map_N, + m3dp_refl = m3dp_map_Pm +}; + +/* material property */ +typedef struct { + uint8_t type; /* property type, see "m3dp_*" enumeration */ + union { + uint32_t color; /* if value is a color, m3dpf_color */ + uint32_t num; /* if value is a number, m3dpf_uint8, m3pf_uint16, m3dpf_uint32 */ + float fnum; /* if value is a floating point number, m3dpf_float */ + M3D_INDEX textureid; /* if value is a texture, m3dpf_map */ + } value; +} m3dp_t; +#define m3d_property_t m3dp_t + +/* material entry */ +typedef struct { + char *name; /* name of the material */ + uint8_t numprop; /* number of properties */ + m3dp_t *prop; /* properties array */ +} m3dm_t; +#define m3d_material_t m3dm_t + +/* face entry */ +typedef struct { + M3D_INDEX materialid; /* material index */ + M3D_INDEX vertex[3]; /* 3D points of the triangle in CCW order */ + M3D_INDEX normal[3]; /* normal vectors */ + M3D_INDEX texcoord[3]; /* UV coordinates */ +} m3df_t; +#define m3d_face_t m3df_t + +/* shape command types. must match the row in m3d_commandtypes */ +enum { + /* special commands */ + m3dc_use = 0, /* use material */ + m3dc_inc, /* include another shape */ + m3dc_mesh, /* include part of polygon mesh */ + /* approximations */ + m3dc_div, /* subdivision by constant resolution for both u, v */ + m3dc_sub, /* subdivision by constant, different for u and v */ + m3dc_len, /* spacial subdivision by maxlength */ + m3dc_dist, /* subdivision by maxdistance and maxangle */ + /* modifiers */ + m3dc_degu, /* degree for both u, v */ + m3dc_deg, /* separate degree for u and v */ + m3dc_rangeu, /* range for u */ + m3dc_range, /* range for u and v */ + m3dc_paru, /* u parameters (knots) */ + m3dc_parv, /* v parameters */ + m3dc_trim, /* outer trimming curve */ + m3dc_hole, /* inner trimming curve */ + m3dc_scrv, /* spacial curve */ + m3dc_sp, /* special points */ + /* helper curves */ + m3dc_bez1, /* Bezier 1D */ + m3dc_bsp1, /* B-spline 1D */ + m3dc_bez2, /* bezier 2D */ + m3dc_bsp2, /* B-spline 2D */ + /* surfaces */ + m3dc_bezun, /* Bezier 3D with control, UV, normal */ + m3dc_bezu, /* with control and UV */ + m3dc_bezn, /* with control and normal */ + m3dc_bez, /* control points only */ + m3dc_nurbsun, /* B-spline 3D */ + m3dc_nurbsu, + m3dc_nurbsn, + m3dc_nurbs, + m3dc_conn, /* connect surfaces */ + /* geometrical */ + m3dc_line, + m3dc_polygon, + m3dc_circle, + m3dc_cylinder, + m3dc_shpere, + m3dc_torus, + m3dc_cone, + m3dc_cube +}; + +/* shape command argument types */ +enum { + m3dcp_mi_t = 1, /* material index */ + m3dcp_hi_t, /* shape index */ + m3dcp_fi_t, /* face index */ + m3dcp_ti_t, /* texture map index */ + m3dcp_vi_t, /* vertex index */ + m3dcp_qi_t, /* vertex index for quaternions */ + m3dcp_vc_t, /* coordinate or radius, float scalar */ + m3dcp_i1_t, /* int8 scalar */ + m3dcp_i2_t, /* int16 scalar */ + m3dcp_i4_t, /* int32 scalar */ + m3dcp_va_t /* variadic arguments */ +}; + +#define M3D_CMDMAXARG 8 /* if you increase this, add more arguments to the macro below */ +typedef struct { +#define M3D_CMDDEF(t, n, p, a, b, c, d, e, f, g, h) \ + { \ + (char *)(n), (p), { (a), (b), (c), (d), (e), (f), (g), (h) } \ + } + char *key; + uint8_t p; + uint8_t a[M3D_CMDMAXARG]; +} m3dcd_t; + +/* shape command */ +typedef struct { + uint16_t type; /* shape type */ + uint32_t *arg; /* arguments array */ +} m3dc_t; +#define m3d_shapecommand_t m3dc_t + +/* shape entry */ +typedef struct { + char *name; /* name of the mathematical shape */ + M3D_INDEX group; /* group this shape belongs to or -1 */ + uint32_t numcmd; /* number of commands */ + m3dc_t *cmd; /* commands array */ +} m3dh_t; +#define m3d_shape_t m3dh_t + +/* label entry */ +typedef struct { + char *name; /* name of the annotation layer or NULL */ + char *lang; /* language code or NULL */ + char *text; /* the label text */ + uint32_t color; /* color */ + M3D_INDEX vertexid; /* the vertex the label refers to */ +} m3dl_t; +#define m3d_label_t m3dl_t + +/* frame transformations / working copy skeleton entry */ +typedef struct { + M3D_INDEX boneid; /* selects a node in bone hierarchy */ + M3D_INDEX pos; /* vertex index new position */ + M3D_INDEX ori; /* vertex index new orientation (quaternion) */ +} m3dtr_t; +#define m3d_transform_t m3dtr_t + +/* animation frame entry */ +typedef struct { + uint32_t msec; /* frame's position on the timeline, timestamp */ + M3D_INDEX numtransform; /* number of transformations in this frame */ + m3dtr_t *transform; /* transformations */ +} m3dfr_t; +#define m3d_frame_t m3dfr_t + +/* model action entry */ +typedef struct { + char *name; /* name of the action */ + uint32_t durationmsec; /* duration in millisec (1/1000 sec) */ + M3D_INDEX numframe; /* number of frames in this animation */ + m3dfr_t *frame; /* frames array */ +} m3da_t; +#define m3d_action_t m3da_t + +/* inlined asset */ +typedef struct { + char *name; /* asset name (same pointer as in texture[].name) */ + uint8_t *data; /* compressed asset data */ + uint32_t length; /* compressed data length */ +} m3di_t; +#define m3d_inlinedasset_t m3di_t + +/*** in-memory model structure ***/ +#define M3D_FLG_FREERAW (1 << 0) +#define M3D_FLG_FREESTR (1 << 1) +#define M3D_FLG_MTLLIB (1 << 2) +#define M3D_FLG_GENNORM (1 << 3) + +typedef struct { + m3dhdr_t *raw; /* pointer to raw data */ + char flags; /* internal flags */ + signed char errcode; /* returned error code */ + char vc_s, vi_s, si_s, ci_s, ti_s, bi_s, nb_s, sk_s, fc_s, hi_s, fi_s; /* decoded sizes for types */ + char *name; /* name of the model, like "Utah teapot" */ + char *license; /* usage condition or license, like "MIT", "LGPL" or "BSD-3clause" */ + char *author; /* nickname, email, homepage or github URL etc. */ + char *desc; /* comments, descriptions. May contain '\n' newline character */ + M3D_FLOAT scale; /* the model's bounding cube's size in SI meters */ + M3D_INDEX numcmap; + uint32_t *cmap; /* color map */ + M3D_INDEX numtmap; + m3dti_t *tmap; /* texture map indices */ + M3D_INDEX numtexture; + m3dtx_t *texture; /* uncompressed textures */ + M3D_INDEX numbone; + m3db_t *bone; /* bone hierarchy */ + M3D_INDEX numvertex; + m3dv_t *vertex; /* vertex data */ + M3D_INDEX numskin; + m3ds_t *skin; /* skin data */ + M3D_INDEX nummaterial; + m3dm_t *material; /* material list */ + M3D_INDEX numface; + m3df_t *face; /* model face, polygon (triangle) mesh */ + M3D_INDEX numshape; + m3dh_t *shape; /* model face, shape commands */ + M3D_INDEX numlabel; + m3dl_t *label; /* annotation labels */ + M3D_INDEX numaction; + m3da_t *action; /* action animations */ + M3D_INDEX numinlined; + m3di_t *inlined; /* inlined assets */ + M3D_INDEX numextra; + m3dchunk_t **extra; /* unknown chunks, application / engine specific data probably */ + m3di_t preview; /* preview chunk */ +} m3d_t; + +/*** export parameters ***/ +#define M3D_EXP_INT8 0 +#define M3D_EXP_INT16 1 +#define M3D_EXP_FLOAT 2 +#define M3D_EXP_DOUBLE 3 + +#define M3D_EXP_NOCMAP (1 << 0) +#define M3D_EXP_NOMATERIAL (1 << 1) +#define M3D_EXP_NOFACE (1 << 2) +#define M3D_EXP_NONORMAL (1 << 3) +#define M3D_EXP_NOTXTCRD (1 << 4) +#define M3D_EXP_FLIPTXTCRD (1 << 5) +#define M3D_EXP_NORECALC (1 << 6) +#define M3D_EXP_IDOSUCK (1 << 7) +#define M3D_EXP_NOBONE (1 << 8) +#define M3D_EXP_NOACTION (1 << 9) +#define M3D_EXP_INLINE (1 << 10) +#define M3D_EXP_EXTRA (1 << 11) +#define M3D_EXP_NOZLIB (1 << 14) +#define M3D_EXP_ASCII (1 << 15) + +/*** error codes ***/ +#define M3D_SUCCESS 0 +#define M3D_ERR_ALLOC -1 +#define M3D_ERR_BADFILE -2 +#define M3D_ERR_UNIMPL -65 +#define M3D_ERR_UNKPROP -66 +#define M3D_ERR_UNKMESH -67 +#define M3D_ERR_UNKIMG -68 +#define M3D_ERR_UNKFRAME -69 +#define M3D_ERR_UNKCMD -70 +#define M3D_ERR_TRUNC -71 +#define M3D_ERR_CMAP -72 +#define M3D_ERR_TMAP -73 +#define M3D_ERR_VRTS -74 +#define M3D_ERR_BONE -75 +#define M3D_ERR_MTRL -76 +#define M3D_ERR_SHPE -77 + +#define M3D_ERR_ISFATAL(x) ((x) < 0 && (x) > -65) + +/* callbacks */ +typedef unsigned char *(*m3dread_t)(char *filename, unsigned int *size); /* read file contents into buffer */ +typedef void (*m3dfree_t)(void *buffer); /* free file contents buffer */ +typedef int (*m3dtxsc_t)(const char *name, const void *script, uint32_t len, m3dtx_t *output); /* interpret texture script */ +typedef int (*m3dprsc_t)(const char *name, const void *script, uint32_t len, m3d_t *model); /* interpret surface script */ +#endif /* ifndef M3D_APIVERSION */ + +/*** C prototypes ***/ +/* import / export */ +m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d_t *mtllib); +unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size); +void m3d_free(m3d_t *model); +/* generate animation pose skeleton */ +m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t *skeleton); +m3db_t *m3d_pose(m3d_t *model, M3D_INDEX actionid, uint32_t msec); + +/* private prototypes used by both importer and exporter */ +char *_m3d_safestr(char *in, int morelines); + +/*** C implementation ***/ +#ifdef M3D_IMPLEMENTATION +#if !defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER) +/* material property definitions */ +static m3dpd_t m3d_propertytypes[] = { + M3D_PROPERTYDEF(m3dpf_color, m3dp_Kd, "Kd"), /* diffuse color */ + M3D_PROPERTYDEF(m3dpf_color, m3dp_Ka, "Ka"), /* ambient color */ + M3D_PROPERTYDEF(m3dpf_color, m3dp_Ks, "Ks"), /* specular color */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Ns, "Ns"), /* specular exponent */ + M3D_PROPERTYDEF(m3dpf_color, m3dp_Ke, "Ke"), /* emissive (emitting light of this color) */ + M3D_PROPERTYDEF(m3dpf_color, m3dp_Tf, "Tf"), /* transmission color */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Km, "Km"), /* bump strength */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_d, "d"), /* dissolve (transparency) */ + M3D_PROPERTYDEF(m3dpf_uint8, m3dp_il, "il"), /* illumination model (informational, ignored by PBR-shaders) */ + + M3D_PROPERTYDEF(m3dpf_float, m3dp_Pr, "Pr"), /* roughness */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Pm, "Pm"), /* metallic, also reflection */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Ps, "Ps"), /* sheen */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Ni, "Ni"), /* index of refraction (optical density) */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Nt, "Nt"), /* thickness of face in millimeter, for printing */ + + /* aliases, note that "map_*" aliases are handled automatically */ + M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Km, "bump"), + M3D_PROPERTYDEF(m3dpf_map, m3dp_map_N, "map_N"), /* as normal map has no scalar version, it's counterpart is 'il' */ + M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Pm, "refl") +}; +/* shape command definitions. if more commands start with the same string, the longer must come first */ +static m3dcd_t m3d_commandtypes[] = { + /* technical */ + M3D_CMDDEF(m3dc_use, "use", 1, m3dcp_mi_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_inc, "inc", 3, m3dcp_hi_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vi_t, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_mesh, "mesh", 1, m3dcp_fi_t, m3dcp_fi_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vi_t, 0, 0, 0), + /* approximations */ + M3D_CMDDEF(m3dc_div, "div", 1, m3dcp_vc_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_sub, "sub", 2, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_len, "len", 1, m3dcp_vc_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_dist, "dist", 2, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + /* modifiers */ + M3D_CMDDEF(m3dc_degu, "degu", 1, m3dcp_i1_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_deg, "deg", 2, m3dcp_i1_t, m3dcp_i1_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_rangeu, "rangeu", 1, m3dcp_ti_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_range, "range", 2, m3dcp_ti_t, m3dcp_ti_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_paru, "paru", 2, m3dcp_va_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_parv, "parv", 2, m3dcp_va_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_trim, "trim", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_hole, "hole", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_scrv, "scrv", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_sp, "sp", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + /* helper curves */ + M3D_CMDDEF(m3dc_bez1, "bez1", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bsp1, "bsp1", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bez2, "bez2", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bsp2, "bsp2", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + /* surfaces */ + M3D_CMDDEF(m3dc_bezun, "bezun", 4, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, m3dcp_vi_t, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bezu, "bezu", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bezn, "bezn", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bez, "bez", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_nurbsun, "nurbsun", 4, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, m3dcp_vi_t, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_nurbsu, "nurbsu", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_nurbsn, "nurbsn", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_nurbs, "nurbs", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_conn, "conn", 6, m3dcp_i2_t, m3dcp_ti_t, m3dcp_i2_t, m3dcp_i2_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0), + /* geometrical */ + M3D_CMDDEF(m3dc_line, "line", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_polygon, "polygon", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_circle, "circle", 3, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_cylinder, "cylinder", 6, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, 0, 0), + M3D_CMDDEF(m3dc_shpere, "shpere", 2, m3dcp_vi_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_torus, "torus", 4, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_cone, "cone", 3, m3dcp_vi_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_cube, "cube", 3, m3dcp_vi_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0) +}; +#endif + +#include <stdlib.h> +#include <string.h> + +#if defined(M3D_EXPORTER) && !defined(INCLUDE_STB_IMAGE_WRITE_H) +/* zlib_compressor from + + stb_image_write - v1.13 - public domain - http://nothings.org/stb/stb_image_write.h +*/ +typedef unsigned char _m3dstbiw__uc; +typedef unsigned short _m3dstbiw__us; + +typedef uint16_t _m3dstbiw__uint16; +typedef int16_t _m3dstbiw__int16; +typedef uint32_t _m3dstbiw__uint32; +typedef int32_t _m3dstbiw__int32; + +#define STBIW_MALLOC(s) M3D_MALLOC(s) +#define STBIW_REALLOC(p, ns) M3D_REALLOC(p, ns) +#define STBIW_REALLOC_SIZED(p, oldsz, newsz) STBIW_REALLOC(p, newsz) +#define STBIW_FREE M3D_FREE +#define STBIW_MEMMOVE memmove +#define STBIW_UCHAR (uint8_t) +#define STBIW_ASSERT(x) +#define _m3dstbiw___sbraw(a) ((int *)(a)-2) +#define _m3dstbiw___sbm(a) _m3dstbiw___sbraw(a)[0] +#define _m3dstbiw___sbn(a) _m3dstbiw___sbraw(a)[1] + +#define _m3dstbiw___sbneedgrow(a, n) ((a) == 0 || _m3dstbiw___sbn(a) + n >= _m3dstbiw___sbm(a)) +#define _m3dstbiw___sbmaybegrow(a, n) (_m3dstbiw___sbneedgrow(a, (n)) ? _m3dstbiw___sbgrow(a, n) : 0) +#define _m3dstbiw___sbgrow(a, n) _m3dstbiw___sbgrowf((void **)&(a), (n), sizeof(*(a))) + +#define _m3dstbiw___sbpush(a, v) (_m3dstbiw___sbmaybegrow(a, 1), (a)[_m3dstbiw___sbn(a)++] = (v)) +#define _m3dstbiw___sbcount(a) ((a) ? _m3dstbiw___sbn(a) : 0) +#define _m3dstbiw___sbfree(a) ((a) ? STBIW_FREE(_m3dstbiw___sbraw(a)), 0 : 0) + +static void *_m3dstbiw___sbgrowf(void **arr, int increment, int itemsize) { + int m = *arr ? 2 * _m3dstbiw___sbm(*arr) + increment : increment + 1; + void *p = STBIW_REALLOC_SIZED(*arr ? _m3dstbiw___sbraw(*arr) : 0, *arr ? (_m3dstbiw___sbm(*arr) * itemsize + sizeof(int) * 2) : 0, itemsize * m + sizeof(int) * 2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *)p)[1] = 0; + *arr = (void *)((int *)p + 2); + _m3dstbiw___sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *_m3dstbiw___zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) { + while (*bitcount >= 8) { + _m3dstbiw___sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int _m3dstbiw___zlib_bitrev(int code, int codebits) { + int res = 0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int _m3dstbiw___zlib_countm(unsigned char *a, unsigned char *b, int limit) { + int i; + for (i = 0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int _m3dstbiw___zhash(unsigned char *data) { + _m3dstbiw__uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define _m3dstbiw___zlib_flush() (out = _m3dstbiw___zlib_flushf(out, &bitbuf, &bitcount)) +#define _m3dstbiw___zlib_add(code, codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), _m3dstbiw___zlib_flush()) +#define _m3dstbiw___zlib_huffa(b, c) _m3dstbiw___zlib_add(_m3dstbiw___zlib_bitrev(b, c), c) +#define _m3dstbiw___zlib_huff1(n) _m3dstbiw___zlib_huffa(0x30 + (n), 8) +#define _m3dstbiw___zlib_huff2(n) _m3dstbiw___zlib_huffa(0x190 + (n)-144, 9) +#define _m3dstbiw___zlib_huff3(n) _m3dstbiw___zlib_huffa(0 + (n)-256, 7) +#define _m3dstbiw___zlib_huff4(n) _m3dstbiw___zlib_huffa(0xc0 + (n)-280, 8) +#define _m3dstbiw___zlib_huff(n) ((n) <= 143 ? _m3dstbiw___zlib_huff1(n) : (n) <= 255 ? _m3dstbiw___zlib_huff2(n) : (n) <= 279 ? _m3dstbiw___zlib_huff3(n) : _m3dstbiw___zlib_huff4(n)) +#define _m3dstbiw___zlib_huffb(n) ((n) <= 143 ? _m3dstbiw___zlib_huff1(n) : _m3dstbiw___zlib_huff2(n)) + +#define _m3dstbiw___ZHASH 16384 + +unsigned char *_m3dstbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) { + static unsigned short lengthc[] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 259 }; + static unsigned char lengtheb[] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 32768 }; + static unsigned char disteb[] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; + unsigned int bitbuf = 0; + int i, j, bitcount = 0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char ***)STBIW_MALLOC(_m3dstbiw___ZHASH * sizeof(char **)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + _m3dstbiw___sbpush(out, 0x78); + _m3dstbiw___sbpush(out, 0x5e); + _m3dstbiw___zlib_add(1, 1); + _m3dstbiw___zlib_add(1, 2); + + for (i = 0; i < _m3dstbiw___ZHASH; ++i) + hash_table[i] = NULL; + + i = 0; + while (i < data_len - 3) { + int h = _m3dstbiw___zhash(data + i) & (_m3dstbiw___ZHASH - 1), best = 3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = _m3dstbiw___sbcount(hlist); + for (j = 0; j < n; ++j) { + if (hlist[j] - data > i - 32768) { + int d = _m3dstbiw___zlib_countm(hlist[j], data + i, data_len - i); + if (d >= best) best = d, bestloc = hlist[j]; + } + } + if (hash_table[h] && _m3dstbiw___sbn(hash_table[h]) == 2 * quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h] + quality, sizeof(hash_table[h][0]) * quality); + _m3dstbiw___sbn(hash_table[h]) = quality; + } + _m3dstbiw___sbpush(hash_table[h], data + i); + + if (bestloc) { + h = _m3dstbiw___zhash(data + i + 1) & (_m3dstbiw___ZHASH - 1); + hlist = hash_table[h]; + n = _m3dstbiw___sbcount(hlist); + for (j = 0; j < n; ++j) { + if (hlist[j] - data > i - 32767) { + int e = _m3dstbiw___zlib_countm(hlist[j], data + i + 1, data_len - i - 1); + if (e > best) { + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int)(data + i - bestloc); + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j = 0; best > lengthc[j + 1] - 1; ++j) + ; + _m3dstbiw___zlib_huff(j + 257); + if (lengtheb[j]) _m3dstbiw___zlib_add(best - lengthc[j], lengtheb[j]); + for (j = 0; d > distc[j + 1] - 1; ++j) + ; + _m3dstbiw___zlib_add(_m3dstbiw___zlib_bitrev(j, 5), 5); + if (disteb[j]) _m3dstbiw___zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + _m3dstbiw___zlib_huffb(data[i]); + ++i; + } + } + for (; i < data_len; ++i) + _m3dstbiw___zlib_huffb(data[i]); + _m3dstbiw___zlib_huff(256); + while (bitcount) + _m3dstbiw___zlib_add(0, 1); + + for (i = 0; i < _m3dstbiw___ZHASH; ++i) + (void)_m3dstbiw___sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + { + unsigned int s1 = 1, s2 = 0; + int blocklen = (int)(data_len % 5552); + j = 0; + while (j < data_len) { + for (i = 0; i < blocklen; ++i) + s1 += data[j + i], s2 += s1; + s1 %= 65521, s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + _m3dstbiw___sbpush(out, STBIW_UCHAR(s2 >> 8)); + _m3dstbiw___sbpush(out, STBIW_UCHAR(s2)); + _m3dstbiw___sbpush(out, STBIW_UCHAR(s1 >> 8)); + _m3dstbiw___sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = _m3dstbiw___sbn(out); + STBIW_MEMMOVE(_m3dstbiw___sbraw(out), out, *out_len); + return (unsigned char *)_m3dstbiw___sbraw(out); +} +#define stbi_zlib_compress _m3dstbi_zlib_compress +#else +unsigned char *_m3dstbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality); +#endif + +#define M3D_CHUNKMAGIC(m, a, b, c, d) ((m)[0] == (a) && (m)[1] == (b) && (m)[2] == (c) && (m)[3] == (d)) + +#include <locale.h> /* sprintf and strtod cares about number locale */ +#include <stdio.h> /* get sprintf */ +#ifdef M3D_PROFILING +#include <sys/time.h> +#endif + +#if !defined(M3D_NOIMPORTER) +/* helper functions for the ASCII parser */ +static char *_m3d_findarg(char *s) { + while (s && *s && *s != ' ' && *s != '\t' && *s != '\r' && *s != '\n') + s++; + while (s && *s && (*s == ' ' || *s == '\t')) + s++; + return s; +} +static char *_m3d_findnl(char *s) { + while (s && *s && *s != '\r' && *s != '\n') + s++; + if (*s == '\r') s++; + if (*s == '\n') s++; + return s; +} +static char *_m3d_gethex(char *s, uint32_t *ret) { + if (*s == '#') s++; + *ret = 0; + for (; *s; s++) { + if (*s >= '0' && *s <= '9') { + *ret <<= 4; + *ret += (uint32_t)(*s - '0'); + } else if (*s >= 'a' && *s <= 'f') { + *ret <<= 4; + *ret += (uint32_t)(*s - 'a' + 10); + } else if (*s >= 'A' && *s <= 'F') { + *ret <<= 4; + *ret += (uint32_t)(*s - 'A' + 10); + } else + break; + } + return _m3d_findarg(s); +} +static char *_m3d_getint(char *s, uint32_t *ret) { + char *e = s; + if (!s || !*s || *s == '\r' || *s == '\n') return s; + for (; *e >= '0' && *e <= '9'; e++) + ; + *ret = atoi(s); + return e; +} +static char *_m3d_getfloat(char *s, M3D_FLOAT *ret) { + char *e = s; + if (!s || !*s || *s == '\r' || *s == '\n') return s; + for (; *e == '-' || *e == '+' || *e == '.' || (*e >= '0' && *e <= '9') || *e == 'e' || *e == 'E'; e++) + ; + *ret = (M3D_FLOAT)strtod(s, NULL); + return _m3d_findarg(e); +} +#endif +#if !defined(M3D_NODUP) && (!defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER)) +/* helper function to create safe strings */ +char *_m3d_safestr(char *in, int morelines) { + char *out, *o, *i = in; + int l; + if (!in || !*in) { + out = (char *)M3D_MALLOC(1); + if (!out) return NULL; + out[0] = 0; + } else { + for (o = in, l = 0; *o && ((morelines & 1) || (*o != '\r' && *o != '\n')) && l < 256; o++, l++) + ; + out = o = (char *)M3D_MALLOC(l + 1); + if (!out) return NULL; + while (*i == ' ' || *i == '\t' || *i == '\r' || (morelines && *i == '\n')) + i++; + for (; *i && (morelines || (*i != '\r' && *i != '\n')) && o - out < l; i++) { + if (*i == '\r') continue; + if (*i == '\n') { + if (morelines >= 3 && o > out && *(o - 1) == '\n') break; + if (i > in && *(i - 1) == '\n') continue; + if (morelines & 1) { + if (morelines == 1) *o++ = '\r'; + *o++ = '\n'; + } else + break; + } else if (*i == ' ' || *i == '\t') { + *o++ = morelines ? ' ' : '_'; + } else + *o++ = !morelines && (*i == '/' || *i == '\\') ? '_' : *i; + } + for (; o > out && (*(o - 1) == ' ' || *(o - 1) == '\t' || *(o - 1) == '\r' || *(o - 1) == '\n'); o--) + ; + *o = 0; + out = (char *)M3D_REALLOC(out, (uintptr_t)o - (uintptr_t)out + 1); + } + return out; +} +#endif +#ifndef M3D_NOIMPORTER +/* helper function to load and decode/generate a texture */ +M3D_INDEX _m3d_gettx(m3d_t *model, m3dread_t readfilecb, m3dfree_t freecb, char *fn) { + unsigned int i, len = 0; + unsigned char *buff = NULL; + char *fn2; +#ifdef STBI__PNG_TYPE + unsigned int w, h; + stbi__context s; + stbi__result_info ri; +#endif + + /* do we have loaded this texture already? */ + for (i = 0; i < model->numtexture; i++) + if (!strcmp(fn, model->texture[i].name)) return i; + /* see if it's inlined in the model */ + if (model->inlined) { + for (i = 0; i < model->numinlined; i++) + if (!strcmp(fn, model->inlined[i].name)) { + buff = model->inlined[i].data; + len = model->inlined[i].length; + freecb = NULL; + break; + } + } + /* try to load from external source */ + if (!buff && readfilecb) { + i = (unsigned int)strlen(fn); + if (i < 5 || fn[i - 4] != '.') { + fn2 = (char *)M3D_MALLOC(i + 5); + if (!fn2) { + model->errcode = M3D_ERR_ALLOC; + return M3D_UNDEF; + } + memcpy(fn2, fn, i); + memcpy(fn2 + i, ".png", 5); + buff = (*readfilecb)(fn2, &len); + M3D_FREE(fn2); + } + if (!buff) { + buff = (*readfilecb)(fn, &len); + if (!buff) return M3D_UNDEF; + } + } + /* add to textures array */ + i = model->numtexture++; + model->texture = (m3dtx_t *)M3D_REALLOC(model->texture, model->numtexture * sizeof(m3dtx_t)); + if (!model->texture) { + if (buff && freecb) (*freecb)(buff); + model->errcode = M3D_ERR_ALLOC; + return M3D_UNDEF; + } + model->texture[i].name = fn; + model->texture[i].w = model->texture[i].h = 0; + model->texture[i].d = NULL; + if (buff) { + if (buff[0] == 0x89 && buff[1] == 'P' && buff[2] == 'N' && buff[3] == 'G') { +#ifdef STBI__PNG_TYPE + s.read_from_callbacks = 0; + s.img_buffer = s.img_buffer_original = (unsigned char *)buff; + s.img_buffer_end = s.img_buffer_original_end = (unsigned char *)buff + len; + /* don't use model->texture[i].w directly, it's a uint16_t */ + w = h = len = 0; + ri.bits_per_channel = 8; + model->texture[i].d = (uint8_t *)stbi__png_load(&s, (int *)&w, (int *)&h, (int *)&len, 0, &ri); + model->texture[i].w = (uint16_t)w; + model->texture[i].h = (uint16_t)h; + model->texture[i].f = (uint8_t)len; +#endif + } else { +#ifdef M3D_TX_INTERP + if ((model->errcode = M3D_TX_INTERP(fn, buff, len, &model->texture[i])) != M3D_SUCCESS) { + M3D_LOG("Unable to generate texture"); + M3D_LOG(fn); + } +#else + M3D_LOG("Unimplemented interpreter"); + M3D_LOG(fn); +#endif + } + if (freecb) (*freecb)(buff); + } + if (!model->texture[i].d) + model->errcode = M3D_ERR_UNKIMG; + return i; +} + +/* helper function to load and generate a procedural surface */ +void _m3d_getpr(m3d_t *model, _unused m3dread_t readfilecb, _unused m3dfree_t freecb, _unused char *fn) { +#ifdef M3D_PR_INTERP + unsigned int i, len = 0; + unsigned char *buff = readfilecb ? (*readfilecb)(fn, &len) : NULL; + + if (!buff && model->inlined) { + for (i = 0; i < model->numinlined; i++) + if (!strcmp(fn, model->inlined[i].name)) { + buff = model->inlined[i].data; + len = model->inlined[i].length; + freecb = NULL; + break; + } + } + if (!buff || !len || (model->errcode = M3D_PR_INTERP(fn, buff, len, model)) != M3D_SUCCESS) { + M3D_LOG("Unable to generate procedural surface"); + M3D_LOG(fn); + model->errcode = M3D_ERR_UNKIMG; + } + if (freecb && buff) (*freecb)(buff); +#else + (void)readfilecb; + (void)freecb; + (void)fn; + M3D_LOG("Unimplemented interpreter"); + M3D_LOG(fn); + model->errcode = M3D_ERR_UNIMPL; +#endif +} +/* helpers to read indices from data stream */ +#define M3D_GETSTR(x) \ + do { \ + offs = 0; \ + data = _m3d_getidx(data, model->si_s, &offs); \ + x = offs ? ((char *)model->raw + 16 + offs) : NULL; \ + } while (0) +_inline static unsigned char *_m3d_getidx(unsigned char *data, char type, M3D_INDEX *idx) { + switch (type) { + case 1: + *idx = data[0] > 253 ? (int8_t)data[0] : data[0]; + data++; + break; + case 2: + *idx = *((uint16_t *)data) > 65533 ? *((int16_t *)data) : *((uint16_t *)data); + data += 2; + break; + case 4: + *idx = *((int32_t *)data); + data += 4; + break; + } + return data; +} + +#ifndef M3D_NOANIMATION +/* multiply 4 x 4 matrices. Do not use float *r[16] as argument, because some compilers misinterpret that as + * 16 pointers each pointing to a float, but we need a single pointer to 16 floats. */ +void _m3d_mul(M3D_FLOAT *r, M3D_FLOAT *a, M3D_FLOAT *b) { + r[0] = b[0] * a[0] + b[4] * a[1] + b[8] * a[2] + b[12] * a[3]; + r[1] = b[1] * a[0] + b[5] * a[1] + b[9] * a[2] + b[13] * a[3]; + r[2] = b[2] * a[0] + b[6] * a[1] + b[10] * a[2] + b[14] * a[3]; + r[3] = b[3] * a[0] + b[7] * a[1] + b[11] * a[2] + b[15] * a[3]; + r[4] = b[0] * a[4] + b[4] * a[5] + b[8] * a[6] + b[12] * a[7]; + r[5] = b[1] * a[4] + b[5] * a[5] + b[9] * a[6] + b[13] * a[7]; + r[6] = b[2] * a[4] + b[6] * a[5] + b[10] * a[6] + b[14] * a[7]; + r[7] = b[3] * a[4] + b[7] * a[5] + b[11] * a[6] + b[15] * a[7]; + r[8] = b[0] * a[8] + b[4] * a[9] + b[8] * a[10] + b[12] * a[11]; + r[9] = b[1] * a[8] + b[5] * a[9] + b[9] * a[10] + b[13] * a[11]; + r[10] = b[2] * a[8] + b[6] * a[9] + b[10] * a[10] + b[14] * a[11]; + r[11] = b[3] * a[8] + b[7] * a[9] + b[11] * a[10] + b[15] * a[11]; + r[12] = b[0] * a[12] + b[4] * a[13] + b[8] * a[14] + b[12] * a[15]; + r[13] = b[1] * a[12] + b[5] * a[13] + b[9] * a[14] + b[13] * a[15]; + r[14] = b[2] * a[12] + b[6] * a[13] + b[10] * a[14] + b[14] * a[15]; + r[15] = b[3] * a[12] + b[7] * a[13] + b[11] * a[14] + b[15] * a[15]; +} +/* calculate 4 x 4 matrix inverse */ +void _m3d_inv(M3D_FLOAT *m) { + M3D_FLOAT r[16]; + M3D_FLOAT det = + m[0] * m[5] * m[10] * m[15] - m[0] * m[5] * m[11] * m[14] + m[0] * m[6] * m[11] * m[13] - m[0] * m[6] * m[9] * m[15] + m[0] * m[7] * m[9] * m[14] - m[0] * m[7] * m[10] * m[13] - m[1] * m[6] * m[11] * m[12] + m[1] * m[6] * m[8] * m[15] - m[1] * m[7] * m[8] * m[14] + m[1] * m[7] * m[10] * m[12] - m[1] * m[4] * m[10] * m[15] + m[1] * m[4] * m[11] * m[14] + m[2] * m[7] * m[8] * m[13] - m[2] * m[7] * m[9] * m[12] + m[2] * m[4] * m[9] * m[15] - m[2] * m[4] * m[11] * m[13] + m[2] * m[5] * m[11] * m[12] - m[2] * m[5] * m[8] * m[15] - m[3] * m[4] * m[9] * m[14] + m[3] * m[4] * m[10] * m[13] - m[3] * m[5] * m[10] * m[12] + m[3] * m[5] * m[8] * m[14] - m[3] * m[6] * m[8] * m[13] + m[3] * m[6] * m[9] * m[12]; + if (det == (M3D_FLOAT)0.0 || det == (M3D_FLOAT)-0.0) + det = (M3D_FLOAT)1.0; + else + det = (M3D_FLOAT)1.0 / det; + r[0] = det * (m[5] * (m[10] * m[15] - m[11] * m[14]) + m[6] * (m[11] * m[13] - m[9] * m[15]) + m[7] * (m[9] * m[14] - m[10] * m[13])); + r[1] = -det * (m[1] * (m[10] * m[15] - m[11] * m[14]) + m[2] * (m[11] * m[13] - m[9] * m[15]) + m[3] * (m[9] * m[14] - m[10] * m[13])); + r[2] = det * (m[1] * (m[6] * m[15] - m[7] * m[14]) + m[2] * (m[7] * m[13] - m[5] * m[15]) + m[3] * (m[5] * m[14] - m[6] * m[13])); + r[3] = -det * (m[1] * (m[6] * m[11] - m[7] * m[10]) + m[2] * (m[7] * m[9] - m[5] * m[11]) + m[3] * (m[5] * m[10] - m[6] * m[9])); + r[4] = -det * (m[4] * (m[10] * m[15] - m[11] * m[14]) + m[6] * (m[11] * m[12] - m[8] * m[15]) + m[7] * (m[8] * m[14] - m[10] * m[12])); + r[5] = det * (m[0] * (m[10] * m[15] - m[11] * m[14]) + m[2] * (m[11] * m[12] - m[8] * m[15]) + m[3] * (m[8] * m[14] - m[10] * m[12])); + r[6] = -det * (m[0] * (m[6] * m[15] - m[7] * m[14]) + m[2] * (m[7] * m[12] - m[4] * m[15]) + m[3] * (m[4] * m[14] - m[6] * m[12])); + r[7] = det * (m[0] * (m[6] * m[11] - m[7] * m[10]) + m[2] * (m[7] * m[8] - m[4] * m[11]) + m[3] * (m[4] * m[10] - m[6] * m[8])); + r[8] = det * (m[4] * (m[9] * m[15] - m[11] * m[13]) + m[5] * (m[11] * m[12] - m[8] * m[15]) + m[7] * (m[8] * m[13] - m[9] * m[12])); + r[9] = -det * (m[0] * (m[9] * m[15] - m[11] * m[13]) + m[1] * (m[11] * m[12] - m[8] * m[15]) + m[3] * (m[8] * m[13] - m[9] * m[12])); + r[10] = det * (m[0] * (m[5] * m[15] - m[7] * m[13]) + m[1] * (m[7] * m[12] - m[4] * m[15]) + m[3] * (m[4] * m[13] - m[5] * m[12])); + r[11] = -det * (m[0] * (m[5] * m[11] - m[7] * m[9]) + m[1] * (m[7] * m[8] - m[4] * m[11]) + m[3] * (m[4] * m[9] - m[5] * m[8])); + r[12] = -det * (m[4] * (m[9] * m[14] - m[10] * m[13]) + m[5] * (m[10] * m[12] - m[8] * m[14]) + m[6] * (m[8] * m[13] - m[9] * m[12])); + r[13] = det * (m[0] * (m[9] * m[14] - m[10] * m[13]) + m[1] * (m[10] * m[12] - m[8] * m[14]) + m[2] * (m[8] * m[13] - m[9] * m[12])); + r[14] = -det * (m[0] * (m[5] * m[14] - m[6] * m[13]) + m[1] * (m[6] * m[12] - m[4] * m[14]) + m[2] * (m[4] * m[13] - m[5] * m[12])); + r[15] = det * (m[0] * (m[5] * m[10] - m[6] * m[9]) + m[1] * (m[6] * m[8] - m[4] * m[10]) + m[2] * (m[4] * m[9] - m[5] * m[8])); + memcpy(m, &r, sizeof(r)); +} +/* compose a column major 4 x 4 matrix from vec3 position and vec4 orientation/rotation quaternion */ +void _m3d_mat(M3D_FLOAT *r, m3dv_t *p, m3dv_t *q) { + if (q->x == (M3D_FLOAT)0.0 && q->y == (M3D_FLOAT)0.0 && q->z >= (M3D_FLOAT)0.7071065 && q->z <= (M3D_FLOAT)0.7071075 && + q->w == (M3D_FLOAT)0.0) { + r[1] = r[2] = r[4] = r[6] = r[8] = r[9] = (M3D_FLOAT)0.0; + r[0] = r[5] = r[10] = (M3D_FLOAT)-1.0; + } else { + r[0] = 1 - 2 * (q->y * q->y + q->z * q->z); + if (r[0] > -M3D_EPSILON && r[0] < M3D_EPSILON) r[0] = (M3D_FLOAT)0.0; + r[1] = 2 * (q->x * q->y - q->z * q->w); + if (r[1] > -M3D_EPSILON && r[1] < M3D_EPSILON) r[1] = (M3D_FLOAT)0.0; + r[2] = 2 * (q->x * q->z + q->y * q->w); + if (r[2] > -M3D_EPSILON && r[2] < M3D_EPSILON) r[2] = (M3D_FLOAT)0.0; + r[4] = 2 * (q->x * q->y + q->z * q->w); + if (r[4] > -M3D_EPSILON && r[4] < M3D_EPSILON) r[4] = (M3D_FLOAT)0.0; + r[5] = 1 - 2 * (q->x * q->x + q->z * q->z); + if (r[5] > -M3D_EPSILON && r[5] < M3D_EPSILON) r[5] = (M3D_FLOAT)0.0; + r[6] = 2 * (q->y * q->z - q->x * q->w); + if (r[6] > -M3D_EPSILON && r[6] < M3D_EPSILON) r[6] = (M3D_FLOAT)0.0; + r[8] = 2 * (q->x * q->z - q->y * q->w); + if (r[8] > -M3D_EPSILON && r[8] < M3D_EPSILON) r[8] = (M3D_FLOAT)0.0; + r[9] = 2 * (q->y * q->z + q->x * q->w); + if (r[9] > -M3D_EPSILON && r[9] < M3D_EPSILON) r[9] = (M3D_FLOAT)0.0; + r[10] = 1 - 2 * (q->x * q->x + q->y * q->y); + if (r[10] > -M3D_EPSILON && r[10] < M3D_EPSILON) r[10] = (M3D_FLOAT)0.0; + } + r[3] = p->x; + r[7] = p->y; + r[11] = p->z; + r[12] = 0; + r[13] = 0; + r[14] = 0; + r[15] = 1; +} +#endif +#if !defined(M3D_NOANIMATION) || !defined(M3D_NONORMALS) +/* portable fast inverse square root calculation. returns 1/sqrt(x) */ +static M3D_FLOAT _m3d_rsq(M3D_FLOAT x) { +#ifdef M3D_DOUBLE + return ((M3D_FLOAT)15.0 / (M3D_FLOAT)8.0) + ((M3D_FLOAT)-5.0 / (M3D_FLOAT)4.0) * x + ((M3D_FLOAT)3.0 / (M3D_FLOAT)8.0) * x * x; +#else + /* John Carmack's */ + float x2 = x * 0.5f; + *((uint32_t *)&x) = (0x5f3759df - (*((uint32_t *)&x) >> 1)); + return x * (1.5f - (x2 * x * x)); +#endif +} +#endif + +/** + * Function to decode a Model 3D into in-memory format + */ +m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d_t *mtllib) { + unsigned char *end, *chunk, *buff, weights[8]; + unsigned int i, j, k, l, n, am, len = 0, reclen, offs; + char *name, *lang; + float f; + m3d_t *model; + M3D_INDEX mi; + M3D_FLOAT w; + m3dcd_t *cd; + m3dtx_t *tx; + m3dh_t *h; + m3dm_t *m; + m3da_t *a; + m3di_t *t; +#ifndef M3D_NONORMALS + char neednorm = 0; + m3dv_t *norm = NULL, *v0, *v1, *v2, va, vb; +#endif +#ifndef M3D_NOANIMATION + M3D_FLOAT r[16]; +#endif +#if !defined(M3D_NOWEIGHTS) || !defined(M3D_NOANIMATION) + m3db_t *b; +#endif +#ifndef M3D_NOWEIGHTS + m3ds_t *sk; +#endif + m3ds_t s; + M3D_INDEX bi[M3D_BONEMAXLEVEL + 1], level; + const char *ol; + char *ptr, *pe, *fn; +#ifdef M3D_PROFILING + struct timeval tv0, tv1, tvd; + gettimeofday(&tv0, NULL); +#endif + + if (!data || (!M3D_CHUNKMAGIC(data, '3', 'D', 'M', 'O') + && !M3D_CHUNKMAGIC(data, '3', 'd', 'm', 'o') + )) + return NULL; + model = (m3d_t *)M3D_MALLOC(sizeof(m3d_t)); + if (!model) { + M3D_LOG("Out of memory"); + return NULL; + } + memset(model, 0, sizeof(m3d_t)); + + if (mtllib) { + model->nummaterial = mtllib->nummaterial; + model->material = mtllib->material; + model->numtexture = mtllib->numtexture; + model->texture = mtllib->texture; + model->flags |= M3D_FLG_MTLLIB; + } + /* ASCII variant? */ + if (M3D_CHUNKMAGIC(data, '3', 'd', 'm', 'o')) { + model->errcode = M3D_ERR_BADFILE; + model->flags |= M3D_FLG_FREESTR; + model->raw = (m3dhdr_t *)data; + ptr = (char *)data; + ol = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + /* parse header. Don't use sscanf, that's incredibly slow */ + ptr = _m3d_findarg(ptr); + if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + pe = _m3d_findnl(ptr); + model->scale = (float)strtod(ptr, NULL); + ptr = pe; + if (model->scale <= (M3D_FLOAT)0.0) model->scale = (M3D_FLOAT)1.0; + model->name = _m3d_safestr(ptr, 2); + ptr = _m3d_findnl(ptr); + if (!*ptr) goto asciiend; + model->license = _m3d_safestr(ptr, 2); + ptr = _m3d_findnl(ptr); + if (!*ptr) goto asciiend; + model->author = _m3d_safestr(ptr, 2); + ptr = _m3d_findnl(ptr); + if (!*ptr) goto asciiend; + if (*ptr != '\r' && *ptr != '\n') + model->desc = _m3d_safestr(ptr, 3); + while (*ptr) { + while (*ptr && *ptr != '\n') + ptr++; + ptr++; + if (*ptr == '\r') ptr++; + if (*ptr == '\n') break; + } + + /* the main chunk reader loop */ + while (*ptr) { + while (*ptr && (*ptr == '\r' || *ptr == '\n')) + ptr++; + if (!*ptr || (ptr[0] == 'E' && ptr[1] == 'n' && ptr[2] == 'd')) break; + /* make sure there's at least one data row */ + pe = ptr; + ptr = _m3d_findnl(ptr); + if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + /* Preview chunk */ + if (!memcmp(pe, "Preview", 7)) { + if (readfilecb) { + pe = _m3d_safestr(ptr, 0); + if (!pe || !*pe) goto asciiend; + model->preview.data = (*readfilecb)(pe, &model->preview.length); + M3D_FREE(pe); + } + while (*ptr && *ptr != '\r' && *ptr != '\n') + ptr = _m3d_findnl(ptr); + } else + /* texture map chunk */ + if (!memcmp(pe, "Textmap", 7)) { + if (model->tmap) { + M3D_LOG("More texture map chunks, should be unique"); + goto asciiend; + } + while (*ptr && *ptr != '\r' && *ptr != '\n') { + i = model->numtmap++; + model->tmap = (m3dti_t *)M3D_REALLOC(model->tmap, model->numtmap * sizeof(m3dti_t)); + if (!model->tmap) goto memerr; + ptr = _m3d_getfloat(ptr, &model->tmap[i].u); + if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + _m3d_getfloat(ptr, &model->tmap[i].v); + ptr = _m3d_findnl(ptr); + } + } else + /* vertex chunk */ + if (!memcmp(pe, "Vertex", 6)) { + if (model->vertex) { + M3D_LOG("More vertex chunks, should be unique"); + goto asciiend; + } + while (*ptr && *ptr != '\r' && *ptr != '\n') { + i = model->numvertex++; + model->vertex = (m3dv_t *)M3D_REALLOC(model->vertex, model->numvertex * sizeof(m3dv_t)); + if (!model->vertex) goto memerr; + memset(&model->vertex[i], 0, sizeof(m3dv_t)); + model->vertex[i].skinid = M3D_UNDEF; + model->vertex[i].color = 0; + model->vertex[i].w = (M3D_FLOAT)1.0; + ptr = _m3d_getfloat(ptr, &model->vertex[i].x); + if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + ptr = _m3d_getfloat(ptr, &model->vertex[i].y); + if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + ptr = _m3d_getfloat(ptr, &model->vertex[i].z); + if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + ptr = _m3d_getfloat(ptr, &model->vertex[i].w); + if (!*ptr) goto asciiend; + if (*ptr == '#') { + ptr = _m3d_gethex(ptr, &model->vertex[i].color); + if (!*ptr) goto asciiend; + } + /* parse skin */ + memset(&s, 0, sizeof(m3ds_t)); + for (j = 0, w = (M3D_FLOAT)0.0; j < M3D_NUMBONE && *ptr && *ptr != '\r' && *ptr != '\n'; j++) { + ptr = _m3d_findarg(ptr); + if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + ptr = _m3d_getint(ptr, &k); + s.boneid[j] = (M3D_INDEX)k; + if (*ptr == ':') { + ptr++; + ptr = _m3d_getfloat(ptr, &s.weight[j]); + w += s.weight[j]; + } else if (!j) + s.weight[j] = (M3D_FLOAT)1.0; + if (!*ptr) goto asciiend; + } + if (s.boneid[0] != M3D_UNDEF && s.weight[0] > (M3D_FLOAT)0.0) { + if (w != (M3D_FLOAT)1.0 && w != (M3D_FLOAT)0.0) + for (j = 0; j < M3D_NUMBONE && s.weight[j] > (M3D_FLOAT)0.0; j++) + s.weight[j] /= w; + k = M3D_NOTDEFINED; + if (model->skin) { + for (j = 0; j < model->numskin; j++) + if (!memcmp(&model->skin[j], &s, sizeof(m3ds_t))) { + k = j; + break; + } + } + if (k == M3D_NOTDEFINED) { + k = model->numskin++; + model->skin = (m3ds_t *)M3D_REALLOC(model->skin, model->numskin * sizeof(m3ds_t)); + memcpy(&model->skin[k], &s, sizeof(m3ds_t)); + } + model->vertex[i].skinid = (M3D_INDEX)k; + } + ptr = _m3d_findnl(ptr); + } + } else + /* Skeleton, bone hierarchy */ + if (!memcmp(pe, "Bones", 5)) { + if (model->bone) { + M3D_LOG("More bones chunks, should be unique"); + goto asciiend; + } + bi[0] = M3D_UNDEF; + while (*ptr && *ptr != '\r' && *ptr != '\n') { + i = model->numbone++; + model->bone = (m3db_t *)M3D_REALLOC(model->bone, model->numbone * sizeof(m3db_t)); + if (!model->bone) goto memerr; + for (level = 0; *ptr == '/'; ptr++, level++) + ; + if (level > M3D_BONEMAXLEVEL || !*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + bi[level + 1] = i; + model->bone[i].numweight = 0; + model->bone[i].weight = NULL; + model->bone[i].parent = bi[level]; + ptr = _m3d_getint(ptr, &k); + ptr = _m3d_findarg(ptr); + if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + model->bone[i].pos = (M3D_INDEX)k; + ptr = _m3d_getint(ptr, &k); + ptr = _m3d_findarg(ptr); + if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + model->bone[i].ori = (M3D_INDEX)k; + model->vertex[k].skinid = M3D_INDEXMAX; + pe = _m3d_safestr(ptr, 0); + if (!pe || !*pe) goto asciiend; + model->bone[i].name = pe; + ptr = _m3d_findnl(ptr); + } + } else + /* material chunk */ + if (!memcmp(pe, "Material", 8)) { + pe = _m3d_findarg(pe); + if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + pe = _m3d_safestr(pe, 0); + if (!pe || !*pe) goto asciiend; + for (i = 0; i < model->nummaterial; i++) + if (!strcmp(pe, model->material[i].name)) { + M3D_LOG("Multiple definitions for material"); + M3D_LOG(pe); + M3D_FREE(pe); + pe = NULL; + while (*ptr && *ptr != '\r' && *ptr != '\n') + ptr = _m3d_findnl(ptr); + break; + } + if (!pe) continue; + i = model->nummaterial++; + if (model->flags & M3D_FLG_MTLLIB) { + m = model->material; + model->material = (m3dm_t *)M3D_MALLOC(model->nummaterial * sizeof(m3dm_t)); + if (!model->material) goto memerr; + memcpy(model->material, m, (model->nummaterial - 1) * sizeof(m3dm_t)); + if (model->texture) { + tx = model->texture; + model->texture = (m3dtx_t *)M3D_MALLOC(model->numtexture * sizeof(m3dtx_t)); + if (!model->texture) goto memerr; + memcpy(model->texture, tx, model->numtexture * sizeof(m3dm_t)); + } + model->flags &= ~M3D_FLG_MTLLIB; + } else { + model->material = (m3dm_t *)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); + if (!model->material) goto memerr; + } + m = &model->material[i]; + m->name = pe; + m->numprop = 0; + m->prop = NULL; + while (*ptr && *ptr != '\r' && *ptr != '\n') { + k = n = 256; + if (*ptr == 'm' && *(ptr + 1) == 'a' && *(ptr + 2) == 'p' && *(ptr + 3) == '_') { + k = m3dpf_map; + ptr += 4; + } + for (j = 0; j < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); j++) + if (!memcmp(ptr, m3d_propertytypes[j].key, strlen(m3d_propertytypes[j].key))) { + n = m3d_propertytypes[j].id; + if (k != m3dpf_map) k = m3d_propertytypes[j].format; + break; + } + if (n != 256 && k != 256) { + ptr = _m3d_findarg(ptr); + if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + j = m->numprop++; + m->prop = (m3dp_t *)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t)); + if (!m->prop) goto memerr; + m->prop[j].type = n + (k == m3dpf_map && n < 128 ? 128 : 0); + switch (k) { + case m3dpf_color: ptr = _m3d_gethex(ptr, &m->prop[j].value.color); break; + case m3dpf_uint8: + case m3dpf_uint16: + case m3dpf_uint32: ptr = _m3d_getint(ptr, &m->prop[j].value.num); break; + case m3dpf_float: ptr = _m3d_getfloat(ptr, &m->prop[j].value.fnum); break; + case m3dpf_map: + pe = _m3d_safestr(ptr, 0); + if (!pe || !*pe) goto asciiend; + m->prop[j].value.textureid = _m3d_gettx(model, readfilecb, freecb, pe); + if (model->errcode == M3D_ERR_ALLOC) { + M3D_FREE(pe); + goto memerr; + } + /* this error code only returned if readfilecb was specified */ + if (m->prop[j].value.textureid == M3D_UNDEF) { + M3D_LOG("Texture not found"); + M3D_LOG(pe); + m->numprop--; + } + M3D_FREE(pe); + break; + } + } else { + M3D_LOG("Unknown material property in"); + M3D_LOG(m->name); + model->errcode = M3D_ERR_UNKPROP; + } + ptr = _m3d_findnl(ptr); + } + if (!m->numprop) model->nummaterial--; + } else + /* procedural */ + if (!memcmp(pe, "Procedural", 10)) { + pe = _m3d_safestr(ptr, 0); + _m3d_getpr(model, readfilecb, freecb, pe); + M3D_FREE(pe); + while (*ptr && *ptr != '\r' && *ptr != '\n') + ptr = _m3d_findnl(ptr); + } else + /* mesh */ + if (!memcmp(pe, "Mesh", 4)) { + mi = M3D_UNDEF; + while (*ptr && *ptr != '\r' && *ptr != '\n') { + if (*ptr == 'u') { + ptr = _m3d_findarg(ptr); + if (!*ptr) goto asciiend; + mi = M3D_UNDEF; + if (*ptr != '\r' && *ptr != '\n') { + pe = _m3d_safestr(ptr, 0); + if (!pe || !*pe) goto asciiend; + for (j = 0; j < model->nummaterial; j++) + if (!strcmp(pe, model->material[j].name)) { + mi = (M3D_INDEX)j; + break; + } + if (mi == M3D_UNDEF && !(model->flags & M3D_FLG_MTLLIB)) { + mi = model->nummaterial++; + model->material = (m3dm_t *)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); + if (!model->material) goto memerr; + model->material[mi].name = pe; + model->material[mi].numprop = 1; + model->material[mi].prop = NULL; + } else + M3D_FREE(pe); + } + } else { + i = model->numface++; + model->face = (m3df_t *)M3D_REALLOC(model->face, model->numface * sizeof(m3df_t)); + if (!model->face) goto memerr; + memset(&model->face[i], 255, sizeof(m3df_t)); /* set all index to -1 by default */ + model->face[i].materialid = mi; + /* hardcoded triangles. */ + for (j = 0; j < 3; j++) { + /* vertex */ + ptr = _m3d_getint(ptr, &k); + model->face[i].vertex[j] = (M3D_INDEX)k; + if (!*ptr) goto asciiend; + if (*ptr == '/') { + ptr++; + if (*ptr != '/') { + /* texcoord */ + ptr = _m3d_getint(ptr, &k); + model->face[i].texcoord[j] = (M3D_INDEX)k; + if (!*ptr) goto asciiend; + } + if (*ptr == '/') { + ptr++; + /* normal */ + ptr = _m3d_getint(ptr, &k); + model->face[i].normal[j] = (M3D_INDEX)k; + if (!*ptr) goto asciiend; + } + } +#ifndef M3D_NONORMALS + if (model->face[i].normal[j] == M3D_UNDEF) neednorm = 1; +#endif + ptr = _m3d_findarg(ptr); + } + } + ptr = _m3d_findnl(ptr); + } + } else + /* mathematical shape */ + if (!memcmp(pe, "Shape", 5)) { + pe = _m3d_findarg(pe); + if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + pe = _m3d_safestr(pe, 0); + if (!pe || !*pe) goto asciiend; + i = model->numshape++; + model->shape = (m3dh_t *)M3D_REALLOC(model->shape, model->numshape * sizeof(m3ds_t)); + if (!model->shape) goto memerr; + h = &model->shape[i]; + h->name = pe; + h->group = M3D_UNDEF; + h->numcmd = 0; + h->cmd = NULL; + while (*ptr && *ptr != '\r' && *ptr != '\n') { + if (!memcmp(ptr, "group", 5)) { + ptr = _m3d_findarg(ptr); + ptr = _m3d_getint(ptr, &h->group); + ptr = _m3d_findnl(ptr); + if (h->group != M3D_UNDEF && h->group >= model->numbone) { + M3D_LOG("Unknown bone id as shape group in shape"); + M3D_LOG(pe); + h->group = M3D_UNDEF; + model->errcode = M3D_ERR_SHPE; + } + continue; + } + for (cd = NULL, k = 0; k < (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0])); k++) { + j = (unsigned int)strlen(m3d_commandtypes[k].key); + if (!memcmp(ptr, m3d_commandtypes[k].key, j) && (ptr[j] == ' ' || ptr[j] == '\r' || ptr[j] == '\n')) { + cd = &m3d_commandtypes[k]; + break; + } + } + if (cd) { + j = h->numcmd++; + h->cmd = (m3dc_t *)M3D_REALLOC(h->cmd, h->numcmd * sizeof(m3dc_t)); + if (!h->cmd) goto memerr; + h->cmd[j].type = k; + h->cmd[j].arg = (uint32_t *)M3D_MALLOC(cd->p * sizeof(uint32_t)); + if (!h->cmd[j].arg) goto memerr; + memset(h->cmd[j].arg, 0, cd->p * sizeof(uint32_t)); + for (k = n = 0, l = cd->p; k < l; k++) { + ptr = _m3d_findarg(ptr); + if (!*ptr) goto asciiend; + if (*ptr == '[') { + ptr = _m3d_findarg(ptr + 1); + if (!*ptr) goto asciiend; + } + if (*ptr == ']' || *ptr == '\r' || *ptr == '\n') break; + switch (cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + mi = M3D_UNDEF; + if (*ptr != '\r' && *ptr != '\n') { + pe = _m3d_safestr(ptr, 0); + if (!pe || !*pe) goto asciiend; + for (n = 0; n < model->nummaterial; n++) + if (!strcmp(pe, model->material[n].name)) { + mi = (M3D_INDEX)n; + break; + } + if (mi == M3D_UNDEF && !(model->flags & M3D_FLG_MTLLIB)) { + mi = model->nummaterial++; + model->material = (m3dm_t *)M3D_REALLOC(model->material, + model->nummaterial * sizeof(m3dm_t)); + if (!model->material) goto memerr; + model->material[mi].name = pe; + model->material[mi].numprop = 1; + model->material[mi].prop = NULL; + } else + M3D_FREE(pe); + } + h->cmd[j].arg[k] = mi; + break; + case m3dcp_vc_t: + _m3d_getfloat(ptr, &w); + h->cmd[j].arg[k] = *((uint32_t *)&w); + break; + case m3dcp_va_t: + ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); + n = k + 1; + l += (h->cmd[j].arg[k] - 1) * (cd->p - k - 1); + h->cmd[j].arg = (uint32_t *)M3D_REALLOC(h->cmd[j].arg, l * sizeof(uint32_t)); + if (!h->cmd[j].arg) goto memerr; + memset(&h->cmd[j].arg[k + 1], 0, (l - k - 1) * sizeof(uint32_t)); + break; + case m3dcp_qi_t: + ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); + model->vertex[h->cmd[i].arg[k]].skinid = M3D_INDEXMAX; + break; + default: + ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); + break; + } + } + } else { + M3D_LOG("Unknown shape command in"); + M3D_LOG(h->name); + model->errcode = M3D_ERR_UNKCMD; + } + ptr = _m3d_findnl(ptr); + } + if (!h->numcmd) model->numshape--; + } else + /* annotation labels */ + if (!memcmp(pe, "Labels", 6)) { + pe = _m3d_findarg(pe); + if (!*pe) goto asciiend; + if (*pe == '\r' || *pe == '\n') + pe = NULL; + else + pe = _m3d_safestr(pe, 0); + k = 0; + fn = NULL; + while (*ptr && *ptr != '\r' && *ptr != '\n') { + if (*ptr == 'c') { + ptr = _m3d_findarg(ptr); + if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + ptr = _m3d_gethex(ptr, &k); + } else if (*ptr == 'l') { + ptr = _m3d_findarg(ptr); + if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + fn = _m3d_safestr(ptr, 2); + } else { + i = model->numlabel++; + model->label = (m3dl_t *)M3D_REALLOC(model->label, model->numlabel * sizeof(m3dl_t)); + if (!model->label) goto memerr; + model->label[i].name = pe; + model->label[i].lang = fn; + model->label[i].color = k; + ptr = _m3d_getint(ptr, &j); + model->label[i].vertexid = (M3D_INDEX)j; + ptr = _m3d_findarg(ptr); + if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + model->label[i].text = _m3d_safestr(ptr, 2); + } + ptr = _m3d_findnl(ptr); + } + } else + /* action */ + if (!memcmp(pe, "Action", 6)) { + pe = _m3d_findarg(pe); + if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + pe = _m3d_getint(pe, &k); + pe = _m3d_findarg(pe); + if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + pe = _m3d_safestr(pe, 0); + if (!pe || !*pe) goto asciiend; + i = model->numaction++; + model->action = (m3da_t *)M3D_REALLOC(model->action, model->numaction * sizeof(m3da_t)); + if (!model->action) goto memerr; + a = &model->action[i]; + a->name = pe; + a->durationmsec = k; + /* skip the first frame marker as there's always at least one frame */ + a->numframe = 1; + a->frame = (m3dfr_t *)M3D_MALLOC(sizeof(m3dfr_t)); + if (!a->frame) goto memerr; + a->frame[0].msec = 0; + a->frame[0].numtransform = 0; + a->frame[0].transform = NULL; + i = 0; + if (*ptr == 'f') + ptr = _m3d_findnl(ptr); + while (*ptr && *ptr != '\r' && *ptr != '\n') { + if (*ptr == 'f') { + i = a->numframe++; + a->frame = (m3dfr_t *)M3D_REALLOC(a->frame, a->numframe * sizeof(m3dfr_t)); + if (!a->frame) goto memerr; + ptr = _m3d_findarg(ptr); + ptr = _m3d_getint(ptr, &a->frame[i].msec); + a->frame[i].numtransform = 0; + a->frame[i].transform = NULL; + } else { + j = a->frame[i].numtransform++; + a->frame[i].transform = (m3dtr_t *)M3D_REALLOC(a->frame[i].transform, + a->frame[i].numtransform * sizeof(m3dtr_t)); + if (!a->frame[i].transform) goto memerr; + ptr = _m3d_getint(ptr, &k); + ptr = _m3d_findarg(ptr); + if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + a->frame[i].transform[j].boneid = (M3D_INDEX)k; + ptr = _m3d_getint(ptr, &k); + ptr = _m3d_findarg(ptr); + if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + a->frame[i].transform[j].pos = (M3D_INDEX)k; + ptr = _m3d_getint(ptr, &k); + if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + a->frame[i].transform[j].ori = (M3D_INDEX)k; + model->vertex[k].skinid = M3D_INDEXMAX; + } + ptr = _m3d_findnl(ptr); + } + } else + /* inlined assets chunk */ + if (!memcmp(pe, "Assets", 6)) { + while (*ptr && *ptr != '\r' && *ptr != '\n') { + if (readfilecb) { + pe = _m3d_safestr(ptr, 2); + if (!pe || !*pe) goto asciiend; + i = model->numinlined++; + model->inlined = (m3di_t *)M3D_REALLOC(model->inlined, model->numinlined * sizeof(m3di_t)); + if (!model->inlined) goto memerr; + t = &model->inlined[i]; + model->inlined[i].data = (*readfilecb)(pe, &model->inlined[i].length); + if (model->inlined[i].data) { + fn = strrchr(pe, '.'); + if (fn && (fn[1] == 'p' || fn[1] == 'P') && (fn[2] == 'n' || fn[2] == 'N') && + (fn[3] == 'g' || fn[3] == 'G')) *fn = 0; + fn = strrchr(pe, '/'); + if (!fn) fn = strrchr(pe, '\\'); + if (!fn) + fn = pe; + else + fn++; + model->inlined[i].name = _m3d_safestr(fn, 0); + } else + model->numinlined--; + M3D_FREE(pe); + } + ptr = _m3d_findnl(ptr); + } + } else + /* extra chunks */ + if (!memcmp(pe, "Extra", 5)) { + pe = _m3d_findarg(pe); + if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + buff = (unsigned char *)_m3d_findnl(ptr); + k = ((uint32_t)((uintptr_t)buff - (uintptr_t)ptr) / 3) + 1; + i = model->numextra++; + model->extra = (m3dchunk_t **)M3D_REALLOC(model->extra, model->numextra * sizeof(m3dchunk_t *)); + if (!model->extra) goto memerr; + model->extra[i] = (m3dchunk_t *)M3D_MALLOC(k + sizeof(m3dchunk_t)); + if (!model->extra[i]) goto memerr; + memcpy(&model->extra[i]->magic, pe, 4); + model->extra[i]->length = sizeof(m3dchunk_t); + pe = (char *)model->extra[i] + sizeof(m3dchunk_t); + while (*ptr && *ptr != '\r' && *ptr != '\n') { + ptr = _m3d_gethex(ptr, &k); + *pe++ = (uint8_t)k; + model->extra[i]->length++; + } + } else + goto asciiend; + } + model->errcode = M3D_SUCCESS; + asciiend: + setlocale(LC_NUMERIC, ol); + goto postprocess; + } + /* Binary variant */ + if (!M3D_CHUNKMAGIC(data + 8, 'H', 'E', 'A', 'D')) { + buff = (unsigned char *)stbi_zlib_decode_malloc_guesssize_headerflag((const char *)data + 8, ((m3dchunk_t *)data)->length - 8, + 4096, (int *)&len, 1); + if (!buff || !len || !M3D_CHUNKMAGIC(buff, 'H', 'E', 'A', 'D')) { + if (buff) M3D_FREE(buff); + M3D_FREE(model); + return NULL; + } + buff = (unsigned char *)M3D_REALLOC(buff, len); + model->flags |= M3D_FLG_FREERAW; /* mark that we have to free the raw buffer */ + data = buff; +#ifdef M3D_PROFILING + gettimeofday(&tv1, NULL); + tvd.tv_sec = tv1.tv_sec - tv0.tv_sec; + tvd.tv_usec = tv1.tv_usec - tv0.tv_usec; + if (tvd.tv_usec < 0) { + tvd.tv_sec--; + tvd.tv_usec += 1000000L; + } + printf(" Deflate model %ld.%06ld sec\n", tvd.tv_sec, tvd.tv_usec); + memcpy(&tv0, &tv1, sizeof(struct timeval)); +#endif + } else { + len = ((m3dhdr_t *)data)->length; + data += 8; + } + model->raw = (m3dhdr_t *)data; + end = data + len; + + /* parse header */ + data += sizeof(m3dhdr_t); + M3D_LOG(data); + model->name = (char *)data; + for (; data < end && *data; data++) { + }; + data++; + model->license = (char *)data; + for (; data < end && *data; data++) { + }; + data++; + model->author = (char *)data; + for (; data < end && *data; data++) { + }; + data++; + model->desc = (char *)data; + chunk = (unsigned char *)model->raw + model->raw->length; + model->scale = (M3D_FLOAT)model->raw->scale; + if (model->scale <= (M3D_FLOAT)0.0) model->scale = (M3D_FLOAT)1.0; + model->vc_s = 1 << ((model->raw->types >> 0) & 3); /* vertex coordinate size */ + model->vi_s = 1 << ((model->raw->types >> 2) & 3); /* vertex index size */ + model->si_s = 1 << ((model->raw->types >> 4) & 3); /* string offset size */ + model->ci_s = 1 << ((model->raw->types >> 6) & 3); /* color index size */ + model->ti_s = 1 << ((model->raw->types >> 8) & 3); /* tmap index size */ + model->bi_s = 1 << ((model->raw->types >> 10) & 3); /* bone index size */ + model->nb_s = 1 << ((model->raw->types >> 12) & 3); /* number of bones per vertex */ + model->sk_s = 1 << ((model->raw->types >> 14) & 3); /* skin index size */ + model->fc_s = 1 << ((model->raw->types >> 16) & 3); /* frame counter size */ + model->hi_s = 1 << ((model->raw->types >> 18) & 3); /* shape index size */ + model->fi_s = 1 << ((model->raw->types >> 20) & 3); /* face index size */ + if (model->ci_s == 8) model->ci_s = 0; /* optional indices */ + if (model->ti_s == 8) model->ti_s = 0; + if (model->bi_s == 8) model->bi_s = 0; + if (model->sk_s == 8) model->sk_s = 0; + if (model->fc_s == 8) model->fc_s = 0; + if (model->hi_s == 8) model->hi_s = 0; + if (model->fi_s == 8) model->fi_s = 0; + + /* variable limit checks */ + if (sizeof(M3D_FLOAT) == 4 && model->vc_s > 4) { + M3D_LOG("Double precision coordinates not supported, truncating to float..."); + model->errcode = M3D_ERR_TRUNC; + } + if (sizeof(M3D_INDEX) == 2 && (model->vi_s > 2 || model->si_s > 2 || model->ci_s > 2 || model->ti_s > 2 || + model->bi_s > 2 || model->sk_s > 2 || model->fc_s > 2 || model->hi_s > 2 || model->fi_s > 2)) { + M3D_LOG("32 bit indices not supported, unable to load model"); + M3D_FREE(model); + return NULL; + } + if (model->vi_s > 4 || model->si_s > 4) { + M3D_LOG("Invalid index size, unable to load model"); + M3D_FREE(model); + return NULL; + } + if (model->nb_s > M3D_NUMBONE) { + M3D_LOG("Model has more bones per vertex than what importer was configured to support"); + model->errcode = M3D_ERR_TRUNC; + } + + /* look for inlined assets in advance, material and procedural chunks may need them */ + buff = chunk; + while (buff < end && !M3D_CHUNKMAGIC(buff, 'O', 'M', 'D', '3')) { + data = buff; + len = ((m3dchunk_t *)data)->length; + buff += len; + if (len < sizeof(m3dchunk_t) || buff >= end) { + M3D_LOG("Invalid chunk size"); + break; + } + len -= sizeof(m3dchunk_t) + model->si_s; + + /* inlined assets */ + if (M3D_CHUNKMAGIC(data, 'A', 'S', 'E', 'T') && len > 0) { + M3D_LOG("Inlined asset"); + i = model->numinlined++; + model->inlined = (m3di_t *)M3D_REALLOC(model->inlined, model->numinlined * sizeof(m3di_t)); + if (!model->inlined) { + memerr: + M3D_LOG("Out of memory"); + model->errcode = M3D_ERR_ALLOC; + return model; + } + data += sizeof(m3dchunk_t); + t = &model->inlined[i]; + M3D_GETSTR(t->name); + M3D_LOG(t->name); + t->data = (uint8_t *)data; + t->length = len; + } + } + + /* parse chunks */ + while (chunk < end && !M3D_CHUNKMAGIC(chunk, 'O', 'M', 'D', '3')) { + data = chunk; + len = ((m3dchunk_t *)chunk)->length; + chunk += len; + if (len < sizeof(m3dchunk_t) || chunk >= end) { + M3D_LOG("Invalid chunk size"); + break; + } + len -= sizeof(m3dchunk_t); + + /* preview chunk */ + if (M3D_CHUNKMAGIC(data, 'P', 'R', 'V', 'W') && len > 0) { + model->preview.length = len; + model->preview.data = data + sizeof(m3dchunk_t); + } else + /* color map */ + if (M3D_CHUNKMAGIC(data, 'C', 'M', 'A', 'P')) { + M3D_LOG("Color map"); + if (model->cmap) { + M3D_LOG("More color map chunks, should be unique"); + model->errcode = M3D_ERR_CMAP; + continue; + } + if (!model->ci_s) { + M3D_LOG("Color map chunk, shouldn't be any"); + model->errcode = M3D_ERR_CMAP; + continue; + } + model->numcmap = len / sizeof(uint32_t); + model->cmap = (uint32_t *)(data + sizeof(m3dchunk_t)); + } else + /* texture map */ + if (M3D_CHUNKMAGIC(data, 'T', 'M', 'A', 'P')) { + M3D_LOG("Texture map"); + if (model->tmap) { + M3D_LOG("More texture map chunks, should be unique"); + model->errcode = M3D_ERR_TMAP; + continue; + } + if (!model->ti_s) { + M3D_LOG("Texture map chunk, shouldn't be any"); + model->errcode = M3D_ERR_TMAP; + continue; + } + reclen = model->vc_s + model->vc_s; + model->numtmap = len / reclen; + model->tmap = (m3dti_t *)M3D_MALLOC(model->numtmap * sizeof(m3dti_t)); + if (!model->tmap) goto memerr; + for (i = 0, data += sizeof(m3dchunk_t); data < chunk; i++) { + switch (model->vc_s) { + case 1: + model->tmap[i].u = (M3D_FLOAT)(data[0]) / (M3D_FLOAT)255.0; + model->tmap[i].v = (M3D_FLOAT)(data[1]) / (M3D_FLOAT)255.0; + break; + case 2: + model->tmap[i].u = (M3D_FLOAT)(*((int16_t *)(data + 0))) / (M3D_FLOAT)65535.0; + model->tmap[i].v = (M3D_FLOAT)(*((int16_t *)(data + 2))) / (M3D_FLOAT)65535.0; + break; + case 4: + model->tmap[i].u = (M3D_FLOAT)(*((float *)(data + 0))); + model->tmap[i].v = (M3D_FLOAT)(*((float *)(data + 4))); + break; + case 8: + model->tmap[i].u = (M3D_FLOAT)(*((double *)(data + 0))); + model->tmap[i].v = (M3D_FLOAT)(*((double *)(data + 8))); + break; + } + data += reclen; + } + } else + /* vertex list */ + if (M3D_CHUNKMAGIC(data, 'V', 'R', 'T', 'S')) { + M3D_LOG("Vertex list"); + if (model->vertex) { + M3D_LOG("More vertex chunks, should be unique"); + model->errcode = M3D_ERR_VRTS; + continue; + } + if (model->ci_s && model->ci_s < 4 && !model->cmap) model->errcode = M3D_ERR_CMAP; + reclen = model->ci_s + model->sk_s + 4 * model->vc_s; + model->numvertex = len / reclen; + model->vertex = (m3dv_t *)M3D_MALLOC(model->numvertex * sizeof(m3dv_t)); + if (!model->vertex) goto memerr; + memset(model->vertex, 0, model->numvertex * sizeof(m3dv_t)); + for (i = 0, data += sizeof(m3dchunk_t); data < chunk && i < model->numvertex; i++) { + switch (model->vc_s) { + case 1: + model->vertex[i].x = (M3D_FLOAT)((int8_t)data[0]) / (M3D_FLOAT)127.0; + model->vertex[i].y = (M3D_FLOAT)((int8_t)data[1]) / (M3D_FLOAT)127.0; + model->vertex[i].z = (M3D_FLOAT)((int8_t)data[2]) / (M3D_FLOAT)127.0; + model->vertex[i].w = (M3D_FLOAT)((int8_t)data[3]) / (M3D_FLOAT)127.0; + data += 4; + break; + case 2: + model->vertex[i].x = (M3D_FLOAT)(*((int16_t *)(data + 0))) / (M3D_FLOAT)32767.0; + model->vertex[i].y = (M3D_FLOAT)(*((int16_t *)(data + 2))) / (M3D_FLOAT)32767.0; + model->vertex[i].z = (M3D_FLOAT)(*((int16_t *)(data + 4))) / (M3D_FLOAT)32767.0; + model->vertex[i].w = (M3D_FLOAT)(*((int16_t *)(data + 6))) / (M3D_FLOAT)32767.0; + data += 8; + break; + case 4: + model->vertex[i].x = (M3D_FLOAT)(*((float *)(data + 0))); + model->vertex[i].y = (M3D_FLOAT)(*((float *)(data + 4))); + model->vertex[i].z = (M3D_FLOAT)(*((float *)(data + 8))); + model->vertex[i].w = (M3D_FLOAT)(*((float *)(data + 12))); + data += 16; + break; + case 8: + model->vertex[i].x = (M3D_FLOAT)(*((double *)(data + 0))); + model->vertex[i].y = (M3D_FLOAT)(*((double *)(data + 8))); + model->vertex[i].z = (M3D_FLOAT)(*((double *)(data + 16))); + model->vertex[i].w = (M3D_FLOAT)(*((double *)(data + 24))); + data += 32; + break; + } + switch (model->ci_s) { + case 1: + model->vertex[i].color = model->cmap ? model->cmap[data[0]] : 0; + data++; + break; + case 2: + model->vertex[i].color = model->cmap ? model->cmap[*((uint16_t *)data)] : 0; + data += 2; + break; + case 4: + model->vertex[i].color = *((uint32_t *)data); + data += 4; + break; + /* case 8: break; */ + } + model->vertex[i].skinid = M3D_UNDEF; + data = _m3d_getidx(data, model->sk_s, &model->vertex[i].skinid); + } + } else + /* skeleton: bone hierarchy and skin */ + if (M3D_CHUNKMAGIC(data, 'B', 'O', 'N', 'E')) { + M3D_LOG("Skeleton"); + if (model->bone) { + M3D_LOG("More bone chunks, should be unique"); + model->errcode = M3D_ERR_BONE; + continue; + } + if (!model->bi_s) { + M3D_LOG("Bone chunk, shouldn't be any"); + model->errcode = M3D_ERR_BONE; + continue; + } + if (!model->vertex) { + M3D_LOG("No vertex chunk before bones"); + model->errcode = M3D_ERR_VRTS; + break; + } + data += sizeof(m3dchunk_t); + model->numbone = 0; + data = _m3d_getidx(data, model->bi_s, &model->numbone); + if (model->numbone) { + model->bone = (m3db_t *)M3D_MALLOC(model->numbone * sizeof(m3db_t)); + if (!model->bone) goto memerr; + } + model->numskin = 0; + data = _m3d_getidx(data, model->sk_s, &model->numskin); + /* read bone hierarchy */ + for (i = 0; i < model->numbone; i++) { + data = _m3d_getidx(data, model->bi_s, &model->bone[i].parent); + M3D_GETSTR(model->bone[i].name); + data = _m3d_getidx(data, model->vi_s, &model->bone[i].pos); + data = _m3d_getidx(data, model->vi_s, &model->bone[i].ori); + model->bone[i].numweight = 0; + model->bone[i].weight = NULL; + } + /* read skin definitions */ + if (model->numskin) { + model->skin = (m3ds_t *)M3D_MALLOC(model->numskin * sizeof(m3ds_t)); + if (!model->skin) goto memerr; + for (i = 0; data < chunk && i < model->numskin; i++) { + for (j = 0; j < M3D_NUMBONE; j++) { + model->skin[i].boneid[j] = M3D_UNDEF; + model->skin[i].weight[j] = (M3D_FLOAT)0.0; + } + memset(&weights, 0, sizeof(weights)); + if (model->nb_s == 1) + weights[0] = 255; + else { + memcpy(&weights, data, model->nb_s); + data += model->nb_s; + } + for (j = 0, w = (M3D_FLOAT)0.0; j < (unsigned int)model->nb_s; j++) { + if (weights[j]) { + if (j >= M3D_NUMBONE) + data += model->bi_s; + else { + model->skin[i].weight[j] = (M3D_FLOAT)(weights[j]) / (M3D_FLOAT)255.0; + w += model->skin[i].weight[j]; + data = _m3d_getidx(data, model->bi_s, &model->skin[i].boneid[j]); + } + } + } + /* this can occur if model has more bones than what the importer is configured to handle */ + if (w != (M3D_FLOAT)1.0 && w != (M3D_FLOAT)0.0) { + for (j = 0; j < M3D_NUMBONE; j++) + model->skin[i].weight[j] /= w; + } + } + } + } else + /* material */ + if (M3D_CHUNKMAGIC(data, 'M', 'T', 'R', 'L')) { + data += sizeof(m3dchunk_t); + M3D_GETSTR(name); + M3D_LOG("Material"); + M3D_LOG(name); + if (model->ci_s < 4 && !model->numcmap) model->errcode = M3D_ERR_CMAP; + for (i = 0; i < model->nummaterial; i++) + if (!strcmp(name, model->material[i].name)) { + model->errcode = M3D_ERR_MTRL; + M3D_LOG("Multiple definitions for material"); + M3D_LOG(name); + name = NULL; + break; + } + if (name) { + i = model->nummaterial++; + if (model->flags & M3D_FLG_MTLLIB) { + m = model->material; + model->material = (m3dm_t *)M3D_MALLOC(model->nummaterial * sizeof(m3dm_t)); + if (!model->material) goto memerr; + memcpy(model->material, m, (model->nummaterial - 1) * sizeof(m3dm_t)); + if (model->texture) { + tx = model->texture; + model->texture = (m3dtx_t *)M3D_MALLOC(model->numtexture * sizeof(m3dtx_t)); + if (!model->texture) goto memerr; + memcpy(model->texture, tx, model->numtexture * sizeof(m3dm_t)); + } + model->flags &= ~M3D_FLG_MTLLIB; + } else { + model->material = (m3dm_t *)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); + if (!model->material) goto memerr; + } + m = &model->material[i]; + m->numprop = 0; + m->name = name; + m->prop = (m3dp_t *)M3D_MALLOC((len / 2) * sizeof(m3dp_t)); + if (!m->prop) goto memerr; + while (data < chunk) { + i = m->numprop++; + m->prop[i].type = *data++; + m->prop[i].value.num = 0; + if (m->prop[i].type >= 128) + k = m3dpf_map; + else { + for (k = 256, j = 0; j < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); j++) + if (m->prop[i].type == m3d_propertytypes[j].id) { + k = m3d_propertytypes[j].format; + break; + } + } + switch (k) { + case m3dpf_color: + switch (model->ci_s) { + case 1: + m->prop[i].value.color = model->cmap ? model->cmap[data[0]] : 0; + data++; + break; + case 2: + m->prop[i].value.color = model->cmap ? model->cmap[*((uint16_t *)data)] : 0; + data += 2; + break; + case 4: + m->prop[i].value.color = *((uint32_t *)data); + data += 4; + break; + } + break; + + case m3dpf_uint8: m->prop[i].value.num = *data++; break; + case m3dpf_uint16: + m->prop[i].value.num = *((uint16_t *)data); + data += 2; + break; + case m3dpf_uint32: + m->prop[i].value.num = *((uint32_t *)data); + data += 4; + break; + case m3dpf_float: + m->prop[i].value.fnum = *((float *)data); + data += 4; + break; + + case m3dpf_map: + M3D_GETSTR(name); + m->prop[i].value.textureid = _m3d_gettx(model, readfilecb, freecb, name); + if (model->errcode == M3D_ERR_ALLOC) goto memerr; + /* this error code only returned if readfilecb was specified */ + if (m->prop[i].value.textureid == M3D_UNDEF) { + M3D_LOG("Texture not found"); + M3D_LOG(m->name); + m->numprop--; + } + break; + + default: + M3D_LOG("Unknown material property in"); + M3D_LOG(m->name); + model->errcode = M3D_ERR_UNKPROP; + data = chunk; + break; + } + } + m->prop = (m3dp_t *)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t)); + if (!m->prop) goto memerr; + } + } else + /* face */ + if (M3D_CHUNKMAGIC(data, 'P', 'R', 'O', 'C')) { + /* procedural surface */ + M3D_GETSTR(name); + M3D_LOG("Procedural surface"); + M3D_LOG(name); + _m3d_getpr(model, readfilecb, freecb, name); + } else if (M3D_CHUNKMAGIC(data, 'M', 'E', 'S', 'H')) { + M3D_LOG("Mesh data"); + /* mesh */ + data += sizeof(m3dchunk_t); + mi = M3D_UNDEF; + am = model->numface; + while (data < chunk) { + k = *data++; + n = k >> 4; + k &= 15; + if (!n) { + /* use material */ + mi = M3D_UNDEF; + M3D_GETSTR(name); + if (name) { + for (j = 0; j < model->nummaterial; j++) + if (!strcmp(name, model->material[j].name)) { + mi = (M3D_INDEX)j; + break; + } + if (mi == M3D_UNDEF) model->errcode = M3D_ERR_MTRL; + } + continue; + } + if (n != 3) { + M3D_LOG("Only triangle mesh supported for now"); + model->errcode = M3D_ERR_UNKMESH; + return model; + } + i = model->numface++; + if (model->numface > am) { + am = model->numface + 4095; + model->face = (m3df_t *)M3D_REALLOC(model->face, am * sizeof(m3df_t)); + if (!model->face) goto memerr; + } + memset(&model->face[i], 255, sizeof(m3df_t)); /* set all index to -1 by default */ + model->face[i].materialid = mi; + for (j = 0; j < n; j++) { + /* vertex */ + data = _m3d_getidx(data, model->vi_s, &model->face[i].vertex[j]); + /* texcoord */ + if (k & 1) + data = _m3d_getidx(data, model->ti_s, &model->face[i].texcoord[j]); + /* normal */ + if (k & 2) + data = _m3d_getidx(data, model->vi_s, &model->face[i].normal[j]); +#ifndef M3D_NONORMALS + if (model->face[i].normal[j] == M3D_UNDEF) neednorm = 1; +#endif + } + } + model->face = (m3df_t *)M3D_REALLOC(model->face, model->numface * sizeof(m3df_t)); + } else if (M3D_CHUNKMAGIC(data, 'S', 'H', 'P', 'E')) { + /* mathematical shape */ + data += sizeof(m3dchunk_t); + M3D_GETSTR(name); + M3D_LOG("Mathematical Shape"); + M3D_LOG(name); + i = model->numshape++; + model->shape = (m3dh_t *)M3D_REALLOC(model->shape, model->numshape * sizeof(m3dh_t)); + if (!model->shape) goto memerr; + h = &model->shape[i]; + h->numcmd = 0; + h->cmd = NULL; + h->name = name; + h->group = M3D_UNDEF; + data = _m3d_getidx(data, model->bi_s, &h->group); + if (h->group != M3D_UNDEF && h->group >= model->numbone) { + M3D_LOG("Unknown bone id as shape group in shape"); + M3D_LOG(name); + h->group = M3D_UNDEF; + model->errcode = M3D_ERR_SHPE; + } + while (data < chunk) { + i = h->numcmd++; + h->cmd = (m3dc_t *)M3D_REALLOC(h->cmd, h->numcmd * sizeof(m3dc_t)); + if (!h->cmd) goto memerr; + h->cmd[i].type = *data++; + if (h->cmd[i].type & 0x80) { + h->cmd[i].type &= 0x7F; + h->cmd[i].type |= (*data++ << 7); + } + if (h->cmd[i].type >= (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0]))) { + M3D_LOG("Unknown shape command in"); + M3D_LOG(h->name); + model->errcode = M3D_ERR_UNKCMD; + break; + } + cd = &m3d_commandtypes[h->cmd[i].type]; + h->cmd[i].arg = (uint32_t *)M3D_MALLOC(cd->p * sizeof(uint32_t)); + if (!h->cmd[i].arg) goto memerr; + memset(h->cmd[i].arg, 0, cd->p * sizeof(uint32_t)); + for (k = n = 0, l = cd->p; k < l; k++) + switch (cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + h->cmd[i].arg[k] = M3D_NOTDEFINED; + M3D_GETSTR(name); + if (name) { + for (n = 0; n < model->nummaterial; n++) + if (!strcmp(name, model->material[n].name)) { + h->cmd[i].arg[k] = n; + break; + } + if (h->cmd[i].arg[k] == M3D_NOTDEFINED) model->errcode = M3D_ERR_MTRL; + } + break; + case m3dcp_vc_t: + f = 0.0f; + switch (model->vc_s) { + case 1: f = (float)((int8_t)data[0]) / 127; break; + case 2: f = (float)(*((int16_t *)(data + 0))) / 32767; break; + case 4: f = (float)(*((float *)(data + 0))); break; + case 8: f = (float)(*((double *)(data + 0))); break; + } + memcpy(&(h->cmd[i].arg[k]), &f, sizeof(uint32_t)); + data += model->vc_s; + break; + case m3dcp_hi_t: data = _m3d_getidx(data, model->hi_s, &h->cmd[i].arg[k]); break; + case m3dcp_fi_t: data = _m3d_getidx(data, model->fi_s, &h->cmd[i].arg[k]); break; + case m3dcp_ti_t: data = _m3d_getidx(data, model->ti_s, &h->cmd[i].arg[k]); break; + case m3dcp_qi_t: + case m3dcp_vi_t: data = _m3d_getidx(data, model->vi_s, &h->cmd[i].arg[k]); break; + case m3dcp_i1_t: data = _m3d_getidx(data, 1, &h->cmd[i].arg[k]); break; + case m3dcp_i2_t: data = _m3d_getidx(data, 2, &h->cmd[i].arg[k]); break; + case m3dcp_i4_t: data = _m3d_getidx(data, 4, &h->cmd[i].arg[k]); break; + case m3dcp_va_t: + data = _m3d_getidx(data, 4, &h->cmd[i].arg[k]); + n = k + 1; + l += (h->cmd[i].arg[k] - 1) * (cd->p - k - 1); + h->cmd[i].arg = (uint32_t *)M3D_REALLOC(h->cmd[i].arg, l * sizeof(uint32_t)); + if (!h->cmd[i].arg) goto memerr; + memset(&h->cmd[i].arg[k + 1], 0, (l - k - 1) * sizeof(uint32_t)); + break; + } + } + } else + /* annotation label list */ + if (M3D_CHUNKMAGIC(data, 'L', 'B', 'L', 'S')) { + data += sizeof(m3dchunk_t); + M3D_GETSTR(name); + M3D_GETSTR(lang); + M3D_LOG("Label list"); + if (name) { + M3D_LOG(name); + } + if (lang) { + M3D_LOG(lang); + } + if (model->ci_s && model->ci_s < 4 && !model->cmap) model->errcode = M3D_ERR_CMAP; + k = 0; + switch (model->ci_s) { + case 1: + k = model->cmap ? model->cmap[data[0]] : 0; + data++; + break; + case 2: + k = model->cmap ? model->cmap[*((uint16_t *)data)] : 0; + data += 2; + break; + case 4: + k = *((uint32_t *)data); + data += 4; + break; + /* case 8: break; */ + } + reclen = model->vi_s + model->si_s; + i = model->numlabel; + model->numlabel += len / reclen; + model->label = (m3dl_t *)M3D_REALLOC(model->label, model->numlabel * sizeof(m3dl_t)); + if (!model->label) goto memerr; + memset(&model->label[i], 0, (model->numlabel - i) * sizeof(m3dl_t)); + for (; data < chunk && i < model->numlabel; i++) { + model->label[i].name = name; + model->label[i].lang = lang; + model->label[i].color = k; + data = _m3d_getidx(data, model->vi_s, &model->label[i].vertexid); + M3D_GETSTR(model->label[i].text); + } + } else + /* action */ + if (M3D_CHUNKMAGIC(data, 'A', 'C', 'T', 'N')) { + M3D_LOG("Action"); + i = model->numaction++; + model->action = (m3da_t *)M3D_REALLOC(model->action, model->numaction * sizeof(m3da_t)); + if (!model->action) goto memerr; + a = &model->action[i]; + data += sizeof(m3dchunk_t); + M3D_GETSTR(a->name); + M3D_LOG(a->name); + a->numframe = *((uint16_t *)data); + data += 2; + if (a->numframe < 1) { + model->numaction--; + } else { + a->durationmsec = *((uint32_t *)data); + data += 4; + a->frame = (m3dfr_t *)M3D_MALLOC(a->numframe * sizeof(m3dfr_t)); + if (!a->frame) goto memerr; + for (i = 0; data < chunk && i < a->numframe; i++) { + a->frame[i].msec = *((uint32_t *)data); + data += 4; + a->frame[i].numtransform = 0; + a->frame[i].transform = NULL; + data = _m3d_getidx(data, model->fc_s, &a->frame[i].numtransform); + if (a->frame[i].numtransform > 0) { + a->frame[i].transform = (m3dtr_t *)M3D_MALLOC(a->frame[i].numtransform * sizeof(m3dtr_t)); + for (j = 0; j < a->frame[i].numtransform; j++) { + data = _m3d_getidx(data, model->bi_s, &a->frame[i].transform[j].boneid); + data = _m3d_getidx(data, model->vi_s, &a->frame[i].transform[j].pos); + data = _m3d_getidx(data, model->vi_s, &a->frame[i].transform[j].ori); + } + } + } + } + } else { + i = model->numextra++; + model->extra = (m3dchunk_t **)M3D_REALLOC(model->extra, model->numextra * sizeof(m3dchunk_t *)); + if (!model->extra) goto memerr; + model->extra[i] = (m3dchunk_t *)data; + } + } + /* calculate normals, normalize skin weights, create bone/vertex cross-references and calculate transform matrices */ +postprocess: + if (model) { + M3D_LOG("Post-process"); +#ifdef M3D_PROFILING + gettimeofday(&tv1, NULL); + tvd.tv_sec = tv1.tv_sec - tv0.tv_sec; + tvd.tv_usec = tv1.tv_usec - tv0.tv_usec; + if (tvd.tv_usec < 0) { + tvd.tv_sec--; + tvd.tv_usec += 1000000L; + } + printf(" Parsing chunks %ld.%06ld sec\n", tvd.tv_sec, tvd.tv_usec); +#endif +#ifndef M3D_NONORMALS + if (model->numface && model->face && neednorm) { + /* if they are missing, calculate triangle normals into a temporary buffer */ + norm = (m3dv_t *)M3D_MALLOC(model->numface * sizeof(m3dv_t)); + if (!norm) goto memerr; + for (i = 0, n = model->numvertex; i < model->numface; i++) + if (model->face[i].normal[0] == M3D_UNDEF) { + v0 = &model->vertex[model->face[i].vertex[0]]; + v1 = &model->vertex[model->face[i].vertex[1]]; + v2 = &model->vertex[model->face[i].vertex[2]]; + va.x = v1->x - v0->x; + va.y = v1->y - v0->y; + va.z = v1->z - v0->z; + vb.x = v2->x - v0->x; + vb.y = v2->y - v0->y; + vb.z = v2->z - v0->z; + v0 = &norm[i]; + v0->x = (va.y * vb.z) - (va.z * vb.y); + v0->y = (va.z * vb.x) - (va.x * vb.z); + v0->z = (va.x * vb.y) - (va.y * vb.x); + w = _m3d_rsq((v0->x * v0->x) + (v0->y * v0->y) + (v0->z * v0->z)); + v0->x *= w; + v0->y *= w; + v0->z *= w; + model->face[i].normal[0] = model->face[i].vertex[0] + n; + model->face[i].normal[1] = model->face[i].vertex[1] + n; + model->face[i].normal[2] = model->face[i].vertex[2] + n; + } + /* this is the fast way, we don't care if a normal is repeated in model->vertex */ + M3D_LOG("Generating normals"); + model->flags |= M3D_FLG_GENNORM; + model->numvertex <<= 1; + model->vertex = (m3dv_t *)M3D_REALLOC(model->vertex, model->numvertex * sizeof(m3dv_t)); + if (!model->vertex) goto memerr; + memset(&model->vertex[n], 0, n * sizeof(m3dv_t)); + for (i = 0; i < model->numface; i++) + for (j = 0; j < 3; j++) { + v0 = &model->vertex[model->face[i].vertex[j] + n]; + v0->x += norm[i].x; + v0->y += norm[i].y; + v0->z += norm[i].z; + } + /* for each vertex, take the average of the temporary normals and use that */ + for (i = 0, v0 = &model->vertex[n]; i < n; i++, v0++) { + w = _m3d_rsq((v0->x * v0->x) + (v0->y * v0->y) + (v0->z * v0->z)); + v0->x *= w; + v0->y *= w; + v0->z *= w; + v0->skinid = M3D_UNDEF; + } + M3D_FREE(norm); + } +#endif + if (model->numbone && model->bone && model->numskin && model->skin && model->numvertex && model->vertex) { +#ifndef M3D_NOWEIGHTS + M3D_LOG("Generating weight cross-reference"); + for (i = 0; i < model->numvertex; i++) { + if (model->vertex[i].skinid < model->numskin) { + sk = &model->skin[model->vertex[i].skinid]; + w = (M3D_FLOAT)0.0; + for (j = 0; j < M3D_NUMBONE && sk->boneid[j] != M3D_UNDEF && sk->weight[j] > (M3D_FLOAT)0.0; j++) + w += sk->weight[j]; + for (j = 0; j < M3D_NUMBONE && sk->boneid[j] != M3D_UNDEF && sk->weight[j] > (M3D_FLOAT)0.0; j++) { + sk->weight[j] /= w; + b = &model->bone[sk->boneid[j]]; + k = b->numweight++; + b->weight = (m3dw_t *)M3D_REALLOC(b->weight, b->numweight * sizeof(m3da_t)); + if (!b->weight) goto memerr; + b->weight[k].vertexid = i; + b->weight[k].weight = sk->weight[j]; + } + } + } +#endif +#ifndef M3D_NOANIMATION + M3D_LOG("Calculating bone transformation matrices"); + for (i = 0; i < model->numbone; i++) { + b = &model->bone[i]; + if (model->bone[i].parent == M3D_UNDEF) { + _m3d_mat((M3D_FLOAT *)&b->mat4, &model->vertex[b->pos], &model->vertex[b->ori]); + } else { + _m3d_mat((M3D_FLOAT *)&r, &model->vertex[b->pos], &model->vertex[b->ori]); + _m3d_mul((M3D_FLOAT *)&b->mat4, (M3D_FLOAT *)&model->bone[b->parent].mat4, (M3D_FLOAT *)&r); + } + } + for (i = 0; i < model->numbone; i++) + _m3d_inv((M3D_FLOAT *)&model->bone[i].mat4); +#endif + } +#ifdef M3D_PROFILING + gettimeofday(&tv0, NULL); + tvd.tv_sec = tv0.tv_sec - tv1.tv_sec; + tvd.tv_usec = tv0.tv_usec - tv1.tv_usec; + if (tvd.tv_usec < 0) { + tvd.tv_sec--; + tvd.tv_usec += 1000000L; + } + printf(" Post-process %ld.%06ld sec\n", tvd.tv_sec, tvd.tv_usec); +#endif + } + return model; +} + +/** + * Calculates skeletons for animation frames, returns a working copy (should be freed after use) + */ +m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t *skeleton) { + unsigned int i; + M3D_INDEX s = frameid; + m3dfr_t *fr; + + if (!model || !model->numbone || !model->bone || (actionid != M3D_UNDEF && (!model->action || actionid >= model->numaction || frameid >= model->action[actionid].numframe))) { + model->errcode = M3D_ERR_UNKFRAME; + return skeleton; + } + model->errcode = M3D_SUCCESS; + if (!skeleton) { + skeleton = (m3dtr_t *)M3D_MALLOC(model->numbone * sizeof(m3dtr_t)); + if (!skeleton) { + model->errcode = M3D_ERR_ALLOC; + return NULL; + } + goto gen; + } + if (actionid == M3D_UNDEF || !frameid) { + gen: + s = 0; + for (i = 0; i < model->numbone; i++) { + skeleton[i].boneid = i; + skeleton[i].pos = model->bone[i].pos; + skeleton[i].ori = model->bone[i].ori; + } + } + if (actionid < model->numaction && (frameid || !model->action[actionid].frame[0].msec)) { + for (; s <= frameid; s++) { + fr = &model->action[actionid].frame[s]; + for (i = 0; i < fr->numtransform; i++) { + skeleton[fr->transform[i].boneid].pos = fr->transform[i].pos; + skeleton[fr->transform[i].boneid].ori = fr->transform[i].ori; + } + } + } + return skeleton; +} + +#ifndef M3D_NOANIMATION +/** + * Returns interpolated animation-pose, a working copy (should be freed after use) + */ +m3db_t *m3d_pose(m3d_t *model, M3D_INDEX actionid, uint32_t msec) { + unsigned int i, j, l; + M3D_FLOAT r[16], t, c, d, s; + m3db_t *ret; + m3dv_t *v, *p, *f; + m3dtr_t *tmp; + m3dfr_t *fr; + + if (!model || !model->numbone || !model->bone) { + model->errcode = M3D_ERR_UNKFRAME; + return NULL; + } + ret = (m3db_t *)M3D_MALLOC(model->numbone * sizeof(m3db_t)); + if (!ret) { + model->errcode = M3D_ERR_ALLOC; + return NULL; + } + memcpy(ret, model->bone, model->numbone * sizeof(m3db_t)); + for (i = 0; i < model->numbone; i++) + _m3d_inv((M3D_FLOAT *)&ret[i].mat4); + if (!model->action || actionid >= model->numaction) { + model->errcode = M3D_ERR_UNKFRAME; + return ret; + } + msec %= model->action[actionid].durationmsec; + model->errcode = M3D_SUCCESS; + fr = &model->action[actionid].frame[0]; + for (j = l = 0; j < model->action[actionid].numframe && model->action[actionid].frame[j].msec <= msec; j++) { + fr = &model->action[actionid].frame[j]; + l = fr->msec; + for (i = 0; i < fr->numtransform; i++) { + ret[fr->transform[i].boneid].pos = fr->transform[i].pos; + ret[fr->transform[i].boneid].ori = fr->transform[i].ori; + } + } + if (l != msec) { + model->vertex = (m3dv_t *)M3D_REALLOC(model->vertex, (model->numvertex + 2 * model->numbone) * sizeof(m3dv_t)); + if (!model->vertex) { + free(ret); + model->errcode = M3D_ERR_ALLOC; + return NULL; + } + tmp = (m3dtr_t *)M3D_MALLOC(model->numbone * sizeof(m3dtr_t)); + if (tmp) { + for (i = 0; i < model->numbone; i++) { + tmp[i].pos = ret[i].pos; + tmp[i].ori = ret[i].ori; + } + fr = &model->action[actionid].frame[j % model->action[actionid].numframe]; + t = l >= fr->msec ? (M3D_FLOAT)1.0 : (M3D_FLOAT)(msec - l) / (M3D_FLOAT)(fr->msec - l); + for (i = 0; i < fr->numtransform; i++) { + tmp[fr->transform[i].boneid].pos = fr->transform[i].pos; + tmp[fr->transform[i].boneid].ori = fr->transform[i].ori; + } + for (i = 0, j = model->numvertex; i < model->numbone; i++) { + /* interpolation of position */ + if (ret[i].pos != tmp[i].pos) { + p = &model->vertex[ret[i].pos]; + f = &model->vertex[tmp[i].pos]; + v = &model->vertex[j]; + v->x = p->x + t * (f->x - p->x); + v->y = p->y + t * (f->y - p->y); + v->z = p->z + t * (f->z - p->z); + ret[i].pos = j++; + } + /* interpolation of orientation */ + if (ret[i].ori != tmp[i].ori) { + p = &model->vertex[ret[i].ori]; + f = &model->vertex[tmp[i].ori]; + v = &model->vertex[j]; + d = p->w * f->w + p->x * f->x + p->y * f->y + p->z * f->z; + if (d < 0) { + d = -d; + s = (M3D_FLOAT)-1.0; + } else + s = (M3D_FLOAT)1.0; +#if 0 + /* don't use SLERP, requires two more variables, libm linkage and it is slow (but nice) */ + a = (M3D_FLOAT)1.0 - t; b = t; + if(d < (M3D_FLOAT)0.999999) { c = acosf(d); b = 1 / sinf(c); a = sinf(a * c) * b; b *= sinf(t * c) * s; } + v->x = p->x * a + f->x * b; + v->y = p->y * a + f->y * b; + v->z = p->z * a + f->z * b; + v->w = p->w * a + f->w * b; +#else + /* approximated NLERP, original approximation by Arseny Kapoulkine, heavily optimized by me */ + c = t - (M3D_FLOAT)0.5; + t += t * c * (t - (M3D_FLOAT)1.0) * (((M3D_FLOAT)1.0904 + d * ((M3D_FLOAT)-3.2452 + d * ((M3D_FLOAT)3.55645 - d * (M3D_FLOAT)1.43519))) * c * c + ((M3D_FLOAT)0.848013 + d * ((M3D_FLOAT)-1.06021 + d * (M3D_FLOAT)0.215638))); + v->x = p->x + t * (s * f->x - p->x); + v->y = p->y + t * (s * f->y - p->y); + v->z = p->z + t * (s * f->z - p->z); + v->w = p->w + t * (s * f->w - p->w); + d = _m3d_rsq(v->w * v->w + v->x * v->x + v->y * v->y + v->z * v->z); + v->x *= d; + v->y *= d; + v->z *= d; + v->w *= d; +#endif + ret[i].ori = j++; + } + } + M3D_FREE(tmp); + } + } + for (i = 0; i < model->numbone; i++) { + if (ret[i].parent == M3D_UNDEF) { + _m3d_mat((M3D_FLOAT *)&ret[i].mat4, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]); + } else { + _m3d_mat((M3D_FLOAT *)&r, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]); + _m3d_mul((M3D_FLOAT *)&ret[i].mat4, (M3D_FLOAT *)&ret[ret[i].parent].mat4, (M3D_FLOAT *)&r); + } + } + return ret; +} + +#endif /* M3D_NOANIMATION */ + +#endif /* M3D_IMPLEMENTATION */ + +#if !defined(M3D_NODUP) && (!defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER)) +/** + * Free the in-memory model + */ +void m3d_free(m3d_t *model) { + unsigned int i, j; + + if (!model) return; + /* if model imported from ASCII, we have to free all strings as well */ + if (model->flags & M3D_FLG_FREESTR) { + if (model->name) M3D_FREE(model->name); + if (model->license) M3D_FREE(model->license); + if (model->author) M3D_FREE(model->author); + if (model->desc) M3D_FREE(model->desc); + if (model->bone) + for (i = 0; i < model->numbone; i++) + if (model->bone[i].name) + M3D_FREE(model->bone[i].name); + if (model->shape) + for (i = 0; i < model->numshape; i++) + if (model->shape[i].name) + M3D_FREE(model->shape[i].name); + if (model->material) + for (i = 0; i < model->nummaterial; i++) + if (model->material[i].name) + M3D_FREE(model->material[i].name); + if (model->action) + for (i = 0; i < model->numaction; i++) + if (model->action[i].name) + M3D_FREE(model->action[i].name); + if (model->texture) + for (i = 0; i < model->numtexture; i++) + if (model->texture[i].name) + M3D_FREE(model->texture[i].name); + if (model->inlined) + for (i = 0; i < model->numinlined; i++) { + if (model->inlined[i].name) + M3D_FREE(model->inlined[i].name); + if (model->inlined[i].data) + M3D_FREE(model->inlined[i].data); + } + if (model->extra) + for (i = 0; i < model->numextra; i++) + if (model->extra[i]) + M3D_FREE(model->extra[i]); + if (model->label) + for (i = 0; i < model->numlabel; i++) { + if (model->label[i].name) { + for (j = i + 1; j < model->numlabel; j++) + if (model->label[j].name == model->label[i].name) + model->label[j].name = NULL; + M3D_FREE(model->label[i].name); + } + if (model->label[i].lang) { + for (j = i + 1; j < model->numlabel; j++) + if (model->label[j].lang == model->label[i].lang) + model->label[j].lang = NULL; + M3D_FREE(model->label[i].lang); + } + if (model->label[i].text) + M3D_FREE(model->label[i].text); + } + if (model->preview.data) + M3D_FREE(model->preview.data); + } + if (model->flags & M3D_FLG_FREERAW) M3D_FREE(model->raw); + + if (model->tmap) M3D_FREE(model->tmap); + if (model->bone) { + for (i = 0; i < model->numbone; i++) + if (model->bone[i].weight) + M3D_FREE(model->bone[i].weight); + M3D_FREE(model->bone); + } + if (model->skin) M3D_FREE(model->skin); + if (model->vertex) M3D_FREE(model->vertex); + if (model->face) M3D_FREE(model->face); + if (model->shape) { + for (i = 0; i < model->numshape; i++) { + if (model->shape[i].cmd) { + for (j = 0; j < model->shape[i].numcmd; j++) + if (model->shape[i].cmd[j].arg) M3D_FREE(model->shape[i].cmd[j].arg); + M3D_FREE(model->shape[i].cmd); + } + } + M3D_FREE(model->shape); + } + if (model->material && !(model->flags & M3D_FLG_MTLLIB)) { + for (i = 0; i < model->nummaterial; i++) + if (model->material[i].prop) M3D_FREE(model->material[i].prop); + M3D_FREE(model->material); + } + if (model->texture) { + for (i = 0; i < model->numtexture; i++) + if (model->texture[i].d) M3D_FREE(model->texture[i].d); + M3D_FREE(model->texture); + } + if (model->action) { + for (i = 0; i < model->numaction; i++) { + if (model->action[i].frame) { + for (j = 0; j < model->action[i].numframe; j++) + if (model->action[i].frame[j].transform) M3D_FREE(model->action[i].frame[j].transform); + M3D_FREE(model->action[i].frame); + } + } + M3D_FREE(model->action); + } + if (model->label) M3D_FREE(model->label); + if (model->inlined) M3D_FREE(model->inlined); + if (model->extra) M3D_FREE(model->extra); + free(model); +} +#endif + +#ifdef M3D_EXPORTER +typedef struct { + char *str; + uint32_t offs; +} m3dstr_t; + +typedef struct { + m3dti_t data; + M3D_INDEX oldidx; + M3D_INDEX newidx; +} m3dtisave_t; + +typedef struct { + m3dv_t data; + M3D_INDEX oldidx; + M3D_INDEX newidx; + unsigned char norm; +} m3dvsave_t; + +typedef struct { + m3ds_t data; + M3D_INDEX oldidx; + M3D_INDEX newidx; +} m3dssave_t; + +typedef struct { + m3df_t data; + int group; + uint8_t opacity; +} m3dfsave_t; + +/* create unique list of strings */ +static m3dstr_t *_m3d_addstr(m3dstr_t *str, uint32_t *numstr, char *s) { + uint32_t i; + if (!s || !*s) return str; + if (str) { + for (i = 0; i < *numstr; i++) + if (str[i].str == s || !strcmp(str[i].str, s)) return str; + } + str = (m3dstr_t *)M3D_REALLOC(str, ((*numstr) + 1) * sizeof(m3dstr_t)); + str[*numstr].str = s; + str[*numstr].offs = 0; + (*numstr)++; + return str; +} + +/* add strings to header */ +m3dhdr_t *_m3d_addhdr(m3dhdr_t *h, m3dstr_t *s) { + int i; + char *safe = _m3d_safestr(s->str, 0); + i = (int)strlen(safe); + h = (m3dhdr_t *)M3D_REALLOC(h, h->length + i + 1); + if (!h) { + M3D_FREE(safe); + return NULL; + } + memcpy((uint8_t *)h + h->length, safe, i + 1); + s->offs = h->length - 16; + h->length += i + 1; + M3D_FREE(safe); + return h; +} + +/* return offset of string */ +static uint32_t _m3d_stridx(m3dstr_t *str, uint32_t numstr, char *s) { + uint32_t i; + char *safe; + if (!s || !*s) return 0; + if (str) { + safe = _m3d_safestr(s, 0); + if (!safe) return 0; + if (!*safe) { + free(safe); + return 0; + } + for (i = 0; i < numstr; i++) + if (!strcmp(str[i].str, s)) { + free(safe); + return str[i].offs; + } + free(safe); + } + return 0; +} + +/* compare to faces by their material */ +static int _m3d_facecmp(const void *a, const void *b) { + const m3dfsave_t *A = (const m3dfsave_t *)a, *B = (const m3dfsave_t *)b; + return A->group != B->group ? A->group - B->group : (A->opacity != B->opacity ? (int)B->opacity - (int)A->opacity : (int)A->data.materialid - (int)B->data.materialid); +} +/* compare face groups */ +static int _m3d_grpcmp(const void *a, const void *b) { + return *((uint32_t *)a) - *((uint32_t *)b); +} +/* compare UVs */ +static int _m3d_ticmp(const void *a, const void *b) { + return memcmp(a, b, sizeof(m3dti_t)); +} +/* compare skin groups */ +static int _m3d_skincmp(const void *a, const void *b) { + return memcmp(a, b, sizeof(m3ds_t)); +} +/* compare vertices */ +static int _m3d_vrtxcmp(const void *a, const void *b) { + int c = memcmp(a, b, 3 * sizeof(M3D_FLOAT)); + if (c) return c; + c = ((m3dvsave_t *)a)->norm - ((m3dvsave_t *)b)->norm; + if (c) return c; + return memcmp(a, b, sizeof(m3dv_t)); +} +/* compare labels */ +static _inline int _m3d_strcmp(char *a, char *b) { + if (a == NULL && b != NULL) return -1; + if (a != NULL && b == NULL) return 1; + if (a == NULL && b == NULL) return 0; + return strcmp(a, b); +} +static int _m3d_lblcmp(const void *a, const void *b) { + const m3dl_t *A = (const m3dl_t *)a, *B = (const m3dl_t *)b; + int c = _m3d_strcmp(A->lang, B->lang); + if (!c) c = _m3d_strcmp(A->name, B->name); + if (!c) c = _m3d_strcmp(A->text, B->text); + return c; +} +/* compare two colors by HSV value */ +_inline static int _m3d_cmapcmp(const void *a, const void *b) { + uint8_t *A = (uint8_t *)a, *B = (uint8_t *)b; + _register int m, vA, vB; + /* get HSV value for A */ + m = A[2] < A[1] ? A[2] : A[1]; + if (A[0] < m) m = A[0]; + vA = A[2] > A[1] ? A[2] : A[1]; + if (A[0] > vA) vA = A[0]; + /* get HSV value for B */ + m = B[2] < B[1] ? B[2] : B[1]; + if (B[0] < m) m = B[0]; + vB = B[2] > B[1] ? B[2] : B[1]; + if (B[0] > vB) vB = B[0]; + return vA - vB; +} + +/* create sorted list of colors */ +static uint32_t *_m3d_addcmap(uint32_t *cmap, uint32_t *numcmap, uint32_t color) { + uint32_t i; + if (cmap) { + for (i = 0; i < *numcmap; i++) + if (cmap[i] == color) return cmap; + } + cmap = (uint32_t *)M3D_REALLOC(cmap, ((*numcmap) + 1) * sizeof(uint32_t)); + for (i = 0; i < *numcmap && _m3d_cmapcmp(&color, &cmap[i]) > 0; i++) + ; + if (i < *numcmap) memmove(&cmap[i + 1], &cmap[i], ((*numcmap) - i) * sizeof(uint32_t)); + cmap[i] = color; + (*numcmap)++; + return cmap; +} + +/* look up a color and return its index */ +static uint32_t _m3d_cmapidx(uint32_t *cmap, uint32_t numcmap, uint32_t color) { + uint32_t i; + if (numcmap >= 65536) + return color; + for (i = 0; i < numcmap; i++) + if (cmap[i] == color) return i; + return 0; +} + +/* add index to output */ +static unsigned char *_m3d_addidx(unsigned char *out, char type, uint32_t idx) { + switch (type) { + case 1: *out++ = (uint8_t)(idx); break; + case 2: + *((uint16_t *)out) = (uint16_t)(idx); + out += 2; + break; + case 4: + *((uint32_t *)out) = (uint32_t)(idx); + out += 4; + break; + /* case 0: case 8: break; */ + } + return out; +} + +/* round a vertex position */ +static void _m3d_round(int quality, m3dv_t *src, m3dv_t *dst) { + _register int t; + /* copy additional attributes */ + if (src != dst) memcpy(dst, src, sizeof(m3dv_t)); + /* round according to quality */ + switch (quality) { + case M3D_EXP_INT8: + t = (int)(src->x * 127 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); + dst->x = (M3D_FLOAT)t / (M3D_FLOAT)127.0; + t = (int)(src->y * 127 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); + dst->y = (M3D_FLOAT)t / (M3D_FLOAT)127.0; + t = (int)(src->z * 127 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); + dst->z = (M3D_FLOAT)t / (M3D_FLOAT)127.0; + t = (int)(src->w * 127 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); + dst->w = (M3D_FLOAT)t / (M3D_FLOAT)127.0; + break; + case M3D_EXP_INT16: + t = (int)(src->x * 32767 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); + dst->x = (M3D_FLOAT)t / (M3D_FLOAT)32767.0; + t = (int)(src->y * 32767 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); + dst->y = (M3D_FLOAT)t / (M3D_FLOAT)32767.0; + t = (int)(src->z * 32767 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); + dst->z = (M3D_FLOAT)t / (M3D_FLOAT)32767.0; + t = (int)(src->w * 32767 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); + dst->w = (M3D_FLOAT)t / (M3D_FLOAT)32767.0; + break; + } + if (dst->x == (M3D_FLOAT)-0.0) dst->x = (M3D_FLOAT)0.0; + if (dst->y == (M3D_FLOAT)-0.0) dst->y = (M3D_FLOAT)0.0; + if (dst->z == (M3D_FLOAT)-0.0) dst->z = (M3D_FLOAT)0.0; + if (dst->w == (M3D_FLOAT)-0.0) dst->w = (M3D_FLOAT)0.0; +} + +/* add a bone to ascii output */ +static char *_m3d_prtbone(char *ptr, m3db_t *bone, M3D_INDEX numbone, M3D_INDEX parent, uint32_t level, M3D_INDEX *vrtxidx) { + uint32_t i, j; + char *sn; + + if (level > M3D_BONEMAXLEVEL || !bone) return ptr; + for (i = 0; i < numbone; i++) { + if (bone[i].parent == parent) { + for (j = 0; j < level; j++) + *ptr++ = '/'; + sn = _m3d_safestr(bone[i].name, 0); + ptr += sprintf(ptr, "%d %d %s\r\n", vrtxidx[bone[i].pos], vrtxidx[bone[i].ori], sn); + M3D_FREE(sn); + ptr = _m3d_prtbone(ptr, bone, numbone, i, level + 1, vrtxidx); + } + } + return ptr; +} + +/** + * Function to encode an in-memory model into on storage Model 3D format + */ +unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size) { + const char *ol; + char *ptr; + char vc_s, vi_s, si_s, ci_s, ti_s, bi_s, nb_s, sk_s, fc_s, hi_s, fi_s; + char *sn = NULL, *sl = NULL, *sa = NULL, *sd = NULL; + unsigned char *out = NULL, *z = NULL, weights[M3D_NUMBONE], *norm = NULL; + unsigned int i = 0, j = 0, k = 0, l = 0, n = 0, len = 0, chunklen = 0, *length = NULL; + M3D_FLOAT scale = (M3D_FLOAT)0.0, min_x, max_x, min_y, max_y, min_z, max_z; + M3D_INDEX last, *vrtxidx = NULL, *mtrlidx = NULL, *tmapidx = NULL, *skinidx = NULL; + uint32_t idx, numcmap = 0, *cmap = NULL, numvrtx = 0, maxvrtx = 0, numtmap = 0, maxtmap = 0, numproc = 0; + uint32_t numskin = 0, maxskin = 0, numstr = 0, maxt = 0, maxbone = 0, numgrp = 0, maxgrp = 0, *grpidx = NULL; + uint8_t *opa = nullptr; + m3dcd_t *cd; + m3dc_t *cmd; + m3dstr_t *str = NULL; + m3dvsave_t *vrtx = NULL, vertex; + m3dtisave_t *tmap = NULL, tcoord; + m3dssave_t *skin = NULL, sk; + m3dfsave_t *face = NULL; + m3dhdr_t *h = NULL; + m3dm_t *m; + m3da_t *a; + + if (!model) { + if (size) *size = 0; + return NULL; + } + model->errcode = M3D_SUCCESS; + if (flags & M3D_EXP_ASCII) quality = M3D_EXP_DOUBLE; + vrtxidx = (M3D_INDEX *)M3D_MALLOC(model->numvertex * sizeof(M3D_INDEX)); + if (!vrtxidx) goto memerr; + memset(vrtxidx, 255, model->numvertex * sizeof(M3D_INDEX)); + if (model->numvertex && !(flags & M3D_EXP_NONORMAL)) { + norm = (unsigned char *)M3D_MALLOC(model->numvertex * sizeof(unsigned char)); + if (!norm) goto memerr; + memset(norm, 0, model->numvertex * sizeof(unsigned char)); + } + if (model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { + mtrlidx = (M3D_INDEX *)M3D_MALLOC(model->nummaterial * sizeof(M3D_INDEX)); + if (!mtrlidx) goto memerr; + memset(mtrlidx, 255, model->nummaterial * sizeof(M3D_INDEX)); + opa = (uint8_t *)M3D_MALLOC(model->nummaterial * 2 * sizeof(M3D_INDEX)); + if (!opa) goto memerr; + memset(opa, 255, model->nummaterial * 2 * sizeof(M3D_INDEX)); + } + if (model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) { + tmapidx = (M3D_INDEX *)M3D_MALLOC(model->numtmap * sizeof(M3D_INDEX)); + if (!tmapidx) goto memerr; + memset(tmapidx, 255, model->numtmap * sizeof(M3D_INDEX)); + } + /** collect array elements that are actually referenced **/ + if (!(flags & M3D_EXP_NOFACE)) { + /* face */ + if (model->numface && model->face) { + M3D_LOG("Processing mesh face"); + face = (m3dfsave_t *)M3D_MALLOC(model->numface * sizeof(m3dfsave_t)); + if (!face) goto memerr; + for (i = 0; i < model->numface; i++) { + memcpy(&face[i].data, &model->face[i], sizeof(m3df_t)); + face[i].group = 0; + face[i].opacity = 255; + if (!(flags & M3D_EXP_NOMATERIAL) && model->face[i].materialid < model->nummaterial) { + if (model->material[model->face[i].materialid].numprop) { + mtrlidx[model->face[i].materialid] = 0; + if (opa[model->face[i].materialid * 2]) { + m = &model->material[model->face[i].materialid]; + for (j = 0; j < m->numprop; j++) + if (m->prop[j].type == m3dp_Kd) { + opa[model->face[i].materialid * 2 + 1] = ((uint8_t *)&m->prop[j].value.color)[3]; + break; + } + for (j = 0; j < m->numprop; j++) + if (m->prop[j].type == m3dp_d) { + opa[model->face[i].materialid * 2 + 1] = (uint8_t)(m->prop[j].value.fnum * 255); + break; + } + opa[model->face[i].materialid * 2] = 0; + } + face[i].opacity = opa[model->face[i].materialid * 2 + 1]; + } else + face[i].data.materialid = M3D_UNDEF; + } + for (j = 0; j < 3; j++) { + k = model->face[i].vertex[j]; + if (k < model->numvertex) + vrtxidx[k] = 0; + if (!(flags & M3D_EXP_NOCMAP)) { + cmap = _m3d_addcmap(cmap, &numcmap, model->vertex[k].color); + if (!cmap) goto memerr; + } + k = model->face[i].normal[j]; + if (k < model->numvertex && !(flags & M3D_EXP_NONORMAL)) { + vrtxidx[k] = 0; + norm[k] = 1; + } + k = model->face[i].texcoord[j]; + if (k < model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) + tmapidx[k] = 0; + } + /* convert from CW to CCW */ + if (flags & M3D_EXP_IDOSUCK) { + j = face[i].data.vertex[1]; + face[i].data.vertex[1] = face[i].data.vertex[2]; + face[i].data.vertex[2] = face[i].data.vertex[1]; + j = face[i].data.normal[1]; + face[i].data.normal[1] = face[i].data.normal[2]; + face[i].data.normal[2] = face[i].data.normal[1]; + j = face[i].data.texcoord[1]; + face[i].data.texcoord[1] = face[i].data.texcoord[2]; + face[i].data.texcoord[2] = face[i].data.texcoord[1]; + } + } + } + if (model->numshape && model->shape) { + M3D_LOG("Processing shape face"); + for (i = 0; i < model->numshape; i++) { + if (!model->shape[i].numcmd) continue; + str = _m3d_addstr(str, &numstr, model->shape[i].name); + if (!str) goto memerr; + for (j = 0; j < model->shape[i].numcmd; j++) { + cmd = &model->shape[i].cmd[j]; + if (cmd->type >= (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0])) || !cmd->arg) + continue; + if (cmd->type == m3dc_mesh) { + if (numgrp + 2 < maxgrp) { + maxgrp += 1024; + grpidx = (uint32_t *)realloc(grpidx, maxgrp * sizeof(uint32_t)); + if (!grpidx) goto memerr; + if (!numgrp) { + grpidx[0] = 0; + grpidx[1] = model->numface; + numgrp += 2; + } + } + grpidx[numgrp + 0] = cmd->arg[0]; + grpidx[numgrp + 1] = cmd->arg[0] + cmd->arg[1]; + numgrp += 2; + } + cd = &m3d_commandtypes[cmd->type]; + for (k = n = 0, l = cd->p; k < l; k++) + switch (cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + if (!(flags & M3D_EXP_NOMATERIAL) && cmd->arg[k] < model->nummaterial) + mtrlidx[cmd->arg[k]] = 0; + break; + case m3dcp_ti_t: + if (!(flags & M3D_EXP_NOTXTCRD) && cmd->arg[k] < model->numtmap) + tmapidx[cmd->arg[k]] = 0; + break; + case m3dcp_qi_t: + case m3dcp_vi_t: + if (cmd->arg[k] < model->numvertex) + vrtxidx[cmd->arg[k]] = 0; + break; + case m3dcp_va_t: + n = k + 1; + l += (cmd->arg[k] - 1) * (cd->p - k - 1); + break; + } + } + } + } + if (model->numface && face) { + if (numgrp && grpidx) { + qsort(grpidx, numgrp, sizeof(uint32_t), _m3d_grpcmp); + for (i = j = 0; i < model->numface && j < numgrp; i++) { + while (j < numgrp && grpidx[j] < i) + j++; + face[i].group = j; + } + } + qsort(face, model->numface, sizeof(m3dfsave_t), _m3d_facecmp); + } + if (grpidx) { + M3D_FREE(grpidx); + grpidx = NULL; + } + if (model->numlabel && model->label) { + M3D_LOG("Processing annotation labels"); + for (i = 0; i < model->numlabel; i++) { + str = _m3d_addstr(str, &numstr, model->label[i].name); + str = _m3d_addstr(str, &numstr, model->label[i].lang); + str = _m3d_addstr(str, &numstr, model->label[i].text); + if (!(flags & M3D_EXP_NOCMAP)) { + cmap = _m3d_addcmap(cmap, &numcmap, model->label[i].color); + if (!cmap) goto memerr; + } + if (model->label[i].vertexid < model->numvertex) + vrtxidx[model->label[i].vertexid] = 0; + } + qsort(model->label, model->numlabel, sizeof(m3dl_t), _m3d_lblcmp); + } + } else if (!(flags & M3D_EXP_NOMATERIAL)) { + /* without a face, simply add all materials, because it can be an mtllib */ + for (i = 0; i < model->nummaterial; i++) + mtrlidx[i] = i; + } + /* bind-pose skeleton */ + if (model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) { + M3D_LOG("Processing bones"); + for (i = 0; i < model->numbone; i++) { + str = _m3d_addstr(str, &numstr, model->bone[i].name); + if (!str) goto memerr; + k = model->bone[i].pos; + if (k < model->numvertex) + vrtxidx[k] = 0; + k = model->bone[i].ori; + if (k < model->numvertex) + vrtxidx[k] = 0; + } + } + /* actions, animated skeleton poses */ + if (model->numaction && model->action && !(flags & M3D_EXP_NOACTION)) { + M3D_LOG("Processing action list"); + for (j = 0; j < model->numaction; j++) { + a = &model->action[j]; + str = _m3d_addstr(str, &numstr, a->name); + if (!str) goto memerr; + if (a->numframe > 65535) a->numframe = 65535; + for (i = 0; i < a->numframe; i++) { + for (l = 0; l < a->frame[i].numtransform; l++) { + k = a->frame[i].transform[l].pos; + if (k < model->numvertex) + vrtxidx[k] = 0; + k = a->frame[i].transform[l].ori; + if (k < model->numvertex) + vrtxidx[k] = 0; + } + if (l > maxt) maxt = l; + } + } + } + /* add colors to color map and texture names to string table */ + if (!(flags & M3D_EXP_NOMATERIAL)) { + M3D_LOG("Processing materials"); + for (i = k = 0; i < model->nummaterial; i++) { + if (mtrlidx[i] == M3D_UNDEF || !model->material[i].numprop) continue; + mtrlidx[i] = k++; + m = &model->material[i]; + str = _m3d_addstr(str, &numstr, m->name); + if (!str) goto memerr; + if (m->prop) + for (j = 0; j < m->numprop; j++) { + if (!(flags & M3D_EXP_NOCMAP) && m->prop[j].type < 128) { + for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++) { + if (m->prop[j].type == m3d_propertytypes[l].id && m3d_propertytypes[l].format == m3dpf_color) { + ((uint8_t *)&m->prop[j].value.color)[3] = opa[i * 2 + 1]; + cmap = _m3d_addcmap(cmap, &numcmap, m->prop[j].value.color); + if (!cmap) goto memerr; + break; + } + } + } + if (m->prop[j].type >= 128 && m->prop[j].value.textureid < model->numtexture && + model->texture[m->prop[j].value.textureid].name) { + str = _m3d_addstr(str, &numstr, model->texture[m->prop[j].value.textureid].name); + if (!str) goto memerr; + } + } + } + } + /* if there's only one black color, don't store it */ + if (numcmap == 1 && cmap && !cmap[0]) numcmap = 0; + + /** compress lists **/ + if (model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) { + M3D_LOG("Compressing tmap"); + tmap = (m3dtisave_t *)M3D_MALLOC(model->numtmap * sizeof(m3dtisave_t)); + if (!tmap) goto memerr; + for (i = 0; i < model->numtmap; i++) { + if (tmapidx[i] == M3D_UNDEF) continue; + switch (quality) { + case M3D_EXP_INT8: + l = (unsigned int)(model->tmap[i].u * 255); + tcoord.data.u = (M3D_FLOAT)l / (M3D_FLOAT)255.0; + l = (unsigned int)(model->tmap[i].v * 255); + tcoord.data.v = (M3D_FLOAT)l / (M3D_FLOAT)255.0; + break; + case M3D_EXP_INT16: + l = (unsigned int)(model->tmap[i].u * 65535); + tcoord.data.u = (M3D_FLOAT)l / (M3D_FLOAT)65535.0; + l = (unsigned int)(model->tmap[i].v * 65535); + tcoord.data.v = (M3D_FLOAT)l / (M3D_FLOAT)65535.0; + break; + default: + tcoord.data.u = model->tmap[i].u; + tcoord.data.v = model->tmap[i].v; + break; + } + if (flags & M3D_EXP_FLIPTXTCRD) + tcoord.data.v = (M3D_FLOAT)1.0 - tcoord.data.v; + tcoord.oldidx = i; + memcpy(&tmap[numtmap++], &tcoord, sizeof(m3dtisave_t)); + } + if (numtmap) { + qsort(tmap, numtmap, sizeof(m3dtisave_t), _m3d_ticmp); + memcpy(&tcoord.data, &tmap[0], sizeof(m3dti_t)); + for (i = 0; i < numtmap; i++) { + if (memcmp(&tcoord.data, &tmap[i].data, sizeof(m3dti_t))) { + memcpy(&tcoord.data, &tmap[i].data, sizeof(m3dti_t)); + maxtmap++; + } + tmap[i].newidx = maxtmap; + tmapidx[tmap[i].oldidx] = maxtmap; + } + maxtmap++; + } + } + if (model->numskin && model->skin && !(flags & M3D_EXP_NOBONE)) { + M3D_LOG("Compressing skin"); + skinidx = (M3D_INDEX *)M3D_MALLOC(model->numskin * sizeof(M3D_INDEX)); + if (!skinidx) goto memerr; + skin = (m3dssave_t *)M3D_MALLOC(model->numskin * sizeof(m3dssave_t)); + if (!skin) goto memerr; + memset(skinidx, 255, model->numskin * sizeof(M3D_INDEX)); + for (i = 0; i < model->numvertex; i++) { + if (vrtxidx[i] != M3D_UNDEF && model->vertex[i].skinid < model->numskin) + skinidx[model->vertex[i].skinid] = 0; + } + for (i = 0; i < model->numskin; i++) { + if (skinidx[i] == M3D_UNDEF) continue; + memset(&sk, 0, sizeof(m3dssave_t)); + for (j = 0, min_x = (M3D_FLOAT)0.0; j < M3D_NUMBONE && model->skin[i].boneid[j] != M3D_UNDEF && + model->skin[i].weight[j] > (M3D_FLOAT)0.0; + j++) { + sk.data.boneid[j] = model->skin[i].boneid[j]; + sk.data.weight[j] = model->skin[i].weight[j]; + min_x += sk.data.weight[j]; + } + if (j > maxbone) maxbone = j; + if (min_x != (M3D_FLOAT)1.0 && min_x != (M3D_FLOAT)0.0) + for (j = 0; j < M3D_NUMBONE && sk.data.weight[j] > (M3D_FLOAT)0.0; j++) + sk.data.weight[j] /= min_x; + sk.oldidx = i; + memcpy(&skin[numskin++], &sk, sizeof(m3dssave_t)); + } + if (numskin) { + qsort(skin, numskin, sizeof(m3dssave_t), _m3d_skincmp); + memcpy(&sk.data, &skin[0].data, sizeof(m3ds_t)); + for (i = 0; i < numskin; i++) { + if (memcmp(&sk.data, &skin[i].data, sizeof(m3ds_t))) { + memcpy(&sk.data, &skin[i].data, sizeof(m3ds_t)); + maxskin++; + } + skin[i].newidx = maxskin; + skinidx[skin[i].oldidx] = maxskin; + } + maxskin++; + } + } + + M3D_LOG("Compressing vertex list"); + min_x = min_y = min_z = (M3D_FLOAT)1e10; + max_x = max_y = max_z = (M3D_FLOAT)-1e10; + if (vrtxidx) { + vrtx = (m3dvsave_t *)M3D_MALLOC(model->numvertex * sizeof(m3dvsave_t)); + if (!vrtx) goto memerr; + for (i = numvrtx = 0; i < model->numvertex; i++) { + if (vrtxidx[i] == M3D_UNDEF) continue; + _m3d_round(quality, &model->vertex[i], &vertex.data); + vertex.norm = norm ? norm[i] : 0; + if (vertex.data.skinid != M3D_INDEXMAX && !vertex.norm) { + vertex.data.skinid = vertex.data.skinid != M3D_UNDEF && skinidx ? skinidx[vertex.data.skinid] : M3D_UNDEF; + if (vertex.data.x > max_x) max_x = vertex.data.x; + if (vertex.data.x < min_x) min_x = vertex.data.x; + if (vertex.data.y > max_y) max_y = vertex.data.y; + if (vertex.data.y < min_y) min_y = vertex.data.y; + if (vertex.data.z > max_z) max_z = vertex.data.z; + if (vertex.data.z < min_z) min_z = vertex.data.z; + } +#ifdef M3D_VERTEXTYPE + vertex.data.type = 0; +#endif + vertex.oldidx = i; + memcpy(&vrtx[numvrtx++], &vertex, sizeof(m3dvsave_t)); + } + if (numvrtx) { + qsort(vrtx, numvrtx, sizeof(m3dvsave_t), _m3d_vrtxcmp); + memcpy(&vertex.data, &vrtx[0].data, sizeof(m3dv_t)); + for (i = 0; i < numvrtx; i++) { + if (memcmp(&vertex.data, &vrtx[i].data, vrtx[i].norm ? 3 * sizeof(M3D_FLOAT) : sizeof(m3dv_t))) { + memcpy(&vertex.data, &vrtx[i].data, sizeof(m3dv_t)); + maxvrtx++; + } + vrtx[i].newidx = maxvrtx; + vrtxidx[vrtx[i].oldidx] = maxvrtx; + } + maxvrtx++; + } + } + if (skinidx) { + M3D_FREE(skinidx); + skinidx = NULL; + } + if (norm) { + M3D_FREE(norm); + norm = NULL; + } + + /* normalize to bounding cube */ + if (numvrtx && !(flags & M3D_EXP_NORECALC)) { + M3D_LOG("Normalizing coordinates"); + if (min_x < (M3D_FLOAT)0.0) min_x = -min_x; + if (max_x < (M3D_FLOAT)0.0) max_x = -max_x; + if (min_y < (M3D_FLOAT)0.0) min_y = -min_y; + if (max_y < (M3D_FLOAT)0.0) max_y = -max_y; + if (min_z < (M3D_FLOAT)0.0) min_z = -min_z; + if (max_z < (M3D_FLOAT)0.0) max_z = -max_z; + scale = min_x; + if (max_x > scale) scale = max_x; + if (min_y > scale) scale = min_y; + if (max_y > scale) scale = max_y; + if (min_z > scale) scale = min_z; + if (max_z > scale) scale = max_z; + if (scale == (M3D_FLOAT)0.0) scale = (M3D_FLOAT)1.0; + if (scale != (M3D_FLOAT)1.0) { + for (i = 0; i < numvrtx; i++) { + if (vrtx[i].data.skinid == M3D_INDEXMAX) continue; + vrtx[i].data.x /= scale; + vrtx[i].data.y /= scale; + vrtx[i].data.z /= scale; + } + } + } + if (model->scale > (M3D_FLOAT)0.0) scale = model->scale; + if (scale <= (M3D_FLOAT)0.0) scale = (M3D_FLOAT)1.0; + + /* meta info */ + sn = _m3d_safestr(model->name && *model->name ? model->name : (char *)"(noname)", 2); + sl = _m3d_safestr(model->license ? model->license : (char *)"MIT", 2); + sa = _m3d_safestr(model->author ? model->author : getenv("LOGNAME"), 2); + if (!sn || !sl || !sa) { + memerr: + if (vrtxidx) M3D_FREE(vrtxidx); + if (mtrlidx) M3D_FREE(mtrlidx); + if (tmapidx) M3D_FREE(tmapidx); + if (skinidx) M3D_FREE(skinidx); + if (grpidx) M3D_FREE(grpidx); + if (norm) M3D_FREE(norm); + if (face) M3D_FREE(face); + if (cmap) M3D_FREE(cmap); + if (tmap) M3D_FREE(tmap); + if (skin) M3D_FREE(skin); + if (str) M3D_FREE(str); + if (vrtx) M3D_FREE(vrtx); + if (sn) M3D_FREE(sn); + if (sl) M3D_FREE(sl); + if (sa) M3D_FREE(sa); + if (sd) M3D_FREE(sd); + if (out) M3D_FREE(out); + if (h) M3D_FREE(h); + M3D_LOG("Out of memory"); + model->errcode = M3D_ERR_ALLOC; + return NULL; + } + + M3D_LOG("Serializing model"); + if (flags & M3D_EXP_ASCII) { + /* use CRLF to make model creators on Win happy... */ + sd = _m3d_safestr(model->desc, 1); + if (!sd) goto memerr; + ol = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + /* header */ + len = 64 + (unsigned int)(strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd)); + out = (unsigned char *)M3D_MALLOC(len); + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr = (char *)out; + ptr += sprintf(ptr, "3dmodel %g\r\n%s\r\n%s\r\n%s\r\n%s\r\n\r\n", scale, + sn, sl, sa, sd); + M3D_FREE(sl); + M3D_FREE(sa); + M3D_FREE(sd); + sl = sa = sd = NULL; + /* preview chunk */ + if (model->preview.data && model->preview.length) { + sl = _m3d_safestr(sn, 0); + if (sl) { + ptr -= (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr + (uintptr_t)20); + out = (unsigned char *)M3D_REALLOC(out, len); + ptr += (uintptr_t)out; + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, "Preview\r\n%s.png\r\n\r\n", sl); + M3D_FREE(sl); + sl = NULL; + } + } + M3D_FREE(sn); + sn = NULL; + /* texture map */ + if (numtmap && tmap && !(flags & M3D_EXP_NOTXTCRD) && !(flags & M3D_EXP_NOFACE)) { + ptr -= (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(maxtmap * 32) + (uintptr_t)12); + out = (unsigned char *)M3D_REALLOC(out, len); + ptr += (uintptr_t)out; + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, "Textmap\r\n"); + last = M3D_UNDEF; + for (i = 0; i < numtmap; i++) { + if (tmap[i].newidx == last) continue; + last = tmap[i].newidx; + ptr += sprintf(ptr, "%g %g\r\n", tmap[i].data.u, tmap[i].data.v); + } + ptr += sprintf(ptr, "\r\n"); + } + /* vertex chunk */ + if (numvrtx && vrtx && !(flags & M3D_EXP_NOFACE)) { + ptr -= (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(maxvrtx * 128) + (uintptr_t)10); + out = (unsigned char *)M3D_REALLOC(out, len); + ptr += (uintptr_t)out; + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, "Vertex\r\n"); + last = M3D_UNDEF; + for (i = 0; i < numvrtx; i++) { + if (vrtx[i].newidx == last) continue; + last = vrtx[i].newidx; + ptr += sprintf(ptr, "%g %g %g %g", vrtx[i].data.x, vrtx[i].data.y, vrtx[i].data.z, vrtx[i].data.w); + if (!(flags & M3D_EXP_NOCMAP) && vrtx[i].data.color) + ptr += sprintf(ptr, " #%08x", vrtx[i].data.color); + if (!(flags & M3D_EXP_NOBONE) && model->numbone && maxskin && vrtx[i].data.skinid < M3D_INDEXMAX) { + if (skin[vrtx[i].data.skinid].data.weight[0] == (M3D_FLOAT)1.0) + ptr += sprintf(ptr, " %d", skin[vrtx[i].data.skinid].data.boneid[0]); + else + for (j = 0; j < M3D_NUMBONE && skin[vrtx[i].data.skinid].data.boneid[j] != M3D_UNDEF && + skin[vrtx[i].data.skinid].data.weight[j] > (M3D_FLOAT)0.0; + j++) + ptr += sprintf(ptr, " %d:%g", skin[vrtx[i].data.skinid].data.boneid[j], + skin[vrtx[i].data.skinid].data.weight[j]); + } + ptr += sprintf(ptr, "\r\n"); + } + ptr += sprintf(ptr, "\r\n"); + } + /* bones chunk */ + if (model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) { + ptr -= (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr + (uintptr_t)9); + for (i = 0; i < model->numbone; i++) { + len += (unsigned int)strlen(model->bone[i].name) + 128; + } + out = (unsigned char *)M3D_REALLOC(out, len); + ptr += (uintptr_t)out; + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, "Bones\r\n"); + ptr = _m3d_prtbone(ptr, model->bone, model->numbone, M3D_UNDEF, 0, vrtxidx); + ptr += sprintf(ptr, "\r\n"); + } + /* materials */ + if (model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { + for (j = 0; j < model->nummaterial; j++) { + if (mtrlidx[j] == M3D_UNDEF || !model->material[j].numprop || !model->material[j].prop) continue; + m = &model->material[j]; + sn = _m3d_safestr(m->name, 0); + if (!sn) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr -= (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)12); + for (i = 0; i < m->numprop; i++) { + if (m->prop[i].type < 128) + len += 32; + else if (m->prop[i].value.textureid < model->numtexture && model->texture[m->prop[i].value.textureid].name) + len += (unsigned int)strlen(model->texture[m->prop[i].value.textureid].name) + 16; + } + out = (unsigned char *)M3D_REALLOC(out, len); + ptr += (uintptr_t)out; + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, "Material %s\r\n", sn); + M3D_FREE(sn); + sn = NULL; + for (i = 0; i < m->numprop; i++) { + k = 256; + if (m->prop[i].type >= 128) { + for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++) + if (m->prop[i].type == m3d_propertytypes[l].id) { + sn = m3d_propertytypes[l].key; + break; + } + if (!sn) + for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++) + if (m->prop[i].type - 128 == m3d_propertytypes[l].id) { + sn = m3d_propertytypes[l].key; + break; + } + k = sn ? m3dpf_map : 256; + } else { + for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++) + if (m->prop[i].type == m3d_propertytypes[l].id) { + sn = m3d_propertytypes[l].key; + k = m3d_propertytypes[l].format; + break; + } + } + switch (k) { + case m3dpf_color: ptr += sprintf(ptr, "%s #%08x\r\n", sn, m->prop[i].value.color); break; + case m3dpf_uint8: + case m3dpf_uint16: + case m3dpf_uint32: ptr += sprintf(ptr, "%s %d\r\n", sn, m->prop[i].value.num); break; + case m3dpf_float: ptr += sprintf(ptr, "%s %g\r\n", sn, m->prop[i].value.fnum); break; + case m3dpf_map: + if (m->prop[i].value.textureid < model->numtexture && + model->texture[m->prop[i].value.textureid].name) { + sl = _m3d_safestr(model->texture[m->prop[i].value.textureid].name, 0); + if (!sl) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + if (*sl) + ptr += sprintf(ptr, "map_%s %s\r\n", sn, sl); + M3D_FREE(sn); + M3D_FREE(sl); + sl = NULL; + } + break; + } + sn = NULL; + } + ptr += sprintf(ptr, "\r\n"); + } + } + /* procedural face */ + if (model->numinlined && model->inlined && !(flags & M3D_EXP_NOFACE)) { + /* all inlined assets which are not textures should be procedural surfaces */ + for (j = 0; j < model->numinlined; j++) { + if (!model->inlined[j].name || !*model->inlined[j].name || !model->inlined[j].length || !model->inlined[j].data || + (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G')) + continue; + for (i = k = 0; i < model->numtexture; i++) { + if (!strcmp(model->inlined[j].name, model->texture[i].name)) { + k = 1; + break; + } + } + if (k) continue; + sn = _m3d_safestr(model->inlined[j].name, 0); + if (!sn) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr -= (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)18); + out = (unsigned char *)M3D_REALLOC(out, len); + ptr += (uintptr_t)out; + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, "Procedural\r\n%s\r\n\r\n", sn); + M3D_FREE(sn); + sn = NULL; + } + } + /* mesh face */ + if (model->numface && face && !(flags & M3D_EXP_NOFACE)) { + ptr -= (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(model->numface * 128) + (uintptr_t)6); + last = M3D_UNDEF; + if (!(flags & M3D_EXP_NOMATERIAL)) + for (i = 0; i < model->numface; i++) { + j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : M3D_UNDEF; + if (j != last) { + last = j; + if (last < model->nummaterial) + len += (unsigned int)strlen(model->material[last].name); + len += 6; + } + } + out = (unsigned char *)M3D_REALLOC(out, len); + ptr += (uintptr_t)out; + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, "Mesh\r\n"); + last = M3D_UNDEF; + for (i = 0; i < model->numface; i++) { + j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : M3D_UNDEF; + if (!(flags & M3D_EXP_NOMATERIAL) && j != last) { + last = j; + if (last < model->nummaterial) { + sn = _m3d_safestr(model->material[last].name, 0); + if (!sn) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, "use %s\r\n", sn); + M3D_FREE(sn); + sn = NULL; + } else + ptr += sprintf(ptr, "use\r\n"); + } + /* hardcoded triangles. Should be repeated as many times as the number of edges in polygon */ + for (j = 0; j < 3; j++) { + ptr += sprintf(ptr, "%s%d", j ? " " : "", vrtxidx[face[i].data.vertex[j]]); + k = M3D_NOTDEFINED; + if (!(flags & M3D_EXP_NOTXTCRD) && (face[i].data.texcoord[j] != M3D_UNDEF) && + (tmapidx[face[i].data.texcoord[j]] != M3D_UNDEF)) { + k = tmapidx[face[i].data.texcoord[j]]; + ptr += sprintf(ptr, "/%d", k); + } + if (!(flags & M3D_EXP_NONORMAL) && (face[i].data.normal[j] != M3D_UNDEF)) + ptr += sprintf(ptr, "%s/%d", k == M3D_NOTDEFINED ? "/" : "", vrtxidx[face[i].data.normal[j]]); + } + ptr += sprintf(ptr, "\r\n"); + } + ptr += sprintf(ptr, "\r\n"); + } + /* mathematical shapes face */ + if (model->numshape && (!(flags & M3D_EXP_NOFACE))) { + for (j = 0; j < model->numshape; j++) { + sn = _m3d_safestr(model->shape[j].name, 0); + if (!sn) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr -= (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)33); + out = (unsigned char *)M3D_REALLOC(out, len); + ptr += (uintptr_t)out; + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, "Shape %s\r\n", sn); + M3D_FREE(sn); + sn = NULL; + if (model->shape[j].group != M3D_UNDEF && !(flags & M3D_EXP_NOBONE)) + ptr += sprintf(ptr, "group %d\r\n", model->shape[j].group); + for (i = 0; i < model->shape[j].numcmd; i++) { + cmd = &model->shape[j].cmd[i]; + if (cmd->type >= (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0])) || !cmd->arg) + continue; + cd = &m3d_commandtypes[cmd->type]; + ptr -= (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(cd->key) + (uintptr_t)3); + for (k = 0; k < cd->p; k++) + switch (cd->a[k]) { + case m3dcp_mi_t: + if (cmd->arg[k] != M3D_NOTDEFINED) { + len += (unsigned int)strlen(model->material[cmd->arg[k]].name) + 1; + } + break; + case m3dcp_va_t: + len += cmd->arg[k] * (cd->p - k - 1) * 16; + k = cd->p; + break; + default: len += 16; break; + } + out = (unsigned char *)M3D_REALLOC(out, len); + ptr += (uintptr_t)out; + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, "%s", cd->key); + for (k = n = 0, l = cd->p; k < l; k++) { + switch (cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + if (cmd->arg[k] != M3D_NOTDEFINED) { + sn = _m3d_safestr(model->material[cmd->arg[k]].name, 0); + if (!sn) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, " %s", sn); + M3D_FREE(sn); + sn = NULL; + } + break; + case m3dcp_vc_t: ptr += sprintf(ptr, " %g", *((float *)&cmd->arg[k])); break; + case m3dcp_va_t: + ptr += sprintf(ptr, " %d[", cmd->arg[k]); + n = k + 1; + l += (cmd->arg[k] - 1) * (cd->p - k - 1); + break; + default: ptr += sprintf(ptr, " %d", cmd->arg[k]); break; + } + } + ptr += sprintf(ptr, "%s\r\n", l > cd->p ? " ]" : ""); + } + ptr += sprintf(ptr, "\r\n"); + } + } + /* annotation labels */ + if (model->numlabel && model->label && !(flags & M3D_EXP_NOFACE)) { + for (i = 0, j = 3, length = NULL; i < model->numlabel; i++) { + if (model->label[i].name) j += (unsigned int)strlen(model->label[i].name); + if (model->label[i].lang) j += (unsigned int)strlen(model->label[i].lang); + if (model->label[i].text) j += (unsigned int)strlen(model->label[i].text); + j += 40; + } + ptr -= (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr + (uintptr_t)j); + out = (unsigned char *)M3D_REALLOC(out, len); + ptr += (uintptr_t)out; + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + for (i = 0; i < model->numlabel; i++) { + if (!i || _m3d_strcmp(sl, model->label[i].lang) || _m3d_strcmp(sn, model->label[i].name)) { + sl = model->label[i].lang; + sn = model->label[i].name; + sd = _m3d_safestr(sn, 0); + if (!sd) { + setlocale(LC_NUMERIC, ol); + sn = sl = NULL; + goto memerr; + } + if (i) ptr += sprintf(ptr, "\r\n"); + ptr += sprintf(ptr, "Labels %s\r\n", sd); + M3D_FREE(sd); + sd = NULL; + if (model->label[i].color) + ptr += sprintf(ptr, "color #0x%08x\r\n", model->label[i].color); + if (sl && *sl) { + sd = _m3d_safestr(sl, 0); + if (!sd) { + setlocale(LC_NUMERIC, ol); + sn = sl = NULL; + goto memerr; + } + ptr += sprintf(ptr, "lang %s\r\n", sd); + M3D_FREE(sd); + sd = NULL; + } + } + sd = _m3d_safestr(model->label[i].text, 2); + if (!sd) { + setlocale(LC_NUMERIC, ol); + sn = sl = NULL; + goto memerr; + } + ptr += sprintf(ptr, "%d %s\r\n", model->label[i].vertexid, sd); + M3D_FREE(sd); + sd = NULL; + } + ptr += sprintf(ptr, "\r\n"); + sn = sl = NULL; + } + /* actions */ + if (model->numaction && model->action && !(flags & M3D_EXP_NOACTION)) { + for (j = 0; j < model->numaction; j++) { + a = &model->action[j]; + sn = _m3d_safestr(a->name, 0); + if (!sn) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr -= (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)48); + for (i = 0; i < a->numframe; i++) + len += a->frame[i].numtransform * 128 + 8; + out = (unsigned char *)M3D_REALLOC(out, len); + ptr += (uintptr_t)out; + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, "Action %d %s\r\n", a->durationmsec, sn); + M3D_FREE(sn); + sn = NULL; + for (i = 0; i < a->numframe; i++) { + ptr += sprintf(ptr, "frame %d\r\n", a->frame[i].msec); + for (k = 0; k < a->frame[i].numtransform; k++) { + ptr += sprintf(ptr, "%d %d %d\r\n", a->frame[i].transform[k].boneid, + vrtxidx[a->frame[i].transform[k].pos], vrtxidx[a->frame[i].transform[k].ori]); + } + } + ptr += sprintf(ptr, "\r\n"); + } + } + /* inlined assets */ + if (model->numinlined && model->inlined) { + for (i = j = 0; i < model->numinlined; i++) + if (model->inlined[i].name) + j += (unsigned int)strlen(model->inlined[i].name) + 6; + if (j > 0) { + ptr -= (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr + (uintptr_t)j + (uintptr_t)16); + out = (unsigned char *)M3D_REALLOC(out, len); + ptr += (uintptr_t)out; + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, "Assets\r\n"); + for (i = 0; i < model->numinlined; i++) + if (model->inlined[i].name) + ptr += sprintf(ptr, "%s%s\r\n", model->inlined[i].name, strrchr(model->inlined[i].name, '.') ? "" : ".png"); + ptr += sprintf(ptr, "\r\n"); + } + } + /* extra info */ + if (model->numextra && (flags & M3D_EXP_EXTRA)) { + for (i = 0; i < model->numextra; i++) { + if (model->extra[i]->length < 9) continue; + ptr -= (uintptr_t)out; + len = (unsigned int)((uintptr_t)ptr + (uintptr_t)17 + (uintptr_t)(model->extra[i]->length * 3)); + out = (unsigned char *)M3D_REALLOC(out, len); + ptr += (uintptr_t)out; + if (!out) { + setlocale(LC_NUMERIC, ol); + goto memerr; + } + ptr += sprintf(ptr, "Extra %c%c%c%c\r\n", + model->extra[i]->magic[0] > ' ' ? model->extra[i]->magic[0] : '_', + model->extra[i]->magic[1] > ' ' ? model->extra[i]->magic[1] : '_', + model->extra[i]->magic[2] > ' ' ? model->extra[i]->magic[2] : '_', + model->extra[i]->magic[3] > ' ' ? model->extra[i]->magic[3] : '_'); + for (j = 0; j < model->extra[i]->length; j++) + ptr += sprintf(ptr, "%02x ", *((unsigned char *)model->extra + sizeof(m3dchunk_t) + j)); + ptr--; + ptr += sprintf(ptr, "\r\n\r\n"); + } + } + setlocale(LC_NUMERIC, ol); + len = (unsigned int)((uintptr_t)ptr - (uintptr_t)out); + out = (unsigned char *)M3D_REALLOC(out, len + 1); + if (!out) goto memerr; + out[len] = 0; + } else + { + /* strictly only use LF (newline) in binary */ + sd = _m3d_safestr(model->desc, 3); + if (!sd) goto memerr; + /* header */ + h = (m3dhdr_t *)M3D_MALLOC(sizeof(m3dhdr_t) + strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd) + 4); + if (!h) goto memerr; + memcpy((uint8_t *)h, "HEAD", 4); + h->length = sizeof(m3dhdr_t); + h->scale = scale; + i = (unsigned int)strlen(sn); + memcpy((uint8_t *)h + h->length, sn, i + 1); + h->length += i + 1; + M3D_FREE(sn); + i = (unsigned int)strlen(sl); + memcpy((uint8_t *)h + h->length, sl, i + 1); + h->length += i + 1; + M3D_FREE(sl); + i = (unsigned int)strlen(sa); + memcpy((uint8_t *)h + h->length, sa, i + 1); + h->length += i + 1; + M3D_FREE(sa); + i = (unsigned int)strlen(sd); + memcpy((uint8_t *)h + h->length, sd, i + 1); + h->length += i + 1; + M3D_FREE(sd); + sn = sl = sa = sd = NULL; + if (model->inlined) + for (i = 0; i < model->numinlined; i++) { + if (model->inlined[i].name && *model->inlined[i].name && model->inlined[i].length > 0) { + str = _m3d_addstr(str, &numstr, model->inlined[i].name); + if (!str) goto memerr; + } + } + if (str) + for (i = 0; i < numstr; i++) { + h = _m3d_addhdr(h, &str[i]); + if (!h) goto memerr; + } + vc_s = quality == M3D_EXP_INT8 ? 1 : (quality == M3D_EXP_INT16 ? 2 : (quality == M3D_EXP_DOUBLE ? 8 : 4)); + vi_s = maxvrtx < 254 ? 1 : (maxvrtx < 65534 ? 2 : 4); + si_s = h->length - 16 < 254 ? 1 : (h->length - 16 < 65534 ? 2 : 4); + ci_s = !numcmap || !cmap ? 0 : (numcmap < 254 ? 1 : (numcmap < 65534 ? 2 : 4)); + ti_s = !maxtmap || !tmap ? 0 : (maxtmap < 254 ? 1 : (maxtmap < 65534 ? 2 : 4)); + bi_s = !model->numbone || !model->bone || (flags & M3D_EXP_NOBONE) ? 0 : (model->numbone < 254 ? 1 : (model->numbone < 65534 ? 2 : 4)); + nb_s = maxbone < 2 ? 1 : (maxbone == 2 ? 2 : (maxbone <= 4 ? 4 : 8)); + sk_s = !bi_s || !maxskin || !skin ? 0 : (maxskin < 254 ? 1 : (maxskin < 65534 ? 2 : 4)); + fc_s = maxt < 254 ? 1 : (maxt < 65534 ? 2 : 4); + hi_s = !model->numshape || !model->shape || (flags & M3D_EXP_NOFACE) ? 0 : (model->numshape < 254 ? 1 : (model->numshape < 65534 ? 2 : 4)); + fi_s = !model->numface || !model->face || (flags & M3D_EXP_NOFACE) ? 0 : (model->numface < 254 ? 1 : (model->numface < 65534 ? 2 : 4)); + h->types = (vc_s == 8 ? (3 << 0) : (vc_s == 2 ? (1 << 0) : (vc_s == 1 ? (0 << 0) : (2 << 0)))) | + (vi_s == 2 ? (1 << 2) : (vi_s == 1 ? (0 << 2) : (2 << 2))) | + (si_s == 2 ? (1 << 4) : (si_s == 1 ? (0 << 4) : (2 << 4))) | + (ci_s == 2 ? (1 << 6) : (ci_s == 1 ? (0 << 6) : (ci_s == 4 ? (2 << 6) : (3 << 6)))) | + (ti_s == 2 ? (1 << 8) : (ti_s == 1 ? (0 << 8) : (ti_s == 4 ? (2 << 8) : (3 << 8)))) | + (bi_s == 2 ? (1 << 10) : (bi_s == 1 ? (0 << 10) : (bi_s == 4 ? (2 << 10) : (3 << 10)))) | + (nb_s == 2 ? (1 << 12) : (nb_s == 1 ? (0 << 12) : (2 << 12))) | + (sk_s == 2 ? (1 << 14) : (sk_s == 1 ? (0 << 14) : (sk_s == 4 ? (2 << 14) : (3 << 14)))) | + (fc_s == 2 ? (1 << 16) : (fc_s == 1 ? (0 << 16) : (2 << 16))) | + (hi_s == 2 ? (1 << 18) : (hi_s == 1 ? (0 << 18) : (hi_s == 4 ? (2 << 18) : (3 << 18)))) | + (fi_s == 2 ? (1 << 20) : (fi_s == 1 ? (0 << 20) : (fi_s == 4 ? (2 << 20) : (3 << 20)))); + len = h->length; + /* preview image chunk, must be the first if exists */ + if (model->preview.data && model->preview.length) { + chunklen = 8 + model->preview.length; + h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); + if (!h) goto memerr; + memcpy((uint8_t *)h + len, "PRVW", 4); + *((uint32_t *)((uint8_t *)h + len + 4)) = chunklen; + memcpy((uint8_t *)h + len + 8, model->preview.data, model->preview.length); + len += chunklen; + } + /* color map */ + if (numcmap && cmap && ci_s < 4 && !(flags & M3D_EXP_NOCMAP)) { + chunklen = 8 + numcmap * sizeof(uint32_t); + h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); + if (!h) goto memerr; + memcpy((uint8_t *)h + len, "CMAP", 4); + *((uint32_t *)((uint8_t *)h + len + 4)) = chunklen; + memcpy((uint8_t *)h + len + 8, cmap, chunklen - 8); + len += chunklen; + } else + numcmap = 0; + /* texture map */ + if (numtmap && tmap && !(flags & M3D_EXP_NOTXTCRD) && !(flags & M3D_EXP_NOFACE)) { + chunklen = 8 + maxtmap * vc_s * 2; + h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); + if (!h) goto memerr; + memcpy((uint8_t *)h + len, "TMAP", 4); + length = (uint32_t *)((uint8_t *)h + len + 4); + out = (uint8_t *)h + len + 8; + last = M3D_UNDEF; + for (i = 0; i < numtmap; i++) { + if (tmap[i].newidx == last) continue; + last = tmap[i].newidx; + switch (vc_s) { + case 1: + *out++ = (uint8_t)(tmap[i].data.u * 255); + *out++ = (uint8_t)(tmap[i].data.v * 255); + break; + case 2: + *((uint16_t *)out) = (uint16_t)(tmap[i].data.u * 65535); + out += 2; + *((uint16_t *)out) = (uint16_t)(tmap[i].data.v * 65535); + out += 2; + break; + case 4: + *((float *)out) = tmap[i].data.u; + out += 4; + *((float *)out) = tmap[i].data.v; + out += 4; + break; + case 8: + *((double *)out) = tmap[i].data.u; + out += 8; + *((double *)out) = tmap[i].data.v; + out += 8; + break; + } + } + *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); + out = NULL; + len += *length; + } + /* vertex */ + if (numvrtx && vrtx) { + chunklen = 8 + maxvrtx * (ci_s + sk_s + 4 * vc_s); + h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); + if (!h) goto memerr; + memcpy((uint8_t *)h + len, "VRTS", 4); + length = (uint32_t *)((uint8_t *)h + len + 4); + out = (uint8_t *)h + len + 8; + last = M3D_UNDEF; + for (i = 0; i < numvrtx; i++) { + if (vrtx[i].newidx == last) continue; + last = vrtx[i].newidx; + switch (vc_s) { + case 1: + *out++ = (int8_t)(vrtx[i].data.x * 127); + *out++ = (int8_t)(vrtx[i].data.y * 127); + *out++ = (int8_t)(vrtx[i].data.z * 127); + *out++ = (int8_t)(vrtx[i].data.w * 127); + break; + case 2: + *((int16_t *)out) = (int16_t)(vrtx[i].data.x * 32767); + out += 2; + *((int16_t *)out) = (int16_t)(vrtx[i].data.y * 32767); + out += 2; + *((int16_t *)out) = (int16_t)(vrtx[i].data.z * 32767); + out += 2; + *((int16_t *)out) = (int16_t)(vrtx[i].data.w * 32767); + out += 2; + break; + case 4: + memcpy(out, &vrtx[i].data.x, sizeof(float)); + out += 4; + memcpy(out, &vrtx[i].data.y, sizeof(float)); + out += 4; + memcpy(out, &vrtx[i].data.z, sizeof(float)); + out += 4; + memcpy(out, &vrtx[i].data.w, sizeof(float)); + out += 4; + break; + case 8: + *((double *)out) = vrtx[i].data.x; + out += 8; + *((double *)out) = vrtx[i].data.y; + out += 8; + *((double *)out) = vrtx[i].data.z; + out += 8; + *((double *)out) = vrtx[i].data.w; + out += 8; + break; + } + idx = _m3d_cmapidx(cmap, numcmap, vrtx[i].data.color); + switch (ci_s) { + case 1: *out++ = (uint8_t)(idx); break; + case 2: + *((uint16_t *)out) = (uint16_t)(idx); + out += 2; + break; + case 4: + *((uint32_t *)out) = vrtx[i].data.color; + out += 4; + break; + } + out = _m3d_addidx(out, sk_s, vrtx[i].data.skinid); + } + uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); + memcpy(length, &v, sizeof(uint32_t)); + //*length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); + out = NULL; + len += v; + } + /* bones chunk */ + if (model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) { + i = 8 + bi_s + sk_s + model->numbone * (bi_s + si_s + 2 * vi_s); + chunklen = i + numskin * nb_s * (bi_s + 1); + h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); + if (!h) goto memerr; + memcpy((uint8_t *)h + len, "BONE", 4); + length = (uint32_t *)((uint8_t *)h + len + 4); + out = (uint8_t *)h + len + 8; + out = _m3d_addidx(out, bi_s, model->numbone); + out = _m3d_addidx(out, sk_s, maxskin); + for (i = 0; i < model->numbone; i++) { + out = _m3d_addidx(out, bi_s, model->bone[i].parent); + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->bone[i].name)); + out = _m3d_addidx(out, vi_s, vrtxidx[model->bone[i].pos]); + out = _m3d_addidx(out, vi_s, vrtxidx[model->bone[i].ori]); + } + if (numskin && skin && sk_s) { + last = M3D_UNDEF; + for (i = 0; i < numskin; i++) { + if (skin[i].newidx == last) continue; + last = skin[i].newidx; + memset(&weights, 0, nb_s); + for (j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != M3D_UNDEF && + skin[i].data.weight[j] > (M3D_FLOAT)0.0; + j++) + weights[j] = (uint8_t)(skin[i].data.weight[j] * 255); + switch (nb_s) { + case 1: weights[0] = 255; break; + case 2: + *((uint16_t *)out) = *((uint16_t *)&weights[0]); + out += 2; + break; + case 4: + *((uint32_t *)out) = *((uint32_t *)&weights[0]); + out += 4; + break; + case 8: + *((uint64_t *)out) = *((uint64_t *)&weights[0]); + out += 8; + break; + } + for (j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != M3D_UNDEF && weights[j]; j++) { + out = _m3d_addidx(out, bi_s, skin[i].data.boneid[j]); + *length += bi_s; + } + } + } + *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); + out = NULL; + len += *length; + } + /* materials */ + if (model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { + for (j = 0; j < model->nummaterial; j++) { + if (mtrlidx[j] == M3D_UNDEF || !model->material[j].numprop || !model->material[j].prop) continue; + m = &model->material[j]; + chunklen = 12 + si_s + m->numprop * 5; + h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); + if (!h) goto memerr; + memcpy((uint8_t *)h + len, "MTRL", 4); + length = (uint32_t *)((uint8_t *)h + len + 4); + out = (uint8_t *)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, m->name)); + for (i = 0; i < m->numprop; i++) { + if (m->prop[i].type >= 128) { + if (m->prop[i].value.textureid >= model->numtexture || + !model->texture[m->prop[i].value.textureid].name) continue; + k = m3dpf_map; + } else { + for (k = 256, l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++) + if (m->prop[i].type == m3d_propertytypes[l].id) { + k = m3d_propertytypes[l].format; + break; + } + } + if (k == 256) continue; + *out++ = m->prop[i].type; + switch (k) { + case m3dpf_color: + if (!(flags & M3D_EXP_NOCMAP)) { + idx = _m3d_cmapidx(cmap, numcmap, m->prop[i].value.color); + switch (ci_s) { + case 1: *out++ = (uint8_t)(idx); break; + case 2: + *((uint16_t *)out) = (uint16_t)(idx); + out += 2; + break; + case 4: + *((uint32_t *)out) = (uint32_t)(m->prop[i].value.color); + out += 4; + break; + } + } else + out--; + break; + case m3dpf_uint8: *out++ = (uint8_t)m->prop[i].value.num; break; + case m3dpf_uint16: + *((uint16_t *)out) = (uint16_t)m->prop[i].value.num; + out += 2; + break; + case m3dpf_uint32: + *((uint32_t *)out) = m->prop[i].value.num; + out += 4; + break; + case m3dpf_float: + *((float *)out) = m->prop[i].value.fnum; + out += 4; + break; + + case m3dpf_map: + idx = _m3d_stridx(str, numstr, model->texture[m->prop[i].value.textureid].name); + out = _m3d_addidx(out, si_s, idx); + break; + } + } + *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); + len += *length; + out = NULL; + } + } + /* procedural face */ + if (model->numinlined && model->inlined && !(flags & M3D_EXP_NOFACE)) { + /* all inlined assets which are not textures should be procedural surfaces */ + for (j = 0; j < model->numinlined; j++) { + if (!model->inlined[j].name || !model->inlined[j].name[0] || model->inlined[j].length < 4 || + !model->inlined[j].data || (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G')) + continue; + for (i = k = 0; i < model->numtexture; i++) { + if (!strcmp(model->inlined[j].name, model->texture[i].name)) { + k = 1; + break; + } + } + if (k) continue; + numproc++; + chunklen = 8 + si_s; + h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); + if (!h) goto memerr; + memcpy((uint8_t *)h + len, "PROC", 4); + *((uint32_t *)((uint8_t *)h + len + 4)) = chunklen; + out = (uint8_t *)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->inlined[j].name)); + out = NULL; + len += chunklen; + } + } + /* mesh face */ + if (model->numface && face && !(flags & M3D_EXP_NOFACE)) { + chunklen = 8 + si_s + model->numface * (6 * vi_s + 3 * ti_s + si_s + 1); + h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); + if (!h) goto memerr; + memcpy((uint8_t *)h + len, "MESH", 4); + length = (uint32_t *)((uint8_t *)h + len + 4); + out = (uint8_t *)h + len + 8; + last = M3D_UNDEF; + for (i = 0; i < model->numface; i++) { + if (!(flags & M3D_EXP_NOMATERIAL) && face[i].data.materialid != last) { + last = face[i].data.materialid; + idx = last < model->nummaterial ? _m3d_stridx(str, numstr, model->material[last].name) : 0; + *out++ = 0; + out = _m3d_addidx(out, si_s, idx); + } + /* hardcoded triangles. */ + k = (3 << 4) | + (((flags & M3D_EXP_NOTXTCRD) || !ti_s || face[i].data.texcoord[0] == M3D_UNDEF || + face[i].data.texcoord[1] == M3D_UNDEF || face[i].data.texcoord[2] == M3D_UNDEF) ? + 0 : + 1) | + (((flags & M3D_EXP_NONORMAL) || face[i].data.normal[0] == M3D_UNDEF || + face[i].data.normal[1] == M3D_UNDEF || face[i].data.normal[2] == M3D_UNDEF) ? + 0 : + 2); + *out++ = (uint8_t)k; + for (j = 0; j < 3; j++) { + out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.vertex[j]]); + if (k & 1) + out = _m3d_addidx(out, ti_s, tmapidx[face[i].data.texcoord[j]]); + if (k & 2) + out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.normal[j]]); + } + } + uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); + memcpy(length, &v, sizeof(uint32_t)); + len += v; + out = NULL; + } + /* mathematical shapes face */ + if (model->numshape && model->shape && !(flags & M3D_EXP_NOFACE)) { + for (j = 0; j < model->numshape; j++) { + chunklen = 12 + si_s + model->shape[j].numcmd * (M3D_CMDMAXARG + 1) * 4; + h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); + if (!h) goto memerr; + memcpy((uint8_t *)h + len, "SHPE", 4); + length = (uint32_t *)((uint8_t *)h + len + 4); + out = (uint8_t *)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->shape[j].name)); + out = _m3d_addidx(out, bi_s, model->shape[j].group); + for (i = 0; i < model->shape[j].numcmd; i++) { + cmd = &model->shape[j].cmd[i]; + if (cmd->type >= (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0])) || !cmd->arg) + continue; + cd = &m3d_commandtypes[cmd->type]; + *out++ = (cmd->type & 0x7F) | (cmd->type > 127 ? 0x80 : 0); + if (cmd->type > 127) *out++ = (cmd->type >> 7) & 0xff; + for (k = n = 0, l = cd->p; k < l; k++) { + switch (cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + out = _m3d_addidx(out, si_s, cmd->arg[k] < model->nummaterial ? _m3d_stridx(str, numstr, model->material[cmd->arg[k]].name) : 0); + break; + case m3dcp_vc_t: + min_x = *((float *)&cmd->arg[k]); + switch (vc_s) { + case 1: *out++ = (int8_t)(min_x * 127); break; + case 2: + *((int16_t *)out) = (int16_t)(min_x * 32767); + out += 2; + break; + case 4: + *((float *)out) = min_x; + out += 4; + break; + case 8: + *((double *)out) = min_x; + out += 8; + break; + } + break; + case m3dcp_hi_t: out = _m3d_addidx(out, hi_s, cmd->arg[k]); break; + case m3dcp_fi_t: out = _m3d_addidx(out, fi_s, cmd->arg[k]); break; + case m3dcp_ti_t: out = _m3d_addidx(out, ti_s, cmd->arg[k]); break; + case m3dcp_qi_t: + case m3dcp_vi_t: out = _m3d_addidx(out, vi_s, cmd->arg[k]); break; + case m3dcp_i1_t: out = _m3d_addidx(out, 1, cmd->arg[k]); break; + case m3dcp_i2_t: out = _m3d_addidx(out, 2, cmd->arg[k]); break; + case m3dcp_i4_t: out = _m3d_addidx(out, 4, cmd->arg[k]); break; + case m3dcp_va_t: + out = _m3d_addidx(out, 4, cmd->arg[k]); + n = k + 1; + l += (cmd->arg[k] - 1) * (cd->p - k - 1); + break; + } + } + } + uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); + memcpy( length, &v, sizeof(uint32_t)); + len += v; + out = NULL; + } + } + /* annotation labels */ + if (model->numlabel && model->label) { + for (i = 0, length = NULL; i < model->numlabel; i++) { + if (!i || _m3d_strcmp(sl, model->label[i].lang) || _m3d_strcmp(sn, model->label[i].name)) { + sl = model->label[i].lang; + sn = model->label[i].name; + if (length) { + *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); + len += *length; + } + chunklen = 8 + 2 * si_s + ci_s + model->numlabel * (vi_s + si_s); + h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); + if (!h) { + sn = NULL; + sl = NULL; + goto memerr; + } + memcpy((uint8_t *)h + len, "LBLS", 4); + length = (uint32_t *)((uint8_t *)h + len + 4); + out = (uint8_t *)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].name)); + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].lang)); + idx = _m3d_cmapidx(cmap, numcmap, model->label[i].color); + switch (ci_s) { + case 1: *out++ = (uint8_t)(idx); break; + case 2: + *((uint16_t *)out) = (uint16_t)(idx); + out += 2; + break; + case 4: + *((uint32_t *)out) = model->label[i].color; + out += 4; + break; + } + } + out = _m3d_addidx(out, vi_s, vrtxidx[model->label[i].vertexid]); + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].text)); + } + if (length) { + uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); + memcpy( length, &v, sizeof(uint32_t)); + len += v; + } + out = NULL; + sn = sl = NULL; + } + /* actions */ + if (model->numaction && model->action && model->numbone && model->bone && !(flags & M3D_EXP_NOACTION)) { + for (j = 0; j < model->numaction; j++) { + a = &model->action[j]; + chunklen = 14 + si_s + a->numframe * (4 + fc_s + maxt * (bi_s + 2 * vi_s)); + h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); + if (!h) goto memerr; + memcpy((uint8_t *)h + len, "ACTN", 4); + length = (uint32_t *)((uint8_t *)h + len + 4); + out = (uint8_t *)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, a->name)); + *((uint16_t *)out) = (uint16_t)(a->numframe); + out += 2; + *((uint32_t *)out) = (uint32_t)(a->durationmsec); + out += 4; + for (i = 0; i < a->numframe; i++) { + *((uint32_t *)out) = (uint32_t)(a->frame[i].msec); + out += 4; + out = _m3d_addidx(out, fc_s, a->frame[i].numtransform); + for (k = 0; k < a->frame[i].numtransform; k++) { + out = _m3d_addidx(out, bi_s, a->frame[i].transform[k].boneid); + out = _m3d_addidx(out, vi_s, vrtxidx[a->frame[i].transform[k].pos]); + out = _m3d_addidx(out, vi_s, vrtxidx[a->frame[i].transform[k].ori]); + } + } + uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); + memcpy( length, &v, sizeof(uint32_t)); + len += v; + out = NULL; + } + } + /* inlined assets */ + if (model->numinlined && model->inlined && (numproc || (flags & M3D_EXP_INLINE))) { + for (j = 0; j < model->numinlined; j++) { + if (!model->inlined[j].name || !model->inlined[j].name[0] || model->inlined[j].length < 4 || !model->inlined[j].data) + continue; + if (!(flags & M3D_EXP_INLINE)) { + if (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G') + continue; + for (i = k = 0; i < model->numtexture; i++) { + if (!strcmp(model->inlined[j].name, model->texture[i].name)) { + k = 1; + break; + } + } + if (k) continue; + } + chunklen = 8 + si_s + model->inlined[j].length; + h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); + if (!h) goto memerr; + memcpy((uint8_t *)h + len, "ASET", 4); + *((uint32_t *)((uint8_t *)h + len + 4)) = chunklen; + out = (uint8_t *)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->inlined[j].name)); + memcpy(out, model->inlined[j].data, model->inlined[j].length); + out = NULL; + len += chunklen; + } + } + /* extra chunks */ + if (model->numextra && model->extra && (flags & M3D_EXP_EXTRA)) { + for (j = 0; j < model->numextra; j++) { + if (!model->extra[j] || model->extra[j]->length < 8) + continue; + chunklen = model->extra[j]->length; + h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); + if (!h) goto memerr; + memcpy((uint8_t *)h + len, model->extra[j], chunklen); + len += chunklen; + } + } + /* add end chunk */ + h = (m3dhdr_t *)M3D_REALLOC(h, len + 4); + if (!h) goto memerr; + memcpy((uint8_t *)h + len, "OMD3", 4); + len += 4; + /* zlib compress */ + if (!(flags & M3D_EXP_NOZLIB)) { + M3D_LOG("Deflating chunks"); + z = stbi_zlib_compress((unsigned char *)h, len, (int *)&l, 9); + if (z && l > 0 && l < len) { + len = l; + M3D_FREE(h); + h = (m3dhdr_t *)z; + } + } + /* add file header at the beginning */ + len += 8; + out = (unsigned char *)M3D_MALLOC(len); + if (!out) goto memerr; + memcpy(out, "3DMO", 4); + *((uint32_t *)(out + 4)) = len; + memcpy(out + 8, h, len - 8); + } + if (size) *size = out ? len : 0; + if (vrtxidx) M3D_FREE(vrtxidx); + if (mtrlidx) M3D_FREE(mtrlidx); + if (tmapidx) M3D_FREE(tmapidx); + if (skinidx) M3D_FREE(skinidx); + if (norm) M3D_FREE(norm); + if (face) M3D_FREE(face); + if (cmap) M3D_FREE(cmap); + if (tmap) M3D_FREE(tmap); + if (skin) M3D_FREE(skin); + if (str) M3D_FREE(str); + if (vrtx) M3D_FREE(vrtx); + if (h) M3D_FREE(h); + return out; +} +#endif + +#endif /* M3D_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#ifdef M3D_CPPWRAPPER +#include <memory> +#include <string> +#include <vector> + +/*** C++ wrapper class ***/ +namespace M3D { +#ifdef M3D_IMPLEMENTATION + +class Model { +public: + m3d_t *model; + +public: + Model() { + this->model = (m3d_t *)malloc(sizeof(m3d_t)); + memset(this->model, 0, sizeof(m3d_t)); + } + Model(_unused const std::string &data, _unused m3dread_t ReadFileCB, + _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) { +#ifndef M3D_NOIMPORTER + this->model = m3d_load((unsigned char *)data.data(), ReadFileCB, FreeCB, mtllib.model); +#else + Model(); +#endif + } + Model(_unused const std::vector<unsigned char> data, _unused m3dread_t ReadFileCB, + _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) { +#ifndef M3D_NOIMPORTER + this->model = m3d_load((unsigned char *)&data[0], ReadFileCB, FreeCB, mtllib.model); +#else + Model(); +#endif + } + Model(_unused const unsigned char *data, _unused m3dread_t ReadFileCB, + _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) { +#ifndef M3D_NOIMPORTER + this->model = m3d_load((unsigned char *)data, ReadFileCB, FreeCB, mtllib.model); +#else + Model(); +#endif + } + ~Model() { m3d_free(this->model); } + +public: + m3d_t *getCStruct() { return this->model; } + std::string getName() { return std::string(this->model->name); } + void setName(std::string name) { this->model->name = (char *)name.c_str(); } + std::string getLicense() { return std::string(this->model->license); } + void setLicense(std::string license) { this->model->license = (char *)license.c_str(); } + std::string getAuthor() { return std::string(this->model->author); } + void setAuthor(std::string author) { this->model->author = (char *)author.c_str(); } + std::string getDescription() { return std::string(this->model->desc); } + void setDescription(std::string desc) { this->model->desc = (char *)desc.c_str(); } + float getScale() { return this->model->scale; } + void setScale(float scale) { this->model->scale = scale; } + std::vector<unsigned char> getPreview() { return this->model->preview.data ? + std::vector<unsigned char>(this->model->preview.data, this->model->preview.data + this->model->preview.length) : + std::vector<unsigned char>(); } + std::vector<uint32_t> getColorMap() { return this->model->cmap ? std::vector<uint32_t>(this->model->cmap, + this->model->cmap + this->model->numcmap) : + std::vector<uint32_t>(); } + std::vector<m3dti_t> getTextureMap() { return this->model->tmap ? std::vector<m3dti_t>(this->model->tmap, + this->model->tmap + this->model->numtmap) : + std::vector<m3dti_t>(); } + std::vector<m3dtx_t> getTextures() { return this->model->texture ? std::vector<m3dtx_t>(this->model->texture, + this->model->texture + this->model->numtexture) : + std::vector<m3dtx_t>(); } + std::string getTextureName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numtexture ? + std::string(this->model->texture[idx].name) : + nullptr; } + std::vector<m3db_t> getBones() { return this->model->bone ? std::vector<m3db_t>(this->model->bone, this->model->bone + + this->model->numbone) : + std::vector<m3db_t>(); } + std::string getBoneName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numbone ? + std::string(this->model->bone[idx].name) : + nullptr; } + std::vector<m3dm_t> getMaterials() { return this->model->material ? std::vector<m3dm_t>(this->model->material, + this->model->material + this->model->nummaterial) : + std::vector<m3dm_t>(); } + std::string getMaterialName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->nummaterial ? + std::string(this->model->material[idx].name) : + nullptr; } + int getMaterialPropertyInt(int idx, int type) { + if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 0 || type >= 127 || + !this->model->material[idx].prop) return -1; + for (int i = 0; i < this->model->material[idx].numprop; i++) { + if (this->model->material[idx].prop[i].type == type) + return this->model->material[idx].prop[i].value.num; + } + return -1; + } + uint32_t getMaterialPropertyColor(int idx, int type) { return this->getMaterialPropertyInt(idx, type); } + float getMaterialPropertyFloat(int idx, int type) { + if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 0 || type >= 127 || + !this->model->material[idx].prop) return -1.0f; + for (int i = 0; i < this->model->material[idx].numprop; i++) { + if (this->model->material[idx].prop[i].type == type) + return this->model->material[idx].prop[i].value.fnum; + } + return -1.0f; + } + m3dtx_t *getMaterialPropertyMap(int idx, int type) { + if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 128 || type > 255 || + !this->model->material[idx].prop) return nullptr; + for (int i = 0; i < this->model->material[idx].numprop; i++) { + if (this->model->material[idx].prop[i].type == type) + return this->model->material[idx].prop[i].value.textureid < this->model->numtexture ? + &this->model->texture[this->model->material[idx].prop[i].value.textureid] : + nullptr; + } + return nullptr; + } + std::vector<m3dv_t> getVertices() { return this->model->vertex ? std::vector<m3dv_t>(this->model->vertex, + this->model->vertex + this->model->numvertex) : + std::vector<m3dv_t>(); } + std::vector<m3df_t> getFace() { return this->model->face ? std::vector<m3df_t>(this->model->face, this->model->face + + this->model->numface) : + std::vector<m3df_t>(); } + std::vector<m3dh_t> getShape() { return this->model->shape ? std::vector<m3dh_t>(this->model->shape, + this->model->shape + this->model->numshape) : + std::vector<m3dh_t>(); } + std::string getShapeName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape && + this->model->shape[idx].name && this->model->shape[idx].name[0] ? + std::string(this->model->shape[idx].name) : + nullptr; } + unsigned int getShapeGroup(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape ? + this->model->shape[idx].group : + 0xFFFFFFFF; } + std::vector<m3dc_t> getShapeCommands(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape && + this->model->shape[idx].cmd ? + std::vector<m3dc_t>(this->model->shape[idx].cmd, this->model->shape[idx].cmd + + this->model->shape[idx].numcmd) : + std::vector<m3dc_t>(); } + std::vector<m3dl_t> getAnnotationLabels() { return this->model->label ? std::vector<m3dl_t>(this->model->label, + this->model->label + this->model->numlabel) : + std::vector<m3dl_t>(); } + std::vector<m3ds_t> getSkin() { return this->model->skin ? std::vector<m3ds_t>(this->model->skin, this->model->skin + + this->model->numskin) : + std::vector<m3ds_t>(); } + std::vector<m3da_t> getActions() { return this->model->action ? std::vector<m3da_t>(this->model->action, + this->model->action + this->model->numaction) : + std::vector<m3da_t>(); } + std::string getActionName(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? + std::string(this->model->action[aidx].name) : + nullptr; } + unsigned int getActionDuration(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? + this->model->action[aidx].durationmsec : + 0; } + std::vector<m3dfr_t> getActionFrames(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? + std::vector<m3dfr_t>(this->model->action[aidx].frame, this->model->action[aidx].frame + + this->model->action[aidx].numframe) : + std::vector<m3dfr_t>(); } + unsigned int getActionFrameTimestamp(int aidx, int fidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? + (fidx >= 0 && (unsigned int)fidx < this->model->action[aidx].numframe ? + this->model->action[aidx].frame[fidx].msec : + 0) : + 0; } + std::vector<m3dtr_t> getActionFrameTransforms(int aidx, int fidx) { + return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? ( + fidx >= 0 && (unsigned int)fidx < this->model->action[aidx].numframe ? + std::vector<m3dtr_t>(this->model->action[aidx].frame[fidx].transform, + this->model->action[aidx].frame[fidx].transform + this->model->action[aidx].frame[fidx].numtransform) : + std::vector<m3dtr_t>()) : + std::vector<m3dtr_t>(); + } + std::vector<m3dtr_t> getActionFrame(int aidx, int fidx, std::vector<m3dtr_t> skeleton) { + m3dtr_t *pose = m3d_frame(this->model, (unsigned int)aidx, (unsigned int)fidx, + skeleton.size() ? &skeleton[0] : nullptr); + return std::vector<m3dtr_t>(pose, pose + this->model->numbone); + } + std::vector<m3db_t> getActionPose(int aidx, unsigned int msec) { + m3db_t *pose = m3d_pose(this->model, (unsigned int)aidx, (unsigned int)msec); + return std::vector<m3db_t>(pose, pose + this->model->numbone); + } + std::vector<m3di_t> getInlinedAssets() { return this->model->inlined ? std::vector<m3di_t>(this->model->inlined, + this->model->inlined + this->model->numinlined) : + std::vector<m3di_t>(); } + std::vector<std::unique_ptr<m3dchunk_t>> getExtras() { return this->model->extra ? + std::vector<std::unique_ptr<m3dchunk_t>>(this->model->extra, + this->model->extra + this->model->numextra) : + std::vector<std::unique_ptr<m3dchunk_t>>(); } + std::vector<unsigned char> Save(_unused int quality, _unused int flags) { +#ifdef M3D_EXPORTER + unsigned int size; + unsigned char *ptr = m3d_save(this->model, quality, flags, &size); + return ptr && size ? std::vector<unsigned char>(ptr, ptr + size) : std::vector<unsigned char>(); +#else + return std::vector<unsigned char>(); +#endif + } +}; + +#else +class Model { +public: + m3d_t *model; + +public: + Model(const std::string &data, m3dread_t ReadFileCB, m3dfree_t FreeCB); + Model(const std::vector<unsigned char> data, m3dread_t ReadFileCB, m3dfree_t FreeCB); + Model(const unsigned char *data, m3dread_t ReadFileCB, m3dfree_t FreeCB); + Model(); + ~Model(); + +public: + m3d_t *getCStruct(); + std::string getName(); + void setName(std::string name); + std::string getLicense(); + void setLicense(std::string license); + std::string getAuthor(); + void setAuthor(std::string author); + std::string getDescription(); + void setDescription(std::string desc); + float getScale(); + void setScale(float scale); + std::vector<unsigned char> getPreview(); + std::vector<uint32_t> getColorMap(); + std::vector<m3dti_t> getTextureMap(); + std::vector<m3dtx_t> getTextures(); + std::string getTextureName(int idx); + std::vector<m3db_t> getBones(); + std::string getBoneName(int idx); + std::vector<m3dm_t> getMaterials(); + std::string getMaterialName(int idx); + int getMaterialPropertyInt(int idx, int type); + uint32_t getMaterialPropertyColor(int idx, int type); + float getMaterialPropertyFloat(int idx, int type); + m3dtx_t *getMaterialPropertyMap(int idx, int type); + std::vector<m3dv_t> getVertices(); + std::vector<m3df_t> getFace(); + std::vector<m3dh_t> getShape(); + std::string getShapeName(int idx); + unsigned int getShapeGroup(int idx); + std::vector<m3dc_t> getShapeCommands(int idx); + std::vector<m3dl_t> getAnnotationLabels(); + std::vector<m3ds_t> getSkin(); + std::vector<m3da_t> getActions(); + std::string getActionName(int aidx); + unsigned int getActionDuration(int aidx); + std::vector<m3dfr_t> getActionFrames(int aidx); + unsigned int getActionFrameTimestamp(int aidx, int fidx); + std::vector<m3dtr_t> getActionFrameTransforms(int aidx, int fidx); + std::vector<m3dtr_t> getActionFrame(int aidx, int fidx, std::vector<m3dtr_t> skeleton); + std::vector<m3db_t> getActionPose(int aidx, unsigned int msec); + std::vector<m3di_t> getInlinedAssets(); + std::vector<std::unique_ptr<m3dchunk_t>> getExtras(); + std::vector<unsigned char> Save(int quality, int flags); +}; + +#endif /* impl */ +} // namespace M3D + +#endif /* M3D_CPPWRAPPER */ + +#if _MSC_VER > 1920 && !defined(__clang__) +# pragma warning(pop) +#endif /* _MSC_VER */ + +#endif /* __cplusplus */ + +#endif diff --git a/libs/assimp/code/AssetLib/MD2/MD2FileData.h b/libs/assimp/code/AssetLib/MD2/MD2FileData.h new file mode 100644 index 0000000..3bce8fe --- /dev/null +++ b/libs/assimp/code/AssetLib/MD2/MD2FileData.h @@ -0,0 +1,164 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MD2FileData.h + * @brief Defines helper data structures for importing MD2 files + * http://linux.ucla.edu/~phaethon/q3/formats/md2-schoenblum.html + */ +#ifndef AI_MD2FILEHELPER_H_INC +#define AI_MD2FILEHELPER_H_INC + +#include <assimp/types.h> +#include <stdint.h> + +#include <assimp/Compiler/pushpack1.h> + +namespace Assimp { +namespace MD2 { + +// to make it easier for us, we test the magic word against both "endianesses" +#define AI_MD2_MAGIC_NUMBER_BE AI_MAKE_MAGIC("IDP2") +#define AI_MD2_MAGIC_NUMBER_LE AI_MAKE_MAGIC("2PDI") + +// common limitations +#define AI_MD2_VERSION 15 +#define AI_MD2_MAXQPATH 64 +#define AI_MD2_MAX_FRAMES 512 +#define AI_MD2_MAX_SKINS 32 +#define AI_MD2_MAX_VERTS 2048 +#define AI_MD2_MAX_TRIANGLES 4096 + +// --------------------------------------------------------------------------- +/** \brief Data structure for the MD2 main header + */ +struct Header +{ + uint32_t magic; + uint32_t version; + uint32_t skinWidth; + uint32_t skinHeight; + uint32_t frameSize; + uint32_t numSkins; + uint32_t numVertices; + uint32_t numTexCoords; + uint32_t numTriangles; + uint32_t numGlCommands; + uint32_t numFrames; + uint32_t offsetSkins; + uint32_t offsetTexCoords; + uint32_t offsetTriangles; + uint32_t offsetFrames; + uint32_t offsetGlCommands; + uint32_t offsetEnd; + +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MD2 OpenGl draw command + */ +struct GLCommand +{ + float s, t; + uint32_t vertexIndex; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MD2 triangle + */ +struct Triangle +{ + uint16_t vertexIndices[3]; + uint16_t textureIndices[3]; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MD2 vertex + */ +struct Vertex +{ + uint8_t vertex[3]; + uint8_t lightNormalIndex; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MD2 frame + */ +struct Frame +{ + float scale[3]; + float translate[3]; + char name[16]; + Vertex vertices[1]; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MD2 texture coordinate + */ +struct TexCoord +{ + uint16_t s; + uint16_t t; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MD2 skin + */ +struct Skin +{ + char name[AI_MD2_MAXQPATH]; /* texture file name */ +} PACK_STRUCT; + +#include <assimp/Compiler/poppack1.h> + + +// --------------------------------------------------------------------------- +//! Lookup a normal vector from Quake's normal lookup table +//! \param index Input index (0-161) +//! \param vOut Receives the output normal +void LookupNormalIndex(uint8_t index,aiVector3D& vOut); + + +} +} + +#endif // !! include guard + diff --git a/libs/assimp/code/AssetLib/MD2/MD2Loader.cpp b/libs/assimp/code/AssetLib/MD2/MD2Loader.cpp new file mode 100644 index 0000000..99ab3f5 --- /dev/null +++ b/libs/assimp/code/AssetLib/MD2/MD2Loader.cpp @@ -0,0 +1,449 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. +--------------------------------------------------------------------------- +*/ + + +#ifndef ASSIMP_BUILD_NO_MD2_IMPORTER + +/** @file Implementation of the MD2 importer class */ +#include "MD2Loader.h" +#include <assimp/ByteSwapper.h> +#include "MD2NormalTable.h" // shouldn't be included by other units +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/scene.h> +#include <assimp/importerdesc.h> +#include <assimp/StringUtils.h> + +#include <memory> + +using namespace Assimp; +using namespace Assimp::MD2; + +// helper macro to determine the size of an array +#if (!defined ARRAYSIZE) +# define ARRAYSIZE(_array) (int(sizeof(_array) / sizeof(_array[0]))) +#endif + +static const aiImporterDesc desc = { + "Quake II Mesh Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "md2" +}; + +// ------------------------------------------------------------------------------------------------ +// Helper function to lookup a normal in Quake 2's precalculated table +void MD2::LookupNormalIndex(uint8_t iNormalIndex,aiVector3D& vOut) +{ + // make sure the normal index has a valid value + if (iNormalIndex >= ARRAYSIZE(g_avNormals)) { + ASSIMP_LOG_WARN("Index overflow in Quake II normal vector list"); + iNormalIndex = ARRAYSIZE(g_avNormals) - 1; + } + vOut = *((const aiVector3D*)(&g_avNormals[iNormalIndex])); +} + + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +MD2Importer::MD2Importer() + : configFrameID(), + m_pcHeader(), + mBuffer(), + fileSize() +{} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +MD2Importer::~MD2Importer() +{} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool MD2Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const +{ + static const uint32_t tokens[] = { AI_MD2_MAGIC_NUMBER_LE }; + return CheckMagicToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Get a list of all extensions supported by this loader +const aiImporterDesc* MD2Importer::GetInfo () const +{ + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties +void MD2Importer::SetupProperties(const Importer* pImp) +{ + // The + // AI_CONFIG_IMPORT_MD2_KEYFRAME option overrides the + // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option. + configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD2_KEYFRAME,-1); + if(static_cast<unsigned int>(-1) == configFrameID){ + configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0); + } +} +// ------------------------------------------------------------------------------------------------ +// Validate the file header +void MD2Importer::ValidateHeader( ) +{ + // check magic number + if (m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_BE && + m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_LE) + { + throw DeadlyImportError("Invalid MD2 magic word: expected IDP2, found ", + ai_str_toprintable((char *)&m_pcHeader->magic, 4)); + } + + // check file format version + if (m_pcHeader->version != 8) + ASSIMP_LOG_WARN( "Unsupported MD2 file version. Continuing happily ..."); + + // check some values whether they are valid + if (0 == m_pcHeader->numFrames) + throw DeadlyImportError( "Invalid MD2 file: NUM_FRAMES is 0"); + + if (m_pcHeader->offsetEnd > (uint32_t)fileSize) + throw DeadlyImportError( "Invalid MD2 file: File is too small"); + + if (m_pcHeader->numSkins > AI_MAX_ALLOC(MD2::Skin)) { + throw DeadlyImportError("Invalid MD2 header: Too many skins, would overflow"); + } + + if (m_pcHeader->numVertices > AI_MAX_ALLOC(MD2::Vertex)) { + throw DeadlyImportError("Invalid MD2 header: Too many vertices, would overflow"); + } + + if (m_pcHeader->numTexCoords > AI_MAX_ALLOC(MD2::TexCoord)) { + throw DeadlyImportError("Invalid MD2 header: Too many texcoords, would overflow"); + } + + if (m_pcHeader->numTriangles > AI_MAX_ALLOC(MD2::Triangle)) { + throw DeadlyImportError("Invalid MD2 header: Too many triangles, would overflow"); + } + + if (m_pcHeader->numFrames > AI_MAX_ALLOC(MD2::Frame)) { + throw DeadlyImportError("Invalid MD2 header: Too many frames, would overflow"); + } + + // -1 because Frame already contains one + unsigned int frameSize = sizeof (MD2::Frame) + (m_pcHeader->numVertices - 1) * sizeof(MD2::Vertex); + + if (m_pcHeader->offsetSkins + m_pcHeader->numSkins * sizeof (MD2::Skin) >= fileSize || + m_pcHeader->offsetTexCoords + m_pcHeader->numTexCoords * sizeof (MD2::TexCoord) >= fileSize || + m_pcHeader->offsetTriangles + m_pcHeader->numTriangles * sizeof (MD2::Triangle) >= fileSize || + m_pcHeader->offsetFrames + m_pcHeader->numFrames * frameSize >= fileSize || + m_pcHeader->offsetEnd > fileSize) + { + throw DeadlyImportError("Invalid MD2 header: Some offsets are outside the file"); + } + + if (m_pcHeader->numSkins > AI_MD2_MAX_SKINS) + ASSIMP_LOG_WARN("The model contains more skins than Quake 2 supports"); + if ( m_pcHeader->numFrames > AI_MD2_MAX_FRAMES) + ASSIMP_LOG_WARN("The model contains more frames than Quake 2 supports"); + if (m_pcHeader->numVertices > AI_MD2_MAX_VERTS) + ASSIMP_LOG_WARN("The model contains more vertices than Quake 2 supports"); + + if (m_pcHeader->numFrames <= configFrameID ) + throw DeadlyImportError("MD2: The requested frame (", configFrameID, ") does not exist in the file"); +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void MD2Importer::InternReadFile( const std::string& pFile, + aiScene* pScene, IOSystem* pIOHandler) +{ + std::unique_ptr<IOStream> file( pIOHandler->Open( pFile)); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open MD2 file ", pFile, ""); + } + + // check whether the md3 file is large enough to contain + // at least the file header + fileSize = (unsigned int)file->FileSize(); + if (fileSize < sizeof(MD2::Header)) { + throw DeadlyImportError("MD2 File is too small"); + } + + std::vector<uint8_t> mBuffer2(fileSize); + file->Read(&mBuffer2[0], 1, fileSize); + mBuffer = &mBuffer2[0]; + + m_pcHeader = (BE_NCONST MD2::Header*)mBuffer; + +#ifdef AI_BUILD_BIG_ENDIAN + + ByteSwap::Swap4(&m_pcHeader->frameSize); + ByteSwap::Swap4(&m_pcHeader->magic); + ByteSwap::Swap4(&m_pcHeader->numFrames); + ByteSwap::Swap4(&m_pcHeader->numGlCommands); + ByteSwap::Swap4(&m_pcHeader->numSkins); + ByteSwap::Swap4(&m_pcHeader->numTexCoords); + ByteSwap::Swap4(&m_pcHeader->numTriangles); + ByteSwap::Swap4(&m_pcHeader->numVertices); + ByteSwap::Swap4(&m_pcHeader->offsetEnd); + ByteSwap::Swap4(&m_pcHeader->offsetFrames); + ByteSwap::Swap4(&m_pcHeader->offsetGlCommands); + ByteSwap::Swap4(&m_pcHeader->offsetSkins); + ByteSwap::Swap4(&m_pcHeader->offsetTexCoords); + ByteSwap::Swap4(&m_pcHeader->offsetTriangles); + ByteSwap::Swap4(&m_pcHeader->skinHeight); + ByteSwap::Swap4(&m_pcHeader->skinWidth); + ByteSwap::Swap4(&m_pcHeader->version); + +#endif + + ValidateHeader(); + + // there won't be more than one mesh inside the file + pScene->mNumMaterials = 1; + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mRootNode->mMeshes[0] = 0; + pScene->mMaterials = new aiMaterial*[1]; + pScene->mMaterials[0] = new aiMaterial(); + pScene->mNumMeshes = 1; + pScene->mMeshes = new aiMesh*[1]; + + aiMesh* pcMesh = pScene->mMeshes[0] = new aiMesh(); + pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + // navigate to the begin of the current frame data + BE_NCONST MD2::Frame* pcFrame = (BE_NCONST MD2::Frame*) ((uint8_t*) + m_pcHeader + m_pcHeader->offsetFrames + (m_pcHeader->frameSize * configFrameID)); + + // navigate to the begin of the triangle data + MD2::Triangle* pcTriangles = (MD2::Triangle*) ((uint8_t*) + m_pcHeader + m_pcHeader->offsetTriangles); + + // navigate to the begin of the tex coords data + BE_NCONST MD2::TexCoord* pcTexCoords = (BE_NCONST MD2::TexCoord*) ((uint8_t*) + m_pcHeader + m_pcHeader->offsetTexCoords); + + // navigate to the begin of the vertex data + BE_NCONST MD2::Vertex* pcVerts = (BE_NCONST MD2::Vertex*) (pcFrame->vertices); + +#ifdef AI_BUILD_BIG_ENDIAN + for (uint32_t i = 0; i< m_pcHeader->numTriangles; ++i) + { + for (unsigned int p = 0; p < 3;++p) + { + ByteSwap::Swap2(& pcTriangles[i].textureIndices[p]); + ByteSwap::Swap2(& pcTriangles[i].vertexIndices[p]); + } + } + for (uint32_t i = 0; i < m_pcHeader->offsetTexCoords;++i) + { + ByteSwap::Swap2(& pcTexCoords[i].s); + ByteSwap::Swap2(& pcTexCoords[i].t); + } + ByteSwap::Swap4( & pcFrame->scale[0] ); + ByteSwap::Swap4( & pcFrame->scale[1] ); + ByteSwap::Swap4( & pcFrame->scale[2] ); + ByteSwap::Swap4( & pcFrame->translate[0] ); + ByteSwap::Swap4( & pcFrame->translate[1] ); + ByteSwap::Swap4( & pcFrame->translate[2] ); +#endif + + pcMesh->mNumFaces = m_pcHeader->numTriangles; + pcMesh->mFaces = new aiFace[m_pcHeader->numTriangles]; + + // allocate output storage + pcMesh->mNumVertices = (unsigned int)pcMesh->mNumFaces*3; + pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; + + // Not sure whether there are MD2 files without texture coordinates + // NOTE: texture coordinates can be there without a texture, + // but a texture can't be there without a valid UV channel + aiMaterial* pcHelper = (aiMaterial*)pScene->mMaterials[0]; + const int iMode = (int)aiShadingMode_Gouraud; + pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + if (m_pcHeader->numTexCoords && m_pcHeader->numSkins) + { + // navigate to the first texture associated with the mesh + const MD2::Skin* pcSkins = (const MD2::Skin*) ((unsigned char*)m_pcHeader + + m_pcHeader->offsetSkins); + + aiColor3D clr; + clr.b = clr.g = clr.r = 1.0f; + pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR); + + clr.b = clr.g = clr.r = 0.05f; + pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + + if (pcSkins->name[0]) + { + aiString szString; + const ai_uint32 iLen = (ai_uint32) ::strlen(pcSkins->name); + ::memcpy(szString.data,pcSkins->name,iLen); + szString.data[iLen] = '\0'; + szString.length = iLen; + + pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + else{ + ASSIMP_LOG_WARN("Texture file name has zero length. It will be skipped."); + } + } + else { + // apply a default material + aiColor3D clr; + clr.b = clr.g = clr.r = 0.6f; + pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR); + + clr.b = clr.g = clr.r = 0.05f; + pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + + aiString szName; + szName.Set(AI_DEFAULT_MATERIAL_NAME); + pcHelper->AddProperty(&szName,AI_MATKEY_NAME); + + aiString sz; + + // TODO: Try to guess the name of the texture file from the model file name + + sz.Set("$texture_dummy.bmp"); + pcHelper->AddProperty(&sz,AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + + + // now read all triangles of the first frame, apply scaling and translation + unsigned int iCurrent = 0; + + float fDivisorU = 1.0f,fDivisorV = 1.0f; + if (m_pcHeader->numTexCoords) { + // allocate storage for texture coordinates, too + pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNumUVComponents[0] = 2; + + // check whether the skin width or height are zero (this would + // cause a division through zero) + if (!m_pcHeader->skinWidth) { + ASSIMP_LOG_ERROR("MD2: No valid skin width given"); + } + else fDivisorU = (float)m_pcHeader->skinWidth; + if (!m_pcHeader->skinHeight){ + ASSIMP_LOG_ERROR("MD2: No valid skin height given"); + } + else fDivisorV = (float)m_pcHeader->skinHeight; + } + + for (unsigned int i = 0; i < (unsigned int)m_pcHeader->numTriangles;++i) { + // Allocate the face + pScene->mMeshes[0]->mFaces[i].mIndices = new unsigned int[3]; + pScene->mMeshes[0]->mFaces[i].mNumIndices = 3; + + // copy texture coordinates + // check whether they are different from the previous value at this index. + // In this case, create a full separate set of vertices/normals/texcoords + for (unsigned int c = 0; c < 3;++c,++iCurrent) { + + // validate vertex indices + unsigned int iIndex = (unsigned int)pcTriangles[i].vertexIndices[c]; + if (iIndex >= m_pcHeader->numVertices) { + ASSIMP_LOG_ERROR("MD2: Vertex index is outside the allowed range"); + iIndex = m_pcHeader->numVertices-1; + } + + // read x,y, and z component of the vertex + aiVector3D& vec = pcMesh->mVertices[iCurrent]; + + vec.x = (float)pcVerts[iIndex].vertex[0] * pcFrame->scale[0]; + vec.x += pcFrame->translate[0]; + + vec.y = (float)pcVerts[iIndex].vertex[1] * pcFrame->scale[1]; + vec.y += pcFrame->translate[1]; + + vec.z = (float)pcVerts[iIndex].vertex[2] * pcFrame->scale[2]; + vec.z += pcFrame->translate[2]; + + // read the normal vector from the precalculated normal table + aiVector3D& vNormal = pcMesh->mNormals[iCurrent]; + LookupNormalIndex(pcVerts[iIndex].lightNormalIndex,vNormal); + + if (m_pcHeader->numTexCoords) { + // validate texture coordinates + iIndex = pcTriangles[i].textureIndices[c]; + if (iIndex >= m_pcHeader->numTexCoords) { + ASSIMP_LOG_ERROR("MD2: UV index is outside the allowed range"); + iIndex = m_pcHeader->numTexCoords-1; + } + + aiVector3D& pcOut = pcMesh->mTextureCoords[0][iCurrent]; + + // the texture coordinates are absolute values but we + // need relative values between 0 and 1 + pcOut.x = pcTexCoords[iIndex].s / fDivisorU; + pcOut.y = 1.f-pcTexCoords[iIndex].t / fDivisorV; + } + pScene->mMeshes[0]->mFaces[i].mIndices[c] = iCurrent; + } + // flip the face order + std::swap( pScene->mMeshes[0]->mFaces[i].mIndices[0], pScene->mMeshes[0]->mFaces[i].mIndices[2] ); + } + // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system + pScene->mRootNode->mTransformation = aiMatrix4x4( + 1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f); +} + +#endif // !! ASSIMP_BUILD_NO_MD2_IMPORTER diff --git a/libs/assimp/code/AssetLib/MD2/MD2Loader.h b/libs/assimp/code/AssetLib/MD2/MD2Loader.h new file mode 100644 index 0000000..a49ccb2 --- /dev/null +++ b/libs/assimp/code/AssetLib/MD2/MD2Loader.h @@ -0,0 +1,116 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MD2Loader.h + * @brief Declaration of the .MD2 importer class. + */ +#pragma once +#ifndef AI_MD2LOADER_H_INCLUDED +#define AI_MD2LOADER_H_INCLUDED + +#include "MD2FileData.h" +#include <assimp/BaseImporter.h> +#include <assimp/ByteSwapper.h> +#include <assimp/types.h> + +struct aiNode; + +namespace Assimp { + +using namespace MD2; + +// --------------------------------------------------------------------------- +/** Importer class for MD2 +*/ +class MD2Importer : public BaseImporter { +public: + MD2Importer(); + ~MD2Importer() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer *pImp) override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + + // ------------------------------------------------------------------- + /** Validate the header of the file + */ + void ValidateHeader(); + +protected: + /** Configuration option: frame to be loaded */ + unsigned int configFrameID; + + /** Header of the MD2 file */ + BE_NCONST MD2::Header *m_pcHeader; + + /** Buffer to hold the loaded file */ + BE_NCONST uint8_t *mBuffer; + + /** Size of the file, in bytes */ + unsigned int fileSize; +}; + +} // end of namespace Assimp + +#endif // AI_3DSIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/MD2/MD2NormalTable.h b/libs/assimp/code/AssetLib/MD2/MD2NormalTable.h new file mode 100644 index 0000000..1837939 --- /dev/null +++ b/libs/assimp/code/AssetLib/MD2/MD2NormalTable.h @@ -0,0 +1,219 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Slightly modified version of the anorms.h header file + * released by ID software with the Quake 2 source code. + * + * Table of normals used by MD2 models + */ + +#ifndef AI_MDL_NORMALTABLE_H_INC +#define AI_MDL_NORMALTABLE_H_INC + + +const float g_avNormals[162][3] = { +{ -0.525731f, 0.000000f, 0.850651f }, +{ -0.442863f, 0.238856f, 0.864188f }, +{ -0.295242f, 0.000000f, 0.955423f }, +{ -0.309017f, 0.500000f, 0.809017f }, +{ -0.162460f, 0.262866f, 0.951056f }, +{ 0.000000f, 0.000000f, 1.000000f }, +{ 0.000000f, 0.850651f, 0.525731f }, +{ -0.147621f, 0.716567f, 0.681718f }, +{ 0.147621f, 0.716567f, 0.681718f }, +{ 0.000000f, 0.525731f, 0.850651f }, +{ 0.309017f, 0.500000f, 0.809017f }, +{ 0.525731f, 0.000000f, 0.850651f }, +{ 0.295242f, 0.000000f, 0.955423f }, +{ 0.442863f, 0.238856f, 0.864188f }, +{ 0.162460f, 0.262866f, 0.951056f }, +{ -0.681718f, 0.147621f, 0.716567f }, +{ -0.809017f, 0.309017f, 0.500000f }, +{ -0.587785f, 0.425325f, 0.688191f }, +{ -0.850651f, 0.525731f, 0.000000f }, +{ -0.864188f, 0.442863f, 0.238856f }, +{ -0.716567f, 0.681718f, 0.147621f }, +{ -0.688191f, 0.587785f, 0.425325f }, +{ -0.500000f, 0.809017f, 0.309017f }, +{ -0.238856f, 0.864188f, 0.442863f }, +{ -0.425325f, 0.688191f, 0.587785f }, +{ -0.716567f, 0.681718f, -0.147621f }, +{ -0.500000f, 0.809017f, -0.309017f }, +{ -0.525731f, 0.850651f, 0.000000f }, +{ 0.000000f, 0.850651f, -0.525731f }, +{ -0.238856f, 0.864188f, -0.442863f }, +{ 0.000000f, 0.955423f, -0.295242f }, +{ -0.262866f, 0.951056f, -0.162460f }, +{ 0.000000f, 1.000000f, 0.000000f }, +{ 0.000000f, 0.955423f, 0.295242f }, +{ -0.262866f, 0.951056f, 0.162460f }, +{ 0.238856f, 0.864188f, 0.442863f }, +{ 0.262866f, 0.951056f, 0.162460f }, +{ 0.500000f, 0.809017f, 0.309017f }, +{ 0.238856f, 0.864188f, -0.442863f }, +{ 0.262866f, 0.951056f, -0.162460f }, +{ 0.500000f, 0.809017f, -0.309017f }, +{ 0.850651f, 0.525731f, 0.000000f }, +{ 0.716567f, 0.681718f, 0.147621f }, +{ 0.716567f, 0.681718f, -0.147621f }, +{ 0.525731f, 0.850651f, 0.000000f }, +{ 0.425325f, 0.688191f, 0.587785f }, +{ 0.864188f, 0.442863f, 0.238856f }, +{ 0.688191f, 0.587785f, 0.425325f }, +{ 0.809017f, 0.309017f, 0.500000f }, +{ 0.681718f, 0.147621f, 0.716567f }, +{ 0.587785f, 0.425325f, 0.688191f }, +{ 0.955423f, 0.295242f, 0.000000f }, +{ 1.000000f, 0.000000f, 0.000000f }, +{ 0.951056f, 0.162460f, 0.262866f }, +{ 0.850651f, -0.525731f, 0.000000f }, +{ 0.955423f, -0.295242f, 0.000000f }, +{ 0.864188f, -0.442863f, 0.238856f }, +{ 0.951056f, -0.162460f, 0.262866f }, +{ 0.809017f, -0.309017f, 0.500000f }, +{ 0.681718f, -0.147621f, 0.716567f }, +{ 0.850651f, 0.000000f, 0.525731f }, +{ 0.864188f, 0.442863f, -0.238856f }, +{ 0.809017f, 0.309017f, -0.500000f }, +{ 0.951056f, 0.162460f, -0.262866f }, +{ 0.525731f, 0.000000f, -0.850651f }, +{ 0.681718f, 0.147621f, -0.716567f }, +{ 0.681718f, -0.147621f, -0.716567f }, +{ 0.850651f, 0.000000f, -0.525731f }, +{ 0.809017f, -0.309017f, -0.500000f }, +{ 0.864188f, -0.442863f, -0.238856f }, +{ 0.951056f, -0.162460f, -0.262866f }, +{ 0.147621f, 0.716567f, -0.681718f }, +{ 0.309017f, 0.500000f, -0.809017f }, +{ 0.425325f, 0.688191f, -0.587785f }, +{ 0.442863f, 0.238856f, -0.864188f }, +{ 0.587785f, 0.425325f, -0.688191f }, +{ 0.688191f, 0.587785f, -0.425325f }, +{ -0.147621f, 0.716567f, -0.681718f }, +{ -0.309017f, 0.500000f, -0.809017f }, +{ 0.000000f, 0.525731f, -0.850651f }, +{ -0.525731f, 0.000000f, -0.850651f }, +{ -0.442863f, 0.238856f, -0.864188f }, +{ -0.295242f, 0.000000f, -0.955423f }, +{ -0.162460f, 0.262866f, -0.951056f }, +{ 0.000000f, 0.000000f, -1.000000f }, +{ 0.295242f, 0.000000f, -0.955423f }, +{ 0.162460f, 0.262866f, -0.951056f }, +{ -0.442863f, -0.238856f, -0.864188f }, +{ -0.309017f, -0.500000f, -0.809017f }, +{ -0.162460f, -0.262866f, -0.951056f }, +{ 0.000000f, -0.850651f, -0.525731f }, +{ -0.147621f, -0.716567f, -0.681718f }, +{ 0.147621f, -0.716567f, -0.681718f }, +{ 0.000000f, -0.525731f, -0.850651f }, +{ 0.309017f, -0.500000f, -0.809017f }, +{ 0.442863f, -0.238856f, -0.864188f }, +{ 0.162460f, -0.262866f, -0.951056f }, +{ 0.238856f, -0.864188f, -0.442863f }, +{ 0.500000f, -0.809017f, -0.309017f }, +{ 0.425325f, -0.688191f, -0.587785f }, +{ 0.716567f, -0.681718f, -0.147621f }, +{ 0.688191f, -0.587785f, -0.425325f }, +{ 0.587785f, -0.425325f, -0.688191f }, +{ 0.000000f, -0.955423f, -0.295242f }, +{ 0.000000f, -1.000000f, 0.000000f }, +{ 0.262866f, -0.951056f, -0.162460f }, +{ 0.000000f, -0.850651f, 0.525731f }, +{ 0.000000f, -0.955423f, 0.295242f }, +{ 0.238856f, -0.864188f, 0.442863f }, +{ 0.262866f, -0.951056f, 0.162460f }, +{ 0.500000f, -0.809017f, 0.309017f }, +{ 0.716567f, -0.681718f, 0.147621f }, +{ 0.525731f, -0.850651f, 0.000000f }, +{ -0.238856f, -0.864188f, -0.442863f }, +{ -0.500000f, -0.809017f, -0.309017f }, +{ -0.262866f, -0.951056f, -0.162460f }, +{ -0.850651f, -0.525731f, 0.000000f }, +{ -0.716567f, -0.681718f, -0.147621f }, +{ -0.716567f, -0.681718f, 0.147621f }, +{ -0.525731f, -0.850651f, 0.000000f }, +{ -0.500000f, -0.809017f, 0.309017f }, +{ -0.238856f, -0.864188f, 0.442863f }, +{ -0.262866f, -0.951056f, 0.162460f }, +{ -0.864188f, -0.442863f, 0.238856f }, +{ -0.809017f, -0.309017f, 0.500000f }, +{ -0.688191f, -0.587785f, 0.425325f }, +{ -0.681718f, -0.147621f, 0.716567f }, +{ -0.442863f, -0.238856f, 0.864188f }, +{ -0.587785f, -0.425325f, 0.688191f }, +{ -0.309017f, -0.500000f, 0.809017f }, +{ -0.147621f, -0.716567f, 0.681718f }, +{ -0.425325f, -0.688191f, 0.587785f }, +{ -0.162460f, -0.262866f, 0.951056f }, +{ 0.442863f, -0.238856f, 0.864188f }, +{ 0.162460f, -0.262866f, 0.951056f }, +{ 0.309017f, -0.500000f, 0.809017f }, +{ 0.147621f, -0.716567f, 0.681718f }, +{ 0.000000f, -0.525731f, 0.850651f }, +{ 0.425325f, -0.688191f, 0.587785f }, +{ 0.587785f, -0.425325f, 0.688191f }, +{ 0.688191f, -0.587785f, 0.425325f }, +{ -0.955423f, 0.295242f, 0.000000f }, +{ -0.951056f, 0.162460f, 0.262866f }, +{ -1.000000f, 0.000000f, 0.000000f }, +{ -0.850651f, 0.000000f, 0.525731f }, +{ -0.955423f, -0.295242f, 0.000000f }, +{ -0.951056f, -0.162460f, 0.262866f }, +{ -0.864188f, 0.442863f, -0.238856f }, +{ -0.951056f, 0.162460f, -0.262866f }, +{ -0.809017f, 0.309017f, -0.500000f }, +{ -0.864188f, -0.442863f, -0.238856f }, +{ -0.951056f, -0.162460f, -0.262866f }, +{ -0.809017f, -0.309017f, -0.500000f }, +{ -0.681718f, 0.147621f, -0.716567f }, +{ -0.681718f, -0.147621f, -0.716567f }, +{ -0.850651f, 0.000000f, -0.525731f }, +{ -0.688191f, 0.587785f, -0.425325f }, +{ -0.587785f, 0.425325f, -0.688191f }, +{ -0.425325f, 0.688191f, -0.587785f }, +{ -0.425325f, -0.688191f, -0.587785f }, +{ -0.587785f, -0.425325f, -0.688191f }, +{ -0.688191f, -0.587785f, -0.425325f } +}; + +#endif // !! include guard diff --git a/libs/assimp/code/AssetLib/MD3/MD3FileData.h b/libs/assimp/code/AssetLib/MD3/MD3FileData.h new file mode 100644 index 0000000..01475e6 --- /dev/null +++ b/libs/assimp/code/AssetLib/MD3/MD3FileData.h @@ -0,0 +1,314 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Md3FileData.h + * + * @brief Defines helper data structures for importing MD3 files. + * http://linux.ucla.edu/~phaethon/q3/formats/md3format.html + */ +#ifndef AI_MD3FILEHELPER_H_INC +#define AI_MD3FILEHELPER_H_INC + +#include <string> +#include <vector> +#include <sstream> +#include <stdint.h> + +#include <assimp/types.h> +#include <assimp/mesh.h> +#include <assimp/anim.h> + +#include <assimp/Compiler/pushpack1.h> + +namespace Assimp { +namespace MD3 { + +// to make it easier for us, we test the magic word against both "endianesses" +#define AI_MD3_MAGIC_NUMBER_BE AI_MAKE_MAGIC("IDP3") +#define AI_MD3_MAGIC_NUMBER_LE AI_MAKE_MAGIC("3PDI") + +// common limitations +#define AI_MD3_VERSION 15 +#define AI_MD3_MAXQPATH 64 +#define AI_MD3_MAXFRAME 16 +#define AI_MD3_MAX_FRAMES 1024 +#define AI_MD3_MAX_TAGS 16 +#define AI_MD3_MAX_SURFACES 32 +#define AI_MD3_MAX_SHADERS 256 +#define AI_MD3_MAX_VERTS 4096 +#define AI_MD3_MAX_TRIANGLES 8192 + +// master scale factor for all vertices in a MD3 model +#define AI_MD3_XYZ_SCALE (1.0f/64.0f) + +// ------------------------------------------------------------------------------- +/** @brief Data structure for the MD3 main header + */ +struct Header +{ + //! magic number + uint32_t IDENT; + + //! file format version + uint32_t VERSION; + + //! original name in .pak archive + char NAME[ AI_MD3_MAXQPATH ]; + + //! unknown + int32_t FLAGS; + + //! number of frames in the file + uint32_t NUM_FRAMES; + + //! number of tags in the file + uint32_t NUM_TAGS; + + //! number of surfaces in the file + uint32_t NUM_SURFACES; + + //! number of skins in the file + uint32_t NUM_SKINS; + + //! offset of the first frame + uint32_t OFS_FRAMES; + + //! offset of the first tag + uint32_t OFS_TAGS; + + //! offset of the first surface + uint32_t OFS_SURFACES; + + //! end of file + uint32_t OFS_EOF; +} PACK_STRUCT; + + +// ------------------------------------------------------------------------------- +/** @brief Data structure for the frame header + */ +struct Frame +{ + //! minimum bounds + aiVector3D min; + + //! maximum bounds + aiVector3D max; + + //! local origin for this frame + aiVector3D origin; + + //! radius of bounding sphere + ai_real radius; + + //! name of frame + char name[ AI_MD3_MAXFRAME ]; + +} /* PACK_STRUCT */; + + +// ------------------------------------------------------------------------------- +/** + * @brief Data structure for the tag header + */ +struct Tag { + //! name of the tag + char NAME[ AI_MD3_MAXQPATH ]; + + //! Local tag origin and orientation + aiVector3D origin; + ai_real orientation[3][3]; + +} /* PACK_STRUCT */; + + +// ------------------------------------------------------------------------------- +/** @brief Data structure for the surface header + */ +struct Surface { + //! magic number + int32_t IDENT; + + //! original name of the surface + char NAME[ AI_MD3_MAXQPATH ]; + + //! unknown + int32_t FLAGS; + + //! number of frames in the surface + uint32_t NUM_FRAMES; + + //! number of shaders in the surface + uint32_t NUM_SHADER; + + //! number of vertices in the surface + uint32_t NUM_VERTICES; + + //! number of triangles in the surface + uint32_t NUM_TRIANGLES; + + //! offset to the triangle data + uint32_t OFS_TRIANGLES; + + //! offset to the shader data + uint32_t OFS_SHADERS; + + //! offset to the texture coordinate data + uint32_t OFS_ST; + + //! offset to the vertex/normal data + uint32_t OFS_XYZNORMAL; + + //! offset to the end of the Surface object + int32_t OFS_END; +} /*PACK_STRUCT*/; + +// ------------------------------------------------------------------------------- +/** @brief Data structure for a shader defined in there + */ +struct Shader { + //! filename of the shader + char NAME[ AI_MD3_MAXQPATH ]; + + //! index of the shader + uint32_t SHADER_INDEX; +} /*PACK_STRUCT*/; + + +// ------------------------------------------------------------------------------- +/** @brief Data structure for a triangle + */ +struct Triangle +{ + //! triangle indices + uint32_t INDEXES[3]; +} /*PACK_STRUCT*/; + + +// ------------------------------------------------------------------------------- +/** @brief Data structure for an UV coord + */ +struct TexCoord +{ + //! UV coordinates + ai_real U,V; +} /*PACK_STRUCT*/; + + +// ------------------------------------------------------------------------------- +/** @brief Data structure for a vertex + */ +struct Vertex +{ + //! X/Y/Z coordinates + int16_t X,Y,Z; + + //! encoded normal vector + uint16_t NORMAL; +} /*PACK_STRUCT*/; + +#include <assimp/Compiler/poppack1.h> + +// ------------------------------------------------------------------------------- +/** @brief Unpack a Q3 16 bit vector to its full float3 representation + * + * @param p_iNormal Input normal vector in latitude/longitude form + * @param p_afOut Pointer to an array of three floats to receive the result + * + * @note This has been taken from q3 source (misc_model.c) + */ +inline void LatLngNormalToVec3(uint16_t p_iNormal, ai_real* p_afOut) +{ + ai_real lat = (ai_real)(( p_iNormal >> 8u ) & 0xff); + ai_real lng = (ai_real)(( p_iNormal & 0xff )); + const ai_real invVal( ai_real( 1.0 ) / ai_real( 128.0 ) ); + lat *= ai_real( 3.141926 ) * invVal; + lng *= ai_real( 3.141926 ) * invVal; + + p_afOut[ 0 ] = std::cos(lat) * std::sin(lng); + p_afOut[ 1 ] = std::sin(lat) * std::sin(lng); + p_afOut[ 2 ] = std::cos(lng); +} + + +// ------------------------------------------------------------------------------- +/** @brief Pack a Q3 normal into 16bit latitude/longitude representation + * @param p_vIn Input vector + * @param p_iOut Output normal + * + * @note This has been taken from q3 source (mathlib.c) + */ +inline void Vec3NormalToLatLng( const aiVector3D& p_vIn, uint16_t& p_iOut ) +{ + // check for singularities + if ( 0.0f == p_vIn[0] && 0.0f == p_vIn[1] ) + { + if ( p_vIn[2] > 0.0f ) + { + ((unsigned char*)&p_iOut)[0] = 0; + ((unsigned char*)&p_iOut)[1] = 0; // lat = 0, long = 0 + } + else + { + ((unsigned char*)&p_iOut)[0] = 128; + ((unsigned char*)&p_iOut)[1] = 0; // lat = 0, long = 128 + } + } + else + { + int a, b; + + a = int(57.2957795f * ( std::atan2( p_vIn[1], p_vIn[0] ) ) * (255.0f / 360.0f )); + a &= 0xff; + + b = int(57.2957795f * ( std::acos( p_vIn[2] ) ) * ( 255.0f / 360.0f )); + b &= 0xff; + + ((unsigned char*)&p_iOut)[0] = (unsigned char) b; // longitude + ((unsigned char*)&p_iOut)[1] = (unsigned char) a; // latitude + } +} + +} // Namespace MD3 +} // Namespace Assimp + +#endif // !! AI_MD3FILEHELPER_H_INC diff --git a/libs/assimp/code/AssetLib/MD3/MD3Loader.cpp b/libs/assimp/code/AssetLib/MD3/MD3Loader.cpp new file mode 100644 index 0000000..6120bd8 --- /dev/null +++ b/libs/assimp/code/AssetLib/MD3/MD3Loader.cpp @@ -0,0 +1,1060 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MD3Loader.cpp + * @brief Implementation of the MD3 importer class + * + * Sources: + * http://www.gamers.org/dEngine/quake3/UQ3S + * http://linux.ucla.edu/~phaethon/q3/formats/md3format.html + * http://www.heppler.com/shader/shader/ + */ + +#ifndef ASSIMP_BUILD_NO_MD3_IMPORTER + +#include "AssetLib/MD3/MD3Loader.h" +#include "Common/Importer.h" + +#include <assimp/GenericProperty.h> +#include <assimp/ParsingUtils.h> +#include <assimp/RemoveComments.h> +#include <assimp/SceneCombiner.h> +#include <assimp/importerdesc.h> +#include <assimp/material.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> + +#include <cctype> +#include <memory> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Quake III Mesh Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "md3" +}; + +// ------------------------------------------------------------------------------------------------ +// Convert a Q3 shader blend function to the appropriate enum value +Q3Shader::BlendFunc StringToBlendFunc(const std::string &m) { + if (m == "GL_ONE") { + return Q3Shader::BLEND_GL_ONE; + } + if (m == "GL_ZERO") { + return Q3Shader::BLEND_GL_ZERO; + } + if (m == "GL_SRC_ALPHA") { + return Q3Shader::BLEND_GL_SRC_ALPHA; + } + if (m == "GL_ONE_MINUS_SRC_ALPHA") { + return Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA; + } + if (m == "GL_ONE_MINUS_DST_COLOR") { + return Q3Shader::BLEND_GL_ONE_MINUS_DST_COLOR; + } + ASSIMP_LOG_ERROR("Q3Shader: Unknown blend function: ", m); + return Q3Shader::BLEND_NONE; +} + +// ------------------------------------------------------------------------------------------------ +// Load a Quake 3 shader +bool Q3Shader::LoadShader(ShaderData &fill, const std::string &pFile, IOSystem *io) { + std::unique_ptr<IOStream> file(io->Open(pFile, "rt")); + if (!file.get()) + return false; // if we can't access the file, don't worry and return + + ASSIMP_LOG_INFO("Loading Quake3 shader file ", pFile); + + // read file in memory + const size_t s = file->FileSize(); + std::vector<char> _buff(s + 1); + file->Read(&_buff[0], s, 1); + _buff[s] = 0; + + // remove comments from it (C++ style) + CommentRemover::RemoveLineComments("//", &_buff[0]); + const char *buff = &_buff[0]; + + Q3Shader::ShaderDataBlock *curData = nullptr; + Q3Shader::ShaderMapBlock *curMap = nullptr; + + // read line per line + for (; SkipSpacesAndLineEnd(&buff); SkipLine(&buff)) { + + if (*buff == '{') { + ++buff; + + // append to last section, if any + if (!curData) { + ASSIMP_LOG_ERROR("Q3Shader: Unexpected shader section token \'{\'"); + return true; // still no failure, the file is there + } + + // read this data section + for (; SkipSpacesAndLineEnd(&buff); SkipLine(&buff)) { + if (*buff == '{') { + ++buff; + // add new map section + curData->maps.push_back(Q3Shader::ShaderMapBlock()); + curMap = &curData->maps.back(); + + for (; SkipSpacesAndLineEnd(&buff); SkipLine(&buff)) { + // 'map' - Specifies texture file name + if (TokenMatchI(buff, "map", 3) || TokenMatchI(buff, "clampmap", 8)) { + curMap->name = GetNextToken(buff); + } + // 'blendfunc' - Alpha blending mode + else if (TokenMatchI(buff, "blendfunc", 9)) { + const std::string blend_src = GetNextToken(buff); + if (blend_src == "add") { + curMap->blend_src = Q3Shader::BLEND_GL_ONE; + curMap->blend_dest = Q3Shader::BLEND_GL_ONE; + } else if (blend_src == "filter") { + curMap->blend_src = Q3Shader::BLEND_GL_DST_COLOR; + curMap->blend_dest = Q3Shader::BLEND_GL_ZERO; + } else if (blend_src == "blend") { + curMap->blend_src = Q3Shader::BLEND_GL_SRC_ALPHA; + curMap->blend_dest = Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA; + } else { + curMap->blend_src = StringToBlendFunc(blend_src); + curMap->blend_dest = StringToBlendFunc(GetNextToken(buff)); + } + } + // 'alphafunc' - Alpha testing mode + else if (TokenMatchI(buff, "alphafunc", 9)) { + const std::string at = GetNextToken(buff); + if (at == "GT0") { + curMap->alpha_test = Q3Shader::AT_GT0; + } else if (at == "LT128") { + curMap->alpha_test = Q3Shader::AT_LT128; + } else if (at == "GE128") { + curMap->alpha_test = Q3Shader::AT_GE128; + } + } else if (*buff == '}') { + ++buff; + // close this map section + curMap = nullptr; + break; + } + } + + } else if (*buff == '}') { + ++buff; + curData = nullptr; + break; + } + + // 'cull' specifies culling behaviour for the model + else if (TokenMatchI(buff, "cull", 4)) { + SkipSpaces(&buff); + if (!ASSIMP_strincmp(buff, "back", 4)) { // render face's backside, does not function in Q3 engine (bug) + curData->cull = Q3Shader::CULL_CCW; + } else if (!ASSIMP_strincmp(buff, "front", 5)) { // is not valid keyword in Q3, but occurs in shaders + curData->cull = Q3Shader::CULL_CW; + } else if (!ASSIMP_strincmp(buff, "none", 4) || !ASSIMP_strincmp(buff, "twosided", 8) || !ASSIMP_strincmp(buff, "disable", 7)) { + curData->cull = Q3Shader::CULL_NONE; + } else { + ASSIMP_LOG_ERROR("Q3Shader: Unrecognized cull mode"); + } + } + } + } else { + // add new section + fill.blocks.push_back(Q3Shader::ShaderDataBlock()); + curData = &fill.blocks.back(); + + // get the name of this section + curData->name = GetNextToken(buff); + } + } + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Load a Quake 3 skin +bool Q3Shader::LoadSkin(SkinData &fill, const std::string &pFile, IOSystem *io) { + std::unique_ptr<IOStream> file(io->Open(pFile, "rt")); + if (!file.get()) + return false; // if we can't access the file, don't worry and return + + ASSIMP_LOG_INFO("Loading Quake3 skin file ", pFile); + + // read file in memory + const size_t s = file->FileSize(); + std::vector<char> _buff(s + 1); + const char *buff = &_buff[0]; + file->Read(&_buff[0], s, 1); + _buff[s] = 0; + + // remove commas + std::replace(_buff.begin(), _buff.end(), ',', ' '); + + // read token by token and fill output table + for (; *buff;) { + SkipSpacesAndLineEnd(&buff); + + // get first identifier + std::string ss = GetNextToken(buff); + + // ignore tokens starting with tag_ + if (!::strncmp(&ss[0], "tag_", std::min((size_t)4, ss.length()))) + continue; + + fill.textures.push_back(SkinData::TextureEntry()); + SkinData::TextureEntry &entry = fill.textures.back(); + + entry.first = ss; + entry.second = GetNextToken(buff); + } + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Convert Q3Shader to material +void Q3Shader::ConvertShaderToMaterial(aiMaterial *out, const ShaderDataBlock &shader) { + ai_assert(nullptr != out); + + /* IMPORTANT: This is not a real conversion. Actually we're just guessing and + * hacking around to build an aiMaterial that looks nearly equal to the + * original Quake 3 shader. We're missing some important features like + * animatable material properties in our material system, but at least + * multiple textures should be handled correctly. + */ + + // Two-sided material? + if (shader.cull == Q3Shader::CULL_NONE) { + const int twosided = 1; + out->AddProperty(&twosided, 1, AI_MATKEY_TWOSIDED); + } + + unsigned int cur_emissive = 0, cur_diffuse = 0, cur_lm = 0; + + // Iterate through all textures + for (std::list<Q3Shader::ShaderMapBlock>::const_iterator it = shader.maps.begin(); it != shader.maps.end(); ++it) { + + // CONVERSION BEHAVIOUR: + // + // + // If the texture is additive + // - if it is the first texture, assume additive blending for the whole material + // - otherwise register it as emissive texture. + // + // If the texture is using standard blend (or if the blend mode is unknown) + // - if first texture: assume default blending for material + // - in any case: set it as diffuse texture + // + // If the texture is using 'filter' blending + // - take as lightmap + // + // Textures with alpha funcs + // - aiTextureFlags_UseAlpha is set (otherwise aiTextureFlags_NoAlpha is explicitly set) + aiString s((*it).name); + aiTextureType type; + unsigned int index; + + if ((*it).blend_src == Q3Shader::BLEND_GL_ONE && (*it).blend_dest == Q3Shader::BLEND_GL_ONE) { + if (it == shader.maps.begin()) { + const int additive = aiBlendMode_Additive; + out->AddProperty(&additive, 1, AI_MATKEY_BLEND_FUNC); + + index = cur_diffuse++; + type = aiTextureType_DIFFUSE; + } else { + index = cur_emissive++; + type = aiTextureType_EMISSIVE; + } + } else if ((*it).blend_src == Q3Shader::BLEND_GL_DST_COLOR && (*it).blend_dest == Q3Shader::BLEND_GL_ZERO) { + index = cur_lm++; + type = aiTextureType_LIGHTMAP; + } else { + const int blend = aiBlendMode_Default; + out->AddProperty(&blend, 1, AI_MATKEY_BLEND_FUNC); + + index = cur_diffuse++; + type = aiTextureType_DIFFUSE; + } + + // setup texture + out->AddProperty(&s, AI_MATKEY_TEXTURE(type, index)); + + // setup texture flags + const int use_alpha = ((*it).alpha_test != Q3Shader::AT_NONE ? aiTextureFlags_UseAlpha : aiTextureFlags_IgnoreAlpha); + out->AddProperty(&use_alpha, 1, AI_MATKEY_TEXFLAGS(type, index)); + } + // If at least one emissive texture was set, set the emissive base color to 1 to ensure + // the texture is actually displayed. + if (0 != cur_emissive) { + aiColor3D one(1.f, 1.f, 1.f); + out->AddProperty(&one, 1, AI_MATKEY_COLOR_EMISSIVE); + } +} + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +MD3Importer::MD3Importer() : + configFrameID(0), configHandleMP(true), configSpeedFlag(), pcHeader(), mBuffer(), fileSize(), mScene(), mIOHandler() {} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +MD3Importer::~MD3Importer() {} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool MD3Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const uint32_t tokens[] = { AI_MD3_MAGIC_NUMBER_LE }; + return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +void MD3Importer::ValidateHeaderOffsets() { + // Check magic number + if (pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE && + pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE) + throw DeadlyImportError("Invalid MD3 file: Magic bytes not found"); + + // Check file format version + if (pcHeader->VERSION > 15) + ASSIMP_LOG_WARN("Unsupported MD3 file version. Continuing happily ..."); + + // Check some offset values whether they are valid + if (!pcHeader->NUM_SURFACES) + throw DeadlyImportError("Invalid md3 file: NUM_SURFACES is 0"); + + if (pcHeader->OFS_FRAMES >= fileSize || pcHeader->OFS_SURFACES >= fileSize || + pcHeader->OFS_EOF > fileSize) { + throw DeadlyImportError("Invalid MD3 header: some offsets are outside the file"); + } + + if (pcHeader->NUM_SURFACES > AI_MAX_ALLOC(MD3::Surface)) { + throw DeadlyImportError("Invalid MD3 header: too many surfaces, would overflow"); + } + + if (pcHeader->OFS_SURFACES + pcHeader->NUM_SURFACES * sizeof(MD3::Surface) >= fileSize) { + throw DeadlyImportError("Invalid MD3 header: some surfaces are outside the file"); + } + + if (pcHeader->NUM_FRAMES <= configFrameID) + throw DeadlyImportError("The requested frame is not existing the file"); +} + +// ------------------------------------------------------------------------------------------------ +void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface *pcSurf) { + // Calculate the relative offset of the surface + const int32_t ofs = int32_t((const unsigned char *)pcSurf - this->mBuffer); + + // Check whether all data chunks are inside the valid range + if (pcSurf->OFS_TRIANGLES + ofs + pcSurf->NUM_TRIANGLES * sizeof(MD3::Triangle) > fileSize || + pcSurf->OFS_SHADERS + ofs + pcSurf->NUM_SHADER * sizeof(MD3::Shader) > fileSize || + pcSurf->OFS_ST + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::TexCoord) > fileSize || + pcSurf->OFS_XYZNORMAL + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::Vertex) > fileSize) { + + throw DeadlyImportError("Invalid MD3 surface header: some offsets are outside the file"); + } + + // Check whether all requirements for Q3 files are met. We don't + // care, but probably someone does. + if (pcSurf->NUM_TRIANGLES > AI_MD3_MAX_TRIANGLES) { + ASSIMP_LOG_WARN("MD3: Quake III triangle limit exceeded"); + } + + if (pcSurf->NUM_SHADER > AI_MD3_MAX_SHADERS) { + ASSIMP_LOG_WARN("MD3: Quake III shader limit exceeded"); + } + + if (pcSurf->NUM_VERTICES > AI_MD3_MAX_VERTS) { + ASSIMP_LOG_WARN("MD3: Quake III vertex limit exceeded"); + } + + if (pcSurf->NUM_FRAMES > AI_MD3_MAX_FRAMES) { + ASSIMP_LOG_WARN("MD3: Quake III frame limit exceeded"); + } +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc *MD3Importer::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties +void MD3Importer::SetupProperties(const Importer *pImp) { + // The + // AI_CONFIG_IMPORT_MD3_KEYFRAME option overrides the + // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option. + configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_KEYFRAME, -1); + if (static_cast<unsigned int>(-1) == configFrameID) { + configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME, 0); + } + + // AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART + configHandleMP = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART, 1)); + + // AI_CONFIG_IMPORT_MD3_SKIN_NAME + configSkinFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SKIN_NAME, "default")); + + // AI_CONFIG_IMPORT_MD3_LOAD_SHADERS + configLoadShaders = (pImp->GetPropertyBool(AI_CONFIG_IMPORT_MD3_LOAD_SHADERS, true)); + + // AI_CONFIG_IMPORT_MD3_SHADER_SRC + configShaderFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SHADER_SRC, "")); + + // AI_CONFIG_FAVOUR_SPEED + configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED, 0)); +} + +// ------------------------------------------------------------------------------------------------ +// Try to read the skin for a MD3 file +void MD3Importer::ReadSkin(Q3Shader::SkinData &fill) const { + // skip any postfixes (e.g. lower_1.md3) + std::string::size_type s = filename.find_last_of('_'); + if (s == std::string::npos) { + s = filename.find_last_of('.'); + if (s == std::string::npos) { + s = filename.size(); + } + } + ai_assert(s != std::string::npos); + + const std::string skin_file = path + filename.substr(0, s) + "_" + configSkinFile + ".skin"; + Q3Shader::LoadSkin(fill, skin_file, mIOHandler); +} + +// ------------------------------------------------------------------------------------------------ +// Try to read the shader for a MD3 file +void MD3Importer::ReadShader(Q3Shader::ShaderData &fill) const { + // Determine Q3 model name from given path + const std::string::size_type s = path.find_last_of("\\/", path.length() - 2); + const std::string model_file = path.substr(s + 1, path.length() - (s + 2)); + + // If no specific dir or file is given, use our default search behaviour + if (!configShaderFile.length()) { + const char sep = mIOHandler->getOsSeparator(); + if (!Q3Shader::LoadShader(fill, path + ".." + sep + ".." + sep + ".." + sep + "scripts" + sep + model_file + ".shader", mIOHandler)) { + Q3Shader::LoadShader(fill, path + ".." + sep + ".." + sep + ".." + sep + "scripts" + sep + filename + ".shader", mIOHandler); + } + } else { + // If the given string specifies a file, load this file. + // Otherwise it's a directory. + const std::string::size_type st = configShaderFile.find_last_of('.'); + if (st == std::string::npos) { + + if (!Q3Shader::LoadShader(fill, configShaderFile + model_file + ".shader", mIOHandler)) { + Q3Shader::LoadShader(fill, configShaderFile + filename + ".shader", mIOHandler); + } + } else { + Q3Shader::LoadShader(fill, configShaderFile, mIOHandler); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Tiny helper to remove a single node from its parent' list +void RemoveSingleNodeFromList(aiNode *nd) { + if (!nd || nd->mNumChildren || !nd->mParent) return; + aiNode *par = nd->mParent; + for (unsigned int i = 0; i < par->mNumChildren; ++i) { + if (par->mChildren[i] == nd) { + --par->mNumChildren; + for (; i < par->mNumChildren; ++i) { + par->mChildren[i] = par->mChildren[i + 1]; + } + delete nd; + break; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Read a multi-part Q3 player model +bool MD3Importer::ReadMultipartFile() { + // check whether the file name contains a common postfix, e.g lower_2.md3 + std::string::size_type s = filename.find_last_of('_'), t = filename.find_last_of('.'); + + if (t == std::string::npos) + t = filename.size(); + if (s == std::string::npos) + s = t; + + const std::string mod_filename = filename.substr(0, s); + const std::string suffix = filename.substr(s, t - s); + + if (mod_filename == "lower" || mod_filename == "upper" || mod_filename == "head") { + const std::string lower = path + "lower" + suffix + ".md3"; + const std::string upper = path + "upper" + suffix + ".md3"; + const std::string head = path + "head" + suffix + ".md3"; + + aiScene *scene_upper = nullptr; + aiScene *scene_lower = nullptr; + aiScene *scene_head = nullptr; + std::string failure; + + aiNode *tag_torso, *tag_head; + std::vector<AttachmentInfo> attach; + + ASSIMP_LOG_INFO("Multi part MD3 player model: lower, upper and head parts are joined"); + + // ensure we won't try to load ourselves recursively + BatchLoader::PropertyMap props; + SetGenericProperty(props.ints, AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART, 0); + + // now read these three files + BatchLoader batch(mIOHandler); + const unsigned int _lower = batch.AddLoadRequest(lower, 0, &props); + const unsigned int _upper = batch.AddLoadRequest(upper, 0, &props); + const unsigned int _head = batch.AddLoadRequest(head, 0, &props); + batch.LoadAll(); + + // now construct a dummy scene to place these three parts in + aiScene *master = new aiScene(); + aiNode *nd = master->mRootNode = new aiNode(); + nd->mName.Set("<MD3_Player>"); + + // ... and get them. We need all of them. + scene_lower = batch.GetImport(_lower); + if (!scene_lower) { + ASSIMP_LOG_ERROR("M3D: Failed to read multi part model, lower.md3 fails to load"); + failure = "lower"; + goto error_cleanup; + } + + scene_upper = batch.GetImport(_upper); + if (!scene_upper) { + ASSIMP_LOG_ERROR("M3D: Failed to read multi part model, upper.md3 fails to load"); + failure = "upper"; + goto error_cleanup; + } + + scene_head = batch.GetImport(_head); + if (!scene_head) { + ASSIMP_LOG_ERROR("M3D: Failed to read multi part model, head.md3 fails to load"); + failure = "head"; + goto error_cleanup; + } + + // build attachment infos. search for typical Q3 tags + + // original root + scene_lower->mRootNode->mName.Set("lower"); + attach.push_back(AttachmentInfo(scene_lower, nd)); + + // tag_torso + tag_torso = scene_lower->mRootNode->FindNode("tag_torso"); + if (!tag_torso) { + ASSIMP_LOG_ERROR("M3D: Failed to find attachment tag for multi part model: tag_torso expected"); + goto error_cleanup; + } + scene_upper->mRootNode->mName.Set("upper"); + attach.push_back(AttachmentInfo(scene_upper, tag_torso)); + + // tag_head + tag_head = scene_upper->mRootNode->FindNode("tag_head"); + if (!tag_head) { + ASSIMP_LOG_ERROR("M3D: Failed to find attachment tag for multi part model: tag_head expected"); + goto error_cleanup; + } + scene_head->mRootNode->mName.Set("head"); + attach.push_back(AttachmentInfo(scene_head, tag_head)); + + // Remove tag_head and tag_torso from all other model parts ... + // this ensures (together with AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) + // that tag_torso/tag_head is also the name of the (unique) output node + RemoveSingleNodeFromList(scene_upper->mRootNode->FindNode("tag_torso")); + RemoveSingleNodeFromList(scene_head->mRootNode->FindNode("tag_head")); + + // Undo the rotations which we applied to the coordinate systems. We're + // working in global Quake space here + scene_head->mRootNode->mTransformation = aiMatrix4x4(); + scene_lower->mRootNode->mTransformation = aiMatrix4x4(); + scene_upper->mRootNode->mTransformation = aiMatrix4x4(); + + // and merge the scenes + SceneCombiner::MergeScenes(&mScene, master, attach, + AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | + AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES | + AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS | + (!configSpeedFlag ? AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY : 0)); + + // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system + mScene->mRootNode->mTransformation = aiMatrix4x4(1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, 0.f, -1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f); + + return true; + + error_cleanup: + delete scene_upper; + delete scene_lower; + delete scene_head; + delete master; + + if (failure == mod_filename) { + throw DeadlyImportError("MD3: failure to read multipart host file"); + } + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +// Convert a MD3 path to a proper value +void MD3Importer::ConvertPath(const char *texture_name, const char *header_name, std::string &out) const { + // If the MD3's internal path itself and the given path are using + // the same directory, remove it completely to get right output paths. + const char *end1 = ::strrchr(header_name, '\\'); + if (!end1) end1 = ::strrchr(header_name, '/'); + + const char *end2 = ::strrchr(texture_name, '\\'); + if (!end2) end2 = ::strrchr(texture_name, '/'); + + // HACK: If the paths starts with "models", ignore the + // next two hierarchy levels, it specifies just the model name. + // Ignored by Q3, it might be not equal to the real model location. + if (end2) { + + size_t len2; + const size_t len1 = (size_t)(end1 - header_name); + if (!ASSIMP_strincmp(texture_name, "models", 6) && (texture_name[6] == '/' || texture_name[6] == '\\')) { + len2 = 6; // ignore the seventh - could be slash or backslash + + if (!header_name[0]) { + // Use the file name only + out = end2 + 1; + return; + } + } else + len2 = std::min(len1, (size_t)(end2 - texture_name)); + if (!ASSIMP_strincmp(texture_name, header_name, static_cast<unsigned int>(len2))) { + // Use the file name only + out = end2 + 1; + return; + } + } + // Use the full path + out = texture_name; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void MD3Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + mFile = pFile; + mScene = pScene; + mIOHandler = pIOHandler; + + // get base path and file name + // todo ... move to PathConverter + std::string::size_type s = mFile.find_last_of("/\\"); + if (s == std::string::npos) { + s = 0; + } else { + ++s; + } + filename = mFile.substr(s), path = mFile.substr(0, s); + for (std::string::iterator it = filename.begin(); it != filename.end(); ++it) { + *it = static_cast<char>(tolower(static_cast<unsigned char>(*it))); + } + + // Load multi-part model file, if necessary + if (configHandleMP) { + if (ReadMultipartFile()) + return; + } + + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile)); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open MD3 file ", pFile, "."); + } + + // Check whether the md3 file is large enough to contain the header + fileSize = (unsigned int)file->FileSize(); + if (fileSize < sizeof(MD3::Header)) + throw DeadlyImportError("MD3 File is too small."); + + // Allocate storage and copy the contents of the file to a memory buffer + std::vector<unsigned char> mBuffer2(fileSize); + file->Read(&mBuffer2[0], 1, fileSize); + mBuffer = &mBuffer2[0]; + + pcHeader = (BE_NCONST MD3::Header *)mBuffer; + + // Ensure correct endianness +#ifdef AI_BUILD_BIG_ENDIAN + + AI_SWAP4(pcHeader->VERSION); + AI_SWAP4(pcHeader->FLAGS); + AI_SWAP4(pcHeader->IDENT); + AI_SWAP4(pcHeader->NUM_FRAMES); + AI_SWAP4(pcHeader->NUM_SKINS); + AI_SWAP4(pcHeader->NUM_SURFACES); + AI_SWAP4(pcHeader->NUM_TAGS); + AI_SWAP4(pcHeader->OFS_EOF); + AI_SWAP4(pcHeader->OFS_FRAMES); + AI_SWAP4(pcHeader->OFS_SURFACES); + AI_SWAP4(pcHeader->OFS_TAGS); + +#endif + + // Validate the file header + ValidateHeaderOffsets(); + + // Navigate to the list of surfaces + BE_NCONST MD3::Surface *pcSurfaces = (BE_NCONST MD3::Surface *)(mBuffer + pcHeader->OFS_SURFACES); + + // Navigate to the list of tags + BE_NCONST MD3::Tag *pcTags = (BE_NCONST MD3::Tag *)(mBuffer + pcHeader->OFS_TAGS); + + // Allocate output storage + pScene->mNumMeshes = pcHeader->NUM_SURFACES; + if (pcHeader->NUM_SURFACES == 0) { + throw DeadlyImportError("MD3: No surfaces"); + } else if (pcHeader->NUM_SURFACES > AI_MAX_ALLOC(aiMesh)) { + // We allocate pointers but check against the size of aiMesh + // since those pointers will eventually have to point to real objects + throw DeadlyImportError("MD3: Too many surfaces, would run out of memory"); + } + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + + pScene->mNumMaterials = pcHeader->NUM_SURFACES; + pScene->mMaterials = new aiMaterial *[pScene->mNumMeshes]; + + // Set arrays to zero to ensue proper destruction if an exception is raised + ::memset(pScene->mMeshes, 0, pScene->mNumMeshes * sizeof(aiMesh *)); + ::memset(pScene->mMaterials, 0, pScene->mNumMaterials * sizeof(aiMaterial *)); + + // Now read possible skins from .skin file + Q3Shader::SkinData skins; + ReadSkin(skins); + + // And check whether we can locate a shader file for this model + Q3Shader::ShaderData shaders; + if (configLoadShaders){ + ReadShader(shaders); + } + + // Adjust all texture paths in the shader + const char *header_name = pcHeader->NAME; + if (!shaders.blocks.empty()) { + for (std::list<Q3Shader::ShaderDataBlock>::iterator dit = shaders.blocks.begin(); dit != shaders.blocks.end(); ++dit) { + ConvertPath((*dit).name.c_str(), header_name, (*dit).name); + + for (std::list<Q3Shader::ShaderMapBlock>::iterator mit = (*dit).maps.begin(); mit != (*dit).maps.end(); ++mit) { + ConvertPath((*mit).name.c_str(), header_name, (*mit).name); + } + } + } + + // Read all surfaces from the file + unsigned int iNum = pcHeader->NUM_SURFACES; + unsigned int iNumMaterials = 0; + while (iNum-- > 0) { + + // Ensure correct endianness +#ifdef AI_BUILD_BIG_ENDIAN + + AI_SWAP4(pcSurfaces->FLAGS); + AI_SWAP4(pcSurfaces->IDENT); + AI_SWAP4(pcSurfaces->NUM_FRAMES); + AI_SWAP4(pcSurfaces->NUM_SHADER); + AI_SWAP4(pcSurfaces->NUM_TRIANGLES); + AI_SWAP4(pcSurfaces->NUM_VERTICES); + AI_SWAP4(pcSurfaces->OFS_END); + AI_SWAP4(pcSurfaces->OFS_SHADERS); + AI_SWAP4(pcSurfaces->OFS_ST); + AI_SWAP4(pcSurfaces->OFS_TRIANGLES); + AI_SWAP4(pcSurfaces->OFS_XYZNORMAL); + +#endif + + // Validate the surface header + ValidateSurfaceHeaderOffsets(pcSurfaces); + + // Navigate to the vertex list of the surface + BE_NCONST MD3::Vertex *pcVertices = (BE_NCONST MD3::Vertex *)(((uint8_t *)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL); + + // Navigate to the triangle list of the surface + BE_NCONST MD3::Triangle *pcTriangles = (BE_NCONST MD3::Triangle *)(((uint8_t *)pcSurfaces) + pcSurfaces->OFS_TRIANGLES); + + // Navigate to the texture coordinate list of the surface + BE_NCONST MD3::TexCoord *pcUVs = (BE_NCONST MD3::TexCoord *)(((uint8_t *)pcSurfaces) + pcSurfaces->OFS_ST); + + // Navigate to the shader list of the surface + BE_NCONST MD3::Shader *pcShaders = (BE_NCONST MD3::Shader *)(((uint8_t *)pcSurfaces) + pcSurfaces->OFS_SHADERS); + + // If the submesh is empty ignore it + if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES) { + pcSurfaces = (BE_NCONST MD3::Surface *)(((uint8_t *)pcSurfaces) + pcSurfaces->OFS_END); + pScene->mNumMeshes--; + continue; + } + + // Allocate output mesh + pScene->mMeshes[iNum] = new aiMesh(); + aiMesh *pcMesh = pScene->mMeshes[iNum]; + + std::string _texture_name; + const char *texture_name = nullptr; + + // Check whether we have a texture record for this surface in the .skin file + std::list<Q3Shader::SkinData::TextureEntry>::iterator it = std::find( + skins.textures.begin(), skins.textures.end(), pcSurfaces->NAME); + + if (it != skins.textures.end()) { + texture_name = &*(_texture_name = (*it).second).begin(); + ASSIMP_LOG_VERBOSE_DEBUG("MD3: Assigning skin texture ", (*it).second, " to surface ", pcSurfaces->NAME); + (*it).resolved = true; // mark entry as resolved + } + + // Get the first shader (= texture?) assigned to the surface + if (!texture_name && pcSurfaces->NUM_SHADER) { + texture_name = pcShaders->NAME; + } + + std::string convertedPath; + if (texture_name) { + if (configLoadShaders){ + ConvertPath(texture_name, header_name, convertedPath); + } + else{ + convertedPath = texture_name; + } + } + + const Q3Shader::ShaderDataBlock *shader = nullptr; + + // Now search the current shader for a record with this name ( + // excluding texture file extension) + if (!shaders.blocks.empty()) { + std::string::size_type sh = convertedPath.find_last_of('.'); + if (sh == std::string::npos) { + sh = convertedPath.length(); + } + + const std::string without_ext = convertedPath.substr(0, sh); + std::list<Q3Shader::ShaderDataBlock>::const_iterator dit = std::find(shaders.blocks.begin(), shaders.blocks.end(), without_ext); + if (dit != shaders.blocks.end()) { + // We made it! + shader = &*dit; + ASSIMP_LOG_INFO("Found shader record for ", without_ext); + } else { + ASSIMP_LOG_WARN("Unable to find shader record for ", without_ext); + } + } + + aiMaterial *pcHelper = new aiMaterial(); + + const int iMode = (int)aiShadingMode_Gouraud; + pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + // Add a small ambient color value - Quake 3 seems to have one + aiColor3D clr; + clr.b = clr.g = clr.r = 0.05f; + pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_AMBIENT); + + clr.b = clr.g = clr.r = 1.0f; + pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_SPECULAR); + + // use surface name + skin_name as material name + aiString name; + name.Set("MD3_[" + configSkinFile + "][" + pcSurfaces->NAME + "]"); + pcHelper->AddProperty(&name, AI_MATKEY_NAME); + + if (!shader) { + // Setup dummy texture file name to ensure UV coordinates are kept during postprocessing + aiString szString; + if (convertedPath.length()) { + szString.Set(convertedPath); + } else { + ASSIMP_LOG_WARN("Texture file name has zero length. Using default name"); + szString.Set("dummy_texture.bmp"); + } + pcHelper->AddProperty(&szString, AI_MATKEY_TEXTURE_DIFFUSE(0)); + + // prevent transparency by default + int no_alpha = aiTextureFlags_IgnoreAlpha; + pcHelper->AddProperty(&no_alpha, 1, AI_MATKEY_TEXFLAGS_DIFFUSE(0)); + } else { + Q3Shader::ConvertShaderToMaterial(pcHelper, *shader); + } + + pScene->mMaterials[iNumMaterials] = (aiMaterial *)pcHelper; + pcMesh->mMaterialIndex = iNumMaterials++; + + // Ensure correct endianness +#ifdef AI_BUILD_BIG_ENDIAN + + for (uint32_t i = 0; i < pcSurfaces->NUM_VERTICES; ++i) { + AI_SWAP2(pcVertices[i].NORMAL); + AI_SWAP2(pcVertices[i].X); + AI_SWAP2(pcVertices[i].Y); + AI_SWAP2(pcVertices[i].Z); + + AI_SWAP4(pcUVs[i].U); + AI_SWAP4(pcUVs[i].V); + } + for (uint32_t i = 0; i < pcSurfaces->NUM_TRIANGLES; ++i) { + AI_SWAP4(pcTriangles[i].INDEXES[0]); + AI_SWAP4(pcTriangles[i].INDEXES[1]); + AI_SWAP4(pcTriangles[i].INDEXES[2]); + } + +#endif + + // Fill mesh information + pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + pcMesh->mNumVertices = pcSurfaces->NUM_TRIANGLES * 3; + pcMesh->mNumFaces = pcSurfaces->NUM_TRIANGLES; + pcMesh->mFaces = new aiFace[pcSurfaces->NUM_TRIANGLES]; + pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNumUVComponents[0] = 2; + + // Fill in all triangles + unsigned int iCurrent = 0; + for (unsigned int i = 0; i < (unsigned int)pcSurfaces->NUM_TRIANGLES; ++i) { + pcMesh->mFaces[i].mIndices = new unsigned int[3]; + pcMesh->mFaces[i].mNumIndices = 3; + + //unsigned int iTemp = iCurrent; + for (unsigned int c = 0; c < 3; ++c, ++iCurrent) { + pcMesh->mFaces[i].mIndices[c] = iCurrent; + + // Read vertices + aiVector3D &vec = pcMesh->mVertices[iCurrent]; + uint32_t index = pcTriangles->INDEXES[c]; + if (index >= pcSurfaces->NUM_VERTICES) { + throw DeadlyImportError("MD3: Invalid vertex index"); + } + vec.x = pcVertices[index].X * AI_MD3_XYZ_SCALE; + vec.y = pcVertices[index].Y * AI_MD3_XYZ_SCALE; + vec.z = pcVertices[index].Z * AI_MD3_XYZ_SCALE; + + // Convert the normal vector to uncompressed float3 format + aiVector3D &nor = pcMesh->mNormals[iCurrent]; + LatLngNormalToVec3(pcVertices[index].NORMAL, (ai_real *)&nor); + + // Read texture coordinates + pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[index].U; + pcMesh->mTextureCoords[0][iCurrent].y = 1.0f - pcUVs[index].V; + } + // Flip face order normally, unless shader is backfacing + if (!(shader && shader->cull == Q3Shader::CULL_CCW)) { + std::swap(pcMesh->mFaces[i].mIndices[2], pcMesh->mFaces[i].mIndices[1]); + } + ++pcTriangles; + } + + // Go to the next surface + pcSurfaces = (BE_NCONST MD3::Surface *)(((unsigned char *)pcSurfaces) + pcSurfaces->OFS_END); + } + + // For debugging purposes: check whether we found matches for all entries in the skins file + if (!DefaultLogger::isNullLogger()) { + for (std::list<Q3Shader::SkinData::TextureEntry>::const_iterator it = skins.textures.begin(); it != skins.textures.end(); ++it) { + if (!(*it).resolved) { + ASSIMP_LOG_ERROR("MD3: Failed to match skin ", (*it).first, " to surface ", (*it).second); + } + } + } + + if (!pScene->mNumMeshes) { + throw DeadlyImportError("MD3: File contains no valid mesh"); + } + pScene->mNumMaterials = iNumMaterials; + + // Now we need to generate an empty node graph + pScene->mRootNode = new aiNode("<MD3Root>"); + pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; + pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; + + // Attach tiny children for all tags + if (pcHeader->NUM_TAGS) { + pScene->mRootNode->mNumChildren = pcHeader->NUM_TAGS; + pScene->mRootNode->mChildren = new aiNode *[pcHeader->NUM_TAGS]; + + for (unsigned int i = 0; i < pcHeader->NUM_TAGS; ++i, ++pcTags) { + aiNode *nd = pScene->mRootNode->mChildren[i] = new aiNode(); + nd->mName.Set((const char *)pcTags->NAME); + nd->mParent = pScene->mRootNode; + + AI_SWAP4(pcTags->origin.x); + AI_SWAP4(pcTags->origin.y); + AI_SWAP4(pcTags->origin.z); + + // Copy local origin, again flip z,y + nd->mTransformation.a4 = pcTags->origin.x; + nd->mTransformation.b4 = pcTags->origin.y; + nd->mTransformation.c4 = pcTags->origin.z; + + // Copy rest of transformation (need to transpose to match row-order matrix) + for (unsigned int a = 0; a < 3; ++a) { + for (unsigned int m = 0; m < 3; ++m) { + nd->mTransformation[m][a] = pcTags->orientation[a][m]; + AI_SWAP4(nd->mTransformation[m][a]); + } + } + } + } + + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) + pScene->mRootNode->mMeshes[i] = i; + + // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system + pScene->mRootNode->mTransformation = aiMatrix4x4( + 1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f); +} + +#endif // !! ASSIMP_BUILD_NO_MD3_IMPORTER diff --git a/libs/assimp/code/AssetLib/MD3/MD3Loader.h b/libs/assimp/code/AssetLib/MD3/MD3Loader.h new file mode 100644 index 0000000..d911bb1 --- /dev/null +++ b/libs/assimp/code/AssetLib/MD3/MD3Loader.h @@ -0,0 +1,314 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Md3Loader.h + * @brief Declaration of the .MD3 importer class. + */ +#ifndef AI_MD3LOADER_H_INCLUDED +#define AI_MD3LOADER_H_INCLUDED + +#include "MD3FileData.h" +#include <assimp/BaseImporter.h> +#include <assimp/ByteSwapper.h> +#include <assimp/StringComparison.h> +#include <assimp/types.h> + +#include <list> + +struct aiMaterial; + +namespace Assimp { + +using namespace MD3; +namespace Q3Shader { + +// --------------------------------------------------------------------------- +/** @brief Tiny utility data structure to hold the data of a .skin file + */ +struct SkinData { + //! A single entry in texture list + struct TextureEntry : public std::pair<std::string, std::string> { + // did we resolve this texture entry? + bool resolved; + + // for std::find() + bool operator==(const std::string &f) const { + return f == first; + } + }; + + //! List of textures + std::list<TextureEntry> textures; + + // rest is ignored for the moment +}; + +// --------------------------------------------------------------------------- +/** @brief Specifies cull modi for Quake shader files. + */ +enum ShaderCullMode { + CULL_NONE, + CULL_CW, + CULL_CCW +}; + +// --------------------------------------------------------------------------- +/** @brief Specifies alpha blend modi (src + dest) for Quake shader files + */ +enum BlendFunc { + BLEND_NONE, + BLEND_GL_ONE, + BLEND_GL_ZERO, + BLEND_GL_DST_COLOR, + BLEND_GL_ONE_MINUS_DST_COLOR, + BLEND_GL_SRC_ALPHA, + BLEND_GL_ONE_MINUS_SRC_ALPHA +}; + +// --------------------------------------------------------------------------- +/** @brief Specifies alpha test modi for Quake texture maps + */ +enum AlphaTestFunc { + AT_NONE, + AT_GT0, + AT_LT128, + AT_GE128 +}; + +// --------------------------------------------------------------------------- +/** @brief Tiny utility data structure to hold a .shader map data block + */ +struct ShaderMapBlock { + ShaderMapBlock() AI_NO_EXCEPT + : blend_src(BLEND_NONE), + blend_dest(BLEND_NONE), + alpha_test(AT_NONE) {} + + //! Name of referenced map + std::string name; + + //! Blend and alpha test settings for texture + BlendFunc blend_src, blend_dest; + AlphaTestFunc alpha_test; + + //! For std::find() + bool operator==(const std::string &o) const { + return !ASSIMP_stricmp(o, name); + } +}; + +// --------------------------------------------------------------------------- +/** @brief Tiny utility data structure to hold a .shader data block + */ +struct ShaderDataBlock { + ShaderDataBlock() AI_NO_EXCEPT + : cull(CULL_CW) {} + + //! Name of referenced data element + std::string name; + + //! Cull mode for the element + ShaderCullMode cull; + + //! Maps defined in the shader + std::list<ShaderMapBlock> maps; + + //! For std::find() + bool operator==(const std::string &o) const { + return !ASSIMP_stricmp(o, name); + } +}; + +// --------------------------------------------------------------------------- +/** @brief Tiny utility data structure to hold the data of a .shader file + */ +struct ShaderData { + //! Shader data blocks + std::list<ShaderDataBlock> blocks; +}; + +// --------------------------------------------------------------------------- +/** @brief Load a shader file + * + * Generally, parsing is error tolerant. There's no failure. + * @param fill Receives output data + * @param file File to be read. + * @param io IOSystem to be used for reading + * @return false if file is not accessible + */ +bool LoadShader(ShaderData &fill, const std::string &file, IOSystem *io); + +// --------------------------------------------------------------------------- +/** @brief Convert a Q3Shader to an aiMaterial + * + * @param[out] out Material structure to be filled. + * @param[in] shader Input shader + */ +void ConvertShaderToMaterial(aiMaterial *out, const ShaderDataBlock &shader); + +// --------------------------------------------------------------------------- +/** @brief Load a skin file + * + * Generally, parsing is error tolerant. There's no failure. + * @param fill Receives output data + * @param file File to be read. + * @param io IOSystem to be used for reading + * @return false if file is not accessible + */ +bool LoadSkin(SkinData &fill, const std::string &file, IOSystem *io); + +} // namespace Q3Shader + +// --------------------------------------------------------------------------- +/** @brief Importer class to load MD3 files +*/ +class MD3Importer : public BaseImporter { +public: + MD3Importer(); + ~MD3Importer() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer *pImp) override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + + // ------------------------------------------------------------------- + /** Validate offsets in the header + */ + void ValidateHeaderOffsets(); + void ValidateSurfaceHeaderOffsets(const MD3::Surface *pcSurfHeader); + + // ------------------------------------------------------------------- + /** Read a Q3 multipart file + * @return true if multi part has been processed + */ + bool ReadMultipartFile(); + + // ------------------------------------------------------------------- + /** Try to read the skin for a MD3 file + * @param fill Receives output information + */ + void ReadSkin(Q3Shader::SkinData &fill) const; + + // ------------------------------------------------------------------- + /** Try to read the shader for a MD3 file + * @param fill Receives output information + */ + void ReadShader(Q3Shader::ShaderData &fill) const; + + // ------------------------------------------------------------------- + /** Convert a texture path in a MD3 file to a proper value + * @param[in] texture_name Path to be converted + * @param[in] header_path Base path specified in MD3 header + * @param[out] out Receives the converted output string + */ + void ConvertPath(const char *texture_name, const char *header_path, + std::string &out) const; + +protected: + /** Configuration option: frame to be loaded */ + unsigned int configFrameID; + + /** Configuration option: process multi-part files */ + bool configHandleMP; + + /** Configuration option: name of skin file to be read */ + std::string configSkinFile; + + /** Configuration option: whether to load shaders */ + bool configLoadShaders; + + /** Configuration option: name or path of shader */ + std::string configShaderFile; + + /** Configuration option: speed flag was set? */ + bool configSpeedFlag; + + /** Header of the MD3 file */ + BE_NCONST MD3::Header *pcHeader; + + /** File buffer */ + BE_NCONST unsigned char *mBuffer; + + /** Size of the file, in bytes */ + unsigned int fileSize; + + /** Current file name */ + std::string mFile; + + /** Current base directory */ + std::string path; + + /** Pure file we're currently reading */ + std::string filename; + + /** Output scene to be filled */ + aiScene *mScene; + + /** IO system to be used to access the data*/ + IOSystem *mIOHandler; +}; + +} // end of namespace Assimp + +#endif // AI_3DSIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/MD4/MD4FileData.h b/libs/assimp/code/AssetLib/MD4/MD4FileData.h new file mode 100644 index 0000000..880df32 --- /dev/null +++ b/libs/assimp/code/AssetLib/MD4/MD4FileData.h @@ -0,0 +1,218 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, ASSIMP Development 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 Development 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 Defines the helper data structures for importing MD4 files */ +#ifndef AI_MD4FILEHELPER_H_INC +#define AI_MD4FILEHELPER_H_INC + +#include <string> +#include <vector> +#include <sstream> + +#include <assimp/types.h> +#include <assimp/mesh.h> +#include <assimp/anim.h> + +#if defined(_MSC_VER) || defined(__BORLANDC__) || defined (__BCPLUSPLUS__) +# pragma pack(push,1) +# define PACK_STRUCT +#elif defined( __GNUC__ ) +# define PACK_STRUCT __attribute__((packed)) +#else +# error Compiler not supported +#endif + + +namespace Assimp +{ +// http://gongo.quakedev.com/md4.html +namespace MD4 +{ + +#define AI_MD4_MAGIC_NUMBER_BE 'IDP4' +#define AI_MD4_MAGIC_NUMBER_LE '4PDI' + +// common limitations +#define AI_MD4_VERSION 4 +#define AI_MD4_MAXQPATH 64 +#define AI_MD4_MAX_FRAMES 2028 +#define AI_MD4_MAX_SURFACES 32 +#define AI_MD4_MAX_BONES 256 +#define AI_MD4_MAX_VERTS 4096 +#define AI_MD4_MAX_TRIANGLES 8192 + +// --------------------------------------------------------------------------- +/** \brief Data structure for the MD4 main header + */ +// --------------------------------------------------------------------------- +struct Header +{ + //! magic number + int32_t magic; + + //! file format version + int32_t version; + + //! original name in .pak archive + unsigned char name[ AI_MD4_MAXQPATH ]; + + //! number of frames in the file + int32_t NUM_FRAMES; + + //! number of bones in the file + int32_t NUM_BONES; + + //! number of surfaces in the file + int32_t NUM_SURFACES; + + //! offset of the first frame + int32_t OFS_FRAMES; + + //! offset of the first bone + int32_t OFS_BONES; + + //! offset of the first surface + int32_t OFS_SURFACES; + + //! end of file + int32_t OFS_EOF; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Stores the local transformation matrix of a bone + */ +// --------------------------------------------------------------------------- +struct BoneFrame +{ + float matrix[3][4]; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Stores the name / parent index / flag of a node + */ +// --------------------------------------------------------------------------- +struct BoneName +{ + char name[32] ; + int parent ; + int flags ; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a surface in a MD4 file + */ +// --------------------------------------------------------------------------- +struct Surface +{ + int32_t ident; + char name[64]; + char shader[64]; + int32_t shaderIndex; + int32_t lodBias; + int32_t minLod; + int32_t ofsHeader; + int32_t numVerts; + int32_t ofsVerts; + int32_t numTris; + int32_t ofsTris; + int32_t numBoneRefs; + int32_t ofsBoneRefs; + int32_t ofsCollapseMap; + int32_t ofsEnd; +} PACK_STRUCT; + + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MD4 vertex' weight + */ +// --------------------------------------------------------------------------- +struct Weight +{ + int32_t boneIndex; + float boneWeight; + float offset[3]; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a vertex in a MD4 file + */ +// --------------------------------------------------------------------------- +struct Vertex +{ + float vertex[3]; + float normal[3]; + float texCoords[2]; + int32_t numWeights; + Weight weights[1]; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a triangle in a MD4 file + */ +// --------------------------------------------------------------------------- +struct Triangle +{ + int32_t indexes[3]; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MD4 frame + */ +// --------------------------------------------------------------------------- +struct Frame +{ + float bounds[3][2]; + float localOrigin[3]; + float radius; + BoneFrame bones[1]; +} PACK_STRUCT; + + +// reset packing to the original value +#if defined(_MSC_VER) || defined(__BORLANDC__) || defined (__BCPLUSPLUS__) +# pragma pack( pop ) +#endif +#undef PACK_STRUCT + + +}; +}; + +#endif // !! AI_MD4FILEHELPER_H_INC diff --git a/libs/assimp/code/AssetLib/MD5/MD5Loader.cpp b/libs/assimp/code/AssetLib/MD5/MD5Loader.cpp new file mode 100644 index 0000000..2d5da4d --- /dev/null +++ b/libs/assimp/code/AssetLib/MD5/MD5Loader.cpp @@ -0,0 +1,735 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MD5Loader.cpp + * @brief Implementation of the MD5 importer class + */ + +#ifndef ASSIMP_BUILD_NO_MD5_IMPORTER + +// internal headers +#include "MD5Loader.h" +#include <assimp/MathFunctions.h> +#include <assimp/RemoveComments.h> +#include <assimp/SkeletonMeshBuilder.h> +#include <assimp/StringComparison.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/Importer.hpp> +#include <memory> + +using namespace Assimp; + +// Minimum weight value. Weights inside [-n ... n] are ignored +#define AI_MD5_WEIGHT_EPSILON Math::getEpsilon<float>() + +static const aiImporterDesc desc = { + "Doom 3 / MD5 Mesh Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "md5mesh md5camera md5anim" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +MD5Importer::MD5Importer() : + mIOHandler(nullptr), + mBuffer(), + mFileSize(), + mLineNumber(), + mScene(), + mHadMD5Mesh(), + mHadMD5Anim(), + mHadMD5Camera(), + mCconfigNoAutoLoad(false) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +MD5Importer::~MD5Importer() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool MD5Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "MD5Version" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Get list of all supported extensions +const aiImporterDesc *MD5Importer::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup import properties +void MD5Importer::SetupProperties(const Importer *pImp) { + // AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD + mCconfigNoAutoLoad = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD, 0)); +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void MD5Importer::InternReadFile(const std::string &pFile, aiScene *_pScene, IOSystem *pIOHandler) { + mIOHandler = pIOHandler; + mScene = _pScene; + mHadMD5Mesh = mHadMD5Anim = mHadMD5Camera = false; + + // remove the file extension + const std::string::size_type pos = pFile.find_last_of('.'); + mFile = (std::string::npos == pos ? pFile : pFile.substr(0, pos + 1)); + + const std::string extension = GetExtension(pFile); + try { + if (extension == "md5camera") { + LoadMD5CameraFile(); + } else if (mCconfigNoAutoLoad || extension == "md5anim") { + // determine file extension and process just *one* file + if (extension.length() == 0) { + throw DeadlyImportError("Failure, need file extension to determine MD5 part type"); + } + if (extension == "md5anim") { + LoadMD5AnimFile(); + } else if (extension == "md5mesh") { + LoadMD5MeshFile(); + } + } else { + LoadMD5MeshFile(); + LoadMD5AnimFile(); + } + } catch (...) { // std::exception, Assimp::DeadlyImportError + UnloadFileFromMemory(); + throw; + } + + // make sure we have at least one file + if (!mHadMD5Mesh && !mHadMD5Anim && !mHadMD5Camera) { + throw DeadlyImportError("Failed to read valid contents out of this MD5* file"); + } + + // Now rotate the whole scene 90 degrees around the x axis to match our internal coordinate system + mScene->mRootNode->mTransformation = aiMatrix4x4(1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, 0.f, -1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f); + + // the output scene wouldn't pass the validation without this flag + if (!mHadMD5Mesh) { + mScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } + + // clean the instance -- the BaseImporter instance may be reused later. + UnloadFileFromMemory(); +} + +// ------------------------------------------------------------------------------------------------ +// Load a file into a memory buffer +void MD5Importer::LoadFileIntoMemory(IOStream *file) { + // unload the previous buffer, if any + UnloadFileFromMemory(); + + ai_assert(nullptr != file); + mFileSize = (unsigned int)file->FileSize(); + ai_assert(mFileSize); + + // allocate storage and copy the contents of the file to a memory buffer + mBuffer = new char[mFileSize + 1]; + file->Read((void *)mBuffer, 1, mFileSize); + mLineNumber = 1; + + // append a terminal 0 + mBuffer[mFileSize] = '\0'; + + // now remove all line comments from the file + CommentRemover::RemoveLineComments("//", mBuffer, ' '); +} + +// ------------------------------------------------------------------------------------------------ +// Unload the current memory buffer +void MD5Importer::UnloadFileFromMemory() { + // delete the file buffer + delete[] mBuffer; + mBuffer = nullptr; + mFileSize = 0; +} + +// ------------------------------------------------------------------------------------------------ +// Build unique vertices +void MD5Importer::MakeDataUnique(MD5::MeshDesc &meshSrc) { + std::vector<bool> abHad(meshSrc.mVertices.size(), false); + + // allocate enough storage to keep the output structures + const unsigned int iNewNum = static_cast<unsigned int>(meshSrc.mFaces.size() * 3); + unsigned int iNewIndex = static_cast<unsigned int>(meshSrc.mVertices.size()); + meshSrc.mVertices.resize(iNewNum); + + // try to guess how much storage we'll need for new weights + const float fWeightsPerVert = meshSrc.mWeights.size() / (float)iNewIndex; + const unsigned int guess = (unsigned int)(fWeightsPerVert * iNewNum); + meshSrc.mWeights.reserve(guess + (guess >> 3)); // + 12.5% as buffer + + for (FaceList::const_iterator iter = meshSrc.mFaces.begin(), iterEnd = meshSrc.mFaces.end(); iter != iterEnd; ++iter) { + const aiFace &face = *iter; + for (unsigned int i = 0; i < 3; ++i) { + if (face.mIndices[0] >= meshSrc.mVertices.size()) { + throw DeadlyImportError("MD5MESH: Invalid vertex index"); + } + + if (abHad[face.mIndices[i]]) { + // generate a new vertex + meshSrc.mVertices[iNewIndex] = meshSrc.mVertices[face.mIndices[i]]; + face.mIndices[i] = iNewIndex++; + } else + abHad[face.mIndices[i]] = true; + } + // swap face order + std::swap(face.mIndices[0], face.mIndices[2]); + } +} + +// ------------------------------------------------------------------------------------------------ +// Recursive node graph construction from a MD5MESH +void MD5Importer::AttachChilds_Mesh(int iParentID, aiNode *piParent, BoneList &bones) { + ai_assert(nullptr != piParent); + ai_assert(!piParent->mNumChildren); + + // First find out how many children we'll have + for (int i = 0; i < (int)bones.size(); ++i) { + if (iParentID != i && bones[i].mParentIndex == iParentID) { + ++piParent->mNumChildren; + } + } + if (piParent->mNumChildren) { + piParent->mChildren = new aiNode *[piParent->mNumChildren]; + for (int i = 0; i < (int)bones.size(); ++i) { + // (avoid infinite recursion) + if (iParentID != i && bones[i].mParentIndex == iParentID) { + aiNode *pc; + // setup a new node + *piParent->mChildren++ = pc = new aiNode(); + pc->mName = aiString(bones[i].mName); + pc->mParent = piParent; + + // get the transformation matrix from rotation and translational components + aiQuaternion quat; + MD5::ConvertQuaternion(bones[i].mRotationQuat, quat); + + bones[i].mTransform = aiMatrix4x4(quat.GetMatrix()); + bones[i].mTransform.a4 = bones[i].mPositionXYZ.x; + bones[i].mTransform.b4 = bones[i].mPositionXYZ.y; + bones[i].mTransform.c4 = bones[i].mPositionXYZ.z; + + // store it for later use + pc->mTransformation = bones[i].mInvTransform = bones[i].mTransform; + bones[i].mInvTransform.Inverse(); + + // the transformations for each bone are absolute, so we need to multiply them + // with the inverse of the absolute matrix of the parent joint + if (-1 != iParentID) { + pc->mTransformation = bones[iParentID].mInvTransform * pc->mTransformation; + } + + // add children to this node, too + AttachChilds_Mesh(i, pc, bones); + } + } + // undo offset computations + piParent->mChildren -= piParent->mNumChildren; + } +} + +// ------------------------------------------------------------------------------------------------ +// Recursive node graph construction from a MD5ANIM +void MD5Importer::AttachChilds_Anim(int iParentID, aiNode *piParent, AnimBoneList &bones, const aiNodeAnim **node_anims) { + ai_assert(nullptr != piParent); + ai_assert(!piParent->mNumChildren); + + // First find out how many children we'll have + for (int i = 0; i < (int)bones.size(); ++i) { + if (iParentID != i && bones[i].mParentIndex == iParentID) { + ++piParent->mNumChildren; + } + } + if (piParent->mNumChildren) { + piParent->mChildren = new aiNode *[piParent->mNumChildren]; + for (int i = 0; i < (int)bones.size(); ++i) { + // (avoid infinite recursion) + if (iParentID != i && bones[i].mParentIndex == iParentID) { + aiNode *pc; + // setup a new node + *piParent->mChildren++ = pc = new aiNode(); + pc->mName = aiString(bones[i].mName); + pc->mParent = piParent; + + // get the corresponding animation channel and its first frame + const aiNodeAnim **cur = node_anims; + while ((**cur).mNodeName != pc->mName) + ++cur; + + aiMatrix4x4::Translation((**cur).mPositionKeys[0].mValue, pc->mTransformation); + pc->mTransformation = pc->mTransformation * aiMatrix4x4((**cur).mRotationKeys[0].mValue.GetMatrix()); + + // add children to this node, too + AttachChilds_Anim(i, pc, bones, node_anims); + } + } + // undo offset computations + piParent->mChildren -= piParent->mNumChildren; + } +} + +// ------------------------------------------------------------------------------------------------ +// Load a MD5MESH file +void MD5Importer::LoadMD5MeshFile() { + std::string filename = mFile + "md5mesh"; + std::unique_ptr<IOStream> file(mIOHandler->Open(filename, "rb")); + + // Check whether we can read from the file + if (file.get() == nullptr || !file->FileSize()) { + ASSIMP_LOG_WARN("Failed to access MD5MESH file: ", filename); + return; + } + mHadMD5Mesh = true; + LoadFileIntoMemory(file.get()); + + // now construct a parser and parse the file + MD5::MD5Parser parser(mBuffer, mFileSize); + + // load the mesh information from it + MD5::MD5MeshParser meshParser(parser.mSections); + + // create the bone hierarchy - first the root node and dummy nodes for all meshes + mScene->mRootNode = new aiNode("<MD5_Root>"); + mScene->mRootNode->mNumChildren = 2; + mScene->mRootNode->mChildren = new aiNode *[2]; + + // build the hierarchy from the MD5MESH file + aiNode *pcNode = mScene->mRootNode->mChildren[1] = new aiNode(); + pcNode->mName.Set("<MD5_Hierarchy>"); + pcNode->mParent = mScene->mRootNode; + AttachChilds_Mesh(-1, pcNode, meshParser.mJoints); + + pcNode = mScene->mRootNode->mChildren[0] = new aiNode(); + pcNode->mName.Set("<MD5_Mesh>"); + pcNode->mParent = mScene->mRootNode; + +#if 0 + if (pScene->mRootNode->mChildren[1]->mNumChildren) /* start at the right hierarchy level */ + SkeletonMeshBuilder skeleton_maker(pScene,pScene->mRootNode->mChildren[1]->mChildren[0]); +#else + + // FIX: MD5 files exported from Blender can have empty meshes + for (std::vector<MD5::MeshDesc>::const_iterator it = meshParser.mMeshes.begin(), end = meshParser.mMeshes.end(); it != end; ++it) { + if (!(*it).mFaces.empty() && !(*it).mVertices.empty()) { + ++mScene->mNumMaterials; + } + } + + // generate all meshes + mScene->mNumMeshes = mScene->mNumMaterials; + mScene->mMeshes = new aiMesh *[mScene->mNumMeshes]; + mScene->mMaterials = new aiMaterial *[mScene->mNumMeshes]; + + // storage for node mesh indices + pcNode->mNumMeshes = mScene->mNumMeshes; + pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes]; + for (unsigned int m = 0; m < pcNode->mNumMeshes; ++m) { + pcNode->mMeshes[m] = m; + } + + unsigned int n = 0; + for (std::vector<MD5::MeshDesc>::iterator it = meshParser.mMeshes.begin(), end = meshParser.mMeshes.end(); it != end; ++it) { + MD5::MeshDesc &meshSrc = *it; + if (meshSrc.mFaces.empty() || meshSrc.mVertices.empty()) { + continue; + } + + aiMesh *mesh = mScene->mMeshes[n] = new aiMesh(); + mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + // generate unique vertices in our internal verbose format + MakeDataUnique(meshSrc); + + std::string name(meshSrc.mShader.C_Str()); + name += ".msh"; + mesh->mName = name; + mesh->mNumVertices = (unsigned int)meshSrc.mVertices.size(); + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; + mesh->mNumUVComponents[0] = 2; + + // copy texture coordinates + aiVector3D *pv = mesh->mTextureCoords[0]; + for (MD5::VertexList::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) { + pv->x = (*iter).mUV.x; + pv->y = 1.0f - (*iter).mUV.y; // D3D to OpenGL + pv->z = 0.0f; + } + + // sort all bone weights - per bone + unsigned int *piCount = new unsigned int[meshParser.mJoints.size()]; + ::memset(piCount, 0, sizeof(unsigned int) * meshParser.mJoints.size()); + + for (MD5::VertexList::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) { + for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights; ++w) { + MD5::WeightDesc &weightDesc = meshSrc.mWeights[w]; + /* FIX for some invalid exporters */ + if (!(weightDesc.mWeight < AI_MD5_WEIGHT_EPSILON && weightDesc.mWeight >= -AI_MD5_WEIGHT_EPSILON)) { + ++piCount[weightDesc.mBone]; + } + } + } + + // check how many we will need + for (unsigned int p = 0; p < meshParser.mJoints.size(); ++p) { + if (piCount[p]) mesh->mNumBones++; + } + + // just for safety + if (mesh->mNumBones) { + mesh->mBones = new aiBone *[mesh->mNumBones]; + for (unsigned int q = 0, h = 0; q < meshParser.mJoints.size(); ++q) { + if (!piCount[q]) continue; + aiBone *p = mesh->mBones[h] = new aiBone(); + p->mNumWeights = piCount[q]; + p->mWeights = new aiVertexWeight[p->mNumWeights]; + p->mName = aiString(meshParser.mJoints[q].mName); + p->mOffsetMatrix = meshParser.mJoints[q].mInvTransform; + + // store the index for later use + MD5::BoneDesc &boneSrc = meshParser.mJoints[q]; + boneSrc.mMap = h++; + + // compute w-component of quaternion + MD5::ConvertQuaternion(boneSrc.mRotationQuat, boneSrc.mRotationQuatConverted); + } + + pv = mesh->mVertices; + for (MD5::VertexList::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) { + // compute the final vertex position from all single weights + *pv = aiVector3D(); + + // there are models which have weights which don't sum to 1 ... + ai_real fSum = 0.0; + for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights; ++w) { + fSum += meshSrc.mWeights[w].mWeight; + } + if (!fSum) { + ASSIMP_LOG_ERROR("MD5MESH: The sum of all vertex bone weights is 0"); + continue; + } + + // process bone weights + for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights; ++w) { + if (w >= meshSrc.mWeights.size()) { + throw DeadlyImportError("MD5MESH: Invalid weight index"); + } + + MD5::WeightDesc &weightDesc = meshSrc.mWeights[w]; + if (weightDesc.mWeight < AI_MD5_WEIGHT_EPSILON && weightDesc.mWeight >= -AI_MD5_WEIGHT_EPSILON) { + continue; + } + + const ai_real fNewWeight = weightDesc.mWeight / fSum; + + // transform the local position into worldspace + MD5::BoneDesc &boneSrc = meshParser.mJoints[weightDesc.mBone]; + const aiVector3D v = boneSrc.mRotationQuatConverted.Rotate(weightDesc.vOffsetPosition); + + // use the original weight to compute the vertex position + // (some MD5s seem to depend on the invalid weight values ...) + *pv += ((boneSrc.mPositionXYZ + v) * (ai_real)weightDesc.mWeight); + + aiBone *bone = mesh->mBones[boneSrc.mMap]; + *bone->mWeights++ = aiVertexWeight((unsigned int)(pv - mesh->mVertices), fNewWeight); + } + } + + // undo our nice offset tricks ... + for (unsigned int p = 0; p < mesh->mNumBones; ++p) { + mesh->mBones[p]->mWeights -= mesh->mBones[p]->mNumWeights; + } + } + + delete[] piCount; + + // now setup all faces - we can directly copy the list + // (however, take care that the aiFace destructor doesn't delete the mIndices array) + mesh->mNumFaces = (unsigned int)meshSrc.mFaces.size(); + mesh->mFaces = new aiFace[mesh->mNumFaces]; + for (unsigned int c = 0; c < mesh->mNumFaces; ++c) { + mesh->mFaces[c].mNumIndices = 3; + mesh->mFaces[c].mIndices = meshSrc.mFaces[c].mIndices; + meshSrc.mFaces[c].mIndices = nullptr; + } + + // generate a material for the mesh + aiMaterial *mat = new aiMaterial(); + mScene->mMaterials[n] = mat; + + // insert the typical doom3 textures: + // nnn_local.tga - normal map + // nnn_h.tga - height map + // nnn_s.tga - specular map + // nnn_d.tga - diffuse map + if (meshSrc.mShader.length && !strchr(meshSrc.mShader.data, '.')) { + + aiString temp(meshSrc.mShader); + temp.Append("_local.tga"); + mat->AddProperty(&temp, AI_MATKEY_TEXTURE_NORMALS(0)); + + temp = aiString(meshSrc.mShader); + temp.Append("_s.tga"); + mat->AddProperty(&temp, AI_MATKEY_TEXTURE_SPECULAR(0)); + + temp = aiString(meshSrc.mShader); + temp.Append("_d.tga"); + mat->AddProperty(&temp, AI_MATKEY_TEXTURE_DIFFUSE(0)); + + temp = aiString(meshSrc.mShader); + temp.Append("_h.tga"); + mat->AddProperty(&temp, AI_MATKEY_TEXTURE_HEIGHT(0)); + + // set this also as material name + mat->AddProperty(&meshSrc.mShader, AI_MATKEY_NAME); + } else { + mat->AddProperty(&meshSrc.mShader, AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + mesh->mMaterialIndex = n++; + } +#endif +} + +// ------------------------------------------------------------------------------------------------ +// Load an MD5ANIM file +void MD5Importer::LoadMD5AnimFile() { + std::string pFile = mFile + "md5anim"; + std::unique_ptr<IOStream> file(mIOHandler->Open(pFile, "rb")); + + // Check whether we can read from the file + if (!file.get() || !file->FileSize()) { + ASSIMP_LOG_WARN("Failed to read MD5ANIM file: ", pFile); + return; + } + + LoadFileIntoMemory(file.get()); + + // parse the basic file structure + MD5::MD5Parser parser(mBuffer, mFileSize); + + // load the animation information from the parse tree + MD5::MD5AnimParser animParser(parser.mSections); + + // generate and fill the output animation + if (animParser.mAnimatedBones.empty() || animParser.mFrames.empty() || + animParser.mBaseFrames.size() != animParser.mAnimatedBones.size()) { + ASSIMP_LOG_ERROR("MD5ANIM: No frames or animated bones loaded"); + } else { + mHadMD5Anim = true; + + mScene->mAnimations = new aiAnimation *[mScene->mNumAnimations = 1]; + aiAnimation *anim = mScene->mAnimations[0] = new aiAnimation(); + anim->mNumChannels = (unsigned int)animParser.mAnimatedBones.size(); + anim->mChannels = new aiNodeAnim *[anim->mNumChannels]; + for (unsigned int i = 0; i < anim->mNumChannels; ++i) { + aiNodeAnim *node = anim->mChannels[i] = new aiNodeAnim(); + node->mNodeName = aiString(animParser.mAnimatedBones[i].mName); + + // allocate storage for the keyframes + node->mPositionKeys = new aiVectorKey[animParser.mFrames.size()]; + node->mRotationKeys = new aiQuatKey[animParser.mFrames.size()]; + } + + // 1 tick == 1 frame + anim->mTicksPerSecond = animParser.fFrameRate; + + for (FrameList::const_iterator iter = animParser.mFrames.begin(), iterEnd = animParser.mFrames.end(); iter != iterEnd; ++iter) { + double dTime = (double)(*iter).iIndex; + aiNodeAnim **pcAnimNode = anim->mChannels; + if (!(*iter).mValues.empty() || iter == animParser.mFrames.begin()) /* be sure we have at least one frame */ + { + // now process all values in there ... read all joints + MD5::BaseFrameDesc *pcBaseFrame = &animParser.mBaseFrames[0]; + for (AnimBoneList::const_iterator iter2 = animParser.mAnimatedBones.begin(); iter2 != animParser.mAnimatedBones.end(); ++iter2, + ++pcAnimNode, ++pcBaseFrame) { + if ((*iter2).iFirstKeyIndex >= (*iter).mValues.size()) { + + // Allow for empty frames + if ((*iter2).iFlags != 0) { + throw DeadlyImportError("MD5: Keyframe index is out of range"); + } + continue; + } + const float *fpCur = &(*iter).mValues[(*iter2).iFirstKeyIndex]; + aiNodeAnim *pcCurAnimBone = *pcAnimNode; + + aiVectorKey *vKey = &pcCurAnimBone->mPositionKeys[pcCurAnimBone->mNumPositionKeys++]; + aiQuatKey *qKey = &pcCurAnimBone->mRotationKeys[pcCurAnimBone->mNumRotationKeys++]; + aiVector3D vTemp; + + // translational component + for (unsigned int i = 0; i < 3; ++i) { + if ((*iter2).iFlags & (1u << i)) { + vKey->mValue[i] = *fpCur++; + } else + vKey->mValue[i] = pcBaseFrame->vPositionXYZ[i]; + } + + // orientation component + for (unsigned int i = 0; i < 3; ++i) { + if ((*iter2).iFlags & (8u << i)) { + vTemp[i] = *fpCur++; + } else + vTemp[i] = pcBaseFrame->vRotationQuat[i]; + } + + MD5::ConvertQuaternion(vTemp, qKey->mValue); + qKey->mTime = vKey->mTime = dTime; + } + } + + // compute the duration of the animation + anim->mDuration = std::max(dTime, anim->mDuration); + } + + // If we didn't build the hierarchy yet (== we didn't load a MD5MESH), + // construct it now from the data given in the MD5ANIM. + if (!mScene->mRootNode) { + mScene->mRootNode = new aiNode(); + mScene->mRootNode->mName.Set("<MD5_Hierarchy>"); + + AttachChilds_Anim(-1, mScene->mRootNode, animParser.mAnimatedBones, (const aiNodeAnim **)anim->mChannels); + + // Call SkeletonMeshBuilder to construct a mesh to represent the shape + if (mScene->mRootNode->mNumChildren) { + SkeletonMeshBuilder skeleton_maker(mScene, mScene->mRootNode->mChildren[0]); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Load an MD5CAMERA file +void MD5Importer::LoadMD5CameraFile() { + std::string pFile = mFile + "md5camera"; + std::unique_ptr<IOStream> file(mIOHandler->Open(pFile, "rb")); + + // Check whether we can read from the file + if (!file.get() || !file->FileSize()) { + throw DeadlyImportError("Failed to read MD5CAMERA file: ", pFile); + } + mHadMD5Camera = true; + LoadFileIntoMemory(file.get()); + + // parse the basic file structure + MD5::MD5Parser parser(mBuffer, mFileSize); + + // load the camera animation data from the parse tree + MD5::MD5CameraParser cameraParser(parser.mSections); + + if (cameraParser.frames.empty()) { + throw DeadlyImportError("MD5CAMERA: No frames parsed"); + } + + std::vector<unsigned int> &cuts = cameraParser.cuts; + std::vector<MD5::CameraAnimFrameDesc> &frames = cameraParser.frames; + + // Construct output graph - a simple root with a dummy child. + // The root node performs the coordinate system conversion + aiNode *root = mScene->mRootNode = new aiNode("<MD5CameraRoot>"); + root->mChildren = new aiNode *[root->mNumChildren = 1]; + root->mChildren[0] = new aiNode("<MD5Camera>"); + root->mChildren[0]->mParent = root; + + // ... but with one camera assigned to it + mScene->mCameras = new aiCamera *[mScene->mNumCameras = 1]; + aiCamera *cam = mScene->mCameras[0] = new aiCamera(); + cam->mName = "<MD5Camera>"; + + // FIXME: Fov is currently set to the first frame's value + cam->mHorizontalFOV = AI_DEG_TO_RAD(frames.front().fFOV); + + // every cut is written to a separate aiAnimation + if (!cuts.size()) { + cuts.push_back(0); + cuts.push_back(static_cast<unsigned int>(frames.size() - 1)); + } else { + cuts.insert(cuts.begin(), 0); + + if (cuts.back() < frames.size() - 1) + cuts.push_back(static_cast<unsigned int>(frames.size() - 1)); + } + + mScene->mNumAnimations = static_cast<unsigned int>(cuts.size() - 1); + aiAnimation **tmp = mScene->mAnimations = new aiAnimation *[mScene->mNumAnimations]; + for (std::vector<unsigned int>::const_iterator it = cuts.begin(); it != cuts.end() - 1; ++it) { + + aiAnimation *anim = *tmp++ = new aiAnimation(); + anim->mName.length = ::ai_snprintf(anim->mName.data, MAXLEN, "anim%u_from_%u_to_%u", (unsigned int)(it - cuts.begin()), (*it), *(it + 1)); + + anim->mTicksPerSecond = cameraParser.fFrameRate; + anim->mChannels = new aiNodeAnim *[anim->mNumChannels = 1]; + aiNodeAnim *nd = anim->mChannels[0] = new aiNodeAnim(); + nd->mNodeName.Set("<MD5Camera>"); + + nd->mNumPositionKeys = nd->mNumRotationKeys = *(it + 1) - (*it); + nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys]; + nd->mRotationKeys = new aiQuatKey[nd->mNumRotationKeys]; + for (unsigned int i = 0; i < nd->mNumPositionKeys; ++i) { + + nd->mPositionKeys[i].mValue = frames[*it + i].vPositionXYZ; + MD5::ConvertQuaternion(frames[*it + i].vRotationQuat, nd->mRotationKeys[i].mValue); + nd->mRotationKeys[i].mTime = nd->mPositionKeys[i].mTime = *it + i; + } + } +} + +#endif // !! ASSIMP_BUILD_NO_MD5_IMPORTER diff --git a/libs/assimp/code/AssetLib/MD5/MD5Loader.h b/libs/assimp/code/AssetLib/MD5/MD5Loader.h new file mode 100644 index 0000000..63fb1c3 --- /dev/null +++ b/libs/assimp/code/AssetLib/MD5/MD5Loader.h @@ -0,0 +1,181 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MD5Loader.h + * @brief Definition of the .MD5 importer class. + * http://www.modwiki.net/wiki/MD5_(file_format) +*/ +#pragma once +#ifndef AI_MD5LOADER_H_INCLUDED +#define AI_MD5LOADER_H_INCLUDED + +#include "MD5Parser.h" +#include <assimp/BaseImporter.h> +#include <assimp/types.h> + +struct aiNode; +struct aiNodeAnim; + +namespace Assimp { + +class IOStream; +using namespace Assimp::MD5; + +// --------------------------------------------------------------------------- +/** Importer class for the MD5 file format +*/ +class MD5Importer : public BaseImporter { +public: + MD5Importer(); + ~MD5Importer() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer *pImp) override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + + // ------------------------------------------------------------------- + /** Load a *.MD5MESH file. + */ + void LoadMD5MeshFile(); + + // ------------------------------------------------------------------- + /** Load a *.MD5ANIM file. + */ + void LoadMD5AnimFile(); + + // ------------------------------------------------------------------- + /** Load a *.MD5CAMERA file. + */ + void LoadMD5CameraFile(); + + // ------------------------------------------------------------------- + /** Construct node hierarchy from a given MD5ANIM + * @param iParentID Current parent ID + * @param piParent Parent node to attach to + * @param bones Input bones + * @param node_anims Generated node animations + */ + void AttachChilds_Anim(int iParentID, aiNode *piParent, + AnimBoneList &bones, const aiNodeAnim **node_anims); + + // ------------------------------------------------------------------- + /** Construct node hierarchy from a given MD5MESH + * @param iParentID Current parent ID + * @param piParent Parent node to attach to + * @param bones Input bones + */ + void AttachChilds_Mesh(int iParentID, aiNode *piParent, BoneList &bones); + + // ------------------------------------------------------------------- + /** Build unique vertex buffers from a given MD5ANIM + * @param meshSrc Input data + */ + void MakeDataUnique(MD5::MeshDesc &meshSrc); + + // ------------------------------------------------------------------- + /** Load the contents of a specific file into memory and + * allocates a buffer to keep it. + * + * mBuffer is modified to point to this buffer. + * @param pFile File stream to be read + */ + void LoadFileIntoMemory(IOStream *pFile); + void UnloadFileFromMemory(); + + /** IOSystem to be used to access files */ + IOSystem *mIOHandler; + + /** Path to the file, excluding the file extension but + with the dot */ + std::string mFile; + + /** Buffer to hold the loaded file */ + char *mBuffer; + + /** Size of the file */ + unsigned int mFileSize; + + /** Current line number. For debugging purposes */ + unsigned int mLineNumber; + + /** Scene to be filled */ + aiScene *mScene; + + /** true if a MD5MESH file has already been parsed */ + bool mHadMD5Mesh; + + /** true if a MD5ANIM file has already been parsed */ + bool mHadMD5Anim; + + /** true if a MD5CAMERA file has already been parsed */ + bool mHadMD5Camera; + + /** configuration option: prevent anim autoload */ + bool mCconfigNoAutoLoad; +}; + +} // end of namespace Assimp + +#endif // AI_3DSIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/MD5/MD5Parser.cpp b/libs/assimp/code/AssetLib/MD5/MD5Parser.cpp new file mode 100644 index 0000000..7ed23cb --- /dev/null +++ b/libs/assimp/code/AssetLib/MD5/MD5Parser.cpp @@ -0,0 +1,472 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MD5Parser.cpp + * @brief Implementation of the MD5 parser class + */ + +// internal headers +#include "AssetLib/MD5/MD5Loader.h" +#include "Material/MaterialSystem.h" + +#include <assimp/ParsingUtils.h> +#include <assimp/StringComparison.h> +#include <assimp/fast_atof.h> +#include <assimp/mesh.h> +#include <assimp/DefaultLogger.hpp> + +using namespace Assimp; +using namespace Assimp::MD5; + +// ------------------------------------------------------------------------------------------------ +// Parse the segment structure for an MD5 file +MD5Parser::MD5Parser(char *_buffer, unsigned int _fileSize) { + ai_assert(nullptr != _buffer); + ai_assert(0 != _fileSize); + + buffer = _buffer; + fileSize = _fileSize; + lineNumber = 0; + + ASSIMP_LOG_DEBUG("MD5Parser begin"); + + // parse the file header + ParseHeader(); + + // and read all sections until we're finished + bool running = true; + while (running) { + mSections.push_back(Section()); + Section &sec = mSections.back(); + if (!ParseSection(sec)) { + break; + } + } + + if (!DefaultLogger::isNullLogger()) { + char szBuffer[128]; // should be sufficiently large + ::ai_snprintf(szBuffer, 128, "MD5Parser end. Parsed %i sections", (int)mSections.size()); + ASSIMP_LOG_DEBUG(szBuffer); + } +} + +// ------------------------------------------------------------------------------------------------ +// Report error to the log stream +/*static*/ AI_WONT_RETURN void MD5Parser::ReportError(const char *error, unsigned int line) { + char szBuffer[1024]; + ::ai_snprintf(szBuffer, 1024, "[MD5] Line %u: %s", line, error); + throw DeadlyImportError(szBuffer); +} + +// ------------------------------------------------------------------------------------------------ +// Report warning to the log stream +/*static*/ void MD5Parser::ReportWarning(const char *warn, unsigned int line) { + char szBuffer[1024]; + ::sprintf(szBuffer, "[MD5] Line %u: %s", line, warn); + ASSIMP_LOG_WARN(szBuffer); +} + +// ------------------------------------------------------------------------------------------------ +// Parse and validate the MD5 header +void MD5Parser::ParseHeader() { + // parse and validate the file version + SkipSpaces(); + if (!TokenMatch(buffer, "MD5Version", 10)) { + ReportError("Invalid MD5 file: MD5Version tag has not been found"); + } + SkipSpaces(); + unsigned int iVer = ::strtoul10(buffer, (const char **)&buffer); + if (10 != iVer) { + ReportError("MD5 version tag is unknown (10 is expected)"); + } + SkipLine(); + + // print the command line options to the console + // FIX: can break the log length limit, so we need to be careful + char *sz = buffer; + while (!IsLineEnd(*buffer++)) + ; + ASSIMP_LOG_INFO(std::string(sz, std::min((uintptr_t)MAX_LOG_MESSAGE_LENGTH, (uintptr_t)(buffer - sz)))); + SkipSpacesAndLineEnd(); +} + +// ------------------------------------------------------------------------------------------------ +// Recursive MD5 parsing function +bool MD5Parser::ParseSection(Section &out) { + // store the current line number for use in error messages + out.iLineNumber = lineNumber; + + // first parse the name of the section + char *sz = buffer; + while (!IsSpaceOrNewLine(*buffer)) + buffer++; + out.mName = std::string(sz, (uintptr_t)(buffer - sz)); + SkipSpaces(); + + bool running = true; + while (running) { + if ('{' == *buffer) { + // it is a normal section so read all lines + buffer++; + bool run = true; + while (run) { + if (!SkipSpacesAndLineEnd()) { + return false; // seems this was the last section + } + if ('}' == *buffer) { + buffer++; + break; + } + + out.mElements.push_back(Element()); + Element &elem = out.mElements.back(); + + elem.iLineNumber = lineNumber; + elem.szStart = buffer; + + // terminate the line with zero + while (!IsLineEnd(*buffer)) + buffer++; + if (*buffer) { + ++lineNumber; + *buffer++ = '\0'; + } + } + break; + } else if (!IsSpaceOrNewLine(*buffer)) { + // it is an element at global scope. Parse its value and go on + sz = buffer; + while (!IsSpaceOrNewLine(*buffer++)) + ; + out.mGlobalValue = std::string(sz, (uintptr_t)(buffer - sz)); + continue; + } + break; + } + return SkipSpacesAndLineEnd(); +} + +// ------------------------------------------------------------------------------------------------ +// Some dirty macros just because they're so funny and easy to debug + +// skip all spaces ... handle EOL correctly +#define AI_MD5_SKIP_SPACES() \ + if (!SkipSpaces(&sz)) \ + MD5Parser::ReportWarning("Unexpected end of line", elem.iLineNumber); + +// read a triple float in brackets: (1.0 1.0 1.0) +#define AI_MD5_READ_TRIPLE(vec) \ + AI_MD5_SKIP_SPACES(); \ + if ('(' != *sz++) \ + MD5Parser::ReportWarning("Unexpected token: ( was expected", elem.iLineNumber); \ + AI_MD5_SKIP_SPACES(); \ + sz = fast_atoreal_move<float>(sz, (float &)vec.x); \ + AI_MD5_SKIP_SPACES(); \ + sz = fast_atoreal_move<float>(sz, (float &)vec.y); \ + AI_MD5_SKIP_SPACES(); \ + sz = fast_atoreal_move<float>(sz, (float &)vec.z); \ + AI_MD5_SKIP_SPACES(); \ + if (')' != *sz++) \ + MD5Parser::ReportWarning("Unexpected token: ) was expected", elem.iLineNumber); + +// parse a string, enclosed in quotation marks or not +#define AI_MD5_PARSE_STRING(out) \ + bool bQuota = (*sz == '\"'); \ + const char *szStart = sz; \ + while (!IsSpaceOrNewLine(*sz)) \ + ++sz; \ + const char *szEnd = sz; \ + if (bQuota) { \ + szStart++; \ + if ('\"' != *(szEnd -= 1)) { \ + MD5Parser::ReportWarning("Expected closing quotation marks in string", \ + elem.iLineNumber); \ + continue; \ + } \ + } \ + out.length = (size_t)(szEnd - szStart); \ + ::memcpy(out.data, szStart, out.length); \ + out.data[out.length] = '\0'; + +// parse a string, enclosed in quotation marks +#define AI_MD5_PARSE_STRING_IN_QUOTATION(out) \ + while ('\"' != *sz) \ + ++sz; \ + const char *szStart = ++sz; \ + while ('\"' != *sz) \ + ++sz; \ + const char *szEnd = (sz++); \ + out.length = (ai_uint32)(szEnd - szStart); \ + ::memcpy(out.data, szStart, out.length); \ + out.data[out.length] = '\0'; +// ------------------------------------------------------------------------------------------------ +// .MD5MESH parsing function +MD5MeshParser::MD5MeshParser(SectionList &mSections) { + ASSIMP_LOG_DEBUG("MD5MeshParser begin"); + + // now parse all sections + for (SectionList::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) { + if ((*iter).mName == "numMeshes") { + mMeshes.reserve(::strtoul10((*iter).mGlobalValue.c_str())); + } else if ((*iter).mName == "numJoints") { + mJoints.reserve(::strtoul10((*iter).mGlobalValue.c_str())); + } else if ((*iter).mName == "joints") { + // "origin" -1 ( -0.000000 0.016430 -0.006044 ) ( 0.707107 0.000000 0.707107 ) + for (const auto &elem : (*iter).mElements) { + mJoints.push_back(BoneDesc()); + BoneDesc &desc = mJoints.back(); + + const char *sz = elem.szStart; + AI_MD5_PARSE_STRING_IN_QUOTATION(desc.mName); + AI_MD5_SKIP_SPACES(); + + // negative values, at least -1, is allowed here + desc.mParentIndex = (int)strtol10(sz, &sz); + + AI_MD5_READ_TRIPLE(desc.mPositionXYZ); + AI_MD5_READ_TRIPLE(desc.mRotationQuat); // normalized quaternion, so w is not there + } + } else if ((*iter).mName == "mesh") { + mMeshes.push_back(MeshDesc()); + MeshDesc &desc = mMeshes.back(); + + for (const auto &elem : (*iter).mElements) { + const char *sz = elem.szStart; + + // shader attribute + if (TokenMatch(sz, "shader", 6)) { + AI_MD5_SKIP_SPACES(); + AI_MD5_PARSE_STRING_IN_QUOTATION(desc.mShader); + } + // numverts attribute + else if (TokenMatch(sz, "numverts", 8)) { + AI_MD5_SKIP_SPACES(); + desc.mVertices.resize(strtoul10(sz)); + } + // numtris attribute + else if (TokenMatch(sz, "numtris", 7)) { + AI_MD5_SKIP_SPACES(); + desc.mFaces.resize(strtoul10(sz)); + } + // numweights attribute + else if (TokenMatch(sz, "numweights", 10)) { + AI_MD5_SKIP_SPACES(); + desc.mWeights.resize(strtoul10(sz)); + } + // vert attribute + // "vert 0 ( 0.394531 0.513672 ) 0 1" + else if (TokenMatch(sz, "vert", 4)) { + AI_MD5_SKIP_SPACES(); + const unsigned int idx = ::strtoul10(sz, &sz); + AI_MD5_SKIP_SPACES(); + if (idx >= desc.mVertices.size()) + desc.mVertices.resize(idx + 1); + + VertexDesc &vert = desc.mVertices[idx]; + if ('(' != *sz++) + MD5Parser::ReportWarning("Unexpected token: ( was expected", elem.iLineNumber); + AI_MD5_SKIP_SPACES(); + sz = fast_atoreal_move<float>(sz, (float &)vert.mUV.x); + AI_MD5_SKIP_SPACES(); + sz = fast_atoreal_move<float>(sz, (float &)vert.mUV.y); + AI_MD5_SKIP_SPACES(); + if (')' != *sz++) + MD5Parser::ReportWarning("Unexpected token: ) was expected", elem.iLineNumber); + AI_MD5_SKIP_SPACES(); + vert.mFirstWeight = ::strtoul10(sz, &sz); + AI_MD5_SKIP_SPACES(); + vert.mNumWeights = ::strtoul10(sz, &sz); + } + // tri attribute + // "tri 0 15 13 12" + else if (TokenMatch(sz, "tri", 3)) { + AI_MD5_SKIP_SPACES(); + const unsigned int idx = strtoul10(sz, &sz); + if (idx >= desc.mFaces.size()) + desc.mFaces.resize(idx + 1); + + aiFace &face = desc.mFaces[idx]; + face.mIndices = new unsigned int[face.mNumIndices = 3]; + for (unsigned int i = 0; i < 3; ++i) { + AI_MD5_SKIP_SPACES(); + face.mIndices[i] = strtoul10(sz, &sz); + } + } + // weight attribute + // "weight 362 5 0.500000 ( -3.553583 11.893474 9.719339 )" + else if (TokenMatch(sz, "weight", 6)) { + AI_MD5_SKIP_SPACES(); + const unsigned int idx = strtoul10(sz, &sz); + AI_MD5_SKIP_SPACES(); + if (idx >= desc.mWeights.size()) + desc.mWeights.resize(idx + 1); + + WeightDesc &weight = desc.mWeights[idx]; + weight.mBone = strtoul10(sz, &sz); + AI_MD5_SKIP_SPACES(); + sz = fast_atoreal_move<float>(sz, weight.mWeight); + AI_MD5_READ_TRIPLE(weight.vOffsetPosition); + } + } + } + } + ASSIMP_LOG_DEBUG("MD5MeshParser end"); +} + +// ------------------------------------------------------------------------------------------------ +// .MD5ANIM parsing function +MD5AnimParser::MD5AnimParser(SectionList &mSections) { + ASSIMP_LOG_DEBUG("MD5AnimParser begin"); + + fFrameRate = 24.0f; + mNumAnimatedComponents = UINT_MAX; + for (SectionList::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) { + if ((*iter).mName == "hierarchy") { + // "sheath" 0 63 6 + for (const auto &elem : (*iter).mElements) { + mAnimatedBones.push_back(AnimBoneDesc()); + AnimBoneDesc &desc = mAnimatedBones.back(); + + const char *sz = elem.szStart; + AI_MD5_PARSE_STRING_IN_QUOTATION(desc.mName); + AI_MD5_SKIP_SPACES(); + + // parent index - negative values are allowed (at least -1) + desc.mParentIndex = ::strtol10(sz, &sz); + + // flags (highest is 2^6-1) + AI_MD5_SKIP_SPACES(); + if (63 < (desc.iFlags = ::strtoul10(sz, &sz))) { + MD5Parser::ReportWarning("Invalid flag combination in hierarchy section", elem.iLineNumber); + } + AI_MD5_SKIP_SPACES(); + + // index of the first animation keyframe component for this joint + desc.iFirstKeyIndex = ::strtoul10(sz, &sz); + } + } else if ((*iter).mName == "baseframe") { + // ( -0.000000 0.016430 -0.006044 ) ( 0.707107 0.000242 0.707107 ) + for (const auto &elem : (*iter).mElements) { + const char *sz = elem.szStart; + + mBaseFrames.push_back(BaseFrameDesc()); + BaseFrameDesc &desc = mBaseFrames.back(); + + AI_MD5_READ_TRIPLE(desc.vPositionXYZ); + AI_MD5_READ_TRIPLE(desc.vRotationQuat); + } + } else if ((*iter).mName == "frame") { + if (!(*iter).mGlobalValue.length()) { + MD5Parser::ReportWarning("A frame section must have a frame index", (*iter).iLineNumber); + continue; + } + + mFrames.push_back(FrameDesc()); + FrameDesc &desc = mFrames.back(); + desc.iIndex = strtoul10((*iter).mGlobalValue.c_str()); + + // we do already know how much storage we will presumably need + if (UINT_MAX != mNumAnimatedComponents) { + desc.mValues.reserve(mNumAnimatedComponents); + } + + // now read all elements (continuous list of floats) + for (const auto &elem : (*iter).mElements) { + const char *sz = elem.szStart; + while (SkipSpacesAndLineEnd(&sz)) { + float f; + sz = fast_atoreal_move<float>(sz, f); + desc.mValues.push_back(f); + } + } + } else if ((*iter).mName == "numFrames") { + mFrames.reserve(strtoul10((*iter).mGlobalValue.c_str())); + } else if ((*iter).mName == "numJoints") { + const unsigned int num = strtoul10((*iter).mGlobalValue.c_str()); + mAnimatedBones.reserve(num); + + // try to guess the number of animated components if that element is not given + if (UINT_MAX == mNumAnimatedComponents) { + mNumAnimatedComponents = num * 6; + } + } else if ((*iter).mName == "numAnimatedComponents") { + mAnimatedBones.reserve(strtoul10((*iter).mGlobalValue.c_str())); + } else if ((*iter).mName == "frameRate") { + fast_atoreal_move<float>((*iter).mGlobalValue.c_str(), fFrameRate); + } + } + ASSIMP_LOG_DEBUG("MD5AnimParser end"); +} + +// ------------------------------------------------------------------------------------------------ +// .MD5CAMERA parsing function +MD5CameraParser::MD5CameraParser(SectionList &mSections) { + ASSIMP_LOG_DEBUG("MD5CameraParser begin"); + fFrameRate = 24.0f; + + for (SectionList::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) { + if ((*iter).mName == "numFrames") { + frames.reserve(strtoul10((*iter).mGlobalValue.c_str())); + } else if ((*iter).mName == "frameRate") { + fFrameRate = fast_atof((*iter).mGlobalValue.c_str()); + } else if ((*iter).mName == "numCuts") { + cuts.reserve(strtoul10((*iter).mGlobalValue.c_str())); + } else if ((*iter).mName == "cuts") { + for (const auto &elem : (*iter).mElements) { + cuts.push_back(strtoul10(elem.szStart) + 1); + } + } else if ((*iter).mName == "camera") { + for (const auto &elem : (*iter).mElements) { + const char *sz = elem.szStart; + + frames.push_back(CameraAnimFrameDesc()); + CameraAnimFrameDesc &cur = frames.back(); + AI_MD5_READ_TRIPLE(cur.vPositionXYZ); + AI_MD5_READ_TRIPLE(cur.vRotationQuat); + AI_MD5_SKIP_SPACES(); + cur.fFOV = fast_atof(sz); + } + } + } + ASSIMP_LOG_DEBUG("MD5CameraParser end"); +} diff --git a/libs/assimp/code/AssetLib/MD5/MD5Parser.h b/libs/assimp/code/AssetLib/MD5/MD5Parser.h new file mode 100644 index 0000000..3108655 --- /dev/null +++ b/libs/assimp/code/AssetLib/MD5/MD5Parser.h @@ -0,0 +1,467 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MD5Parser.h + * @brief Definition of the .MD5 parser class. + * http://www.modwiki.net/wiki/MD5_(file_format) + */ +#ifndef AI_MD5PARSER_H_INCLUDED +#define AI_MD5PARSER_H_INCLUDED + +#include <assimp/types.h> +#include <assimp/ParsingUtils.h> +#include <vector> +#include <stdint.h> + +struct aiFace; + +namespace Assimp { +namespace MD5 { + +// --------------------------------------------------------------------------- +/** Represents a single element in a MD5 file + * + * Elements are always contained in sections. +*/ +struct Element +{ + //! Points to the starting point of the element + //! Whitespace at the beginning and at the end have been removed, + //! Elements are terminated with \0 + char* szStart; + + //! Original line number (can be used in error messages + //! if a parsing error occurs) + unsigned int iLineNumber; +}; + +typedef std::vector< Element > ElementList; + +// --------------------------------------------------------------------------- +/** Represents a section of a MD5 file (such as the mesh or the joints section) + * + * A section is always enclosed in { and } brackets. +*/ +struct Section +{ + //! Original line number (can be used in error messages + //! if a parsing error occurs) + unsigned int iLineNumber; + + //! List of all elements which have been parsed in this section. + ElementList mElements; + + //! Name of the section + std::string mName; + + //! For global elements: the value of the element as string + //! Iif !length() the section is not a global element + std::string mGlobalValue; +}; + +typedef std::vector< Section> SectionList; + +// --------------------------------------------------------------------------- +/** Basic information about a joint +*/ +struct BaseJointDescription +{ + //! Name of the bone + aiString mName; + + //! Parent index of the bone + int mParentIndex; +}; + +// --------------------------------------------------------------------------- +/** Represents a bone (joint) descriptor in a MD5Mesh file +*/ +struct BoneDesc : BaseJointDescription +{ + //! Absolute position of the bone + aiVector3D mPositionXYZ; + + //! Absolute rotation of the bone + aiVector3D mRotationQuat; + aiQuaternion mRotationQuatConverted; + + //! Absolute transformation of the bone + //! (temporary) + aiMatrix4x4 mTransform; + + //! Inverse transformation of the bone + //! (temporary) + aiMatrix4x4 mInvTransform; + + //! Internal + unsigned int mMap; +}; + +typedef std::vector< BoneDesc > BoneList; + +// --------------------------------------------------------------------------- +/** Represents a bone (joint) descriptor in a MD5Anim file +*/ +struct AnimBoneDesc : BaseJointDescription +{ + //! Flags (AI_MD5_ANIMATION_FLAG_xxx) + unsigned int iFlags; + + //! Index of the first key that corresponds to this anim bone + unsigned int iFirstKeyIndex; +}; + +typedef std::vector< AnimBoneDesc > AnimBoneList; + + +// --------------------------------------------------------------------------- +/** Represents a base frame descriptor in a MD5Anim file +*/ +struct BaseFrameDesc +{ + aiVector3D vPositionXYZ; + aiVector3D vRotationQuat; +}; + +typedef std::vector< BaseFrameDesc > BaseFrameList; + +// --------------------------------------------------------------------------- +/** Represents a camera animation frame in a MDCamera file +*/ +struct CameraAnimFrameDesc : BaseFrameDesc +{ + float fFOV; +}; + +typedef std::vector< CameraAnimFrameDesc > CameraFrameList; + +// --------------------------------------------------------------------------- +/** Represents a frame descriptor in a MD5Anim file +*/ +struct FrameDesc +{ + //! Index of the frame + unsigned int iIndex; + + //! Animation keyframes - a large blob of data at first + std::vector< float > mValues; +}; + +typedef std::vector< FrameDesc > FrameList; + +// --------------------------------------------------------------------------- +/** Represents a vertex descriptor in a MD5 file +*/ +struct VertexDesc { + VertexDesc() AI_NO_EXCEPT + : mFirstWeight(0) + , mNumWeights(0) { + // empty + } + + //! UV coordinate of the vertex + aiVector2D mUV; + + //! Index of the first weight of the vertex in + //! the vertex weight list + unsigned int mFirstWeight; + + //! Number of weights assigned to this vertex + unsigned int mNumWeights; +}; + +typedef std::vector< VertexDesc > VertexList; + +// --------------------------------------------------------------------------- +/** Represents a vertex weight descriptor in a MD5 file +*/ +struct WeightDesc +{ + //! Index of the bone to which this weight refers + unsigned int mBone; + + //! The weight value + float mWeight; + + //! The offset position of this weight + // ! (in the coordinate system defined by the parent bone) + aiVector3D vOffsetPosition; +}; + +typedef std::vector< WeightDesc > WeightList; +typedef std::vector< aiFace > FaceList; + +// --------------------------------------------------------------------------- +/** Represents a mesh in a MD5 file +*/ +struct MeshDesc +{ + //! Weights of the mesh + WeightList mWeights; + + //! Vertices of the mesh + VertexList mVertices; + + //! Faces of the mesh + FaceList mFaces; + + //! Name of the shader (=texture) to be assigned to the mesh + aiString mShader; +}; + +typedef std::vector< MeshDesc > MeshList; + +// --------------------------------------------------------------------------- +// Convert a quaternion to its usual representation +inline void ConvertQuaternion (const aiVector3D& in, aiQuaternion& out) { + + out.x = in.x; + out.y = in.y; + out.z = in.z; + + const float t = 1.0f - (in.x*in.x) - (in.y*in.y) - (in.z*in.z); + + if (t < 0.0f) + out.w = 0.0f; + else out.w = std::sqrt (t); + + // Assimp convention. + out.w *= -1.f; +} + +// --------------------------------------------------------------------------- +/** Parses the data sections of a MD5 mesh file +*/ +class MD5MeshParser +{ +public: + + // ------------------------------------------------------------------- + /** Constructs a new MD5MeshParser instance from an existing + * preparsed list of file sections. + * + * @param mSections List of file sections (output of MD5Parser) + */ + explicit MD5MeshParser(SectionList& mSections); + + //! List of all meshes + MeshList mMeshes; + + //! List of all joints + BoneList mJoints; +}; + +// remove this flag if you need to the bounding box data +#define AI_MD5_PARSE_NO_BOUNDS + +// --------------------------------------------------------------------------- +/** Parses the data sections of a MD5 animation file +*/ +class MD5AnimParser +{ +public: + + // ------------------------------------------------------------------- + /** Constructs a new MD5AnimParser instance from an existing + * preparsed list of file sections. + * + * @param mSections List of file sections (output of MD5Parser) + */ + explicit MD5AnimParser(SectionList& mSections); + + + //! Output frame rate + float fFrameRate; + + //! List of animation bones + AnimBoneList mAnimatedBones; + + //! List of base frames + BaseFrameList mBaseFrames; + + //! List of animation frames + FrameList mFrames; + + //! Number of animated components + unsigned int mNumAnimatedComponents; +}; + +// --------------------------------------------------------------------------- +/** Parses the data sections of a MD5 camera animation file +*/ +class MD5CameraParser +{ +public: + + // ------------------------------------------------------------------- + /** Constructs a new MD5CameraParser instance from an existing + * preparsed list of file sections. + * + * @param mSections List of file sections (output of MD5Parser) + */ + explicit MD5CameraParser(SectionList& mSections); + + + //! Output frame rate + float fFrameRate; + + //! List of cuts + std::vector<unsigned int> cuts; + + //! Frames + CameraFrameList frames; +}; + +// --------------------------------------------------------------------------- +/** Parses the block structure of MD5MESH and MD5ANIM files (but does no + * further processing) +*/ +class MD5Parser +{ +public: + + // ------------------------------------------------------------------- + /** Constructs a new MD5Parser instance from an existing buffer. + * + * @param buffer File buffer + * @param fileSize Length of the file in bytes (excluding a terminal 0) + */ + MD5Parser(char* buffer, unsigned int fileSize); + + + // ------------------------------------------------------------------- + /** Report a specific error message and throw an exception + * @param error Error message to be reported + * @param line Index of the line where the error occurred + */ + AI_WONT_RETURN static void ReportError (const char* error, unsigned int line) AI_WONT_RETURN_SUFFIX; + + // ------------------------------------------------------------------- + /** Report a specific warning + * @param warn Warn message to be reported + * @param line Index of the line where the error occurred + */ + static void ReportWarning (const char* warn, unsigned int line); + + + void ReportError (const char* error) { + return ReportError(error, lineNumber); + } + + void ReportWarning (const char* warn) { + return ReportWarning(warn, lineNumber); + } + +public: + + //! List of all sections which have been read + SectionList mSections; + +private: + + // ------------------------------------------------------------------- + /** Parses a file section. The current file pointer must be outside + * of a section. + * @param out Receives the section data + * @return true if the end of the file has been reached + * @throws ImportErrorException if an error occurs + */ + bool ParseSection(Section& out); + + // ------------------------------------------------------------------- + /** Parses the file header + * @throws ImportErrorException if an error occurs + */ + void ParseHeader(); + + + // override these functions to make sure the line counter gets incremented + // ------------------------------------------------------------------- + bool SkipLine( const char* in, const char** out) + { + ++lineNumber; + return Assimp::SkipLine(in,out); + } + // ------------------------------------------------------------------- + bool SkipLine( ) + { + return SkipLine(buffer,(const char**)&buffer); + } + // ------------------------------------------------------------------- + bool SkipSpacesAndLineEnd( const char* in, const char** out) + { + bool bHad = false; + bool running = true; + while (running) { + if( *in == '\r' || *in == '\n') { + // we open files in binary mode, so there could be \r\n sequences ... + if (!bHad) { + bHad = true; + ++lineNumber; + } + } + else if (*in == '\t' || *in == ' ')bHad = false; + else break; + in++; + } + *out = in; + return *in != '\0'; + } + // ------------------------------------------------------------------- + bool SkipSpacesAndLineEnd( ) + { + return SkipSpacesAndLineEnd(buffer,(const char**)&buffer); + } + // ------------------------------------------------------------------- + bool SkipSpaces( ) + { + return Assimp::SkipSpaces((const char**)&buffer); + } + + char* buffer; + unsigned int fileSize; + unsigned int lineNumber; +}; +}} + +#endif // AI_MD5PARSER_H_INCLUDED diff --git a/libs/assimp/code/AssetLib/MDC/MDCFileData.h b/libs/assimp/code/AssetLib/MDC/MDCFileData.h new file mode 100644 index 0000000..36c589e --- /dev/null +++ b/libs/assimp/code/AssetLib/MDC/MDCFileData.h @@ -0,0 +1,208 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines the helper data structures for importing MDC files + +********************************************************************** +File format specification: +http://themdcfile.planetwolfenstein.gamespy.com/MDC_File_Format.pdf +********************************************************************** + +*/ +#ifndef AI_MDCFILEHELPER_H_INC +#define AI_MDCFILEHELPER_H_INC + +#include <assimp/types.h> +#include <assimp/mesh.h> +#include <assimp/anim.h> + +#include <assimp/Compiler/pushpack1.h> +#include <stdint.h> + +namespace Assimp { +namespace MDC { + +// to make it easier for us, we test the magic word against both "endianesses" +#define AI_MDC_MAGIC_NUMBER_BE AI_MAKE_MAGIC("CPDI") +#define AI_MDC_MAGIC_NUMBER_LE AI_MAKE_MAGIC("IDPC") + +// common limitations +#define AI_MDC_VERSION 2 +#define AI_MDC_MAXQPATH 64 +#define AI_MDC_MAX_BONES 128 + +#define AI_MDC_CVERT_BIAS 127.0f +#define AI_MDC_DELTA_SCALING 4.0f +#define AI_MDC_BASE_SCALING (1.0f / 64.0f) + + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MDC file's main header + */ +struct Header { + uint32_t ulIdent ; + uint32_t ulVersion ; + char ucName [ AI_MDC_MAXQPATH ] ; + uint32_t ulFlags ; + uint32_t ulNumFrames ; + uint32_t ulNumTags ; + uint32_t ulNumSurfaces ; + uint32_t ulNumSkins ; + uint32_t ulOffsetBorderFrames ; + uint32_t ulOffsetTagNames ; + uint32_t ulOffsetTagFrames ; + uint32_t ulOffsetSurfaces ; + uint32_t ulOffsetEnd ; +} PACK_STRUCT ; + + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MDC file's surface header + */ +struct Surface { + uint32_t ulIdent ; + char ucName [ AI_MDC_MAXQPATH ] ; + uint32_t ulFlags ; + uint32_t ulNumCompFrames ; + uint32_t ulNumBaseFrames ; + uint32_t ulNumShaders ; + uint32_t ulNumVertices ; + uint32_t ulNumTriangles ; + uint32_t ulOffsetTriangles ; + uint32_t ulOffsetShaders ; + uint32_t ulOffsetTexCoords ; + uint32_t ulOffsetBaseVerts ; + uint32_t ulOffsetCompVerts ; + uint32_t ulOffsetFrameBaseFrames ; + uint32_t ulOffsetFrameCompFrames ; + uint32_t ulOffsetEnd; + Surface() AI_NO_EXCEPT + : ulIdent() + , ulFlags() + , ulNumCompFrames() + , ulNumBaseFrames() + , ulNumShaders() + , ulNumVertices() + , ulNumTriangles() + , ulOffsetTriangles() + , ulOffsetShaders() + , ulOffsetTexCoords() + , ulOffsetBaseVerts() + , ulOffsetCompVerts() + , ulOffsetFrameBaseFrames() + , ulOffsetFrameCompFrames() + , ulOffsetEnd() { + ucName[AI_MDC_MAXQPATH-1] = '\0'; + } +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MDC frame + */ +struct Frame { + //! bounding box minimum coords + aiVector3D bboxMin ; + + //! bounding box maximum coords + aiVector3D bboxMax ; + + //! local origin of the frame + aiVector3D localOrigin ; + + //! radius of the BB + float radius ; + + //! Name of the frame + char name [ 16 ] ; +} /*PACK_STRUCT*/; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MDC triangle + */ +struct Triangle { + uint32_t aiIndices[3]; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MDC texture coordinate + */ +struct TexturCoord { + float u,v; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MDC base vertex + */ +struct BaseVertex { + int16_t x,y,z; + uint16_t normal; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MDC compressed vertex + */ +struct CompressedVertex { + uint8_t xd,yd,zd,nd; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a MDC shader + */ +struct Shader { + char ucName [ AI_MDC_MAXQPATH ] ; + uint32_t ulPath; +} PACK_STRUCT; + +#include <assimp/Compiler/poppack1.h> + +// --------------------------------------------------------------------------- +/** Build a floating point vertex from the compressed data in MDC files + */ +void BuildVertex(const Frame& frame, + const BaseVertex& bvert, + const CompressedVertex& cvert, + aiVector3D& vXYZOut, + aiVector3D& vNorOut); +} +} + +#endif // !! AI_MDCFILEHELPER_H_INC diff --git a/libs/assimp/code/AssetLib/MDC/MDCLoader.cpp b/libs/assimp/code/AssetLib/MDC/MDCLoader.cpp new file mode 100644 index 0000000..7810800 --- /dev/null +++ b/libs/assimp/code/AssetLib/MDC/MDCLoader.cpp @@ -0,0 +1,460 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the MDC importer class */ + +#ifndef ASSIMP_BUILD_NO_MDC_IMPORTER + +// internal headers +#include "AssetLib/MDC/MDCLoader.h" +#include "AssetLib/MD3/MD3FileData.h" +#include "AssetLib/MDC/MDCNormalTable.h" // shouldn't be included by other units + +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/Importer.hpp> +#include <assimp/StringUtils.h> + +#include <memory> + +using namespace Assimp; +using namespace Assimp::MDC; + +static const aiImporterDesc desc = { + "Return To Castle Wolfenstein Mesh Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "mdc" +}; + +// ------------------------------------------------------------------------------------------------ +void MDC::BuildVertex(const Frame &frame, + const BaseVertex &bvert, + const CompressedVertex &cvert, + aiVector3D &vXYZOut, + aiVector3D &vNorOut) { + // compute the position + const float xd = (cvert.xd - AI_MDC_CVERT_BIAS) * AI_MDC_DELTA_SCALING; + const float yd = (cvert.yd - AI_MDC_CVERT_BIAS) * AI_MDC_DELTA_SCALING; + const float zd = (cvert.zd - AI_MDC_CVERT_BIAS) * AI_MDC_DELTA_SCALING; + vXYZOut.x = frame.localOrigin.x + AI_MDC_BASE_SCALING * (bvert.x + xd); + vXYZOut.y = frame.localOrigin.y + AI_MDC_BASE_SCALING * (bvert.y + yd); + vXYZOut.z = frame.localOrigin.z + AI_MDC_BASE_SCALING * (bvert.z + zd); + + // compute the normal vector .. ehm ... lookup it in the table :-) + vNorOut.x = mdcNormals[cvert.nd][0]; + vNorOut.y = mdcNormals[cvert.nd][1]; + vNorOut.z = mdcNormals[cvert.nd][2]; +} + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +MDCImporter::MDCImporter() : + configFrameID(), + pcHeader(), + mBuffer(), + fileSize() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +MDCImporter::~MDCImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool MDCImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const uint32_t tokens[] = { AI_MDC_MAGIC_NUMBER_LE }; + return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc *MDCImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Validate the header of the given MDC file +void MDCImporter::ValidateHeader() { + AI_SWAP4(this->pcHeader->ulVersion); + AI_SWAP4(this->pcHeader->ulFlags); + AI_SWAP4(this->pcHeader->ulNumFrames); + AI_SWAP4(this->pcHeader->ulNumTags); + AI_SWAP4(this->pcHeader->ulNumSurfaces); + AI_SWAP4(this->pcHeader->ulNumSkins); + AI_SWAP4(this->pcHeader->ulOffsetBorderFrames); + + if (pcHeader->ulIdent != AI_MDC_MAGIC_NUMBER_BE && + pcHeader->ulIdent != AI_MDC_MAGIC_NUMBER_LE) { + throw DeadlyImportError("Invalid MDC magic word: expected IDPC, found ", + ai_str_toprintable((char *)&pcHeader->ulIdent, 4)); + } + + if (pcHeader->ulVersion != AI_MDC_VERSION) { + ASSIMP_LOG_WARN("Unsupported MDC file version (2 (AI_MDC_VERSION) was expected)"); + } + + if (pcHeader->ulOffsetBorderFrames + pcHeader->ulNumFrames * sizeof(MDC::Frame) > this->fileSize || + pcHeader->ulOffsetSurfaces + pcHeader->ulNumSurfaces * sizeof(MDC::Surface) > this->fileSize) { + throw DeadlyImportError("Some of the offset values in the MDC header are invalid " + "and point to something behind the file."); + } + + if (this->configFrameID >= this->pcHeader->ulNumFrames) { + throw DeadlyImportError("The requested frame is not available"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Validate the header of a given MDC file surface +void MDCImporter::ValidateSurfaceHeader(BE_NCONST MDC::Surface *pcSurf) { + AI_SWAP4(pcSurf->ulFlags); + AI_SWAP4(pcSurf->ulNumCompFrames); + AI_SWAP4(pcSurf->ulNumBaseFrames); + AI_SWAP4(pcSurf->ulNumShaders); + AI_SWAP4(pcSurf->ulNumVertices); + AI_SWAP4(pcSurf->ulNumTriangles); + AI_SWAP4(pcSurf->ulOffsetTriangles); + AI_SWAP4(pcSurf->ulOffsetTexCoords); + AI_SWAP4(pcSurf->ulOffsetBaseVerts); + AI_SWAP4(pcSurf->ulOffsetCompVerts); + AI_SWAP4(pcSurf->ulOffsetFrameBaseFrames); + AI_SWAP4(pcSurf->ulOffsetFrameCompFrames); + AI_SWAP4(pcSurf->ulOffsetEnd); + + const unsigned int iMax = this->fileSize - (unsigned int)((int8_t *)pcSurf - (int8_t *)pcHeader); + + if (pcSurf->ulOffsetBaseVerts + pcSurf->ulNumVertices * sizeof(MDC::BaseVertex) > iMax || + (pcSurf->ulNumCompFrames && pcSurf->ulOffsetCompVerts + pcSurf->ulNumVertices * sizeof(MDC::CompressedVertex) > iMax) || + pcSurf->ulOffsetTriangles + pcSurf->ulNumTriangles * sizeof(MDC::Triangle) > iMax || + pcSurf->ulOffsetTexCoords + pcSurf->ulNumVertices * sizeof(MDC::TexturCoord) > iMax || + pcSurf->ulOffsetShaders + pcSurf->ulNumShaders * sizeof(MDC::Shader) > iMax || + pcSurf->ulOffsetFrameBaseFrames + pcSurf->ulNumBaseFrames * 2 > iMax || + (pcSurf->ulNumCompFrames && pcSurf->ulOffsetFrameCompFrames + pcSurf->ulNumCompFrames * 2 > iMax)) { + throw DeadlyImportError("Some of the offset values in the MDC surface header " + "are invalid and point somewhere behind the file."); + } +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties +void MDCImporter::SetupProperties(const Importer *pImp) { + // The AI_CONFIG_IMPORT_MDC_KEYFRAME option overrides the + // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option. + if (static_cast<unsigned int>(-1) == (configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MDC_KEYFRAME, -1))) { + configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME, 0); + } +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void MDCImporter::InternReadFile( + const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile)); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open MDC file ", pFile, "."); + } + + // check whether the mdc file is large enough to contain the file header + fileSize = static_cast<unsigned int>(file->FileSize()); + if (fileSize < sizeof(MDC::Header)) { + throw DeadlyImportError("MDC File is too small."); + } + + std::vector<unsigned char> mBuffer2(fileSize); + file->Read(&mBuffer2[0], 1, fileSize); + mBuffer = &mBuffer2[0]; + + // validate the file header + this->pcHeader = (BE_NCONST MDC::Header *)this->mBuffer; + this->ValidateHeader(); + + std::vector<std::string> aszShaders; + + // get a pointer to the frame we want to read + BE_NCONST MDC::Frame *pcFrame = (BE_NCONST MDC::Frame *)(this->mBuffer + + this->pcHeader->ulOffsetBorderFrames); + + // no need to swap the other members, we won't need them + pcFrame += configFrameID; + AI_SWAP4(pcFrame->localOrigin[0]); + AI_SWAP4(pcFrame->localOrigin[1]); + AI_SWAP4(pcFrame->localOrigin[2]); + + // get the number of valid surfaces + BE_NCONST MDC::Surface *pcSurface, *pcSurface2; + pcSurface = pcSurface2 = reinterpret_cast<BE_NCONST MDC::Surface *>(mBuffer + pcHeader->ulOffsetSurfaces); + unsigned int iNumShaders = 0; + for (unsigned int i = 0; i < pcHeader->ulNumSurfaces; ++i) { + // validate the surface header + this->ValidateSurfaceHeader(pcSurface2); + + if (pcSurface2->ulNumVertices && pcSurface2->ulNumTriangles) { + ++pScene->mNumMeshes; + } + iNumShaders += pcSurface2->ulNumShaders; + pcSurface2 = reinterpret_cast<BE_NCONST MDC::Surface *>((BE_NCONST int8_t *)pcSurface2 + pcSurface2->ulOffsetEnd); + } + aszShaders.reserve(iNumShaders); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + + // necessary that we don't crash if an exception occurs + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + pScene->mMeshes[i] = nullptr; + } + + // now read all surfaces + unsigned int iDefaultMatIndex = UINT_MAX; + for (unsigned int i = 0, iNum = 0; i < pcHeader->ulNumSurfaces; ++i) { + if (!pcSurface->ulNumVertices || !pcSurface->ulNumTriangles) continue; + aiMesh *pcMesh = pScene->mMeshes[iNum++] = new aiMesh(); + + pcMesh->mNumFaces = pcSurface->ulNumTriangles; + pcMesh->mNumVertices = pcMesh->mNumFaces * 3; + + // store the name of the surface for use as node name. + pcMesh->mName.Set(std::string(pcSurface->ucName, strnlen(pcSurface->ucName, AI_MDC_MAXQPATH - 1))); + + // go to the first shader in the file. ignore the others. + if (pcSurface->ulNumShaders) { + const MDC::Shader *pcShader = (const MDC::Shader *)((int8_t *)pcSurface + pcSurface->ulOffsetShaders); + pcMesh->mMaterialIndex = (unsigned int)aszShaders.size(); + + // create a new shader + aszShaders.push_back(std::string(pcShader->ucName, + ::strnlen(pcShader->ucName, sizeof(pcShader->ucName)))); + } + // need to create a default material + else if (UINT_MAX == iDefaultMatIndex) { + pcMesh->mMaterialIndex = iDefaultMatIndex = (unsigned int)aszShaders.size(); + aszShaders.push_back(std::string()); + } + // otherwise assign a reference to the default material + else + pcMesh->mMaterialIndex = iDefaultMatIndex; + + // allocate output storage for the mesh + aiVector3D *pcVertCur = pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; + aiVector3D *pcNorCur = pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; + aiVector3D *pcUVCur = pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; + aiFace *pcFaceCur = pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; + + // create all vertices/faces + BE_NCONST MDC::Triangle *pcTriangle = (BE_NCONST MDC::Triangle *)((int8_t *)pcSurface + pcSurface->ulOffsetTriangles); + + BE_NCONST MDC::TexturCoord *const pcUVs = (BE_NCONST MDC::TexturCoord *)((int8_t *)pcSurface + pcSurface->ulOffsetTexCoords); + + // get a pointer to the uncompressed vertices + int16_t iOfs = *((int16_t *)((int8_t *)pcSurface + + pcSurface->ulOffsetFrameBaseFrames) + + this->configFrameID); + + AI_SWAP2(iOfs); + + BE_NCONST MDC::BaseVertex *const pcVerts = (BE_NCONST MDC::BaseVertex *)((int8_t *)pcSurface + pcSurface->ulOffsetBaseVerts) + + ((int)iOfs * pcSurface->ulNumVertices * 4); + + // do the main swapping stuff ... +#if (defined AI_BUILD_BIG_ENDIAN) + + // swap all triangles + for (unsigned int i = 0; i < pcSurface->ulNumTriangles; ++i) { + AI_SWAP4(pcTriangle[i].aiIndices[0]); + AI_SWAP4(pcTriangle[i].aiIndices[1]); + AI_SWAP4(pcTriangle[i].aiIndices[2]); + } + + // swap all vertices + for (unsigned int i = 0; i < pcSurface->ulNumVertices * pcSurface->ulNumBaseFrames; ++i) { + AI_SWAP2(pcVerts->normal); + AI_SWAP2(pcVerts->x); + AI_SWAP2(pcVerts->y); + AI_SWAP2(pcVerts->z); + } + + // swap all texture coordinates + for (unsigned int i = 0; i < pcSurface->ulNumVertices; ++i) { + AI_SWAP4(pcUVs->u); + AI_SWAP4(pcUVs->v); + } + +#endif + + const MDC::CompressedVertex *pcCVerts = nullptr; + int16_t *mdcCompVert = nullptr; + + // access compressed frames for large frame numbers, but never for the first + if (this->configFrameID && pcSurface->ulNumCompFrames > 0) { + mdcCompVert = (int16_t *)((int8_t *)pcSurface + pcSurface->ulOffsetFrameCompFrames) + this->configFrameID; + AI_SWAP2P(mdcCompVert); + if (*mdcCompVert >= 0) { + pcCVerts = (const MDC::CompressedVertex *)((int8_t *)pcSurface + + pcSurface->ulOffsetCompVerts) + + *mdcCompVert * pcSurface->ulNumVertices; + } else + mdcCompVert = nullptr; + } + + // copy all faces + for (unsigned int iFace = 0; iFace < pcSurface->ulNumTriangles; ++iFace, + ++pcTriangle, ++pcFaceCur) { + const unsigned int iOutIndex = iFace * 3; + pcFaceCur->mNumIndices = 3; + pcFaceCur->mIndices = new unsigned int[3]; + + for (unsigned int iIndex = 0; iIndex < 3; ++iIndex, + ++pcVertCur, ++pcUVCur, ++pcNorCur) { + uint32_t quak = pcTriangle->aiIndices[iIndex]; + if (quak >= pcSurface->ulNumVertices) { + ASSIMP_LOG_ERROR("MDC vertex index is out of range"); + quak = pcSurface->ulNumVertices - 1; + } + + // compressed vertices? + if (mdcCompVert) { + MDC::BuildVertex(*pcFrame, pcVerts[quak], pcCVerts[quak], + *pcVertCur, *pcNorCur); + } else { + // copy position + pcVertCur->x = pcVerts[quak].x * AI_MDC_BASE_SCALING; + pcVertCur->y = pcVerts[quak].y * AI_MDC_BASE_SCALING; + pcVertCur->z = pcVerts[quak].z * AI_MDC_BASE_SCALING; + + // copy normals + MD3::LatLngNormalToVec3(pcVerts[quak].normal, &pcNorCur->x); + + // copy texture coordinates + pcUVCur->x = pcUVs[quak].u; + pcUVCur->y = ai_real(1.0) - pcUVs[quak].v; // DX to OGL + } + pcVertCur->x += pcFrame->localOrigin[0]; + pcVertCur->y += pcFrame->localOrigin[1]; + pcVertCur->z += pcFrame->localOrigin[2]; + } + + // swap the face order - DX to OGL + pcFaceCur->mIndices[0] = iOutIndex + 2; + pcFaceCur->mIndices[1] = iOutIndex + 1; + pcFaceCur->mIndices[2] = iOutIndex + 0; + } + + pcSurface = reinterpret_cast<BE_NCONST MDC::Surface *>((BE_NCONST int8_t *)pcSurface + pcSurface->ulOffsetEnd); + } + + // create a flat node graph with a root node and one child for each surface + if (!pScene->mNumMeshes) + throw DeadlyImportError("Invalid MDC file: File contains no valid mesh"); + else if (1 == pScene->mNumMeshes) { + pScene->mRootNode = new aiNode(); + if (nullptr != pScene->mMeshes[0]) { + pScene->mRootNode->mName = pScene->mMeshes[0]->mName; + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mRootNode->mMeshes[0] = 0; + } + } else { + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mNumChildren = pScene->mNumMeshes; + pScene->mRootNode->mChildren = new aiNode *[pScene->mNumMeshes]; + pScene->mRootNode->mName.Set("<root>"); + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + aiNode *pcNode = pScene->mRootNode->mChildren[i] = new aiNode(); + pcNode->mParent = pScene->mRootNode; + pcNode->mName = pScene->mMeshes[i]->mName; + pcNode->mNumMeshes = 1; + pcNode->mMeshes = new unsigned int[1]; + pcNode->mMeshes[0] = i; + } + } + + // create materials + pScene->mNumMaterials = (unsigned int)aszShaders.size(); + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) { + aiMaterial *pcMat = new aiMaterial(); + pScene->mMaterials[i] = pcMat; + + const std::string &name = aszShaders[i]; + + int iMode = (int)aiShadingMode_Gouraud; + pcMat->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + // add a small ambient color value - RtCW seems to have one + aiColor3D clr; + clr.b = clr.g = clr.r = 0.05f; + pcMat->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_AMBIENT); + + if (name.length()) + clr.b = clr.g = clr.r = 1.0f; + else + clr.b = clr.g = clr.r = 0.6f; + + pcMat->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); + pcMat->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_SPECULAR); + + if (name.length()) { + aiString path; + path.Set(name); + pcMat->AddProperty(&path, AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + } + + // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system + pScene->mRootNode->mTransformation = aiMatrix4x4( + 1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f); +} + +#endif // !! ASSIMP_BUILD_NO_MDC_IMPORTER diff --git a/libs/assimp/code/AssetLib/MDC/MDCLoader.h b/libs/assimp/code/AssetLib/MDC/MDCLoader.h new file mode 100644 index 0000000..6e67cd1 --- /dev/null +++ b/libs/assimp/code/AssetLib/MDC/MDCLoader.h @@ -0,0 +1,120 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MDCLoader.h + * @brief Definition of the MDC importer class. + */ +#ifndef AI_MDCLOADER_H_INCLUDED +#define AI_MDCLOADER_H_INCLUDED + +#include <assimp/types.h> + +#include "MDCFileData.h" +#include <assimp/BaseImporter.h> +#include <assimp/ByteSwapper.h> + +namespace Assimp { + +using namespace MDC; + +// --------------------------------------------------------------------------- +/** Importer class to load the RtCW MDC file format +*/ +class MDCImporter : public BaseImporter { +public: + MDCImporter(); + ~MDCImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer *pImp) override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + + // ------------------------------------------------------------------- + /** Validate the header of the file + */ + void ValidateHeader(); + + // ------------------------------------------------------------------- + /** Validate the header of a MDC surface + */ + void ValidateSurfaceHeader(BE_NCONST MDC::Surface *pcSurf); + +protected: + /** Configuration option: frame to be loaded */ + unsigned int configFrameID; + + /** Header of the MDC file */ + BE_NCONST MDC::Header *pcHeader; + + /** Buffer to hold the loaded file */ + unsigned char *mBuffer; + + /** size of the file, in bytes */ + unsigned int fileSize; +}; + +} // end of namespace Assimp + +#endif // AI_3DSIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/MDC/MDCNormalTable.h b/libs/assimp/code/AssetLib/MDC/MDCNormalTable.h new file mode 100644 index 0000000..c96eba7 --- /dev/null +++ b/libs/assimp/code/AssetLib/MDC/MDCNormalTable.h @@ -0,0 +1,299 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use 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 names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +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. + +----------------------------------------------------------------------------- */ + +#if (!defined MDC_NORMAL_TABLE_INCLUDED) +#define MDC_NORMAL_TABLE_INCLUDED + +/* mdc decoding normal table */ +const float mdcNormals[ 256 ][ 3 ] = +{ + { 1.000000f, 0.000000f, 0.000000f }, + { 0.980785f, 0.195090f, 0.000000f }, + { 0.923880f, 0.382683f, 0.000000f }, + { 0.831470f, 0.555570f, 0.000000f }, + { 0.707107f, 0.707107f, 0.000000f }, + { 0.555570f, 0.831470f, 0.000000f }, + { 0.382683f, 0.923880f, 0.000000f }, + { 0.195090f, 0.980785f, 0.000000f }, + { -0.000000f, 1.000000f, 0.000000f }, + { -0.195090f, 0.980785f, 0.000000f }, + { -0.382683f, 0.923880f, 0.000000f }, + { -0.555570f, 0.831470f, 0.000000f }, + { -0.707107f, 0.707107f, 0.000000f }, + { -0.831470f, 0.555570f, 0.000000f }, + { -0.923880f, 0.382683f, 0.000000f }, + { -0.980785f, 0.195090f, 0.000000f }, + { -1.000000f, -0.000000f, 0.000000f }, + { -0.980785f, -0.195090f, 0.000000f }, + { -0.923880f, -0.382683f, 0.000000f }, + { -0.831470f, -0.555570f, 0.000000f }, + { -0.707107f, -0.707107f, 0.000000f }, + { -0.555570f, -0.831469f, 0.000000f }, + { -0.382684f, -0.923880f, 0.000000f }, + { -0.195090f, -0.980785f, 0.000000f }, + { 0.000000f, -1.000000f, 0.000000f }, + { 0.195090f, -0.980785f, 0.000000f }, + { 0.382684f, -0.923879f, 0.000000f }, + { 0.555570f, -0.831470f, 0.000000f }, + { 0.707107f, -0.707107f, 0.000000f }, + { 0.831470f, -0.555570f, 0.000000f }, + { 0.923880f, -0.382683f, 0.000000f }, + { 0.980785f, -0.195090f, 0.000000f }, + { 0.980785f, 0.000000f, -0.195090f }, + { 0.956195f, 0.218245f, -0.195090f }, + { 0.883657f, 0.425547f, -0.195090f }, + { 0.766809f, 0.611510f, -0.195090f }, + { 0.611510f, 0.766809f, -0.195090f }, + { 0.425547f, 0.883657f, -0.195090f }, + { 0.218245f, 0.956195f, -0.195090f }, + { -0.000000f, 0.980785f, -0.195090f }, + { -0.218245f, 0.956195f, -0.195090f }, + { -0.425547f, 0.883657f, -0.195090f }, + { -0.611510f, 0.766809f, -0.195090f }, + { -0.766809f, 0.611510f, -0.195090f }, + { -0.883657f, 0.425547f, -0.195090f }, + { -0.956195f, 0.218245f, -0.195090f }, + { -0.980785f, -0.000000f, -0.195090f }, + { -0.956195f, -0.218245f, -0.195090f }, + { -0.883657f, -0.425547f, -0.195090f }, + { -0.766809f, -0.611510f, -0.195090f }, + { -0.611510f, -0.766809f, -0.195090f }, + { -0.425547f, -0.883657f, -0.195090f }, + { -0.218245f, -0.956195f, -0.195090f }, + { 0.000000f, -0.980785f, -0.195090f }, + { 0.218245f, -0.956195f, -0.195090f }, + { 0.425547f, -0.883657f, -0.195090f }, + { 0.611510f, -0.766809f, -0.195090f }, + { 0.766809f, -0.611510f, -0.195090f }, + { 0.883657f, -0.425547f, -0.195090f }, + { 0.956195f, -0.218245f, -0.195090f }, + { 0.923880f, 0.000000f, -0.382683f }, + { 0.892399f, 0.239118f, -0.382683f }, + { 0.800103f, 0.461940f, -0.382683f }, + { 0.653281f, 0.653281f, -0.382683f }, + { 0.461940f, 0.800103f, -0.382683f }, + { 0.239118f, 0.892399f, -0.382683f }, + { -0.000000f, 0.923880f, -0.382683f }, + { -0.239118f, 0.892399f, -0.382683f }, + { -0.461940f, 0.800103f, -0.382683f }, + { -0.653281f, 0.653281f, -0.382683f }, + { -0.800103f, 0.461940f, -0.382683f }, + { -0.892399f, 0.239118f, -0.382683f }, + { -0.923880f, -0.000000f, -0.382683f }, + { -0.892399f, -0.239118f, -0.382683f }, + { -0.800103f, -0.461940f, -0.382683f }, + { -0.653282f, -0.653281f, -0.382683f }, + { -0.461940f, -0.800103f, -0.382683f }, + { -0.239118f, -0.892399f, -0.382683f }, + { 0.000000f, -0.923880f, -0.382683f }, + { 0.239118f, -0.892399f, -0.382683f }, + { 0.461940f, -0.800103f, -0.382683f }, + { 0.653281f, -0.653282f, -0.382683f }, + { 0.800103f, -0.461940f, -0.382683f }, + { 0.892399f, -0.239117f, -0.382683f }, + { 0.831470f, 0.000000f, -0.555570f }, + { 0.790775f, 0.256938f, -0.555570f }, + { 0.672673f, 0.488726f, -0.555570f }, + { 0.488726f, 0.672673f, -0.555570f }, + { 0.256938f, 0.790775f, -0.555570f }, + { -0.000000f, 0.831470f, -0.555570f }, + { -0.256938f, 0.790775f, -0.555570f }, + { -0.488726f, 0.672673f, -0.555570f }, + { -0.672673f, 0.488726f, -0.555570f }, + { -0.790775f, 0.256938f, -0.555570f }, + { -0.831470f, -0.000000f, -0.555570f }, + { -0.790775f, -0.256938f, -0.555570f }, + { -0.672673f, -0.488726f, -0.555570f }, + { -0.488725f, -0.672673f, -0.555570f }, + { -0.256938f, -0.790775f, -0.555570f }, + { 0.000000f, -0.831470f, -0.555570f }, + { 0.256938f, -0.790775f, -0.555570f }, + { 0.488725f, -0.672673f, -0.555570f }, + { 0.672673f, -0.488726f, -0.555570f }, + { 0.790775f, -0.256938f, -0.555570f }, + { 0.707107f, 0.000000f, -0.707107f }, + { 0.653281f, 0.270598f, -0.707107f }, + { 0.500000f, 0.500000f, -0.707107f }, + { 0.270598f, 0.653281f, -0.707107f }, + { -0.000000f, 0.707107f, -0.707107f }, + { -0.270598f, 0.653282f, -0.707107f }, + { -0.500000f, 0.500000f, -0.707107f }, + { -0.653281f, 0.270598f, -0.707107f }, + { -0.707107f, -0.000000f, -0.707107f }, + { -0.653281f, -0.270598f, -0.707107f }, + { -0.500000f, -0.500000f, -0.707107f }, + { -0.270598f, -0.653281f, -0.707107f }, + { 0.000000f, -0.707107f, -0.707107f }, + { 0.270598f, -0.653281f, -0.707107f }, + { 0.500000f, -0.500000f, -0.707107f }, + { 0.653282f, -0.270598f, -0.707107f }, + { 0.555570f, 0.000000f, -0.831470f }, + { 0.481138f, 0.277785f, -0.831470f }, + { 0.277785f, 0.481138f, -0.831470f }, + { -0.000000f, 0.555570f, -0.831470f }, + { -0.277785f, 0.481138f, -0.831470f }, + { -0.481138f, 0.277785f, -0.831470f }, + { -0.555570f, -0.000000f, -0.831470f }, + { -0.481138f, -0.277785f, -0.831470f }, + { -0.277785f, -0.481138f, -0.831470f }, + { 0.000000f, -0.555570f, -0.831470f }, + { 0.277785f, -0.481138f, -0.831470f }, + { 0.481138f, -0.277785f, -0.831470f }, + { 0.382683f, 0.000000f, -0.923880f }, + { 0.270598f, 0.270598f, -0.923880f }, + { -0.000000f, 0.382683f, -0.923880f }, + { -0.270598f, 0.270598f, -0.923880f }, + { -0.382683f, -0.000000f, -0.923880f }, + { -0.270598f, -0.270598f, -0.923880f }, + { 0.000000f, -0.382683f, -0.923880f }, + { 0.270598f, -0.270598f, -0.923880f }, + { 0.195090f, 0.000000f, -0.980785f }, + { -0.000000f, 0.195090f, -0.980785f }, + { -0.195090f, -0.000000f, -0.980785f }, + { 0.000000f, -0.195090f, -0.980785f }, + { 0.980785f, 0.000000f, 0.195090f }, + { 0.956195f, 0.218245f, 0.195090f }, + { 0.883657f, 0.425547f, 0.195090f }, + { 0.766809f, 0.611510f, 0.195090f }, + { 0.611510f, 0.766809f, 0.195090f }, + { 0.425547f, 0.883657f, 0.195090f }, + { 0.218245f, 0.956195f, 0.195090f }, + { -0.000000f, 0.980785f, 0.195090f }, + { -0.218245f, 0.956195f, 0.195090f }, + { -0.425547f, 0.883657f, 0.195090f }, + { -0.611510f, 0.766809f, 0.195090f }, + { -0.766809f, 0.611510f, 0.195090f }, + { -0.883657f, 0.425547f, 0.195090f }, + { -0.956195f, 0.218245f, 0.195090f }, + { -0.980785f, -0.000000f, 0.195090f }, + { -0.956195f, -0.218245f, 0.195090f }, + { -0.883657f, -0.425547f, 0.195090f }, + { -0.766809f, -0.611510f, 0.195090f }, + { -0.611510f, -0.766809f, 0.195090f }, + { -0.425547f, -0.883657f, 0.195090f }, + { -0.218245f, -0.956195f, 0.195090f }, + { 0.000000f, -0.980785f, 0.195090f }, + { 0.218245f, -0.956195f, 0.195090f }, + { 0.425547f, -0.883657f, 0.195090f }, + { 0.611510f, -0.766809f, 0.195090f }, + { 0.766809f, -0.611510f, 0.195090f }, + { 0.883657f, -0.425547f, 0.195090f }, + { 0.956195f, -0.218245f, 0.195090f }, + { 0.923880f, 0.000000f, 0.382683f }, + { 0.892399f, 0.239118f, 0.382683f }, + { 0.800103f, 0.461940f, 0.382683f }, + { 0.653281f, 0.653281f, 0.382683f }, + { 0.461940f, 0.800103f, 0.382683f }, + { 0.239118f, 0.892399f, 0.382683f }, + { -0.000000f, 0.923880f, 0.382683f }, + { -0.239118f, 0.892399f, 0.382683f }, + { -0.461940f, 0.800103f, 0.382683f }, + { -0.653281f, 0.653281f, 0.382683f }, + { -0.800103f, 0.461940f, 0.382683f }, + { -0.892399f, 0.239118f, 0.382683f }, + { -0.923880f, -0.000000f, 0.382683f }, + { -0.892399f, -0.239118f, 0.382683f }, + { -0.800103f, -0.461940f, 0.382683f }, + { -0.653282f, -0.653281f, 0.382683f }, + { -0.461940f, -0.800103f, 0.382683f }, + { -0.239118f, -0.892399f, 0.382683f }, + { 0.000000f, -0.923880f, 0.382683f }, + { 0.239118f, -0.892399f, 0.382683f }, + { 0.461940f, -0.800103f, 0.382683f }, + { 0.653281f, -0.653282f, 0.382683f }, + { 0.800103f, -0.461940f, 0.382683f }, + { 0.892399f, -0.239117f, 0.382683f }, + { 0.831470f, 0.000000f, 0.555570f }, + { 0.790775f, 0.256938f, 0.555570f }, + { 0.672673f, 0.488726f, 0.555570f }, + { 0.488726f, 0.672673f, 0.555570f }, + { 0.256938f, 0.790775f, 0.555570f }, + { -0.000000f, 0.831470f, 0.555570f }, + { -0.256938f, 0.790775f, 0.555570f }, + { -0.488726f, 0.672673f, 0.555570f }, + { -0.672673f, 0.488726f, 0.555570f }, + { -0.790775f, 0.256938f, 0.555570f }, + { -0.831470f, -0.000000f, 0.555570f }, + { -0.790775f, -0.256938f, 0.555570f }, + { -0.672673f, -0.488726f, 0.555570f }, + { -0.488725f, -0.672673f, 0.555570f }, + { -0.256938f, -0.790775f, 0.555570f }, + { 0.000000f, -0.831470f, 0.555570f }, + { 0.256938f, -0.790775f, 0.555570f }, + { 0.488725f, -0.672673f, 0.555570f }, + { 0.672673f, -0.488726f, 0.555570f }, + { 0.790775f, -0.256938f, 0.555570f }, + { 0.707107f, 0.000000f, 0.707107f }, + { 0.653281f, 0.270598f, 0.707107f }, + { 0.500000f, 0.500000f, 0.707107f }, + { 0.270598f, 0.653281f, 0.707107f }, + { -0.000000f, 0.707107f, 0.707107f }, + { -0.270598f, 0.653282f, 0.707107f }, + { -0.500000f, 0.500000f, 0.707107f }, + { -0.653281f, 0.270598f, 0.707107f }, + { -0.707107f, -0.000000f, 0.707107f }, + { -0.653281f, -0.270598f, 0.707107f }, + { -0.500000f, -0.500000f, 0.707107f }, + { -0.270598f, -0.653281f, 0.707107f }, + { 0.000000f, -0.707107f, 0.707107f }, + { 0.270598f, -0.653281f, 0.707107f }, + { 0.500000f, -0.500000f, 0.707107f }, + { 0.653282f, -0.270598f, 0.707107f }, + { 0.555570f, 0.000000f, 0.831470f }, + { 0.481138f, 0.277785f, 0.831470f }, + { 0.277785f, 0.481138f, 0.831470f }, + { -0.000000f, 0.555570f, 0.831470f }, + { -0.277785f, 0.481138f, 0.831470f }, + { -0.481138f, 0.277785f, 0.831470f }, + { -0.555570f, -0.000000f, 0.831470f }, + { -0.481138f, -0.277785f, 0.831470f }, + { -0.277785f, -0.481138f, 0.831470f }, + { 0.000000f, -0.555570f, 0.831470f }, + { 0.277785f, -0.481138f, 0.831470f }, + { 0.481138f, -0.277785f, 0.831470f }, + { 0.382683f, 0.000000f, 0.923880f }, + { 0.270598f, 0.270598f, 0.923880f }, + { -0.000000f, 0.382683f, 0.923880f }, + { -0.270598f, 0.270598f, 0.923880f }, + { -0.382683f, -0.000000f, 0.923880f }, + { -0.270598f, -0.270598f, 0.923880f }, + { 0.000000f, -0.382683f, 0.923880f }, + { 0.270598f, -0.270598f, 0.923880f }, + { 0.195090f, 0.000000f, 0.980785f }, + { -0.000000f, 0.195090f, 0.980785f }, + { -0.195090f, -0.000000f, 0.980785f }, + { 0.000000f, -0.195090f, 0.980785f } +}; + +#endif // !! MDC_NORMAL_TABLE_INCLUDED diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HL1FileData.h b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1FileData.h new file mode 100644 index 0000000..28b1b28 --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1FileData.h @@ -0,0 +1,600 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 HL1FileData.h + * @brief Definition of in-memory structures for the + * Half-Life 1 MDL file format. + */ + +#ifndef AI_HL1FILEDATA_INCLUDED +#define AI_HL1FILEDATA_INCLUDED + +#include "HalfLifeMDLBaseHeader.h" + +#include <assimp/Compiler/pushpack1.h> +#include <assimp/types.h> + +namespace Assimp { +namespace MDL { +namespace HalfLife { + +using vec3_t = float[3]; + +/** \struct Header_HL1 + * \brief Data structure for the HL1 MDL file header. + */ +struct Header_HL1 : HalfLifeMDLBaseHeader { + //! The model name. + char name[64]; + + //! The total file size in bytes. + int32_t length; + + //! Ideal eye position. + vec3_t eyeposition; + + //! Ideal movement hull size. + vec3_t min; + vec3_t max; + + //! Clipping bounding box. + vec3_t bbmin; + vec3_t bbmax; + + //! Was "flags". + int32_t unused; + + //! The number of bones. + int32_t numbones; + + //! Offset to the first bone chunk. + int32_t boneindex; + + //! The number of bone controllers. + int32_t numbonecontrollers; + + //! Offset to the first bone controller chunk. + int32_t bonecontrollerindex; + + //! The number of hitboxes. + int32_t numhitboxes; + + //! Offset to the first hitbox chunk. + int32_t hitboxindex; + + //! The number of sequences. + int32_t numseq; + + //! Offset to the first sequence description chunk. + int32_t seqindex; + + //! The number of sequence groups. + int32_t numseqgroups; + + //! Offset to the first sequence group chunk. + int32_t seqgroupindex; + + //! The number of textures. + int32_t numtextures; + + //! Offset to the first texture chunk. + int32_t textureindex; + + //! Offset to the first texture's image data. + int32_t texturedataindex; + + //! The number of replaceable textures. + int32_t numskinref; + + //! The number of skin families. + int32_t numskinfamilies; + + //! Offset to the first replaceable texture. + int32_t skinindex; + + //! The number of bodyparts. + int32_t numbodyparts; + + //! Offset the the first bodypart. + int32_t bodypartindex; + + //! The number of attachments. + int32_t numattachments; + + //! Offset the the first attachment chunk. + int32_t attachmentindex; + + //! Was "soundtable". + int32_t unused2; + + //! Was "soundindex". + int32_t unused3; + + //! Was "soundgroups". + int32_t unused4; + + //! Was "soundgroupindex". + int32_t unused5; + + //! The number of nodes in the sequence transition graph. + int32_t numtransitions; + + //! Offset the the first sequence transition. + int32_t transitionindex; +} PACK_STRUCT; + +/** \struct SequenceHeader_HL1 + * \brief Data structure for the file header of a demand loaded + * HL1 MDL sequence group file. + */ +struct SequenceHeader_HL1 : HalfLifeMDLBaseHeader { + //! The sequence group file name. + char name[64]; + + //! The total file size in bytes. + int32_t length; +} PACK_STRUCT; + +/** \struct Bone_HL1 + * \brief Data structure for a bone in HL1 MDL files. + */ +struct Bone_HL1 { + //! The bone name. + char name[32]; + + //! The parent bone index. (-1) If it has no parent. + int32_t parent; + + //! Was "flags". + int32_t unused; + + //! Available bone controller per motion type. + //! (-1) if no controller is available. + int32_t bonecontroller[6]; + + /*! Default position and rotation values where + * scale[0] = position.X + * scale[1] = position.Y + * scale[2] = position.Z + * scale[3] = rotation.X + * scale[4] = rotation.Y + * scale[5] = rotation.Z + */ + float value[6]; + + /*! Compressed scale values where + * scale[0] = position.X scale + * scale[1] = position.Y scale + * scale[2] = position.Z scale + * scale[3] = rotation.X scale + * scale[4] = rotation.Y scale + * scale[5] = rotation.Z scale + */ + float scale[6]; +} PACK_STRUCT; + +/** \struct BoneController_HL1 + * \brief Data structure for a bone controller in HL1 MDL files. + */ +struct BoneController_HL1 { + //! Bone affected by this controller. + int32_t bone; + + //! The motion type. + int32_t type; + + //! The minimum and maximum values. + float start; + float end; + + // Was "rest". + int32_t unused; + + // The bone controller channel. + int32_t index; +} PACK_STRUCT; + +/** \struct Hitbox_HL1 + * \brief Data structure for a hitbox in HL1 MDL files. + */ +struct Hitbox_HL1 { + //! The bone this hitbox follows. + int32_t bone; + + //! The hit group. + int32_t group; + + //! The hitbox minimum and maximum extents. + vec3_t bbmin; + vec3_t bbmax; +} PACK_STRUCT; + +/** \struct SequenceGroup_HL1 + * \brief Data structure for a sequence group in HL1 MDL files. + */ +struct SequenceGroup_HL1 { + //! A textual name for this sequence group. + char label[32]; + + //! The file name. + char name[64]; + + //! Was "cache". + int32_t unused; + + //! Was "data". + int32_t unused2; +} PACK_STRUCT; + +//! The type of blending for a sequence. +enum SequenceBlendMode_HL1 { + NoBlend = 1, + TwoWayBlending = 2, + FourWayBlending = 4, +}; + +/** \struct SequenceDesc_HL1 + * \brief Data structure for a sequence description in HL1 MDL files. + */ +struct SequenceDesc_HL1 { + //! The sequence name. + char label[32]; + + //! Frames per second. + float fps; + + //! looping/non-looping flags. + int32_t flags; + + //! The sequence activity. + int32_t activity; + + //! The sequence activity weight. + int32_t actweight; + + //! The number of animation events. + int32_t numevents; + + //! Offset the the first animation event chunk. + int32_t eventindex; + + //! The number of frames in the sequence. + int32_t numframes; + + //! Was "numpivots". + int32_t unused; + + //! Was "pivotindex". + int32_t unused2; + + //! Linear motion type. + int32_t motiontype; + + //! Linear motion bone. + int32_t motionbone; + + //! Linear motion. + vec3_t linearmovement; + + //! Was "automoveposindex". + int32_t unused3; + + //! Was "automoveangleindex". + int32_t unused4; + + //! The sequence minimum and maximum extents. + vec3_t bbmin; + vec3_t bbmax; + + //! The number of blend animations. + int32_t numblends; + + //! Offset to first the AnimValueOffset_HL1 chunk. + //! This offset is relative to the SequenceHeader_HL1 of the file + //! that contains the animation data. + int32_t animindex; + + //! The motion type of each blend controller. + int32_t blendtype[2]; + + //! The starting value of each blend controller. + float blendstart[2]; + + //! The ending value of each blend controller. + float blendend[2]; + + //! Was "blendparent". + int32_t unused5; + + //! The sequence group. + int32_t seqgroup; + + //! The node at entry in the sequence transition graph. + int32_t entrynode; + + //! The node at exit in the sequence transition graph. + int32_t exitnode; + + //! Transition rules. + int32_t nodeflags; + + //! Was "nextseq" + int32_t unused6; +} PACK_STRUCT; + +/** \struct AnimEvent_HL1 + * \brief Data structure for an animation event in HL1 MDL files. + */ +struct AnimEvent_HL1 { + //! The frame at which this animation event occurs. + int32_t frame; + + //! The script event type. + int32_t event; + + //! was "type" + int32_t unused; + + //! Options. Could be path to sound WAVE files. + char options[64]; +} PACK_STRUCT; + +/** \struct Attachment_HL1 + * \brief Data structure for an attachment in HL1 MDL files. + */ +struct Attachment_HL1 { + //! Was "name". + char unused[32]; + + //! Was "type". + int32_t unused2; + + //! The bone this attachment follows. + int32_t bone; + + //! The attachment origin. + vec3_t org; + + //! Was "vectors" + vec3_t unused3[3]; +} PACK_STRUCT; + +/** \struct AnimValueOffset_HL1 + * \brief Data structure to hold offsets (one per motion type) + * to the first animation frame value for a single bone + * in HL1 MDL files. + */ +struct AnimValueOffset_HL1 { + unsigned short offset[6]; +} PACK_STRUCT; + +/** \struct AnimValue_HL1 + * \brief Data structure for an animation frame in HL1 MDL files. + */ +union AnimValue_HL1 { + struct { + uint8_t valid; + uint8_t total; + } num; + short value; +} PACK_STRUCT; + +/** \struct Bodypart_HL1 + * \brief Data structure for a bodypart in HL1 MDL files. + */ +struct Bodypart_HL1 { + //! The bodypart name. + char name[64]; + + //! The number of available models for this bodypart. + int32_t nummodels; + + //! Used to convert from a global model index + //! to a local bodypart model index. + int32_t base; + + //! The offset to the first model chunk. + int32_t modelindex; +} PACK_STRUCT; + +/** \struct Texture_HL1 + * \brief Data structure for a texture in HL1 MDL files. + */ +struct Texture_HL1 { + //! Texture file name. + char name[64]; + + //! Texture flags. + int32_t flags; + + //! Texture width in pixels. + int32_t width; + + //! Texture height in pixels. + int32_t height; + + //! Offset to the image data. + //! This offset is relative to the texture file header. + int32_t index; +} PACK_STRUCT; + +/** \struct Model_HL1 + * \brief Data structure for a model in HL1 MDL files. + */ +struct Model_HL1 { + //! Model name. + char name[64]; + + //! Was "type". + int32_t unused; + + //! Was "boundingradius". + float unused2; + + //! The number of meshes in the model. + int32_t nummesh; + + //! Offset to the first mesh chunk. + int32_t meshindex; + + //! The number of unique vertices. + int32_t numverts; + + //! Offset to the vertex bone array. + int32_t vertinfoindex; + + //! Offset to the vertex array. + int32_t vertindex; + + //! The number of unique normals. + int32_t numnorms; + + //! Offset to the normal bone array. + int32_t norminfoindex; + + //! Offset to the normal array. + int32_t normindex; + + //! Was "numgroups". + int32_t unused3; + + //! Was "groupindex". + int32_t unused4; +} PACK_STRUCT; + +/** \struct Mesh_HL1 + * \brief Data structure for a mesh in HL1 MDL files. + */ +struct Mesh_HL1 { + //! Can be interpreted as the number of triangles in the mesh. + int32_t numtris; + + //! Offset to the start of the tris sequence. + int32_t triindex; + + //! The skin index. + int32_t skinref; + + //! The number of normals in the mesh. + int32_t numnorms; + + //! Was "normindex". + int32_t unused; +} PACK_STRUCT; + +/** \struct Trivert + * \brief Data structure for a trivert in HL1 MDL files. + */ +struct Trivert { + //! Index into Model_HL1 vertex array. + short vertindex; + + //! Index into Model_HL1 normal array. + short normindex; + + //! Texture coordinates in absolute space (unnormalized). + short s, t; +} PACK_STRUCT; + +#include <assimp/Compiler/poppack1.h> + +#if (!defined AI_MDL_HL1_VERSION) +#define AI_MDL_HL1_VERSION 10 +#endif +#if (!defined AI_MDL_HL1_MAX_TRIANGLES) +#define AI_MDL_HL1_MAX_TRIANGLES 20000 +#endif +#if (!defined AI_MDL_HL1_MAX_VERTICES) +#define AI_MDL_HL1_MAX_VERTICES 2048 +#endif +#if (!defined AI_MDL_HL1_MAX_SEQUENCES) +#define AI_MDL_HL1_MAX_SEQUENCES 2048 +#endif +#if (!defined AI_MDL_HL1_MAX_SEQUENCE_GROUPS) +#define AI_MDL_HL1_MAX_SEQUENCE_GROUPS 32 +#endif +#if (!defined AI_MDL_HL1_MAX_TEXTURES) +#define AI_MDL_HL1_MAX_TEXTURES 100 +#endif +#if (!defined AI_MDL_HL1_MAX_SKIN_FAMILIES) +#define AI_MDL_HL1_MAX_SKIN_FAMILIES 100 +#endif +#if (!defined AI_MDL_HL1_MAX_BONES) +#define AI_MDL_HL1_MAX_BONES 128 +#endif +#if (!defined AI_MDL_HL1_MAX_BODYPARTS) +#define AI_MDL_HL1_MAX_BODYPARTS 32 +#endif +#if (!defined AI_MDL_HL1_MAX_MODELS) +#define AI_MDL_HL1_MAX_MODELS 32 +#endif +#if (!defined AI_MDL_HL1_MAX_MESHES) +#define AI_MDL_HL1_MAX_MESHES 256 +#endif +#if (!defined AI_MDL_HL1_MAX_EVENTS) +#define AI_MDL_HL1_MAX_EVENTS 1024 +#endif +#if (!defined AI_MDL_HL1_MAX_BONE_CONTROLLERS) +#define AI_MDL_HL1_MAX_BONE_CONTROLLERS 8 +#endif +#if (!defined AI_MDL_HL1_MAX_ATTACHMENTS) +#define AI_MDL_HL1_MAX_ATTACHMENTS 512 +#endif + +// lighting options +#if (!defined AI_MDL_HL1_STUDIO_NF_FLATSHADE) +#define AI_MDL_HL1_STUDIO_NF_FLATSHADE 0x0001 +#endif +#if (!defined AI_MDL_HL1_STUDIO_NF_CHROME) +#define AI_MDL_HL1_STUDIO_NF_CHROME 0x0002 +#endif +#if (!defined AI_MDL_HL1_STUDIO_NF_ADDITIVE) +#define AI_MDL_HL1_STUDIO_NF_ADDITIVE 0x0020 +#endif +#if (!defined AI_MDL_HL1_STUDIO_NF_MASKED) +#define AI_MDL_HL1_STUDIO_NF_MASKED 0x0040 +#endif + +} // namespace HalfLife +} // namespace MDL +} // namespace Assimp + +#endif // AI_HL1FILEDATA_INCLUDED diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportDefinitions.h b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportDefinitions.h new file mode 100644 index 0000000..d412aee --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportDefinitions.h @@ -0,0 +1,64 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 HL1ImportDefinitions.h + * @brief HL1 MDL loader specific definitions. + */ + +#ifndef AI_MDL_HL1_IMPORT_DEFINITIONS_INCLUDED +#define AI_MDL_HL1_IMPORT_DEFINITIONS_INCLUDED + +#define AI_MDL_HL1_NODE_ROOT "<MDL_root>" +#define AI_MDL_HL1_NODE_BODYPARTS "<MDL_bodyparts>" +#define AI_MDL_HL1_NODE_BONES "<MDL_bones>" +#define AI_MDL_HL1_NODE_BONE_CONTROLLERS "<MDL_bone_controllers>" +#define AI_MDL_HL1_NODE_SEQUENCE_INFOS "<MDL_sequence_infos>" +#define AI_MDL_HL1_NODE_SEQUENCE_GROUPS "<MDL_sequence_groups>" +#define AI_MDL_HL1_NODE_SEQUENCE_TRANSITION_GRAPH "<MDL_sequence_transition_graph>" +#define AI_MDL_HL1_NODE_ATTACHMENTS "<MDL_attachments>" +#define AI_MDL_HL1_NODE_HITBOXES "<MDL_hitboxes>" +#define AI_MDL_HL1_NODE_GLOBAL_INFO "<MDL_global_info>" +#define AI_MDL_HL1_NODE_ANIMATION_EVENTS "AnimationEvents" +#define AI_MDL_HL1_NODE_BLEND_CONTROLLERS "BlendControllers" + +#define AI_MDL_HL1_MATKEY_CHROME(type, N) "$mat.HL1.chrome", type, N + +#endif // AI_MDL_HL1_IMPORT_DEFINITIONS_INCLUDED diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportSettings.h b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportSettings.h new file mode 100644 index 0000000..340ba2d --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1ImportSettings.h @@ -0,0 +1,85 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 HL1ImportSettings.h + * @brief Half-Life 1 MDL loader configuration settings. + */ + +#ifndef AI_HL1IMPORTSETTINGS_INCLUDED +#define AI_HL1IMPORTSETTINGS_INCLUDED + +#include <string> + +namespace Assimp { +namespace MDL { +namespace HalfLife { + +struct HL1ImportSettings { + HL1ImportSettings() : + read_animations(false), + read_animation_events(false), + read_blend_controllers(false), + read_sequence_groups_info(false), + read_sequence_transitions(false), + read_attachments(false), + read_bone_controllers(false), + read_hitboxes(false), + read_textures(false), + read_misc_global_info(false) { + } + + bool read_animations; + bool read_animation_events; + bool read_blend_controllers; + bool read_sequence_groups_info; + bool read_sequence_transitions; + bool read_attachments; + bool read_bone_controllers; + bool read_hitboxes; + bool read_textures; + bool read_misc_global_info; +}; + +} // namespace HalfLife +} // namespace MDL +} // namespace Assimp + +#endif // AI_HL1IMPORTSETTINGS_INCLUDED diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp new file mode 100644 index 0000000..93d3753 --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp @@ -0,0 +1,1353 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 HL1MDLLoader.cpp + * @brief Implementation for the Half-Life 1 MDL loader. + */ + +#include "HL1MDLLoader.h" +#include "HL1ImportDefinitions.h" +#include "HL1MeshTrivert.h" +#include "UniqueNameGenerator.h" + +#include <assimp/BaseImporter.h> +#include <assimp/StringUtils.h> +#include <assimp/ai_assert.h> +#include <assimp/qnan.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> + +#include <iomanip> +#include <sstream> +#include <map> + +#ifdef MDL_HALFLIFE_LOG_WARN_HEADER +#undef MDL_HALFLIFE_LOG_WARN_HEADER +#endif +#define MDL_HALFLIFE_LOG_HEADER "[Half-Life 1 MDL] " +#include "LogFunctions.h" + +namespace Assimp { +namespace MDL { +namespace HalfLife { + +#ifdef _MSC_VER +# pragma warning(disable : 4706) +#endif // _MSC_VER + +// ------------------------------------------------------------------------------------------------ +HL1MDLLoader::HL1MDLLoader( + aiScene *scene, + IOSystem *io, + const unsigned char *buffer, + const std::string &file_path, + const HL1ImportSettings &import_settings) : + scene_(scene), + io_(io), + buffer_(buffer), + file_path_(file_path), + import_settings_(import_settings), + header_(nullptr), + texture_header_(nullptr), + anim_headers_(nullptr), + texture_buffer_(nullptr), + anim_buffers_(nullptr), + num_sequence_groups_(0), + rootnode_children_(), + unique_name_generator_(), + unique_sequence_names_(), + unique_sequence_groups_names_(), + temp_bones_(), + num_blend_controllers_(0), + total_models_(0) { + load_file(); +} + +// ------------------------------------------------------------------------------------------------ +HL1MDLLoader::~HL1MDLLoader() { + release_resources(); +} + +// ------------------------------------------------------------------------------------------------ +void HL1MDLLoader::release_resources() { + if (buffer_ != texture_buffer_) { + delete[] texture_buffer_; + texture_buffer_ = nullptr; + } + + if (num_sequence_groups_ && anim_buffers_) { + for (int i = 1; i < num_sequence_groups_; ++i) { + if (anim_buffers_[i]) { + delete[] anim_buffers_[i]; + anim_buffers_[i] = nullptr; + } + } + + delete[] anim_buffers_; + anim_buffers_ = nullptr; + } + + if (anim_headers_) { + delete[] anim_headers_; + anim_headers_ = nullptr; + } + + // Root has some children nodes. so let's proceed them + if (!rootnode_children_.empty()) { + // Here, it means that the nodes were not added to the + // scene root node. We still have to delete them. + for (auto it = rootnode_children_.begin(); it != rootnode_children_.end(); ++it) { + if (*it) { + delete *it; + } + } + // Ensure this happens only once. + rootnode_children_.clear(); + } +} + +// ------------------------------------------------------------------------------------------------ +void HL1MDLLoader::load_file() { + try { + header_ = (const Header_HL1 *)buffer_; + validate_header(header_, false); + + // Create the root scene node. + scene_->mRootNode = new aiNode(AI_MDL_HL1_NODE_ROOT); + + load_texture_file(); + + if (import_settings_.read_animations) { + load_sequence_groups_files(); + } + + read_textures(); + read_skins(); + + read_bones(); + read_meshes(); + + if (import_settings_.read_animations) { + read_sequence_groups_info(); + read_animations(); + read_sequence_infos(); + if (import_settings_.read_sequence_transitions) + read_sequence_transitions(); + } + + if (import_settings_.read_attachments) { + read_attachments(); + } + + if (import_settings_.read_hitboxes) { + read_hitboxes(); + } + + if (import_settings_.read_bone_controllers) { + read_bone_controllers(); + } + + read_global_info(); + + if (!header_->numbodyparts) { + // This could be an MDL external texture file. In this case, + // add this flag to allow the scene to be loaded even if it + // has no meshes. + scene_->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } + + // Append children to root node. + if (rootnode_children_.size()) { + scene_->mRootNode->addChildren( + static_cast<unsigned int>(rootnode_children_.size()), + rootnode_children_.data()); + + // Clear the list of nodes so they will not be destroyed + // when resources are released. + rootnode_children_.clear(); + } + + release_resources(); + + } catch (...) { + release_resources(); + throw; + } +} + +// ------------------------------------------------------------------------------------------------ +void HL1MDLLoader::validate_header(const Header_HL1 *header, bool is_texture_header) { + if (is_texture_header) { + // Every single Half-Life model is assumed to have at least one texture. + if (!header->numtextures) { + throw DeadlyImportError(MDL_HALFLIFE_LOG_HEADER "There are no textures in the file"); + } + + if (header->numtextures > AI_MDL_HL1_MAX_TEXTURES) { + log_warning_limit_exceeded<AI_MDL_HL1_MAX_TEXTURES>(header->numtextures, "textures"); + } + + if (header->numskinfamilies > AI_MDL_HL1_MAX_SKIN_FAMILIES) { + log_warning_limit_exceeded<AI_MDL_HL1_MAX_SKIN_FAMILIES>(header->numskinfamilies, "skin families"); + } + + } else { + + if (header->numbodyparts > AI_MDL_HL1_MAX_BODYPARTS) { + log_warning_limit_exceeded<AI_MDL_HL1_MAX_BODYPARTS>(header->numbodyparts, "bodyparts"); + } + + if (header->numbones > AI_MDL_HL1_MAX_BONES) { + log_warning_limit_exceeded<AI_MDL_HL1_MAX_BONES>(header->numbones, "bones"); + } + + if (header->numbonecontrollers > AI_MDL_HL1_MAX_BONE_CONTROLLERS) { + log_warning_limit_exceeded<AI_MDL_HL1_MAX_BONE_CONTROLLERS>(header->numbonecontrollers, "bone controllers"); + } + + if (header->numseq > AI_MDL_HL1_MAX_SEQUENCES) { + log_warning_limit_exceeded<AI_MDL_HL1_MAX_SEQUENCES>(header->numseq, "sequences"); + } + + if (header->numseqgroups > AI_MDL_HL1_MAX_SEQUENCE_GROUPS) { + log_warning_limit_exceeded<AI_MDL_HL1_MAX_SEQUENCE_GROUPS>(header->numseqgroups, "sequence groups"); + } + + if (header->numattachments > AI_MDL_HL1_MAX_ATTACHMENTS) { + log_warning_limit_exceeded<AI_MDL_HL1_MAX_ATTACHMENTS>(header->numattachments, "attachments"); + } + } +} + +// ------------------------------------------------------------------------------------------------ +/* + Load textures. + + There are two ways for textures to be stored in a Half-Life model: + + 1. Directly in the MDL file (filePath) or + 2. In an external MDL file. + + Due to the way StudioMDL works (tool used to compile SMDs into MDLs), + it is assumed that an external texture file follows the naming + convention: <YourModelName>T.mdl. Note the extra (T) at the end of the + model name. + + .e.g For a given model named MyModel.mdl + + The external texture file name would be MyModelT.mdl +*/ +void HL1MDLLoader::load_texture_file() { + if (header_->numtextures == 0) { + // Load an external MDL texture file. + std::string texture_file_path = + DefaultIOSystem::absolutePath(file_path_) + io_->getOsSeparator() + + DefaultIOSystem::completeBaseName(file_path_) + "T." + + BaseImporter::GetExtension(file_path_); + + load_file_into_buffer<Header_HL1>(texture_file_path, texture_buffer_); + } else { + // Model has no external texture file. This means the texture is stored inside the main MDL file. + texture_buffer_ = const_cast<unsigned char *>(buffer_); + } + + texture_header_ = (const Header_HL1 *)texture_buffer_; + + // Validate texture header. + validate_header(texture_header_, true); +} + +// ------------------------------------------------------------------------------------------------ +/* + Load sequence group files if any. + + Due to the way StudioMDL works (tool used to compile SMDs into MDLs), + it is assumed that a sequence group file follows the naming + convention: <YourModelName>0X.mdl. Note the extra (0X) at the end of + the model name, where (X) is the sequence group. + + .e.g For a given model named MyModel.mdl + + Sequence group 1 => MyModel01.mdl + Sequence group 2 => MyModel02.mdl + Sequence group X => MyModel0X.mdl + +*/ +void HL1MDLLoader::load_sequence_groups_files() { + if (header_->numseqgroups <= 1) { + return; + } + + num_sequence_groups_ = header_->numseqgroups; + + anim_buffers_ = new unsigned char *[num_sequence_groups_]; + anim_headers_ = new SequenceHeader_HL1 *[num_sequence_groups_]; + for (int i = 0; i < num_sequence_groups_; ++i) { + anim_buffers_[i] = nullptr; + anim_headers_[i] = nullptr; + } + + std::string file_path_without_extension = + DefaultIOSystem::absolutePath(file_path_) + + io_->getOsSeparator() + + DefaultIOSystem::completeBaseName(file_path_); + + for (int i = 1; i < num_sequence_groups_; ++i) { + std::stringstream ss; + ss << file_path_without_extension; + ss << std::setw(2) << std::setfill('0') << i; + ss << '.' << BaseImporter::GetExtension(file_path_); + + std::string sequence_file_path = ss.str(); + + load_file_into_buffer<SequenceHeader_HL1>(sequence_file_path, anim_buffers_[i]); + + anim_headers_[i] = (SequenceHeader_HL1 *)anim_buffers_[i]; + } +} + +// ------------------------------------------------------------------------------------------------ +// Read an MDL texture. +void HL1MDLLoader::read_texture(const Texture_HL1 *ptexture, + uint8_t *data, uint8_t *pal, aiTexture *pResult, + aiColor3D &last_palette_color) { + pResult->mFilename = ptexture->name; + pResult->mWidth = static_cast<unsigned int>(ptexture->width); + pResult->mHeight = static_cast<unsigned int>(ptexture->height); + pResult->achFormatHint[0] = 'r'; + pResult->achFormatHint[1] = 'g'; + pResult->achFormatHint[2] = 'b'; + pResult->achFormatHint[3] = 'a'; + pResult->achFormatHint[4] = '8'; + pResult->achFormatHint[5] = '8'; + pResult->achFormatHint[6] = '8'; + pResult->achFormatHint[7] = '8'; + pResult->achFormatHint[8] = '\0'; + + const size_t num_pixels = pResult->mWidth * pResult->mHeight; + aiTexel *out = pResult->pcData = new aiTexel[num_pixels]; + + // Convert indexed 8 bit to 32 bit RGBA. + for (size_t i = 0; i < num_pixels; ++i, ++out) { + out->r = pal[data[i] * 3]; + out->g = pal[data[i] * 3 + 1]; + out->b = pal[data[i] * 3 + 2]; + out->a = 255; + } + + // Get the last palette color. + last_palette_color.r = pal[255 * 3]; + last_palette_color.g = pal[255 * 3 + 1]; + last_palette_color.b = pal[255 * 3 + 2]; +} + +// ------------------------------------------------------------------------------------------------ +void HL1MDLLoader::read_textures() { + const Texture_HL1 *ptexture = (const Texture_HL1 *)((uint8_t *)texture_header_ + texture_header_->textureindex); + unsigned char *pin = texture_buffer_; + + scene_->mNumTextures = scene_->mNumMaterials = texture_header_->numtextures; + scene_->mTextures = new aiTexture *[scene_->mNumTextures]; + scene_->mMaterials = new aiMaterial *[scene_->mNumMaterials]; + + for (int i = 0; i < texture_header_->numtextures; ++i) { + scene_->mTextures[i] = new aiTexture(); + + aiColor3D last_palette_color; + read_texture(&ptexture[i], + pin + ptexture[i].index, + pin + ptexture[i].width * ptexture[i].height + ptexture[i].index, + scene_->mTextures[i], + last_palette_color); + + aiMaterial *scene_material = scene_->mMaterials[i] = new aiMaterial(); + + const aiTextureType texture_type = aiTextureType_DIFFUSE; + aiString texture_name(ptexture[i].name); + scene_material->AddProperty(&texture_name, AI_MATKEY_TEXTURE(texture_type, 0)); + + // Is this a chrome texture? + int chrome = ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_CHROME ? 1 : 0; + scene_material->AddProperty(&chrome, 1, AI_MDL_HL1_MATKEY_CHROME(texture_type, 0)); + + if (ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_FLATSHADE) { + // Flat shading. + const aiShadingMode shading_mode = aiShadingMode_Flat; + scene_material->AddProperty(&shading_mode, 1, AI_MATKEY_SHADING_MODEL); + } + + if (ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_ADDITIVE) { + // Additive texture. + const aiBlendMode blend_mode = aiBlendMode_Additive; + scene_material->AddProperty(&blend_mode, 1, AI_MATKEY_BLEND_FUNC); + } else if (ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_MASKED) { + // Texture with 1 bit alpha test. + const aiTextureFlags use_alpha = aiTextureFlags_UseAlpha; + scene_material->AddProperty(&use_alpha, 1, AI_MATKEY_TEXFLAGS(texture_type, 0)); + scene_material->AddProperty(&last_palette_color, 1, AI_MATKEY_COLOR_TRANSPARENT); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void HL1MDLLoader::read_skins() { + // Read skins, if any. + if (texture_header_->numskinfamilies <= 1) { + return; + } + + // Pointer to base texture index. + short *default_skin_ptr = (short *)((uint8_t *)texture_header_ + texture_header_->skinindex); + + // Start at first replacement skin. + short *replacement_skin_ptr = default_skin_ptr + texture_header_->numskinref; + + for (int i = 1; i < texture_header_->numskinfamilies; ++i, replacement_skin_ptr += texture_header_->numskinref) { + for (int j = 0; j < texture_header_->numskinref; ++j) { + if (default_skin_ptr[j] != replacement_skin_ptr[j]) { + // Save replacement textures. + aiString skinMaterialId(scene_->mTextures[replacement_skin_ptr[j]]->mFilename); + scene_->mMaterials[default_skin_ptr[j]]->AddProperty(&skinMaterialId, AI_MATKEY_TEXTURE_DIFFUSE(i)); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void HL1MDLLoader::read_bones() { + if (!header_->numbones) { + return; + } + + const Bone_HL1 *pbone = (const Bone_HL1 *)((uint8_t *)header_ + header_->boneindex); + + std::vector<std::string> unique_bones_names(header_->numbones); + for (int i = 0; i < header_->numbones; ++i) { + unique_bones_names[i] = pbone[i].name; + } + + // Ensure bones have unique names. + unique_name_generator_.set_template_name("Bone"); + unique_name_generator_.make_unique(unique_bones_names); + + temp_bones_.resize(header_->numbones); + + aiNode *bones_node = new aiNode(AI_MDL_HL1_NODE_BONES); + rootnode_children_.push_back(bones_node); + bones_node->mNumChildren = static_cast<unsigned int>(header_->numbones); + bones_node->mChildren = new aiNode *[bones_node->mNumChildren]; + + // Create bone matrices in local space. + for (int i = 0; i < header_->numbones; ++i) { + aiNode *bone_node = temp_bones_[i].node = bones_node->mChildren[i] = new aiNode(unique_bones_names[i]); + + aiVector3D angles(pbone[i].value[3], pbone[i].value[4], pbone[i].value[5]); + temp_bones_[i].absolute_transform = bone_node->mTransformation = + aiMatrix4x4(aiVector3D(1), aiQuaternion(angles.y, angles.z, angles.x), + aiVector3D(pbone[i].value[0], pbone[i].value[1], pbone[i].value[2])); + + if (pbone[i].parent == -1) { + bone_node->mParent = scene_->mRootNode; + } else { + bone_node->mParent = bones_node->mChildren[pbone[i].parent]; + + temp_bones_[i].absolute_transform = + temp_bones_[pbone[i].parent].absolute_transform * bone_node->mTransformation; + } + + temp_bones_[i].offset_matrix = temp_bones_[i].absolute_transform; + temp_bones_[i].offset_matrix.Inverse(); + } +} + +// ------------------------------------------------------------------------------------------------ +/* + Read meshes. + + Half-Life MDLs are structured such that each MDL + contains one or more 'bodypart(s)', which contain one + or more 'model(s)', which contains one or more mesh(es). + + * Bodyparts are used to group models that may be replaced + in the game .e.g a character could have a 'heads' group, + 'torso' group, 'shoes' group, with each group containing + different 'model(s)'. + + * Models, also called 'sub models', contain vertices as + well as a reference to each mesh used by the sub model. + + * Meshes contain a list of tris, also known as 'triverts'. + Each tris contains the following information: + + 1. The index of the position to use for the vertex. + 2. The index of the normal to use for the vertex. + 3. The S coordinate to use for the vertex UV. + 4. The T coordinate ^ + + These tris represent the way to represent the triangles + for each mesh. Depending on how the tool compiled the MDL, + those triangles were saved as strips and or fans. + + NOTE: Each tris is NOT unique. This means that you + might encounter the same vertex index but with a different + normal index, S coordinate, T coordinate. + + In addition, each mesh contains the texture's index. + + ------------------------------------------------------ + With the details above, there are several things to + take into consideration. + + * The Half-Life models store the vertices by sub model + rather than by mesh. Due to Assimp's structure, it + is necessary to remap each model vertex to be used + per mesh. Unfortunately, this has the consequence + to duplicate vertices. + + * Because the mesh triangles are comprised of strips and + fans, it is necessary to convert each primitive to + triangles, respectively (3 indices per face). +*/ +void HL1MDLLoader::read_meshes() { + if (!header_->numbodyparts) { + return; + } + + int total_verts = 0; + int total_triangles = 0; + total_models_ = 0; + + const Bodypart_HL1 *pbodypart = (const Bodypart_HL1 *)((uint8_t *)header_ + header_->bodypartindex); + const Model_HL1 *pmodel = nullptr; + const Mesh_HL1 *pmesh = nullptr; + + const Texture_HL1 *ptexture = (const Texture_HL1 *)((uint8_t *)texture_header_ + texture_header_->textureindex); + short *pskinref = (short *)((uint8_t *)texture_header_ + texture_header_->skinindex); + + scene_->mNumMeshes = 0; + + std::vector<std::string> unique_bodyparts_names; + unique_bodyparts_names.resize(header_->numbodyparts); + + // Count the number of meshes. + + for (int i = 0; i < header_->numbodyparts; ++i, ++pbodypart) { + unique_bodyparts_names[i] = pbodypart->name; + + pmodel = (Model_HL1 *)((uint8_t *)header_ + pbodypart->modelindex); + for (int j = 0; j < pbodypart->nummodels; ++j, ++pmodel) { + scene_->mNumMeshes += pmodel->nummesh; + total_verts += pmodel->numverts; + } + + total_models_ += pbodypart->nummodels; + } + + // Display limit infos. + if (total_verts > AI_MDL_HL1_MAX_VERTICES) { + log_warning_limit_exceeded<AI_MDL_HL1_MAX_VERTICES>(total_verts, "vertices"); + } + + if (scene_->mNumMeshes > AI_MDL_HL1_MAX_MESHES) { + log_warning_limit_exceeded<AI_MDL_HL1_MAX_MESHES>(scene_->mNumMeshes, "meshes"); + } + + if (total_models_ > AI_MDL_HL1_MAX_MODELS) { + log_warning_limit_exceeded<AI_MDL_HL1_MAX_MODELS>(total_models_, "models"); + } + + // Ensure bodyparts have unique names. + unique_name_generator_.set_template_name("Bodypart"); + unique_name_generator_.make_unique(unique_bodyparts_names); + + // Now do the same for each model. + pbodypart = (const Bodypart_HL1 *)((uint8_t *)header_ + header_->bodypartindex); + + // Prepare template name for bodypart models. + std::vector<std::string> unique_models_names; + unique_models_names.resize(total_models_); + + unsigned int model_index = 0; + + for (int i = 0; i < header_->numbodyparts; ++i, ++pbodypart) { + pmodel = (Model_HL1 *)((uint8_t *)header_ + pbodypart->modelindex); + for (int j = 0; j < pbodypart->nummodels; ++j, ++pmodel, ++model_index) + unique_models_names[model_index] = pmodel->name; + } + + unique_name_generator_.set_template_name("Model"); + unique_name_generator_.make_unique(unique_models_names); + + unsigned int mesh_index = 0; + + scene_->mMeshes = new aiMesh *[scene_->mNumMeshes]; + + pbodypart = (const Bodypart_HL1 *)((uint8_t *)header_ + header_->bodypartindex); + + /* Create a node that will represent the mesh hierarchy. + + <MDL_bodyparts> + | + +-- bodypart --+-- model -- [mesh index, mesh index, ...] + | | + | +-- model -- [mesh index, mesh index, ...] + | | + | ... + | + |-- bodypart -- ... + | + ... + */ + aiNode *bodyparts_node = new aiNode(AI_MDL_HL1_NODE_BODYPARTS); + rootnode_children_.push_back(bodyparts_node); + bodyparts_node->mNumChildren = static_cast<unsigned int>(header_->numbodyparts); + bodyparts_node->mChildren = new aiNode *[bodyparts_node->mNumChildren]; + aiNode **bodyparts_node_ptr = bodyparts_node->mChildren; + + // The following variables are defined here so they don't have + // to be recreated every iteration. + + // Model_HL1 vertices, in bind pose space. + std::vector<aiVector3D> bind_pose_vertices; + + // Model_HL1 normals, in bind pose space. + std::vector<aiVector3D> bind_pose_normals; + + // Used to contain temporary information for building a mesh. + std::vector<HL1MeshTrivert> triverts; + + std::vector<short> tricmds; + + // Which triverts to use for the mesh. + std::vector<short> mesh_triverts_indices; + + std::vector<HL1MeshFace> mesh_faces; + + /* triverts that have the same vertindex, but have different normindex,s,t values. + Similar triverts are mapped from vertindex to a list of similar triverts. */ + std::map<short, std::set<short>> triverts_similars; + + // triverts per bone. + std::map<int, std::set<short>> bone_triverts; + + /** This function adds a trivert index to the list of triverts per bone. + * \param[in] bone The bone that affects the trivert at index \p trivert_index. + * \param[in] trivert_index The trivert index. + */ + auto AddTrivertToBone = [&](int bone, short trivert_index) { + if (bone_triverts.count(bone) == 0) + bone_triverts.insert({ bone, std::set<short>{ trivert_index }}); + else + bone_triverts[bone].insert(trivert_index); + }; + + /** This function creates and appends a new trivert to the list of triverts. + * \param[in] trivert The trivert to use as a prototype. + * \param[in] bone The bone that affects \p trivert. + */ + auto AddSimilarTrivert = [&](const Trivert &trivert, const int bone) { + HL1MeshTrivert new_trivert(trivert); + new_trivert.localindex = static_cast<short>(mesh_triverts_indices.size()); + + short new_trivert_index = static_cast<short>(triverts.size()); + + if (triverts_similars.count(trivert.vertindex) == 0) + triverts_similars.insert({ trivert.vertindex, std::set<short>{ new_trivert_index }}); + else + triverts_similars[trivert.vertindex].insert(new_trivert_index); + + triverts.push_back(new_trivert); + + mesh_triverts_indices.push_back(new_trivert_index); + tricmds.push_back(new_trivert.localindex); + AddTrivertToBone(bone, new_trivert.localindex); + }; + + model_index = 0; + + for (int i = 0; i < header_->numbodyparts; ++i, ++pbodypart, ++bodyparts_node_ptr) { + pmodel = (const Model_HL1 *)((uint8_t *)header_ + pbodypart->modelindex); + + // Create bodypart node for the mesh tree hierarchy. + aiNode *bodypart_node = (*bodyparts_node_ptr) = new aiNode(unique_bodyparts_names[i]); + bodypart_node->mParent = bodyparts_node; + bodypart_node->mMetaData = aiMetadata::Alloc(1); + bodypart_node->mMetaData->Set(0, "Base", pbodypart->base); + + bodypart_node->mNumChildren = static_cast<unsigned int>(pbodypart->nummodels); + bodypart_node->mChildren = new aiNode *[bodypart_node->mNumChildren]; + aiNode **bodypart_models_ptr = bodypart_node->mChildren; + + for (int j = 0; j < pbodypart->nummodels; + ++j, ++pmodel, ++bodypart_models_ptr, ++model_index) { + + pmesh = (const Mesh_HL1 *)((uint8_t *)header_ + pmodel->meshindex); + + uint8_t *pvertbone = ((uint8_t *)header_ + pmodel->vertinfoindex); + uint8_t *pnormbone = ((uint8_t *)header_ + pmodel->norminfoindex); + vec3_t *pstudioverts = (vec3_t *)((uint8_t *)header_ + pmodel->vertindex); + vec3_t *pstudionorms = (vec3_t *)((uint8_t *)header_ + pmodel->normindex); + + // Each vertex and normal is in local space, so transform + // each of them to bring them in bind pose. + bind_pose_vertices.resize(pmodel->numverts); + bind_pose_normals.resize(pmodel->numnorms); + for (size_t k = 0; k < bind_pose_vertices.size(); ++k) { + const vec3_t &vert = pstudioverts[k]; + bind_pose_vertices[k] = temp_bones_[pvertbone[k]].absolute_transform * aiVector3D(vert[0], vert[1], vert[2]); + } + for (size_t k = 0; k < bind_pose_normals.size(); ++k) { + const vec3_t &norm = pstudionorms[k]; + // Compute the normal matrix to transform the normal into bind pose, + // without affecting its length. + const aiMatrix4x4 normal_matrix = aiMatrix4x4(temp_bones_[pnormbone[k]].absolute_transform).Inverse().Transpose(); + bind_pose_normals[k] = normal_matrix * aiVector3D(norm[0], norm[1], norm[2]); + } + + // Create model node for the mesh tree hierarchy. + aiNode *model_node = (*bodypart_models_ptr) = new aiNode(unique_models_names[model_index]); + model_node->mParent = bodypart_node; + model_node->mNumMeshes = static_cast<unsigned int>(pmodel->nummesh); + model_node->mMeshes = new unsigned int[model_node->mNumMeshes]; + unsigned int *model_meshes_ptr = model_node->mMeshes; + + for (int k = 0; k < pmodel->nummesh; ++k, ++pmesh, ++mesh_index, ++model_meshes_ptr) { + *model_meshes_ptr = mesh_index; + + // Read triverts. + short *ptricmds = (short *)((uint8_t *)header_ + pmesh->triindex); + float texcoords_s_scale = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width; + float texcoords_t_scale = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height; + + // Reset the data for the upcoming mesh. + triverts.clear(); + triverts.resize(pmodel->numverts); + mesh_triverts_indices.clear(); + mesh_faces.clear(); + triverts_similars.clear(); + bone_triverts.clear(); + + int l; + while ((l = *(ptricmds++))) { + bool is_triangle_fan = false; + + if (l < 0) { + l = -l; + is_triangle_fan = true; + } + + // Clear the list of tris for the upcoming tris. + tricmds.clear(); + + for (; l > 0; l--, ptricmds += 4) { + const Trivert *input_trivert = reinterpret_cast<const Trivert *>(ptricmds); + const int bone = pvertbone[input_trivert->vertindex]; + + HL1MeshTrivert *private_trivert = &triverts[input_trivert->vertindex]; + if (private_trivert->localindex == -1) { + // First time referenced. + *private_trivert = *input_trivert; + private_trivert->localindex = static_cast<short>(mesh_triverts_indices.size()); + mesh_triverts_indices.push_back(input_trivert->vertindex); + tricmds.push_back(private_trivert->localindex); + AddTrivertToBone(bone, private_trivert->localindex); + } else if (*private_trivert == *input_trivert) { + // Exists and is the same. + tricmds.push_back(private_trivert->localindex); + } else { + // No similar trivert associated to the trivert currently processed. + if (triverts_similars.count(input_trivert->vertindex) == 0) + AddSimilarTrivert(*input_trivert, bone); + else { + // Search in the list of similar triverts to see if the + // trivert in process is already registered. + short similar_index = -1; + for (auto it = triverts_similars[input_trivert->vertindex].cbegin(); + similar_index == -1 && it != triverts_similars[input_trivert->vertindex].cend(); + ++it) { + if (triverts[*it] == *input_trivert) + similar_index = *it; + } + + // If a similar trivert has been found, reuse it. + // Otherwise, add it. + if (similar_index == -1) + AddSimilarTrivert(*input_trivert, bone); + else + tricmds.push_back(triverts[similar_index].localindex); + } + } + } + + // Build mesh faces. + const int num_faces = static_cast<int>(tricmds.size() - 2); + mesh_faces.reserve(num_faces); + + if (is_triangle_fan) { + for (int faceIdx = 0; faceIdx < num_faces; ++faceIdx) { + mesh_faces.push_back(HL1MeshFace{ + tricmds[0], + tricmds[faceIdx + 1], + tricmds[faceIdx + 2] }); + } + } else { + for (int faceIdx = 0; faceIdx < num_faces; ++faceIdx) { + if (faceIdx & 1) { + // Preserve winding order. + mesh_faces.push_back(HL1MeshFace{ + tricmds[faceIdx + 1], + tricmds[faceIdx], + tricmds[faceIdx + 2] }); + } else { + mesh_faces.push_back(HL1MeshFace{ + tricmds[faceIdx], + tricmds[faceIdx + 1], + tricmds[faceIdx + 2] }); + } + } + } + + total_triangles += num_faces; + } + + // Create the scene mesh. + aiMesh *scene_mesh = scene_->mMeshes[mesh_index] = new aiMesh(); + scene_mesh->mPrimitiveTypes = aiPrimitiveType::aiPrimitiveType_TRIANGLE; + scene_mesh->mMaterialIndex = pskinref[pmesh->skinref]; + + scene_mesh->mNumVertices = static_cast<unsigned int>(mesh_triverts_indices.size()); + + if (scene_mesh->mNumVertices) { + scene_mesh->mVertices = new aiVector3D[scene_mesh->mNumVertices]; + scene_mesh->mNormals = new aiVector3D[scene_mesh->mNumVertices]; + + scene_mesh->mNumUVComponents[0] = 2; + scene_mesh->mTextureCoords[0] = new aiVector3D[scene_mesh->mNumVertices]; + + // Add vertices. + for (unsigned int v = 0; v < scene_mesh->mNumVertices; ++v) { + const HL1MeshTrivert *pTrivert = &triverts[mesh_triverts_indices[v]]; + scene_mesh->mVertices[v] = bind_pose_vertices[pTrivert->vertindex]; + scene_mesh->mNormals[v] = bind_pose_normals[pTrivert->normindex]; + scene_mesh->mTextureCoords[0][v] = aiVector3D( + pTrivert->s * texcoords_s_scale, + pTrivert->t * -texcoords_t_scale, 0); + } + + // Add face and indices. + scene_mesh->mNumFaces = static_cast<unsigned int>(mesh_faces.size()); + scene_mesh->mFaces = new aiFace[scene_mesh->mNumFaces]; + + for (unsigned int f = 0; f < scene_mesh->mNumFaces; ++f) { + aiFace *face = &scene_mesh->mFaces[f]; + face->mNumIndices = 3; + face->mIndices = new unsigned int[3]; + face->mIndices[0] = mesh_faces[f].v2; + face->mIndices[1] = mesh_faces[f].v1; + face->mIndices[2] = mesh_faces[f].v0; + } + + // Add mesh bones. + scene_mesh->mNumBones = static_cast<unsigned int>(bone_triverts.size()); + scene_mesh->mBones = new aiBone *[scene_mesh->mNumBones]; + + aiBone **scene_bone_ptr = scene_mesh->mBones; + + for (auto bone_it = bone_triverts.cbegin(); + bone_it != bone_triverts.cend(); + ++bone_it, ++scene_bone_ptr) { + const int bone_index = bone_it->first; + + aiBone *scene_bone = (*scene_bone_ptr) = new aiBone(); + scene_bone->mName = temp_bones_[bone_index].node->mName; + + scene_bone->mOffsetMatrix = temp_bones_[bone_index].offset_matrix; + + auto vertex_ids = bone_triverts.at(bone_index); + + // Add vertex weight per bone. + scene_bone->mNumWeights = static_cast<unsigned int>(vertex_ids.size()); + aiVertexWeight *vertex_weight_ptr = scene_bone->mWeights = new aiVertexWeight[scene_bone->mNumWeights]; + + for (auto vertex_it = vertex_ids.begin(); + vertex_it != vertex_ids.end(); + ++vertex_it, ++vertex_weight_ptr) { + vertex_weight_ptr->mVertexId = *vertex_it; + vertex_weight_ptr->mWeight = 1.0f; + } + } + } + } + } + } + + if (total_triangles > AI_MDL_HL1_MAX_TRIANGLES) { + log_warning_limit_exceeded<AI_MDL_HL1_MAX_TRIANGLES>(total_triangles, "triangles"); + } +} + +// ------------------------------------------------------------------------------------------------ +void HL1MDLLoader::read_animations() { + if (!header_->numseq) { + return; + } + + const SequenceDesc_HL1 *pseqdesc = (const SequenceDesc_HL1 *)((uint8_t *)header_ + header_->seqindex); + const SequenceGroup_HL1 *pseqgroup = nullptr; + const AnimValueOffset_HL1 *panim = nullptr; + const AnimValue_HL1 *panimvalue = nullptr; + + unique_sequence_names_.resize(header_->numseq); + for (int i = 0; i < header_->numseq; ++i) + unique_sequence_names_[i] = pseqdesc[i].label; + + // Ensure sequences have unique names. + unique_name_generator_.set_template_name("Sequence"); + unique_name_generator_.make_unique(unique_sequence_names_); + + scene_->mNumAnimations = 0; + + int highest_num_blend_animations = SequenceBlendMode_HL1::NoBlend; + + // Count the total number of animations. + for (int i = 0; i < header_->numseq; ++i, ++pseqdesc) { + scene_->mNumAnimations += pseqdesc->numblends; + highest_num_blend_animations = std::max(pseqdesc->numblends, highest_num_blend_animations); + } + + // Get the number of available blend controllers for global info. + get_num_blend_controllers(highest_num_blend_animations, num_blend_controllers_); + + pseqdesc = (const SequenceDesc_HL1 *)((uint8_t *)header_ + header_->seqindex); + + aiAnimation **scene_animations_ptr = scene_->mAnimations = new aiAnimation *[scene_->mNumAnimations]; + + for (int sequence = 0; sequence < header_->numseq; ++sequence, ++pseqdesc) { + pseqgroup = (const SequenceGroup_HL1 *)((uint8_t *)header_ + header_->seqgroupindex) + pseqdesc->seqgroup; + + if (pseqdesc->seqgroup == 0) { + panim = (const AnimValueOffset_HL1 *)((uint8_t *)header_ + pseqgroup->unused2 + pseqdesc->animindex); + } else { + panim = (const AnimValueOffset_HL1 *)((uint8_t *)anim_headers_[pseqdesc->seqgroup] + pseqdesc->animindex); + } + + for (int blend = 0; blend < pseqdesc->numblends; ++blend, ++scene_animations_ptr) { + + const Bone_HL1 *pbone = (const Bone_HL1 *)((uint8_t *)header_ + header_->boneindex); + + aiAnimation *scene_animation = (*scene_animations_ptr) = new aiAnimation(); + + scene_animation->mName = unique_sequence_names_[sequence]; + scene_animation->mTicksPerSecond = pseqdesc->fps; + scene_animation->mDuration = static_cast<double>(pseqdesc->fps) * pseqdesc->numframes; + scene_animation->mNumChannels = static_cast<unsigned int>(header_->numbones); + scene_animation->mChannels = new aiNodeAnim *[scene_animation->mNumChannels]; + + for (int bone = 0; bone < header_->numbones; bone++, ++pbone, ++panim) { + aiNodeAnim *node_anim = scene_animation->mChannels[bone] = new aiNodeAnim(); + node_anim->mNodeName = temp_bones_[bone].node->mName; + + node_anim->mNumPositionKeys = pseqdesc->numframes; + node_anim->mNumRotationKeys = node_anim->mNumPositionKeys; + node_anim->mNumScalingKeys = 0; + + node_anim->mPositionKeys = new aiVectorKey[node_anim->mNumPositionKeys]; + node_anim->mRotationKeys = new aiQuatKey[node_anim->mNumRotationKeys]; + + for (int frame = 0; frame < pseqdesc->numframes; ++frame) { + aiVectorKey *position_key = &node_anim->mPositionKeys[frame]; + aiQuatKey *rotation_key = &node_anim->mRotationKeys[frame]; + + aiVector3D angle1; + for (int j = 0; j < 3; ++j) { + if (panim->offset[j + 3] != 0) { + // Read compressed rotation delta. + panimvalue = (const AnimValue_HL1 *)((uint8_t *)panim + panim->offset[j + 3]); + extract_anim_value(panimvalue, frame, pbone->scale[j + 3], angle1[j]); + } + + // Add the default rotation value. + angle1[j] += pbone->value[j + 3]; + + if (panim->offset[j] != 0) { + // Read compressed position delta. + panimvalue = (const AnimValue_HL1 *)((uint8_t *)panim + panim->offset[j]); + extract_anim_value(panimvalue, frame, pbone->scale[j], position_key->mValue[j]); + } + + // Add the default position value. + position_key->mValue[j] += pbone->value[j]; + } + + position_key->mTime = rotation_key->mTime = static_cast<double>(frame); + /* The Half-Life engine uses X as forward, Y as left, Z as up. Therefore, + pitch,yaw,roll is represented as (YZX). */ + rotation_key->mValue = aiQuaternion(angle1.y, angle1.z, angle1.x); + rotation_key->mValue.Normalize(); + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void HL1MDLLoader::read_sequence_groups_info() { + if (!header_->numseqgroups) { + return; + } + + aiNode *sequence_groups_node = new aiNode(AI_MDL_HL1_NODE_SEQUENCE_GROUPS); + rootnode_children_.push_back(sequence_groups_node); + + sequence_groups_node->mNumChildren = static_cast<unsigned int>(header_->numseqgroups); + sequence_groups_node->mChildren = new aiNode *[sequence_groups_node->mNumChildren]; + + const SequenceGroup_HL1 *pseqgroup = (const SequenceGroup_HL1 *)((uint8_t *)header_ + header_->seqgroupindex); + + unique_sequence_groups_names_.resize(header_->numseqgroups); + for (int i = 0; i < header_->numseqgroups; ++i) { + unique_sequence_groups_names_[i] = pseqgroup[i].label; + } + + // Ensure sequence groups have unique names. + unique_name_generator_.set_template_name("SequenceGroup"); + unique_name_generator_.make_unique(unique_sequence_groups_names_); + + for (int i = 0; i < header_->numseqgroups; ++i, ++pseqgroup) { + aiNode *sequence_group_node = sequence_groups_node->mChildren[i] = new aiNode(unique_sequence_groups_names_[i]); + sequence_group_node->mParent = sequence_groups_node; + + aiMetadata *md = sequence_group_node->mMetaData = aiMetadata::Alloc(1); + if (i == 0) { + /* StudioMDL does not write the file name for the default sequence group, + so we will write it. */ + md->Set(0, "File", aiString(file_path_)); + } else { + md->Set(0, "File", aiString(pseqgroup->name)); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void HL1MDLLoader::read_sequence_infos() { + if (!header_->numseq) { + return; + } + + const SequenceDesc_HL1 *pseqdesc = (const SequenceDesc_HL1 *)((uint8_t *)header_ + header_->seqindex); + + aiNode *sequence_infos_node = new aiNode(AI_MDL_HL1_NODE_SEQUENCE_INFOS); + rootnode_children_.push_back(sequence_infos_node); + + sequence_infos_node->mNumChildren = static_cast<unsigned int>(header_->numseq); + sequence_infos_node->mChildren = new aiNode *[sequence_infos_node->mNumChildren]; + + std::vector<aiNode *> sequence_info_node_children; + + int animation_index = 0; + for (int i = 0; i < header_->numseq; ++i, ++pseqdesc) { + // Clear the list of children for the upcoming sequence info node. + sequence_info_node_children.clear(); + + aiNode *sequence_info_node = sequence_infos_node->mChildren[i] = new aiNode(unique_sequence_names_[i]); + sequence_info_node->mParent = sequence_infos_node; + + // Setup sequence info node Metadata. + aiMetadata *md = sequence_info_node->mMetaData = aiMetadata::Alloc(16); + md->Set(0, "AnimationIndex", animation_index); + animation_index += pseqdesc->numblends; + + // Reference the sequence group by name. This allows us to search a particular + // sequence group by name using aiNode(s). + md->Set(1, "SequenceGroup", aiString(unique_sequence_groups_names_[pseqdesc->seqgroup])); + md->Set(2, "FramesPerSecond", pseqdesc->fps); + md->Set(3, "NumFrames", pseqdesc->numframes); + md->Set(4, "NumBlends", pseqdesc->numblends); + md->Set(5, "Activity", pseqdesc->activity); + md->Set(6, "ActivityWeight", pseqdesc->actweight); + md->Set(7, "MotionFlags", pseqdesc->motiontype); + md->Set(8, "MotionBone", temp_bones_[pseqdesc->motionbone].node->mName); + md->Set(9, "LinearMovement", aiVector3D(pseqdesc->linearmovement[0], pseqdesc->linearmovement[1], pseqdesc->linearmovement[2])); + md->Set(10, "BBMin", aiVector3D(pseqdesc->bbmin[0], pseqdesc->bbmin[1], pseqdesc->bbmin[2])); + md->Set(11, "BBMax", aiVector3D(pseqdesc->bbmax[0], pseqdesc->bbmax[1], pseqdesc->bbmax[2])); + md->Set(12, "EntryNode", pseqdesc->entrynode); + md->Set(13, "ExitNode", pseqdesc->exitnode); + md->Set(14, "NodeFlags", pseqdesc->nodeflags); + md->Set(15, "Flags", pseqdesc->flags); + + if (import_settings_.read_blend_controllers) { + int num_blend_controllers; + if (get_num_blend_controllers(pseqdesc->numblends, num_blend_controllers) && num_blend_controllers) { + // Read blend controllers info. + aiNode *blend_controllers_node = new aiNode(AI_MDL_HL1_NODE_BLEND_CONTROLLERS); + sequence_info_node_children.push_back(blend_controllers_node); + blend_controllers_node->mParent = sequence_info_node; + blend_controllers_node->mNumChildren = static_cast<unsigned int>(num_blend_controllers); + blend_controllers_node->mChildren = new aiNode *[blend_controllers_node->mNumChildren]; + + for (unsigned int j = 0; j < blend_controllers_node->mNumChildren; ++j) { + aiNode *blend_controller_node = blend_controllers_node->mChildren[j] = new aiNode(); + blend_controller_node->mParent = blend_controllers_node; + + aiMetadata *metaData = blend_controller_node->mMetaData = aiMetadata::Alloc(3); + metaData->Set(0, "Start", pseqdesc->blendstart[j]); + metaData->Set(1, "End", pseqdesc->blendend[j]); + metaData->Set(2, "MotionFlags", pseqdesc->blendtype[j]); + } + } + } + + if (import_settings_.read_animation_events && pseqdesc->numevents) { + // Read animation events. + + if (pseqdesc->numevents > AI_MDL_HL1_MAX_EVENTS) { + log_warning_limit_exceeded<AI_MDL_HL1_MAX_EVENTS>( + "Sequence " + std::string(pseqdesc->label), + pseqdesc->numevents, "animation events"); + } + + const AnimEvent_HL1 *pevent = (const AnimEvent_HL1 *)((uint8_t *)header_ + pseqdesc->eventindex); + + aiNode *pEventsNode = new aiNode(AI_MDL_HL1_NODE_ANIMATION_EVENTS); + sequence_info_node_children.push_back(pEventsNode); + pEventsNode->mParent = sequence_info_node; + pEventsNode->mNumChildren = static_cast<unsigned int>(pseqdesc->numevents); + pEventsNode->mChildren = new aiNode *[pEventsNode->mNumChildren]; + + for (unsigned int j = 0; j < pEventsNode->mNumChildren; ++j, ++pevent) { + aiNode *pEvent = pEventsNode->mChildren[j] = new aiNode(); + pEvent->mParent = pEventsNode; + + aiMetadata *metaData = pEvent->mMetaData = aiMetadata::Alloc(3); + metaData->Set(0, "Frame", pevent->frame); + metaData->Set(1, "ScriptEvent", pevent->event); + metaData->Set(2, "Options", aiString(pevent->options)); + } + } + + if (sequence_info_node_children.size()) { + sequence_info_node->addChildren( + static_cast<unsigned int>(sequence_info_node_children.size()), + sequence_info_node_children.data()); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void HL1MDLLoader::read_sequence_transitions() { + if (!header_->numtransitions) { + return; + } + + // Read sequence transition graph. + aiNode *transition_graph_node = new aiNode(AI_MDL_HL1_NODE_SEQUENCE_TRANSITION_GRAPH); + rootnode_children_.push_back(transition_graph_node); + + uint8_t *ptransitions = ((uint8_t *)header_ + header_->transitionindex); + aiMetadata *md = transition_graph_node->mMetaData = aiMetadata::Alloc(header_->numtransitions * header_->numtransitions); + for (unsigned int i = 0; i < md->mNumProperties; ++i) + md->Set(i, std::to_string(i), static_cast<int>(ptransitions[i])); +} + +void HL1MDLLoader::read_attachments() { + if (!header_->numattachments) { + return; + } + + const Attachment_HL1 *pattach = (const Attachment_HL1 *)((uint8_t *)header_ + header_->attachmentindex); + + aiNode *attachments_node = new aiNode(AI_MDL_HL1_NODE_ATTACHMENTS); + rootnode_children_.push_back(attachments_node); + attachments_node->mNumChildren = static_cast<unsigned int>(header_->numattachments); + attachments_node->mChildren = new aiNode *[attachments_node->mNumChildren]; + + for (int i = 0; i < header_->numattachments; ++i, ++pattach) { + aiNode *attachment_node = attachments_node->mChildren[i] = new aiNode(); + attachment_node->mParent = attachments_node; + attachment_node->mMetaData = aiMetadata::Alloc(2); + attachment_node->mMetaData->Set(0, "Position", aiVector3D(pattach->org[0], pattach->org[1], pattach->org[2])); + // Reference the bone by name. This allows us to search a particular + // bone by name using aiNode(s). + attachment_node->mMetaData->Set(1, "Bone", temp_bones_[pattach->bone].node->mName); + } +} + +// ------------------------------------------------------------------------------------------------ +void HL1MDLLoader::read_hitboxes() { + if (!header_->numhitboxes) { + return; + } + + const Hitbox_HL1 *phitbox = (const Hitbox_HL1 *)((uint8_t *)header_ + header_->hitboxindex); + + aiNode *hitboxes_node = new aiNode(AI_MDL_HL1_NODE_HITBOXES); + rootnode_children_.push_back(hitboxes_node); + hitboxes_node->mNumChildren = static_cast<unsigned int>(header_->numhitboxes); + hitboxes_node->mChildren = new aiNode *[hitboxes_node->mNumChildren]; + + for (int i = 0; i < header_->numhitboxes; ++i, ++phitbox) { + aiNode *hitbox_node = hitboxes_node->mChildren[i] = new aiNode(); + hitbox_node->mParent = hitboxes_node; + + aiMetadata *md = hitbox_node->mMetaData = aiMetadata::Alloc(4); + // Reference the bone by name. This allows us to search a particular + // bone by name using aiNode(s). + md->Set(0, "Bone", temp_bones_[phitbox->bone].node->mName); + md->Set(1, "HitGroup", phitbox->group); + md->Set(2, "BBMin", aiVector3D(phitbox->bbmin[0], phitbox->bbmin[1], phitbox->bbmin[2])); + md->Set(3, "BBMax", aiVector3D(phitbox->bbmax[0], phitbox->bbmax[1], phitbox->bbmax[2])); + } +} + +// ------------------------------------------------------------------------------------------------ +void HL1MDLLoader::read_bone_controllers() { + if (!header_->numbonecontrollers) { + return; + } + + const BoneController_HL1 *pbonecontroller = (const BoneController_HL1 *)((uint8_t *)header_ + header_->bonecontrollerindex); + + aiNode *bones_controller_node = new aiNode(AI_MDL_HL1_NODE_BONE_CONTROLLERS); + rootnode_children_.push_back(bones_controller_node); + bones_controller_node->mNumChildren = static_cast<unsigned int>(header_->numbonecontrollers); + bones_controller_node->mChildren = new aiNode *[bones_controller_node->mNumChildren]; + + for (int i = 0; i < header_->numbonecontrollers; ++i, ++pbonecontroller) { + aiNode *bone_controller_node = bones_controller_node->mChildren[i] = new aiNode(); + bone_controller_node->mParent = bones_controller_node; + + aiMetadata *md = bone_controller_node->mMetaData = aiMetadata::Alloc(5); + // Reference the bone by name. This allows us to search a particular + // bone by name using aiNode(s). + md->Set(0, "Bone", temp_bones_[pbonecontroller->bone].node->mName); + md->Set(1, "MotionFlags", pbonecontroller->type); + md->Set(2, "Start", pbonecontroller->start); + md->Set(3, "End", pbonecontroller->end); + md->Set(4, "Channel", pbonecontroller->index); + } +} + +// ------------------------------------------------------------------------------------------------ +void HL1MDLLoader::read_global_info() { + aiNode *global_info_node = new aiNode(AI_MDL_HL1_NODE_GLOBAL_INFO); + rootnode_children_.push_back(global_info_node); + + aiMetadata *md = global_info_node->mMetaData = aiMetadata::Alloc(import_settings_.read_misc_global_info ? 16 : 11); + md->Set(0, "Version", AI_MDL_HL1_VERSION); + md->Set(1, "NumBodyparts", header_->numbodyparts); + md->Set(2, "NumModels", total_models_); + md->Set(3, "NumBones", header_->numbones); + md->Set(4, "NumAttachments", import_settings_.read_attachments ? header_->numattachments : 0); + md->Set(5, "NumSkinFamilies", texture_header_->numskinfamilies); + md->Set(6, "NumHitboxes", import_settings_.read_hitboxes ? header_->numhitboxes : 0); + md->Set(7, "NumBoneControllers", import_settings_.read_bone_controllers ? header_->numbonecontrollers : 0); + md->Set(8, "NumSequences", import_settings_.read_animations ? header_->numseq : 0); + md->Set(9, "NumBlendControllers", import_settings_.read_blend_controllers ? num_blend_controllers_ : 0); + md->Set(10, "NumTransitionNodes", import_settings_.read_sequence_transitions ? header_->numtransitions : 0); + + if (import_settings_.read_misc_global_info) { + md->Set(11, "EyePosition", aiVector3D(header_->eyeposition[0], header_->eyeposition[1], header_->eyeposition[2])); + md->Set(12, "HullMin", aiVector3D(header_->min[0], header_->min[1], header_->min[2])); + md->Set(13, "HullMax", aiVector3D(header_->max[0], header_->max[1], header_->max[2])); + md->Set(14, "CollisionMin", aiVector3D(header_->bbmin[0], header_->bbmin[1], header_->bbmin[2])); + md->Set(15, "CollisionMax", aiVector3D(header_->bbmax[0], header_->bbmax[1], header_->bbmax[2])); + } +} + +// ------------------------------------------------------------------------------------------------ +/** @brief This method reads a compressed anim value. +* +* @note The structure of this method is taken from HL2 source code. +* Although this is from HL2, it's implementation is almost identical +* to code found in HL1 SDK. See HL1 and HL2 SDKs for more info. +* +* source: +* HL1 source code. +* file: studio_render.cpp +* function(s): CalcBoneQuaternion and CalcBonePosition +* +* HL2 source code. +* file: bone_setup.cpp +* function(s): ExtractAnimValue +*/ +void HL1MDLLoader::extract_anim_value( + const AnimValue_HL1 *panimvalue, + int frame, float bone_scale, ai_real &value) { + int k = frame; + + // find span of values that includes the frame we want + while (panimvalue->num.total <= k) { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + } + + // Bah, missing blend! + if (panimvalue->num.valid > k) { + value = panimvalue[k + 1].value * bone_scale; + } else { + value = panimvalue[panimvalue->num.valid].value * bone_scale; + } +} + +// ------------------------------------------------------------------------------------------------ +// Get the number of blend controllers. +bool HL1MDLLoader::get_num_blend_controllers(const int num_blend_animations, int &num_blend_controllers) { + + switch (num_blend_animations) { + case SequenceBlendMode_HL1::NoBlend: + num_blend_controllers = 0; + return true; + case SequenceBlendMode_HL1::TwoWayBlending: + num_blend_controllers = 1; + return true; + case SequenceBlendMode_HL1::FourWayBlending: + num_blend_controllers = 2; + return true; + default: + num_blend_controllers = 0; + ASSIMP_LOG_WARN(MDL_HALFLIFE_LOG_HEADER "Unsupported number of blend animations (", num_blend_animations, ")"); + return false; + } +} + +} // namespace HalfLife +} // namespace MDL +} // namespace Assimp diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h new file mode 100644 index 0000000..d87d6d3 --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h @@ -0,0 +1,243 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 HL1MDLLoader.h + * @brief Declaration of the Half-Life 1 MDL loader. + */ + +#ifndef AI_HL1MDLLOADER_INCLUDED +#define AI_HL1MDLLOADER_INCLUDED + +#include "HL1FileData.h" +#include "HL1ImportSettings.h" +#include "UniqueNameGenerator.h" + +#include <memory> +#include <string> + +#include <assimp/types.h> +#include <assimp/scene.h> +#include <assimp/texture.h> +#include <assimp/IOSystem.hpp> +#include <assimp/DefaultIOSystem.h> +#include <assimp/Exceptional.h> + +namespace Assimp { +namespace MDL { +namespace HalfLife { + +class HL1MDLLoader { +public: + HL1MDLLoader() = delete; + HL1MDLLoader(const HL1MDLLoader &) = delete; + + /** See variables descriptions at the end for more details. */ + HL1MDLLoader( + aiScene *scene, + IOSystem *io, + const unsigned char *buffer, + const std::string &file_path, + const HL1ImportSettings &import_settings); + + ~HL1MDLLoader(); + + void load_file(); + +protected: + /** \brief Validate the header data structure of a Half-Life 1 MDL file. + * \param[in] header Input header to be validated. + * \param[in] is_texture_header Whether or not we are reading an MDL + * texture file. + */ + void validate_header(const Header_HL1 *header, bool is_texture_header); + + void load_texture_file(); + void load_sequence_groups_files(); + void read_textures(); + void read_skins(); + void read_bones(); + void read_meshes(); + void read_animations(); + void read_sequence_groups_info(); + void read_sequence_infos(); + void read_sequence_transitions(); + void read_attachments(); + void read_hitboxes(); + void read_bone_controllers(); + void read_global_info(); + +private: + void release_resources(); + + /** \brief Load a file and copy it's content to a buffer. + * \param file_path The path to the file to be loaded. + * \param buffer A pointer to a buffer to receive the data. + */ + template <typename MDLFileHeader> + void load_file_into_buffer(const std::string &file_path, unsigned char *&buffer); + + /** \brief Read an MDL texture. + * \param[in] ptexture A pointer to an MDL texture. + * \param[in] data A pointer to the data from \p ptexture. + * \param[in] pal A pointer to the texture palette from \p ptexture. + * \param[in,out] pResult A pointer to the output resulting Assimp texture. + * \param[in,out] last_palette_color The last color from the image palette. + */ + void read_texture(const Texture_HL1 *ptexture, + uint8_t *data, uint8_t *pal, aiTexture *pResult, + aiColor3D &last_palette_color); + + /** \brief This method reads a compressed anim value. + * \param[in] panimvalue A pointer to the animation data. + * \param[in] frame The frame to look for. + * \param[in] bone_scale The current bone scale to apply to the compressed value. + * \param[in,out] value The decompressed anim value at \p frame. + */ + void extract_anim_value(const AnimValue_HL1 *panimvalue, + int frame, float bone_scale, ai_real &value); + + /** + * \brief Given the number of blend animations, determine the number of blend controllers. + * + * \param[in] num_blend_animations The number of blend animations. + * \param[out] num_blend_controllers The number of blend controllers. + * \return True if the number of blend controllers was determined. False otherwise. + */ + static bool get_num_blend_controllers(const int num_blend_animations, int &num_blend_controllers); + + /** Output scene to be filled */ + aiScene *scene_; + + /** Output I/O handler. Required for additional IO operations. */ + IOSystem *io_; + + /** Buffer from MDLLoader class. */ + const unsigned char *buffer_; + + /** The full file path to the MDL file we are trying to load. + * Used to locate other MDL files since MDL may store resources + * in external MDL files. */ + const std::string &file_path_; + + /** Configuration for HL1 MDL */ + const HL1ImportSettings &import_settings_; + + /** Main MDL header. */ + const Header_HL1 *header_; + + /** External MDL texture header. */ + const Header_HL1 *texture_header_; + + /** External MDL animation headers. + * One for each loaded animation file. */ + SequenceHeader_HL1 **anim_headers_; + + /** Texture file data. */ + unsigned char *texture_buffer_; + + /** Animation files data. */ + unsigned char **anim_buffers_; + + /** The number of sequence groups. */ + int num_sequence_groups_; + + /** The list of children to be appended to the scene's root node. */ + std::vector<aiNode *> rootnode_children_; + + /** A unique name generator. Used to generate names for MDL values + * that may have empty/duplicate names. */ + UniqueNameGenerator unique_name_generator_; + + /** The list of unique sequence names. */ + std::vector<std::string> unique_sequence_names_; + + /** The list of unique sequence groups names. */ + std::vector<std::string> unique_sequence_groups_names_; + + /** Structure to store temporary bone information. */ + struct TempBone { + + TempBone() : + node(nullptr), + absolute_transform(), + offset_matrix() {} + + aiNode *node; + aiMatrix4x4 absolute_transform; + aiMatrix4x4 offset_matrix; + }; + + std::vector<TempBone> temp_bones_; + + /** The number of available bone controllers in the model. */ + int num_blend_controllers_; + + /** Self explanatory. */ + int total_models_; +}; + +// ------------------------------------------------------------------------------------------------ +template <typename MDLFileHeader> +void HL1MDLLoader::load_file_into_buffer(const std::string &file_path, unsigned char *&buffer) { + if (!io_->Exists(file_path)) + throw DeadlyImportError("Missing file ", DefaultIOSystem::fileName(file_path), "."); + + std::unique_ptr<IOStream> file(io_->Open(file_path)); + + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open MDL file ", DefaultIOSystem::fileName(file_path), "."); + } + + const size_t file_size = file->FileSize(); + if (file_size < sizeof(MDLFileHeader)) { + throw DeadlyImportError("MDL file is too small."); + } + + buffer = new unsigned char[1 + file_size]; + file->Read((void *)buffer, 1, file_size); + buffer[file_size] = '\0'; +} + +} // namespace HalfLife +} // namespace MDL +} // namespace Assimp + +#endif // AI_HL1MDLLOADER_INCLUDED diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MeshTrivert.h b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MeshTrivert.h new file mode 100644 index 0000000..4ef8a13 --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HL1MeshTrivert.h @@ -0,0 +1,127 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 HL1MeshTrivert.h + * @brief This file contains the class declaration for the + * HL1 mesh trivert class. + */ + +#ifndef AI_HL1MESHTRIVERT_INCLUDED +#define AI_HL1MESHTRIVERT_INCLUDED + +#include "HL1FileData.h" + +namespace Assimp { +namespace MDL { +namespace HalfLife { + +/* A class to help map model triverts to mesh triverts. */ +struct HL1MeshTrivert { + HL1MeshTrivert() : + vertindex(-1), + normindex(-1), + s(0), + t(0), + localindex(-1) { + } + + HL1MeshTrivert(short vertindex, short normindex, short s, short t, short localindex) : + vertindex(vertindex), + normindex(normindex), + s(s), + t(t), + localindex(localindex) { + } + + HL1MeshTrivert(const Trivert &a) : + vertindex(a.vertindex), + normindex(a.normindex), + s(a.s), + t(a.t), + localindex(-1) { + } + + inline bool operator==(const Trivert &a) const { + return vertindex == a.vertindex && + normindex == a.normindex && + s == a.s && + t == a.t; + } + + inline bool operator!=(const Trivert &a) const { + return !(*this == a); + } + + inline bool operator==(const HL1MeshTrivert &a) const { + return localindex == a.localindex && + vertindex == a.vertindex && + normindex == a.normindex && + s == a.s && + t == a.t; + } + + inline bool operator!=(const HL1MeshTrivert &a) const { + return !(*this == a); + } + + inline HL1MeshTrivert &operator=(const Trivert &other) { + vertindex = other.vertindex; + normindex = other.normindex; + s = other.s; + t = other.t; + return *this; + } + + short vertindex; + short normindex; + short s, t; + short localindex; +}; + +struct HL1MeshFace { + short v0, v1, v2; +}; + +} // namespace HalfLife +} // namespace MDL +} // namespace Assimp + +#endif // AI_HL1MESHTRIVERT_INCLUDED diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/HalfLifeMDLBaseHeader.h b/libs/assimp/code/AssetLib/MDL/HalfLife/HalfLifeMDLBaseHeader.h new file mode 100644 index 0000000..c7808c4 --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/HalfLife/HalfLifeMDLBaseHeader.h @@ -0,0 +1,67 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 HalfLifeMDLBaseHeader.h */ + +#ifndef AI_HALFLIFEMDLBASEHEADER_INCLUDED +#define AI_HALFLIFEMDLBASEHEADER_INCLUDED + +#include <assimp/types.h> + +namespace Assimp { +namespace MDL { +namespace HalfLife { + +/** Used to interface different Valve MDL formats. */ +struct HalfLifeMDLBaseHeader +{ + //! Magic number: "IDST"/"IDSQ" + char ident[4]; + + //! The file format version. + int32_t version; +}; + +} +} +} + +#endif // AI_HALFLIFEMDLBASEHEADER_INCLUDED diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/LogFunctions.h b/libs/assimp/code/AssetLib/MDL/HalfLife/LogFunctions.h new file mode 100644 index 0000000..003774d --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/HalfLife/LogFunctions.h @@ -0,0 +1,95 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 LogFunctions.h */ + +#ifndef AI_MDL_HALFLIFE_LOGFUNCTIONS_INCLUDED +#define AI_MDL_HALFLIFE_LOGFUNCTIONS_INCLUDED + +#include <assimp/Logger.hpp> +#include <string> + +namespace Assimp { +namespace MDL { +namespace HalfLife { + +/** + * \brief A function to log precise messages regarding limits exceeded. + * + * \param[in] subject Subject. + * \param[in] current_amount Current amount. + * \param[in] direct_object Direct object. + * LIMIT Limit constant. + * + * Example: Model has 100 textures, which exceeds the limit (50) + * + * where \p subject is 'Model' + * \p current_amount is '100' + * \p direct_object is 'textures' + * LIMIT is '50' + */ +template <int LIMIT> +static inline void log_warning_limit_exceeded( + const std::string &subject, int current_amount, + const std::string &direct_object) { + + ASSIMP_LOG_WARN(MDL_HALFLIFE_LOG_HEADER + + subject + + " has " + + std::to_string(current_amount) + " " + + direct_object + + ", which exceeds the limit (" + + std::to_string(LIMIT) + + ")"); +} + +/** \brief Same as above, but uses 'Model' as the subject. */ +template <int LIMIT> +static inline void log_warning_limit_exceeded(int current_amount, + const std::string &direct_object) { + log_warning_limit_exceeded<LIMIT>("Model", current_amount, direct_object); +} + +} // namespace HalfLife +} // namespace MDL +} // namespace Assimp + +#endif // AI_MDL_HALFLIFE_LOGFUNCTIONS_INCLUDED diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.cpp b/libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.cpp new file mode 100644 index 0000000..6fc8b11 --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.cpp @@ -0,0 +1,180 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 UniqueNameGenerator.cpp + * @brief Implementation for the unique name generator. + */ + +#include "UniqueNameGenerator.h" +#include <algorithm> +#include <list> +#include <map> +#include <numeric> + +namespace Assimp { +namespace MDL { +namespace HalfLife { + +UniqueNameGenerator::UniqueNameGenerator() : + template_name_("unnamed"), + separator_("_") { +} + +UniqueNameGenerator::UniqueNameGenerator(const char *template_name) : + template_name_(template_name), + separator_("_") { +} + +UniqueNameGenerator::UniqueNameGenerator(const char *template_name, const char *separator) : + template_name_(template_name), + separator_(separator) { +} + +UniqueNameGenerator::~UniqueNameGenerator() { +} + +void UniqueNameGenerator::make_unique(std::vector<std::string> &names) { + struct DuplicateInfo { + DuplicateInfo() : + indices(), + next_id(0) { + } + + std::list<size_t> indices; + size_t next_id; + }; + + std::vector<size_t> empty_names_indices; + std::vector<size_t> template_name_duplicates; + std::map<std::string, DuplicateInfo> names_to_duplicates; + + const std::string template_name_with_separator(template_name_ + separator_); + + auto format_name = [&](const std::string &base_name, size_t id) -> std::string { + return base_name + separator_ + std::to_string(id); + }; + + auto generate_unique_name = [&](const std::string &base_name) -> std::string { + auto *duplicate_info = &names_to_duplicates[base_name]; + + std::string new_name; + + bool found_identical_name; + bool tried_with_base_name_only = false; + do { + // Assume that no identical name exists. + found_identical_name = false; + + if (!tried_with_base_name_only) { + // First try with only the base name. + new_name = base_name; + } else { + // Create the name expected to be unique. + new_name = format_name(base_name, duplicate_info->next_id); + } + + // Check in the list of duplicates for an identical name. + for (size_t i = 0; + i < names.size() && + !found_identical_name; + ++i) { + if (new_name == names[i]) + found_identical_name = true; + } + + if (tried_with_base_name_only) + ++duplicate_info->next_id; + + tried_with_base_name_only = true; + + } while (found_identical_name); + + return new_name; + }; + + for (size_t i = 0; i < names.size(); ++i) { + // Check for empty names. + if (names[i].find_first_not_of(' ') == std::string::npos) { + empty_names_indices.push_back(i); + continue; + } + + /* Check for potential duplicate. + a) Either if this name is the same as the template name or + b) <template name><separator> is found at the beginning. */ + if (names[i] == template_name_ || + names[i].substr(0, template_name_with_separator.length()) == template_name_with_separator) + template_name_duplicates.push_back(i); + + // Map each unique name to it's duplicate. + if (names_to_duplicates.count(names[i]) == 0) + names_to_duplicates.insert({ names[i], DuplicateInfo()}); + else + names_to_duplicates[names[i]].indices.push_back(i); + } + + // Make every non-empty name unique. + for (auto it = names_to_duplicates.begin(); + it != names_to_duplicates.end(); ++it) { + for (auto it2 = it->second.indices.begin(); + it2 != it->second.indices.end(); + ++it2) + names[*it2] = generate_unique_name(it->first); + } + + // Generate a unique name for every empty string. + if (template_name_duplicates.size()) { + // At least one string ressembles to <template name>. + for (auto it = empty_names_indices.begin(); + it != empty_names_indices.end(); ++it) + names[*it] = generate_unique_name(template_name_); + } else { + // No string alike <template name> exists. + size_t i = 0; + for (auto it = empty_names_indices.begin(); + it != empty_names_indices.end(); ++it, ++i) + names[*it] = format_name(template_name_, i); + } +} + +} // namespace HalfLife +} // namespace MDL +} // namespace Assimp diff --git a/libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.h b/libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.h new file mode 100644 index 0000000..73b6f9e --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.h @@ -0,0 +1,81 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 UniqueNameGenerator.h + * @brief Declaration of the unique name generator. + */ + +#ifndef AI_UNIQUENAMEGENERATOR_INCLUDED +#define AI_UNIQUENAMEGENERATOR_INCLUDED + +#include <string> +#include <vector> + +namespace Assimp { +namespace MDL { +namespace HalfLife { + +class UniqueNameGenerator { +public: + UniqueNameGenerator(); + UniqueNameGenerator(const char *template_name); + UniqueNameGenerator(const char *template_name, const char *separator); + ~UniqueNameGenerator(); + + inline void set_template_name(const char *template_name) { + template_name_ = template_name; + } + inline void set_separator(const char *separator) { + separator_ = separator; + } + + void make_unique(std::vector<std::string> &names); + +private: + std::string template_name_; + std::string separator_; +}; + +} // namespace HalfLife +} // namespace MDL +} // namespace Assimp + +#endif // AI_UNIQUENAMEGENERATOR_INCLUDED diff --git a/libs/assimp/code/AssetLib/MDL/MDLDefaultColorMap.h b/libs/assimp/code/AssetLib/MDL/MDLDefaultColorMap.h new file mode 100644 index 0000000..2eecac2 --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/MDLDefaultColorMap.h @@ -0,0 +1,120 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines the default color map used for Quake 1 model textures + * + * The lib tries to load colormap.lmp from the model's directory. + * This table is only used when required. + */ + +#ifndef AI_MDL_DEFAULTLMP_H_INC +#define AI_MDL_DEFAULTLMP_H_INC + +const unsigned char g_aclrDefaultColorMap[256][3] = { +{ 0, 0, 0}, { 15, 15, 15}, { 31, 31, 31}, { 47, 47, 47}, +{ 63, 63, 63}, { 75, 75, 75}, { 91, 91, 91}, {107, 107, 107}, +{123, 123, 123}, {139, 139, 139}, {155, 155, 155}, {171, 171, 171}, +{187, 187, 187}, {203, 203, 203}, {219, 219, 219}, {235, 235, 235}, +{ 15, 11, 7}, { 23, 15, 11}, { 31, 23, 11}, { 39, 27, 15}, +{ 47, 35, 19}, { 55, 43, 23}, { 63, 47, 23}, { 75, 55, 27}, +{ 83, 59, 27}, { 91, 67, 31}, { 99, 75, 31}, {107, 83, 31}, +{115, 87, 31}, {123, 95, 35}, {131, 103, 35}, {143, 111, 35}, +{ 11, 11, 15}, { 19, 19, 27}, { 27, 27, 39}, { 39, 39, 51}, +{ 47, 47, 63}, { 55, 55, 75}, { 63, 63, 87}, { 71, 71, 103}, +{ 79, 79, 115}, { 91, 91, 127}, { 99, 99, 139}, {107, 107, 151}, +{115, 115, 163}, {123, 123, 175}, {131, 131, 187}, {139, 139, 203}, +{ 0, 0, 0}, { 7, 7, 0}, { 11, 11, 0}, { 19, 19, 0}, +{ 27, 27, 0}, { 35, 35, 0}, { 43, 43, 7}, { 47, 47, 7}, +{ 55, 55, 7}, { 63, 63, 7}, { 71, 71, 7}, { 75, 75, 11}, +{ 83, 83, 11}, { 91, 91, 11}, { 99, 99, 11}, {107, 107, 15}, +{ 7, 0, 0}, { 15, 0, 0}, { 23, 0, 0}, { 31, 0, 0}, +{ 39, 0, 0}, { 47, 0, 0}, { 55, 0, 0}, { 63, 0, 0}, +{ 71, 0, 0}, { 79, 0, 0}, { 87, 0, 0}, { 95, 0, 0}, +{103, 0, 0}, {111, 0, 0}, {119, 0, 0}, {127, 0, 0}, +{ 19, 19, 0}, { 27, 27, 0}, { 35, 35, 0}, { 47, 43, 0}, +{ 55, 47, 0}, { 67, 55, 0}, { 75, 59, 7}, { 87, 67, 7}, +{ 95, 71, 7}, {107, 75, 11}, {119, 83, 15}, {131, 87, 19}, +{139, 91, 19}, {151, 95, 27}, {163, 99, 31}, {175, 103, 35}, +{ 35, 19, 7}, { 47, 23, 11}, { 59, 31, 15}, { 75, 35, 19}, +{ 87, 43, 23}, { 99, 47, 31}, {115, 55, 35}, {127, 59, 43}, +{143, 67, 51}, {159, 79, 51}, {175, 99, 47}, {191, 119, 47}, +{207, 143, 43}, {223, 171, 39}, {239, 203, 31}, {255, 243, 27}, +{ 11, 7, 0}, { 27, 19, 0}, { 43, 35, 15}, { 55, 43, 19}, +{ 71, 51, 27}, { 83, 55, 35}, { 99, 63, 43}, {111, 71, 51}, +{127, 83, 63}, {139, 95, 71}, {155, 107, 83}, {167, 123, 95}, +{183, 135, 107}, {195, 147, 123}, {211, 163, 139}, {227, 179, 151}, +{171, 139, 163}, {159, 127, 151}, {147, 115, 135}, {139, 103, 123}, +{127, 91, 111}, {119, 83, 99}, {107, 75, 87}, { 95, 63, 75}, +{ 87, 55, 67}, { 75, 47, 55}, { 67, 39, 47}, { 55, 31, 35}, +{ 43, 23, 27}, { 35, 19, 19}, { 23, 11, 11}, { 15, 7, 7}, +{187, 115, 159}, {175, 107, 143}, {163, 95, 131}, {151, 87, 119}, +{139, 79, 107}, {127, 75, 95}, {115, 67, 83}, {107, 59, 75}, +{ 95, 51, 63}, { 83, 43, 55}, { 71, 35, 43}, { 59, 31, 35}, +{ 47, 23, 27}, { 35, 19, 19}, { 23, 11, 11}, { 15, 7, 7}, +{219, 195, 187}, {203, 179, 167}, {191, 163, 155}, {175, 151, 139}, +{163, 135, 123}, {151, 123, 111}, {135, 111, 95}, {123, 99, 83}, +{107, 87, 71}, { 95, 75, 59}, { 83, 63, 51}, { 67, 51, 39}, +{ 55, 43, 31}, { 39, 31, 23}, { 27, 19, 15}, { 15, 11, 7}, +{111, 131, 123}, {103, 123, 111}, { 95, 115, 103}, { 87, 107, 95}, +{ 79, 99, 87}, { 71, 91, 79}, { 63, 83, 71}, { 55, 75, 63}, +{ 47, 67, 55}, { 43, 59, 47}, { 35, 51, 39}, { 31, 43, 31}, +{ 23, 35, 23}, { 15, 27, 19}, { 11, 19, 11}, { 7, 11, 7}, +{255, 243, 27}, {239, 223, 23}, {219, 203, 19}, {203, 183, 15}, +{187, 167, 15}, {171, 151, 11}, {155, 131, 7}, {139, 115, 7}, +{123, 99, 7}, {107, 83, 0}, { 91, 71, 0}, { 75, 55, 0}, +{ 59, 43, 0}, { 43, 31, 0}, { 27, 15, 0}, { 11, 7, 0}, +{ 0, 0, 255}, { 11, 11, 239}, { 19, 19, 223}, { 27, 27, 207}, +{ 35, 35, 191}, { 43, 43, 175}, { 47, 47, 159}, { 47, 47, 143}, +{ 47, 47, 127}, { 47, 47, 111}, { 47, 47, 95}, { 43, 43, 79}, +{ 35, 35, 63}, { 27, 27, 47}, { 19, 19, 31}, { 11, 11, 15}, +{ 43, 0, 0}, { 59, 0, 0}, { 75, 7, 0}, { 95, 7, 0}, +{111, 15, 0}, {127, 23, 7}, {147, 31, 7}, {163, 39, 11}, +{183, 51, 15}, {195, 75, 27}, {207, 99, 43}, {219, 127, 59}, +{227, 151, 79}, {231, 171, 95}, {239, 191, 119}, {247, 211, 139}, +{167, 123, 59}, {183, 155, 55}, {199, 195, 55}, {231, 227, 87}, +{127, 191, 255}, {171, 231, 255}, {215, 255, 255}, {103, 0, 0}, +{139, 0, 0}, {179, 0, 0}, {215, 0, 0}, {255, 0, 0}, +{255, 243, 147}, {255, 247, 199}, {255, 255, 255}, {159, 91, 83} }; + + +#endif // !! AI_MDL_DEFAULTLMP_H_INC diff --git a/libs/assimp/code/AssetLib/MDL/MDLFileData.h b/libs/assimp/code/AssetLib/MDL/MDLFileData.h new file mode 100644 index 0000000..7ec2afe --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/MDLFileData.h @@ -0,0 +1,945 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MDLFileData.h + * @brief Definition of in-memory structures for the MDL file format. + * + * The specification has been taken from various sources on the internet. + * - http://tfc.duke.free.fr/coding/mdl-specs-en.html + * - Conitec's MED SDK + * - Many quite long HEX-editor sessions + */ + +#ifndef AI_MDLFILEHELPER_H_INC +#define AI_MDLFILEHELPER_H_INC + +#include <assimp/anim.h> +#include <assimp/mesh.h> +#include <assimp/Compiler/pushpack1.h> +#include <assimp/ByteSwapper.h> +#include <stdint.h> +#include <vector> + +struct aiMaterial; + +namespace Assimp { +namespace MDL { + +// ------------------------------------------------------------------------------------- +// to make it easier for us, we test the magic word against both "endianesses" + +// magic bytes used in Quake 1 MDL meshes +#define AI_MDL_MAGIC_NUMBER_BE AI_MAKE_MAGIC("IDPO") +#define AI_MDL_MAGIC_NUMBER_LE AI_MAKE_MAGIC("OPDI") + +// magic bytes used in GameStudio A<very low> MDL meshes +#define AI_MDL_MAGIC_NUMBER_BE_GS3 AI_MAKE_MAGIC("MDL2") +#define AI_MDL_MAGIC_NUMBER_LE_GS3 AI_MAKE_MAGIC("2LDM") + +// magic bytes used in GameStudio A4 MDL meshes +#define AI_MDL_MAGIC_NUMBER_BE_GS4 AI_MAKE_MAGIC("MDL3") +#define AI_MDL_MAGIC_NUMBER_LE_GS4 AI_MAKE_MAGIC("3LDM") + +// magic bytes used in GameStudio A5+ MDL meshes +#define AI_MDL_MAGIC_NUMBER_BE_GS5a AI_MAKE_MAGIC("MDL4") +#define AI_MDL_MAGIC_NUMBER_LE_GS5a AI_MAKE_MAGIC("4LDM") +#define AI_MDL_MAGIC_NUMBER_BE_GS5b AI_MAKE_MAGIC("MDL5") +#define AI_MDL_MAGIC_NUMBER_LE_GS5b AI_MAKE_MAGIC("5LDM") + +// magic bytes used in GameStudio A7+ MDL meshes +#define AI_MDL_MAGIC_NUMBER_BE_GS7 AI_MAKE_MAGIC("MDL7") +#define AI_MDL_MAGIC_NUMBER_LE_GS7 AI_MAKE_MAGIC("7LDM") + +// common limitations for Quake1 meshes. The loader does not check them, +// (however it warns) but models should not exceed these limits. +#if (!defined AI_MDL_VERSION) +# define AI_MDL_VERSION 6 +#endif +#if (!defined AI_MDL_MAX_FRAMES) +# define AI_MDL_MAX_FRAMES 256 +#endif +#if (!defined AI_MDL_MAX_UVS) +# define AI_MDL_MAX_UVS 1024 +#endif +#if (!defined AI_MDL_MAX_VERTS) +# define AI_MDL_MAX_VERTS 1024 +#endif +#if (!defined AI_MDL_MAX_TRIANGLES) +# define AI_MDL_MAX_TRIANGLES 2048 +#endif + +// material key that is set for dummy materials that are +// just referencing another material +#if (!defined AI_MDL7_REFERRER_MATERIAL) +# define AI_MDL7_REFERRER_MATERIAL "&&&referrer&&&",0,0 +#endif + +// ------------------------------------------------------------------------------------- +/** \struct Header + * \brief Data structure for the MDL main header + */ +struct Header { + //! magic number: "IDPO" + uint32_t ident; + + //! version number: 6 + int32_t version; + + //! scale factors for each axis + ai_real scale[3]; + + //! translation factors for each axis + ai_real translate[3]; + + //! bounding radius of the mesh + float boundingradius; + + //! Position of the viewer's exe. Ignored + ai_real vEyePos[3]; + + //! Number of textures + int32_t num_skins; + + //! Texture width in pixels + int32_t skinwidth; + + //! Texture height in pixels + int32_t skinheight; + + //! Number of vertices contained in the file + int32_t num_verts; + + //! Number of triangles contained in the file + int32_t num_tris; + + //! Number of frames contained in the file + int32_t num_frames; + + //! 0 = synchron, 1 = random . Ignored + //! (MDLn formats: number of texture coordinates) + int32_t synctype; + + //! State flag + int32_t flags; + + //! Could be the total size of the file (and not a float) + float size; +} PACK_STRUCT; + + +// ------------------------------------------------------------------------------------- +/** \struct Header_MDL7 + * \brief Data structure for the MDL 7 main header + */ +struct Header_MDL7 { + //! magic number: "MDL7" + char ident[4]; + + //! Version number. Ignored + int32_t version; + + //! Number of bones in file + uint32_t bones_num; + + //! Number of groups in file + uint32_t groups_num; + + //! Size of data in the file + uint32_t data_size; + + //! Ignored. Used to store entity specific information + int32_t entlump_size; + + //! Ignored. Used to store MED related data + int32_t medlump_size; + + //! Size of the Bone_MDL7 data structure used in the file + uint16_t bone_stc_size; + + //! Size of the Skin_MDL 7 data structure used in the file + uint16_t skin_stc_size; + + //! Size of a single color (e.g. in a material) + uint16_t colorvalue_stc_size; + + //! Size of the Material_MDL7 data structure used in the file + uint16_t material_stc_size; + + //! Size of a texture coordinate set in the file + uint16_t skinpoint_stc_size; + + //! Size of a triangle in the file + uint16_t triangle_stc_size; + + //! Size of a normal vertex in the file + uint16_t mainvertex_stc_size; + + //! Size of a per-frame animated vertex in the file + //! (this is not supported) + uint16_t framevertex_stc_size; + + //! Size of a bone animation matrix + uint16_t bonetrans_stc_size; + + //! Size of the Frame_MDL7 data structure used in the file + uint16_t frame_stc_size; +} PACK_STRUCT; + + +// ------------------------------------------------------------------------------------- +/** \struct Bone_MDL7 + * \brief Data structure for a bone in a MDL7 file + */ +struct Bone_MDL7 { + //! Index of the parent bone of *this* bone. 0xffff means: + //! "hey, I have no parent, I'm an orphan" + uint16_t parent_index; + uint8_t _unused_[2]; + + //! Relative position of the bone (relative to the + //! parent bone) + float x,y,z; + + //! Optional name of the bone + char name[1 /* DUMMY SIZE */]; +} PACK_STRUCT; + +#if (!defined AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_20_CHARS) +# define AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_20_CHARS (16 + 20) +#endif + +#if (!defined AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_32_CHARS) +# define AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_32_CHARS (16 + 32) +#endif + +#if (!defined AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE) +# define AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE (16) +#endif + +#if (!defined AI_MDL7_MAX_GROUPNAMESIZE) +# define AI_MDL7_MAX_GROUPNAMESIZE 16 +#endif // ! AI_MDL7_MAX_GROUPNAMESIZE + +// ------------------------------------------------------------------------------------- +/** \struct Group_MDL7 + * \brief Group in a MDL7 file + */ +struct Group_MDL7 { + //! = '1' -> triangle based Mesh + unsigned char typ; + + int8_t deformers; + int8_t max_weights; + int8_t _unused_; + + //! size of data for this group in bytes ( MD7_GROUP stc. included). + int32_t groupdata_size; + char name[AI_MDL7_MAX_GROUPNAMESIZE]; + + //! Number of skins + int32_t numskins; + + //! Number of texture coordinates + int32_t num_stpts; + + //! Number of triangles + int32_t numtris; + + //! Number of vertices + int32_t numverts; + + //! Number of frames + int32_t numframes; +} PACK_STRUCT; + +#define AI_MDL7_SKINTYPE_MIPFLAG 0x08 +#define AI_MDL7_SKINTYPE_MATERIAL 0x10 +#define AI_MDL7_SKINTYPE_MATERIAL_ASCDEF 0x20 +#define AI_MDL7_SKINTYPE_RGBFLAG 0x80 + +#if (!defined AI_MDL7_MAX_BONENAMESIZE) +# define AI_MDL7_MAX_BONENAMESIZE 20 +#endif // !! AI_MDL7_MAX_BONENAMESIZE + +// ------------------------------------------------------------------------------------- +/** \struct Deformer_MDL7 + * \brief Deformer in a MDL7 file + */ +struct Deformer_MDL7 { + int8_t deformer_version; // 0 + int8_t deformer_typ; // 0 - bones + int8_t _unused_[2]; + int32_t group_index; + int32_t elements; + int32_t deformerdata_size; +} PACK_STRUCT; + + +// ------------------------------------------------------------------------------------- +/** \struct DeformerElement_MDL7 + * \brief Deformer element in a MDL7 file + */ +struct DeformerElement_MDL7 { + //! bei deformer_typ==0 (==bones) element_index == bone index + int32_t element_index; + char element_name[AI_MDL7_MAX_BONENAMESIZE]; + int32_t weights; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct DeformerWeight_MDL7 + * \brief Deformer weight in a MDL7 file + */ +struct DeformerWeight_MDL7 { + //! for deformer_typ==0 (==bones) index == vertex index + int32_t index; + float weight; +} PACK_STRUCT; + +// don't know why this was in the original headers ... +typedef int32_t MD7_MATERIAL_ASCDEFSIZE; + +// ------------------------------------------------------------------------------------- +/** \struct ColorValue_MDL7 + * \brief Data structure for a color value in a MDL7 file + */ +struct ColorValue_MDL7 { + float r,g,b,a; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct Material_MDL7 + * \brief Data structure for a Material in a MDL7 file + */ +struct Material_MDL7 { + //! Diffuse base color of the material + ColorValue_MDL7 Diffuse; + + //! Ambient base color of the material + ColorValue_MDL7 Ambient; + + //! Specular base color of the material + ColorValue_MDL7 Specular; + + //! Emissive base color of the material + ColorValue_MDL7 Emissive; + + //! Phong power + float Power; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct Skin + * \brief Skin data structure #1 - used by Quake1, MDL2, MDL3 and MDL4 + */ +struct Skin { + //! 0 = single (Skin), 1 = group (GroupSkin) + //! For MDL3-5: Defines the type of the skin and there + //! fore the size of the data to skip: + //------------------------------------------------------- + //! 2 for 565 RGB, + //! 3 for 4444 ARGB, + //! 10 for 565 mipmapped, + //! 11 for 4444 mipmapped (bpp = 2), + //! 12 for 888 RGB mipmapped (bpp = 3), + //! 13 for 8888 ARGB mipmapped (bpp = 4) + //------------------------------------------------------- + int32_t group; + + //! Texture data + uint8_t *data; +} PACK_STRUCT; + + +// ------------------------------------------------------------------------------------- +/** \struct Skin + * \brief Skin data structure #2 - used by MDL5, MDL6 and MDL7 + * \see Skin + */ +struct Skin_MDL5 { + int32_t size, width, height; + uint8_t *data; +} PACK_STRUCT; + +// maximum length of texture file name +#if (!defined AI_MDL7_MAX_TEXNAMESIZE) +# define AI_MDL7_MAX_TEXNAMESIZE 0x10 +#endif + +// --------------------------------------------------------------------------- +/** \struct Skin_MDL7 + * \brief Skin data structure #3 - used by MDL7 and HMP7 + */ +struct Skin_MDL7 { + uint8_t typ; + int8_t _unused_[3]; + int32_t width; + int32_t height; + char texture_name[AI_MDL7_MAX_TEXNAMESIZE]; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct RGB565 + * \brief Data structure for a RGB565 pixel in a texture + */ +struct RGB565 { + uint16_t r : 5; + uint16_t g : 6; + uint16_t b : 5; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct ARGB4 + * \brief Data structure for a ARGB4444 pixel in a texture + */ +struct ARGB4 { + uint16_t a : 4; + uint16_t r : 4; + uint16_t g : 4; + uint16_t b : 4; +} /*PACK_STRUCT*/; + +// ------------------------------------------------------------------------------------- +/** \struct GroupSkin + * \brief Skin data structure #2 (group of pictures) + */ +struct GroupSkin { + //! 0 = single (Skin), 1 = group (GroupSkin) + int32_t group; + + //! Number of images + int32_t nb; + + //! Time for each image + float *time; + + //! Data of each image + uint8_t **data; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct TexCoord + * \brief Texture coordinate data structure used by the Quake1 MDL format + */ +struct TexCoord { + //! Is the vertex on the noundary between front and back piece? + int32_t onseam; + + //! Texture coordinate in the tx direction + int32_t s; + + //! Texture coordinate in the ty direction + int32_t t; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct TexCoord_MDL3 + * \brief Data structure for an UV coordinate in the 3DGS MDL3 format + */ +struct TexCoord_MDL3 { + //! position, horizontally in range 0..skinwidth-1 + int16_t u; + + //! position, vertically in range 0..skinheight-1 + int16_t v; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct TexCoord_MDL7 + * \brief Data structure for an UV coordinate in the 3DGS MDL7 format + */ +struct TexCoord_MDL7 { + //! position, horizontally in range 0..1 + float u; + + //! position, vertically in range 0..1 + float v; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct SkinSet_MDL7 + * \brief Skin set data structure for the 3DGS MDL7 format + * MDL7 references UV coordinates per face via an index list. + * This allows the use of multiple skins per face with just one + * UV coordinate set. + */ +struct SkinSet_MDL7 +{ + //! Index into the UV coordinate list + uint16_t st_index[3]; // size 6 + + //! Material index + int32_t material; // size 4 +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct Triangle + * \brief Triangle data structure for the Quake1 MDL format + */ +struct Triangle +{ + //! 0 = backface, 1 = frontface + int32_t facesfront; + + //! Vertex indices + int32_t vertex[3]; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct Triangle_MDL3 + * \brief Triangle data structure for the 3DGS MDL3 format + */ +struct Triangle_MDL3 +{ + //! Index of 3 3D vertices in range 0..numverts + uint16_t index_xyz[3]; + + //! Index of 3 skin vertices in range 0..numskinverts + uint16_t index_uv[3]; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct Triangle_MDL7 + * \brief Triangle data structure for the 3DGS MDL7 format + */ +struct Triangle_MDL7 +{ + //! Vertex indices + uint16_t v_index[3]; // size 6 + + //! Two skinsets. The second will be used for multi-texturing + SkinSet_MDL7 skinsets[2]; +} PACK_STRUCT; + +#if (!defined AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV) +# define AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV (6+sizeof(SkinSet_MDL7)-sizeof(uint32_t)) +#endif +#if (!defined AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV_WITH_MATINDEX) +# define AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV_WITH_MATINDEX (6+sizeof(SkinSet_MDL7)) +#endif +#if (!defined AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV) +# define AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV (6+2*sizeof(SkinSet_MDL7)) +#endif + +// Helper constants for Triangle::facesfront +#if (!defined AI_MDL_BACKFACE) +# define AI_MDL_BACKFACE 0x0 +#endif +#if (!defined AI_MDL_FRONTFACE) +# define AI_MDL_FRONTFACE 0x1 +#endif + +// ------------------------------------------------------------------------------------- +/** \struct Vertex + * \brief Vertex data structure + */ +struct Vertex +{ + uint8_t v[3]; + uint8_t normalIndex; +} PACK_STRUCT; + + +// ------------------------------------------------------------------------------------- +struct Vertex_MDL4 +{ + uint16_t v[3]; + uint8_t normalIndex; + uint8_t unused; +} PACK_STRUCT; + +#define AI_MDL7_FRAMEVERTEX120503_STCSIZE 16 +#define AI_MDL7_FRAMEVERTEX030305_STCSIZE 26 + +// ------------------------------------------------------------------------------------- +/** \struct Vertex_MDL7 + * \brief Vertex data structure used in MDL7 files + */ +struct Vertex_MDL7 +{ + float x,y,z; + uint16_t vertindex; // = bone index + union { + uint8_t norm162index; + float norm[3]; + }; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct BoneTransform_MDL7 + * \brief bone transformation matrix structure used in MDL7 files + */ +struct BoneTransform_MDL7 +{ + //! 4*3 + float m [4*4]; + + //! the index of this vertex, 0.. header::bones_num - 1 + uint16_t bone_index; + + //! I HATE 3DGS AND THE SILLY DEVELOPER WHO DESIGNED + //! THIS STUPID FILE FORMAT! + int8_t _unused_[2]; +} PACK_STRUCT; + + +#define AI_MDL7_MAX_FRAMENAMESIZE 16 + +// ------------------------------------------------------------------------------------- +/** \struct Frame_MDL7 + * \brief Frame data structure used by MDL7 files + */ +struct Frame_MDL7 +{ + char frame_name[AI_MDL7_MAX_FRAMENAMESIZE]; + uint32_t vertices_count; + uint32_t transmatrix_count; +}; + + +// ------------------------------------------------------------------------------------- +/** \struct SimpleFrame + * \brief Data structure for a simple frame + */ +struct SimpleFrame +{ + //! Minimum vertex of the bounding box + Vertex bboxmin; + + //! Maximum vertex of the bounding box + Vertex bboxmax; + + //! Name of the frame + char name[16]; + + //! Vertex list of the frame + Vertex *verts; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct Frame + * \brief Model frame data structure + */ +struct Frame +{ + //! 0 = simple frame, !0 = group frame + int32_t type; + + //! Frame data + SimpleFrame frame; +} PACK_STRUCT; + + +// ------------------------------------------------------------------------------------- +struct SimpleFrame_MDLn_SP +{ + //! Minimum vertex of the bounding box + Vertex_MDL4 bboxmin; + + //! Maximum vertex of the bounding box + Vertex_MDL4 bboxmax; + + //! Name of the frame + char name[16]; + + //! Vertex list of the frame + Vertex_MDL4 *verts; +} PACK_STRUCT; + +// ------------------------------------------------------------------------------------- +/** \struct GroupFrame + * \brief Data structure for a group of frames + */ +struct GroupFrame +{ + //! 0 = simple frame, !0 = group frame + int32_t type; + + int32_t numframes; + + //! Minimum vertex for all single frames + Vertex min; + + //! Maximum vertex for all single frames + Vertex max; + + //! List of times for all single frames + float *times; + + //! List of single frames + SimpleFrame *frames; +} PACK_STRUCT; + +#include <assimp/Compiler/poppack1.h> + +// ------------------------------------------------------------------------------------- +/** \struct IntFace_MDL7 + * \brief Internal data structure to temporarily represent a face + */ +struct IntFace_MDL7 { + // provide a constructor for our own convenience + IntFace_MDL7() AI_NO_EXCEPT { + ::memset( mIndices, 0, sizeof(uint32_t) *3); + ::memset( iMatIndex, 0, sizeof( unsigned int) *2); + } + + //! Vertex indices + uint32_t mIndices[3]; + + //! Material index (maximally two channels, which are joined later) + unsigned int iMatIndex[2]; +}; + +// ------------------------------------------------------------------------------------- +/** \struct IntMaterial_MDL7 + * \brief Internal data structure to temporarily represent a material + * which has been created from two single materials along with the + * original material indices. + */ +struct IntMaterial_MDL7 { + // provide a constructor for our own convenience + IntMaterial_MDL7() AI_NO_EXCEPT + : pcMat( nullptr ) { + ::memset( iOldMatIndices, 0, sizeof(unsigned int) *2); + } + + //! Material instance + aiMaterial* pcMat; + + //! Old material indices + unsigned int iOldMatIndices[2]; +}; + +// ------------------------------------------------------------------------------------- +/** \struct IntBone_MDL7 + * \brief Internal data structure to represent a bone in a MDL7 file with + * all of its animation channels assigned to it. + */ +struct IntBone_MDL7 : aiBone +{ + //! Default constructor + IntBone_MDL7() AI_NO_EXCEPT : iParent (0xffff) + { + pkeyPositions.reserve(30); + pkeyScalings.reserve(30); + pkeyRotations.reserve(30); + } + + //! Parent bone of the bone + uint64_t iParent; + + //! Relative position of the bone + aiVector3D vPosition; + + //! Array of position keys + std::vector<aiVectorKey> pkeyPositions; + + //! Array of scaling keys + std::vector<aiVectorKey> pkeyScalings; + + //! Array of rotation keys + std::vector<aiQuatKey> pkeyRotations; +}; + +// ------------------------------------------------------------------------------------- +//! Describes a MDL7 frame +struct IntFrameInfo_MDL7 +{ + //! Construction from an existing frame header + IntFrameInfo_MDL7(BE_NCONST MDL::Frame_MDL7* _pcFrame,unsigned int _iIndex) + : iIndex(_iIndex) + , pcFrame(_pcFrame) + {} + + //! Index of the frame + unsigned int iIndex; + + //! Points to the header of the frame + BE_NCONST MDL::Frame_MDL7* pcFrame; +}; + +// ------------------------------------------------------------------------------------- +//! Describes a MDL7 mesh group +struct IntGroupInfo_MDL7 +{ + //! Default constructor + IntGroupInfo_MDL7() AI_NO_EXCEPT + : iIndex(0) + , pcGroup(nullptr) + , pcGroupUVs(nullptr) + , pcGroupTris(nullptr) + , pcGroupVerts(nullptr) + {} + + //! Construction from an existing group header + IntGroupInfo_MDL7(BE_NCONST MDL::Group_MDL7* _pcGroup, unsigned int _iIndex) + : iIndex(_iIndex) + , pcGroup(_pcGroup) + , pcGroupUVs() + , pcGroupTris() + , pcGroupVerts() + {} + + //! Index of the group + unsigned int iIndex; + + //! Points to the header of the group + BE_NCONST MDL::Group_MDL7* pcGroup; + + //! Points to the beginning of the uv coordinate section + BE_NCONST MDL::TexCoord_MDL7* pcGroupUVs; + + //! Points to the beginning of the triangle section + MDL::Triangle_MDL7* pcGroupTris; + + //! Points to the beginning of the vertex section + BE_NCONST MDL::Vertex_MDL7* pcGroupVerts; +}; + +// ------------------------------------------------------------------------------------- +//! Holds the data that belongs to a MDL7 mesh group +struct IntGroupData_MDL7 +{ + IntGroupData_MDL7() AI_NO_EXCEPT + : bNeed2UV(false) + {} + + //! Array of faces that belong to the group + std::vector<MDL::IntFace_MDL7> pcFaces; + + //! Array of vertex positions + std::vector<aiVector3D> vPositions; + + //! Array of vertex normals + std::vector<aiVector3D> vNormals; + + //! Array of bones indices + std::vector<unsigned int> aiBones; + + //! First UV coordinate set + std::vector<aiVector3D> vTextureCoords1; + + //! Optional second UV coordinate set + std::vector<aiVector3D> vTextureCoords2; + + //! Specifies whether there are two texture + //! coordinate sets required + bool bNeed2UV; +}; + +// ------------------------------------------------------------------------------------- +//! Holds data from an MDL7 file that is shared by all mesh groups +struct IntSharedData_MDL7 { + //! Default constructor + IntSharedData_MDL7() AI_NO_EXCEPT + : apcOutBones(), + iNum() + { + abNeedMaterials.reserve(10); + } + + //! Destruction: properly delete all allocated resources + ~IntSharedData_MDL7() + { + // kill all bones + if (this->apcOutBones) + { + for (unsigned int m = 0; m < iNum;++m) + delete this->apcOutBones[m]; + delete[] this->apcOutBones; + } + } + + //! Specifies which materials are used + std::vector<bool> abNeedMaterials; + + //! List of all materials + std::vector<aiMaterial*> pcMats; + + //! List of all bones + IntBone_MDL7** apcOutBones; + + //! number of bones + unsigned int iNum; +}; + +// ------------------------------------------------------------------------------------- +//! Contains input data for GenerateOutputMeshes_3DGS_MDL7 +struct IntSplitGroupData_MDL7 +{ + //! Construction from a given shared data set + IntSplitGroupData_MDL7(IntSharedData_MDL7& _shared, + std::vector<aiMesh*>& _avOutList) + + : aiSplit(), shared(_shared), avOutList(_avOutList) + { + } + + //! Destruction: properly delete all allocated resources + ~IntSplitGroupData_MDL7() + { + // kill all face lists + if(this->aiSplit) + { + for (unsigned int m = 0; m < shared.pcMats.size();++m) + delete this->aiSplit[m]; + delete[] this->aiSplit; + } + } + + //! Contains a list of all faces per material + std::vector<unsigned int>** aiSplit; + + //! Shared data for all groups of the model + IntSharedData_MDL7& shared; + + //! List of meshes + std::vector<aiMesh*>& avOutList; +}; + + +} +} // end namespaces + +#endif // !! AI_MDLFILEHELPER_H_INC diff --git a/libs/assimp/code/AssetLib/MDL/MDLLoader.cpp b/libs/assimp/code/AssetLib/MDL/MDLLoader.cpp new file mode 100644 index 0000000..1e90c8e --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/MDLLoader.cpp @@ -0,0 +1,1976 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MDLLoader.cpp + * @brief Implementation of the main parts of the MDL importer class + * *TODO* Cleanup and further testing of some parts necessary + */ + +// internal headers + +#ifndef ASSIMP_BUILD_NO_MDL_IMPORTER + +#include "AssetLib/MDL/MDLLoader.h" +#include "AssetLib/MD2/MD2FileData.h" +#include "AssetLib/MDL/HalfLife/HL1MDLLoader.h" +#include "AssetLib/MDL/MDLDefaultColorMap.h" + +#include <assimp/StringUtils.h> +#include <assimp/importerdesc.h> +#include <assimp/qnan.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/Importer.hpp> + +#include <memory> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Quake Mesh / 3D GameStudio Mesh Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 7, + 0, + "mdl" +}; + +// ------------------------------------------------------------------------------------------------ +// Ugly stuff ... nevermind +#define _AI_MDL7_ACCESS(_data, _index, _limit, _type) \ + (*((const _type *)(((const char *)_data) + _index * _limit))) + +#define _AI_MDL7_ACCESS_PTR(_data, _index, _limit, _type) \ + ((BE_NCONST _type *)(((const char *)_data) + _index * _limit)) + +#define _AI_MDL7_ACCESS_VERT(_data, _index, _limit) \ + _AI_MDL7_ACCESS(_data, _index, _limit, MDL::Vertex_MDL7) + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +MDLImporter::MDLImporter() : + configFrameID(), mBuffer(), iGSFileVersion(), mIOHandler(nullptr), pScene(), iFileSize() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +MDLImporter::~MDLImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool MDLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const uint32_t tokens[] = { + AI_MDL_MAGIC_NUMBER_LE_HL2a, + AI_MDL_MAGIC_NUMBER_LE_HL2b, + AI_MDL_MAGIC_NUMBER_LE_GS7, + AI_MDL_MAGIC_NUMBER_LE_GS5b, + AI_MDL_MAGIC_NUMBER_LE_GS5a, + AI_MDL_MAGIC_NUMBER_LE_GS4, + AI_MDL_MAGIC_NUMBER_LE_GS3, + AI_MDL_MAGIC_NUMBER_LE + }; + return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties +void MDLImporter::SetupProperties(const Importer *pImp) { + configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MDL_KEYFRAME, -1); + + // The + // AI_CONFIG_IMPORT_MDL_KEYFRAME option overrides the + // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option. + if (static_cast<unsigned int>(-1) == configFrameID) { + configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME, 0); + } + + // AI_CONFIG_IMPORT_MDL_COLORMAP - palette file + configPalette = pImp->GetPropertyString(AI_CONFIG_IMPORT_MDL_COLORMAP, "colormap.lmp"); + + // Read configuration specific to MDL (Half-Life 1). + mHL1ImportSettings.read_animations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS, true); + if (mHL1ImportSettings.read_animations) { + mHL1ImportSettings.read_animation_events = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATION_EVENTS, true); + mHL1ImportSettings.read_blend_controllers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_BLEND_CONTROLLERS, true); + mHL1ImportSettings.read_sequence_transitions = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_SEQUENCE_TRANSITIONS, true); + } + mHL1ImportSettings.read_attachments = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ATTACHMENTS, true); + mHL1ImportSettings.read_bone_controllers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_BONE_CONTROLLERS, true); + mHL1ImportSettings.read_hitboxes = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_HITBOXES, true); + mHL1ImportSettings.read_misc_global_info = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_MISC_GLOBAL_INFO, true); +} + +// ------------------------------------------------------------------------------------------------ +// Get a list of all supported extensions +const aiImporterDesc *MDLImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void MDLImporter::InternReadFile(const std::string &pFile, + aiScene *_pScene, IOSystem *pIOHandler) { + pScene = _pScene; + mIOHandler = pIOHandler; + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile)); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open MDL file ", pFile, "."); + } + + // This should work for all other types of MDL files, too ... + // the HL1 sequence group header is one of the smallest, afaik + iFileSize = (unsigned int)file->FileSize(); + if (iFileSize < sizeof(MDL::HalfLife::SequenceHeader_HL1)) { + throw DeadlyImportError("MDL File is too small."); + } + + // delete the file buffer and cleanup. + auto DeleteBufferAndCleanup = [&]() { + if (mBuffer) { + delete[] mBuffer; + mBuffer = nullptr; + } + AI_DEBUG_INVALIDATE_PTR(mIOHandler); + AI_DEBUG_INVALIDATE_PTR(pScene); + }; + + try { + // Allocate storage and copy the contents of the file to a memory buffer + mBuffer = new unsigned char[iFileSize + 1]; + file->Read((void *)mBuffer, 1, iFileSize); + + // Append a binary zero to the end of the buffer. + // this is just for safety that string parsing routines + // find the end of the buffer ... + mBuffer[iFileSize] = '\0'; + const uint32_t iMagicWord = *((uint32_t *)mBuffer); + + // Determine the file subtype and call the appropriate member function + bool is_half_life = false; + + // Original Quake1 format + if (AI_MDL_MAGIC_NUMBER_BE == iMagicWord || AI_MDL_MAGIC_NUMBER_LE == iMagicWord) { + ASSIMP_LOG_DEBUG("MDL subtype: Quake 1, magic word is IDPO"); + iGSFileVersion = 0; + InternReadFile_Quake1(); + } + // GameStudio A<old> MDL2 format - used by some test models that come with 3DGS + else if (AI_MDL_MAGIC_NUMBER_BE_GS3 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS3 == iMagicWord) { + ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A2, magic word is MDL2"); + iGSFileVersion = 2; + InternReadFile_Quake1(); + } + // GameStudio A4 MDL3 format + else if (AI_MDL_MAGIC_NUMBER_BE_GS4 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS4 == iMagicWord) { + ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A4, magic word is MDL3"); + iGSFileVersion = 3; + InternReadFile_3DGS_MDL345(); + } + // GameStudio A5+ MDL4 format + else if (AI_MDL_MAGIC_NUMBER_BE_GS5a == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS5a == iMagicWord) { + ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A4, magic word is MDL4"); + iGSFileVersion = 4; + InternReadFile_3DGS_MDL345(); + } + // GameStudio A5+ MDL5 format + else if (AI_MDL_MAGIC_NUMBER_BE_GS5b == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS5b == iMagicWord) { + ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A5, magic word is MDL5"); + iGSFileVersion = 5; + InternReadFile_3DGS_MDL345(); + } + // GameStudio A7 MDL7 format + else if (AI_MDL_MAGIC_NUMBER_BE_GS7 == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_GS7 == iMagicWord) { + ASSIMP_LOG_DEBUG("MDL subtype: 3D GameStudio A7, magic word is MDL7"); + iGSFileVersion = 7; + InternReadFile_3DGS_MDL7(); + } + // IDST/IDSQ Format (CS:S/HL^2, etc ...) + else if (AI_MDL_MAGIC_NUMBER_BE_HL2a == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2a == iMagicWord || + AI_MDL_MAGIC_NUMBER_BE_HL2b == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2b == iMagicWord) { + iGSFileVersion = 0; + is_half_life = true; + + HalfLife::HalfLifeMDLBaseHeader *pHeader = (HalfLife::HalfLifeMDLBaseHeader *)mBuffer; + if (pHeader->version == AI_MDL_HL1_VERSION) { + ASSIMP_LOG_DEBUG("MDL subtype: Half-Life 1/Goldsrc Engine, magic word is IDST/IDSQ"); + InternReadFile_HL1(pFile, iMagicWord); + } else { + ASSIMP_LOG_DEBUG("MDL subtype: Source(tm) Engine, magic word is IDST/IDSQ"); + InternReadFile_HL2(); + } + } else { + // print the magic word to the log file + throw DeadlyImportError("Unknown MDL subformat ", pFile, + ". Magic word (", ai_str_toprintable((const char *)&iMagicWord, sizeof(iMagicWord)), ") is not known"); + } + + if (is_half_life){ + // Now rotate the whole scene 90 degrees around the z and x axes to convert to internal coordinate system + pScene->mRootNode->mTransformation = aiMatrix4x4( + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + -1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f); + } + else { + // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system + pScene->mRootNode->mTransformation = aiMatrix4x4(1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, 0.f, -1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f); + } + + DeleteBufferAndCleanup(); + } catch (...) { + DeleteBufferAndCleanup(); + throw; + } +} + +// ------------------------------------------------------------------------------------------------ +// Check whether we're still inside the valid file range +void MDLImporter::SizeCheck(const void *szPos) { + if (!szPos || (const unsigned char *)szPos > this->mBuffer + this->iFileSize) { + throw DeadlyImportError("Invalid MDL file. The file is too small " + "or contains invalid data."); + } +} + +// ------------------------------------------------------------------------------------------------ +// Just for debugging purposes +void MDLImporter::SizeCheck(const void *szPos, const char *szFile, unsigned int iLine) { + ai_assert(nullptr != szFile); + if (!szPos || (const unsigned char *)szPos > mBuffer + iFileSize) { + // remove a directory if there is one + const char *szFilePtr = ::strrchr(szFile, '\\'); + if (!szFilePtr) { + szFilePtr = ::strrchr(szFile, '/'); + if (nullptr == szFilePtr) { + szFilePtr = szFile; + } + } + if (szFilePtr) { + ++szFilePtr; + } + + char szBuffer[1024]; + ::sprintf(szBuffer, "Invalid MDL file. The file is too small " + "or contains invalid data (File: %s Line: %u)", + szFilePtr, iLine); + + throw DeadlyImportError(szBuffer); + } +} + +// ------------------------------------------------------------------------------------------------ +// Validate a quake file header +void MDLImporter::ValidateHeader_Quake1(const MDL::Header *pcHeader) { + // some values may not be nullptr + if (!pcHeader->num_frames) + throw DeadlyImportError("[Quake 1 MDL] There are no frames in the file"); + + if (!pcHeader->num_verts) + throw DeadlyImportError("[Quake 1 MDL] There are no vertices in the file"); + + if (!pcHeader->num_tris) + throw DeadlyImportError("[Quake 1 MDL] There are no triangles in the file"); + + // check whether the maxima are exceeded ...however, this applies for Quake 1 MDLs only + if (!this->iGSFileVersion) { + if (pcHeader->num_verts > AI_MDL_MAX_VERTS) + ASSIMP_LOG_WARN("Quake 1 MDL model has more than AI_MDL_MAX_VERTS vertices"); + + if (pcHeader->num_tris > AI_MDL_MAX_TRIANGLES) + ASSIMP_LOG_WARN("Quake 1 MDL model has more than AI_MDL_MAX_TRIANGLES triangles"); + + if (pcHeader->num_frames > AI_MDL_MAX_FRAMES) + ASSIMP_LOG_WARN("Quake 1 MDL model has more than AI_MDL_MAX_FRAMES frames"); + + // (this does not apply for 3DGS MDLs) + if (!this->iGSFileVersion && pcHeader->version != AI_MDL_VERSION) + ASSIMP_LOG_WARN("Quake 1 MDL model has an unknown version: AI_MDL_VERSION (=6) is " + "the expected file format version"); + if (pcHeader->num_skins && (!pcHeader->skinwidth || !pcHeader->skinheight)) + ASSIMP_LOG_WARN("Skin width or height are 0"); + } +} + +#ifdef AI_BUILD_BIG_ENDIAN +// ------------------------------------------------------------------------------------------------ +void FlipQuakeHeader(BE_NCONST MDL::Header *pcHeader) { + AI_SWAP4(pcHeader->ident); + AI_SWAP4(pcHeader->version); + AI_SWAP4(pcHeader->boundingradius); + AI_SWAP4(pcHeader->flags); + AI_SWAP4(pcHeader->num_frames); + AI_SWAP4(pcHeader->num_skins); + AI_SWAP4(pcHeader->num_tris); + AI_SWAP4(pcHeader->num_verts); + for (unsigned int i = 0; i < 3; ++i) { + AI_SWAP4(pcHeader->scale[i]); + AI_SWAP4(pcHeader->translate[i]); + } + AI_SWAP4(pcHeader->size); + AI_SWAP4(pcHeader->skinheight); + AI_SWAP4(pcHeader->skinwidth); + AI_SWAP4(pcHeader->synctype); +} +#endif + +// ------------------------------------------------------------------------------------------------ +// Read a Quake 1 file +void MDLImporter::InternReadFile_Quake1() { + ai_assert(nullptr != pScene); + + BE_NCONST MDL::Header *pcHeader = (BE_NCONST MDL::Header *)this->mBuffer; + +#ifdef AI_BUILD_BIG_ENDIAN + FlipQuakeHeader(pcHeader); +#endif + + ValidateHeader_Quake1(pcHeader); + + // current cursor position in the file + const unsigned char *szCurrent = (const unsigned char *)(pcHeader + 1); + + // need to read all textures + for (unsigned int i = 0; i < (unsigned int)pcHeader->num_skins; ++i) { + union { + BE_NCONST MDL::Skin *pcSkin; + BE_NCONST MDL::GroupSkin *pcGroupSkin; + }; + if (szCurrent + sizeof(MDL::Skin) > this->mBuffer + this->iFileSize) { + throw DeadlyImportError("[Quake 1 MDL] Unexpected EOF"); + } + pcSkin = (BE_NCONST MDL::Skin *)szCurrent; + + AI_SWAP4(pcSkin->group); + + // Quake 1 group-skins + if (1 == pcSkin->group) { + AI_SWAP4(pcGroupSkin->nb); + + // need to skip multiple images + const unsigned int iNumImages = (unsigned int)pcGroupSkin->nb; + szCurrent += sizeof(uint32_t) * 2; + + if (0 != iNumImages) { + if (!i) { + // however, create only one output image (the first) + this->CreateTextureARGB8_3DGS_MDL3(szCurrent + iNumImages * sizeof(float)); + } + // go to the end of the skin section / the beginning of the next skin + szCurrent += pcHeader->skinheight * pcHeader->skinwidth + + sizeof(float) * iNumImages; + } + } else { + szCurrent += sizeof(uint32_t); + unsigned int iSkip = i ? UINT_MAX : 0; + CreateTexture_3DGS_MDL4(szCurrent, pcSkin->group, &iSkip); + szCurrent += iSkip; + } + } + // get a pointer to the texture coordinates + BE_NCONST MDL::TexCoord *pcTexCoords = (BE_NCONST MDL::TexCoord *)szCurrent; + szCurrent += sizeof(MDL::TexCoord) * pcHeader->num_verts; + + // get a pointer to the triangles + BE_NCONST MDL::Triangle *pcTriangles = (BE_NCONST MDL::Triangle *)szCurrent; + szCurrent += sizeof(MDL::Triangle) * pcHeader->num_tris; + VALIDATE_FILE_SIZE(szCurrent); + + // now get a pointer to the first frame in the file + BE_NCONST MDL::Frame *pcFrames = (BE_NCONST MDL::Frame *)szCurrent; + MDL::SimpleFrame *pcFirstFrame; + + if (0 == pcFrames->type) { + // get address of single frame + pcFirstFrame = (MDL::SimpleFrame *)&pcFrames->frame; + } else { + // get the first frame in the group + BE_NCONST MDL::GroupFrame *pcFrames2 = (BE_NCONST MDL::GroupFrame *)szCurrent; + pcFirstFrame = (MDL::SimpleFrame *)( szCurrent + sizeof(MDL::GroupFrame::type) + sizeof(MDL::GroupFrame::numframes) + + sizeof(MDL::GroupFrame::min) + sizeof(MDL::GroupFrame::max) + sizeof(*MDL::GroupFrame::times) * pcFrames2->numframes ); + } + BE_NCONST MDL::Vertex *pcVertices = (BE_NCONST MDL::Vertex *)((pcFirstFrame->name) + sizeof(pcFirstFrame->name)); + VALIDATE_FILE_SIZE((const unsigned char *)(pcVertices + pcHeader->num_verts)); + +#ifdef AI_BUILD_BIG_ENDIAN + for (int i = 0; i < pcHeader->num_verts; ++i) { + AI_SWAP4(pcTexCoords[i].onseam); + AI_SWAP4(pcTexCoords[i].s); + AI_SWAP4(pcTexCoords[i].t); + } + + for (int i = 0; i < pcHeader->num_tris; ++i) { + AI_SWAP4(pcTriangles[i].facesfront); + AI_SWAP4(pcTriangles[i].vertex[0]); + AI_SWAP4(pcTriangles[i].vertex[1]); + AI_SWAP4(pcTriangles[i].vertex[2]); + } +#endif + + // setup materials + SetupMaterialProperties_3DGS_MDL5_Quake1(); + + // allocate enough storage to hold all vertices and triangles + aiMesh *pcMesh = new aiMesh(); + + pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + pcMesh->mNumVertices = pcHeader->num_tris * 3; + pcMesh->mNumFaces = pcHeader->num_tris; + pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; + pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNumUVComponents[0] = 2; + + // there won't be more than one mesh inside the file + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mRootNode->mMeshes[0] = 0; + pScene->mNumMeshes = 1; + pScene->mMeshes = new aiMesh *[1]; + pScene->mMeshes[0] = pcMesh; + + // now iterate through all triangles + unsigned int iCurrent = 0; + for (unsigned int i = 0; i < (unsigned int)pcHeader->num_tris; ++i) { + pcMesh->mFaces[i].mIndices = new unsigned int[3]; + pcMesh->mFaces[i].mNumIndices = 3; + + unsigned int iTemp = iCurrent; + for (unsigned int c = 0; c < 3; ++c, ++iCurrent) { + pcMesh->mFaces[i].mIndices[c] = iCurrent; + + // read vertices + unsigned int iIndex = pcTriangles->vertex[c]; + if (iIndex >= (unsigned int)pcHeader->num_verts) { + iIndex = pcHeader->num_verts - 1; + ASSIMP_LOG_WARN("Index overflow in Q1-MDL vertex list."); + } + + aiVector3D &vec = pcMesh->mVertices[iCurrent]; + vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0]; + vec.x += pcHeader->translate[0]; + + vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1]; + vec.y += pcHeader->translate[1]; + //vec.y *= -1.0f; + + vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2]; + vec.z += pcHeader->translate[2]; + + // read the normal vector from the precalculated normal table + MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex, pcMesh->mNormals[iCurrent]); + //pcMesh->mNormals[iCurrent].y *= -1.0f; + + // read texture coordinates + float s = (float)pcTexCoords[iIndex].s; + float t = (float)pcTexCoords[iIndex].t; + + // translate texture coordinates + if (0 == pcTriangles->facesfront && 0 != pcTexCoords[iIndex].onseam) { + s += pcHeader->skinwidth * 0.5f; + } + + // Scale s and t to range from 0.0 to 1.0 + pcMesh->mTextureCoords[0][iCurrent].x = (s + 0.5f) / pcHeader->skinwidth; + pcMesh->mTextureCoords[0][iCurrent].y = 1.0f - (t + 0.5f) / pcHeader->skinheight; + } + pcMesh->mFaces[i].mIndices[0] = iTemp + 2; + pcMesh->mFaces[i].mIndices[1] = iTemp + 1; + pcMesh->mFaces[i].mIndices[2] = iTemp + 0; + pcTriangles++; + } + return; +} + +// ------------------------------------------------------------------------------------------------ +// Setup material properties for Quake and older GameStudio files +void MDLImporter::SetupMaterialProperties_3DGS_MDL5_Quake1() { + const MDL::Header *const pcHeader = (const MDL::Header *)this->mBuffer; + + // allocate ONE material + pScene->mMaterials = new aiMaterial *[1]; + pScene->mMaterials[0] = new aiMaterial(); + pScene->mNumMaterials = 1; + + // setup the material's properties + const int iMode = (int)aiShadingMode_Gouraud; + aiMaterial *const pcHelper = (aiMaterial *)pScene->mMaterials[0]; + pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + aiColor4D clr; + if (0 != pcHeader->num_skins && pScene->mNumTextures) { + // can we replace the texture with a single color? + clr = this->ReplaceTextureWithColor(pScene->mTextures[0]); + if (is_not_qnan(clr.r)) { + delete pScene->mTextures[0]; + delete[] pScene->mTextures; + + pScene->mTextures = nullptr; + pScene->mNumTextures = 0; + } else { + clr.b = clr.a = clr.g = clr.r = 1.0f; + aiString szString; + ::memcpy(szString.data, AI_MAKE_EMBEDDED_TEXNAME(0), 3); + szString.length = 2; + pcHelper->AddProperty(&szString, AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + } + + pcHelper->AddProperty<aiColor4D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty<aiColor4D>(&clr, 1, AI_MATKEY_COLOR_SPECULAR); + + clr.r *= 0.05f; + clr.g *= 0.05f; + clr.b *= 0.05f; + clr.a = 1.0f; + pcHelper->AddProperty<aiColor4D>(&clr, 1, AI_MATKEY_COLOR_AMBIENT); +} + +// ------------------------------------------------------------------------------------------------ +// Read a MDL 3,4,5 file +void MDLImporter::InternReadFile_3DGS_MDL345() { + ai_assert(nullptr != pScene); + + // the header of MDL 3/4/5 is nearly identical to the original Quake1 header + BE_NCONST MDL::Header *pcHeader = (BE_NCONST MDL::Header *)this->mBuffer; +#ifdef AI_BUILD_BIG_ENDIAN + FlipQuakeHeader(pcHeader); +#endif + ValidateHeader_Quake1(pcHeader); + + // current cursor position in the file + const unsigned char *szCurrent = (const unsigned char *)(pcHeader + 1); + const unsigned char *szEnd = mBuffer + iFileSize; + + // need to read all textures + for (unsigned int i = 0; i < (unsigned int)pcHeader->num_skins; ++i) { + if (szCurrent + sizeof(uint32_t) > szEnd) { + throw DeadlyImportError("Texture data past end of file."); + } + BE_NCONST MDL::Skin *pcSkin; + pcSkin = (BE_NCONST MDL::Skin *)szCurrent; + AI_SWAP4(pcSkin->group); + // create one output image + unsigned int iSkip = i ? UINT_MAX : 0; + if (5 <= iGSFileVersion) { + // MDL5 format could contain MIPmaps + CreateTexture_3DGS_MDL5((unsigned char *)pcSkin + sizeof(uint32_t), + pcSkin->group, &iSkip); + } else { + CreateTexture_3DGS_MDL4((unsigned char *)pcSkin + sizeof(uint32_t), + pcSkin->group, &iSkip); + } + // need to skip one image + szCurrent += iSkip + sizeof(uint32_t); + } + // get a pointer to the texture coordinates + BE_NCONST MDL::TexCoord_MDL3 *pcTexCoords = (BE_NCONST MDL::TexCoord_MDL3 *)szCurrent; + szCurrent += sizeof(MDL::TexCoord_MDL3) * pcHeader->synctype; + + // NOTE: for MDLn formats "synctype" corresponds to the number of UV coords + + // get a pointer to the triangles + BE_NCONST MDL::Triangle_MDL3 *pcTriangles = (BE_NCONST MDL::Triangle_MDL3 *)szCurrent; + szCurrent += sizeof(MDL::Triangle_MDL3) * pcHeader->num_tris; + +#ifdef AI_BUILD_BIG_ENDIAN + + for (int i = 0; i < pcHeader->synctype; ++i) { + AI_SWAP2(pcTexCoords[i].u); + AI_SWAP2(pcTexCoords[i].v); + } + + for (int i = 0; i < pcHeader->num_tris; ++i) { + AI_SWAP2(pcTriangles[i].index_xyz[0]); + AI_SWAP2(pcTriangles[i].index_xyz[1]); + AI_SWAP2(pcTriangles[i].index_xyz[2]); + AI_SWAP2(pcTriangles[i].index_uv[0]); + AI_SWAP2(pcTriangles[i].index_uv[1]); + AI_SWAP2(pcTriangles[i].index_uv[2]); + } + +#endif + + VALIDATE_FILE_SIZE(szCurrent); + + // setup materials + SetupMaterialProperties_3DGS_MDL5_Quake1(); + + // allocate enough storage to hold all vertices and triangles + aiMesh *pcMesh = new aiMesh(); + pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + pcMesh->mNumVertices = pcHeader->num_tris * 3; + pcMesh->mNumFaces = pcHeader->num_tris; + pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; + + // there won't be more than one mesh inside the file + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mRootNode->mMeshes[0] = 0; + pScene->mNumMeshes = 1; + pScene->mMeshes = new aiMesh *[1]; + pScene->mMeshes[0] = pcMesh; + + // allocate output storage + pcMesh->mNumVertices = (unsigned int)pcHeader->num_tris * 3; + pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; + + if (pcHeader->synctype) { + pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNumUVComponents[0] = 2; + } + + // now get a pointer to the first frame in the file + BE_NCONST MDL::Frame *pcFrames = (BE_NCONST MDL::Frame *)szCurrent; + AI_SWAP4(pcFrames->type); + + // byte packed vertices + // FIXME: these two snippets below are almost identical ... join them? + ///////////////////////////////////////////////////////////////////////////////////// + if (0 == pcFrames->type || 3 >= this->iGSFileVersion) { + + const MDL::SimpleFrame *pcFirstFrame = (const MDL::SimpleFrame *)(szCurrent + sizeof(uint32_t)); + const MDL::Vertex *pcVertices = (const MDL::Vertex *)((pcFirstFrame->name) + sizeof(pcFirstFrame->name)); + + VALIDATE_FILE_SIZE(pcVertices + pcHeader->num_verts); + + // now iterate through all triangles + unsigned int iCurrent = 0; + for (unsigned int i = 0; i < (unsigned int)pcHeader->num_tris; ++i) { + pcMesh->mFaces[i].mIndices = new unsigned int[3]; + pcMesh->mFaces[i].mNumIndices = 3; + + unsigned int iTemp = iCurrent; + for (unsigned int c = 0; c < 3; ++c, ++iCurrent) { + // read vertices + unsigned int iIndex = pcTriangles->index_xyz[c]; + if (iIndex >= (unsigned int)pcHeader->num_verts) { + iIndex = pcHeader->num_verts - 1; + ASSIMP_LOG_WARN("Index overflow in MDLn vertex list"); + } + + aiVector3D &vec = pcMesh->mVertices[iCurrent]; + vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0]; + vec.x += pcHeader->translate[0]; + + vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1]; + vec.y += pcHeader->translate[1]; + // vec.y *= -1.0f; + + vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2]; + vec.z += pcHeader->translate[2]; + + // read the normal vector from the precalculated normal table + MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex, pcMesh->mNormals[iCurrent]); + // pcMesh->mNormals[iCurrent].y *= -1.0f; + + // read texture coordinates + if (pcHeader->synctype) { + ImportUVCoordinate_3DGS_MDL345(pcMesh->mTextureCoords[0][iCurrent], + pcTexCoords, pcTriangles->index_uv[c]); + } + } + pcMesh->mFaces[i].mIndices[0] = iTemp + 2; + pcMesh->mFaces[i].mIndices[1] = iTemp + 1; + pcMesh->mFaces[i].mIndices[2] = iTemp + 0; + pcTriangles++; + } + + } + // short packed vertices + ///////////////////////////////////////////////////////////////////////////////////// + else { + // now get a pointer to the first frame in the file + const MDL::SimpleFrame_MDLn_SP *pcFirstFrame = (const MDL::SimpleFrame_MDLn_SP *)(szCurrent + sizeof(uint32_t)); + + // get a pointer to the vertices + const MDL::Vertex_MDL4 *pcVertices = (const MDL::Vertex_MDL4 *)((pcFirstFrame->name) + + sizeof(pcFirstFrame->name)); + + VALIDATE_FILE_SIZE(pcVertices + pcHeader->num_verts); + + // now iterate through all triangles + unsigned int iCurrent = 0; + for (unsigned int i = 0; i < (unsigned int)pcHeader->num_tris; ++i) { + pcMesh->mFaces[i].mIndices = new unsigned int[3]; + pcMesh->mFaces[i].mNumIndices = 3; + + unsigned int iTemp = iCurrent; + for (unsigned int c = 0; c < 3; ++c, ++iCurrent) { + // read vertices + unsigned int iIndex = pcTriangles->index_xyz[c]; + if (iIndex >= (unsigned int)pcHeader->num_verts) { + iIndex = pcHeader->num_verts - 1; + ASSIMP_LOG_WARN("Index overflow in MDLn vertex list"); + } + + aiVector3D &vec = pcMesh->mVertices[iCurrent]; + vec.x = (float)pcVertices[iIndex].v[0] * pcHeader->scale[0]; + vec.x += pcHeader->translate[0]; + + vec.y = (float)pcVertices[iIndex].v[1] * pcHeader->scale[1]; + vec.y += pcHeader->translate[1]; + // vec.y *= -1.0f; + + vec.z = (float)pcVertices[iIndex].v[2] * pcHeader->scale[2]; + vec.z += pcHeader->translate[2]; + + // read the normal vector from the precalculated normal table + MD2::LookupNormalIndex(pcVertices[iIndex].normalIndex, pcMesh->mNormals[iCurrent]); + // pcMesh->mNormals[iCurrent].y *= -1.0f; + + // read texture coordinates + if (pcHeader->synctype) { + ImportUVCoordinate_3DGS_MDL345(pcMesh->mTextureCoords[0][iCurrent], + pcTexCoords, pcTriangles->index_uv[c]); + } + } + pcMesh->mFaces[i].mIndices[0] = iTemp + 2; + pcMesh->mFaces[i].mIndices[1] = iTemp + 1; + pcMesh->mFaces[i].mIndices[2] = iTemp + 0; + pcTriangles++; + } + } + + // For MDL5 we will need to build valid texture coordinates + // basing upon the file loaded (only support one file as skin) + if (0x5 == iGSFileVersion) + CalculateUVCoordinates_MDL5(); + return; +} + +// ------------------------------------------------------------------------------------------------ +// Get a single UV coordinate for Quake and older GameStudio files +void MDLImporter::ImportUVCoordinate_3DGS_MDL345( + aiVector3D &vOut, + const MDL::TexCoord_MDL3 *pcSrc, + unsigned int iIndex) { + ai_assert(nullptr != pcSrc); + const MDL::Header *const pcHeader = (const MDL::Header *)this->mBuffer; + + // validate UV indices + if (iIndex >= (unsigned int)pcHeader->synctype) { + iIndex = pcHeader->synctype - 1; + ASSIMP_LOG_WARN("Index overflow in MDLn UV coord list"); + } + + float s = (float)pcSrc[iIndex].u; + float t = (float)pcSrc[iIndex].v; + + // Scale s and t to range from 0.0 to 1.0 + if (0x5 != iGSFileVersion) { + s = (s + 0.5f) / pcHeader->skinwidth; + t = 1.0f - (t + 0.5f) / pcHeader->skinheight; + } + + vOut.x = s; + vOut.y = t; + vOut.z = 0.0f; +} + +// ------------------------------------------------------------------------------------------------ +// Compute UV coordinates for a MDL5 file +void MDLImporter::CalculateUVCoordinates_MDL5() { + const MDL::Header *const pcHeader = (const MDL::Header *)this->mBuffer; + if (pcHeader->num_skins && this->pScene->mNumTextures) { + const aiTexture *pcTex = this->pScene->mTextures[0]; + + // if the file is loaded in DDS format: get the size of the + // texture from the header of the DDS file + // skip three DWORDs and read first height, then the width + unsigned int iWidth, iHeight; + if (!pcTex->mHeight) { + const uint32_t *piPtr = (uint32_t *)pcTex->pcData; + + piPtr += 3; + iHeight = (unsigned int)*piPtr++; + iWidth = (unsigned int)*piPtr; + if (!iHeight || !iWidth) { + ASSIMP_LOG_WARN("Either the width or the height of the " + "embedded DDS texture is zero. Unable to compute final texture " + "coordinates. The texture coordinates remain in their original " + "0-x/0-y (x,y = texture size) range."); + iWidth = 1; + iHeight = 1; + } + } else { + iWidth = pcTex->mWidth; + iHeight = pcTex->mHeight; + } + + if (1 != iWidth || 1 != iHeight) { + const float fWidth = (float)iWidth; + const float fHeight = (float)iHeight; + aiMesh *pcMesh = this->pScene->mMeshes[0]; + for (unsigned int i = 0; i < pcMesh->mNumVertices; ++i) { + pcMesh->mTextureCoords[0][i].x /= fWidth; + pcMesh->mTextureCoords[0][i].y /= fHeight; + pcMesh->mTextureCoords[0][i].y = 1.0f - pcMesh->mTextureCoords[0][i].y; // DX to OGL + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Validate the header of a MDL7 file +void MDLImporter::ValidateHeader_3DGS_MDL7(const MDL::Header_MDL7 *pcHeader) { + ai_assert(nullptr != pcHeader); + + // There are some fixed sizes ... + if (sizeof(MDL::ColorValue_MDL7) != pcHeader->colorvalue_stc_size) { + throw DeadlyImportError( + "[3DGS MDL7] sizeof(MDL::ColorValue_MDL7) != pcHeader->colorvalue_stc_size"); + } + if (sizeof(MDL::TexCoord_MDL7) != pcHeader->skinpoint_stc_size) { + throw DeadlyImportError( + "[3DGS MDL7] sizeof(MDL::TexCoord_MDL7) != pcHeader->skinpoint_stc_size"); + } + if (sizeof(MDL::Skin_MDL7) != pcHeader->skin_stc_size) { + throw DeadlyImportError( + "sizeof(MDL::Skin_MDL7) != pcHeader->skin_stc_size"); + } + + // if there are no groups ... how should we load such a file? + if (!pcHeader->groups_num) { + throw DeadlyImportError("[3DGS MDL7] No frames found"); + } +} + +// ------------------------------------------------------------------------------------------------ +// resolve bone animation matrices +void MDLImporter::CalcAbsBoneMatrices_3DGS_MDL7(MDL::IntBone_MDL7 **apcOutBones) { + const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)this->mBuffer; + const MDL::Bone_MDL7 *pcBones = (const MDL::Bone_MDL7 *)(pcHeader + 1); + ai_assert(nullptr != apcOutBones); + + // first find the bone that has NO parent, calculate the + // animation matrix for it, then go on and search for the next parent + // index (0) and so on until we can't find a new node. + uint16_t iParent = 0xffff; + uint32_t iIterations = 0; + while (iIterations++ < pcHeader->bones_num) { + for (uint32_t iBone = 0; iBone < pcHeader->bones_num; ++iBone) { + BE_NCONST MDL::Bone_MDL7 *pcBone = _AI_MDL7_ACCESS_PTR(pcBones, iBone, + pcHeader->bone_stc_size, MDL::Bone_MDL7); + + AI_SWAP2(pcBone->parent_index); + AI_SWAP4(pcBone->x); + AI_SWAP4(pcBone->y); + AI_SWAP4(pcBone->z); + + if (iParent == pcBone->parent_index) { + // MDL7 readme + //////////////////////////////////////////////////////////////// + /* + The animation matrix is then calculated the following way: + + vector3 bPos = <absolute bone position> + matrix44 laM; // local animation matrix + sphrvector key_rotate = <bone rotation> + + matrix44 m1,m2; + create_trans_matrix(m1, -bPos.x, -bPos.y, -bPos.z); + create_trans_matrix(m2, -bPos.x, -bPos.y, -bPos.z); + + create_rotation_matrix(laM,key_rotate); + + laM = sm1 * laM; + laM = laM * sm2; + */ + ///////////////////////////////////////////////////////////////// + + MDL::IntBone_MDL7 *const pcOutBone = apcOutBones[iBone]; + + // store the parent index of the bone + pcOutBone->iParent = pcBone->parent_index; + if (0xffff != iParent) { + const MDL::IntBone_MDL7 *pcParentBone = apcOutBones[iParent]; + pcOutBone->mOffsetMatrix.a4 = -pcParentBone->vPosition.x; + pcOutBone->mOffsetMatrix.b4 = -pcParentBone->vPosition.y; + pcOutBone->mOffsetMatrix.c4 = -pcParentBone->vPosition.z; + } + pcOutBone->vPosition.x = pcBone->x; + pcOutBone->vPosition.y = pcBone->y; + pcOutBone->vPosition.z = pcBone->z; + pcOutBone->mOffsetMatrix.a4 -= pcBone->x; + pcOutBone->mOffsetMatrix.b4 -= pcBone->y; + pcOutBone->mOffsetMatrix.c4 -= pcBone->z; + + if (AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE == pcHeader->bone_stc_size) { + // no real name for our poor bone is specified :-( + pcOutBone->mName.length = ai_snprintf(pcOutBone->mName.data, MAXLEN, + "UnnamedBone_%i", iBone); + } else { + // Make sure we won't run over the buffer's end if there is no + // terminal 0 character (however the documentation says there + // should be one) + uint32_t iMaxLen = pcHeader->bone_stc_size - 16; + for (uint32_t qq = 0; qq < iMaxLen; ++qq) { + if (!pcBone->name[qq]) { + iMaxLen = qq; + break; + } + } + + // store the name of the bone + pcOutBone->mName.length = (size_t)iMaxLen; + ::memcpy(pcOutBone->mName.data, pcBone->name, pcOutBone->mName.length); + pcOutBone->mName.data[pcOutBone->mName.length] = '\0'; + } + } + } + ++iParent; + } +} + +// ------------------------------------------------------------------------------------------------ +// read bones from a MDL7 file +MDL::IntBone_MDL7 **MDLImporter::LoadBones_3DGS_MDL7() { + const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)this->mBuffer; + if (pcHeader->bones_num) { + // validate the size of the bone data structure in the file + if (AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_20_CHARS != pcHeader->bone_stc_size && + AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_32_CHARS != pcHeader->bone_stc_size && + AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE != pcHeader->bone_stc_size) { + ASSIMP_LOG_WARN("Unknown size of bone data structure"); + return nullptr; + } + + MDL::IntBone_MDL7 **apcBonesOut = new MDL::IntBone_MDL7 *[pcHeader->bones_num]; + for (uint32_t crank = 0; crank < pcHeader->bones_num; ++crank) + apcBonesOut[crank] = new MDL::IntBone_MDL7(); + + // and calculate absolute bone offset matrices ... + CalcAbsBoneMatrices_3DGS_MDL7(apcBonesOut); + return apcBonesOut; + } + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// read faces from a MDL7 file +void MDLImporter::ReadFaces_3DGS_MDL7(const MDL::IntGroupInfo_MDL7 &groupInfo, + MDL::IntGroupData_MDL7 &groupData) { + const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)this->mBuffer; + MDL::Triangle_MDL7 *pcGroupTris = groupInfo.pcGroupTris; + + // iterate through all triangles and build valid display lists + unsigned int iOutIndex = 0; + for (unsigned int iTriangle = 0; iTriangle < (unsigned int)groupInfo.pcGroup->numtris; ++iTriangle) { + AI_SWAP2(pcGroupTris->v_index[0]); + AI_SWAP2(pcGroupTris->v_index[1]); + AI_SWAP2(pcGroupTris->v_index[2]); + + // iterate through all indices of the current triangle + for (unsigned int c = 0; c < 3; ++c, ++iOutIndex) { + + // validate the vertex index + unsigned int iIndex = pcGroupTris->v_index[c]; + if (iIndex > (unsigned int)groupInfo.pcGroup->numverts) { + // (we might need to read this section a second time - to process frame vertices correctly) + pcGroupTris->v_index[c] = (uint16_t)(iIndex = groupInfo.pcGroup->numverts - 1); + ASSIMP_LOG_WARN("Index overflow in MDL7 vertex list"); + } + + // write the output face index + groupData.pcFaces[iTriangle].mIndices[2 - c] = iOutIndex; + + aiVector3D &vPosition = groupData.vPositions[iOutIndex]; + vPosition.x = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).x; + vPosition.y = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).y; + vPosition.z = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).z; + + // if we have bones, save the index + if (!groupData.aiBones.empty()) { + groupData.aiBones[iOutIndex] = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, + iIndex, pcHeader->mainvertex_stc_size) + .vertindex; + } + + // now read the normal vector + if (AI_MDL7_FRAMEVERTEX030305_STCSIZE <= pcHeader->mainvertex_stc_size) { + // read the full normal vector + aiVector3D &vNormal = groupData.vNormals[iOutIndex]; + vNormal.x = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).norm[0]; + AI_SWAP4(vNormal.x); + vNormal.y = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).norm[1]; + AI_SWAP4(vNormal.y); + vNormal.z = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, pcHeader->mainvertex_stc_size).norm[2]; + AI_SWAP4(vNormal.z); + } else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size) { + // read the normal vector from Quake2's smart table + aiVector3D &vNormal = groupData.vNormals[iOutIndex]; + MD2::LookupNormalIndex(_AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts, iIndex, + pcHeader->mainvertex_stc_size) + .norm162index, + vNormal); + } + // validate and process the first uv coordinate set + if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV) { + + if (groupInfo.pcGroup->num_stpts) { + AI_SWAP2(pcGroupTris->skinsets[0].st_index[0]); + AI_SWAP2(pcGroupTris->skinsets[0].st_index[1]); + AI_SWAP2(pcGroupTris->skinsets[0].st_index[2]); + + iIndex = pcGroupTris->skinsets[0].st_index[c]; + if (iIndex > (unsigned int)groupInfo.pcGroup->num_stpts) { + iIndex = groupInfo.pcGroup->num_stpts - 1; + ASSIMP_LOG_WARN("Index overflow in MDL7 UV coordinate list (#1)"); + } + + float u = groupInfo.pcGroupUVs[iIndex].u; + float v = 1.0f - groupInfo.pcGroupUVs[iIndex].v; // DX to OGL + + groupData.vTextureCoords1[iOutIndex].x = u; + groupData.vTextureCoords1[iOutIndex].y = v; + } + // assign the material index, but only if it is existing + if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_ONE_UV_WITH_MATINDEX) { + AI_SWAP4(pcGroupTris->skinsets[0].material); + groupData.pcFaces[iTriangle].iMatIndex[0] = pcGroupTris->skinsets[0].material; + } + } + // validate and process the second uv coordinate set + if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV) { + + if (groupInfo.pcGroup->num_stpts) { + AI_SWAP2(pcGroupTris->skinsets[1].st_index[0]); + AI_SWAP2(pcGroupTris->skinsets[1].st_index[1]); + AI_SWAP2(pcGroupTris->skinsets[1].st_index[2]); + AI_SWAP4(pcGroupTris->skinsets[1].material); + + iIndex = pcGroupTris->skinsets[1].st_index[c]; + if (iIndex > (unsigned int)groupInfo.pcGroup->num_stpts) { + iIndex = groupInfo.pcGroup->num_stpts - 1; + ASSIMP_LOG_WARN("Index overflow in MDL7 UV coordinate list (#2)"); + } + + float u = groupInfo.pcGroupUVs[iIndex].u; + float v = 1.0f - groupInfo.pcGroupUVs[iIndex].v; + + groupData.vTextureCoords2[iOutIndex].x = u; + groupData.vTextureCoords2[iOutIndex].y = v; // DX to OGL + + // check whether we do really need the second texture + // coordinate set ... wastes memory and loading time + if (0 != iIndex && (u != groupData.vTextureCoords1[iOutIndex].x || + v != groupData.vTextureCoords1[iOutIndex].y)) + groupData.bNeed2UV = true; + + // if the material differs, we need a second skin, too + if (pcGroupTris->skinsets[1].material != pcGroupTris->skinsets[0].material) + groupData.bNeed2UV = true; + } + // assign the material index + groupData.pcFaces[iTriangle].iMatIndex[1] = pcGroupTris->skinsets[1].material; + } + } + // get the next triangle in the list + pcGroupTris = (MDL::Triangle_MDL7 *)((const char *)pcGroupTris + pcHeader->triangle_stc_size); + } +} + +// ------------------------------------------------------------------------------------------------ +// handle frames in a MDL7 file +bool MDLImporter::ProcessFrames_3DGS_MDL7(const MDL::IntGroupInfo_MDL7 &groupInfo, + MDL::IntGroupData_MDL7 &groupData, + MDL::IntSharedData_MDL7 &shared, + const unsigned char *szCurrent, + const unsigned char **szCurrentOut) { + ai_assert(nullptr != szCurrent); + ai_assert(nullptr != szCurrentOut); + + const MDL::Header_MDL7 *pcHeader = (const MDL::Header_MDL7 *)mBuffer; + + // if we have no bones we can simply skip all frames, + // otherwise we'll need to process them. + // FIX: If we need another frame than the first we must apply frame vertex replacements ... + for (unsigned int iFrame = 0; iFrame < (unsigned int)groupInfo.pcGroup->numframes; ++iFrame) { + MDL::IntFrameInfo_MDL7 frame((BE_NCONST MDL::Frame_MDL7 *)szCurrent, iFrame); + + AI_SWAP4(frame.pcFrame->vertices_count); + AI_SWAP4(frame.pcFrame->transmatrix_count); + + const unsigned int iAdd = pcHeader->frame_stc_size + + frame.pcFrame->vertices_count * pcHeader->framevertex_stc_size + + frame.pcFrame->transmatrix_count * pcHeader->bonetrans_stc_size; + + if (((const char *)szCurrent - (const char *)pcHeader) + iAdd > (unsigned int)pcHeader->data_size) { + ASSIMP_LOG_WARN("Index overflow in frame area. " + "Ignoring all frames and all further mesh groups, too."); + + // don't parse more groups if we can't even read one + // FIXME: sometimes this seems to occur even for valid files ... + *szCurrentOut = szCurrent; + return false; + } + // our output frame? + if (configFrameID == iFrame) { + BE_NCONST MDL::Vertex_MDL7 *pcFrameVertices = (BE_NCONST MDL::Vertex_MDL7 *)(szCurrent + pcHeader->frame_stc_size); + + for (unsigned int qq = 0; qq < frame.pcFrame->vertices_count; ++qq) { + // I assume this are simple replacements for normal vertices, the bone index serving + // as the index of the vertex to be replaced. + uint16_t iIndex = _AI_MDL7_ACCESS(pcFrameVertices, qq, pcHeader->framevertex_stc_size, MDL::Vertex_MDL7).vertindex; + AI_SWAP2(iIndex); + if (iIndex >= groupInfo.pcGroup->numverts) { + ASSIMP_LOG_WARN("Invalid vertex index in frame vertex section"); + continue; + } + + aiVector3D vPosition, vNormal; + + vPosition.x = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).x; + AI_SWAP4(vPosition.x); + vPosition.y = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).y; + AI_SWAP4(vPosition.y); + vPosition.z = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).z; + AI_SWAP4(vPosition.z); + + // now read the normal vector + if (AI_MDL7_FRAMEVERTEX030305_STCSIZE <= pcHeader->mainvertex_stc_size) { + // read the full normal vector + vNormal.x = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).norm[0]; + AI_SWAP4(vNormal.x); + vNormal.y = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).norm[1]; + AI_SWAP4(vNormal.y); + vNormal.z = _AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, pcHeader->framevertex_stc_size).norm[2]; + AI_SWAP4(vNormal.z); + } else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size) { + // read the normal vector from Quake2's smart table + MD2::LookupNormalIndex(_AI_MDL7_ACCESS_VERT(pcFrameVertices, qq, + pcHeader->framevertex_stc_size) + .norm162index, + vNormal); + } + + // FIXME: O(n^2) at the moment ... + BE_NCONST MDL::Triangle_MDL7 *pcGroupTris = groupInfo.pcGroupTris; + unsigned int iOutIndex = 0; + for (unsigned int iTriangle = 0; iTriangle < (unsigned int)groupInfo.pcGroup->numtris; ++iTriangle) { + // iterate through all indices of the current triangle + for (unsigned int c = 0; c < 3; ++c, ++iOutIndex) { + // replace the vertex with the new data + const unsigned int iCurIndex = pcGroupTris->v_index[c]; + if (iCurIndex == iIndex) { + groupData.vPositions[iOutIndex] = vPosition; + groupData.vNormals[iOutIndex] = vNormal; + } + } + // get the next triangle in the list + pcGroupTris = (BE_NCONST MDL::Triangle_MDL7 *)((const char *) + pcGroupTris + + pcHeader->triangle_stc_size); + } + } + } + // parse bone trafo matrix keys (only if there are bones ...) + if (shared.apcOutBones) { + ParseBoneTrafoKeys_3DGS_MDL7(groupInfo, frame, shared); + } + szCurrent += iAdd; + } + *szCurrentOut = szCurrent; + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Sort faces by material, handle multiple UVs correctly +void MDLImporter::SortByMaterials_3DGS_MDL7( + const MDL::IntGroupInfo_MDL7 &groupInfo, + MDL::IntGroupData_MDL7 &groupData, + MDL::IntSplitGroupData_MDL7 &splitGroupData) { + const unsigned int iNumMaterials = (unsigned int)splitGroupData.shared.pcMats.size(); + if (!groupData.bNeed2UV) { + // if we don't need a second set of texture coordinates there is no reason to keep it in memory ... + groupData.vTextureCoords2.clear(); + + // allocate the array + splitGroupData.aiSplit = new std::vector<unsigned int> *[iNumMaterials]; + + for (unsigned int m = 0; m < iNumMaterials; ++m) + splitGroupData.aiSplit[m] = new std::vector<unsigned int>(); + + // iterate through all faces and sort by material + for (unsigned int iFace = 0; iFace < (unsigned int)groupInfo.pcGroup->numtris; ++iFace) { + // check range + if (groupData.pcFaces[iFace].iMatIndex[0] >= iNumMaterials) { + // use the last material instead + splitGroupData.aiSplit[iNumMaterials - 1]->push_back(iFace); + + // sometimes MED writes -1, but normally only if there is only + // one skin assigned. No warning in this case + if (0xFFFFFFFF != groupData.pcFaces[iFace].iMatIndex[0]) + ASSIMP_LOG_WARN("Index overflow in MDL7 material list [#0]"); + } else + splitGroupData.aiSplit[groupData.pcFaces[iFace].iMatIndex[0]]->push_back(iFace); + } + } else { + // we need to build combined materials for each combination of + std::vector<MDL::IntMaterial_MDL7> avMats; + avMats.reserve(iNumMaterials * 2); + + // fixme: why on the heap? + std::vector<std::vector<unsigned int> *> aiTempSplit(iNumMaterials * 2); + for (unsigned int m = 0; m < iNumMaterials; ++m) + aiTempSplit[m] = new std::vector<unsigned int>(); + + // iterate through all faces and sort by material + for (unsigned int iFace = 0; iFace < (unsigned int)groupInfo.pcGroup->numtris; ++iFace) { + // check range + unsigned int iMatIndex = groupData.pcFaces[iFace].iMatIndex[0]; + if (iMatIndex >= iNumMaterials) { + // sometimes MED writes -1, but normally only if there is only + // one skin assigned. No warning in this case + if (UINT_MAX != iMatIndex) + ASSIMP_LOG_WARN("Index overflow in MDL7 material list [#1]"); + iMatIndex = iNumMaterials - 1; + } + unsigned int iMatIndex2 = groupData.pcFaces[iFace].iMatIndex[1]; + + unsigned int iNum = iMatIndex; + if (UINT_MAX != iMatIndex2 && iMatIndex != iMatIndex2) { + if (iMatIndex2 >= iNumMaterials) { + // sometimes MED writes -1, but normally only if there is only + // one skin assigned. No warning in this case + ASSIMP_LOG_WARN("Index overflow in MDL7 material list [#2]"); + iMatIndex2 = iNumMaterials - 1; + } + + // do a slow search in the list ... + iNum = 0; + bool bFound = false; + for (std::vector<MDL::IntMaterial_MDL7>::iterator i = avMats.begin(); i != avMats.end(); ++i, ++iNum) { + if ((*i).iOldMatIndices[0] == iMatIndex && (*i).iOldMatIndices[1] == iMatIndex2) { + // reuse this material + bFound = true; + break; + } + } + if (!bFound) { + // build a new material ... + MDL::IntMaterial_MDL7 sHelper; + sHelper.pcMat = new aiMaterial(); + sHelper.iOldMatIndices[0] = iMatIndex; + sHelper.iOldMatIndices[1] = iMatIndex2; + JoinSkins_3DGS_MDL7(splitGroupData.shared.pcMats[iMatIndex], + splitGroupData.shared.pcMats[iMatIndex2], sHelper.pcMat); + + // and add it to the list + avMats.push_back(sHelper); + iNum = (unsigned int)avMats.size() - 1; + } + // adjust the size of the file array + if (iNum == aiTempSplit.size()) { + aiTempSplit.push_back(new std::vector<unsigned int>()); + } + } + aiTempSplit[iNum]->push_back(iFace); + } + + // now add the newly created materials to the old list + if (0 == groupInfo.iIndex) { + splitGroupData.shared.pcMats.resize(avMats.size()); + for (unsigned int o = 0; o < avMats.size(); ++o) + splitGroupData.shared.pcMats[o] = avMats[o].pcMat; + } else { + // This might result in redundant materials ... + splitGroupData.shared.pcMats.resize(iNumMaterials + avMats.size()); + for (unsigned int o = iNumMaterials; o < avMats.size(); ++o) + splitGroupData.shared.pcMats[o] = avMats[o].pcMat; + } + + // and build the final face-to-material array + splitGroupData.aiSplit = new std::vector<unsigned int> *[aiTempSplit.size()]; + for (unsigned int m = 0; m < iNumMaterials; ++m) + splitGroupData.aiSplit[m] = aiTempSplit[m]; + } +} + +// ------------------------------------------------------------------------------------------------ +// Read a MDL7 file +void MDLImporter::InternReadFile_3DGS_MDL7() { + ai_assert(nullptr != pScene); + + MDL::IntSharedData_MDL7 sharedData; + + // current cursor position in the file + BE_NCONST MDL::Header_MDL7 *pcHeader = (BE_NCONST MDL::Header_MDL7 *)this->mBuffer; + const unsigned char *szCurrent = (const unsigned char *)(pcHeader + 1); + + AI_SWAP4(pcHeader->version); + AI_SWAP4(pcHeader->bones_num); + AI_SWAP4(pcHeader->groups_num); + AI_SWAP4(pcHeader->data_size); + AI_SWAP4(pcHeader->entlump_size); + AI_SWAP4(pcHeader->medlump_size); + AI_SWAP2(pcHeader->bone_stc_size); + AI_SWAP2(pcHeader->skin_stc_size); + AI_SWAP2(pcHeader->colorvalue_stc_size); + AI_SWAP2(pcHeader->material_stc_size); + AI_SWAP2(pcHeader->skinpoint_stc_size); + AI_SWAP2(pcHeader->triangle_stc_size); + AI_SWAP2(pcHeader->mainvertex_stc_size); + AI_SWAP2(pcHeader->framevertex_stc_size); + AI_SWAP2(pcHeader->bonetrans_stc_size); + AI_SWAP2(pcHeader->frame_stc_size); + + // validate the header of the file. There are some structure + // sizes that are expected by the loader to be constant + this->ValidateHeader_3DGS_MDL7(pcHeader); + + // load all bones (they are shared by all groups, so + // we'll need to add them to all groups/meshes later) + // apcBonesOut is a list of all bones or nullptr if they could not been loaded + szCurrent += pcHeader->bones_num * pcHeader->bone_stc_size; + sharedData.apcOutBones = this->LoadBones_3DGS_MDL7(); + + // vector to held all created meshes + std::vector<aiMesh *> *avOutList; + + // 3 meshes per group - that should be OK for most models + avOutList = new std::vector<aiMesh *>[pcHeader->groups_num]; + for (uint32_t i = 0; i < pcHeader->groups_num; ++i) + avOutList[i].reserve(3); + + // buffer to held the names of all groups in the file + const size_t buffersize(AI_MDL7_MAX_GROUPNAMESIZE * pcHeader->groups_num); + char *aszGroupNameBuffer = new char[buffersize]; + + // read all groups + for (unsigned int iGroup = 0; iGroup < (unsigned int)pcHeader->groups_num; ++iGroup) { + MDL::IntGroupInfo_MDL7 groupInfo((BE_NCONST MDL::Group_MDL7 *)szCurrent, iGroup); + szCurrent = (const unsigned char *)(groupInfo.pcGroup + 1); + + VALIDATE_FILE_SIZE(szCurrent); + + AI_SWAP4(groupInfo.pcGroup->groupdata_size); + AI_SWAP4(groupInfo.pcGroup->numskins); + AI_SWAP4(groupInfo.pcGroup->num_stpts); + AI_SWAP4(groupInfo.pcGroup->numtris); + AI_SWAP4(groupInfo.pcGroup->numverts); + AI_SWAP4(groupInfo.pcGroup->numframes); + + if (1 != groupInfo.pcGroup->typ) { + // Not a triangle-based mesh + ASSIMP_LOG_WARN("[3DGS MDL7] Not a triangle mesh group. Continuing happily"); + } + + // store the name of the group + const unsigned int ofs = iGroup * AI_MDL7_MAX_GROUPNAMESIZE; + ::memcpy(&aszGroupNameBuffer[ofs], + groupInfo.pcGroup->name, AI_MDL7_MAX_GROUPNAMESIZE); + + // make sure '\0' is at the end + aszGroupNameBuffer[ofs + AI_MDL7_MAX_GROUPNAMESIZE - 1] = '\0'; + + // read all skins + sharedData.pcMats.reserve(sharedData.pcMats.size() + groupInfo.pcGroup->numskins); + sharedData.abNeedMaterials.resize(sharedData.abNeedMaterials.size() + + groupInfo.pcGroup->numskins, + false); + + for (unsigned int iSkin = 0; iSkin < (unsigned int)groupInfo.pcGroup->numskins; ++iSkin) { + ParseSkinLump_3DGS_MDL7(szCurrent, &szCurrent, sharedData.pcMats); + } + // if we have absolutely no skin loaded we need to generate a default material + if (sharedData.pcMats.empty()) { + const int iMode = (int)aiShadingMode_Gouraud; + sharedData.pcMats.push_back(new aiMaterial()); + aiMaterial *pcHelper = (aiMaterial *)sharedData.pcMats[0]; + pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + aiColor3D clr; + clr.b = clr.g = clr.r = 0.6f; + pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_SPECULAR); + + clr.b = clr.g = clr.r = 0.05f; + pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_AMBIENT); + + aiString szName; + szName.Set(AI_DEFAULT_MATERIAL_NAME); + pcHelper->AddProperty(&szName, AI_MATKEY_NAME); + + sharedData.abNeedMaterials.resize(1, false); + } + + // now get a pointer to all texture coords in the group + groupInfo.pcGroupUVs = (BE_NCONST MDL::TexCoord_MDL7 *)szCurrent; + for (int i = 0; i < groupInfo.pcGroup->num_stpts; ++i) { + AI_SWAP4(groupInfo.pcGroupUVs[i].u); + AI_SWAP4(groupInfo.pcGroupUVs[i].v); + } + szCurrent += pcHeader->skinpoint_stc_size * groupInfo.pcGroup->num_stpts; + + // now get a pointer to all triangle in the group + groupInfo.pcGroupTris = (Triangle_MDL7 *)szCurrent; + szCurrent += pcHeader->triangle_stc_size * groupInfo.pcGroup->numtris; + + // now get a pointer to all vertices in the group + groupInfo.pcGroupVerts = (BE_NCONST MDL::Vertex_MDL7 *)szCurrent; + for (int i = 0; i < groupInfo.pcGroup->numverts; ++i) { + AI_SWAP4(groupInfo.pcGroupVerts[i].x); + AI_SWAP4(groupInfo.pcGroupVerts[i].y); + AI_SWAP4(groupInfo.pcGroupVerts[i].z); + + AI_SWAP2(groupInfo.pcGroupVerts[i].vertindex); + //We can not swap the normal information now as we don't know which of the two kinds it is + } + szCurrent += pcHeader->mainvertex_stc_size * groupInfo.pcGroup->numverts; + VALIDATE_FILE_SIZE(szCurrent); + + MDL::IntSplitGroupData_MDL7 splitGroupData(sharedData, avOutList[iGroup]); + MDL::IntGroupData_MDL7 groupData; + if (groupInfo.pcGroup->numtris && groupInfo.pcGroup->numverts) { + // build output vectors + const unsigned int iNumVertices = groupInfo.pcGroup->numtris * 3; + groupData.vPositions.resize(iNumVertices); + groupData.vNormals.resize(iNumVertices); + + if (sharedData.apcOutBones) groupData.aiBones.resize(iNumVertices, UINT_MAX); + + // it is also possible that there are 0 UV coordinate sets + if (groupInfo.pcGroup->num_stpts) { + groupData.vTextureCoords1.resize(iNumVertices, aiVector3D()); + + // check whether the triangle data structure is large enough + // to contain a second UV coordinate set + if (pcHeader->triangle_stc_size >= AI_MDL7_TRIANGLE_STD_SIZE_TWO_UV) { + groupData.vTextureCoords2.resize(iNumVertices, aiVector3D()); + groupData.bNeed2UV = true; + } + } + groupData.pcFaces.resize(groupInfo.pcGroup->numtris); + + // read all faces into the preallocated arrays + ReadFaces_3DGS_MDL7(groupInfo, groupData); + + // sort by materials + SortByMaterials_3DGS_MDL7(groupInfo, groupData, + splitGroupData); + + for (unsigned int qq = 0; qq < sharedData.pcMats.size(); ++qq) { + if (!splitGroupData.aiSplit[qq]->empty()) + sharedData.abNeedMaterials[qq] = true; + } + } else + ASSIMP_LOG_WARN("[3DGS MDL7] Mesh group consists of 0 " + "vertices or faces. It will be skipped."); + + // process all frames and generate output meshes + ProcessFrames_3DGS_MDL7(groupInfo, groupData, sharedData, szCurrent, &szCurrent); + GenerateOutputMeshes_3DGS_MDL7(groupData, splitGroupData); + } + + // generate a nodegraph and subnodes for each group + pScene->mRootNode = new aiNode(); + + // now we need to build a final mesh list + for (uint32_t i = 0; i < pcHeader->groups_num; ++i) + pScene->mNumMeshes += (unsigned int)avOutList[i].size(); + + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + { + unsigned int p = 0, q = 0; + for (uint32_t i = 0; i < pcHeader->groups_num; ++i) { + for (unsigned int a = 0; a < avOutList[i].size(); ++a) { + pScene->mMeshes[p++] = avOutList[i][a]; + } + if (!avOutList[i].empty()) ++pScene->mRootNode->mNumChildren; + } + // we will later need an extra node to serve as parent for all bones + if (sharedData.apcOutBones) ++pScene->mRootNode->mNumChildren; + this->pScene->mRootNode->mChildren = new aiNode *[pScene->mRootNode->mNumChildren]; + p = 0; + for (uint32_t i = 0; i < pcHeader->groups_num; ++i) { + if (avOutList[i].empty()) continue; + + aiNode *const pcNode = pScene->mRootNode->mChildren[p] = new aiNode(); + pcNode->mNumMeshes = (unsigned int)avOutList[i].size(); + pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes]; + pcNode->mParent = this->pScene->mRootNode; + for (unsigned int a = 0; a < pcNode->mNumMeshes; ++a) + pcNode->mMeshes[a] = q + a; + q += (unsigned int)avOutList[i].size(); + + // setup the name of the node + char *const szBuffer = &aszGroupNameBuffer[i * AI_MDL7_MAX_GROUPNAMESIZE]; + if ('\0' == *szBuffer) { + const size_t maxSize(buffersize - (i * AI_MDL7_MAX_GROUPNAMESIZE)); + pcNode->mName.length = ai_snprintf(szBuffer, maxSize, "Group_%u", p); + } else { + pcNode->mName.length = (ai_uint32)::strlen(szBuffer); + } + ::strncpy(pcNode->mName.data, szBuffer, MAXLEN - 1); + ++p; + } + } + + // if there is only one root node with a single child we can optimize it a bit ... + if (1 == pScene->mRootNode->mNumChildren && !sharedData.apcOutBones) { + aiNode *pcOldRoot = this->pScene->mRootNode; + pScene->mRootNode = pcOldRoot->mChildren[0]; + pcOldRoot->mChildren[0] = nullptr; + delete pcOldRoot; + pScene->mRootNode->mParent = nullptr; + } else + pScene->mRootNode->mName.Set("<mesh_root>"); + + delete[] avOutList; + delete[] aszGroupNameBuffer; + AI_DEBUG_INVALIDATE_PTR(avOutList); + AI_DEBUG_INVALIDATE_PTR(aszGroupNameBuffer); + + // build a final material list. + CopyMaterials_3DGS_MDL7(sharedData); + HandleMaterialReferences_3DGS_MDL7(); + + // generate output bone animations and add all bones to the scenegraph + if (sharedData.apcOutBones) { + // this step adds empty dummy bones to the nodegraph + // insert another dummy node to avoid name conflicts + aiNode *const pc = pScene->mRootNode->mChildren[pScene->mRootNode->mNumChildren - 1] = new aiNode(); + + pc->mName.Set("<skeleton_root>"); + + // add bones to the nodegraph + AddBonesToNodeGraph_3DGS_MDL7((const Assimp::MDL::IntBone_MDL7 **) + sharedData.apcOutBones, + pc, 0xffff); + + // this steps build a valid output animation + BuildOutputAnims_3DGS_MDL7((const Assimp::MDL::IntBone_MDL7 **) + sharedData.apcOutBones); + } +} + +// ------------------------------------------------------------------------------------------------ +// Copy materials +void MDLImporter::CopyMaterials_3DGS_MDL7(MDL::IntSharedData_MDL7 &shared) { + pScene->mNumMaterials = (unsigned int)shared.pcMats.size(); + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) + pScene->mMaterials[i] = shared.pcMats[i]; +} + +// ------------------------------------------------------------------------------------------------ +// Process material references +void MDLImporter::HandleMaterialReferences_3DGS_MDL7() { + // search for referrer materials + for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) { + int iIndex = 0; + if (AI_SUCCESS == aiGetMaterialInteger(pScene->mMaterials[i], AI_MDL7_REFERRER_MATERIAL, &iIndex)) { + for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) { + aiMesh *const pcMesh = pScene->mMeshes[a]; + if (i == pcMesh->mMaterialIndex) { + pcMesh->mMaterialIndex = iIndex; + } + } + // collapse the rest of the array + delete pScene->mMaterials[i]; + for (unsigned int pp = i; pp < pScene->mNumMaterials - 1; ++pp) { + + pScene->mMaterials[pp] = pScene->mMaterials[pp + 1]; + for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) { + aiMesh *const pcMesh = pScene->mMeshes[a]; + if (pcMesh->mMaterialIndex > i) --pcMesh->mMaterialIndex; + } + } + --pScene->mNumMaterials; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Read bone transformation keys +void MDLImporter::ParseBoneTrafoKeys_3DGS_MDL7( + const MDL::IntGroupInfo_MDL7 &groupInfo, + IntFrameInfo_MDL7 &frame, + MDL::IntSharedData_MDL7 &shared) { + const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)this->mBuffer; + + // only the first group contains bone animation keys + if (frame.pcFrame->transmatrix_count) { + if (!groupInfo.iIndex) { + // skip all frames vertices. We can't support them + const MDL::BoneTransform_MDL7 *pcBoneTransforms = (const MDL::BoneTransform_MDL7 *)(((const char *)frame.pcFrame) + pcHeader->frame_stc_size + + frame.pcFrame->vertices_count * pcHeader->framevertex_stc_size); + + // read all transformation matrices + for (unsigned int iTrafo = 0; iTrafo < frame.pcFrame->transmatrix_count; ++iTrafo) { + if (pcBoneTransforms->bone_index >= pcHeader->bones_num) { + ASSIMP_LOG_WARN("Index overflow in frame area. " + "Unable to parse this bone transformation"); + } else { + AddAnimationBoneTrafoKey_3DGS_MDL7(frame.iIndex, + pcBoneTransforms, shared.apcOutBones); + } + pcBoneTransforms = (const MDL::BoneTransform_MDL7 *)((const char *)pcBoneTransforms + pcHeader->bonetrans_stc_size); + } + } else { + ASSIMP_LOG_WARN("Ignoring animation keyframes in groups != 0"); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Attach bones to the output nodegraph +void MDLImporter::AddBonesToNodeGraph_3DGS_MDL7(const MDL::IntBone_MDL7 **apcBones, + aiNode *pcParent, uint16_t iParentIndex) { + ai_assert(nullptr != apcBones); + ai_assert(nullptr != pcParent); + + // get a pointer to the header ... + const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)this->mBuffer; + + const MDL::IntBone_MDL7 **apcBones2 = apcBones; + for (uint32_t i = 0; i < pcHeader->bones_num; ++i) { + + const MDL::IntBone_MDL7 *const pcBone = *apcBones2++; + if (pcBone->iParent == iParentIndex) { + ++pcParent->mNumChildren; + } + } + pcParent->mChildren = new aiNode *[pcParent->mNumChildren]; + unsigned int qq = 0; + for (uint32_t i = 0; i < pcHeader->bones_num; ++i) { + + const MDL::IntBone_MDL7 *const pcBone = *apcBones++; + if (pcBone->iParent != iParentIndex) continue; + + aiNode *pcNode = pcParent->mChildren[qq++] = new aiNode(); + pcNode->mName = aiString(pcBone->mName); + + AddBonesToNodeGraph_3DGS_MDL7(apcBones, pcNode, (uint16_t)i); + } +} + +// ------------------------------------------------------------------------------------------------ +// Build output animations +void MDLImporter::BuildOutputAnims_3DGS_MDL7( + const MDL::IntBone_MDL7 **apcBonesOut) { + ai_assert(nullptr != apcBonesOut); + const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)mBuffer; + + // one animation ... + aiAnimation *pcAnim = new aiAnimation(); + for (uint32_t i = 0; i < pcHeader->bones_num; ++i) { + if (!apcBonesOut[i]->pkeyPositions.empty()) { + + // get the last frame ... (needn't be equal to pcHeader->frames_num) + for (size_t qq = 0; qq < apcBonesOut[i]->pkeyPositions.size(); ++qq) { + pcAnim->mDuration = std::max(pcAnim->mDuration, (double) + apcBonesOut[i] + ->pkeyPositions[qq] + .mTime); + } + ++pcAnim->mNumChannels; + } + } + if (pcAnim->mDuration) { + pcAnim->mChannels = new aiNodeAnim *[pcAnim->mNumChannels]; + + unsigned int iCnt = 0; + for (uint32_t i = 0; i < pcHeader->bones_num; ++i) { + if (!apcBonesOut[i]->pkeyPositions.empty()) { + const MDL::IntBone_MDL7 *const intBone = apcBonesOut[i]; + + aiNodeAnim *const pcNodeAnim = pcAnim->mChannels[iCnt++] = new aiNodeAnim(); + pcNodeAnim->mNodeName = aiString(intBone->mName); + + // allocate enough storage for all keys + pcNodeAnim->mNumPositionKeys = (unsigned int)intBone->pkeyPositions.size(); + pcNodeAnim->mNumScalingKeys = (unsigned int)intBone->pkeyPositions.size(); + pcNodeAnim->mNumRotationKeys = (unsigned int)intBone->pkeyPositions.size(); + + pcNodeAnim->mPositionKeys = new aiVectorKey[pcNodeAnim->mNumPositionKeys]; + pcNodeAnim->mScalingKeys = new aiVectorKey[pcNodeAnim->mNumPositionKeys]; + pcNodeAnim->mRotationKeys = new aiQuatKey[pcNodeAnim->mNumPositionKeys]; + + // copy all keys + for (unsigned int qq = 0; qq < pcNodeAnim->mNumPositionKeys; ++qq) { + pcNodeAnim->mPositionKeys[qq] = intBone->pkeyPositions[qq]; + pcNodeAnim->mScalingKeys[qq] = intBone->pkeyScalings[qq]; + pcNodeAnim->mRotationKeys[qq] = intBone->pkeyRotations[qq]; + } + } + } + + // store the output animation + pScene->mNumAnimations = 1; + pScene->mAnimations = new aiAnimation *[1]; + pScene->mAnimations[0] = pcAnim; + } else + delete pcAnim; +} + +// ------------------------------------------------------------------------------------------------ +void MDLImporter::AddAnimationBoneTrafoKey_3DGS_MDL7(unsigned int iTrafo, + const MDL::BoneTransform_MDL7 *pcBoneTransforms, + MDL::IntBone_MDL7 **apcBonesOut) { + ai_assert(nullptr != pcBoneTransforms); + ai_assert(nullptr != apcBonesOut); + + // first .. get the transformation matrix + aiMatrix4x4 mTransform; + mTransform.a1 = pcBoneTransforms->m[0]; + mTransform.b1 = pcBoneTransforms->m[1]; + mTransform.c1 = pcBoneTransforms->m[2]; + mTransform.d1 = pcBoneTransforms->m[3]; + + mTransform.a2 = pcBoneTransforms->m[4]; + mTransform.b2 = pcBoneTransforms->m[5]; + mTransform.c2 = pcBoneTransforms->m[6]; + mTransform.d2 = pcBoneTransforms->m[7]; + + mTransform.a3 = pcBoneTransforms->m[8]; + mTransform.b3 = pcBoneTransforms->m[9]; + mTransform.c3 = pcBoneTransforms->m[10]; + mTransform.d3 = pcBoneTransforms->m[11]; + + // now decompose the transformation matrix into separate + // scaling, rotation and translation + aiVectorKey vScaling, vPosition; + aiQuatKey qRotation; + + // FIXME: Decompose will assert in debug builds if the matrix is invalid ... + mTransform.Decompose(vScaling.mValue, qRotation.mValue, vPosition.mValue); + + // now generate keys + vScaling.mTime = qRotation.mTime = vPosition.mTime = (double)iTrafo; + + // add the keys to the bone + MDL::IntBone_MDL7 *const pcBoneOut = apcBonesOut[pcBoneTransforms->bone_index]; + pcBoneOut->pkeyPositions.push_back(vPosition); + pcBoneOut->pkeyScalings.push_back(vScaling); + pcBoneOut->pkeyRotations.push_back(qRotation); +} + +// ------------------------------------------------------------------------------------------------ +// Construct output meshes +void MDLImporter::GenerateOutputMeshes_3DGS_MDL7( + MDL::IntGroupData_MDL7 &groupData, + MDL::IntSplitGroupData_MDL7 &splitGroupData) { + const MDL::IntSharedData_MDL7 &shared = splitGroupData.shared; + + // get a pointer to the header ... + const MDL::Header_MDL7 *const pcHeader = (const MDL::Header_MDL7 *)this->mBuffer; + const unsigned int iNumOutBones = pcHeader->bones_num; + + for (std::vector<aiMaterial *>::size_type i = 0; i < shared.pcMats.size(); ++i) { + if (!splitGroupData.aiSplit[i]->empty()) { + + // allocate the output mesh + aiMesh *pcMesh = new aiMesh(); + + pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + pcMesh->mMaterialIndex = (unsigned int)i; + + // allocate output storage + pcMesh->mNumFaces = (unsigned int)splitGroupData.aiSplit[i]->size(); + pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; + + pcMesh->mNumVertices = pcMesh->mNumFaces * 3; + pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; + + if (!groupData.vTextureCoords1.empty()) { + pcMesh->mNumUVComponents[0] = 2; + pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; + if (!groupData.vTextureCoords2.empty()) { + pcMesh->mNumUVComponents[1] = 2; + pcMesh->mTextureCoords[1] = new aiVector3D[pcMesh->mNumVertices]; + } + } + + // iterate through all faces and build an unique set of vertices + unsigned int iCurrent = 0; + for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces; ++iFace) { + pcMesh->mFaces[iFace].mNumIndices = 3; + pcMesh->mFaces[iFace].mIndices = new unsigned int[3]; + + unsigned int iSrcFace = splitGroupData.aiSplit[i]->operator[](iFace); + const MDL::IntFace_MDL7 &oldFace = groupData.pcFaces[iSrcFace]; + + // iterate through all face indices + for (unsigned int c = 0; c < 3; ++c) { + const uint32_t iIndex = oldFace.mIndices[c]; + pcMesh->mVertices[iCurrent] = groupData.vPositions[iIndex]; + pcMesh->mNormals[iCurrent] = groupData.vNormals[iIndex]; + + if (!groupData.vTextureCoords1.empty()) { + + pcMesh->mTextureCoords[0][iCurrent] = groupData.vTextureCoords1[iIndex]; + if (!groupData.vTextureCoords2.empty()) { + pcMesh->mTextureCoords[1][iCurrent] = groupData.vTextureCoords2[iIndex]; + } + } + pcMesh->mFaces[iFace].mIndices[c] = iCurrent++; + } + } + + // if we have bones in the mesh we'll need to generate + // proper vertex weights for them + if (!groupData.aiBones.empty()) { + std::vector<std::vector<unsigned int>> aaiVWeightList; + aaiVWeightList.resize(iNumOutBones); + + int iCurrentWeight = 0; + for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces; ++iFace) { + unsigned int iSrcFace = splitGroupData.aiSplit[i]->operator[](iFace); + const MDL::IntFace_MDL7 &oldFace = groupData.pcFaces[iSrcFace]; + + // iterate through all face indices + for (unsigned int c = 0; c < 3; ++c) { + unsigned int iBone = groupData.aiBones[oldFace.mIndices[c]]; + if (UINT_MAX != iBone) { + if (iBone >= iNumOutBones) { + ASSIMP_LOG_ERROR("Bone index overflow. " + "The bone index of a vertex exceeds the allowed range. "); + iBone = iNumOutBones - 1; + } + aaiVWeightList[iBone].push_back(iCurrentWeight); + } + ++iCurrentWeight; + } + } + // now check which bones are required ... + for (std::vector<std::vector<unsigned int>>::const_iterator k = aaiVWeightList.begin(); k != aaiVWeightList.end(); ++k) { + if (!(*k).empty()) { + ++pcMesh->mNumBones; + } + } + pcMesh->mBones = new aiBone *[pcMesh->mNumBones]; + iCurrent = 0; + for (std::vector<std::vector<unsigned int>>::const_iterator k = aaiVWeightList.begin(); k != aaiVWeightList.end(); ++k, ++iCurrent) { + if ((*k).empty()) + continue; + + // seems we'll need this node + aiBone *pcBone = pcMesh->mBones[iCurrent] = new aiBone(); + pcBone->mName = aiString(shared.apcOutBones[iCurrent]->mName); + pcBone->mOffsetMatrix = shared.apcOutBones[iCurrent]->mOffsetMatrix; + + // setup vertex weights + pcBone->mNumWeights = (unsigned int)(*k).size(); + pcBone->mWeights = new aiVertexWeight[pcBone->mNumWeights]; + + for (unsigned int weight = 0; weight < pcBone->mNumWeights; ++weight) { + pcBone->mWeights[weight].mVertexId = (*k)[weight]; + pcBone->mWeights[weight].mWeight = 1.0f; + } + } + } + // add the mesh to the list of output meshes + splitGroupData.avOutList.push_back(pcMesh); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Join to materials +void MDLImporter::JoinSkins_3DGS_MDL7( + aiMaterial *pcMat1, + aiMaterial *pcMat2, + aiMaterial *pcMatOut) { + ai_assert(nullptr != pcMat1); + ai_assert(nullptr != pcMat2); + ai_assert(nullptr != pcMatOut); + + // first create a full copy of the first skin property set + // and assign it to the output material + aiMaterial::CopyPropertyList(pcMatOut, pcMat1); + + int iVal = 0; + pcMatOut->AddProperty<int>(&iVal, 1, AI_MATKEY_UVWSRC_DIFFUSE(0)); + + // then extract the diffuse texture from the second skin, + // setup 1 as UV source and we have it + aiString sString; + if (AI_SUCCESS == aiGetMaterialString(pcMat2, AI_MATKEY_TEXTURE_DIFFUSE(0), &sString)) { + iVal = 1; + pcMatOut->AddProperty<int>(&iVal, 1, AI_MATKEY_UVWSRC_DIFFUSE(1)); + pcMatOut->AddProperty(&sString, AI_MATKEY_TEXTURE_DIFFUSE(1)); + } +} + +// ------------------------------------------------------------------------------------------------ +// Read a Half-life 1 MDL +void MDLImporter::InternReadFile_HL1(const std::string &pFile, const uint32_t iMagicWord) { + // We can't correctly load an MDL from a MDL "sequence" file. + if (iMagicWord == AI_MDL_MAGIC_NUMBER_BE_HL2b || iMagicWord == AI_MDL_MAGIC_NUMBER_LE_HL2b) + throw DeadlyImportError("Impossible to properly load a model from an MDL sequence file."); + + // Read the MDL file. + HalfLife::HL1MDLLoader loader( + pScene, + mIOHandler, + mBuffer, + pFile, + mHL1ImportSettings); +} + +// ------------------------------------------------------------------------------------------------ +// Read a half-life 2 MDL +void MDLImporter::InternReadFile_HL2() { + //const MDL::Header_HL2* pcHeader = (const MDL::Header_HL2*)this->mBuffer; + throw DeadlyImportError("HL2 MDLs are not implemented"); +} + +#endif // !! ASSIMP_BUILD_NO_MDL_IMPORTER diff --git a/libs/assimp/code/AssetLib/MDL/MDLLoader.h b/libs/assimp/code/AssetLib/MDL/MDLLoader.h new file mode 100644 index 0000000..b7d87e8 --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/MDLLoader.h @@ -0,0 +1,451 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MDLLoader.h + * @brief Declaration of the loader for MDL files + */ +#pragma once +#ifndef AI_MDLLOADER_H_INCLUDED +#define AI_MDLLOADER_H_INCLUDED + +#include <assimp/BaseImporter.h> +#include "MDLFileData.h" +#include "AssetLib/HMP/HalfLifeFileData.h" +#include "AssetLib/MDL/HalfLife/HL1ImportSettings.h" + +struct aiNode; +struct aiTexture; + +namespace Assimp { + +using namespace MDL; + +// -------------------------------------------------------------------------------------- +// Include file/line information in debug builds +#ifdef ASSIMP_BUILD_DEBUG +# define VALIDATE_FILE_SIZE(msg) SizeCheck(msg,__FILE__,__LINE__) +#else +# define VALIDATE_FILE_SIZE(msg) SizeCheck(msg) +#endif + +// -------------------------------------------------------------------------------------- +/** @brief Class to load MDL files. + * + * Several subformats exist: + * <ul> + * <li>Quake I</li> + * <li>3D Game Studio MDL3, MDL4</li> + * <li>3D Game Studio MDL5</li> + * <li>3D Game Studio MDL7</li> + * <li>Halflife 1</li> + * <li>Halflife 2</li> + * </ul> + * These formats are partially identical and it would be possible to load + * them all with a single 1000-line function-beast. However, it has been + * split into several code paths to make the code easier to read and maintain. +*/ +class MDLImporter : public BaseImporter +{ +public: + MDLImporter(); + ~MDLImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool checkSig) const override; + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer* pImp) override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc* GetInfo () const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler) override; + + // ------------------------------------------------------------------- + /** Import a quake 1 MDL file (IDPO) + */ + void InternReadFile_Quake1( ); + + // ------------------------------------------------------------------- + /** Import a GameStudio A4/A5 file (MDL 3,4,5) + */ + void InternReadFile_3DGS_MDL345( ); + + // ------------------------------------------------------------------- + /** Import a GameStudio A7 file (MDL 7) + */ + void InternReadFile_3DGS_MDL7( ); + + // ------------------------------------------------------------------- + /** Import a Half-Life 1 MDL file + */ + void InternReadFile_HL1(const std::string& pFile, const uint32_t iMagicWord); + + // ------------------------------------------------------------------- + /** Import a CS:S/HL2 MDL file (not fully implemented) + */ + void InternReadFile_HL2( ); + + // ------------------------------------------------------------------- + /** Check whether a given position is inside the valid range + * Throw a DeadlyImportError if it is not + * \param szPos Cursor position + * \param szFile Name of the source file from which the function was called + * \param iLine Source code line from which the function was called + */ + void SizeCheck(const void* szPos); + void SizeCheck(const void* szPos, const char* szFile, unsigned int iLine); + + // ------------------------------------------------------------------- + /** Validate the header data structure of a game studio MDL7 file + * \param pcHeader Input header to be validated + */ + void ValidateHeader_3DGS_MDL7(const MDL::Header_MDL7* pcHeader); + + // ------------------------------------------------------------------- + /** Validate the header data structure of a Quake 1 model + * \param pcHeader Input header to be validated + */ + void ValidateHeader_Quake1(const MDL::Header* pcHeader); + + // ------------------------------------------------------------------- + /** Try to load a palette from the current directory (colormap.lmp) + * If it is not found the default palette of Quake1 is returned + */ + void SearchPalette(const unsigned char** pszColorMap); + + // ------------------------------------------------------------------- + /** Free a palette created with a previous call to SearchPalette() + */ + void FreePalette(const unsigned char* pszColorMap); + + // ------------------------------------------------------------------- + /** Load a palletized texture from the file and convert it to 32bpp + */ + void CreateTextureARGB8_3DGS_MDL3(const unsigned char* szData); + + // ------------------------------------------------------------------- + /** Used to load textures from MDL3/4 + * \param szData Input data + * \param iType Color data type + * \param piSkip Receive: Size to skip, in bytes + */ + void CreateTexture_3DGS_MDL4(const unsigned char* szData, + unsigned int iType, + unsigned int* piSkip); + + // ------------------------------------------------------------------- + /** Used to load textures from MDL5 + * \param szData Input data + * \param iType Color data type + * \param piSkip Receive: Size to skip, in bytes + */ + void CreateTexture_3DGS_MDL5(const unsigned char* szData, + unsigned int iType, + unsigned int* piSkip); + + // ------------------------------------------------------------------- + /** Checks whether a texture can be replaced with a single color + * This is useful for all file formats before MDL7 (all those + * that are not containing material colors separate from textures). + * MED seems to write dummy 8x8 monochrome images instead. + * \param pcTexture Input texture + * \return aiColor.r is set to qnan if the function fails and no + * color can be found. + */ + aiColor4D ReplaceTextureWithColor(const aiTexture* pcTexture); + + // ------------------------------------------------------------------- + /** Converts the absolute texture coordinates in MDL5 files to + * relative in a range between 0 and 1 + */ + void CalculateUVCoordinates_MDL5(); + + // ------------------------------------------------------------------- + /** Read an UV coordinate from the file. If the file format is not + * MDL5, the function calculates relative texture coordinates + * \param vOut Receives the output UV coord + * \param pcSrc UV coordinate buffer + * \param UV coordinate index + */ + void ImportUVCoordinate_3DGS_MDL345( aiVector3D& vOut, + const MDL::TexCoord_MDL3* pcSrc, + unsigned int iIndex); + + // ------------------------------------------------------------------- + /** Setup the material properties for Quake and MDL<7 models. + * These formats don't support more than one material per mesh, + * therefore the method processes only ONE skin and removes + * all others. + */ + void SetupMaterialProperties_3DGS_MDL5_Quake1( ); + + // ------------------------------------------------------------------- + /** Parse a skin lump in a MDL7/HMP7 file with all of its features + * variant 1: Current cursor position is the beginning of the skin header + * \param szCurrent Current data pointer + * \param szCurrentOut Output data pointer + * \param pcMats Material list for this group. To be filled ... + */ + void ParseSkinLump_3DGS_MDL7( + const unsigned char* szCurrent, + const unsigned char** szCurrentOut, + std::vector<aiMaterial*>& pcMats); + + // ------------------------------------------------------------------- + /** Parse a skin lump in a MDL7/HMP7 file with all of its features + * variant 2: Current cursor position is the beginning of the skin data + * \param szCurrent Current data pointer + * \param szCurrentOut Output data pointer + * \param pcMatOut Output material + * \param iType header.typ + * \param iWidth header.width + * \param iHeight header.height + */ + void ParseSkinLump_3DGS_MDL7( + const unsigned char* szCurrent, + const unsigned char** szCurrentOut, + aiMaterial* pcMatOut, + unsigned int iType, + unsigned int iWidth, + unsigned int iHeight); + + // ------------------------------------------------------------------- + /** Skip a skin lump in a MDL7/HMP7 file + * \param szCurrent Current data pointer + * \param szCurrentOut Output data pointer. Points to the byte just + * behind the last byte of the skin. + * \param iType header.typ + * \param iWidth header.width + * \param iHeight header.height + */ + void SkipSkinLump_3DGS_MDL7(const unsigned char* szCurrent, + const unsigned char** szCurrentOut, + unsigned int iType, + unsigned int iWidth, + unsigned int iHeight); + + // ------------------------------------------------------------------- + /** Parse texture color data for MDL5, MDL6 and MDL7 formats + * \param szData Current data pointer + * \param iType type of the texture data. No DDS or external + * \param piSkip Receive the number of bytes to skip + * \param pcNew Must point to fully initialized data. Width and + * height must be set. If pcNew->pcData is set to UINT_MAX, + * piSkip will receive the size of the texture, in bytes, but no + * color data will be read. + */ + void ParseTextureColorData(const unsigned char* szData, + unsigned int iType, + unsigned int* piSkip, + aiTexture* pcNew); + + // ------------------------------------------------------------------- + /** Join two materials / skins. Setup UV source ... etc + * \param pcMat1 First input material + * \param pcMat2 Second input material + * \param pcMatOut Output material instance to be filled. Must be empty + */ + void JoinSkins_3DGS_MDL7(aiMaterial* pcMat1, + aiMaterial* pcMat2, + aiMaterial* pcMatOut); + + // ------------------------------------------------------------------- + /** Add a bone transformation key to an animation + * \param iTrafo Index of the transformation (always==frame index?) + * No need to validate this index, it is always valid. + * \param pcBoneTransforms Bone transformation for this index + * \param apcOutBones Output bones array + */ + void AddAnimationBoneTrafoKey_3DGS_MDL7(unsigned int iTrafo, + const MDL::BoneTransform_MDL7* pcBoneTransforms, + MDL::IntBone_MDL7** apcBonesOut); + + // ------------------------------------------------------------------- + /** Load the bone list of a MDL7 file + * \return If the bones could be loaded successfully, a valid + * array containing pointers to a temporary bone + * representation. nullptr if the bones could not be loaded. + */ + MDL::IntBone_MDL7** LoadBones_3DGS_MDL7(); + + // ------------------------------------------------------------------- + /** Load bone transformation keyframes from a file chunk + * \param groupInfo -> doc of data structure + * \param frame -> doc of data structure + * \param shared -> doc of data structure + */ + void ParseBoneTrafoKeys_3DGS_MDL7( + const MDL::IntGroupInfo_MDL7& groupInfo, + IntFrameInfo_MDL7& frame, + MDL::IntSharedData_MDL7& shared); + + // ------------------------------------------------------------------- + /** Calculate absolute bone animation matrices for each bone + * \param apcOutBones Output bones array + */ + void CalcAbsBoneMatrices_3DGS_MDL7(MDL::IntBone_MDL7** apcOutBones); + + // ------------------------------------------------------------------- + /** Add all bones to the nodegraph (as children of the root node) + * \param apcBonesOut List of bones + * \param pcParent Parent node. New nodes will be added to this node + * \param iParentIndex Index of the parent bone + */ + void AddBonesToNodeGraph_3DGS_MDL7(const MDL::IntBone_MDL7** apcBonesOut, + aiNode* pcParent,uint16_t iParentIndex); + + // ------------------------------------------------------------------- + /** Build output animations + * \param apcBonesOut List of bones + */ + void BuildOutputAnims_3DGS_MDL7(const MDL::IntBone_MDL7** apcBonesOut); + + // ------------------------------------------------------------------- + /** Handles materials that are just referencing another material + * There is no test file for this feature, but Conitec's doc + * say it is used. + */ + void HandleMaterialReferences_3DGS_MDL7(); + + // ------------------------------------------------------------------- + /** Copies only the material that are referenced by at least one + * mesh to the final output material list. All other materials + * will be discarded. + * \param shared -> doc of data structure + */ + void CopyMaterials_3DGS_MDL7(MDL::IntSharedData_MDL7 &shared); + + // ------------------------------------------------------------------- + /** Process the frame section at the end of a group + * \param groupInfo -> doc of data structure + * \param shared -> doc of data structure + * \param szCurrent Pointer to the start of the frame section + * \param szCurrentOut Receives a pointer to the first byte of the + * next data section. + * \return false to read no further groups (a small workaround for + * some tiny and unsolved problems ... ) + */ + bool ProcessFrames_3DGS_MDL7(const MDL::IntGroupInfo_MDL7& groupInfo, + MDL::IntGroupData_MDL7& groupData, + MDL::IntSharedData_MDL7& shared, + const unsigned char* szCurrent, + const unsigned char** szCurrentOut); + + // ------------------------------------------------------------------- + /** Sort all faces by their materials. If the mesh is using + * multiple materials per face (that are blended together) the function + * might create new materials. + * \param groupInfo -> doc of data structure + * \param groupData -> doc of data structure + * \param splitGroupData -> doc of data structure + */ + void SortByMaterials_3DGS_MDL7( + const MDL::IntGroupInfo_MDL7& groupInfo, + MDL::IntGroupData_MDL7& groupData, + MDL::IntSplitGroupData_MDL7& splitGroupData); + + // ------------------------------------------------------------------- + /** Read all faces and vertices from a MDL7 group. The function fills + * preallocated memory buffers. + * \param groupInfo -> doc of data structure + * \param groupData -> doc of data structure + */ + void ReadFaces_3DGS_MDL7(const MDL::IntGroupInfo_MDL7& groupInfo, + MDL::IntGroupData_MDL7& groupData); + + // ------------------------------------------------------------------- + /** Generate the final output meshes for a7 models + * \param groupData -> doc of data structure + * \param splitGroupData -> doc of data structure + */ + void GenerateOutputMeshes_3DGS_MDL7( + MDL::IntGroupData_MDL7& groupData, + MDL::IntSplitGroupData_MDL7& splitGroupData); + +protected: + + /** Configuration option: frame to be loaded */ + unsigned int configFrameID; + + /** Configuration option: palette to be used to decode palletized images*/ + std::string configPalette; + + /** Buffer to hold the loaded file */ + unsigned char* mBuffer; + + /** For GameStudio MDL files: The number in the magic word, either 3,4 or 5 + * (MDL7 doesn't need this, the format has a separate loader) */ + unsigned int iGSFileVersion; + + /** Output I/O handler. used to load external lmp files */ + IOSystem* mIOHandler; + + /** Output scene to be filled */ + aiScene* pScene; + + /** Size of the input file in bytes */ + unsigned int iFileSize; + + /* Configuration for HL1 MDL */ + HalfLife::HL1ImportSettings mHL1ImportSettings; +}; + +} // end of namespace Assimp + +#endif // AI_3DSIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/MDL/MDLMaterialLoader.cpp b/libs/assimp/code/AssetLib/MDL/MDLMaterialLoader.cpp new file mode 100644 index 0000000..eebb9d1 --- /dev/null +++ b/libs/assimp/code/AssetLib/MDL/MDLMaterialLoader.cpp @@ -0,0 +1,765 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the material part of the MDL importer class */ + +#ifndef ASSIMP_BUILD_NO_MDL_IMPORTER + +#include "MDLDefaultColorMap.h" +#include "MDLLoader.h" + +#include <assimp/StringUtils.h> +#include <assimp/qnan.h> +#include <assimp/scene.h> +#include <assimp/texture.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> + +#include <memory> + +using namespace Assimp; + +static aiTexel *const bad_texel = reinterpret_cast<aiTexel *>(SIZE_MAX); + +// ------------------------------------------------------------------------------------------------ +// Find a suitable palette file or take the default one +void MDLImporter::SearchPalette(const unsigned char **pszColorMap) { + // now try to find the color map in the current directory + IOStream *pcStream = mIOHandler->Open(configPalette, "rb"); + + const unsigned char *szColorMap = (const unsigned char *)::g_aclrDefaultColorMap; + if (pcStream) { + if (pcStream->FileSize() >= 768) { + size_t len = 256 * 3; + unsigned char *colorMap = new unsigned char[len]; + szColorMap = colorMap; + pcStream->Read(colorMap, len, 1); + ASSIMP_LOG_INFO("Found valid colormap.lmp in directory. " + "It will be used to decode embedded textures in palletized formats."); + } + delete pcStream; + pcStream = nullptr; + } + *pszColorMap = szColorMap; +} + +// ------------------------------------------------------------------------------------------------ +// Free the palette again +void MDLImporter::FreePalette(const unsigned char *szColorMap) { + if (szColorMap != (const unsigned char *)::g_aclrDefaultColorMap) { + delete[] szColorMap; + } +} + +// ------------------------------------------------------------------------------------------------ +// Check whether we can replace a texture with a single color +aiColor4D MDLImporter::ReplaceTextureWithColor(const aiTexture *pcTexture) { + ai_assert(nullptr != pcTexture); + + aiColor4D clrOut; + clrOut.r = get_qnan(); + if (!pcTexture->mHeight || !pcTexture->mWidth) + return clrOut; + + const unsigned int iNumPixels = pcTexture->mHeight * pcTexture->mWidth; + const aiTexel *pcTexel = pcTexture->pcData + 1; + const aiTexel *const pcTexelEnd = &pcTexture->pcData[iNumPixels]; + + while (pcTexel != pcTexelEnd) { + if (*pcTexel != *(pcTexel - 1)) { + pcTexel = nullptr; + break; + } + ++pcTexel; + } + if (pcTexel) { + clrOut.r = pcTexture->pcData->r / 255.0f; + clrOut.g = pcTexture->pcData->g / 255.0f; + clrOut.b = pcTexture->pcData->b / 255.0f; + clrOut.a = pcTexture->pcData->a / 255.0f; + } + return clrOut; +} + +// ------------------------------------------------------------------------------------------------ +// Read a texture from a MDL3 file +void MDLImporter::CreateTextureARGB8_3DGS_MDL3(const unsigned char *szData) { + const MDL::Header *pcHeader = (const MDL::Header *)mBuffer; //the endianness is already corrected in the InternReadFile_3DGS_MDL345 function + + VALIDATE_FILE_SIZE(szData + pcHeader->skinwidth * + pcHeader->skinheight); + + // allocate a new texture object + aiTexture *pcNew = new aiTexture(); + pcNew->mWidth = pcHeader->skinwidth; + pcNew->mHeight = pcHeader->skinheight; + + if(pcNew->mWidth != 0 && pcNew->mHeight > UINT_MAX/pcNew->mWidth) { + throw DeadlyImportError("Invalid MDL file. A texture is too big."); + } + pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight]; + + const unsigned char *szColorMap; + this->SearchPalette(&szColorMap); + + // copy texture data + for (unsigned int i = 0; i < pcNew->mWidth * pcNew->mHeight; ++i) { + const unsigned char val = szData[i]; + const unsigned char *sz = &szColorMap[val * 3]; + + pcNew->pcData[i].a = 0xFF; + pcNew->pcData[i].r = *sz++; + pcNew->pcData[i].g = *sz++; + pcNew->pcData[i].b = *sz; + } + + FreePalette(szColorMap); + + // store the texture + aiTexture **pc = this->pScene->mTextures; + this->pScene->mTextures = new aiTexture *[pScene->mNumTextures + 1]; + for (unsigned int i = 0; i < pScene->mNumTextures; ++i) + pScene->mTextures[i] = pc[i]; + + pScene->mTextures[this->pScene->mNumTextures] = pcNew; + pScene->mNumTextures++; + delete[] pc; +} + +// ------------------------------------------------------------------------------------------------ +// Read a texture from a MDL4 file +void MDLImporter::CreateTexture_3DGS_MDL4(const unsigned char *szData, + unsigned int iType, + unsigned int *piSkip) { + ai_assert(nullptr != piSkip); + + const MDL::Header *pcHeader = (const MDL::Header *)mBuffer; //the endianness is already corrected in the InternReadFile_3DGS_MDL345 function + + if (iType == 1 || iType > 3) { + ASSIMP_LOG_ERROR("Unsupported texture file format"); + return; + } + + const bool bNoRead = *piSkip == UINT_MAX; + + // allocate a new texture object + aiTexture *pcNew = new aiTexture(); + pcNew->mWidth = pcHeader->skinwidth; + pcNew->mHeight = pcHeader->skinheight; + + if (bNoRead) pcNew->pcData = bad_texel; + ParseTextureColorData(szData, iType, piSkip, pcNew); + + // store the texture + if (!bNoRead) { + if (!this->pScene->mNumTextures) { + pScene->mNumTextures = 1; + pScene->mTextures = new aiTexture *[1]; + pScene->mTextures[0] = pcNew; + } else { + aiTexture **pc = pScene->mTextures; + pScene->mTextures = new aiTexture *[pScene->mNumTextures + 1]; + for (unsigned int i = 0; i < this->pScene->mNumTextures; ++i) + pScene->mTextures[i] = pc[i]; + pScene->mTextures[pScene->mNumTextures] = pcNew; + pScene->mNumTextures++; + delete[] pc; + } + } else { + pcNew->pcData = nullptr; + delete pcNew; + } + return; +} + +// ------------------------------------------------------------------------------------------------ +// Load color data of a texture and convert it to our output format +void MDLImporter::ParseTextureColorData(const unsigned char *szData, + unsigned int iType, + unsigned int *piSkip, + aiTexture *pcNew) { + const bool do_read = bad_texel != pcNew->pcData; + + // allocate storage for the texture image + if (do_read) { + if(pcNew->mWidth != 0 && pcNew->mHeight > UINT_MAX/pcNew->mWidth) { + throw DeadlyImportError("Invalid MDL file. A texture is too big."); + } + pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight]; + } + + // R5G6B5 format (with or without MIPs) + // **************************************************************** + if (2 == iType || 10 == iType) { + VALIDATE_FILE_SIZE(szData + pcNew->mWidth * pcNew->mHeight * 2); + + // copy texture data + unsigned int i; + if (do_read) { + for (i = 0; i < pcNew->mWidth * pcNew->mHeight; ++i) { + MDL::RGB565 val = ((MDL::RGB565 *)szData)[i]; + AI_SWAP2(val); + + pcNew->pcData[i].a = 0xFF; + pcNew->pcData[i].r = (unsigned char)val.b << 3; + pcNew->pcData[i].g = (unsigned char)val.g << 2; + pcNew->pcData[i].b = (unsigned char)val.r << 3; + } + } else { + i = pcNew->mWidth * pcNew->mHeight; + } + *piSkip = i * 2; + + // apply MIP maps + if (10 == iType) { + *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) << 1; + VALIDATE_FILE_SIZE(szData + *piSkip); + } + } + // ARGB4 format (with or without MIPs) + // **************************************************************** + else if (3 == iType || 11 == iType) { + VALIDATE_FILE_SIZE(szData + pcNew->mWidth * pcNew->mHeight * 4); + + // copy texture data + unsigned int i; + if (do_read) { + for (i = 0; i < pcNew->mWidth * pcNew->mHeight; ++i) { + MDL::ARGB4 val = ((MDL::ARGB4 *)szData)[i]; + AI_SWAP2(val); + + pcNew->pcData[i].a = (unsigned char)val.a << 4; + pcNew->pcData[i].r = (unsigned char)val.r << 4; + pcNew->pcData[i].g = (unsigned char)val.g << 4; + pcNew->pcData[i].b = (unsigned char)val.b << 4; + } + } else + i = pcNew->mWidth * pcNew->mHeight; + *piSkip = i * 2; + + // apply MIP maps + if (11 == iType) { + *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) << 1; + VALIDATE_FILE_SIZE(szData + *piSkip); + } + } + // RGB8 format (with or without MIPs) + // **************************************************************** + else if (4 == iType || 12 == iType) { + VALIDATE_FILE_SIZE(szData + pcNew->mWidth * pcNew->mHeight * 3); + + // copy texture data + unsigned int i; + if (do_read) { + for (i = 0; i < pcNew->mWidth * pcNew->mHeight; ++i) { + const unsigned char *_szData = &szData[i * 3]; + + pcNew->pcData[i].a = 0xFF; + pcNew->pcData[i].b = *_szData++; + pcNew->pcData[i].g = *_szData++; + pcNew->pcData[i].r = *_szData; + } + } else + i = pcNew->mWidth * pcNew->mHeight; + + // apply MIP maps + *piSkip = i * 3; + if (12 == iType) { + *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) * 3; + VALIDATE_FILE_SIZE(szData + *piSkip); + } + } + // ARGB8 format (with ir without MIPs) + // **************************************************************** + else if (5 == iType || 13 == iType) { + VALIDATE_FILE_SIZE(szData + pcNew->mWidth * pcNew->mHeight * 4); + + // copy texture data + unsigned int i; + if (do_read) { + for (i = 0; i < pcNew->mWidth * pcNew->mHeight; ++i) { + const unsigned char *_szData = &szData[i * 4]; + + pcNew->pcData[i].b = *_szData++; + pcNew->pcData[i].g = *_szData++; + pcNew->pcData[i].r = *_szData++; + pcNew->pcData[i].a = *_szData; + } + } else { + i = pcNew->mWidth * pcNew->mHeight; + } + + // apply MIP maps + *piSkip = i << 2; + if (13 == iType) { + *piSkip += ((i >> 2) + (i >> 4) + (i >> 6)) << 2; + } + } + // palletized 8 bit texture. As for Quake 1 + // **************************************************************** + else if (0 == iType) { + VALIDATE_FILE_SIZE(szData + pcNew->mWidth * pcNew->mHeight); + + // copy texture data + unsigned int i; + if (do_read) { + + const unsigned char *szColorMap; + SearchPalette(&szColorMap); + + for (i = 0; i < pcNew->mWidth * pcNew->mHeight; ++i) { + const unsigned char val = szData[i]; + const unsigned char *sz = &szColorMap[val * 3]; + + pcNew->pcData[i].a = 0xFF; + pcNew->pcData[i].r = *sz++; + pcNew->pcData[i].g = *sz++; + pcNew->pcData[i].b = *sz; + } + this->FreePalette(szColorMap); + + } else + i = pcNew->mWidth * pcNew->mHeight; + *piSkip = i; + + // FIXME: Also support for MIP maps? + } +} + +// ------------------------------------------------------------------------------------------------ +// Get a texture from a MDL5 file +void MDLImporter::CreateTexture_3DGS_MDL5(const unsigned char *szData, + unsigned int iType, + unsigned int *piSkip) { + ai_assert(nullptr != piSkip); + bool bNoRead = *piSkip == UINT_MAX; + + // allocate a new texture object + aiTexture *pcNew = new aiTexture(); + + VALIDATE_FILE_SIZE(szData + 8); + + // first read the size of the texture + pcNew->mWidth = *((uint32_t *)szData); + AI_SWAP4(pcNew->mWidth); + szData += sizeof(uint32_t); + + pcNew->mHeight = *((uint32_t *)szData); + AI_SWAP4(pcNew->mHeight); + szData += sizeof(uint32_t); + + if (bNoRead) { + pcNew->pcData = bad_texel; + } + + // this should not occur - at least the docs say it shouldn't. + // however, one can easily try out what MED does if you have + // a model with a DDS texture and export it to MDL5 ... + // yeah, it embeds the DDS file. + if (6 == iType) { + // this is a compressed texture in DDS format + *piSkip = pcNew->mWidth; + VALIDATE_FILE_SIZE(szData + *piSkip); + + if (!bNoRead) { + // place a hint and let the application know that this is a DDS file + pcNew->mHeight = 0; + pcNew->achFormatHint[0] = 'd'; + pcNew->achFormatHint[1] = 'd'; + pcNew->achFormatHint[2] = 's'; + pcNew->achFormatHint[3] = '\0'; + + pcNew->pcData = (aiTexel *)new unsigned char[pcNew->mWidth]; + ::memcpy(pcNew->pcData, szData, pcNew->mWidth); + } + } else { + // parse the color data of the texture + ParseTextureColorData(szData, iType, piSkip, pcNew); + } + *piSkip += sizeof(uint32_t) * 2; + + if (!bNoRead) { + // store the texture + if (!this->pScene->mNumTextures) { + pScene->mNumTextures = 1; + pScene->mTextures = new aiTexture *[1]; + pScene->mTextures[0] = pcNew; + } else { + aiTexture **pc = pScene->mTextures; + pScene->mTextures = new aiTexture *[pScene->mNumTextures + 1]; + for (unsigned int i = 0; i < pScene->mNumTextures; ++i) + this->pScene->mTextures[i] = pc[i]; + + pScene->mTextures[pScene->mNumTextures] = pcNew; + pScene->mNumTextures++; + delete[] pc; + } + } else { + pcNew->pcData = nullptr; + delete pcNew; + } + return; +} + +// ------------------------------------------------------------------------------------------------ +// Get a skin from a MDL7 file - more complex than all other subformats +void MDLImporter::ParseSkinLump_3DGS_MDL7( + const unsigned char *szCurrent, + const unsigned char **szCurrentOut, + aiMaterial *pcMatOut, + unsigned int iType, + unsigned int iWidth, + unsigned int iHeight) { + std::unique_ptr<aiTexture> pcNew; + + // get the type of the skin + unsigned int iMasked = (unsigned int)(iType & 0xF); + + if (0x1 == iMasked) { + // ***** REFERENCE TO ANOTHER SKIN INDEX ***** + int referrer = (int)iWidth; + pcMatOut->AddProperty<int>(&referrer, 1, AI_MDL7_REFERRER_MATERIAL); + } else if (0x6 == iMasked) { + // ***** EMBEDDED DDS FILE ***** + if (1 != iHeight) { + ASSIMP_LOG_WARN("Found a reference to an embedded DDS texture, " + "but texture height is not equal to 1, which is not supported by MED"); + } + if (iWidth == 0) { + ASSIMP_LOG_ERROR("Found a reference to an embedded DDS texture, but texture width is zero, aborting import."); + return; + } + + pcNew.reset(new aiTexture); + pcNew->mHeight = 0; + pcNew->mWidth = iWidth; + + // place a proper format hint + pcNew->achFormatHint[0] = 'd'; + pcNew->achFormatHint[1] = 'd'; + pcNew->achFormatHint[2] = 's'; + pcNew->achFormatHint[3] = '\0'; + + pcNew->pcData = (aiTexel *)new unsigned char[pcNew->mWidth]; + memcpy(pcNew->pcData, szCurrent, pcNew->mWidth); + szCurrent += iWidth; + } else if (0x7 == iMasked) { + // ***** REFERENCE TO EXTERNAL FILE ***** + if (1 != iHeight) { + ASSIMP_LOG_WARN("Found a reference to an external texture, " + "but texture height is not equal to 1, which is not supported by MED"); + } + + aiString szFile; + const size_t iLen = strlen((const char *)szCurrent); + size_t iLen2 = iLen + 1; + iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2; + memcpy(szFile.data, (const char *)szCurrent, iLen2); + szFile.length = (ai_uint32)iLen; + + szCurrent += iLen2; + + // place this as diffuse texture + pcMatOut->AddProperty(&szFile, AI_MATKEY_TEXTURE_DIFFUSE(0)); + } else if (iMasked || !iType || (iType && iWidth && iHeight)) { + pcNew.reset(new aiTexture()); + if (!iHeight || !iWidth) { + ASSIMP_LOG_WARN("Found embedded texture, but its width " + "an height are both 0. Is this a joke?"); + + // generate an empty chess pattern + pcNew->mWidth = pcNew->mHeight = 8; + pcNew->pcData = new aiTexel[64]; + for (unsigned int x = 0; x < 8; ++x) { + for (unsigned int y = 0; y < 8; ++y) { + const bool bSet = ((0 == x % 2 && 0 != y % 2) || + (0 != x % 2 && 0 == y % 2)); + + aiTexel *pc = &pcNew->pcData[y * 8 + x]; + pc->r = pc->b = pc->g = (bSet ? 0xFF : 0); + pc->a = 0xFF; + } + } + } else { + // it is a standard color texture. Fill in width and height + // and call the same function we used for loading MDL5 files + + pcNew->mWidth = iWidth; + pcNew->mHeight = iHeight; + + unsigned int iSkip = 0; + ParseTextureColorData(szCurrent, iMasked, &iSkip, pcNew.get()); + + // skip length of texture data + szCurrent += iSkip; + } + } + + // sometimes there are MDL7 files which have a monochrome + // texture instead of material colors ... possible they have + // been converted to MDL7 from other formats, such as MDL5 + aiColor4D clrTexture; + if (pcNew) + clrTexture = ReplaceTextureWithColor(pcNew.get()); + else + clrTexture.r = get_qnan(); + + // check whether a material definition is contained in the skin + if (iType & AI_MDL7_SKINTYPE_MATERIAL) { + BE_NCONST MDL::Material_MDL7 *pcMatIn = (BE_NCONST MDL::Material_MDL7 *)szCurrent; + szCurrent = (unsigned char *)(pcMatIn + 1); + VALIDATE_FILE_SIZE(szCurrent); + + aiColor3D clrTemp; + +#define COLOR_MULTIPLY_RGB() \ + if (is_not_qnan(clrTexture.r)) { \ + clrTemp.r *= clrTexture.r; \ + clrTemp.g *= clrTexture.g; \ + clrTemp.b *= clrTexture.b; \ + } + + // read diffuse color + clrTemp.r = pcMatIn->Diffuse.r; + AI_SWAP4(clrTemp.r); + clrTemp.g = pcMatIn->Diffuse.g; + AI_SWAP4(clrTemp.g); + clrTemp.b = pcMatIn->Diffuse.b; + AI_SWAP4(clrTemp.b); + COLOR_MULTIPLY_RGB(); + pcMatOut->AddProperty<aiColor3D>(&clrTemp, 1, AI_MATKEY_COLOR_DIFFUSE); + + // read specular color + clrTemp.r = pcMatIn->Specular.r; + AI_SWAP4(clrTemp.r); + clrTemp.g = pcMatIn->Specular.g; + AI_SWAP4(clrTemp.g); + clrTemp.b = pcMatIn->Specular.b; + AI_SWAP4(clrTemp.b); + COLOR_MULTIPLY_RGB(); + pcMatOut->AddProperty<aiColor3D>(&clrTemp, 1, AI_MATKEY_COLOR_SPECULAR); + + // read ambient color + clrTemp.r = pcMatIn->Ambient.r; + AI_SWAP4(clrTemp.r); + clrTemp.g = pcMatIn->Ambient.g; + AI_SWAP4(clrTemp.g); + clrTemp.b = pcMatIn->Ambient.b; + AI_SWAP4(clrTemp.b); + COLOR_MULTIPLY_RGB(); + pcMatOut->AddProperty<aiColor3D>(&clrTemp, 1, AI_MATKEY_COLOR_AMBIENT); + + // read emissive color + clrTemp.r = pcMatIn->Emissive.r; + AI_SWAP4(clrTemp.r); + clrTemp.g = pcMatIn->Emissive.g; + AI_SWAP4(clrTemp.g); + clrTemp.b = pcMatIn->Emissive.b; + AI_SWAP4(clrTemp.b); + pcMatOut->AddProperty<aiColor3D>(&clrTemp, 1, AI_MATKEY_COLOR_EMISSIVE); + +#undef COLOR_MULITPLY_RGB + + // FIX: Take the opacity from the ambient color. + // The doc say something else, but it is fact that MED exports the + // opacity like this .... oh well. + clrTemp.r = pcMatIn->Ambient.a; + AI_SWAP4(clrTemp.r); + if (is_not_qnan(clrTexture.r)) { + clrTemp.r *= clrTexture.a; + } + pcMatOut->AddProperty<ai_real>(&clrTemp.r, 1, AI_MATKEY_OPACITY); + + // read phong power + int iShadingMode = (int)aiShadingMode_Gouraud; + AI_SWAP4(pcMatIn->Power); + if (0.0f != pcMatIn->Power) { + iShadingMode = (int)aiShadingMode_Phong; + // pcMatIn is packed, we can't form pointers to its members + float power = pcMatIn->Power; + pcMatOut->AddProperty<float>(&power, 1, AI_MATKEY_SHININESS); + } + pcMatOut->AddProperty<int>(&iShadingMode, 1, AI_MATKEY_SHADING_MODEL); + } else if (is_not_qnan(clrTexture.r)) { + pcMatOut->AddProperty<aiColor4D>(&clrTexture, 1, AI_MATKEY_COLOR_DIFFUSE); + pcMatOut->AddProperty<aiColor4D>(&clrTexture, 1, AI_MATKEY_COLOR_SPECULAR); + } + // if the texture could be replaced by a single material color + // we don't need the texture anymore + if (is_not_qnan(clrTexture.r)) { + pcNew.reset(); + } + + // If an ASCII effect description (HLSL?) is contained in the file, + // we can simply ignore it ... + if (iType & AI_MDL7_SKINTYPE_MATERIAL_ASCDEF) { + VALIDATE_FILE_SIZE(szCurrent); + int32_t iMe = *((int32_t *)szCurrent); + AI_SWAP4(iMe); + szCurrent += sizeof(char) * iMe + sizeof(int32_t); + VALIDATE_FILE_SIZE(szCurrent); + } + + // If an embedded texture has been loaded setup the corresponding + // data structures in the aiScene instance + if (pcNew && pScene->mNumTextures <= 999) { + // place this as diffuse texture + char current[5]; + ai_snprintf(current, 5, "*%i", this->pScene->mNumTextures); + + aiString szFile; + const size_t iLen = strlen((const char *)current); + ::memcpy(szFile.data, (const char *)current, iLen + 1); + szFile.length = (ai_uint32)iLen; + + pcMatOut->AddProperty(&szFile, AI_MATKEY_TEXTURE_DIFFUSE(0)); + + // store the texture + if (!pScene->mNumTextures) { + pScene->mNumTextures = 1; + pScene->mTextures = new aiTexture *[1]; + pScene->mTextures[0] = pcNew.release(); + } else { + aiTexture **pc = pScene->mTextures; + pScene->mTextures = new aiTexture *[pScene->mNumTextures + 1]; + for (unsigned int i = 0; i < pScene->mNumTextures; ++i) { + pScene->mTextures[i] = pc[i]; + } + + pScene->mTextures[pScene->mNumTextures] = pcNew.release(); + pScene->mNumTextures++; + delete[] pc; + } + } + VALIDATE_FILE_SIZE(szCurrent); + *szCurrentOut = szCurrent; +} + +// ------------------------------------------------------------------------------------------------ +// Skip a skin lump +void MDLImporter::SkipSkinLump_3DGS_MDL7( + const unsigned char *szCurrent, + const unsigned char **szCurrentOut, + unsigned int iType, + unsigned int iWidth, + unsigned int iHeight) { + // get the type of the skin + const unsigned int iMasked = (unsigned int)(iType & 0xF); + + if (0x6 == iMasked) { + szCurrent += iWidth; + } + if (0x7 == iMasked) { + const size_t iLen = std::strlen((const char *)szCurrent); + szCurrent += iLen + 1; + } else if (iMasked || !iType) { + if (iMasked || !iType || (iType && iWidth && iHeight)) { + // ParseTextureColorData(..., aiTexture::pcData == bad_texel) will simply + // return the size of the color data in bytes in iSkip + unsigned int iSkip = 0; + + aiTexture tex; + tex.pcData = bad_texel; + tex.mHeight = iHeight; + tex.mWidth = iWidth; + ParseTextureColorData(szCurrent, iMasked, &iSkip, &tex); + + // FIX: Important, otherwise the destructor will crash + tex.pcData = nullptr; + + // skip length of texture data + szCurrent += iSkip; + } + } + + // check whether a material definition is contained in the skin + if (iType & AI_MDL7_SKINTYPE_MATERIAL) { + BE_NCONST MDL::Material_MDL7 *pcMatIn = (BE_NCONST MDL::Material_MDL7 *)szCurrent; + szCurrent = (unsigned char *)(pcMatIn + 1); + } + + // if an ASCII effect description (HLSL?) is contained in the file, + // we can simply ignore it ... + if (iType & AI_MDL7_SKINTYPE_MATERIAL_ASCDEF) { + int32_t iMe = *((int32_t *)szCurrent); + AI_SWAP4(iMe); + szCurrent += sizeof(char) * iMe + sizeof(int32_t); + } + *szCurrentOut = szCurrent; +} + +// ------------------------------------------------------------------------------------------------ +void MDLImporter::ParseSkinLump_3DGS_MDL7( + const unsigned char *szCurrent, + const unsigned char **szCurrentOut, + std::vector<aiMaterial *> &pcMats) { + ai_assert(nullptr != szCurrent); + ai_assert(nullptr != szCurrentOut); + + *szCurrentOut = szCurrent; + BE_NCONST MDL::Skin_MDL7 *pcSkin = (BE_NCONST MDL::Skin_MDL7 *)szCurrent; + AI_SWAP4(pcSkin->width); + AI_SWAP4(pcSkin->height); + szCurrent += 12; + + // allocate an output material + aiMaterial *pcMatOut = new aiMaterial(); + pcMats.push_back(pcMatOut); + + // skip length of file name + szCurrent += AI_MDL7_MAX_TEXNAMESIZE; + + ParseSkinLump_3DGS_MDL7(szCurrent, szCurrentOut, pcMatOut, + pcSkin->typ, pcSkin->width, pcSkin->height); + + // place the name of the skin in the material + if (pcSkin->texture_name[0]) { + // the 0 termination could be there or not - we can't know + aiString szFile; + ::memcpy(szFile.data, pcSkin->texture_name, sizeof(pcSkin->texture_name)); + szFile.data[sizeof(pcSkin->texture_name)] = '\0'; + szFile.length = (ai_uint32)::strlen(szFile.data); + + pcMatOut->AddProperty(&szFile, AI_MATKEY_NAME); + } +} + +#endif // !! ASSIMP_BUILD_NO_MDL_IMPORTER diff --git a/libs/assimp/code/AssetLib/MMD/MMDCpp14.h b/libs/assimp/code/AssetLib/MMD/MMDCpp14.h new file mode 100644 index 0000000..10d3768 --- /dev/null +++ b/libs/assimp/code/AssetLib/MMD/MMDCpp14.h @@ -0,0 +1,83 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#pragma once + +#ifndef MMD_CPP14_H +#define MMD_CPP14_H + +#include <cstddef> +#include <memory> +#include <type_traits> +#include <utility> + +namespace mmd { + template<class T> struct _Unique_if { + typedef std::unique_ptr<T> _Single_object; + }; + + template<class T> struct _Unique_if<T[]> { + typedef std::unique_ptr<T[]> _Unknown_bound; + }; + + template<class T, size_t N> struct _Unique_if<T[N]> { + typedef void _Known_bound; + }; + + template<class T, class... Args> + typename _Unique_if<T>::_Single_object + make_unique(Args&&... args) { + return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); + } + + template<class T> + typename _Unique_if<T>::_Unknown_bound + make_unique(size_t n) { + typedef typename std::remove_extent<T>::type U; + return std::unique_ptr<T>(new U[n]()); + } + + template<class T, class... Args> + typename _Unique_if<T>::_Known_bound + make_unique(Args&&...) = delete; +} + +#endif diff --git a/libs/assimp/code/AssetLib/MMD/MMDImporter.cpp b/libs/assimp/code/AssetLib/MMD/MMDImporter.cpp new file mode 100644 index 0000000..e0e68f7 --- /dev/null +++ b/libs/assimp/code/AssetLib/MMD/MMDImporter.cpp @@ -0,0 +1,368 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. +--------------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_MMD_IMPORTER + +#include "AssetLib/MMD/MMDImporter.h" +#include "AssetLib/MMD/MMDPmdParser.h" +#include "AssetLib/MMD/MMDPmxParser.h" +#include "AssetLib/MMD/MMDVmdParser.h" +#include "PostProcessing/ConvertToLHProcess.h" + +#include <assimp/DefaultIOSystem.h> +#include <assimp/ai_assert.h> +#include <assimp/scene.h> +#include <assimp/Importer.hpp> + +#include <fstream> +#include <iomanip> +#include <memory> + +static const aiImporterDesc desc = { "MMD Importer", + "", + "", + "surfaces supported?", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "pmx" }; + +namespace Assimp { + +using namespace std; + +// ------------------------------------------------------------------------------------------------ +// Default constructor +MMDImporter::MMDImporter() : + m_Buffer(), + m_strAbsPath() { + DefaultIOSystem io; + m_strAbsPath = io.getOsSeparator(); +} + +// ------------------------------------------------------------------------------------------------ +// Destructor. +MMDImporter::~MMDImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns true, if file is an pmx file. +bool MMDImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool /*checkSig*/) const { + static const char *tokens[] = { "PMX " }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc *MMDImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// MMD import implementation +void MMDImporter::InternReadFile(const std::string &file, aiScene *pScene, + IOSystem * /*pIOHandler*/) { + // Read file by istream + std::filebuf fb; + if (!fb.open(file, std::ios::in | std::ios::binary)) { + throw DeadlyImportError("Failed to open file ", file, "."); + } + + std::istream fileStream(&fb); + + // Get the file-size and validate it, throwing an exception when fails + fileStream.seekg(0, fileStream.end); + size_t fileSize = static_cast<size_t>(fileStream.tellg()); + fileStream.seekg(0, fileStream.beg); + + if (fileSize < sizeof(pmx::PmxModel)) { + throw DeadlyImportError(file, " is too small."); + } + + pmx::PmxModel model; + model.Read(&fileStream); + + CreateDataFromImport(&model, pScene); +} + +// ------------------------------------------------------------------------------------------------ +void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel, + aiScene *pScene) { + if (pModel == nullptr) { + return; + } + + aiNode *pNode = new aiNode; + if (!pModel->model_name.empty()) { + pNode->mName.Set(pModel->model_name); + } + + pScene->mRootNode = pNode; + + pNode = new aiNode; + pScene->mRootNode->addChildren(1, &pNode); + pNode->mName.Set(string(pModel->model_name) + string("_mesh")); + + // split mesh by materials + pNode->mNumMeshes = pModel->material_count; + pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; + for (unsigned int index = 0; index < pNode->mNumMeshes; index++) { + pNode->mMeshes[index] = index; + } + + pScene->mNumMeshes = pModel->material_count; + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + for (unsigned int i = 0, indexStart = 0; i < pScene->mNumMeshes; i++) { + const int indexCount = pModel->materials[i].index_count; + + pScene->mMeshes[i] = CreateMesh(pModel, indexStart, indexCount); + pScene->mMeshes[i]->mName = pModel->materials[i].material_name; + pScene->mMeshes[i]->mMaterialIndex = i; + indexStart += indexCount; + } + + // create node hierarchy for bone position + std::unique_ptr<aiNode *[]> ppNode(new aiNode *[pModel->bone_count]); + for (auto i = 0; i < pModel->bone_count; i++) { + ppNode[i] = new aiNode(pModel->bones[i].bone_name); + } + + for (auto i = 0; i < pModel->bone_count; i++) { + const pmx::PmxBone &bone = pModel->bones[i]; + + if (bone.parent_index < 0) { + pScene->mRootNode->addChildren(1, ppNode.get() + i); + } else { + ppNode[bone.parent_index]->addChildren(1, ppNode.get() + i); + + aiVector3D v3 = aiVector3D( + bone.position[0] - pModel->bones[bone.parent_index].position[0], + bone.position[1] - pModel->bones[bone.parent_index].position[1], + bone.position[2] - pModel->bones[bone.parent_index].position[2]); + aiMatrix4x4::Translation(v3, ppNode[i]->mTransformation); + } + } + + // create materials + pScene->mNumMaterials = pModel->material_count; + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + for (unsigned int i = 0; i < pScene->mNumMaterials; i++) { + pScene->mMaterials[i] = CreateMaterial(&pModel->materials[i], pModel); + } + + // Convert everything to OpenGL space + MakeLeftHandedProcess convertProcess; + convertProcess.Execute(pScene); + + FlipUVsProcess uvFlipper; + uvFlipper.Execute(pScene); + + FlipWindingOrderProcess windingFlipper; + windingFlipper.Execute(pScene); +} + +// ------------------------------------------------------------------------------------------------ +aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel, + const int indexStart, const int indexCount) { + aiMesh *pMesh = new aiMesh; + + pMesh->mNumVertices = indexCount; + + pMesh->mNumFaces = indexCount / 3; + pMesh->mFaces = new aiFace[pMesh->mNumFaces]; + + const int numIndices = 3; // triangular face + for (unsigned int index = 0; index < pMesh->mNumFaces; index++) { + pMesh->mFaces[index].mNumIndices = numIndices; + unsigned int *indices = new unsigned int[numIndices]; + indices[0] = numIndices * index; + indices[1] = numIndices * index + 1; + indices[2] = numIndices * index + 2; + pMesh->mFaces[index].mIndices = indices; + } + + pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; + pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices]; + pMesh->mNumUVComponents[0] = 2; + + // additional UVs + for (int i = 1; i <= pModel->setting.uv; i++) { + pMesh->mTextureCoords[i] = new aiVector3D[pMesh->mNumVertices]; + pMesh->mNumUVComponents[i] = 4; + } + + map<int, vector<aiVertexWeight>> bone_vertex_map; + + // fill in contents and create bones + for (int index = 0; index < indexCount; index++) { + const pmx::PmxVertex *v = + &pModel->vertices[pModel->indices[indexStart + index]]; + const float *position = v->position; + pMesh->mVertices[index].Set(position[0], position[1], position[2]); + const float *normal = v->normal; + + pMesh->mNormals[index].Set(normal[0], normal[1], normal[2]); + pMesh->mTextureCoords[0][index].x = v->uv[0]; + pMesh->mTextureCoords[0][index].y = v->uv[1]; + + for (int i = 1; i <= pModel->setting.uv; i++) { + // TODO: wrong here? use quaternion transform? + pMesh->mTextureCoords[i][index].x = v->uva[i][0]; + pMesh->mTextureCoords[i][index].y = v->uva[i][1]; + } + + // handle bone map + const auto vsBDEF1_ptr = + dynamic_cast<pmx::PmxVertexSkinningBDEF1 *>(v->skinning.get()); + const auto vsBDEF2_ptr = + dynamic_cast<pmx::PmxVertexSkinningBDEF2 *>(v->skinning.get()); + const auto vsBDEF4_ptr = + dynamic_cast<pmx::PmxVertexSkinningBDEF4 *>(v->skinning.get()); + const auto vsSDEF_ptr = + dynamic_cast<pmx::PmxVertexSkinningSDEF *>(v->skinning.get()); + switch (v->skinning_type) { + case pmx::PmxVertexSkinningType::BDEF1: + bone_vertex_map[vsBDEF1_ptr->bone_index].push_back( + aiVertexWeight(index, 1.0)); + break; + case pmx::PmxVertexSkinningType::BDEF2: + bone_vertex_map[vsBDEF2_ptr->bone_index1].push_back( + aiVertexWeight(index, vsBDEF2_ptr->bone_weight)); + bone_vertex_map[vsBDEF2_ptr->bone_index2].push_back( + aiVertexWeight(index, 1.0f - vsBDEF2_ptr->bone_weight)); + break; + case pmx::PmxVertexSkinningType::BDEF4: + bone_vertex_map[vsBDEF4_ptr->bone_index1].push_back( + aiVertexWeight(index, vsBDEF4_ptr->bone_weight1)); + bone_vertex_map[vsBDEF4_ptr->bone_index2].push_back( + aiVertexWeight(index, vsBDEF4_ptr->bone_weight2)); + bone_vertex_map[vsBDEF4_ptr->bone_index3].push_back( + aiVertexWeight(index, vsBDEF4_ptr->bone_weight3)); + bone_vertex_map[vsBDEF4_ptr->bone_index4].push_back( + aiVertexWeight(index, vsBDEF4_ptr->bone_weight4)); + break; + case pmx::PmxVertexSkinningType::SDEF: // TODO: how to use sdef_c, sdef_r0, + // sdef_r1? + bone_vertex_map[vsSDEF_ptr->bone_index1].push_back( + aiVertexWeight(index, vsSDEF_ptr->bone_weight)); + bone_vertex_map[vsSDEF_ptr->bone_index2].push_back( + aiVertexWeight(index, 1.0f - vsSDEF_ptr->bone_weight)); + break; + case pmx::PmxVertexSkinningType::QDEF: + const auto vsQDEF_ptr = + dynamic_cast<pmx::PmxVertexSkinningQDEF *>(v->skinning.get()); + bone_vertex_map[vsQDEF_ptr->bone_index1].push_back( + aiVertexWeight(index, vsQDEF_ptr->bone_weight1)); + bone_vertex_map[vsQDEF_ptr->bone_index2].push_back( + aiVertexWeight(index, vsQDEF_ptr->bone_weight2)); + bone_vertex_map[vsQDEF_ptr->bone_index3].push_back( + aiVertexWeight(index, vsQDEF_ptr->bone_weight3)); + bone_vertex_map[vsQDEF_ptr->bone_index4].push_back( + aiVertexWeight(index, vsQDEF_ptr->bone_weight4)); + break; + } + } + + // make all bones for each mesh + // assign bone weights to skinned bones (otherwise just initialize) + auto bone_ptr_ptr = new aiBone *[pModel->bone_count]; + pMesh->mNumBones = pModel->bone_count; + pMesh->mBones = bone_ptr_ptr; + for (auto ii = 0; ii < pModel->bone_count; ++ii) { + auto pBone = new aiBone; + const auto &pmxBone = pModel->bones[ii]; + pBone->mName = pmxBone.bone_name; + aiVector3D pos(pmxBone.position[0], pmxBone.position[1], pmxBone.position[2]); + aiMatrix4x4::Translation(-pos, pBone->mOffsetMatrix); + auto it = bone_vertex_map.find(ii); + if (it != bone_vertex_map.end()) { + pBone->mNumWeights = static_cast<unsigned int>(it->second.size()); + pBone->mWeights = new aiVertexWeight[pBone->mNumWeights]; + for (unsigned int j = 0; j < pBone->mNumWeights; j++) { + pBone->mWeights[j] = it->second[j]; + } + } + bone_ptr_ptr[ii] = pBone; + } + + return pMesh; +} + +// ------------------------------------------------------------------------------------------------ +aiMaterial *MMDImporter::CreateMaterial(const pmx::PmxMaterial *pMat, + const pmx::PmxModel *pModel) { + aiMaterial *mat = new aiMaterial(); + aiString name(pMat->material_english_name); + mat->AddProperty(&name, AI_MATKEY_NAME); + + aiColor3D diffuse(pMat->diffuse[0], pMat->diffuse[1], pMat->diffuse[2]); + mat->AddProperty(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + aiColor3D specular(pMat->specular[0], pMat->specular[1], pMat->specular[2]); + mat->AddProperty(&specular, 1, AI_MATKEY_COLOR_SPECULAR); + aiColor3D ambient(pMat->ambient[0], pMat->ambient[1], pMat->ambient[2]); + mat->AddProperty(&ambient, 1, AI_MATKEY_COLOR_AMBIENT); + + float opacity = pMat->diffuse[3]; + mat->AddProperty(&opacity, 1, AI_MATKEY_OPACITY); + float shininess = pMat->specularlity; + mat->AddProperty(&shininess, 1, AI_MATKEY_SHININESS_STRENGTH); + + if (pMat->diffuse_texture_index >= 0) { + aiString texture_path(pModel->textures[pMat->diffuse_texture_index]); + mat->AddProperty(&texture_path, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); + } + + int mapping_uvwsrc = 0; + mat->AddProperty(&mapping_uvwsrc, 1, + AI_MATKEY_UVWSRC(aiTextureType_DIFFUSE, 0)); + + return mat; +} + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_MMD_IMPORTER diff --git a/libs/assimp/code/AssetLib/MMD/MMDImporter.h b/libs/assimp/code/AssetLib/MMD/MMDImporter.h new file mode 100644 index 0000000..36f3848 --- /dev/null +++ b/libs/assimp/code/AssetLib/MMD/MMDImporter.h @@ -0,0 +1,96 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef MMD_FILE_IMPORTER_H_INC +#define MMD_FILE_IMPORTER_H_INC + +#include <assimp/BaseImporter.h> +#include "MMDPmxParser.h" +#include <assimp/material.h> +#include <vector> + +struct aiMesh; + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +/// \class MMDImporter +/// \brief Imports MMD a pmx/pmd/vmd file +// ------------------------------------------------------------------------------------------------ +class MMDImporter : public BaseImporter { +public: + /// \brief Default constructor + MMDImporter(); + + /// \brief Destructor + ~MMDImporter() override; + +public: + /// \brief Returns whether the class can handle the format of the given file. + /// \remark See BaseImporter::CanRead() for details. + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override; + +private: + //! \brief Appends the supported extension. + const aiImporterDesc* GetInfo() const override; + + //! \brief File import implementation. + void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) override; + + //! \brief Create the data from imported content. + void CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pScene); + + //! \brief Create the mesh + aiMesh* CreateMesh(const pmx::PmxModel* pModel, const int indexStart, const int indexCount); + + //! \brief Create the material + aiMaterial* CreateMaterial(const pmx::PmxMaterial* pMat, const pmx::PmxModel* pModel); + +private: + //! Data buffer + std::vector<char> m_Buffer; + //! Absolute pathname of model in file system + std::string m_strAbsPath; +}; + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/MMD/MMDPmdParser.h b/libs/assimp/code/AssetLib/MMD/MMDPmdParser.h new file mode 100644 index 0000000..35e4f09 --- /dev/null +++ b/libs/assimp/code/AssetLib/MMD/MMDPmdParser.h @@ -0,0 +1,597 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#pragma once + +#include <vector> +#include <string> +#include <memory> +#include <iostream> +#include <fstream> +#include "MMDCpp14.h" + +namespace pmd +{ + class PmdHeader + { + public: + std::string name; + std::string name_english; + std::string comment; + std::string comment_english; + + bool Read(std::ifstream* stream) + { + char buffer[256]; + stream->read(buffer, 20); + name = std::string(buffer); + stream->read(buffer, 256); + comment = std::string(buffer); + return true; + } + + bool ReadExtension(std::ifstream* stream) + { + char buffer[256]; + stream->read(buffer, 20); + name_english = std::string(buffer); + stream->read(buffer, 256); + comment_english = std::string(buffer); + return true; + } + }; + + class PmdVertex + { + public: + float position[3]; + + float normal[3]; + + float uv[2]; + + uint16_t bone_index[2]; + + uint8_t bone_weight; + + bool edge_invisible; + + bool Read(std::ifstream* stream) + { + stream->read((char*) position, sizeof(float) * 3); + stream->read((char*) normal, sizeof(float) * 3); + stream->read((char*) uv, sizeof(float) * 2); + stream->read((char*) bone_index, sizeof(uint16_t) * 2); + stream->read((char*) &bone_weight, sizeof(uint8_t)); + stream->read((char*) &edge_invisible, sizeof(uint8_t)); + return true; + } + }; + + class PmdMaterial + { + public: + float diffuse[4]; + float power; + float specular[3]; + float ambient[3]; + uint8_t toon_index; + uint8_t edge_flag; + uint32_t index_count; + std::string texture_filename; + std::string sphere_filename; + + bool Read(std::ifstream* stream) + { + char buffer[20]; + stream->read((char*) &diffuse, sizeof(float) * 4); + stream->read((char*) &power, sizeof(float)); + stream->read((char*) &specular, sizeof(float) * 3); + stream->read((char*) &ambient, sizeof(float) * 3); + stream->read((char*) &toon_index, sizeof(uint8_t)); + stream->read((char*) &edge_flag, sizeof(uint8_t)); + stream->read((char*) &index_count, sizeof(uint32_t)); + stream->read((char*) &buffer, sizeof(char) * 20); + char* pstar = strchr(buffer, '*'); + if (nullptr == pstar) + { + texture_filename = std::string(buffer); + sphere_filename.clear(); + } + else { + *pstar = 0; + texture_filename = std::string(buffer); + sphere_filename = std::string(pstar+1); + } + return true; + } + }; + + enum class BoneType : uint8_t + { + Rotation, + RotationAndMove, + IkEffector, + Unknown, + IkEffectable, + RotationEffectable, + IkTarget, + Invisible, + Twist, + RotationMovement + }; + + class PmdBone + { + public: + std::string name; + std::string name_english; + uint16_t parent_bone_index; + uint16_t tail_pos_bone_index; + BoneType bone_type; + uint16_t ik_parent_bone_index; + float bone_head_pos[3]; + + void Read(std::istream *stream) + { + char buffer[20]; + stream->read(buffer, 20); + name = std::string(buffer); + stream->read((char*) &parent_bone_index, sizeof(uint16_t)); + stream->read((char*) &tail_pos_bone_index, sizeof(uint16_t)); + stream->read((char*) &bone_type, sizeof(uint8_t)); + stream->read((char*) &ik_parent_bone_index, sizeof(uint16_t)); + stream->read((char*) &bone_head_pos, sizeof(float) * 3); + } + + void ReadExpantion(std::istream *stream) + { + char buffer[20]; + stream->read(buffer, 20); + name_english = std::string(buffer); + } + }; + + class PmdIk + { + public: + uint16_t ik_bone_index; + uint16_t target_bone_index; + uint16_t iterations; + float angle_limit; + std::vector<uint16_t> ik_child_bone_index; + + void Read(std::istream *stream) + { + stream->read((char *) &ik_bone_index, sizeof(uint16_t)); + stream->read((char *) &target_bone_index, sizeof(uint16_t)); + uint8_t ik_chain_length; + stream->read((char*) &ik_chain_length, sizeof(uint8_t)); + stream->read((char *) &iterations, sizeof(uint16_t)); + stream->read((char *) &angle_limit, sizeof(float)); + ik_child_bone_index.resize(ik_chain_length); + for (int i = 0; i < ik_chain_length; i++) + { + stream->read((char *) &ik_child_bone_index[i], sizeof(uint16_t)); + } + } + }; + + class PmdFaceVertex + { + public: + int vertex_index; + float position[3]; + + void Read(std::istream *stream) + { + stream->read((char *) &vertex_index, sizeof(int)); + stream->read((char *) position, sizeof(float) * 3); + } + }; + + enum class FaceCategory : uint8_t + { + Base, + Eyebrow, + Eye, + Mouth, + Other + }; + + class PmdFace + { + public: + std::string name; + FaceCategory type; + std::vector<PmdFaceVertex> vertices; + std::string name_english; + + void Read(std::istream *stream) + { + char buffer[20]; + stream->read(buffer, 20); + name = std::string(buffer); + int vertex_count; + stream->read((char*) &vertex_count, sizeof(int)); + stream->read((char*) &type, sizeof(uint8_t)); + vertices.resize(vertex_count); + for (int i = 0; i < vertex_count; i++) + { + vertices[i].Read(stream); + } + } + + void ReadExpantion(std::istream *stream) + { + char buffer[20]; + stream->read(buffer, 20); + name_english = std::string(buffer); + } + }; + + class PmdBoneDispName + { + public: + std::string bone_disp_name; + std::string bone_disp_name_english; + + void Read(std::istream *stream) + { + char buffer[50]; + stream->read(buffer, 50); + bone_disp_name = std::string(buffer); + bone_disp_name_english.clear(); + } + void ReadExpantion(std::istream *stream) + { + char buffer[50]; + stream->read(buffer, 50); + bone_disp_name_english = std::string(buffer); + } + }; + + class PmdBoneDisp + { + public: + uint16_t bone_index; + uint8_t bone_disp_index; + + void Read(std::istream *stream) + { + stream->read((char*) &bone_index, sizeof(uint16_t)); + stream->read((char*) &bone_disp_index, sizeof(uint8_t)); + } + }; + + enum class RigidBodyShape : uint8_t + { + Sphere = 0, + Box = 1, + Cpusel = 2 + }; + + enum class RigidBodyType : uint8_t + { + BoneConnected = 0, + Physics = 1, + ConnectedPhysics = 2 + }; + + class PmdRigidBody + { + public: + std::string name; + uint16_t related_bone_index; + uint8_t group_index; + uint16_t mask; + RigidBodyShape shape; + float size[3]; + float position[3]; + float orientation[3]; + float weight; + float linear_damping; + float anglar_damping; + float restitution; + float friction; + RigidBodyType rigid_type; + + void Read(std::istream *stream) + { + char buffer[20]; + stream->read(buffer, sizeof(char) * 20); + name = (std::string(buffer)); + stream->read((char*) &related_bone_index, sizeof(uint16_t)); + stream->read((char*) &group_index, sizeof(uint8_t)); + stream->read((char*) &mask, sizeof(uint16_t)); + stream->read((char*) &shape, sizeof(uint8_t)); + stream->read((char*) size, sizeof(float) * 3); + stream->read((char*) position, sizeof(float) * 3); + stream->read((char*) orientation, sizeof(float) * 3); + stream->read((char*) &weight, sizeof(float)); + stream->read((char*) &linear_damping, sizeof(float)); + stream->read((char*) &anglar_damping, sizeof(float)); + stream->read((char*) &restitution, sizeof(float)); + stream->read((char*) &friction, sizeof(float)); + stream->read((char*) &rigid_type, sizeof(char)); + } + }; + + class PmdConstraint + { + public: + std::string name; + uint32_t rigid_body_index_a; + uint32_t rigid_body_index_b; + float position[3]; + float orientation[3]; + float linear_lower_limit[3]; + float linear_upper_limit[3]; + float angular_lower_limit[3]; + float angular_upper_limit[3]; + float linear_stiffness[3]; + float angular_stiffness[3]; + + void Read(std::istream *stream) + { + char buffer[20]; + stream->read(buffer, 20); + name = std::string(buffer); + stream->read((char *) &rigid_body_index_a, sizeof(uint32_t)); + stream->read((char *) &rigid_body_index_b, sizeof(uint32_t)); + stream->read((char *) position, sizeof(float) * 3); + stream->read((char *) orientation, sizeof(float) * 3); + stream->read((char *) linear_lower_limit, sizeof(float) * 3); + stream->read((char *) linear_upper_limit, sizeof(float) * 3); + stream->read((char *) angular_lower_limit, sizeof(float) * 3); + stream->read((char *) angular_upper_limit, sizeof(float) * 3); + stream->read((char *) linear_stiffness, sizeof(float) * 3); + stream->read((char *) angular_stiffness, sizeof(float) * 3); + } + }; + + class PmdModel + { + public: + float version; + PmdHeader header; + std::vector<PmdVertex> vertices; + std::vector<uint16_t> indices; + std::vector<PmdMaterial> materials; + std::vector<PmdBone> bones; + std::vector<PmdIk> iks; + std::vector<PmdFace> faces; + std::vector<uint16_t> faces_indices; + std::vector<PmdBoneDispName> bone_disp_name; + std::vector<PmdBoneDisp> bone_disp; + std::vector<std::string> toon_filenames; + std::vector<PmdRigidBody> rigid_bodies; + std::vector<PmdConstraint> constraints; + + static std::unique_ptr<PmdModel> LoadFromFile(const char *filename) + { + std::ifstream stream(filename, std::ios::binary); + if (stream.fail()) + { + std::cerr << "could not open \"" << filename << "\"" << std::endl; + return nullptr; + } + auto result = LoadFromStream(&stream); + stream.close(); + return result; + } + + static std::unique_ptr<PmdModel> LoadFromStream(std::ifstream *stream) + { + auto result = mmd::make_unique<PmdModel>(); + char buffer[100]; + + // magic + char magic[3]; + stream->read(magic, 3); + if (magic[0] != 'P' || magic[1] != 'm' || magic[2] != 'd') + { + std::cerr << "invalid file" << std::endl; + return nullptr; + } + + // version + stream->read((char*) &(result->version), sizeof(float)); + if (result ->version != 1.0f) + { + std::cerr << "invalid version" << std::endl; + return nullptr; + } + + // header + result->header.Read(stream); + + // vertices + uint32_t vertex_num; + stream->read((char*) &vertex_num, sizeof(uint32_t)); + result->vertices.resize(vertex_num); + for (uint32_t i = 0; i < vertex_num; i++) + { + result->vertices[i].Read(stream); + } + + // indices + uint32_t index_num; + stream->read((char*) &index_num, sizeof(uint32_t)); + result->indices.resize(index_num); + for (uint32_t i = 0; i < index_num; i++) + { + stream->read((char*) &result->indices[i], sizeof(uint16_t)); + } + + // materials + uint32_t material_num; + stream->read((char*) &material_num, sizeof(uint32_t)); + result->materials.resize(material_num); + for (uint32_t i = 0; i < material_num; i++) + { + result->materials[i].Read(stream); + } + + // bones + uint16_t bone_num; + stream->read((char*) &bone_num, sizeof(uint16_t)); + result->bones.resize(bone_num); + for (uint32_t i = 0; i < bone_num; i++) + { + result->bones[i].Read(stream); + } + + // iks + uint16_t ik_num; + stream->read((char*) &ik_num, sizeof(uint16_t)); + result->iks.resize(ik_num); + for (uint32_t i = 0; i < ik_num; i++) + { + result->iks[i].Read(stream); + } + + // faces + uint16_t face_num; + stream->read((char*) &face_num, sizeof(uint16_t)); + result->faces.resize(face_num); + for (uint32_t i = 0; i < face_num; i++) + { + result->faces[i].Read(stream); + } + + // face frames + uint8_t face_frame_num; + stream->read((char*) &face_frame_num, sizeof(uint8_t)); + result->faces_indices.resize(face_frame_num); + for (uint32_t i = 0; i < face_frame_num; i++) + { + stream->read((char*) &result->faces_indices[i], sizeof(uint16_t)); + } + + // bone names + uint8_t bone_disp_num; + stream->read((char*) &bone_disp_num, sizeof(uint8_t)); + result->bone_disp_name.resize(bone_disp_num); + for (uint32_t i = 0; i < bone_disp_num; i++) + { + result->bone_disp_name[i].Read(stream); + } + + // bone frame + uint32_t bone_frame_num; + stream->read((char*) &bone_frame_num, sizeof(uint32_t)); + result->bone_disp.resize(bone_frame_num); + for (uint32_t i = 0; i < bone_frame_num; i++) + { + result->bone_disp[i].Read(stream); + } + + // english name + bool english; + stream->read((char*) &english, sizeof(char)); + if (english) + { + result->header.ReadExtension(stream); + for (uint32_t i = 0; i < bone_num; i++) + { + result->bones[i].ReadExpantion(stream); + } + for (uint32_t i = 0; i < face_num; i++) + { + if (result->faces[i].type == pmd::FaceCategory::Base) + { + continue; + } + result->faces[i].ReadExpantion(stream); + } + for (uint32_t i = 0; i < result->bone_disp_name.size(); i++) + { + result->bone_disp_name[i].ReadExpantion(stream); + } + } + + // toon textures + if (stream->peek() == std::ios::traits_type::eof()) + { + result->toon_filenames.clear(); + } + else { + result->toon_filenames.resize(10); + for (uint32_t i = 0; i < 10; i++) + { + stream->read(buffer, 100); + result->toon_filenames[i] = std::string(buffer); + } + } + + // physics + if (stream->peek() == std::ios::traits_type::eof()) + { + result->rigid_bodies.clear(); + result->constraints.clear(); + } + else { + uint32_t rigid_body_num; + stream->read((char*) &rigid_body_num, sizeof(uint32_t)); + result->rigid_bodies.resize(rigid_body_num); + for (uint32_t i = 0; i < rigid_body_num; i++) + { + result->rigid_bodies[i].Read(stream); + } + uint32_t constraint_num; + stream->read((char*) &constraint_num, sizeof(uint32_t)); + result->constraints.resize(constraint_num); + for (uint32_t i = 0; i < constraint_num; i++) + { + result->constraints[i].Read(stream); + } + } + + if (stream->peek() != std::ios::traits_type::eof()) + { + std::cerr << "there is unknown data" << std::endl; + } + + return result; + } + }; +} diff --git a/libs/assimp/code/AssetLib/MMD/MMDPmxParser.cpp b/libs/assimp/code/AssetLib/MMD/MMDPmxParser.cpp new file mode 100644 index 0000000..ca37ba1 --- /dev/null +++ b/libs/assimp/code/AssetLib/MMD/MMDPmxParser.cpp @@ -0,0 +1,605 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#include <utility> +#include "MMDPmxParser.h" +#include <assimp/StringUtils.h> +#ifdef ASSIMP_USE_HUNTER +# include <utf8.h> +#else +# include "../contrib/utf8cpp/source/utf8.h" +#endif +#include <assimp/Exceptional.h> + +namespace pmx +{ + int ReadIndex(std::istream *stream, int size) + { + switch (size) + { + case 1: + uint8_t tmp8; + stream->read((char*) &tmp8, sizeof(uint8_t)); + if (255 == tmp8) + { + return -1; + } + else { + return (int) tmp8; + } + case 2: + uint16_t tmp16; + stream->read((char*) &tmp16, sizeof(uint16_t)); + if (65535 == tmp16) + { + return -1; + } + else { + return (int) tmp16; + } + case 4: + int tmp32; + stream->read((char*) &tmp32, sizeof(int)); + return tmp32; + default: + return -1; + } + } + + std::string ReadString(std::istream *stream, uint8_t encoding) + { + int size; + stream->read((char*) &size, sizeof(int)); + std::vector<char> buffer; + if (size == 0) + { + return std::string(); + } + buffer.reserve(size); + stream->read((char*) buffer.data(), size); + if (encoding == 0) + { + // UTF16 to UTF8 + const uint16_t* sourceStart = (uint16_t*)buffer.data(); + const unsigned int targetSize = size * 3; // enough to encode + char *targetStart = new char[targetSize]; + std::memset(targetStart, 0, targetSize * sizeof(char)); + + utf8::utf16to8( sourceStart, sourceStart + size/2, targetStart ); + + std::string result(targetStart); + delete [] targetStart; + return result; + } + else + { + // the name is already UTF8 + return std::string((const char*)buffer.data(), size); + } + } + + void PmxSetting::Read(std::istream *stream) + { + uint8_t count; + stream->read((char*) &count, sizeof(uint8_t)); + if (count < 8) + { + throw DeadlyImportError("MMD: invalid size"); + } + stream->read((char*) &encoding, sizeof(uint8_t)); + stream->read((char*) &uv, sizeof(uint8_t)); + stream->read((char*) &vertex_index_size, sizeof(uint8_t)); + stream->read((char*) &texture_index_size, sizeof(uint8_t)); + stream->read((char*) &material_index_size, sizeof(uint8_t)); + stream->read((char*) &bone_index_size, sizeof(uint8_t)); + stream->read((char*) &morph_index_size, sizeof(uint8_t)); + stream->read((char*) &rigidbody_index_size, sizeof(uint8_t)); + uint8_t temp; + for (int i = 8; i < count; i++) + { + stream->read((char*)&temp, sizeof(uint8_t)); + } + } + + void PmxVertexSkinningBDEF1::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_index = ReadIndex(stream, setting->bone_index_size); + } + + void PmxVertexSkinningBDEF2::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_index1 = ReadIndex(stream, setting->bone_index_size); + this->bone_index2 = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->bone_weight, sizeof(float)); + } + + void PmxVertexSkinningBDEF4::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_index1 = ReadIndex(stream, setting->bone_index_size); + this->bone_index2 = ReadIndex(stream, setting->bone_index_size); + this->bone_index3 = ReadIndex(stream, setting->bone_index_size); + this->bone_index4 = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->bone_weight1, sizeof(float)); + stream->read((char*) &this->bone_weight2, sizeof(float)); + stream->read((char*) &this->bone_weight3, sizeof(float)); + stream->read((char*) &this->bone_weight4, sizeof(float)); + } + + void PmxVertexSkinningSDEF::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_index1 = ReadIndex(stream, setting->bone_index_size); + this->bone_index2 = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->bone_weight, sizeof(float)); + stream->read((char*) this->sdef_c, sizeof(float) * 3); + stream->read((char*) this->sdef_r0, sizeof(float) * 3); + stream->read((char*) this->sdef_r1, sizeof(float) * 3); + } + + void PmxVertexSkinningQDEF::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_index1 = ReadIndex(stream, setting->bone_index_size); + this->bone_index2 = ReadIndex(stream, setting->bone_index_size); + this->bone_index3 = ReadIndex(stream, setting->bone_index_size); + this->bone_index4 = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->bone_weight1, sizeof(float)); + stream->read((char*) &this->bone_weight2, sizeof(float)); + stream->read((char*) &this->bone_weight3, sizeof(float)); + stream->read((char*) &this->bone_weight4, sizeof(float)); + } + + void PmxVertex::Read(std::istream *stream, PmxSetting *setting) + { + stream->read((char*) this->position, sizeof(float) * 3); + stream->read((char*) this->normal, sizeof(float) * 3); + stream->read((char*) this->uv, sizeof(float) * 2); + for (int i = 0; i < setting->uv; ++i) + { + stream->read((char*) this->uva[i], sizeof(float) * 4); + } + stream->read((char*) &this->skinning_type, sizeof(PmxVertexSkinningType)); + switch (this->skinning_type) + { + case PmxVertexSkinningType::BDEF1: + this->skinning = mmd::make_unique<PmxVertexSkinningBDEF1>(); + break; + case PmxVertexSkinningType::BDEF2: + this->skinning = mmd::make_unique<PmxVertexSkinningBDEF2>(); + break; + case PmxVertexSkinningType::BDEF4: + this->skinning = mmd::make_unique<PmxVertexSkinningBDEF4>(); + break; + case PmxVertexSkinningType::SDEF: + this->skinning = mmd::make_unique<PmxVertexSkinningSDEF>(); + break; + case PmxVertexSkinningType::QDEF: + this->skinning = mmd::make_unique<PmxVertexSkinningQDEF>(); + break; + default: + throw "invalid skinning type"; + } + this->skinning->Read(stream, setting); + stream->read((char*) &this->edge, sizeof(float)); + } + + void PmxMaterial::Read(std::istream *stream, PmxSetting *setting) + { + this->material_name = ReadString(stream, setting->encoding); + this->material_english_name = ReadString(stream, setting->encoding); + stream->read((char*) this->diffuse, sizeof(float) * 4); + stream->read((char*) this->specular, sizeof(float) * 3); + stream->read((char*) &this->specularlity, sizeof(float)); + stream->read((char*) this->ambient, sizeof(float) * 3); + stream->read((char*) &this->flag, sizeof(uint8_t)); + stream->read((char*) this->edge_color, sizeof(float) * 4); + stream->read((char*) &this->edge_size, sizeof(float)); + this->diffuse_texture_index = ReadIndex(stream, setting->texture_index_size); + this->sphere_texture_index = ReadIndex(stream, setting->texture_index_size); + stream->read((char*) &this->sphere_op_mode, sizeof(uint8_t)); + stream->read((char*) &this->common_toon_flag, sizeof(uint8_t)); + if (this->common_toon_flag) + { + stream->read((char*) &this->toon_texture_index, sizeof(uint8_t)); + } + else { + this->toon_texture_index = ReadIndex(stream, setting->texture_index_size); + } + this->memo = ReadString(stream, setting->encoding); + stream->read((char*) &this->index_count, sizeof(int)); + } + + void PmxIkLink::Read(std::istream *stream, PmxSetting *setting) + { + this->link_target = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->angle_lock, sizeof(uint8_t)); + if (angle_lock == 1) + { + stream->read((char*) this->max_radian, sizeof(float) * 3); + stream->read((char*) this->min_radian, sizeof(float) * 3); + } + } + + void PmxBone::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_name = ReadString(stream, setting->encoding); + this->bone_english_name = ReadString(stream, setting->encoding); + stream->read((char*) this->position, sizeof(float) * 3); + this->parent_index = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->level, sizeof(int)); + stream->read((char*) &this->bone_flag, sizeof(uint16_t)); + if (this->bone_flag & 0x0001) { + this->target_index = ReadIndex(stream, setting->bone_index_size); + } + else { + stream->read((char*)this->offset, sizeof(float) * 3); + } + if (this->bone_flag & (0x0100 | 0x0200)) { + this->grant_parent_index = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->grant_weight, sizeof(float)); + } + if (this->bone_flag & 0x0400) { + stream->read((char*)this->lock_axis_orientation, sizeof(float) * 3); + } + if (this->bone_flag & 0x0800) { + stream->read((char*)this->local_axis_x_orientation, sizeof(float) * 3); + stream->read((char*)this->local_axis_y_orientation, sizeof(float) * 3); + } + if (this->bone_flag & 0x2000) { + stream->read((char*) &this->key, sizeof(int)); + } + if (this->bone_flag & 0x0020) { + this->ik_target_bone_index = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &ik_loop, sizeof(int)); + stream->read((char*) &ik_loop_angle_limit, sizeof(float)); + stream->read((char*) &ik_link_count, sizeof(int)); + this->ik_links = mmd::make_unique<PmxIkLink []>(ik_link_count); + for (int i = 0; i < ik_link_count; i++) { + ik_links[i].Read(stream, setting); + } + } + } + + void PmxMorphVertexOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->vertex_index = ReadIndex(stream, setting->vertex_index_size); + stream->read((char*)this->position_offset, sizeof(float) * 3); + } + + void PmxMorphUVOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->vertex_index = ReadIndex(stream, setting->vertex_index_size); + stream->read((char*)this->uv_offset, sizeof(float) * 4); + } + + void PmxMorphBoneOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_index = ReadIndex(stream, setting->bone_index_size); + stream->read((char*)this->translation, sizeof(float) * 3); + stream->read((char*)this->rotation, sizeof(float) * 4); + } + + void PmxMorphMaterialOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->material_index = ReadIndex(stream, setting->material_index_size); + stream->read((char*) &this->offset_operation, sizeof(uint8_t)); + stream->read((char*)this->diffuse, sizeof(float) * 4); + stream->read((char*)this->specular, sizeof(float) * 3); + stream->read((char*) &this->specularity, sizeof(float)); + stream->read((char*)this->ambient, sizeof(float) * 3); + stream->read((char*)this->edge_color, sizeof(float) * 4); + stream->read((char*) &this->edge_size, sizeof(float)); + stream->read((char*)this->texture_argb, sizeof(float) * 4); + stream->read((char*)this->sphere_texture_argb, sizeof(float) * 4); + stream->read((char*)this->toon_texture_argb, sizeof(float) * 4); + } + + void PmxMorphGroupOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->morph_index = ReadIndex(stream, setting->morph_index_size); + stream->read((char*) &this->morph_weight, sizeof(float)); + } + + void PmxMorphFlipOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->morph_index = ReadIndex(stream, setting->morph_index_size); + stream->read((char*) &this->morph_value, sizeof(float)); + } + + void PmxMorphImplusOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->rigid_body_index = ReadIndex(stream, setting->rigidbody_index_size); + stream->read((char*) &this->is_local, sizeof(uint8_t)); + stream->read((char*)this->velocity, sizeof(float) * 3); + stream->read((char*)this->angular_torque, sizeof(float) * 3); + } + + void PmxMorph::Read(std::istream *stream, PmxSetting *setting) + { + this->morph_name = ReadString(stream, setting->encoding); + this->morph_english_name = ReadString(stream, setting->encoding); + stream->read((char*) &category, sizeof(MorphCategory)); + stream->read((char*) &morph_type, sizeof(MorphType)); + stream->read((char*) &this->offset_count, sizeof(int)); + switch (this->morph_type) + { + case MorphType::Group: + group_offsets = mmd::make_unique<PmxMorphGroupOffset []>(this->offset_count); + for (int i = 0; i < offset_count; i++) + { + group_offsets[i].Read(stream, setting); + } + break; + case MorphType::Vertex: + vertex_offsets = mmd::make_unique<PmxMorphVertexOffset []>(this->offset_count); + for (int i = 0; i < offset_count; i++) + { + vertex_offsets[i].Read(stream, setting); + } + break; + case MorphType::Bone: + bone_offsets = mmd::make_unique<PmxMorphBoneOffset []>(this->offset_count); + for (int i = 0; i < offset_count; i++) + { + bone_offsets[i].Read(stream, setting); + } + break; + case MorphType::Matrial: + material_offsets = mmd::make_unique<PmxMorphMaterialOffset []>(this->offset_count); + for (int i = 0; i < offset_count; i++) + { + material_offsets[i].Read(stream, setting); + } + break; + case MorphType::UV: + case MorphType::AdditionalUV1: + case MorphType::AdditionalUV2: + case MorphType::AdditionalUV3: + case MorphType::AdditionalUV4: + uv_offsets = mmd::make_unique<PmxMorphUVOffset []>(this->offset_count); + for (int i = 0; i < offset_count; i++) + { + uv_offsets[i].Read(stream, setting); + } + break; + default: + throw DeadlyImportError("MMD: unknown morth type"); + } + } + + void PmxFrameElement::Read(std::istream *stream, PmxSetting *setting) + { + stream->read((char*) &this->element_target, sizeof(uint8_t)); + if (this->element_target == 0x00) + { + this->index = ReadIndex(stream, setting->bone_index_size); + } + else { + this->index = ReadIndex(stream, setting->morph_index_size); + } + } + + void PmxFrame::Read(std::istream *stream, PmxSetting *setting) + { + this->frame_name = ReadString(stream, setting->encoding); + this->frame_english_name = ReadString(stream, setting->encoding); + stream->read((char*) &this->frame_flag, sizeof(uint8_t)); + stream->read((char*) &this->element_count, sizeof(int)); + this->elements = mmd::make_unique<PmxFrameElement []>(this->element_count); + for (int i = 0; i < this->element_count; i++) + { + this->elements[i].Read(stream, setting); + } + } + + void PmxRigidBody::Read(std::istream *stream, PmxSetting *setting) + { + this->girid_body_name = ReadString(stream, setting->encoding); + this->girid_body_english_name = ReadString(stream, setting->encoding); + this->target_bone = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->group, sizeof(uint8_t)); + stream->read((char*) &this->mask, sizeof(uint16_t)); + stream->read((char*) &this->shape, sizeof(uint8_t)); + stream->read((char*) this->size, sizeof(float) * 3); + stream->read((char*) this->position, sizeof(float) * 3); + stream->read((char*) this->orientation, sizeof(float) * 3); + stream->read((char*) &this->mass, sizeof(float)); + stream->read((char*) &this->move_attenuation, sizeof(float)); + stream->read((char*) &this->rotation_attenuation, sizeof(float)); + stream->read((char*) &this->repulsion, sizeof(float)); + stream->read((char*) &this->friction, sizeof(float)); + stream->read((char*) &this->physics_calc_type, sizeof(uint8_t)); + } + + void PmxJointParam::Read(std::istream *stream, PmxSetting *setting) + { + this->rigid_body1 = ReadIndex(stream, setting->rigidbody_index_size); + this->rigid_body2 = ReadIndex(stream, setting->rigidbody_index_size); + stream->read((char*) this->position, sizeof(float) * 3); + stream->read((char*) this->orientaiton, sizeof(float) * 3); + stream->read((char*) this->move_limitation_min, sizeof(float) * 3); + stream->read((char*) this->move_limitation_max, sizeof(float) * 3); + stream->read((char*) this->rotation_limitation_min, sizeof(float) * 3); + stream->read((char*) this->rotation_limitation_max, sizeof(float) * 3); + stream->read((char*) this->spring_move_coefficient, sizeof(float) * 3); + stream->read((char*) this->spring_rotation_coefficient, sizeof(float) * 3); + } + + void PmxJoint::Read(std::istream *stream, PmxSetting *setting) + { + this->joint_name = ReadString(stream, setting->encoding); + this->joint_english_name = ReadString(stream, setting->encoding); + stream->read((char*) &this->joint_type, sizeof(uint8_t)); + this->param.Read(stream, setting); + } + + void PmxAncherRigidBody::Read(std::istream *stream, PmxSetting *setting) + { + this->related_rigid_body = ReadIndex(stream, setting->rigidbody_index_size); + this->related_vertex = ReadIndex(stream, setting->vertex_index_size); + stream->read((char*) &this->is_near, sizeof(uint8_t)); + } + + void PmxSoftBody::Read(std::istream * /*stream*/, PmxSetting * /*setting*/) + { + throw DeadlyImportError("MMD: Soft Body support is not implemented."); + } + + void PmxModel::Init() + { + this->version = 0.0f; + this->model_name.clear(); + this->model_english_name.clear(); + this->model_comment.clear(); + this->model_english_comment.clear(); + this->vertex_count = 0; + this->vertices = nullptr; + this->index_count = 0; + this->indices = nullptr; + this->texture_count = 0; + this->textures = nullptr; + this->material_count = 0; + this->materials = nullptr; + this->bone_count = 0; + this->bones = nullptr; + this->morph_count = 0; + this->morphs = nullptr; + this->frame_count = 0; + this->frames = nullptr; + this->rigid_body_count = 0; + this->rigid_bodies = nullptr; + this->joint_count = 0; + this->joints = nullptr; + this->soft_body_count = 0; + this->soft_bodies = nullptr; + } + + void PmxModel::Read(std::istream *stream) + { + char magic[4]; + stream->read((char*) magic, sizeof(char) * 4); + if (magic[0] != 0x50 || magic[1] != 0x4d || magic[2] != 0x58 || magic[3] != 0x20) + { + throw DeadlyImportError("MMD: Invalid magic number."); + } + stream->read((char*) &version, sizeof(float)); + if (version != 2.0f && version != 2.1f) + { + throw DeadlyImportError("MMD: Unsupported version (must be 2.0 or 2.1): ", ai_to_string(version)); + } + this->setting.Read(stream); + + this->model_name = ReadString(stream, setting.encoding); + this->model_english_name = ReadString(stream, setting.encoding); + this->model_comment = ReadString(stream, setting.encoding); + this->model_english_comment = ReadString(stream, setting.encoding); + + // read vertices + stream->read((char*) &vertex_count, sizeof(int)); + this->vertices = mmd::make_unique<PmxVertex []>(vertex_count); + for (int i = 0; i < vertex_count; i++) + { + vertices[i].Read(stream, &setting); + } + + // read indices + stream->read((char*) &index_count, sizeof(int)); + this->indices = mmd::make_unique<int []>(index_count); + for (int i = 0; i < index_count; i++) + { + this->indices[i] = ReadIndex(stream, setting.vertex_index_size); + } + + // read texture names + stream->read((char*) &texture_count, sizeof(int)); + this->textures = mmd::make_unique<std::string []>(texture_count); + for (int i = 0; i < texture_count; i++) + { + this->textures[i] = ReadString(stream, setting.encoding); + } + + // read materials + stream->read((char*) &material_count, sizeof(int)); + this->materials = mmd::make_unique<PmxMaterial []>(material_count); + for (int i = 0; i < material_count; i++) + { + this->materials[i].Read(stream, &setting); + } + + // read bones + stream->read((char*) &this->bone_count, sizeof(int)); + this->bones = mmd::make_unique<PmxBone []>(this->bone_count); + for (int i = 0; i < this->bone_count; i++) + { + this->bones[i].Read(stream, &setting); + } + + // read morphs + stream->read((char*) &this->morph_count, sizeof(int)); + this->morphs = mmd::make_unique<PmxMorph []>(this->morph_count); + for (int i = 0; i < this->morph_count; i++) + { + this->morphs[i].Read(stream, &setting); + } + + // read display frames + stream->read((char*) &this->frame_count, sizeof(int)); + this->frames = mmd::make_unique<PmxFrame []>(this->frame_count); + for (int i = 0; i < this->frame_count; i++) + { + this->frames[i].Read(stream, &setting); + } + + // read rigid bodies + stream->read((char*) &this->rigid_body_count, sizeof(int)); + this->rigid_bodies = mmd::make_unique<PmxRigidBody []>(this->rigid_body_count); + for (int i = 0; i < this->rigid_body_count; i++) + { + this->rigid_bodies[i].Read(stream, &setting); + } + + // read joints + stream->read((char*) &this->joint_count, sizeof(int)); + this->joints = mmd::make_unique<PmxJoint []>(this->joint_count); + for (int i = 0; i < this->joint_count; i++) + { + this->joints[i].Read(stream, &setting); + } + } +} diff --git a/libs/assimp/code/AssetLib/MMD/MMDPmxParser.h b/libs/assimp/code/AssetLib/MMD/MMDPmxParser.h new file mode 100644 index 0000000..08f57f6 --- /dev/null +++ b/libs/assimp/code/AssetLib/MMD/MMDPmxParser.h @@ -0,0 +1,782 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#pragma once + +#include <vector> +#include <string> +#include <iostream> +#include <fstream> +#include <memory> +#include "MMDCpp14.h" + +namespace pmx +{ + class PmxSetting + { + public: + PmxSetting() + : encoding(0) + , uv(0) + , vertex_index_size(0) + , texture_index_size(0) + , material_index_size(0) + , bone_index_size(0) + , morph_index_size(0) + , rigidbody_index_size(0) + {} + + uint8_t encoding; + uint8_t uv; + uint8_t vertex_index_size; + uint8_t texture_index_size; + uint8_t material_index_size; + uint8_t bone_index_size; + uint8_t morph_index_size; + uint8_t rigidbody_index_size; + void Read(std::istream *stream); + }; + + enum class PmxVertexSkinningType : uint8_t + { + BDEF1 = 0, + BDEF2 = 1, + BDEF4 = 2, + SDEF = 3, + QDEF = 4, + }; + + class PmxVertexSkinning + { + public: + virtual void Read(std::istream *stream, PmxSetting *setting) = 0; + virtual ~PmxVertexSkinning() {} + }; + + class PmxVertexSkinningBDEF1 : public PmxVertexSkinning + { + public: + PmxVertexSkinningBDEF1() + : bone_index(0) + {} + + int bone_index; + void Read(std::istream *stresam, PmxSetting *setting); + }; + + class PmxVertexSkinningBDEF2 : public PmxVertexSkinning + { + public: + PmxVertexSkinningBDEF2() + : bone_index1(0) + , bone_index2(0) + , bone_weight(0.0f) + {} + + int bone_index1; + int bone_index2; + float bone_weight; + void Read(std::istream *stresam, PmxSetting *setting); + }; + + class PmxVertexSkinningBDEF4 : public PmxVertexSkinning + { + public: + PmxVertexSkinningBDEF4() + : bone_index1(0) + , bone_index2(0) + , bone_index3(0) + , bone_index4(0) + , bone_weight1(0.0f) + , bone_weight2(0.0f) + , bone_weight3(0.0f) + , bone_weight4(0.0f) + {} + + int bone_index1; + int bone_index2; + int bone_index3; + int bone_index4; + float bone_weight1; + float bone_weight2; + float bone_weight3; + float bone_weight4; + void Read(std::istream *stresam, PmxSetting *setting); + }; + + class PmxVertexSkinningSDEF : public PmxVertexSkinning + { + public: + PmxVertexSkinningSDEF() + : bone_index1(0) + , bone_index2(0) + , bone_weight(0.0f) + { + for (int i = 0; i < 3; ++i) { + sdef_c[i] = 0.0f; + sdef_r0[i] = 0.0f; + sdef_r1[i] = 0.0f; + } + } + + int bone_index1; + int bone_index2; + float bone_weight; + float sdef_c[3]; + float sdef_r0[3]; + float sdef_r1[3]; + void Read(std::istream *stresam, PmxSetting *setting); + }; + + class PmxVertexSkinningQDEF : public PmxVertexSkinning + { + public: + PmxVertexSkinningQDEF() + : bone_index1(0) + , bone_index2(0) + , bone_index3(0) + , bone_index4(0) + , bone_weight1(0.0f) + , bone_weight2(0.0f) + , bone_weight3(0.0f) + , bone_weight4(0.0f) + {} + + int bone_index1; + int bone_index2; + int bone_index3; + int bone_index4; + float bone_weight1; + float bone_weight2; + float bone_weight3; + float bone_weight4; + void Read(std::istream *stresam, PmxSetting *setting); + }; + + class PmxVertex + { + public: + PmxVertex() + : edge(0.0f) + { + uv[0] = uv[1] = 0.0f; + for (int i = 0; i < 3; ++i) { + position[i] = 0.0f; + normal[i] = 0.0f; + } + for (int i = 0; i < 4; ++i) { + for (int k = 0; k < 4; ++k) { + uva[i][k] = 0.0f; + } + } + } + + float position[3]; + float normal[3]; + float uv[2]; + float uva[4][4]; + PmxVertexSkinningType skinning_type; + std::unique_ptr<PmxVertexSkinning> skinning; + float edge; + void Read(std::istream *stream, PmxSetting *setting); + }; + + class PmxMaterial + { + public: + PmxMaterial() + : specularlity(0.0f) + , flag(0) + , edge_size(0.0f) + , diffuse_texture_index(0) + , sphere_texture_index(0) + , sphere_op_mode(0) + , common_toon_flag(0) + , toon_texture_index(0) + , index_count(0) + { + for (int i = 0; i < 3; ++i) { + specular[i] = 0.0f; + ambient[i] = 0.0f; + edge_color[i] = 0.0f; + } + for (int i = 0; i < 4; ++i) { + diffuse[i] = 0.0f; + } + } + + std::string material_name; + std::string material_english_name; + float diffuse[4]; + float specular[3]; + float specularlity; + float ambient[3]; + uint8_t flag; + float edge_color[4]; + float edge_size; + int diffuse_texture_index; + int sphere_texture_index; + uint8_t sphere_op_mode; + uint8_t common_toon_flag; + int toon_texture_index; + std::string memo; + int index_count; + void Read(std::istream *stream, PmxSetting *setting); + }; + + class PmxIkLink + { + public: + PmxIkLink() + : link_target(0) + , angle_lock(0) + { + for (int i = 0; i < 3; ++i) { + max_radian[i] = 0.0f; + min_radian[i] = 0.0f; + } + } + + int link_target; + uint8_t angle_lock; + float max_radian[3]; + float min_radian[3]; + void Read(std::istream *stream, PmxSetting *settingn); + }; + + class PmxBone + { + public: + PmxBone() + : parent_index(0) + , level(0) + , bone_flag(0) + , target_index(0) + , grant_parent_index(0) + , grant_weight(0.0f) + , key(0) + , ik_target_bone_index(0) + , ik_loop(0) + , ik_loop_angle_limit(0.0f) + , ik_link_count(0) + { + for (int i = 0; i < 3; ++i) { + position[i] = 0.0f; + offset[i] = 0.0f; + lock_axis_orientation[i] = 0.0f; + local_axis_x_orientation[i] = 0.0f; + local_axis_y_orientation[i] = 0.0f; + } + } + + std::string bone_name; + std::string bone_english_name; + float position[3]; + int parent_index; + int level; + uint16_t bone_flag; + float offset[3]; + int target_index; + int grant_parent_index; + float grant_weight; + float lock_axis_orientation[3]; + float local_axis_x_orientation[3]; + float local_axis_y_orientation[3]; + int key; + int ik_target_bone_index; + int ik_loop; + float ik_loop_angle_limit; + int ik_link_count; + std::unique_ptr<PmxIkLink []> ik_links; + void Read(std::istream *stream, PmxSetting *setting); + }; + + enum class MorphType : uint8_t + { + Group = 0, + Vertex = 1, + Bone = 2, + UV = 3, + AdditionalUV1 = 4, + AdditionalUV2 = 5, + AdditionalUV3 = 6, + AdditionalUV4 = 7, + Matrial = 8, + Flip = 9, + Implus = 10, + }; + + enum class MorphCategory : uint8_t + { + ReservedCategory = 0, + Eyebrow = 1, + Eye = 2, + Mouth = 3, + Other = 4, + }; + + class PmxMorphOffset + { + public: + void virtual Read(std::istream *stream, PmxSetting *setting) = 0; + }; + + class PmxMorphVertexOffset : public PmxMorphOffset + { + public: + PmxMorphVertexOffset() + : vertex_index(0) + { + for (int i = 0; i < 3; ++i) { + position_offset[i] = 0.0f; + } + } + int vertex_index; + float position_offset[3]; + void Read(std::istream *stream, PmxSetting *setting); //override; + }; + + class PmxMorphUVOffset : public PmxMorphOffset + { + public: + PmxMorphUVOffset() + : vertex_index(0) + { + for (int i = 0; i < 4; ++i) { + uv_offset[i] = 0.0f; + } + } + int vertex_index; + float uv_offset[4]; + void Read(std::istream *stream, PmxSetting *setting); //override; + }; + + class PmxMorphBoneOffset : public PmxMorphOffset + { + public: + PmxMorphBoneOffset() + : bone_index(0) + { + for (int i = 0; i < 3; ++i) { + translation[i] = 0.0f; + } + for (int i = 0; i < 4; ++i) { + rotation[i] = 0.0f; + } + } + int bone_index; + float translation[3]; + float rotation[4]; + void Read(std::istream *stream, PmxSetting *setting); //override; + }; + + class PmxMorphMaterialOffset : public PmxMorphOffset + { + public: + PmxMorphMaterialOffset() + : specularity(0.0f) + , edge_size(0.0f) + { + for (int i = 0; i < 3; ++i) { + specular[i] = 0.0f; + ambient[i] = 0.0f; + } + for (int i = 0; i < 4; ++i) { + diffuse[i] = 0.0f; + edge_color[i] = 0.0f; + texture_argb[i] = 0.0f; + sphere_texture_argb[i] = 0.0f; + toon_texture_argb[i] = 0.0f; + } + } + int material_index; + uint8_t offset_operation; + float diffuse[4]; + float specular[3]; + float specularity; + float ambient[3]; + float edge_color[4]; + float edge_size; + float texture_argb[4]; + float sphere_texture_argb[4]; + float toon_texture_argb[4]; + void Read(std::istream *stream, PmxSetting *setting); //override; + }; + + class PmxMorphGroupOffset : public PmxMorphOffset + { + public: + PmxMorphGroupOffset() + : morph_index(0) + , morph_weight(0.0f) + {} + int morph_index; + float morph_weight; + void Read(std::istream *stream, PmxSetting *setting); //override; + }; + + class PmxMorphFlipOffset : public PmxMorphOffset + { + public: + PmxMorphFlipOffset() + : morph_index(0) + , morph_value(0.0f) + {} + int morph_index; + float morph_value; + void Read(std::istream *stream, PmxSetting *setting); //override; + }; + + class PmxMorphImplusOffset : public PmxMorphOffset + { + public: + PmxMorphImplusOffset() + : rigid_body_index(0) + , is_local(0) + { + for (int i = 0; i < 3; ++i) { + velocity[i] = 0.0f; + angular_torque[i] = 0.0f; + } + } + int rigid_body_index; + uint8_t is_local; + float velocity[3]; + float angular_torque[3]; + void Read(std::istream *stream, PmxSetting *setting); //override; + }; + + class PmxMorph + { + public: + PmxMorph() + : offset_count(0) + { + } + std::string morph_name; + std::string morph_english_name; + MorphCategory category; + MorphType morph_type; + int offset_count; + std::unique_ptr<PmxMorphVertexOffset []> vertex_offsets; + std::unique_ptr<PmxMorphUVOffset []> uv_offsets; + std::unique_ptr<PmxMorphBoneOffset []> bone_offsets; + std::unique_ptr<PmxMorphMaterialOffset []> material_offsets; + std::unique_ptr<PmxMorphGroupOffset []> group_offsets; + std::unique_ptr<PmxMorphFlipOffset []> flip_offsets; + std::unique_ptr<PmxMorphImplusOffset []> implus_offsets; + void Read(std::istream *stream, PmxSetting *setting); + }; + + class PmxFrameElement + { + public: + PmxFrameElement() + : element_target(0) + , index(0) + { + } + uint8_t element_target; + int index; + void Read(std::istream *stream, PmxSetting *setting); + }; + + class PmxFrame + { + public: + PmxFrame() + : frame_flag(0) + , element_count(0) + { + } + std::string frame_name; + std::string frame_english_name; + uint8_t frame_flag; + int element_count; + std::unique_ptr<PmxFrameElement []> elements; + void Read(std::istream *stream, PmxSetting *setting); + }; + + class PmxRigidBody + { + public: + PmxRigidBody() + : target_bone(0) + , group(0) + , mask(0) + , shape(0) + , mass(0.0f) + , move_attenuation(0.0f) + , rotation_attenuation(0.0f) + , repulsion(0.0f) + , friction(0.0f) + , physics_calc_type(0) + { + for (int i = 0; i < 3; ++i) { + size[i] = 0.0f; + position[i] = 0.0f; + orientation[i] = 0.0f; + } + } + std::string girid_body_name; + std::string girid_body_english_name; + int target_bone; + uint8_t group; + uint16_t mask; + uint8_t shape; + float size[3]; + float position[3]; + float orientation[3]; + float mass; + float move_attenuation; + float rotation_attenuation; + float repulsion; + float friction; + uint8_t physics_calc_type; + void Read(std::istream *stream, PmxSetting *setting); + }; + + enum class PmxJointType : uint8_t + { + Generic6DofSpring = 0, + Generic6Dof = 1, + Point2Point = 2, + ConeTwist = 3, + Slider = 5, + Hinge = 6 + }; + + class PmxJointParam + { + public: + PmxJointParam() + : rigid_body1(0) + , rigid_body2(0) + { + for (int i = 0; i < 3; ++i) { + position[i] = 0.0f; + orientaiton[i] = 0.0f; + move_limitation_min[i] = 0.0f; + move_limitation_max[i] = 0.0f; + rotation_limitation_min[i] = 0.0f; + rotation_limitation_max[i] = 0.0f; + spring_move_coefficient[i] = 0.0f; + spring_rotation_coefficient[i] = 0.0f; + } + } + int rigid_body1; + int rigid_body2; + float position[3]; + float orientaiton[3]; + float move_limitation_min[3]; + float move_limitation_max[3]; + float rotation_limitation_min[3]; + float rotation_limitation_max[3]; + float spring_move_coefficient[3]; + float spring_rotation_coefficient[3]; + void Read(std::istream *stream, PmxSetting *setting); + }; + + class PmxJoint + { + public: + std::string joint_name; + std::string joint_english_name; + PmxJointType joint_type; + PmxJointParam param; + void Read(std::istream *stream, PmxSetting *setting); + }; + + enum PmxSoftBodyFlag : uint8_t + { + BLink = 0x01, + Cluster = 0x02, + Link = 0x04 + }; + + class PmxAncherRigidBody + { + public: + PmxAncherRigidBody() + : related_rigid_body(0) + , related_vertex(0) + , is_near(false) + {} + int related_rigid_body; + int related_vertex; + bool is_near; + void Read(std::istream *stream, PmxSetting *setting); + }; + + class PmxSoftBody + { + public: + PmxSoftBody() + : shape(0) + , target_material(0) + , group(0) + , mask(0) + , blink_distance(0) + , cluster_count(0) + , mass(0.0) + , collisioni_margin(0.0) + , aero_model(0) + , VCF(0.0f) + , DP(0.0f) + , DG(0.0f) + , LF(0.0f) + , PR(0.0f) + , VC(0.0f) + , DF(0.0f) + , MT(0.0f) + , CHR(0.0f) + , KHR(0.0f) + , SHR(0.0f) + , AHR(0.0f) + , SRHR_CL(0.0f) + , SKHR_CL(0.0f) + , SSHR_CL(0.0f) + , SR_SPLT_CL(0.0f) + , SK_SPLT_CL(0.0f) + , SS_SPLT_CL(0.0f) + , V_IT(0) + , P_IT(0) + , D_IT(0) + , C_IT(0) + , LST(0.0f) + , AST(0.0f) + , VST(0.0f) + , anchor_count(0) + , pin_vertex_count(0) + {} + std::string soft_body_name; + std::string soft_body_english_name; + uint8_t shape; + int target_material; + uint8_t group; + uint16_t mask; + PmxSoftBodyFlag flag; + int blink_distance; + int cluster_count; + float mass; + float collisioni_margin; + int aero_model; + float VCF; + float DP; + float DG; + float LF; + float PR; + float VC; + float DF; + float MT; + float CHR; + float KHR; + float SHR; + float AHR; + float SRHR_CL; + float SKHR_CL; + float SSHR_CL; + float SR_SPLT_CL; + float SK_SPLT_CL; + float SS_SPLT_CL; + int V_IT; + int P_IT; + int D_IT; + int C_IT; + float LST; + float AST; + float VST; + int anchor_count; + std::unique_ptr<PmxAncherRigidBody []> anchers; + int pin_vertex_count; + std::unique_ptr<int []> pin_vertices; + void Read(std::istream *stream, PmxSetting *setting); + }; + + class PmxModel + { + public: + PmxModel() + : version(0.0f) + , vertex_count(0) + , index_count(0) + , texture_count(0) + , material_count(0) + , bone_count(0) + , morph_count(0) + , frame_count(0) + , rigid_body_count(0) + , joint_count(0) + , soft_body_count(0) + {} + + float version; + PmxSetting setting; + std::string model_name; + std::string model_english_name; + std::string model_comment; + std::string model_english_comment; + int vertex_count; + std::unique_ptr<PmxVertex []> vertices; + int index_count; + std::unique_ptr<int []> indices; + int texture_count; + std::unique_ptr< std::string []> textures; + int material_count; + std::unique_ptr<PmxMaterial []> materials; + int bone_count; + std::unique_ptr<PmxBone []> bones; + int morph_count; + std::unique_ptr<PmxMorph []> morphs; + int frame_count; + std::unique_ptr<PmxFrame [] > frames; + int rigid_body_count; + std::unique_ptr<PmxRigidBody []> rigid_bodies; + int joint_count; + std::unique_ptr<PmxJoint []> joints; + int soft_body_count; + std::unique_ptr<PmxSoftBody []> soft_bodies; + void Init(); + void Read(std::istream *stream); + //static std::unique_ptr<PmxModel> ReadFromFile(const char *filename); + //static std::unique_ptr<PmxModel> ReadFromStream(std::istream *stream); + }; +} diff --git a/libs/assimp/code/AssetLib/MMD/MMDVmdParser.h b/libs/assimp/code/AssetLib/MMD/MMDVmdParser.h new file mode 100644 index 0000000..d5c7ded --- /dev/null +++ b/libs/assimp/code/AssetLib/MMD/MMDVmdParser.h @@ -0,0 +1,376 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#pragma once + +#include <vector> +#include <string> +#include <memory> +#include <iostream> +#include <fstream> +#include <ostream> +#include "MMDCpp14.h" + +namespace vmd +{ + class VmdBoneFrame + { + public: + std::string name; + int frame; + float position[3]; + float orientation[4]; + char interpolation[4][4][4]; + + void Read(std::istream* stream) + { + char buffer[15]; + stream->read((char*) buffer, sizeof(char)*15); + name = std::string(buffer); + stream->read((char*) &frame, sizeof(int)); + stream->read((char*) position, sizeof(float)*3); + stream->read((char*) orientation, sizeof(float)*4); + stream->read((char*) interpolation, sizeof(char) * 4 * 4 * 4); + } + + void Write(std::ostream* stream) + { + stream->write((char*)name.c_str(), sizeof(char) * 15); + stream->write((char*)&frame, sizeof(int)); + stream->write((char*)position, sizeof(float) * 3); + stream->write((char*)orientation, sizeof(float) * 4); + stream->write((char*)interpolation, sizeof(char) * 4 * 4 * 4); + } + }; + + class VmdFaceFrame + { + public: + std::string face_name; + float weight; + uint32_t frame; + + void Read(std::istream* stream) + { + char buffer[15]; + stream->read((char*) &buffer, sizeof(char) * 15); + face_name = std::string(buffer); + stream->read((char*) &frame, sizeof(int)); + stream->read((char*) &weight, sizeof(float)); + } + + void Write(std::ostream* stream) + { + stream->write((char*)face_name.c_str(), sizeof(char) * 15); + stream->write((char*)&frame, sizeof(int)); + stream->write((char*)&weight, sizeof(float)); + } + }; + + class VmdCameraFrame + { + public: + int frame; + float distance; + float position[3]; + float orientation[3]; + char interpolation[6][4]; + float angle; + char unknown[3]; + + void Read(std::istream *stream) + { + stream->read((char*) &frame, sizeof(int)); + stream->read((char*) &distance, sizeof(float)); + stream->read((char*) position, sizeof(float) * 3); + stream->read((char*) orientation, sizeof(float) * 3); + stream->read((char*) interpolation, sizeof(char) * 24); + stream->read((char*) &angle, sizeof(float)); + stream->read((char*) unknown, sizeof(char) * 3); + } + + void Write(std::ostream *stream) + { + stream->write((char*)&frame, sizeof(int)); + stream->write((char*)&distance, sizeof(float)); + stream->write((char*)position, sizeof(float) * 3); + stream->write((char*)orientation, sizeof(float) * 3); + stream->write((char*)interpolation, sizeof(char) * 24); + stream->write((char*)&angle, sizeof(float)); + stream->write((char*)unknown, sizeof(char) * 3); + } + }; + + class VmdLightFrame + { + public: + int frame; + float color[3]; + float position[3]; + + void Read(std::istream* stream) + { + stream->read((char*) &frame, sizeof(int)); + stream->read((char*) color, sizeof(float) * 3); + stream->read((char*) position, sizeof(float) * 3); + } + + void Write(std::ostream* stream) + { + stream->write((char*)&frame, sizeof(int)); + stream->write((char*)color, sizeof(float) * 3); + stream->write((char*)position, sizeof(float) * 3); + } + }; + + class VmdIkEnable + { + public: + std::string ik_name; + bool enable; + }; + + class VmdIkFrame + { + public: + int frame; + bool display; + std::vector<VmdIkEnable> ik_enable; + + void Read(std::istream *stream) + { + char buffer[20]; + stream->read((char*) &frame, sizeof(int)); + stream->read((char*) &display, sizeof(uint8_t)); + int ik_count; + stream->read((char*) &ik_count, sizeof(int)); + ik_enable.resize(ik_count); + for (int i = 0; i < ik_count; i++) + { + stream->read(buffer, 20); + ik_enable[i].ik_name = std::string(buffer); + stream->read((char*) &ik_enable[i].enable, sizeof(uint8_t)); + } + } + + void Write(std::ostream *stream) + { + stream->write((char*)&frame, sizeof(int)); + stream->write((char*)&display, sizeof(uint8_t)); + int ik_count = static_cast<int>(ik_enable.size()); + stream->write((char*)&ik_count, sizeof(int)); + for (int i = 0; i < ik_count; i++) + { + const VmdIkEnable& ik_enable_ref = this->ik_enable.at(i); + stream->write(ik_enable_ref.ik_name.c_str(), 20); + stream->write((char *)&ik_enable_ref.enable, sizeof(uint8_t)); + } + } + }; + + class VmdMotion + { + public: + std::string model_name; + int version; + std::vector<VmdBoneFrame> bone_frames; + std::vector<VmdFaceFrame> face_frames; + std::vector<VmdCameraFrame> camera_frames; + std::vector<VmdLightFrame> light_frames; + std::vector<VmdIkFrame> ik_frames; + + static std::unique_ptr<VmdMotion> LoadFromFile(char const *filename) + { + std::ifstream stream(filename, std::ios::binary); + auto result = LoadFromStream(&stream); + stream.close(); + return result; + } + + static std::unique_ptr<VmdMotion> LoadFromStream(std::ifstream *stream) + { + + char buffer[30]; + auto result = mmd::make_unique<VmdMotion>(); + + // magic and version + stream->read((char*) buffer, 30); + if (strncmp(buffer, "Vocaloid Motion Data", 20)) + { + std::cerr << "invalid vmd file." << std::endl; + return nullptr; + } + result->version = std::atoi(buffer + 20); + + // name + stream->read(buffer, 20); + result->model_name = std::string(buffer); + + // bone frames + int bone_frame_num; + stream->read((char*) &bone_frame_num, sizeof(int)); + result->bone_frames.resize(bone_frame_num); + for (int i = 0; i < bone_frame_num; i++) + { + result->bone_frames[i].Read(stream); + } + + // face frames + int face_frame_num; + stream->read((char*) &face_frame_num, sizeof(int)); + result->face_frames.resize(face_frame_num); + for (int i = 0; i < face_frame_num; i++) + { + result->face_frames[i].Read(stream); + } + + // camera frames + int camera_frame_num; + stream->read((char*) &camera_frame_num, sizeof(int)); + result->camera_frames.resize(camera_frame_num); + for (int i = 0; i < camera_frame_num; i++) + { + result->camera_frames[i].Read(stream); + } + + // light frames + int light_frame_num; + stream->read((char*) &light_frame_num, sizeof(int)); + result->light_frames.resize(light_frame_num); + for (int i = 0; i < light_frame_num; i++) + { + result->light_frames[i].Read(stream); + } + + // unknown2 + stream->read(buffer, 4); + + // ik frames + if (stream->peek() != std::ios::traits_type::eof()) + { + int ik_num; + stream->read((char*) &ik_num, sizeof(int)); + result->ik_frames.resize(ik_num); + for (int i = 0; i < ik_num; i++) + { + result->ik_frames[i].Read(stream); + } + } + + if (stream->peek() != std::ios::traits_type::eof()) + { + std::cerr << "vmd stream has unknown data." << std::endl; + } + + return result; + } + + bool SaveToFile(const std::u16string& /*filename*/) + { + // TODO: How to adapt u16string to string? + /* + std::ofstream stream(filename.c_str(), std::ios::binary); + auto result = SaveToStream(&stream); + stream.close(); + return result; + */ + return false; + } + + bool SaveToStream(std::ofstream *stream) + { + std::string magic = "Vocaloid Motion Data 0002\0"; + magic.resize(30); + + // magic and version + stream->write(magic.c_str(), 30); + + // name + stream->write(model_name.c_str(), 20); + + // bone frames + const int bone_frame_num = static_cast<int>(bone_frames.size()); + stream->write(reinterpret_cast<const char*>(&bone_frame_num), sizeof(int)); + for (int i = 0; i < bone_frame_num; i++) + { + bone_frames[i].Write(stream); + } + + // face frames + const int face_frame_num = static_cast<int>(face_frames.size()); + stream->write(reinterpret_cast<const char*>(&face_frame_num), sizeof(int)); + for (int i = 0; i < face_frame_num; i++) + { + face_frames[i].Write(stream); + } + + // camera frames + const int camera_frame_num = static_cast<int>(camera_frames.size()); + stream->write(reinterpret_cast<const char*>(&camera_frame_num), sizeof(int)); + for (int i = 0; i < camera_frame_num; i++) + { + camera_frames[i].Write(stream); + } + + // light frames + const int light_frame_num = static_cast<int>(light_frames.size()); + stream->write(reinterpret_cast<const char*>(&light_frame_num), sizeof(int)); + for (int i = 0; i < light_frame_num; i++) + { + light_frames[i].Write(stream); + } + + // self shadow data + const int self_shadow_num = 0; + stream->write(reinterpret_cast<const char*>(&self_shadow_num), sizeof(int)); + + // ik frames + const int ik_num = static_cast<int>(ik_frames.size()); + stream->write(reinterpret_cast<const char*>(&ik_num), sizeof(int)); + for (int i = 0; i < ik_num; i++) + { + ik_frames[i].Write(stream); + } + + return true; + } + }; +} diff --git a/libs/assimp/code/AssetLib/MS3D/MS3DLoader.cpp b/libs/assimp/code/AssetLib/MS3D/MS3DLoader.cpp new file mode 100644 index 0000000..fc2ed04 --- /dev/null +++ b/libs/assimp/code/AssetLib/MS3D/MS3DLoader.cpp @@ -0,0 +1,656 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MS3DLoader.cpp + * @brief Implementation of the Ms3D importer class. + * Written against http://chumbalum.swissquake.ch/ms3d/ms3dspec.txt + */ + + +#ifndef ASSIMP_BUILD_NO_MS3D_IMPORTER + +// internal headers +#include "MS3DLoader.h" +#include <assimp/StreamReader.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/scene.h> +#include <assimp/IOSystem.hpp> +#include <assimp/importerdesc.h> +#include <map> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Milkshape 3D Importer", + "", + "", + "http://chumbalum.swissquake.ch/", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "ms3d" +}; + +// ASSIMP_BUILD_MS3D_ONE_NODE_PER_MESH +// (enable old code path, which generates extra nodes per mesh while +// the newer code uses aiMesh::mName to express the name of the +// meshes (a.k.a. groups in MS3D)) + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +MS3DImporter::MS3DImporter() + : mScene() +{} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +MS3DImporter::~MS3DImporter() +{} +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool MS3DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const +{ + static const char* tokens[] = { "MS3D000000" }; + return SearchFileHeaderForToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc* MS3DImporter::GetInfo () const +{ + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +void ReadColor(StreamReaderLE& stream, aiColor4D& ambient) +{ + // aiColor4D is packed on gcc, implicit binding to float& fails therefore. + stream >> (float&)ambient.r >> (float&)ambient.g >> (float&)ambient.b >> (float&)ambient.a; +} + +// ------------------------------------------------------------------------------------------------ +void ReadVector(StreamReaderLE& stream, aiVector3D& pos) +{ + // See note in ReadColor() + stream >> (float&)pos.x >> (float&)pos.y >> (float&)pos.z; +} + +// ------------------------------------------------------------------------------------------------ +template<typename T> +void MS3DImporter :: ReadComments(StreamReaderLE& stream, std::vector<T>& outp) +{ + uint16_t cnt; + stream >> cnt; + + for(unsigned int i = 0; i < cnt; ++i) { + uint32_t index, clength; + stream >> index >> clength; + + if(index >= outp.size()) { + ASSIMP_LOG_WARN("MS3D: Invalid index in comment section"); + } + else if (clength > stream.GetRemainingSize()) { + throw DeadlyImportError("MS3D: Failure reading comment, length field is out of range"); + } + else { + outp[index].comment = std::string(reinterpret_cast<char*>(stream.GetPtr()),clength); + } + stream.IncPtr(clength); + } +} + +// ------------------------------------------------------------------------------------------------ +template <typename T, typename T2, typename T3> bool inrange(const T& in, const T2& lower, const T3& higher) +{ + return in > lower && in <= higher; +} + +// ------------------------------------------------------------------------------------------------ +void MS3DImporter :: CollectChildJoints(const std::vector<TempJoint>& joints, + std::vector<bool>& hadit, + aiNode* nd, + const aiMatrix4x4& absTrafo) +{ + unsigned int cnt = 0; + for(size_t i = 0; i < joints.size(); ++i) { + if (!hadit[i] && !strcmp(joints[i].parentName,nd->mName.data)) { + ++cnt; + } + } + + nd->mChildren = new aiNode*[nd->mNumChildren = cnt]; + cnt = 0; + for(size_t i = 0; i < joints.size(); ++i) { + if (!hadit[i] && !strcmp(joints[i].parentName,nd->mName.data)) { + aiNode* ch = nd->mChildren[cnt++] = new aiNode(joints[i].name); + ch->mParent = nd; + + ch->mTransformation = aiMatrix4x4::Translation(joints[i].position,aiMatrix4x4()=aiMatrix4x4())* + aiMatrix4x4().FromEulerAnglesXYZ(joints[i].rotation); + + const aiMatrix4x4 abs = absTrafo*ch->mTransformation; + for(unsigned int a = 0; a < mScene->mNumMeshes; ++a) { + aiMesh* const msh = mScene->mMeshes[a]; + for(unsigned int n = 0; n < msh->mNumBones; ++n) { + aiBone* const bone = msh->mBones[n]; + + if(bone->mName == ch->mName) { + bone->mOffsetMatrix = aiMatrix4x4(abs).Inverse(); + } + } + } + + hadit[i] = true; + CollectChildJoints(joints,hadit,ch,abs); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void MS3DImporter :: CollectChildJoints(const std::vector<TempJoint>& joints, aiNode* nd) +{ + std::vector<bool> hadit(joints.size(),false); + aiMatrix4x4 trafo; + + CollectChildJoints(joints,hadit,nd,trafo); +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void MS3DImporter::InternReadFile( const std::string& pFile, + aiScene* pScene, IOSystem* pIOHandler) +{ + + auto file = pIOHandler->Open(pFile, "rb"); + if (!file) + throw DeadlyImportError("MS3D: Could not open ", pFile); + + StreamReaderLE stream(file); + + // CanRead() should have done this already + char head[10]; + int32_t version; + + mScene = pScene; + + + // 1 ------------ read into temporary data structures mirroring the original file + + stream.CopyAndAdvance(head,10); + stream >> version; + if (strncmp(head,"MS3D000000",10)) { + throw DeadlyImportError("Not a MS3D file, magic string MS3D000000 not found: ", pFile); + } + + if (version != 4) { + throw DeadlyImportError("MS3D: Unsupported file format version, 4 was expected"); + } + + uint16_t verts; + stream >> verts; + + std::vector<TempVertex> vertices(verts); + for (unsigned int i = 0; i < verts; ++i) { + TempVertex& v = vertices[i]; + + stream.IncPtr(1); + ReadVector(stream,v.pos); + v.bone_id[0] = stream.GetI1(); + v.ref_cnt = stream.GetI1(); + + v.bone_id[1] = v.bone_id[2] = v.bone_id[3] = UINT_MAX; + v.weights[1] = v.weights[2] = v.weights[3] = 0.f; + v.weights[0] = 1.f; + } + + uint16_t tris; + stream >> tris; + + std::vector<TempTriangle> triangles(tris); + for (unsigned int i = 0;i < tris; ++i) { + TempTriangle& t = triangles[i]; + + stream.IncPtr(2); + for (unsigned int j = 0; j < 3; ++j) { + t.indices[j] = stream.GetI2(); + } + + for (unsigned int j = 0; j < 3; ++j) { + ReadVector(stream,t.normals[j]); + } + + for (unsigned int j = 0; j < 3; ++j) { + stream >> (float&)(t.uv[j].x); // see note in ReadColor() + } + for (unsigned int j = 0; j < 3; ++j) { + stream >> (float&)(t.uv[j].y); + } + + t.sg = stream.GetI1(); + t.group = stream.GetI1(); + } + + uint16_t grp; + stream >> grp; + + bool need_default = false; + std::vector<TempGroup> groups(grp); + for (unsigned int i = 0;i < grp; ++i) { + TempGroup& t = groups[i]; + + stream.IncPtr(1); + stream.CopyAndAdvance(t.name,32); + + t.name[32] = '\0'; + uint16_t num; + stream >> num; + + t.triangles.resize(num); + for (unsigned int j = 0; j < num; ++j) { + t.triangles[j] = stream.GetI2(); + } + t.mat = stream.GetI1(); + if (t.mat == UINT_MAX) { + need_default = true; + } + } + + uint16_t mat; + stream >> mat; + + std::vector<TempMaterial> materials(mat); + for (unsigned int j = 0;j < mat; ++j) { + TempMaterial& t = materials[j]; + + stream.CopyAndAdvance(t.name,32); + t.name[32] = '\0'; + + ReadColor(stream,t.ambient); + ReadColor(stream,t.diffuse); + ReadColor(stream,t.specular); + ReadColor(stream,t.emissive); + stream >> t.shininess >> t.transparency; + + stream.IncPtr(1); + + stream.CopyAndAdvance(t.texture,128); + t.texture[128] = '\0'; + + stream.CopyAndAdvance(t.alphamap,128); + t.alphamap[128] = '\0'; + } + + float animfps, currenttime; + uint32_t totalframes; + stream >> animfps >> currenttime >> totalframes; + + uint16_t joint; + stream >> joint; + + std::vector<TempJoint> joints(joint); + for(unsigned int ii = 0; ii < joint; ++ii) { + TempJoint& j = joints[ii]; + + stream.IncPtr(1); + stream.CopyAndAdvance(j.name,32); + j.name[32] = '\0'; + + stream.CopyAndAdvance(j.parentName,32); + j.parentName[32] = '\0'; + + ReadVector(stream,j.rotation); + ReadVector(stream,j.position); + + j.rotFrames.resize(stream.GetI2()); + j.posFrames.resize(stream.GetI2()); + + for(unsigned int a = 0; a < j.rotFrames.size(); ++a) { + TempKeyFrame& kf = j.rotFrames[a]; + stream >> kf.time; + ReadVector(stream,kf.value); + } + for(unsigned int a = 0; a < j.posFrames.size(); ++a) { + TempKeyFrame& kf = j.posFrames[a]; + stream >> kf.time; + ReadVector(stream,kf.value); + } + } + + if(stream.GetRemainingSize() > 4) { + uint32_t subversion; + stream >> subversion; + if (subversion == 1) { + ReadComments<TempGroup>(stream,groups); + ReadComments<TempMaterial>(stream,materials); + ReadComments<TempJoint>(stream,joints); + + // model comment - print it for we have such a nice log. + if (stream.GetI4()) { + const size_t len = static_cast<size_t>(stream.GetI4()); + if (len > stream.GetRemainingSize()) { + throw DeadlyImportError("MS3D: Model comment is too long"); + } + + const std::string& s = std::string(reinterpret_cast<char*>(stream.GetPtr()),len); + ASSIMP_LOG_DEBUG("MS3D: Model comment: ", s); + } + + if(stream.GetRemainingSize() > 4 && inrange((stream >> subversion,subversion),1u,3u)) { + for(unsigned int i = 0; i < verts; ++i) { + TempVertex& v = vertices[i]; + v.weights[3]=1.f; + for(unsigned int n = 0; n < 3; v.weights[3]-=v.weights[n++]) { + v.bone_id[n+1] = stream.GetI1(); + v.weights[n] = static_cast<float>(static_cast<unsigned int>(stream.GetI1()))/255.f; + } + stream.IncPtr((subversion-1)<<2u); + } + + // even further extra data is not of interest for us, at least now now. + } + } + } + + // 2 ------------ convert to proper aiXX data structures ----------------------------------- + + if (need_default && materials.size()) { + ASSIMP_LOG_WARN("MS3D: Found group with no material assigned, spawning default material"); + // if one of the groups has no material assigned, but there are other + // groups with materials, a default material needs to be added ( + // scenepreprocessor adds a default material only if nummat==0). + materials.push_back(TempMaterial()); + TempMaterial& m = materials.back(); + + strcpy(m.name,"<MS3D_DefaultMat>"); + m.diffuse = aiColor4D(0.6f,0.6f,0.6f,1.0); + m.transparency = 1.f; + m.shininess = 0.f; + + // this is because these TempXXX struct's have no c'tors. + m.texture[0] = m.alphamap[0] = '\0'; + + for (unsigned int i = 0; i < groups.size(); ++i) { + TempGroup& g = groups[i]; + if (g.mat == UINT_MAX) { + g.mat = static_cast<unsigned int>(materials.size()-1); + } + } + } + + // convert materials to our generic key-value dict-alike + if (materials.size()) { + pScene->mMaterials = new aiMaterial*[materials.size()]; + for (size_t i = 0; i < materials.size(); ++i) { + + aiMaterial* mo = new aiMaterial(); + pScene->mMaterials[pScene->mNumMaterials++] = mo; + + const TempMaterial& mi = materials[i]; + + aiString tmp; + if (0[mi.alphamap]) { + tmp = aiString(mi.alphamap); + mo->AddProperty(&tmp,AI_MATKEY_TEXTURE_OPACITY(0)); + } + if (0[mi.texture]) { + tmp = aiString(mi.texture); + mo->AddProperty(&tmp,AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + if (0[mi.name]) { + tmp = aiString(mi.name); + mo->AddProperty(&tmp,AI_MATKEY_NAME); + } + + mo->AddProperty(&mi.ambient,1,AI_MATKEY_COLOR_AMBIENT); + mo->AddProperty(&mi.diffuse,1,AI_MATKEY_COLOR_DIFFUSE); + mo->AddProperty(&mi.specular,1,AI_MATKEY_COLOR_SPECULAR); + mo->AddProperty(&mi.emissive,1,AI_MATKEY_COLOR_EMISSIVE); + + mo->AddProperty(&mi.shininess,1,AI_MATKEY_SHININESS); + mo->AddProperty(&mi.transparency,1,AI_MATKEY_OPACITY); + + const int sm = mi.shininess>0.f?aiShadingMode_Phong:aiShadingMode_Gouraud; + mo->AddProperty(&sm,1,AI_MATKEY_SHADING_MODEL); + } + } + + // convert groups to meshes + if (groups.empty()) { + throw DeadlyImportError("MS3D: Didn't get any group records, file is malformed"); + } + + pScene->mMeshes = new aiMesh*[pScene->mNumMeshes=static_cast<unsigned int>(groups.size())](); + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + + aiMesh* m = pScene->mMeshes[i] = new aiMesh(); + const TempGroup& g = groups[i]; + + if (pScene->mNumMaterials && g.mat > pScene->mNumMaterials) { + throw DeadlyImportError("MS3D: Encountered invalid material index, file is malformed"); + } // no error if no materials at all - scenepreprocessor adds one then + + m->mMaterialIndex = g.mat; + m->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + m->mFaces = new aiFace[m->mNumFaces = static_cast<unsigned int>(g.triangles.size())]; + m->mNumVertices = m->mNumFaces*3; + + // storage for vertices - verbose format, as requested by the postprocessing pipeline + m->mVertices = new aiVector3D[m->mNumVertices]; + m->mNormals = new aiVector3D[m->mNumVertices]; + m->mTextureCoords[0] = new aiVector3D[m->mNumVertices]; + m->mNumUVComponents[0] = 2; + + typedef std::map<unsigned int,unsigned int> BoneSet; + BoneSet mybones; + + for (unsigned int j = 0,n = 0; j < m->mNumFaces; ++j) { + aiFace& f = m->mFaces[j]; + if (g.triangles[j]>triangles.size()) { + throw DeadlyImportError("MS3D: Encountered invalid triangle index, file is malformed"); + } + + TempTriangle& t = triangles[g.triangles[j]]; + f.mIndices = new unsigned int[f.mNumIndices=3]; + + for (unsigned int k = 0; k < 3; ++k,++n) { + if (t.indices[k]>vertices.size()) { + throw DeadlyImportError("MS3D: Encountered invalid vertex index, file is malformed"); + } + + const TempVertex& v = vertices[t.indices[k]]; + for(unsigned int a = 0; a < 4; ++a) { + if (v.bone_id[a] != UINT_MAX) { + if (v.bone_id[a] >= joints.size()) { + throw DeadlyImportError("MS3D: Encountered invalid bone index, file is malformed"); + } + if (mybones.find(v.bone_id[a]) == mybones.end()) { + mybones[v.bone_id[a]] = 1; + } + else ++mybones[v.bone_id[a]]; + } + } + + // collect vertex components + m->mVertices[n] = v.pos; + + m->mNormals[n] = t.normals[k]; + m->mTextureCoords[0][n] = aiVector3D(t.uv[k].x,1.f-t.uv[k].y,0.0); + f.mIndices[k] = n; + } + } + + // allocate storage for bones + if(!mybones.empty()) { + std::vector<unsigned int> bmap(joints.size()); + m->mBones = new aiBone*[mybones.size()](); + for(BoneSet::const_iterator it = mybones.begin(); it != mybones.end(); ++it) { + aiBone* const bn = m->mBones[m->mNumBones] = new aiBone(); + const TempJoint& jnt = joints[(*it).first]; + + bn->mName.Set(jnt.name); + bn->mWeights = new aiVertexWeight[(*it).second]; + + bmap[(*it).first] = m->mNumBones++; + } + + // .. and collect bone weights + for (unsigned int j = 0,n = 0; j < m->mNumFaces; ++j) { + TempTriangle& t = triangles[g.triangles[j]]; + + for (unsigned int k = 0; k < 3; ++k,++n) { + const TempVertex& v = vertices[t.indices[k]]; + for(unsigned int a = 0; a < 4; ++a) { + const unsigned int bone = v.bone_id[a]; + if(bone==UINT_MAX){ + continue; + } + + aiBone* const outbone = m->mBones[bmap[bone]]; + aiVertexWeight& outwght = outbone->mWeights[outbone->mNumWeights++]; + + outwght.mVertexId = n; + outwght.mWeight = v.weights[a]; + } + } + } + } + } + + // ... add dummy nodes under a single root, each holding a reference to one + // mesh. If we didn't do this, we'd lose the group name. + aiNode* rt = pScene->mRootNode = new aiNode("<MS3DRoot>"); + +#ifdef ASSIMP_BUILD_MS3D_ONE_NODE_PER_MESH + rt->mChildren = new aiNode*[rt->mNumChildren=pScene->mNumMeshes+(joints.size()?1:0)](); + + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + aiNode* nd = rt->mChildren[i] = new aiNode(); + + const TempGroup& g = groups[i]; + + // we need to generate an unique name for all mesh nodes. + // since we want to keep the group name, a prefix is + // prepended. + nd->mName = aiString("<MS3DMesh>_"); + nd->mName.Append(g.name); + nd->mParent = rt; + + nd->mMeshes = new unsigned int[nd->mNumMeshes = 1]; + nd->mMeshes[0] = i; + } +#else + rt->mMeshes = new unsigned int[pScene->mNumMeshes]; + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + rt->mMeshes[rt->mNumMeshes++] = i; + } +#endif + + // convert animations as well + if(joints.size()) { +#ifndef ASSIMP_BUILD_MS3D_ONE_NODE_PER_MESH + rt->mChildren = new aiNode*[1](); + rt->mNumChildren = 1; + + aiNode* jt = rt->mChildren[0] = new aiNode(); +#else + aiNode* jt = rt->mChildren[pScene->mNumMeshes] = new aiNode(); +#endif + jt->mParent = rt; + CollectChildJoints(joints,jt); + jt->mName.Set("<MS3DJointRoot>"); + + pScene->mAnimations = new aiAnimation*[ pScene->mNumAnimations = 1 ]; + aiAnimation* const anim = pScene->mAnimations[0] = new aiAnimation(); + + anim->mName.Set("<MS3DMasterAnim>"); + + // carry the fps info to the user by scaling all times with it + anim->mTicksPerSecond = animfps; + + // leave duration at its default, so ScenePreprocessor will fill an appropriate + // value (the values taken from some MS3D files seem to be too unreliable + // to pass the validation) + // anim->mDuration = totalframes/animfps; + + anim->mChannels = new aiNodeAnim*[joints.size()](); + for(std::vector<TempJoint>::const_iterator it = joints.begin(); it != joints.end(); ++it) { + if ((*it).rotFrames.empty() && (*it).posFrames.empty()) { + continue; + } + + aiNodeAnim* nd = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim(); + nd->mNodeName.Set((*it).name); + + if ((*it).rotFrames.size()) { + nd->mRotationKeys = new aiQuatKey[(*it).rotFrames.size()]; + for(std::vector<TempKeyFrame>::const_iterator rot = (*it).rotFrames.begin(); rot != (*it).rotFrames.end(); ++rot) { + aiQuatKey& q = nd->mRotationKeys[nd->mNumRotationKeys++]; + + q.mTime = (*rot).time*animfps; + q.mValue = aiQuaternion(aiMatrix3x3(aiMatrix4x4().FromEulerAnglesXYZ((*it).rotation)* + aiMatrix4x4().FromEulerAnglesXYZ((*rot).value))); + } + } + + if ((*it).posFrames.size()) { + nd->mPositionKeys = new aiVectorKey[(*it).posFrames.size()]; + + aiQuatKey* qu = nd->mRotationKeys; + for(std::vector<TempKeyFrame>::const_iterator pos = (*it).posFrames.begin(); pos != (*it).posFrames.end(); ++pos,++qu) { + aiVectorKey& v = nd->mPositionKeys[nd->mNumPositionKeys++]; + + v.mTime = (*pos).time*animfps; + v.mValue = (*it).position + (*pos).value; + } + } + } + // fixup to pass the validation if not a single animation channel is non-trivial + if (!anim->mNumChannels) { + anim->mChannels = nullptr; + } + } +} + +#endif diff --git a/libs/assimp/code/AssetLib/MS3D/MS3DLoader.h b/libs/assimp/code/AssetLib/MS3D/MS3DLoader.h new file mode 100644 index 0000000..4bd4175 --- /dev/null +++ b/libs/assimp/code/AssetLib/MS3D/MS3DLoader.h @@ -0,0 +1,146 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MS3DLoader.h + * @brief Declaration of the MS3D importer class. + */ +#pragma once +#ifndef AI_MS3DLOADER_H_INCLUDED +#define AI_MS3DLOADER_H_INCLUDED + +#include <assimp/BaseImporter.h> +#include <assimp/StreamReader.h> +struct aiNode; + +namespace Assimp { + +// ---------------------------------------------------------------------------------------------- +/** Milkshape 3D importer implementation */ +// ---------------------------------------------------------------------------------------------- +class MS3DImporter : public BaseImporter { +public: + MS3DImporter(); + ~MS3DImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details */ + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + +private: + struct TempJoint; + void CollectChildJoints(const std::vector<TempJoint> &joints, std::vector<bool> &hadit, aiNode *nd, const aiMatrix4x4 &absTrafo); + void CollectChildJoints(const std::vector<TempJoint> &joints, aiNode *nd); + + template <typename T> + void ReadComments(StreamReaderLE &stream, std::vector<T> &outp); + +private: + aiScene *mScene; + +private: + struct TempVertex { + aiVector3D pos; + unsigned int bone_id[4], ref_cnt; + float weights[4]; + }; + + struct TempTriangle { + unsigned int indices[3]; + aiVector3D normals[3]; + aiVector2D uv[3]; + + unsigned int sg, group; + }; + + struct TempGroup { + char name[33]; // +0 + std::vector<unsigned int> triangles; + unsigned int mat; // 0xff is no material + std::string comment; + }; + + struct TempMaterial { + // again, add an extra 0 character to all strings - + char name[33]; + char texture[129]; + char alphamap[129]; + + aiColor4D diffuse, specular, ambient, emissive; + float shininess, transparency; + std::string comment; + }; + + struct TempKeyFrame { + float time; + aiVector3D value; + }; + + struct TempJoint { + char name[33]; + char parentName[33]; + aiVector3D rotation, position; + + std::vector<TempKeyFrame> rotFrames; + std::vector<TempKeyFrame> posFrames; + std::string comment; + }; + + //struct TempModel { + // std::string comment; + //}; +}; + +} // namespace Assimp +#endif diff --git a/libs/assimp/code/AssetLib/NDO/NDOLoader.cpp b/libs/assimp/code/AssetLib/NDO/NDOLoader.cpp new file mode 100644 index 0000000..37c5436 --- /dev/null +++ b/libs/assimp/code/AssetLib/NDO/NDOLoader.cpp @@ -0,0 +1,306 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 NDOLoader.cpp + * Implementation of the NDO importer class. + */ + + +#ifndef ASSIMP_BUILD_NO_NDO_IMPORTER +#include "NDOLoader.h" +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/scene.h> +#include <assimp/importerdesc.h> +#include <assimp/StreamReader.h> +#include <map> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Nendo Mesh Importer", + "", + "", + "http://www.izware.com/nendo/index.htm", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "ndo" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +NDOImporter::NDOImporter() +{} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +NDOImporter::~NDOImporter() +{} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool NDOImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const +{ + static const char* tokens[] = {"nendo"}; + return SearchFileHeaderForToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens),5); +} + +// ------------------------------------------------------------------------------------------------ +// Build a string of all file extensions supported +const aiImporterDesc* NDOImporter::GetInfo () const +{ + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties for the loader +void NDOImporter::SetupProperties(const Importer* /*pImp*/) +{ + // nothing to be done for the moment +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void NDOImporter::InternReadFile( const std::string& pFile, + aiScene* pScene, IOSystem* pIOHandler) +{ + + auto file = pIOHandler->Open( pFile, "rb"); + if (!file) { + throw DeadlyImportError("Nendo: Could not open ", pFile); + } + + StreamReaderBE reader(file); + + // first 9 bytes are nendo file format ("nendo 1.n") + const char* head = (const char*)reader.GetPtr(); + reader.IncPtr(9); + + if (strncmp("nendo ",head,6)) { + throw DeadlyImportError("Not a Nendo file; magic signature missing"); + } + // check if this is a supported version. if not, continue, too -- users, + // please don't complain if it doesn't work then ... + unsigned int file_format = 12; + if (!strncmp("1.0",head+6,3)) { + file_format = 10; + ASSIMP_LOG_INFO("NDO file format is 1.0"); + } + else if (!strncmp("1.1",head+6,3)) { + file_format = 11; + ASSIMP_LOG_INFO("NDO file format is 1.1"); + } + else if (!strncmp("1.2",head+6,3)) { + file_format = 12; + ASSIMP_LOG_INFO("NDO file format is 1.2"); + } + else { + ASSIMP_LOG_WARN( "Unrecognized nendo file format version, continuing happily ... :", (head+6)); + } + + reader.IncPtr(2); /* skip flags */ + if (file_format >= 12) { + reader.IncPtr(2); + } + unsigned int temp = reader.GetU1(); + + std::vector<Object> objects(temp); /* buffer to store all the loaded objects in */ + + // read all objects + for (unsigned int o = 0; o < objects.size(); ++o) { + +// if (file_format < 12) { + if (!reader.GetI1()) { + continue; /* skip over empty object */ + } + // reader.GetI2(); +// } + Object& obj = objects[o]; + + temp = file_format >= 12 ? reader.GetU4() : reader.GetU2(); + head = (const char*)reader.GetPtr(); + reader.IncPtr(temp + 76); /* skip unknown stuff */ + + obj.name = std::string(head, temp); + + // read edge table + temp = file_format >= 12 ? reader.GetU4() : reader.GetU2(); + obj.edges.reserve(temp); + for (unsigned int e = 0; e < temp; ++e) { + + obj.edges.push_back(Edge()); + Edge& edge = obj.edges.back(); + + for (unsigned int i = 0; i< 8; ++i) { + edge.edge[i] = file_format >= 12 ? reader.GetU4() : reader.GetU2(); + } + edge.hard = file_format >= 11 ? reader.GetU1() : 0; + for (unsigned int i = 0; i< 8; ++i) { + edge.color[i] = reader.GetU1(); + } + } + + // read face table + temp = file_format >= 12 ? reader.GetU4() : reader.GetU2(); + obj.faces.reserve(temp); + for (unsigned int e = 0; e < temp; ++e) { + + obj.faces.push_back(Face()); + Face& face = obj.faces.back(); + + face.elem = file_format >= 12 ? reader.GetU4() : reader.GetU2(); + } + + // read vertex table + temp = file_format >= 12 ? reader.GetU4() : reader.GetU2(); + obj.vertices.reserve(temp); + for (unsigned int e = 0; e < temp; ++e) { + + obj.vertices.push_back(Vertex()); + Vertex& v = obj.vertices.back(); + + v.num = file_format >= 12 ? reader.GetU4() : reader.GetU2(); + v.val.x = reader.GetF4(); + v.val.y = reader.GetF4(); + v.val.z = reader.GetF4(); + } + + // read UVs + temp = file_format >= 12 ? reader.GetU4() : reader.GetU2(); + for (unsigned int e = 0; e < temp; ++e) { + file_format >= 12 ? reader.GetU4() : reader.GetU2(); + } + + temp = file_format >= 12 ? reader.GetU4() : reader.GetU2(); + for (unsigned int e = 0; e < temp; ++e) { + file_format >= 12 ? reader.GetU4() : reader.GetU2(); + } + + if (reader.GetU1()) { + const unsigned int x = reader.GetU2(), y = reader.GetU2(); + temp = 0; + while (temp < x*y) { + unsigned int repeat = reader.GetU1(); + reader.GetU1(); + reader.GetU1(); + reader.GetU1(); + temp += repeat; + } + } + } + + // construct a dummy node graph and add all named objects as child nodes + aiNode* root = pScene->mRootNode = new aiNode("$NDODummyRoot"); + aiNode** cc = root->mChildren = new aiNode* [ root->mNumChildren = static_cast<unsigned int>( objects.size()) ] (); + pScene->mMeshes = new aiMesh* [ root->mNumChildren] (); + + std::vector<aiVector3D> vertices; + std::vector<unsigned int> indices; + + for(const Object& obj : objects) { + aiNode* nd = *cc++ = new aiNode(obj.name); + nd->mParent = root; + + // translated from a python dict() - a vector might be sufficient as well + typedef std::map<unsigned int, unsigned int> FaceTable; + FaceTable face_table; + + unsigned int n = 0; + for(const Edge& edge : obj.edges) { + + face_table[edge.edge[2]] = n; + face_table[edge.edge[3]] = n; + + ++n; + } + + aiMesh* mesh = new aiMesh(); + mesh->mNumFaces=static_cast<unsigned int>(face_table.size()); + aiFace* faces = mesh->mFaces = new aiFace[mesh->mNumFaces]; + + vertices.clear(); + vertices.reserve(4 * face_table.size()); // arbitrarily chosen + for(FaceTable::value_type& v : face_table) { + indices.clear(); + + aiFace& f = *faces++; + + const unsigned int key = v.first; + unsigned int cur_edge = v.second; + while (1) { + unsigned int next_edge, next_vert; + if (key == obj.edges[cur_edge].edge[3]) { + next_edge = obj.edges[cur_edge].edge[5]; + next_vert = obj.edges[cur_edge].edge[1]; + } + else { + next_edge = obj.edges[cur_edge].edge[4]; + next_vert = obj.edges[cur_edge].edge[0]; + } + indices.push_back( static_cast<unsigned int>(vertices.size()) ); + vertices.push_back(obj.vertices[ next_vert ].val); + + cur_edge = next_edge; + if (cur_edge == v.second) { + break; + } + } + + f.mIndices = new unsigned int[f.mNumIndices = static_cast<unsigned int>(indices.size())]; + std::copy(indices.begin(),indices.end(),f.mIndices); + } + + mesh->mVertices = new aiVector3D[mesh->mNumVertices = static_cast<unsigned int>(vertices.size())]; + std::copy(vertices.begin(),vertices.end(),mesh->mVertices); + + if (mesh->mNumVertices) { + pScene->mMeshes[pScene->mNumMeshes] = mesh; + + (nd->mMeshes = new unsigned int[nd->mNumMeshes=1])[0]=pScene->mNumMeshes++; + }else + delete mesh; + } +} + +#endif diff --git a/libs/assimp/code/AssetLib/NDO/NDOLoader.h b/libs/assimp/code/AssetLib/NDO/NDOLoader.h new file mode 100644 index 0000000..61dd939 --- /dev/null +++ b/libs/assimp/code/AssetLib/NDO/NDOLoader.h @@ -0,0 +1,116 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 NDOLoader.h + * Declaration of the Nendo importer class. + */ +#ifndef INCLUDED_AI_NDO_LOADER_H +#define INCLUDED_AI_NDO_LOADER_H + +#include <assimp/BaseImporter.h> +#include <assimp/vector3.h> +#include <cstdint> +#include <string> +#include <vector> + +struct aiImporterDesc; +struct aiScene; + +namespace Assimp { +class IOSystem; +class Importer; + +// --------------------------------------------------------------------------- +/** @brief Importer class to load meshes from Nendo. + * + * Basing on + * <blender>/blender/release/scripts/nendo_import.py by Anthony D'Agostino. +*/ +class NDOImporter : public BaseImporter { +public: + NDOImporter(); + ~NDOImporter() override; + + //! Represents a single edge + struct Edge { + unsigned int edge[8]; + unsigned int hard; + uint8_t color[8]; + }; + + //! Represents a single face + struct Face { + unsigned int elem; + }; + + struct Vertex { + unsigned int num; + aiVector3D val; + }; + + //! Represents a single object + struct Object { + std::string name; + + std::vector<Edge> edges; + std::vector<Face> faces; + std::vector<Vertex> vertices; + }; + + // ------------------------------------------------------------------- + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + void SetupProperties(const Importer *pImp) override; + + // ------------------------------------------------------------------- + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + +}; // end of class NDOImporter + +} // end of namespace Assimp + +#endif // INCLUDED_AI_NDO_LOADER_H diff --git a/libs/assimp/code/AssetLib/NFF/NFFLoader.cpp b/libs/assimp/code/AssetLib/NFF/NFFLoader.cpp new file mode 100644 index 0000000..48271d1 --- /dev/null +++ b/libs/assimp/code/AssetLib/NFF/NFFLoader.cpp @@ -0,0 +1,1168 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the STL importer class */ + +#ifndef ASSIMP_BUILD_NO_NFF_IMPORTER + +// internal headers +#include "NFFLoader.h" +#include <assimp/ParsingUtils.h> +#include <assimp/RemoveComments.h> +#include <assimp/StandardShapes.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/qnan.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <memory> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Neutral File Format Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "enff nff" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +NFFImporter::NFFImporter() {} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +NFFImporter::~NFFImporter() {} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool NFFImporter::CanRead(const std::string & pFile, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const { + return SimpleExtensionCheck(pFile, "nff", "enff"); +} + +// ------------------------------------------------------------------------------------------------ +// Get the list of all supported file extensions +const aiImporterDesc *NFFImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +#define AI_NFF_PARSE_FLOAT(f) \ + SkipSpaces(&sz); \ + if (!::IsLineEnd(*sz)) sz = fast_atoreal_move<ai_real>(sz, (ai_real &)f); + +// ------------------------------------------------------------------------------------------------ +#define AI_NFF_PARSE_TRIPLE(v) \ + AI_NFF_PARSE_FLOAT(v[0]) \ + AI_NFF_PARSE_FLOAT(v[1]) \ + AI_NFF_PARSE_FLOAT(v[2]) + +// ------------------------------------------------------------------------------------------------ +#define AI_NFF_PARSE_SHAPE_INFORMATION() \ + aiVector3D center, radius(1.0f, get_qnan(), get_qnan()); \ + AI_NFF_PARSE_TRIPLE(center); \ + AI_NFF_PARSE_TRIPLE(radius); \ + if (is_qnan(radius.z)) radius.z = radius.x; \ + if (is_qnan(radius.y)) radius.y = radius.x; \ + curMesh.radius = radius; \ + curMesh.center = center; + +// ------------------------------------------------------------------------------------------------ +#define AI_NFF2_GET_NEXT_TOKEN() \ + do { \ + if (!GetNextLine(buffer, line)) { \ + ASSIMP_LOG_WARN("NFF2: Unexpected EOF, can't read next token"); \ + break; \ + } \ + SkipSpaces(line, &sz); \ + } while (IsLineEnd(*sz)) + +// ------------------------------------------------------------------------------------------------ +// Loads the material table for the NFF2 file format from an external file +void NFFImporter::LoadNFF2MaterialTable(std::vector<ShadingInfo> &output, + const std::string &path, IOSystem *pIOHandler) { + std::unique_ptr<IOStream> file(pIOHandler->Open(path, "rb")); + + // Check whether we can read from the file + if (!file.get()) { + ASSIMP_LOG_ERROR("NFF2: Unable to open material library ", path, "."); + return; + } + + // get the size of the file + const unsigned int m = (unsigned int)file->FileSize(); + + // allocate storage and copy the contents of the file to a memory buffer + // (terminate it with zero) + std::vector<char> mBuffer2(m + 1); + TextFileToBuffer(file.get(), mBuffer2); + const char *buffer = &mBuffer2[0]; + + // First of all: remove all comments from the file + CommentRemover::RemoveLineComments("//", &mBuffer2[0]); + + // The file should start with the magic sequence "mat" + if (!TokenMatch(buffer, "mat", 3)) { + ASSIMP_LOG_ERROR("NFF2: Not a valid material library ", path, "."); + return; + } + + ShadingInfo *curShader = nullptr; + + // No read the file line per line + char line[4096]; + const char *sz; + while (GetNextLine(buffer, line)) { + SkipSpaces(line, &sz); + + // 'version' defines the version of the file format + if (TokenMatch(sz, "version", 7)) { + ASSIMP_LOG_INFO("NFF (Sense8) material library file format: ", std::string(sz)); + } + // 'matdef' starts a new material in the file + else if (TokenMatch(sz, "matdef", 6)) { + // add a new material to the list + output.push_back(ShadingInfo()); + curShader = &output.back(); + + // parse the name of the material + } else if (!TokenMatch(sz, "valid", 5)) { + // check whether we have an active material at the moment + if (!IsLineEnd(*sz)) { + if (!curShader) { + ASSIMP_LOG_ERROR("NFF2 material library: Found element ", sz, "but there is no active material"); + continue; + } + } else + continue; + + // now read the material property and determine its type + aiColor3D c; + if (TokenMatch(sz, "ambient", 7)) { + AI_NFF_PARSE_TRIPLE(c); + curShader->ambient = c; + } else if (TokenMatch(sz, "diffuse", 7) || TokenMatch(sz, "ambientdiffuse", 14) /* correct? */) { + AI_NFF_PARSE_TRIPLE(c); + curShader->diffuse = curShader->ambient = c; + } else if (TokenMatch(sz, "specular", 8)) { + AI_NFF_PARSE_TRIPLE(c); + curShader->specular = c; + } else if (TokenMatch(sz, "emission", 8)) { + AI_NFF_PARSE_TRIPLE(c); + curShader->emissive = c; + } else if (TokenMatch(sz, "shininess", 9)) { + AI_NFF_PARSE_FLOAT(curShader->shininess); + } else if (TokenMatch(sz, "opacity", 7)) { + AI_NFF_PARSE_FLOAT(curShader->opacity); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void NFFImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb")); + + // Check whether we can read from the file + if (!file.get()) + throw DeadlyImportError("Failed to open NFF file ", pFile, "."); + + // allocate storage and copy the contents of the file to a memory buffer + // (terminate it with zero) + std::vector<char> mBuffer2; + TextFileToBuffer(file.get(), mBuffer2); + const char *buffer = &mBuffer2[0]; + + // mesh arrays - separate here to make the handling of the pointers below easier. + std::vector<MeshInfo> meshes; + std::vector<MeshInfo> meshesWithNormals; + std::vector<MeshInfo> meshesWithUVCoords; + std::vector<MeshInfo> meshesLocked; + + char line[4096]; + const char *sz; + + // camera parameters + aiVector3D camPos, camUp(0.f, 1.f, 0.f), camLookAt(0.f, 0.f, 1.f); + ai_real angle = 45.f; + aiVector2D resolution; + + bool hasCam = false; + + MeshInfo *currentMeshWithNormals = nullptr; + MeshInfo *currentMesh = nullptr; + MeshInfo *currentMeshWithUVCoords = nullptr; + + ShadingInfo s; // current material info + + // degree of tessellation + unsigned int iTesselation = 4; + + // some temporary variables we need to parse the file + unsigned int sphere = 0, + cylinder = 0, + cone = 0, + numNamed = 0, + dodecahedron = 0, + octahedron = 0, + tetrahedron = 0, + hexahedron = 0; + + // lights imported from the file + std::vector<Light> lights; + + // check whether this is the NFF2 file format + if (TokenMatch(buffer, "nff", 3)) { + const ai_real qnan = get_qnan(); + const aiColor4D cQNAN = aiColor4D(qnan, 0.f, 0.f, 1.f); + const aiVector3D vQNAN = aiVector3D(qnan, 0.f, 0.f); + + // another NFF file format ... just a raw parser has been implemented + // no support for further details, I don't think it is worth the effort + // http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/nff/nff2.html + // http://www.netghost.narod.ru/gff/graphics/summary/sense8.htm + + // First of all: remove all comments from the file + CommentRemover::RemoveLineComments("//", &mBuffer2[0]); + + while (GetNextLine(buffer, line)) { + SkipSpaces(line, &sz); + if (TokenMatch(sz, "version", 7)) { + ASSIMP_LOG_INFO("NFF (Sense8) file format: ", sz); + } else if (TokenMatch(sz, "viewpos", 7)) { + AI_NFF_PARSE_TRIPLE(camPos); + hasCam = true; + } else if (TokenMatch(sz, "viewdir", 7)) { + AI_NFF_PARSE_TRIPLE(camLookAt); + hasCam = true; + } + // This starts a new object section + else if (!IsSpaceOrNewLine(*sz)) { + unsigned int subMeshIdx = 0; + + // read the name of the object, skip all spaces + // at the end of it. + const char *sz3 = sz; + while (!IsSpaceOrNewLine(*sz)) + ++sz; + std::string objectName = std::string(sz3, (unsigned int)(sz - sz3)); + + const unsigned int objStart = (unsigned int)meshes.size(); + + // There could be a material table in a separate file + std::vector<ShadingInfo> materialTable; + while (true) { + AI_NFF2_GET_NEXT_TOKEN(); + + // material table - an external file + if (TokenMatch(sz, "mtable", 6)) { + SkipSpaces(&sz); + sz3 = sz; + while (!IsSpaceOrNewLine(*sz)) + ++sz; + const unsigned int diff = (unsigned int)(sz - sz3); + if (!diff) + ASSIMP_LOG_WARN("NFF2: Found empty mtable token"); + else { + // The material table has the file extension .mat. + // If it is not there, we need to append it + std::string path = std::string(sz3, diff); + if (std::string::npos == path.find_last_of(".mat")) { + path.append(".mat"); + } + + // Now extract the working directory from the path to + // this file and append the material library filename + // to it. + std::string::size_type sepPos; + if ((std::string::npos == (sepPos = path.find_last_of('\\')) || !sepPos) && + (std::string::npos == (sepPos = path.find_last_of('/')) || !sepPos)) { + sepPos = pFile.find_last_of('\\'); + if (std::string::npos == sepPos) { + sepPos = pFile.find_last_of('/'); + } + if (std::string::npos != sepPos) { + path = pFile.substr(0, sepPos + 1) + path; + } + } + LoadNFF2MaterialTable(materialTable, path, pIOHandler); + } + } else + break; + } + + // read the numbr of vertices + unsigned int num = ::strtoul10(sz, &sz); + + // temporary storage + std::vector<aiColor4D> tempColors; + std::vector<aiVector3D> tempPositions, tempTextureCoords, tempNormals; + + bool hasNormals = false, hasUVs = false, hasColor = false; + + tempPositions.reserve(num); + tempColors.reserve(num); + tempNormals.reserve(num); + tempTextureCoords.reserve(num); + for (unsigned int i = 0; i < num; ++i) { + AI_NFF2_GET_NEXT_TOKEN(); + aiVector3D v; + AI_NFF_PARSE_TRIPLE(v); + tempPositions.push_back(v); + + // parse all other attributes in the line + while (true) { + SkipSpaces(&sz); + if (IsLineEnd(*sz)) break; + + // color definition + if (TokenMatch(sz, "0x", 2)) { + hasColor = true; + unsigned int numIdx = ::strtoul16(sz, &sz); + aiColor4D clr; + clr.a = 1.f; + + // 0xRRGGBB + clr.r = ((numIdx >> 16u) & 0xff) / 255.f; + clr.g = ((numIdx >> 8u) & 0xff) / 255.f; + clr.b = ((numIdx)&0xff) / 255.f; + tempColors.push_back(clr); + } + // normal vector + else if (TokenMatch(sz, "norm", 4)) { + hasNormals = true; + AI_NFF_PARSE_TRIPLE(v); + tempNormals.push_back(v); + } + // UV coordinate + else if (TokenMatch(sz, "uv", 2)) { + hasUVs = true; + AI_NFF_PARSE_FLOAT(v.x); + AI_NFF_PARSE_FLOAT(v.y); + v.z = 0.f; + tempTextureCoords.push_back(v); + } + } + + // fill in dummies for all attributes that have not been set + if (tempNormals.size() != tempPositions.size()) + tempNormals.push_back(vQNAN); + + if (tempTextureCoords.size() != tempPositions.size()) + tempTextureCoords.push_back(vQNAN); + + if (tempColors.size() != tempPositions.size()) + tempColors.push_back(cQNAN); + } + + AI_NFF2_GET_NEXT_TOKEN(); + if (!num) throw DeadlyImportError("NFF2: There are zero vertices"); + num = ::strtoul10(sz, &sz); + + std::vector<unsigned int> tempIdx; + tempIdx.reserve(10); + for (unsigned int i = 0; i < num; ++i) { + AI_NFF2_GET_NEXT_TOKEN(); + SkipSpaces(line, &sz); + unsigned int numIdx = ::strtoul10(sz, &sz); + + // read all faces indices + if (numIdx) { + // mesh.faces.push_back(numIdx); + // tempIdx.erase(tempIdx.begin(),tempIdx.end()); + tempIdx.resize(numIdx); + + for (unsigned int a = 0; a < numIdx; ++a) { + SkipSpaces(sz, &sz); + unsigned int m = ::strtoul10(sz, &sz); + if (m >= (unsigned int)tempPositions.size()) { + ASSIMP_LOG_ERROR("NFF2: Vertex index overflow"); + m = 0; + } + // mesh.vertices.push_back (tempPositions[idx]); + tempIdx[a] = m; + } + } + + // build a temporary shader object for the face. + ShadingInfo shader; + unsigned int matIdx = 0; + + // white material color - we have vertex colors + shader.color = aiColor3D(1.f, 1.f, 1.f); + aiColor4D c = aiColor4D(1.f, 1.f, 1.f, 1.f); + while (true) { + SkipSpaces(sz, &sz); + if (IsLineEnd(*sz)) break; + + // per-polygon colors + if (TokenMatch(sz, "0x", 2)) { + hasColor = true; + const char *sz2 = sz; + numIdx = ::strtoul16(sz, &sz); + const unsigned int diff = (unsigned int)(sz - sz2); + + // 0xRRGGBB + if (diff > 3) { + c.r = ((numIdx >> 16u) & 0xff) / 255.f; + c.g = ((numIdx >> 8u) & 0xff) / 255.f; + c.b = ((numIdx)&0xff) / 255.f; + } + // 0xRGB + else { + c.r = ((numIdx >> 8u) & 0xf) / 16.f; + c.g = ((numIdx >> 4u) & 0xf) / 16.f; + c.b = ((numIdx)&0xf) / 16.f; + } + } + // TODO - implement texture mapping here +#if 0 + // mirror vertex texture coordinate? + else if (TokenMatch(sz,"mirror",6)) + { + } + // texture coordinate scaling + else if (TokenMatch(sz,"scale",5)) + { + } + // texture coordinate translation + else if (TokenMatch(sz,"trans",5)) + { + } + // texture coordinate rotation angle + else if (TokenMatch(sz,"rot",3)) + { + } +#endif + + // texture file name for this polygon + mapping information + else if ('_' == sz[0]) { + // get mapping information + switch (sz[1]) { + case 'v': + case 'V': + + shader.shaded = false; + break; + + case 't': + case 'T': + case 'u': + case 'U': + + ASSIMP_LOG_WARN("Unsupported NFF2 texture attribute: trans"); + }; + if (!sz[1] || '_' != sz[2]) { + ASSIMP_LOG_WARN("NFF2: Expected underscore after texture attributes"); + continue; + } + const char *sz2 = sz + 3; + while (!IsSpaceOrNewLine(*sz)) + ++sz; + const unsigned int diff = (unsigned int)(sz - sz2); + if (diff) shader.texFile = std::string(sz2, diff); + } + + // Two-sided material? + else if (TokenMatch(sz, "both", 4)) { + shader.twoSided = true; + } + + // Material ID? + else if (!materialTable.empty() && TokenMatch(sz, "matid", 5)) { + SkipSpaces(&sz); + matIdx = ::strtoul10(sz, &sz); + if (matIdx >= materialTable.size()) { + ASSIMP_LOG_ERROR("NFF2: Material index overflow."); + matIdx = 0; + } + + // now combine our current shader with the shader we + // read from the material table. + ShadingInfo &mat = materialTable[matIdx]; + shader.ambient = mat.ambient; + shader.diffuse = mat.diffuse; + shader.emissive = mat.emissive; + shader.opacity = mat.opacity; + shader.specular = mat.specular; + shader.shininess = mat.shininess; + } else + SkipToken(sz); + } + + // search the list of all shaders we have for this object whether + // there is an identical one. In this case, we append our mesh + // data to it. + MeshInfo *mesh = nullptr; + for (std::vector<MeshInfo>::iterator it = meshes.begin() + objStart, end = meshes.end(); + it != end; ++it) { + if ((*it).shader == shader && (*it).matIndex == matIdx) { + // we have one, we can append our data to it + mesh = &(*it); + } + } + if (!mesh) { + meshes.push_back(MeshInfo(PatchType_Simple, false)); + mesh = &meshes.back(); + mesh->matIndex = matIdx; + + // We need to add a new mesh to the list. We assign + // an unique name to it to make sure the scene will + // pass the validation step for the moment. + // TODO: fix naming of objects in the scenegraph later + if (objectName.length()) { + ::strcpy(mesh->name, objectName.c_str()); + ASSIMP_itoa10(&mesh->name[objectName.length()], 30, subMeshIdx++); + } + + // copy the shader to the mesh. + mesh->shader = shader; + } + + // fill the mesh with data + if (!tempIdx.empty()) { + mesh->faces.push_back((unsigned int)tempIdx.size()); + for (std::vector<unsigned int>::const_iterator it = tempIdx.begin(), end = tempIdx.end(); + it != end; ++it) { + unsigned int m = *it; + + // copy colors -vertex color specifications override polygon color specifications + if (hasColor) { + const aiColor4D &clr = tempColors[m]; + mesh->colors.push_back((is_qnan(clr.r) ? c : clr)); + } + + // positions should always be there + mesh->vertices.push_back(tempPositions[m]); + + // copy normal vectors + if (hasNormals) + mesh->normals.push_back(tempNormals[m]); + + // copy texture coordinates + if (hasUVs) + mesh->uvs.push_back(tempTextureCoords[m]); + } + } + } + if (!num) throw DeadlyImportError("NFF2: There are zero faces"); + } + } + camLookAt = camLookAt + camPos; + } else // "Normal" Neutral file format that is quite more common + { + while (GetNextLine(buffer, line)) { + sz = line; + if ('p' == line[0] || TokenMatch(sz, "tpp", 3)) { + MeshInfo *out = nullptr; + + // 'tpp' - texture polygon patch primitive + if ('t' == line[0]) { + currentMeshWithUVCoords = nullptr; + for (auto &mesh : meshesWithUVCoords) { + if (mesh.shader == s) { + currentMeshWithUVCoords = &mesh; + break; + } + } + + if (!currentMeshWithUVCoords) { + meshesWithUVCoords.push_back(MeshInfo(PatchType_UVAndNormals)); + currentMeshWithUVCoords = &meshesWithUVCoords.back(); + currentMeshWithUVCoords->shader = s; + } + out = currentMeshWithUVCoords; + } + // 'pp' - polygon patch primitive + else if ('p' == line[1]) { + currentMeshWithNormals = nullptr; + for (auto &mesh : meshesWithNormals) { + if (mesh.shader == s) { + currentMeshWithNormals = &mesh; + break; + } + } + + if (!currentMeshWithNormals) { + meshesWithNormals.push_back(MeshInfo(PatchType_Normals)); + currentMeshWithNormals = &meshesWithNormals.back(); + currentMeshWithNormals->shader = s; + } + sz = &line[2]; + out = currentMeshWithNormals; + } + // 'p' - polygon primitive + else { + currentMesh = nullptr; + for (auto &mesh : meshes) { + if (mesh.shader == s) { + currentMesh = &mesh; + break; + } + } + + if (!currentMesh) { + meshes.push_back(MeshInfo(PatchType_Simple)); + currentMesh = &meshes.back(); + currentMesh->shader = s; + } + sz = &line[1]; + out = currentMesh; + } + SkipSpaces(sz, &sz); + unsigned int m = strtoul10(sz); + + // ---- flip the face order + out->vertices.resize(out->vertices.size() + m); + if (out != currentMesh) { + out->normals.resize(out->vertices.size()); + } + if (out == currentMeshWithUVCoords) { + out->uvs.resize(out->vertices.size()); + } + for (unsigned int n = 0; n < m; ++n) { + if (!GetNextLine(buffer, line)) { + ASSIMP_LOG_ERROR("NFF: Unexpected EOF was encountered. Patch definition incomplete"); + continue; + } + + aiVector3D v; + sz = &line[0]; + AI_NFF_PARSE_TRIPLE(v); + out->vertices[out->vertices.size() - n - 1] = v; + + if (out != currentMesh) { + AI_NFF_PARSE_TRIPLE(v); + out->normals[out->vertices.size() - n - 1] = v; + } + if (out == currentMeshWithUVCoords) { + // FIX: in one test file this wraps over multiple lines + SkipSpaces(&sz); + if (IsLineEnd(*sz)) { + GetNextLine(buffer, line); + sz = line; + } + AI_NFF_PARSE_FLOAT(v.x); + SkipSpaces(&sz); + if (IsLineEnd(*sz)) { + GetNextLine(buffer, line); + sz = line; + } + AI_NFF_PARSE_FLOAT(v.y); + v.y = 1.f - v.y; + out->uvs[out->vertices.size() - n - 1] = v; + } + } + out->faces.push_back(m); + } + // 'f' - shading information block + else if (TokenMatch(sz, "f", 1)) { + ai_real d; + + // read the RGB colors + AI_NFF_PARSE_TRIPLE(s.color); + + // read the other properties + AI_NFF_PARSE_FLOAT(s.diffuse.r); + AI_NFF_PARSE_FLOAT(s.specular.r); + AI_NFF_PARSE_FLOAT(d); // skip shininess and transmittance + AI_NFF_PARSE_FLOAT(d); + AI_NFF_PARSE_FLOAT(s.refracti); + + // NFF2 uses full colors here so we need to use them too + // although NFF uses simple scaling factors + s.diffuse.g = s.diffuse.b = s.diffuse.r; + s.specular.g = s.specular.b = s.specular.r; + + // if the next one is NOT a number we assume it is a texture file name + // this feature is used by some NFF files on the internet and it has + // been implemented as it can be really useful + SkipSpaces(&sz); + if (!IsNumeric(*sz)) { + // TODO: Support full file names with spaces and quotation marks ... + const char *p = sz; + while (!IsSpaceOrNewLine(*sz)) + ++sz; + + unsigned int diff = (unsigned int)(sz - p); + if (diff) { + s.texFile = std::string(p, diff); + } + } else { + AI_NFF_PARSE_FLOAT(s.ambient); // optional + } + } + // 'shader' - other way to specify a texture + else if (TokenMatch(sz, "shader", 6)) { + SkipSpaces(&sz); + const char *old = sz; + while (!IsSpaceOrNewLine(*sz)) + ++sz; + s.texFile = std::string(old, (uintptr_t)sz - (uintptr_t)old); + } + // 'l' - light source + else if (TokenMatch(sz, "l", 1)) { + lights.push_back(Light()); + Light &light = lights.back(); + + AI_NFF_PARSE_TRIPLE(light.position); + AI_NFF_PARSE_FLOAT(light.intensity); + AI_NFF_PARSE_TRIPLE(light.color); + } + // 's' - sphere + else if (TokenMatch(sz, "s", 1)) { + meshesLocked.push_back(MeshInfo(PatchType_Simple, true)); + MeshInfo &curMesh = meshesLocked.back(); + curMesh.shader = s; + curMesh.shader.mapping = aiTextureMapping_SPHERE; + + AI_NFF_PARSE_SHAPE_INFORMATION(); + + // we don't need scaling or translation here - we do it in the node's transform + StandardShapes::MakeSphere(iTesselation, curMesh.vertices); + curMesh.faces.resize(curMesh.vertices.size() / 3, 3); + + // generate a name for the mesh + ::ai_snprintf(curMesh.name, MeshInfo::MaxNameLen, "sphere_%i", sphere++); + } + // 'dod' - dodecahedron + else if (TokenMatch(sz, "dod", 3)) { + meshesLocked.push_back(MeshInfo(PatchType_Simple, true)); + MeshInfo &curMesh = meshesLocked.back(); + curMesh.shader = s; + curMesh.shader.mapping = aiTextureMapping_SPHERE; + + AI_NFF_PARSE_SHAPE_INFORMATION(); + + // we don't need scaling or translation here - we do it in the node's transform + StandardShapes::MakeDodecahedron(curMesh.vertices); + curMesh.faces.resize(curMesh.vertices.size() / 3, 3); + + // generate a name for the mesh + ::ai_snprintf(curMesh.name, 128, "dodecahedron_%i", dodecahedron++); + } + + // 'oct' - octahedron + else if (TokenMatch(sz, "oct", 3)) { + meshesLocked.push_back(MeshInfo(PatchType_Simple, true)); + MeshInfo &curMesh = meshesLocked.back(); + curMesh.shader = s; + curMesh.shader.mapping = aiTextureMapping_SPHERE; + + AI_NFF_PARSE_SHAPE_INFORMATION(); + + // we don't need scaling or translation here - we do it in the node's transform + StandardShapes::MakeOctahedron(curMesh.vertices); + curMesh.faces.resize(curMesh.vertices.size() / 3, 3); + + // generate a name for the mesh + ::ai_snprintf(curMesh.name, MeshInfo::MaxNameLen, "octahedron_%i", octahedron++); + } + + // 'tet' - tetrahedron + else if (TokenMatch(sz, "tet", 3)) { + meshesLocked.push_back(MeshInfo(PatchType_Simple, true)); + MeshInfo &curMesh = meshesLocked.back(); + curMesh.shader = s; + curMesh.shader.mapping = aiTextureMapping_SPHERE; + + AI_NFF_PARSE_SHAPE_INFORMATION(); + + // we don't need scaling or translation here - we do it in the node's transform + StandardShapes::MakeTetrahedron(curMesh.vertices); + curMesh.faces.resize(curMesh.vertices.size() / 3, 3); + + // generate a name for the mesh + ::ai_snprintf(curMesh.name, MeshInfo::MaxNameLen, "tetrahedron_%i", tetrahedron++); + } + + // 'hex' - hexahedron + else if (TokenMatch(sz, "hex", 3)) { + meshesLocked.push_back(MeshInfo(PatchType_Simple, true)); + MeshInfo &curMesh = meshesLocked.back(); + curMesh.shader = s; + curMesh.shader.mapping = aiTextureMapping_BOX; + + AI_NFF_PARSE_SHAPE_INFORMATION(); + + // we don't need scaling or translation here - we do it in the node's transform + StandardShapes::MakeHexahedron(curMesh.vertices); + curMesh.faces.resize(curMesh.vertices.size() / 3, 3); + + // generate a name for the mesh + ::ai_snprintf(curMesh.name, MeshInfo::MaxNameLen, "hexahedron_%i", hexahedron++); + } + // 'c' - cone + else if (TokenMatch(sz, "c", 1)) { + meshesLocked.push_back(MeshInfo(PatchType_Simple, true)); + MeshInfo &curMesh = meshesLocked.back(); + curMesh.shader = s; + curMesh.shader.mapping = aiTextureMapping_CYLINDER; + + if (!GetNextLine(buffer, line)) { + ASSIMP_LOG_ERROR("NFF: Unexpected end of file (cone definition not complete)"); + break; + } + sz = line; + + // read the two center points and the respective radii + aiVector3D center1, center2; + ai_real radius1 = 0.f, radius2 = 0.f; + AI_NFF_PARSE_TRIPLE(center1); + AI_NFF_PARSE_FLOAT(radius1); + + if (!GetNextLine(buffer, line)) { + ASSIMP_LOG_ERROR("NFF: Unexpected end of file (cone definition not complete)"); + break; + } + sz = line; + + AI_NFF_PARSE_TRIPLE(center2); + AI_NFF_PARSE_FLOAT(radius2); + + // compute the center point of the cone/cylinder - + // it is its local transformation origin + curMesh.dir = center2 - center1; + curMesh.center = center1 + curMesh.dir / (ai_real)2.0; + + ai_real f; + if ((f = curMesh.dir.Length()) < 10e-3f) { + ASSIMP_LOG_ERROR("NFF: Cone height is close to zero"); + continue; + } + curMesh.dir /= f; // normalize + + // generate the cone - it consists of simple triangles + StandardShapes::MakeCone(f, radius1, radius2, + integer_pow(4, iTesselation), curMesh.vertices); + + // MakeCone() returns tris + curMesh.faces.resize(curMesh.vertices.size() / 3, 3); + + // generate a name for the mesh. 'cone' if it a cone, + // 'cylinder' if it is a cylinder. Funny, isn't it? + if (radius1 != radius2) { + ::ai_snprintf(curMesh.name, MeshInfo::MaxNameLen, "cone_%i", cone++); + } else { + ::ai_snprintf(curMesh.name, MeshInfo::MaxNameLen, "cylinder_%i", cylinder++); + } + } + // 'tess' - tessellation + else if (TokenMatch(sz, "tess", 4)) { + SkipSpaces(&sz); + iTesselation = strtoul10(sz); + } + // 'from' - camera position + else if (TokenMatch(sz, "from", 4)) { + AI_NFF_PARSE_TRIPLE(camPos); + hasCam = true; + } + // 'at' - camera look-at vector + else if (TokenMatch(sz, "at", 2)) { + AI_NFF_PARSE_TRIPLE(camLookAt); + hasCam = true; + } + // 'up' - camera up vector + else if (TokenMatch(sz, "up", 2)) { + AI_NFF_PARSE_TRIPLE(camUp); + hasCam = true; + } + // 'angle' - (half?) camera field of view + else if (TokenMatch(sz, "angle", 5)) { + AI_NFF_PARSE_FLOAT(angle); + hasCam = true; + } + // 'resolution' - used to compute the screen aspect + else if (TokenMatch(sz, "resolution", 10)) { + AI_NFF_PARSE_FLOAT(resolution.x); + AI_NFF_PARSE_FLOAT(resolution.y); + hasCam = true; + } + // 'pb' - bezier patch. Not supported yet + else if (TokenMatch(sz, "pb", 2)) { + ASSIMP_LOG_ERROR("NFF: Encountered unsupported ID: bezier patch"); + } + // 'pn' - NURBS. Not supported yet + else if (TokenMatch(sz, "pn", 2) || TokenMatch(sz, "pnn", 3)) { + ASSIMP_LOG_ERROR("NFF: Encountered unsupported ID: NURBS"); + } + // '' - comment + else if ('#' == line[0]) { + const char *space; + SkipSpaces(&line[1], &space); + if (!IsLineEnd(*space)) { + ASSIMP_LOG_INFO(space); + } + } + } + } + + // copy all arrays into one large + meshes.reserve(meshes.size() + meshesLocked.size() + meshesWithNormals.size() + meshesWithUVCoords.size()); + meshes.insert(meshes.end(), meshesLocked.begin(), meshesLocked.end()); + meshes.insert(meshes.end(), meshesWithNormals.begin(), meshesWithNormals.end()); + meshes.insert(meshes.end(), meshesWithUVCoords.begin(), meshesWithUVCoords.end()); + + // now generate output meshes. first find out how many meshes we'll need + std::vector<MeshInfo>::const_iterator it = meshes.begin(), end = meshes.end(); + for (; it != end; ++it) { + if (!(*it).faces.empty()) { + ++pScene->mNumMeshes; + if ((*it).name[0]) ++numNamed; + } + } + + // generate a dummy root node - assign all unnamed elements such + // as polygons and polygon patches to the root node and generate + // sub nodes for named objects such as spheres and cones. + aiNode *const root = new aiNode(); + root->mName.Set("<NFF_Root>"); + root->mNumChildren = numNamed + (hasCam ? 1 : 0) + (unsigned int)lights.size(); + root->mNumMeshes = pScene->mNumMeshes - numNamed; + + aiNode **ppcChildren = nullptr; + unsigned int *pMeshes = nullptr; + if (root->mNumMeshes) + pMeshes = root->mMeshes = new unsigned int[root->mNumMeshes]; + if (root->mNumChildren) + ppcChildren = root->mChildren = new aiNode *[root->mNumChildren]; + + // generate the camera + if (hasCam) { + ai_assert(ppcChildren); + aiNode *nd = new aiNode(); + *ppcChildren = nd; + nd->mName.Set("<NFF_Camera>"); + nd->mParent = root; + + // allocate the camera in the scene + pScene->mNumCameras = 1; + pScene->mCameras = new aiCamera *[1]; + aiCamera *c = pScene->mCameras[0] = new aiCamera; + + c->mName = nd->mName; // make sure the names are identical + c->mHorizontalFOV = AI_DEG_TO_RAD(angle); + c->mLookAt = camLookAt - camPos; + c->mPosition = camPos; + c->mUp = camUp; + + // If the resolution is not specified in the file, we + // need to set 1.0 as aspect. + c->mAspect = (!resolution.y ? 0.f : resolution.x / resolution.y); + ++ppcChildren; + } + + // generate light sources + if (!lights.empty()) { + ai_assert(ppcChildren); + pScene->mNumLights = (unsigned int)lights.size(); + pScene->mLights = new aiLight *[pScene->mNumLights]; + for (unsigned int i = 0; i < pScene->mNumLights; ++i, ++ppcChildren) { + const Light &l = lights[i]; + + aiNode *nd = new aiNode(); + *ppcChildren = nd; + nd->mParent = root; + + nd->mName.length = ::ai_snprintf(nd->mName.data, 1024, "<NFF_Light%u>", i); + + // allocate the light in the scene data structure + aiLight *out = pScene->mLights[i] = new aiLight(); + out->mName = nd->mName; // make sure the names are identical + out->mType = aiLightSource_POINT; + out->mColorDiffuse = out->mColorSpecular = l.color * l.intensity; + out->mPosition = l.position; + } + } + + if (!pScene->mNumMeshes) throw DeadlyImportError("NFF: No meshes loaded"); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials = pScene->mNumMeshes]; + unsigned int m = 0; + for (it = meshes.begin(); it != end; ++it) { + if ((*it).faces.empty()) continue; + + const MeshInfo &src = *it; + aiMesh *const mesh = pScene->mMeshes[m] = new aiMesh(); + mesh->mNumVertices = (unsigned int)src.vertices.size(); + mesh->mNumFaces = (unsigned int)src.faces.size(); + + // Generate sub nodes for named meshes + if (src.name[0] && nullptr != ppcChildren) { + aiNode *const node = *ppcChildren = new aiNode(); + node->mParent = root; + node->mNumMeshes = 1; + node->mMeshes = new unsigned int[1]; + node->mMeshes[0] = m; + node->mName.Set(src.name); + + // setup the transformation matrix of the node + aiMatrix4x4::FromToMatrix(aiVector3D(0.f, 1.f, 0.f), + src.dir, node->mTransformation); + + aiMatrix4x4 &mat = node->mTransformation; + mat.a1 *= src.radius.x; + mat.b1 *= src.radius.x; + mat.c1 *= src.radius.x; + mat.a2 *= src.radius.y; + mat.b2 *= src.radius.y; + mat.c2 *= src.radius.y; + mat.a3 *= src.radius.z; + mat.b3 *= src.radius.z; + mat.c3 *= src.radius.z; + mat.a4 = src.center.x; + mat.b4 = src.center.y; + mat.c4 = src.center.z; + + ++ppcChildren; + } else { + *pMeshes++ = m; + } + + // copy vertex positions + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + ::memcpy(mesh->mVertices, &src.vertices[0], + sizeof(aiVector3D) * mesh->mNumVertices); + + // NFF2: there could be vertex colors + if (!src.colors.empty()) { + ai_assert(src.colors.size() == src.vertices.size()); + + // copy vertex colors + mesh->mColors[0] = new aiColor4D[mesh->mNumVertices]; + ::memcpy(mesh->mColors[0], &src.colors[0], + sizeof(aiColor4D) * mesh->mNumVertices); + } + + if (!src.normals.empty()) { + ai_assert(src.normals.size() == src.vertices.size()); + + // copy normal vectors + mesh->mNormals = new aiVector3D[mesh->mNumVertices]; + ::memcpy(mesh->mNormals, &src.normals[0], + sizeof(aiVector3D) * mesh->mNumVertices); + } + + if (!src.uvs.empty()) { + ai_assert(src.uvs.size() == src.vertices.size()); + + // copy texture coordinates + mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; + ::memcpy(mesh->mTextureCoords[0], &src.uvs[0], + sizeof(aiVector3D) * mesh->mNumVertices); + } + + // generate faces + unsigned int p = 0; + aiFace *pFace = mesh->mFaces = new aiFace[mesh->mNumFaces]; + for (std::vector<unsigned int>::const_iterator it2 = src.faces.begin(), + end2 = src.faces.end(); + it2 != end2; ++it2, ++pFace) { + pFace->mIndices = new unsigned int[pFace->mNumIndices = *it2]; + for (unsigned int o = 0; o < pFace->mNumIndices; ++o) + pFace->mIndices[o] = p++; + } + + // generate a material for the mesh + aiMaterial *pcMat = (aiMaterial *)(pScene->mMaterials[m] = new aiMaterial()); + + mesh->mMaterialIndex = m++; + + aiString matName; + matName.Set(AI_DEFAULT_MATERIAL_NAME); + pcMat->AddProperty(&matName, AI_MATKEY_NAME); + + // FIX: Ignore diffuse == 0 + aiColor3D c = src.shader.color * (src.shader.diffuse.r ? src.shader.diffuse : aiColor3D(1.f, 1.f, 1.f)); + pcMat->AddProperty(&c, 1, AI_MATKEY_COLOR_DIFFUSE); + c = src.shader.color * src.shader.specular; + pcMat->AddProperty(&c, 1, AI_MATKEY_COLOR_SPECULAR); + + // NFF2 - default values for NFF + pcMat->AddProperty(&src.shader.ambient, 1, AI_MATKEY_COLOR_AMBIENT); + pcMat->AddProperty(&src.shader.emissive, 1, AI_MATKEY_COLOR_EMISSIVE); + pcMat->AddProperty(&src.shader.opacity, 1, AI_MATKEY_OPACITY); + + // setup the first texture layer, if existing + if (src.shader.texFile.length()) { + matName.Set(src.shader.texFile); + pcMat->AddProperty(&matName, AI_MATKEY_TEXTURE_DIFFUSE(0)); + + if (aiTextureMapping_UV != src.shader.mapping) { + + aiVector3D v(0.f, -1.f, 0.f); + pcMat->AddProperty(&v, 1, AI_MATKEY_TEXMAP_AXIS_DIFFUSE(0)); + pcMat->AddProperty((int *)&src.shader.mapping, 1, AI_MATKEY_MAPPING_DIFFUSE(0)); + } + } + + // setup the name of the material + if (src.shader.name.length()) { + matName.Set(src.shader.texFile); + pcMat->AddProperty(&matName, AI_MATKEY_NAME); + } + + // setup some more material properties that are specific to NFF2 + int i; + if (src.shader.twoSided) { + i = 1; + pcMat->AddProperty(&i, 1, AI_MATKEY_TWOSIDED); + } + i = (src.shader.shaded ? aiShadingMode_Gouraud : aiShadingMode_NoShading); + if (src.shader.shininess) { + i = aiShadingMode_Phong; + pcMat->AddProperty(&src.shader.shininess, 1, AI_MATKEY_SHININESS); + } + pcMat->AddProperty(&i, 1, AI_MATKEY_SHADING_MODEL); + } + pScene->mRootNode = root; +} + +#endif // !! ASSIMP_BUILD_NO_NFF_IMPORTER diff --git a/libs/assimp/code/AssetLib/NFF/NFFLoader.h b/libs/assimp/code/AssetLib/NFF/NFFLoader.h new file mode 100644 index 0000000..0fa615b --- /dev/null +++ b/libs/assimp/code/AssetLib/NFF/NFFLoader.h @@ -0,0 +1,191 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 NFFLoader.h + * @brief Declaration of the NFF importer class. + */ +#pragma once +#ifndef AI_NFFLOADER_H_INCLUDED +#define AI_NFFLOADER_H_INCLUDED + +#include <assimp/BaseImporter.h> +#include <assimp/material.h> +#include <assimp/types.h> +#include <vector> + +namespace Assimp { + +// ---------------------------------------------------------------------------------- +/** NFF (Neutral File Format) Importer class. + * + * The class implements both Eric Haynes NFF format and Sense8's NFF (NFF2) format. + * Both are quite different and the loading code is somewhat dirty at + * the moment. Sense8 should be moved to a separate loader. +*/ +class NFFImporter : public BaseImporter { +public: + NFFImporter(); + ~NFFImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + +private: + // describes face material properties + struct ShadingInfo { + ShadingInfo() : + color(0.6f, 0.6f, 0.6f), + diffuse(1.f, 1.f, 1.f), + specular(1.f, 1.f, 1.f), + ambient(0.f, 0.f, 0.f), + emissive(0.f, 0.f, 0.f), + refracti(1.f), + twoSided(false), // for NFF2 + shaded(true), // for NFF2 + opacity(1.f), + shininess(0.f), + mapping(aiTextureMapping_UV) { + // empty + } + + aiColor3D color, diffuse, specular, ambient, emissive; + ai_real refracti; + std::string texFile; + bool twoSided; // For NFF2 + bool shaded; + ai_real opacity, shininess; + std::string name; + + // texture mapping to be generated for the mesh - uv is the default + // it means: use UV if there, nothing otherwise. This property is + // used for locked meshes. + aiTextureMapping mapping; + + // shininess is ignored for the moment + bool operator == (const ShadingInfo &other) const { + return color == other.color && + diffuse == other.diffuse && + specular == other.specular && + ambient == other.ambient && + refracti == other.refracti && + texFile == other.texFile && + twoSided == other.twoSided && + shaded == other.shaded; + + // Some properties from NFF2 aren't compared by this operator. + // Comparing MeshInfo::matIndex should do that. + } + }; + + // describes a NFF light source + struct Light { + Light() : + intensity(1.f), color(1.f, 1.f, 1.f) {} + + aiVector3D position; + ai_real intensity; + aiColor3D color; + }; + + enum PatchType { + PatchType_Simple = 0x0, + PatchType_Normals = 0x1, + PatchType_UVAndNormals = 0x2 + }; + + // describes a NFF mesh + struct MeshInfo { + MeshInfo(PatchType _pType, bool bL = false) : + pType(_pType), bLocked(bL), radius(1.f, 1.f, 1.f), dir(0.f, 1.f, 0.f), matIndex(0) { + name[0] = '\0'; // by default meshes are unnamed + } + + ShadingInfo shader; + PatchType pType; + bool bLocked; + + // for spheres, cones and cylinders: center point of the object + aiVector3D center, radius, dir; + static const size_t MaxNameLen = 128; + char name[MaxNameLen]; + + std::vector<aiVector3D> vertices, normals, uvs; + std::vector<unsigned int> faces; + + // for NFF2 + std::vector<aiColor4D> colors; + unsigned int matIndex; + }; + + // ------------------------------------------------------------------- + /** Loads the material table for the NFF2 file format from an + * external file. + * + * @param output Receives the list of output meshes + * @param path Path to the file (abs. or rel.) + * @param pIOHandler IOSystem to be used to open the file + */ + void LoadNFF2MaterialTable(std::vector<ShadingInfo> &output, + const std::string &path, IOSystem *pIOHandler); +}; + +} // end of namespace Assimp + +#endif // AI_NFFIMPORTER_H_IN diff --git a/libs/assimp/code/AssetLib/OFF/OFFLoader.cpp b/libs/assimp/code/AssetLib/OFF/OFFLoader.cpp new file mode 100644 index 0000000..640e792 --- /dev/null +++ b/libs/assimp/code/AssetLib/OFF/OFFLoader.cpp @@ -0,0 +1,334 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 OFFLoader.cpp + * @brief Implementation of the OFF importer class + */ + + +#ifndef ASSIMP_BUILD_NO_OFF_IMPORTER + +// internal headers +#include "OFFLoader.h" +#include <assimp/ParsingUtils.h> +#include <assimp/fast_atof.h> +#include <memory> +#include <assimp/IOSystem.hpp> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/importerdesc.h> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "OFF Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "off" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +OFFImporter::OFFImporter() +{} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +OFFImporter::~OFFImporter() +{} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool OFFImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const +{ + static const char* tokens[] = { "off" }; + return SearchFileHeaderForToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens),3); +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc* OFFImporter::GetInfo () const +{ + return &desc; +} + + +// skip blank space, lines and comments +static void NextToken(const char **car, const char* end) { + SkipSpacesAndLineEnd(car); + while (*car < end && (**car == '#' || **car == '\n' || **car == '\r')) { + SkipLine(car); + SkipSpacesAndLineEnd(car); + } +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void OFFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) { + std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb")); + + // Check whether we can read from the file + if( file.get() == nullptr) { + throw DeadlyImportError( "Failed to open OFF file ", pFile, "."); + } + + // allocate storage and copy the contents of the file to a memory buffer + std::vector<char> mBuffer2; + TextFileToBuffer(file.get(),mBuffer2); + const char* buffer = &mBuffer2[0]; + + // Proper OFF header parser. We only implement normal loading for now. + bool hasTexCoord = false, hasNormals = false, hasColors = false; + bool hasHomogenous = false, hasDimension = false; + unsigned int dimensions = 3; + const char* car = buffer; + const char* end = buffer + mBuffer2.size(); + NextToken(&car, end); + + if (car < end - 2 && car[0] == 'S' && car[1] == 'T') { + hasTexCoord = true; car += 2; + } + if (car < end - 1 && car[0] == 'C') { + hasColors = true; car++; + } + if (car < end- 1 && car[0] == 'N') { + hasNormals = true; car++; + } + if (car < end - 1 && car[0] == '4') { + hasHomogenous = true; car++; + } + if (car < end - 1 && car[0] == 'n') { + hasDimension = true; car++; + } + if (car < end - 3 && car[0] == 'O' && car[1] == 'F' && car[2] == 'F') { + car += 3; + NextToken(&car, end); + } else { + // in case there is no OFF header (which is allowed by the + // specification...), then we might have unintentionally read an + // additional dimension from the primitive count fields + dimensions = 3; + hasHomogenous = false; + NextToken(&car, end); + + // at this point the next token should be an integer number + if (car >= end - 1 || *car < '0' || *car > '9') { + throw DeadlyImportError("OFF: Header is invalid"); + } + } + if (hasDimension) { + dimensions = strtoul10(car, &car); + NextToken(&car, end); + } + if (dimensions > 3) { + throw DeadlyImportError + ("OFF: Number of vertex coordinates higher than 3 unsupported"); + } + + NextToken(&car, end); + const unsigned int numVertices = strtoul10(car, &car); + NextToken(&car, end); + const unsigned int numFaces = strtoul10(car, &car); + NextToken(&car, end); + strtoul10(car, &car); // skip edge count + NextToken(&car, end); + + if (!numVertices) { + throw DeadlyImportError("OFF: There are no valid vertices"); + } + if (!numFaces) { + throw DeadlyImportError("OFF: There are no valid faces"); + } + + pScene->mNumMeshes = 1; + pScene->mMeshes = new aiMesh*[ pScene->mNumMeshes ]; + + aiMesh* mesh = new aiMesh(); + pScene->mMeshes[0] = mesh; + + mesh->mNumFaces = numFaces; + aiFace* faces = new aiFace[mesh->mNumFaces]; + mesh->mFaces = faces; + + mesh->mNumVertices = numVertices; + mesh->mVertices = new aiVector3D[numVertices]; + mesh->mNormals = hasNormals ? new aiVector3D[numVertices] : nullptr; + mesh->mColors[0] = hasColors ? new aiColor4D[numVertices] : nullptr; + + if (hasTexCoord) { + mesh->mNumUVComponents[0] = 2; + mesh->mTextureCoords[0] = new aiVector3D[numVertices]; + } + char line[4096]; + buffer = car; + const char *sz = car; + + // now read all vertex lines + for (unsigned int i = 0; i < numVertices; ++i) { + if(!GetNextLine(buffer, line)) { + ASSIMP_LOG_ERROR("OFF: The number of verts in the header is incorrect"); + break; + } + aiVector3D& v = mesh->mVertices[i]; + sz = line; + + // helper array to write a for loop over possible dimension values + ai_real* vec[3] = {&v.x, &v.y, &v.z}; + + // stop at dimensions: this allows loading 1D or 2D coordinate vertices + for (unsigned int dim = 0; dim < dimensions; ++dim ) { + SkipSpaces(&sz); + sz = fast_atoreal_move<ai_real>(sz, *vec[dim]); + } + + // if has homogeneous coordinate, divide others by this one + if (hasHomogenous) { + SkipSpaces(&sz); + ai_real w = 1.; + sz = fast_atoreal_move<ai_real>(sz, w); + for (unsigned int dim = 0; dim < dimensions; ++dim ) { + *(vec[dim]) /= w; + } + } + + // read optional normals + if (hasNormals) { + aiVector3D& n = mesh->mNormals[i]; + SkipSpaces(&sz); + sz = fast_atoreal_move<ai_real>(sz,(ai_real&)n.x); + SkipSpaces(&sz); + sz = fast_atoreal_move<ai_real>(sz,(ai_real&)n.y); + SkipSpaces(&sz); + fast_atoreal_move<ai_real>(sz,(ai_real&)n.z); + } + + // reading colors is a pain because the specification says it can be + // integers or floats, and any number of them between 1 and 4 included, + // until the next comment or end of line + // in theory should be testing type ! + if (hasColors) { + aiColor4D& c = mesh->mColors[0][i]; + SkipSpaces(&sz); + sz = fast_atoreal_move<ai_real>(sz,(ai_real&)c.r); + if (*sz != '#' && *sz != '\n' && *sz != '\r') { + SkipSpaces(&sz); + sz = fast_atoreal_move<ai_real>(sz,(ai_real&)c.g); + } else { + c.g = 0.; + } + if (*sz != '#' && *sz != '\n' && *sz != '\r') { + SkipSpaces(&sz); + sz = fast_atoreal_move<ai_real>(sz,(ai_real&)c.b); + } else { + c.b = 0.; + } + if (*sz != '#' && *sz != '\n' && *sz != '\r') { + SkipSpaces(&sz); + sz = fast_atoreal_move<ai_real>(sz,(ai_real&)c.a); + } else { + c.a = 1.; + } + } + if (hasTexCoord) { + aiVector3D& t = mesh->mTextureCoords[0][i]; + SkipSpaces(&sz); + sz = fast_atoreal_move<ai_real>(sz,(ai_real&)t.x); + SkipSpaces(&sz); + fast_atoreal_move<ai_real>(sz,(ai_real&)t.y); + } + } + + // load faces with their indices + faces = mesh->mFaces; + for (unsigned int i = 0; i < numFaces; ) { + if(!GetNextLine(buffer,line)) { + ASSIMP_LOG_ERROR("OFF: The number of faces in the header is incorrect"); + break; + } + unsigned int idx; + sz = line; SkipSpaces(&sz); + idx = strtoul10(sz,&sz); + if(!idx || idx > 9) { + ASSIMP_LOG_ERROR("OFF: Faces with zero indices aren't allowed"); + --mesh->mNumFaces; + continue; + } + faces->mNumIndices = idx; + faces->mIndices = new unsigned int[faces->mNumIndices]; + for (unsigned int m = 0; m < faces->mNumIndices;++m) { + SkipSpaces(&sz); + idx = strtoul10(sz,&sz); + if (idx >= numVertices) { + ASSIMP_LOG_ERROR("OFF: Vertex index is out of range"); + idx = numVertices - 1; + } + faces->mIndices[m] = idx; + } + ++i; + ++faces; + } + + // generate the output node graph + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mName.Set("<OFFRoot>"); + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int [pScene->mRootNode->mNumMeshes]; + pScene->mRootNode->mMeshes[0] = 0; + + // generate a default material + pScene->mNumMaterials = 1; + pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]; + aiMaterial* pcMat = new aiMaterial(); + + aiColor4D clr( ai_real( 0.6 ), ai_real( 0.6 ), ai_real( 0.6 ), ai_real( 1.0 ) ); + pcMat->AddProperty(&clr,1,AI_MATKEY_COLOR_DIFFUSE); + pScene->mMaterials[0] = pcMat; + + const int twosided = 1; + pcMat->AddProperty(&twosided, 1, AI_MATKEY_TWOSIDED); +} + +#endif // !! ASSIMP_BUILD_NO_OFF_IMPORTER diff --git a/libs/assimp/code/AssetLib/OFF/OFFLoader.h b/libs/assimp/code/AssetLib/OFF/OFFLoader.h new file mode 100644 index 0000000..04bb7f4 --- /dev/null +++ b/libs/assimp/code/AssetLib/OFF/OFFLoader.h @@ -0,0 +1,86 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 OFFLoader.h + * @brief Declaration of the OFF importer class. + */ +#pragma once +#ifndef AI_OFFLOADER_H_INCLUDED +#define AI_OFFLOADER_H_INCLUDED + +#include <assimp/BaseImporter.h> +#include <assimp/types.h> +#include <vector> + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** Importer class for the Object File Format (.off) +*/ +class OFFImporter : public BaseImporter { +public: + OFFImporter(); + ~OFFImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; +}; + +} // end of namespace Assimp + +#endif // AI_3DSIMPORTER_H_IN diff --git a/libs/assimp/code/AssetLib/Obj/ObjExporter.cpp b/libs/assimp/code/AssetLib/Obj/ObjExporter.cpp new file mode 100644 index 0000000..882f3a9 --- /dev/null +++ b/libs/assimp/code/AssetLib/Obj/ObjExporter.cpp @@ -0,0 +1,414 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER + +#include "ObjExporter.h" +#include <assimp/Exceptional.h> +#include <assimp/StringComparison.h> +#include <assimp/version.h> +#include <assimp/IOSystem.hpp> +#include <assimp/Exporter.hpp> +#include <assimp/material.h> +#include <assimp/scene.h> +#include <memory> + +using namespace Assimp; + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +// Worker function for exporting a scene to Wavefront OBJ. Prototyped and registered in Exporter.cpp +void ExportSceneObj(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) { + // invoke the exporter + ObjExporter exporter(pFile, pScene); + + if (exporter.mOutput.fail() || exporter.mOutputMat.fail()) { + throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); + } + + // we're still here - export successfully completed. Write both the main OBJ file and the material script + { + std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt")); + if (outfile == nullptr) { + throw DeadlyExportError("could not open output .obj file: " + std::string(pFile)); + } + outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1); + } + { + std::unique_ptr<IOStream> outfile (pIOSystem->Open(exporter.GetMaterialLibFileName(),"wt")); + if (outfile == nullptr) { + throw DeadlyExportError("could not open output .mtl file: " + std::string(exporter.GetMaterialLibFileName())); + } + outfile->Write( exporter.mOutputMat.str().c_str(), static_cast<size_t>(exporter.mOutputMat.tellp()),1); + } +} + +// ------------------------------------------------------------------------------------------------ +// Worker function for exporting a scene to Wavefront OBJ without the material file. Prototyped and registered in Exporter.cpp +void ExportSceneObjNoMtl(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* ) { + // invoke the exporter + ObjExporter exporter(pFile, pScene, true); + + if (exporter.mOutput.fail() || exporter.mOutputMat.fail()) { + throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); + } + + // we're still here - export successfully completed. Write both the main OBJ file and the material script + { + std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt")); + if (outfile == nullptr) { + throw DeadlyExportError("could not open output .obj file: " + std::string(pFile)); + } + outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1); + } + + +} + +} // end of namespace Assimp + +static const std::string MaterialExt = ".mtl"; + +// ------------------------------------------------------------------------------------------------ +ObjExporter::ObjExporter(const char* _filename, const aiScene* pScene, bool noMtl) +: filename(_filename) +, pScene(pScene) +, vn() +, vt() +, vp() +, useVc(false) +, mVnMap() +, mVtMap() +, mVpMap() +, mMeshes() +, endl("\n") { + // make sure that all formatting happens using the standard, C locale and not the user's current locale + const std::locale& l = std::locale("C"); + mOutput.imbue(l); + mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); + mOutputMat.imbue(l); + mOutputMat.precision(ASSIMP_AI_REAL_TEXT_PRECISION); + + WriteGeometryFile(noMtl); + if ( !noMtl ) { + WriteMaterialFile(); + } +} + +// ------------------------------------------------------------------------------------------------ +ObjExporter::~ObjExporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +std::string ObjExporter::GetMaterialLibName() { + // within the Obj file, we use just the relative file name with the path stripped + const std::string& s = GetMaterialLibFileName(); + std::string::size_type il = s.find_last_of("/\\"); + if (il != std::string::npos) { + return s.substr(il + 1); + } + + return s; +} + +// ------------------------------------------------------------------------------------------------ +std::string ObjExporter::GetMaterialLibFileName() { + // Remove existing .obj file extension so that the final material file name will be fileName.mtl and not fileName.obj.mtl + size_t lastdot = filename.find_last_of('.'); + if ( lastdot != std::string::npos ) { + return filename.substr( 0, lastdot ) + MaterialExt; + } + + return filename + MaterialExt; +} + +// ------------------------------------------------------------------------------------------------ +void ObjExporter::WriteHeader(std::ostringstream& out) { + out << "# File produced by Open Asset Import Library (http://www.assimp.sf.net)" << endl; + out << "# (assimp v" << aiGetVersionMajor() << '.' << aiGetVersionMinor() << '.' + << aiGetVersionRevision() << ")" << endl << endl; +} + +// ------------------------------------------------------------------------------------------------ +std::string ObjExporter::GetMaterialName(unsigned int index) { + const aiMaterial* const mat = pScene->mMaterials[index]; + if ( nullptr == mat ) { + static const std::string EmptyStr; + return EmptyStr; + } + + aiString s; + if(AI_SUCCESS == mat->Get(AI_MATKEY_NAME,s)) { + return std::string(s.data,s.length); + } + + char number[ sizeof(unsigned int) * 3 + 1 ]; + ASSIMP_itoa10(number,index); + return "$Material_" + std::string(number); +} + +// ------------------------------------------------------------------------------------------------ +void ObjExporter::WriteMaterialFile() { + WriteHeader(mOutputMat); + + for(unsigned int i = 0; i < pScene->mNumMaterials; ++i) { + const aiMaterial* const mat = pScene->mMaterials[i]; + + int illum = 1; + mOutputMat << "newmtl " << GetMaterialName(i) << endl; + + aiColor4D c; + if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_DIFFUSE,c)) { + mOutputMat << "Kd " << c.r << " " << c.g << " " << c.b << endl; + } + if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_AMBIENT,c)) { + mOutputMat << "Ka " << c.r << " " << c.g << " " << c.b << endl; + } + if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_SPECULAR,c)) { + mOutputMat << "Ks " << c.r << " " << c.g << " " << c.b << endl; + } + if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_EMISSIVE,c)) { + mOutputMat << "Ke " << c.r << " " << c.g << " " << c.b << endl; + } + if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_TRANSPARENT,c)) { + mOutputMat << "Tf " << c.r << " " << c.g << " " << c.b << endl; + } + + ai_real o; + if(AI_SUCCESS == mat->Get(AI_MATKEY_OPACITY,o)) { + mOutputMat << "d " << o << endl; + } + if(AI_SUCCESS == mat->Get(AI_MATKEY_REFRACTI,o)) { + mOutputMat << "Ni " << o << endl; + } + + if(AI_SUCCESS == mat->Get(AI_MATKEY_SHININESS,o) && o) { + mOutputMat << "Ns " << o << endl; + illum = 2; + } + + mOutputMat << "illum " << illum << endl; + + aiString s; + if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_DIFFUSE(0),s)) { + mOutputMat << "map_Kd " << s.data << endl; + } + if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_AMBIENT(0),s)) { + mOutputMat << "map_Ka " << s.data << endl; + } + if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_SPECULAR(0),s)) { + mOutputMat << "map_Ks " << s.data << endl; + } + if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_SHININESS(0),s)) { + mOutputMat << "map_Ns " << s.data << endl; + } + if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_OPACITY(0),s)) { + mOutputMat << "map_d " << s.data << endl; + } + if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_HEIGHT(0),s) || AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_NORMALS(0),s)) { + // implementations seem to vary here, so write both variants + mOutputMat << "bump " << s.data << endl; + mOutputMat << "map_bump " << s.data << endl; + } + + mOutputMat << endl; + } +} + +void ObjExporter::WriteGeometryFile(bool noMtl) { + WriteHeader(mOutput); + if (!noMtl) + mOutput << "mtllib " << GetMaterialLibName() << endl << endl; + + // collect mesh geometry + aiMatrix4x4 mBase; + AddNode(pScene->mRootNode, mBase); + + // write vertex positions with colors, if any + mVpMap.getKeys( vp ); + if ( !useVc ) { + mOutput << "# " << vp.size() << " vertex positions" << endl; + for ( const vertexData& v : vp ) { + mOutput << "v " << v.vp.x << " " << v.vp.y << " " << v.vp.z << endl; + } + } else { + mOutput << "# " << vp.size() << " vertex positions and colors" << endl; + for ( const vertexData& v : vp ) { + mOutput << "v " << v.vp.x << " " << v.vp.y << " " << v.vp.z << " " << v.vc.r << " " << v.vc.g << " " << v.vc.b << endl; + } + } + mOutput << endl; + + // write uv coordinates + mVtMap.getKeys(vt); + mOutput << "# " << vt.size() << " UV coordinates" << endl; + for(const aiVector3D& v : vt) { + mOutput << "vt " << v.x << " " << v.y << " " << v.z << endl; + } + mOutput << endl; + + // write vertex normals + mVnMap.getKeys(vn); + mOutput << "# " << vn.size() << " vertex normals" << endl; + for(const aiVector3D& v : vn) { + mOutput << "vn " << v.x << " " << v.y << " " << v.z << endl; + } + mOutput << endl; + + // now write all mesh instances + for(const MeshInstance& m : mMeshes) { + mOutput << "# Mesh \'" << m.name << "\' with " << m.faces.size() << " faces" << endl; + if (!m.name.empty()) { + mOutput << "g " << m.name << endl; + } + if ( !noMtl ) { + mOutput << "usemtl " << m.matname << endl; + } + + for(const Face& f : m.faces) { + mOutput << f.kind << ' '; + for(const FaceVertex& fv : f.indices) { + mOutput << ' ' << fv.vp; + + if (f.kind != 'p') { + if (fv.vt || f.kind == 'f') { + mOutput << '/'; + } + if (fv.vt) { + mOutput << fv.vt; + } + if (f.kind == 'f' && fv.vn) { + mOutput << '/' << fv.vn; + } + } + } + + mOutput << endl; + } + mOutput << endl; + } +} + +// ------------------------------------------------------------------------------------------------ +void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat) { + mMeshes.push_back(MeshInstance() ); + MeshInstance& mesh = mMeshes.back(); + + if ( nullptr != m->mColors[ 0 ] ) { + useVc = true; + } + + mesh.name = std::string( name.data, name.length ); + mesh.matname = GetMaterialName(m->mMaterialIndex); + + mesh.faces.resize(m->mNumFaces); + + for(unsigned int i = 0; i < m->mNumFaces; ++i) { + const aiFace& f = m->mFaces[i]; + + Face& face = mesh.faces[i]; + switch (f.mNumIndices) { + case 1: + face.kind = 'p'; + break; + case 2: + face.kind = 'l'; + break; + default: + face.kind = 'f'; + } + face.indices.resize(f.mNumIndices); + + for(unsigned int a = 0; a < f.mNumIndices; ++a) { + const unsigned int idx = f.mIndices[a]; + + aiVector3D vert = mat * m->mVertices[idx]; + + if ( nullptr != m->mColors[ 0 ] ) { + aiColor4D col4 = m->mColors[ 0 ][ idx ]; + face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(col4.r, col4.g, col4.b)}); + } else { + face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(0,0,0)}); + } + + if (m->mNormals) { + aiVector3D norm = aiMatrix3x3(mat) * m->mNormals[idx]; + face.indices[a].vn = mVnMap.getIndex(norm); + } else { + face.indices[a].vn = 0; + } + + if ( m->mTextureCoords[ 0 ] ) { + face.indices[a].vt = mVtMap.getIndex(m->mTextureCoords[0][idx]); + } else { + face.indices[a].vt = 0; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void ObjExporter::AddNode(const aiNode* nd, const aiMatrix4x4& mParent) { + const aiMatrix4x4& mAbs = mParent * nd->mTransformation; + + aiMesh *cm( nullptr ); + for(unsigned int i = 0; i < nd->mNumMeshes; ++i) { + cm = pScene->mMeshes[nd->mMeshes[i]]; + if (nullptr != cm) { + AddMesh(cm->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs); + } else { + AddMesh(nd->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs); + } + } + + for(unsigned int i = 0; i < nd->mNumChildren; ++i) { + AddNode(nd->mChildren[i], mAbs); + } +} + +// ------------------------------------------------------------------------------------------------ + +#endif // ASSIMP_BUILD_NO_OBJ_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/Obj/ObjExporter.h b/libs/assimp/code/AssetLib/Obj/ObjExporter.h new file mode 100644 index 0000000..a64f38f --- /dev/null +++ b/libs/assimp/code/AssetLib/Obj/ObjExporter.h @@ -0,0 +1,190 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, 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 ObjExporter.h + * Declares the exporter class to write a scene to a Collada file + */ +#ifndef AI_OBJEXPORTER_H_INC +#define AI_OBJEXPORTER_H_INC + +#include <assimp/types.h> +#include <sstream> +#include <vector> +#include <map> + +struct aiScene; +struct aiNode; +struct aiMesh; + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +/** Helper class to export a given scene to an OBJ file. */ +// ------------------------------------------------------------------------------------------------ +class ObjExporter { +public: + /// Constructor for a specific scene to export + ObjExporter(const char* filename, const aiScene* pScene, bool noMtl=false); + ~ObjExporter(); + std::string GetMaterialLibName(); + std::string GetMaterialLibFileName(); + + /// public string-streams to write all output into + std::ostringstream mOutput, mOutputMat; + +private: + // intermediate data structures + struct FaceVertex { + FaceVertex() + : vp() + , vn() + , vt() { + // empty + } + + // one-based, 0 means: 'does not exist' + unsigned int vp, vn, vt; + }; + + struct Face { + char kind; + std::vector<FaceVertex> indices; + }; + + struct MeshInstance { + std::string name, matname; + std::vector<Face> faces; + }; + + void WriteHeader(std::ostringstream& out); + void WriteMaterialFile(); + void WriteGeometryFile(bool noMtl=false); + std::string GetMaterialName(unsigned int index); + void AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat); + void AddNode(const aiNode* nd, const aiMatrix4x4& mParent); + +private: + std::string filename; + const aiScene* const pScene; + + struct vertexData { + aiVector3D vp; + aiColor3D vc; // OBJ does not support 4D color + }; + + std::vector<aiVector3D> vn, vt; + std::vector<aiColor4D> vc; + std::vector<vertexData> vp; + bool useVc; + + struct vertexDataCompare { + bool operator() ( const vertexData& a, const vertexData& b ) const { + // position + if (a.vp.x < b.vp.x) return true; + if (a.vp.x > b.vp.x) return false; + if (a.vp.y < b.vp.y) return true; + if (a.vp.y > b.vp.y) return false; + if (a.vp.z < b.vp.z) return true; + if (a.vp.z > b.vp.z) return false; + + // color + if (a.vc.r < b.vc.r) return true; + if (a.vc.r > b.vc.r) return false; + if (a.vc.g < b.vc.g) return true; + if (a.vc.g > b.vc.g) return false; + if (a.vc.b < b.vc.b) return true; + if (a.vc.b > b.vc.b) return false; + return false; + } + }; + + struct aiVectorCompare { + bool operator() (const aiVector3D& a, const aiVector3D& b) const { + if(a.x < b.x) return true; + if(a.x > b.x) return false; + if(a.y < b.y) return true; + if(a.y > b.y) return false; + if(a.z < b.z) return true; + return false; + } + }; + + template <class T, class Compare = std::less<T>> + class indexMap { + int mNextIndex; + typedef std::map<T, int, Compare> dataType; + dataType vecMap; + + public: + indexMap() + : mNextIndex(1) { + // empty + } + + int getIndex(const T& key) { + typename dataType::iterator vertIt = vecMap.find(key); + // vertex already exists, so reference it + if(vertIt != vecMap.end()){ + return vertIt->second; + } + return vecMap[key] = mNextIndex++; + }; + + void getKeys( std::vector<T>& keys ) { + keys.resize(vecMap.size()); + for(typename dataType::iterator it = vecMap.begin(); it != vecMap.end(); ++it){ + keys[it->second-1] = it->first; + } + }; + }; + + indexMap<aiVector3D, aiVectorCompare> mVnMap, mVtMap; + indexMap<vertexData, vertexDataCompare> mVpMap; + std::vector<MeshInstance> mMeshes; + + // this endl() doesn't flush() the stream + const std::string endl; +}; + +} + +#endif diff --git a/libs/assimp/code/AssetLib/Obj/ObjFileData.h b/libs/assimp/code/AssetLib/Obj/ObjFileData.h new file mode 100644 index 0000000..3d504d0 --- /dev/null +++ b/libs/assimp/code/AssetLib/Obj/ObjFileData.h @@ -0,0 +1,344 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, 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. + +---------------------------------------------------------------------- +*/ + +#pragma once +#ifndef OBJ_FILEDATA_H_INC +#define OBJ_FILEDATA_H_INC + +#include <assimp/mesh.h> +#include <assimp/types.h> +#include <map> +#include <vector> + +namespace Assimp { +namespace ObjFile { + +struct Object; +struct Face; +struct Material; + +// ------------------------------------------------------------------------------------------------ +//! \struct Face +//! \brief Data structure for a simple obj-face, describes discredit,l.ation and materials +// ------------------------------------------------------------------------------------------------ +struct Face { + using IndexArray = std::vector<unsigned int>; + + //! Primitive type + aiPrimitiveType m_PrimitiveType; + //! Vertex indices + IndexArray m_vertices; + //! Normal indices + IndexArray m_normals; + //! Texture coordinates indices + IndexArray m_texturCoords; + //! Pointer to assigned material + Material *m_pMaterial; + + //! \brief Default constructor + Face(aiPrimitiveType pt = aiPrimitiveType_POLYGON) : + m_PrimitiveType(pt), m_vertices(), m_normals(), m_texturCoords(), m_pMaterial(0L) { + // empty + } + + //! \brief Destructor + ~Face() { + // empty + } +}; + +// ------------------------------------------------------------------------------------------------ +//! \struct Object +//! \brief Stores all objects of an obj-file object definition +// ------------------------------------------------------------------------------------------------ +struct Object { + enum ObjectType { + ObjType, + GroupType + }; + + //! Object name + std::string m_strObjName; + //! Transformation matrix, stored in OpenGL format + aiMatrix4x4 m_Transformation; + //! All sub-objects referenced by this object + std::vector<Object *> m_SubObjects; + /// Assigned meshes + std::vector<unsigned int> m_Meshes; + + //! \brief Default constructor + Object() = default; + + //! \brief Destructor + ~Object() { + for (std::vector<Object *>::iterator it = m_SubObjects.begin(); it != m_SubObjects.end(); ++it) { + delete *it; + } + } +}; + +// ------------------------------------------------------------------------------------------------ +//! \struct Material +//! \brief Data structure to store all material specific data +// ------------------------------------------------------------------------------------------------ +struct Material { + //! Name of material description + aiString MaterialName; + //! Texture names + aiString texture; + aiString textureSpecular; + aiString textureAmbient; + aiString textureEmissive; + aiString textureBump; + aiString textureNormal; + aiString textureReflection[6]; + aiString textureSpecularity; + aiString textureOpacity; + aiString textureDisp; + aiString textureRoughness; + aiString textureMetallic; + aiString textureSheen; + aiString textureRMA; + + enum TextureType { + TextureDiffuseType = 0, + TextureSpecularType, + TextureAmbientType, + TextureEmissiveType, + TextureBumpType, + TextureNormalType, + TextureReflectionSphereType, + TextureReflectionCubeTopType, + TextureReflectionCubeBottomType, + TextureReflectionCubeFrontType, + TextureReflectionCubeBackType, + TextureReflectionCubeLeftType, + TextureReflectionCubeRightType, + TextureSpecularityType, + TextureOpacityType, + TextureDispType, + TextureRoughnessType, + TextureMetallicType, + TextureSheenType, + TextureRMAType, + TextureTypeCount + }; + bool clamp[TextureTypeCount]; + + //! Ambient color + aiColor3D ambient; + //! Diffuse color + aiColor3D diffuse; + //! Specular color + aiColor3D specular; + //! Emissive color + aiColor3D emissive; + //! Alpha value + ai_real alpha; + //! Shineness factor + ai_real shineness; + //! Illumination model + int illumination_model; + //! Index of refraction + ai_real ior; + //! Transparency color + aiColor3D transparent; + + //! PBR Roughness + ai_real roughness; + //! PBR Metallic + ai_real metallic; + //! PBR Metallic + aiColor3D sheen; + //! PBR Clearcoat Thickness + ai_real clearcoat_thickness; + //! PBR Clearcoat Rougness + ai_real clearcoat_roughness; + //! PBR Anisotropy + ai_real anisotropy; + + //! bump map multipler (normal map scalar)(-bm) + ai_real bump_multiplier; + + //! Constructor + Material() : + diffuse(ai_real(0.6), ai_real(0.6), ai_real(0.6)), + alpha(ai_real(1.0)), + shineness(ai_real(0.0)), + illumination_model(1), + ior(ai_real(1.0)), + transparent(ai_real(1.0), ai_real(1.0), ai_real(1.0)), + roughness(ai_real(1.0)), + metallic(ai_real(0.0)), + sheen(ai_real(1.0), ai_real(1.0), ai_real(1.0)), + clearcoat_thickness(ai_real(0.0)), + clearcoat_roughness(ai_real(0.0)), + anisotropy(ai_real(0.0)), + bump_multiplier(ai_real(1.0)) { + std::fill_n(clamp, static_cast<unsigned int>(TextureTypeCount), false); + } + + // Destructor + ~Material() = default; +}; + +// ------------------------------------------------------------------------------------------------ +//! \struct Mesh +//! \brief Data structure to store a mesh +// ------------------------------------------------------------------------------------------------ +struct Mesh { + static const unsigned int NoMaterial = ~0u; + /// The name for the mesh + std::string m_name; + /// Array with pointer to all stored faces + std::vector<Face *> m_Faces; + /// Assigned material + Material *m_pMaterial; + /// Number of stored indices. + unsigned int m_uiNumIndices; + /// Number of UV + unsigned int m_uiUVCoordinates[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + /// Material index. + unsigned int m_uiMaterialIndex; + /// True, if normals are stored. + bool m_hasNormals; + /// True, if vertex colors are stored. + bool m_hasVertexColors; + + /// Constructor + explicit Mesh(const std::string &name) : + m_name(name), + m_pMaterial(nullptr), + m_uiNumIndices(0), + m_uiMaterialIndex(NoMaterial), + m_hasNormals(false) { + memset(m_uiUVCoordinates, 0, sizeof(unsigned int) * AI_MAX_NUMBER_OF_TEXTURECOORDS); + } + + /// Destructor + ~Mesh() { + for (std::vector<Face *>::iterator it = m_Faces.begin(); + it != m_Faces.end(); ++it) { + delete *it; + } + } +}; + +// ------------------------------------------------------------------------------------------------ +//! \struct Model +//! \brief Data structure to store all obj-specific model data +// ------------------------------------------------------------------------------------------------ +struct Model { + using GroupMap = std::map<std::string, std::vector<unsigned int> *>; + using GroupMapIt = std::map<std::string, std::vector<unsigned int> *>::iterator; + using ConstGroupMapIt = std::map<std::string, std::vector<unsigned int> *>::const_iterator; + + //! Model name + std::string m_ModelName; + //! List ob assigned objects + std::vector<Object *> m_Objects; + //! Pointer to current object + ObjFile::Object *m_pCurrent; + //! Pointer to current material + ObjFile::Material *m_pCurrentMaterial; + //! Pointer to default material + ObjFile::Material *m_pDefaultMaterial; + //! Vector with all generated materials + std::vector<std::string> m_MaterialLib; + //! Vector with all generated vertices + std::vector<aiVector3D> m_Vertices; + //! vector with all generated normals + std::vector<aiVector3D> m_Normals; + //! vector with all vertex colors + std::vector<aiVector3D> m_VertexColors; + //! Group map + GroupMap m_Groups; + //! Group to face id assignment + std::vector<unsigned int> *m_pGroupFaceIDs; + //! Active group + std::string m_strActiveGroup; + //! Vector with generated texture coordinates + std::vector<aiVector3D> m_TextureCoord; + //! Maximum dimension of texture coordinates + unsigned int m_TextureCoordDim; + //! Current mesh instance + Mesh *m_pCurrentMesh; + //! Vector with stored meshes + std::vector<Mesh *> m_Meshes; + //! Material map + std::map<std::string, Material *> m_MaterialMap; + + //! \brief The default class constructor + Model() : + m_ModelName(), + m_pCurrent(nullptr), + m_pCurrentMaterial(nullptr), + m_pDefaultMaterial(nullptr), + m_pGroupFaceIDs(nullptr), + m_strActiveGroup(), + m_TextureCoordDim(0), + m_pCurrentMesh(nullptr) { + // empty + } + + //! \brief The class destructor + ~Model() { + for (auto & it : m_Objects) { + delete it; + } + for (auto & Meshe : m_Meshes) { + delete Meshe; + } + for (auto & Group : m_Groups) { + delete Group.second; + } + for (auto & it : m_MaterialMap) { + delete it.second; + } + } +}; + +// ------------------------------------------------------------------------------------------------ + +} // Namespace ObjFile +} // Namespace Assimp + +#endif // OBJ_FILEDATA_H_INC diff --git a/libs/assimp/code/AssetLib/Obj/ObjFileImporter.cpp b/libs/assimp/code/AssetLib/Obj/ObjFileImporter.cpp new file mode 100644 index 0000000..68fdb21 --- /dev/null +++ b/libs/assimp/code/AssetLib/Obj/ObjFileImporter.cpp @@ -0,0 +1,783 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2020, 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. +--------------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER + +#include "ObjFileImporter.h" +#include "ObjFileData.h" +#include "ObjFileParser.h" +#include <assimp/DefaultIOSystem.h> +#include <assimp/IOStreamBuffer.h> +#include <assimp/ai_assert.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> +#include <assimp/ObjMaterial.h> +#include <memory> + +static const aiImporterDesc desc = { + "Wavefront Object Importer", + "", + "", + "surfaces not supported", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "obj" +}; + +static const unsigned int ObjMinSize = 16; + +namespace Assimp { + +using namespace std; + +// ------------------------------------------------------------------------------------------------ +// Default constructor +ObjFileImporter::ObjFileImporter() : + m_Buffer(), + m_pRootObject(nullptr), + m_strAbsPath(std::string(1, DefaultIOSystem().getOsSeparator())) {} + +// ------------------------------------------------------------------------------------------------ +// Destructor. +ObjFileImporter::~ObjFileImporter() { + delete m_pRootObject; + m_pRootObject = nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// Returns true if file is an obj file. +bool ObjFileImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "mtllib", "usemtl", "v ", "vt ", "vn ", "o ", "g ", "s ", "f " }; + return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens), 200, false, true); +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc *ObjFileImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Obj-file import implementation +void ObjFileImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) { + // Read file into memory + static const std::string mode = "rb"; + auto streamCloser = [&](IOStream *pStream) { + pIOHandler->Close(pStream); + }; + std::unique_ptr<IOStream, decltype(streamCloser)> fileStream(pIOHandler->Open(file, mode), streamCloser); + if (!fileStream.get()) { + throw DeadlyImportError("Failed to open file ", file, "."); + } + + // Get the file-size and validate it, throwing an exception when fails + size_t fileSize = fileStream->FileSize(); + if (fileSize < ObjMinSize) { + throw DeadlyImportError("OBJ-file is too small."); + } + + IOStreamBuffer<char> streamedBuffer; + streamedBuffer.open(fileStream.get()); + + // Allocate buffer and read file into it + //TextFileToBuffer( fileStream.get(),m_Buffer); + + // Get the model name + std::string modelName, folderName; + std::string::size_type pos = file.find_last_of("\\/"); + if (pos != std::string::npos) { + modelName = file.substr(pos + 1, file.size() - pos - 1); + folderName = file.substr(0, pos); + if (!folderName.empty()) { + pIOHandler->PushDirectory(folderName); + } + } else { + modelName = file; + } + + // parse the file into a temporary representation + ObjFileParser parser(streamedBuffer, modelName, pIOHandler, m_progress, file); + + // And create the proper return structures out of it + CreateDataFromImport(parser.GetModel(), pScene); + + streamedBuffer.close(); + + // Clean up allocated storage for the next import + m_Buffer.clear(); + + // Pop directory stack + if (pIOHandler->StackSize() > 0) { + pIOHandler->PopDirectory(); + } +} + +// ------------------------------------------------------------------------------------------------ +// Create the data from parsed obj-file +void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene *pScene) { + if (nullptr == pModel) { + return; + } + + // Create the root node of the scene + pScene->mRootNode = new aiNode; + if (!pModel->m_ModelName.empty()) { + // Set the name of the scene + pScene->mRootNode->mName.Set(pModel->m_ModelName); + } else { + // This is a fatal error, so break down the application + ai_assert(false); + } + + if (!pModel->m_Objects.empty()) { + + unsigned int meshCount = 0; + unsigned int childCount = 0; + + for (auto object : pModel->m_Objects) { + if (object) { + ++childCount; + meshCount += (unsigned int)object->m_Meshes.size(); + } + } + + // Allocate space for the child nodes on the root node + pScene->mRootNode->mChildren = new aiNode *[childCount]; + + // Create nodes for the whole scene + std::vector<aiMesh *> MeshArray; + MeshArray.reserve(meshCount); + for (size_t index = 0; index < pModel->m_Objects.size(); ++index) { + createNodes(pModel, pModel->m_Objects[index], pScene->mRootNode, pScene, MeshArray); + } + + ai_assert(pScene->mRootNode->mNumChildren == childCount); + + // Create mesh pointer buffer for this scene + if (pScene->mNumMeshes > 0) { + pScene->mMeshes = new aiMesh *[MeshArray.size()]; + for (size_t index = 0; index < MeshArray.size(); ++index) { + pScene->mMeshes[index] = MeshArray[index]; + } + } + + // Create all materials + createMaterials(pModel, pScene); + } else { + if (pModel->m_Vertices.empty()) { + return; + } + + std::unique_ptr<aiMesh> mesh(new aiMesh); + mesh->mPrimitiveTypes = aiPrimitiveType_POINT; + unsigned int n = (unsigned int)pModel->m_Vertices.size(); + mesh->mNumVertices = n; + + mesh->mVertices = new aiVector3D[n]; + memcpy(mesh->mVertices, pModel->m_Vertices.data(), n * sizeof(aiVector3D)); + + if (!pModel->m_Normals.empty()) { + mesh->mNormals = new aiVector3D[n]; + if (pModel->m_Normals.size() < n) { + throw DeadlyImportError("OBJ: vertex normal index out of range"); + } + memcpy(mesh->mNormals, pModel->m_Normals.data(), n * sizeof(aiVector3D)); + } + + if (!pModel->m_VertexColors.empty()) { + mesh->mColors[0] = new aiColor4D[mesh->mNumVertices]; + for (unsigned int i = 0; i < n; ++i) { + if (i < pModel->m_VertexColors.size()) { + const aiVector3D &color = pModel->m_VertexColors[i]; + mesh->mColors[0][i] = aiColor4D(color.x, color.y, color.z, 1.0); + } else { + throw DeadlyImportError("OBJ: vertex color index out of range"); + } + } + } + + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mRootNode->mMeshes[0] = 0; + pScene->mMeshes = new aiMesh *[1]; + pScene->mNumMeshes = 1; + pScene->mMeshes[0] = mesh.release(); + } +} + +// ------------------------------------------------------------------------------------------------ +// Creates all nodes of the model +aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile::Object *pObject, + aiNode *pParent, aiScene *pScene, + std::vector<aiMesh *> &MeshArray) { + ai_assert(nullptr != pModel); + if (nullptr == pObject) { + return nullptr; + } + + // Store older mesh size to be able to computes mesh offsets for new mesh instances + const size_t oldMeshSize = MeshArray.size(); + aiNode *pNode = new aiNode; + + pNode->mName = pObject->m_strObjName; + + // If we have a parent node, store it + ai_assert(nullptr != pParent); + appendChildToParentNode(pParent, pNode); + + for (size_t i = 0; i < pObject->m_Meshes.size(); ++i) { + unsigned int meshId = pObject->m_Meshes[i]; + aiMesh *pMesh = createTopology(pModel, pObject, meshId); + if (pMesh) { + if (pMesh->mNumFaces > 0) { + MeshArray.push_back(pMesh); + } else { + delete pMesh; + } + } + } + + // Create all nodes from the sub-objects stored in the current object + if (!pObject->m_SubObjects.empty()) { + size_t numChilds = pObject->m_SubObjects.size(); + pNode->mNumChildren = static_cast<unsigned int>(numChilds); + pNode->mChildren = new aiNode *[numChilds]; + pNode->mNumMeshes = 1; + pNode->mMeshes = new unsigned int[1]; + } + + // Set mesh instances into scene- and node-instances + const size_t meshSizeDiff = MeshArray.size() - oldMeshSize; + if (meshSizeDiff > 0) { + pNode->mMeshes = new unsigned int[meshSizeDiff]; + pNode->mNumMeshes = static_cast<unsigned int>(meshSizeDiff); + size_t index = 0; + for (size_t i = oldMeshSize; i < MeshArray.size(); ++i) { + pNode->mMeshes[index] = pScene->mNumMeshes; + pScene->mNumMeshes++; + ++index; + } + } + + return pNode; +} + +// ------------------------------------------------------------------------------------------------ +// Create topology data +aiMesh *ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, unsigned int meshIndex) { + // Checking preconditions + ai_assert(nullptr != pModel); + + if (nullptr == pData) { + return nullptr; + } + + // Create faces + ObjFile::Mesh *pObjMesh = pModel->m_Meshes[meshIndex]; + if (!pObjMesh) { + return nullptr; + } + + if (pObjMesh->m_Faces.empty()) { + return nullptr; + } + + std::unique_ptr<aiMesh> pMesh(new aiMesh); + if (!pObjMesh->m_name.empty()) { + pMesh->mName.Set(pObjMesh->m_name); + } + + for (size_t index = 0; index < pObjMesh->m_Faces.size(); index++) { + ObjFile::Face *const inp = pObjMesh->m_Faces[index]; + ai_assert(nullptr != inp); + + if (inp->m_PrimitiveType == aiPrimitiveType_LINE) { + pMesh->mNumFaces += static_cast<unsigned int>(inp->m_vertices.size() - 1); + pMesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + } else if (inp->m_PrimitiveType == aiPrimitiveType_POINT) { + pMesh->mNumFaces += static_cast<unsigned int>(inp->m_vertices.size()); + pMesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + } else { + ++pMesh->mNumFaces; + if (inp->m_vertices.size() > 3) { + pMesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + } else { + pMesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + } + } + } + + unsigned int uiIdxCount(0u); + if (pMesh->mNumFaces > 0) { + pMesh->mFaces = new aiFace[pMesh->mNumFaces]; + if (pObjMesh->m_uiMaterialIndex != ObjFile::Mesh::NoMaterial) { + pMesh->mMaterialIndex = pObjMesh->m_uiMaterialIndex; + } + + unsigned int outIndex(0); + + // Copy all data from all stored meshes + for (auto &face : pObjMesh->m_Faces) { + ObjFile::Face *const inp = face; + if (inp->m_PrimitiveType == aiPrimitiveType_LINE) { + for (size_t i = 0; i < inp->m_vertices.size() - 1; ++i) { + aiFace &f = pMesh->mFaces[outIndex++]; + uiIdxCount += f.mNumIndices = 2; + f.mIndices = new unsigned int[2]; + } + continue; + } else if (inp->m_PrimitiveType == aiPrimitiveType_POINT) { + for (size_t i = 0; i < inp->m_vertices.size(); ++i) { + aiFace &f = pMesh->mFaces[outIndex++]; + uiIdxCount += f.mNumIndices = 1; + f.mIndices = new unsigned int[1]; + } + continue; + } + + aiFace *pFace = &pMesh->mFaces[outIndex++]; + const unsigned int uiNumIndices = (unsigned int)face->m_vertices.size(); + uiIdxCount += pFace->mNumIndices = (unsigned int)uiNumIndices; + if (pFace->mNumIndices > 0) { + pFace->mIndices = new unsigned int[uiNumIndices]; + } + } + } + + // Create mesh vertices + createVertexArray(pModel, pData, meshIndex, pMesh.get(), uiIdxCount); + + return pMesh.release(); +} + +// ------------------------------------------------------------------------------------------------ +// Creates a vertex array +void ObjFileImporter::createVertexArray(const ObjFile::Model *pModel, + const ObjFile::Object *pCurrentObject, + unsigned int uiMeshIndex, + aiMesh *pMesh, + unsigned int numIndices) { + // Checking preconditions + ai_assert(nullptr != pCurrentObject); + + // Break, if no faces are stored in object + if (pCurrentObject->m_Meshes.empty()) + return; + + // Get current mesh + ObjFile::Mesh *pObjMesh = pModel->m_Meshes[uiMeshIndex]; + if (nullptr == pObjMesh || pObjMesh->m_uiNumIndices < 1) { + return; + } + + // Copy vertices of this mesh instance + pMesh->mNumVertices = numIndices; + if (pMesh->mNumVertices == 0) { + throw DeadlyImportError("OBJ: no vertices"); + } else if (pMesh->mNumVertices > AI_MAX_VERTICES) { + throw DeadlyImportError("OBJ: Too many vertices"); + } + pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; + + // Allocate buffer for normal vectors + if (!pModel->m_Normals.empty() && pObjMesh->m_hasNormals) + pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + + // Allocate buffer for vertex-color vectors + if (!pModel->m_VertexColors.empty()) + pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices]; + + // Allocate buffer for texture coordinates + if (!pModel->m_TextureCoord.empty() && pObjMesh->m_uiUVCoordinates[0]) { + pMesh->mNumUVComponents[0] = pModel->m_TextureCoordDim; + pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices]; + } + + // Copy vertices, normals and textures into aiMesh instance + bool normalsok = true, uvok = true; + unsigned int newIndex = 0, outIndex = 0; + for (auto sourceFace : pObjMesh->m_Faces) { + // Copy all index arrays + for (size_t vertexIndex = 0, outVertexIndex = 0; vertexIndex < sourceFace->m_vertices.size(); vertexIndex++) { + const unsigned int vertex = sourceFace->m_vertices.at(vertexIndex); + if (vertex >= pModel->m_Vertices.size()) { + throw DeadlyImportError("OBJ: vertex index out of range"); + } + + if (pMesh->mNumVertices <= newIndex) { + throw DeadlyImportError("OBJ: bad vertex index"); + } + + pMesh->mVertices[newIndex] = pModel->m_Vertices[vertex]; + + // Copy all normals + if (normalsok && !pModel->m_Normals.empty() && vertexIndex < sourceFace->m_normals.size()) { + const unsigned int normal = sourceFace->m_normals.at(vertexIndex); + if (normal >= pModel->m_Normals.size()) { + normalsok = false; + } else { + pMesh->mNormals[newIndex] = pModel->m_Normals[normal]; + } + } + + // Copy all vertex colors + if (vertex < pModel->m_VertexColors.size()) { + const aiVector3D &color = pModel->m_VertexColors[vertex]; + pMesh->mColors[0][newIndex] = aiColor4D(color.x, color.y, color.z, 1.0); + } + + // Copy all texture coordinates + if (uvok && !pModel->m_TextureCoord.empty() && vertexIndex < sourceFace->m_texturCoords.size()) { + const unsigned int tex = sourceFace->m_texturCoords.at(vertexIndex); + + if (tex >= pModel->m_TextureCoord.size()) { + uvok = false; + } else { + const aiVector3D &coord3d = pModel->m_TextureCoord[tex]; + pMesh->mTextureCoords[0][newIndex] = aiVector3D(coord3d.x, coord3d.y, coord3d.z); + } + } + + // Get destination face + aiFace *pDestFace = &pMesh->mFaces[outIndex]; + + const bool last = (vertexIndex == sourceFace->m_vertices.size() - 1); + if (sourceFace->m_PrimitiveType != aiPrimitiveType_LINE || !last) { + pDestFace->mIndices[outVertexIndex] = newIndex; + outVertexIndex++; + } + + if (sourceFace->m_PrimitiveType == aiPrimitiveType_POINT) { + outIndex++; + outVertexIndex = 0; + } else if (sourceFace->m_PrimitiveType == aiPrimitiveType_LINE) { + outVertexIndex = 0; + + if (!last) + outIndex++; + + if (vertexIndex) { + if (!last) { + pMesh->mVertices[newIndex + 1] = pMesh->mVertices[newIndex]; + if (!sourceFace->m_normals.empty() && !pModel->m_Normals.empty()) { + pMesh->mNormals[newIndex + 1] = pMesh->mNormals[newIndex]; + } + if (!pModel->m_TextureCoord.empty()) { + for (size_t i = 0; i < pMesh->GetNumUVChannels(); i++) { + pMesh->mTextureCoords[i][newIndex + 1] = pMesh->mTextureCoords[i][newIndex]; + } + } + ++newIndex; + } + + pDestFace[-1].mIndices[1] = newIndex; + } + } else if (last) { + outIndex++; + } + ++newIndex; + } + } + + if (!normalsok) { + delete[] pMesh->mNormals; + pMesh->mNormals = nullptr; + } + + if (!uvok) { + delete[] pMesh->mTextureCoords[0]; + pMesh->mTextureCoords[0] = nullptr; + } +} + +// ------------------------------------------------------------------------------------------------ +// Counts all stored meshes +void ObjFileImporter::countObjects(const std::vector<ObjFile::Object *> &rObjects, int &iNumMeshes) { + iNumMeshes = 0; + if (rObjects.empty()) + return; + + iNumMeshes += static_cast<unsigned int>(rObjects.size()); + for (auto object : rObjects) { + if (!object->m_SubObjects.empty()) { + countObjects(object->m_SubObjects, iNumMeshes); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Add clamp mode property to material if necessary +void ObjFileImporter::addTextureMappingModeProperty(aiMaterial *mat, aiTextureType type, int clampMode, int index) { + if (nullptr == mat) { + return; + } + + mat->AddProperty<int>(&clampMode, 1, AI_MATKEY_MAPPINGMODE_U(type, index)); + mat->AddProperty<int>(&clampMode, 1, AI_MATKEY_MAPPINGMODE_V(type, index)); +} + +// ------------------------------------------------------------------------------------------------ +// Creates the material +void ObjFileImporter::createMaterials(const ObjFile::Model *pModel, aiScene *pScene) { + if (nullptr == pScene) { + return; + } + + const unsigned int numMaterials = (unsigned int)pModel->m_MaterialLib.size(); + pScene->mNumMaterials = 0; + if (pModel->m_MaterialLib.empty()) { + ASSIMP_LOG_DEBUG("OBJ: no materials specified"); + return; + } + + pScene->mMaterials = new aiMaterial *[numMaterials]; + for (unsigned int matIndex = 0; matIndex < numMaterials; matIndex++) { + // Store material name + std::map<std::string, ObjFile::Material *>::const_iterator it; + it = pModel->m_MaterialMap.find(pModel->m_MaterialLib[matIndex]); + + // No material found, use the default material + if (pModel->m_MaterialMap.end() == it) + continue; + + aiMaterial *mat = new aiMaterial; + ObjFile::Material *pCurrentMaterial = (*it).second; + mat->AddProperty(&pCurrentMaterial->MaterialName, AI_MATKEY_NAME); + + // convert illumination model + int sm = 0; + switch (pCurrentMaterial->illumination_model) { + case 0: + sm = aiShadingMode_NoShading; + break; + case 1: + sm = aiShadingMode_Gouraud; + break; + case 2: + sm = aiShadingMode_Phong; + break; + default: + sm = aiShadingMode_Gouraud; + ASSIMP_LOG_ERROR("OBJ: unexpected illumination model (0-2 recognized)"); + } + + mat->AddProperty<int>(&sm, 1, AI_MATKEY_SHADING_MODEL); + + // Preserve the original illum value + mat->AddProperty<int>(&pCurrentMaterial->illumination_model, 1, AI_MATKEY_OBJ_ILLUM); + + // Adding material colors + mat->AddProperty(&pCurrentMaterial->ambient, 1, AI_MATKEY_COLOR_AMBIENT); + mat->AddProperty(&pCurrentMaterial->diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty(&pCurrentMaterial->specular, 1, AI_MATKEY_COLOR_SPECULAR); + mat->AddProperty(&pCurrentMaterial->emissive, 1, AI_MATKEY_COLOR_EMISSIVE); + mat->AddProperty(&pCurrentMaterial->shineness, 1, AI_MATKEY_SHININESS); + mat->AddProperty(&pCurrentMaterial->alpha, 1, AI_MATKEY_OPACITY); + mat->AddProperty(&pCurrentMaterial->transparent, 1, AI_MATKEY_COLOR_TRANSPARENT); + mat->AddProperty(&pCurrentMaterial->roughness, 1, AI_MATKEY_ROUGHNESS_FACTOR); + mat->AddProperty(&pCurrentMaterial->metallic, 1, AI_MATKEY_METALLIC_FACTOR); + mat->AddProperty(&pCurrentMaterial->sheen, 1, AI_MATKEY_SHEEN_COLOR_FACTOR); + mat->AddProperty(&pCurrentMaterial->clearcoat_thickness, 1, AI_MATKEY_CLEARCOAT_FACTOR); + mat->AddProperty(&pCurrentMaterial->clearcoat_roughness, 1, AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR); + mat->AddProperty(&pCurrentMaterial->anisotropy, 1, AI_MATKEY_ANISOTROPY_FACTOR); + + // Adding refraction index + mat->AddProperty(&pCurrentMaterial->ior, 1, AI_MATKEY_REFRACTI); + + // Adding textures + const int uvwIndex = 0; + + if (0 != pCurrentMaterial->texture.length) { + mat->AddProperty(&pCurrentMaterial->texture, AI_MATKEY_TEXTURE_DIFFUSE(0)); + mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_DIFFUSE(0)); + if (pCurrentMaterial->clamp[ObjFile::Material::TextureDiffuseType]) { + addTextureMappingModeProperty(mat, aiTextureType_DIFFUSE); + } + } + + if (0 != pCurrentMaterial->textureAmbient.length) { + mat->AddProperty(&pCurrentMaterial->textureAmbient, AI_MATKEY_TEXTURE_AMBIENT(0)); + mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_AMBIENT(0)); + if (pCurrentMaterial->clamp[ObjFile::Material::TextureAmbientType]) { + addTextureMappingModeProperty(mat, aiTextureType_AMBIENT); + } + } + + if (0 != pCurrentMaterial->textureEmissive.length) { + mat->AddProperty(&pCurrentMaterial->textureEmissive, AI_MATKEY_TEXTURE_EMISSIVE(0)); + mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_EMISSIVE(0)); + } + + if (0 != pCurrentMaterial->textureSpecular.length) { + mat->AddProperty(&pCurrentMaterial->textureSpecular, AI_MATKEY_TEXTURE_SPECULAR(0)); + mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_SPECULAR(0)); + if (pCurrentMaterial->clamp[ObjFile::Material::TextureSpecularType]) { + addTextureMappingModeProperty(mat, aiTextureType_SPECULAR); + } + } + + if (0 != pCurrentMaterial->textureBump.length) { + mat->AddProperty(&pCurrentMaterial->textureBump, AI_MATKEY_TEXTURE_HEIGHT(0)); + mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_HEIGHT(0)); + if (pCurrentMaterial->bump_multiplier != 1.0) { + mat->AddProperty(&pCurrentMaterial->bump_multiplier, 1, AI_MATKEY_OBJ_BUMPMULT_HEIGHT(0)); + } + if (pCurrentMaterial->clamp[ObjFile::Material::TextureBumpType]) { + addTextureMappingModeProperty(mat, aiTextureType_HEIGHT); + } + } + + if (0 != pCurrentMaterial->textureNormal.length) { + mat->AddProperty(&pCurrentMaterial->textureNormal, AI_MATKEY_TEXTURE_NORMALS(0)); + mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_NORMALS(0)); + if (pCurrentMaterial->bump_multiplier != 1.0) { + mat->AddProperty(&pCurrentMaterial->bump_multiplier, 1, AI_MATKEY_OBJ_BUMPMULT_NORMALS(0)); + } + if (pCurrentMaterial->clamp[ObjFile::Material::TextureNormalType]) { + addTextureMappingModeProperty(mat, aiTextureType_NORMALS); + } + } + + if (0 != pCurrentMaterial->textureReflection[0].length) { + ObjFile::Material::TextureType type = 0 != pCurrentMaterial->textureReflection[1].length ? + ObjFile::Material::TextureReflectionCubeTopType : + ObjFile::Material::TextureReflectionSphereType; + + unsigned count = type == ObjFile::Material::TextureReflectionSphereType ? 1 : 6; + for (unsigned i = 0; i < count; i++) { + mat->AddProperty(&pCurrentMaterial->textureReflection[i], AI_MATKEY_TEXTURE_REFLECTION(i)); + mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_REFLECTION(i)); + + if (pCurrentMaterial->clamp[type]) + addTextureMappingModeProperty(mat, aiTextureType_REFLECTION, 1, i); + } + } + + if (0 != pCurrentMaterial->textureDisp.length) { + mat->AddProperty(&pCurrentMaterial->textureDisp, AI_MATKEY_TEXTURE_DISPLACEMENT(0)); + mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_DISPLACEMENT(0)); + if (pCurrentMaterial->clamp[ObjFile::Material::TextureDispType]) { + addTextureMappingModeProperty(mat, aiTextureType_DISPLACEMENT); + } + } + + if (0 != pCurrentMaterial->textureOpacity.length) { + mat->AddProperty(&pCurrentMaterial->textureOpacity, AI_MATKEY_TEXTURE_OPACITY(0)); + mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_OPACITY(0)); + if (pCurrentMaterial->clamp[ObjFile::Material::TextureOpacityType]) { + addTextureMappingModeProperty(mat, aiTextureType_OPACITY); + } + } + + if (0 != pCurrentMaterial->textureSpecularity.length) { + mat->AddProperty(&pCurrentMaterial->textureSpecularity, AI_MATKEY_TEXTURE_SHININESS(0)); + mat->AddProperty(&uvwIndex, 1, AI_MATKEY_UVWSRC_SHININESS(0)); + if (pCurrentMaterial->clamp[ObjFile::Material::TextureSpecularityType]) { + addTextureMappingModeProperty(mat, aiTextureType_SHININESS); + } + } + + if (0 != pCurrentMaterial->textureRoughness.length) { + mat->AddProperty(&pCurrentMaterial->textureRoughness, _AI_MATKEY_TEXTURE_BASE, aiTextureType_DIFFUSE_ROUGHNESS, 0); + mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_DIFFUSE_ROUGHNESS, 0 ); + if (pCurrentMaterial->clamp[ObjFile::Material::TextureRoughnessType]) { + addTextureMappingModeProperty(mat, aiTextureType_DIFFUSE_ROUGHNESS); + } + } + + if (0 != pCurrentMaterial->textureMetallic.length) { + mat->AddProperty(&pCurrentMaterial->textureMetallic, _AI_MATKEY_TEXTURE_BASE, aiTextureType_METALNESS, 0); + mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_METALNESS, 0 ); + if (pCurrentMaterial->clamp[ObjFile::Material::TextureMetallicType]) { + addTextureMappingModeProperty(mat, aiTextureType_METALNESS); + } + } + + if (0 != pCurrentMaterial->textureSheen.length) { + mat->AddProperty(&pCurrentMaterial->textureSheen, _AI_MATKEY_TEXTURE_BASE, aiTextureType_SHEEN, 0); + mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_SHEEN, 0 ); + if (pCurrentMaterial->clamp[ObjFile::Material::TextureSheenType]) { + addTextureMappingModeProperty(mat, aiTextureType_SHEEN); + } + } + + if (0 != pCurrentMaterial->textureRMA.length) { + // NOTE: glTF importer places Rough/Metal/AO texture in Unknown so doing the same here for consistency. + mat->AddProperty(&pCurrentMaterial->textureRMA, _AI_MATKEY_TEXTURE_BASE, aiTextureType_UNKNOWN, 0); + mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_UNKNOWN, 0 ); + if (pCurrentMaterial->clamp[ObjFile::Material::TextureRMAType]) { + addTextureMappingModeProperty(mat, aiTextureType_UNKNOWN); + } + } + + // Store material property info in material array in scene + pScene->mMaterials[pScene->mNumMaterials] = mat; + pScene->mNumMaterials++; + } + + // Test number of created materials. + ai_assert(pScene->mNumMaterials == numMaterials); +} + +// ------------------------------------------------------------------------------------------------ +// Appends this node to the parent node +void ObjFileImporter::appendChildToParentNode(aiNode *pParent, aiNode *pChild) { + // Checking preconditions + ai_assert(nullptr != pParent); + ai_assert(nullptr != pChild); + + // Assign parent to child + pChild->mParent = pParent; + + // Copy node instances into parent node + pParent->mNumChildren++; + pParent->mChildren[pParent->mNumChildren - 1] = pChild; +} + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_OBJ_IMPORTER diff --git a/libs/assimp/code/AssetLib/Obj/ObjFileImporter.h b/libs/assimp/code/AssetLib/Obj/ObjFileImporter.h new file mode 100644 index 0000000..e76c279 --- /dev/null +++ b/libs/assimp/code/AssetLib/Obj/ObjFileImporter.h @@ -0,0 +1,122 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, 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. + +---------------------------------------------------------------------- +*/ +#pragma once +#ifndef OBJ_FILE_IMPORTER_H_INC +#define OBJ_FILE_IMPORTER_H_INC + +#include <assimp/BaseImporter.h> +#include <assimp/material.h> +#include <vector> + +struct aiMesh; +struct aiNode; + +namespace Assimp { + +namespace ObjFile { +struct Object; +struct Model; +} // namespace ObjFile + +// ------------------------------------------------------------------------------------------------ +/// \class ObjFileImporter +/// \brief Imports a waveform obj file +// ------------------------------------------------------------------------------------------------ +class ObjFileImporter : public BaseImporter { +public: + /// \brief Default constructor + ObjFileImporter(); + + /// \brief Destructor + ~ObjFileImporter() override; + + /// \brief Returns whether the class can handle the format of the given file. + /// \remark See BaseImporter::CanRead() for details. + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + +protected: + //! \brief Appends the supported extension. + const aiImporterDesc *GetInfo() const override; + + //! \brief File import implementation. + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + + //! \brief Create the data from imported content. + void CreateDataFromImport(const ObjFile::Model *pModel, aiScene *pScene); + + //! \brief Creates all nodes stored in imported content. + aiNode *createNodes(const ObjFile::Model *pModel, const ObjFile::Object *pData, + aiNode *pParent, aiScene *pScene, std::vector<aiMesh *> &MeshArray); + + //! \brief Creates topology data like faces and meshes for the geometry. + aiMesh *createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, + unsigned int uiMeshIndex); + + //! \brief Creates vertices from model. + void createVertexArray(const ObjFile::Model *pModel, const ObjFile::Object *pCurrentObject, + unsigned int uiMeshIndex, aiMesh *pMesh, unsigned int numIndices); + + //! \brief Object counter helper method. + void countObjects(const std::vector<ObjFile::Object *> &rObjects, int &iNumMeshes); + + //! \brief Material creation. + void createMaterials(const ObjFile::Model *pModel, aiScene *pScene); + + /// @brief Adds special property for the used texture mapping mode of the model. + void addTextureMappingModeProperty(aiMaterial *mat, aiTextureType type, int clampMode = 1, int index = 0); + + //! \brief Appends a child node to a parent node and updates the data structures. + void appendChildToParentNode(aiNode *pParent, aiNode *pChild); + +private: + //! Data buffer + std::vector<char> m_Buffer; + //! Pointer to root object instance + ObjFile::Object *m_pRootObject; + //! Absolute pathname of model in file system + std::string m_strAbsPath; +}; + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/Obj/ObjFileMtlImporter.cpp b/libs/assimp/code/AssetLib/Obj/ObjFileMtlImporter.cpp new file mode 100644 index 0000000..2441c17 --- /dev/null +++ b/libs/assimp/code/AssetLib/Obj/ObjFileMtlImporter.cpp @@ -0,0 +1,497 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2020, 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. +--------------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER + +#include "ObjFileMtlImporter.h" +#include "ObjFileData.h" +#include "ObjTools.h" +#include <assimp/ParsingUtils.h> +#include <assimp/fast_atof.h> +#include <assimp/material.h> +#include <stdlib.h> +#include <assimp/DefaultLogger.hpp> + +namespace Assimp { + +// Material specific token (case insensitive compare) +static const std::string DiffuseTexture = "map_Kd"; +static const std::string AmbientTexture = "map_Ka"; +static const std::string SpecularTexture = "map_Ks"; +static const std::string OpacityTexture = "map_d"; +static const std::string EmissiveTexture1 = "map_emissive"; +static const std::string EmissiveTexture2 = "map_Ke"; +static const std::string BumpTexture1 = "map_bump"; +static const std::string BumpTexture2 = "bump"; +static const std::string NormalTextureV1 = "map_Kn"; +static const std::string NormalTextureV2 = "norm"; +static const std::string ReflectionTexture = "refl"; +static const std::string DisplacementTexture1 = "map_disp"; +static const std::string DisplacementTexture2 = "disp"; +static const std::string SpecularityTexture = "map_ns"; +static const std::string RoughnessTexture = "map_Pr"; +static const std::string MetallicTexture = "map_Pm"; +static const std::string SheenTexture = "map_Ps"; +static const std::string RMATexture = "map_Ps"; + +// texture option specific token +static const std::string BlendUOption = "-blendu"; +static const std::string BlendVOption = "-blendv"; +static const std::string BoostOption = "-boost"; +static const std::string ModifyMapOption = "-mm"; +static const std::string OffsetOption = "-o"; +static const std::string ScaleOption = "-s"; +static const std::string TurbulenceOption = "-t"; +static const std::string ResolutionOption = "-texres"; +static const std::string ClampOption = "-clamp"; +static const std::string BumpOption = "-bm"; +static const std::string ChannelOption = "-imfchan"; +static const std::string TypeOption = "-type"; + +// ------------------------------------------------------------------- +// Constructor +ObjFileMtlImporter::ObjFileMtlImporter(std::vector<char> &buffer, + const std::string &, + ObjFile::Model *pModel) : + m_DataIt(buffer.begin()), + m_DataItEnd(buffer.end()), + m_pModel(pModel), + m_uiLine(0), + m_buffer() { + ai_assert(nullptr != m_pModel); + m_buffer.resize(BUFFERSIZE); + std::fill(m_buffer.begin(), m_buffer.end(), '\0'); + if (nullptr == m_pModel->m_pDefaultMaterial) { + m_pModel->m_pDefaultMaterial = new ObjFile::Material; + m_pModel->m_pDefaultMaterial->MaterialName.Set("default"); + } + load(); +} + +// ------------------------------------------------------------------- +// Destructor +ObjFileMtlImporter::~ObjFileMtlImporter() { + // empty +} + +// ------------------------------------------------------------------- +// Loads the material description +void ObjFileMtlImporter::load() { + if (m_DataIt == m_DataItEnd) + return; + + while (m_DataIt != m_DataItEnd) { + switch (*m_DataIt) { + case 'k': + case 'K': { + ++m_DataIt; + if (*m_DataIt == 'a') // Ambient color + { + ++m_DataIt; + getColorRGBA(&m_pModel->m_pCurrentMaterial->ambient); + } else if (*m_DataIt == 'd') { + // Diffuse color + ++m_DataIt; + getColorRGBA(&m_pModel->m_pCurrentMaterial->diffuse); + } else if (*m_DataIt == 's') { + ++m_DataIt; + getColorRGBA(&m_pModel->m_pCurrentMaterial->specular); + } else if (*m_DataIt == 'e') { + ++m_DataIt; + getColorRGBA(&m_pModel->m_pCurrentMaterial->emissive); + } + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + } break; + case 'T': { + ++m_DataIt; + // Material transmission color + if (*m_DataIt == 'f') { + ++m_DataIt; + getColorRGBA(&m_pModel->m_pCurrentMaterial->transparent); + } else if (*m_DataIt == 'r') { + // Material transmission alpha value + ++m_DataIt; + ai_real d; + getFloatValue(d); + m_pModel->m_pCurrentMaterial->alpha = static_cast<ai_real>(1.0) - d; + } + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + } break; + case 'd': { + if (*(m_DataIt + 1) == 'i' && *(m_DataIt + 2) == 's' && *(m_DataIt + 3) == 'p') { + // A displacement map + getTexture(); + } else { + // Alpha value + ++m_DataIt; + getFloatValue(m_pModel->m_pCurrentMaterial->alpha); + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + } + } break; + + case 'N': + case 'n': { + ++m_DataIt; + switch (*m_DataIt) { + case 's': // Specular exponent + ++m_DataIt; + getFloatValue(m_pModel->m_pCurrentMaterial->shineness); + break; + case 'i': // Index Of refraction + ++m_DataIt; + getFloatValue(m_pModel->m_pCurrentMaterial->ior); + break; + case 'e': // New material + createMaterial(); + break; + case 'o': // Norm texture + --m_DataIt; + getTexture(); + break; + } + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + } break; + + case 'P': + { + ++m_DataIt; + switch(*m_DataIt) + { + case 'r': + ++m_DataIt; + getFloatValue(m_pModel->m_pCurrentMaterial->roughness); + break; + case 'm': + ++m_DataIt; + getFloatValue(m_pModel->m_pCurrentMaterial->metallic); + break; + case 's': + ++m_DataIt; + getColorRGBA(&m_pModel->m_pCurrentMaterial->sheen); + break; + case 'c': + ++m_DataIt; + if (*m_DataIt == 'r') { + ++m_DataIt; + getFloatValue(m_pModel->m_pCurrentMaterial->clearcoat_roughness); + } else { + getFloatValue(m_pModel->m_pCurrentMaterial->clearcoat_thickness); + } + break; + } + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + } + break; + + case 'm': // Texture + case 'b': // quick'n'dirty - for 'bump' sections + case 'r': // quick'n'dirty - for 'refl' sections + { + getTexture(); + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + } break; + + case 'i': // Illumination model + { + m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); + getIlluminationModel(m_pModel->m_pCurrentMaterial->illumination_model); + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + } break; + + case 'a': // Anisotropy + { + getFloatValue(m_pModel->m_pCurrentMaterial->anisotropy); + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + } break; + + default: { + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + } break; + } + } +} + +// ------------------------------------------------------------------- +// Loads a color definition +void ObjFileMtlImporter::getColorRGBA(aiColor3D *pColor) { + ai_assert(nullptr != pColor); + + ai_real r(0.0), g(0.0), b(0.0); + m_DataIt = getFloat<DataArrayIt>(m_DataIt, m_DataItEnd, r); + pColor->r = r; + + // we have to check if color is default 0 with only one token + if (!IsLineEnd(*m_DataIt)) { + m_DataIt = getFloat<DataArrayIt>(m_DataIt, m_DataItEnd, g); + m_DataIt = getFloat<DataArrayIt>(m_DataIt, m_DataItEnd, b); + } + pColor->g = g; + pColor->b = b; +} + +// ------------------------------------------------------------------- +// Loads the kind of illumination model. +void ObjFileMtlImporter::getIlluminationModel(int &illum_model) { + m_DataIt = CopyNextWord<DataArrayIt>(m_DataIt, m_DataItEnd, &m_buffer[0], BUFFERSIZE); + illum_model = atoi(&m_buffer[0]); +} + +// ------------------------------------------------------------------- +// Loads a single float value. +void ObjFileMtlImporter::getFloatValue(ai_real &value) { + m_DataIt = CopyNextWord<DataArrayIt>(m_DataIt, m_DataItEnd, &m_buffer[0], BUFFERSIZE); + size_t len = std::strlen(&m_buffer[0]); + if (0 == len) { + value = 0.0f; + return; + } + + value = (ai_real)fast_atof(&m_buffer[0]); +} + +// ------------------------------------------------------------------- +// Creates a material from loaded data. +void ObjFileMtlImporter::createMaterial() { + std::string line; + while (!IsLineEnd(*m_DataIt)) { + line += *m_DataIt; + ++m_DataIt; + } + + std::vector<std::string> token; + const unsigned int numToken = tokenize<std::string>(line, token, " \t"); + std::string name; + if (numToken == 1) { + name = AI_DEFAULT_MATERIAL_NAME; + } else { + // skip newmtl and all following white spaces + std::size_t first_ws_pos = line.find_first_of(" \t"); + std::size_t first_non_ws_pos = line.find_first_not_of(" \t", first_ws_pos); + if (first_non_ws_pos != std::string::npos) { + name = line.substr(first_non_ws_pos); + } + } + + name = trim_whitespaces(name); + + std::map<std::string, ObjFile::Material *>::iterator it = m_pModel->m_MaterialMap.find(name); + if (m_pModel->m_MaterialMap.end() == it) { + // New Material created + m_pModel->m_pCurrentMaterial = new ObjFile::Material(); + m_pModel->m_pCurrentMaterial->MaterialName.Set(name); + m_pModel->m_MaterialLib.push_back(name); + m_pModel->m_MaterialMap[name] = m_pModel->m_pCurrentMaterial; + + if (m_pModel->m_pCurrentMesh) { + m_pModel->m_pCurrentMesh->m_uiMaterialIndex = static_cast<unsigned int>(m_pModel->m_MaterialLib.size() - 1); + } + } else { + // Use older material + m_pModel->m_pCurrentMaterial = (*it).second; + } +} + +// ------------------------------------------------------------------- +// Gets a texture name from data. +void ObjFileMtlImporter::getTexture() { + aiString *out(nullptr); + int clampIndex = -1; + + const char *pPtr(&(*m_DataIt)); + if (!ASSIMP_strincmp(pPtr, DiffuseTexture.c_str(), static_cast<unsigned int>(DiffuseTexture.size()))) { + // Diffuse texture + out = &m_pModel->m_pCurrentMaterial->texture; + clampIndex = ObjFile::Material::TextureDiffuseType; + } else if (!ASSIMP_strincmp(pPtr, AmbientTexture.c_str(), static_cast<unsigned int>(AmbientTexture.size()))) { + // Ambient texture + out = &m_pModel->m_pCurrentMaterial->textureAmbient; + clampIndex = ObjFile::Material::TextureAmbientType; + } else if (!ASSIMP_strincmp(pPtr, SpecularTexture.c_str(), static_cast<unsigned int>(SpecularTexture.size()))) { + // Specular texture + out = &m_pModel->m_pCurrentMaterial->textureSpecular; + clampIndex = ObjFile::Material::TextureSpecularType; + } else if (!ASSIMP_strincmp(pPtr, DisplacementTexture1.c_str(), static_cast<unsigned int>(DisplacementTexture1.size())) || + !ASSIMP_strincmp(pPtr, DisplacementTexture2.c_str(), static_cast<unsigned int>(DisplacementTexture2.size()))) { + // Displacement texture + out = &m_pModel->m_pCurrentMaterial->textureDisp; + clampIndex = ObjFile::Material::TextureDispType; + } else if (!ASSIMP_strincmp(pPtr, OpacityTexture.c_str(), static_cast<unsigned int>(OpacityTexture.size()))) { + // Opacity texture + out = &m_pModel->m_pCurrentMaterial->textureOpacity; + clampIndex = ObjFile::Material::TextureOpacityType; + } else if (!ASSIMP_strincmp(pPtr, EmissiveTexture1.c_str(), static_cast<unsigned int>(EmissiveTexture1.size())) || + !ASSIMP_strincmp(pPtr, EmissiveTexture2.c_str(), static_cast<unsigned int>(EmissiveTexture2.size()))) { + // Emissive texture + out = &m_pModel->m_pCurrentMaterial->textureEmissive; + clampIndex = ObjFile::Material::TextureEmissiveType; + } else if (!ASSIMP_strincmp(pPtr, BumpTexture1.c_str(), static_cast<unsigned int>(BumpTexture1.size())) || + !ASSIMP_strincmp(pPtr, BumpTexture2.c_str(), static_cast<unsigned int>(BumpTexture2.size()))) { + // Bump texture + out = &m_pModel->m_pCurrentMaterial->textureBump; + clampIndex = ObjFile::Material::TextureBumpType; + } else if (!ASSIMP_strincmp(pPtr, NormalTextureV1.c_str(), static_cast<unsigned int>(NormalTextureV1.size())) || !ASSIMP_strincmp(pPtr, NormalTextureV2.c_str(), static_cast<unsigned int>(NormalTextureV2.size()))) { + // Normal map + out = &m_pModel->m_pCurrentMaterial->textureNormal; + clampIndex = ObjFile::Material::TextureNormalType; + } else if (!ASSIMP_strincmp(pPtr, ReflectionTexture.c_str(), static_cast<unsigned int>(ReflectionTexture.size()))) { + // Reflection texture(s) + //Do nothing here + return; + } else if (!ASSIMP_strincmp(pPtr, SpecularityTexture.c_str(), static_cast<unsigned int>(SpecularityTexture.size()))) { + // Specularity scaling (glossiness) + out = &m_pModel->m_pCurrentMaterial->textureSpecularity; + clampIndex = ObjFile::Material::TextureSpecularityType; + } else if ( !ASSIMP_strincmp( pPtr, RoughnessTexture.c_str(), static_cast<unsigned int>(RoughnessTexture.size()))) { + // PBR Roughness texture + out = & m_pModel->m_pCurrentMaterial->textureRoughness; + clampIndex = ObjFile::Material::TextureRoughnessType; + } else if ( !ASSIMP_strincmp( pPtr, MetallicTexture.c_str(), static_cast<unsigned int>(MetallicTexture.size()))) { + // PBR Metallic texture + out = & m_pModel->m_pCurrentMaterial->textureMetallic; + clampIndex = ObjFile::Material::TextureMetallicType; + } else if (!ASSIMP_strincmp( pPtr, SheenTexture.c_str(), static_cast<unsigned int>(SheenTexture.size()))) { + // PBR Sheen (reflectance) texture + out = & m_pModel->m_pCurrentMaterial->textureSheen; + clampIndex = ObjFile::Material::TextureSheenType; + } else if (!ASSIMP_strincmp( pPtr, RMATexture.c_str(), static_cast<unsigned int>(RMATexture.size()))) { + // PBR Rough/Metal/AO texture + out = & m_pModel->m_pCurrentMaterial->textureRMA; + clampIndex = ObjFile::Material::TextureRMAType; + } else { + ASSIMP_LOG_ERROR("OBJ/MTL: Encountered unknown texture type"); + return; + } + + bool clamp = false; + getTextureOption(clamp, clampIndex, out); + m_pModel->m_pCurrentMaterial->clamp[clampIndex] = clamp; + + std::string texture; + m_DataIt = getName<DataArrayIt>(m_DataIt, m_DataItEnd, texture); + if (nullptr != out) { + out->Set(texture); + } +} + +/* ///////////////////////////////////////////////////////////////////////////// + * Texture Option + * ///////////////////////////////////////////////////////////////////////////// + * According to http://en.wikipedia.org/wiki/Wavefront_.obj_file#Texture_options + * Texture map statement can contains various texture option, for example: + * + * map_Ka -o 1 1 1 some.png + * map_Kd -clamp on some.png + * + * So we need to parse and skip these options, and leave the last part which is + * the url of image, otherwise we will get a wrong url like "-clamp on some.png". + * + * Because aiMaterial supports clamp option, so we also want to return it + * ///////////////////////////////////////////////////////////////////////////// + */ +void ObjFileMtlImporter::getTextureOption(bool &clamp, int &clampIndex, aiString *&out) { + m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); + + // If there is any more texture option + while (!isEndOfBuffer(m_DataIt, m_DataItEnd) && *m_DataIt == '-') { + const char *pPtr(&(*m_DataIt)); + //skip option key and value + int skipToken = 1; + + if (!ASSIMP_strincmp(pPtr, ClampOption.c_str(), static_cast<unsigned int>(ClampOption.size()))) { + DataArrayIt it = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); + char value[3]; + CopyNextWord(it, m_DataItEnd, value, sizeof(value) / sizeof(*value)); + if (!ASSIMP_strincmp(value, "on", 2)) { + clamp = true; + } + + skipToken = 2; + } else if (!ASSIMP_strincmp(pPtr, TypeOption.c_str(), static_cast<unsigned int>(TypeOption.size()))) { + DataArrayIt it = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); + char value[12]; + CopyNextWord(it, m_DataItEnd, value, sizeof(value) / sizeof(*value)); + if (!ASSIMP_strincmp(value, "cube_top", 8)) { + clampIndex = ObjFile::Material::TextureReflectionCubeTopType; + out = &m_pModel->m_pCurrentMaterial->textureReflection[0]; + } else if (!ASSIMP_strincmp(value, "cube_bottom", 11)) { + clampIndex = ObjFile::Material::TextureReflectionCubeBottomType; + out = &m_pModel->m_pCurrentMaterial->textureReflection[1]; + } else if (!ASSIMP_strincmp(value, "cube_front", 10)) { + clampIndex = ObjFile::Material::TextureReflectionCubeFrontType; + out = &m_pModel->m_pCurrentMaterial->textureReflection[2]; + } else if (!ASSIMP_strincmp(value, "cube_back", 9)) { + clampIndex = ObjFile::Material::TextureReflectionCubeBackType; + out = &m_pModel->m_pCurrentMaterial->textureReflection[3]; + } else if (!ASSIMP_strincmp(value, "cube_left", 9)) { + clampIndex = ObjFile::Material::TextureReflectionCubeLeftType; + out = &m_pModel->m_pCurrentMaterial->textureReflection[4]; + } else if (!ASSIMP_strincmp(value, "cube_right", 10)) { + clampIndex = ObjFile::Material::TextureReflectionCubeRightType; + out = &m_pModel->m_pCurrentMaterial->textureReflection[5]; + } else if (!ASSIMP_strincmp(value, "sphere", 6)) { + clampIndex = ObjFile::Material::TextureReflectionSphereType; + out = &m_pModel->m_pCurrentMaterial->textureReflection[0]; + } + + skipToken = 2; + } else if (!ASSIMP_strincmp(pPtr, BumpOption.c_str(), static_cast<unsigned int>(BumpOption.size()))) { + DataArrayIt it = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); + getFloat(it, m_DataItEnd, m_pModel->m_pCurrentMaterial->bump_multiplier); + skipToken = 2; + } else if (!ASSIMP_strincmp(pPtr, BlendUOption.c_str(), static_cast<unsigned int>(BlendUOption.size())) || !ASSIMP_strincmp(pPtr, BlendVOption.c_str(), static_cast<unsigned int>(BlendVOption.size())) || !ASSIMP_strincmp(pPtr, BoostOption.c_str(), static_cast<unsigned int>(BoostOption.size())) || !ASSIMP_strincmp(pPtr, ResolutionOption.c_str(), static_cast<unsigned int>(ResolutionOption.size())) || !ASSIMP_strincmp(pPtr, ChannelOption.c_str(), static_cast<unsigned int>(ChannelOption.size()))) { + skipToken = 2; + } else if (!ASSIMP_strincmp(pPtr, ModifyMapOption.c_str(), static_cast<unsigned int>(ModifyMapOption.size()))) { + skipToken = 3; + } else if (!ASSIMP_strincmp(pPtr, OffsetOption.c_str(), static_cast<unsigned int>(OffsetOption.size())) || !ASSIMP_strincmp(pPtr, ScaleOption.c_str(), static_cast<unsigned int>(ScaleOption.size())) || !ASSIMP_strincmp(pPtr, TurbulenceOption.c_str(), static_cast<unsigned int>(TurbulenceOption.size()))) { + skipToken = 4; + } + + for (int i = 0; i < skipToken; ++i) { + m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); + } + } +} + +// ------------------------------------------------------------------- + +} // Namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_OBJ_IMPORTER diff --git a/libs/assimp/code/AssetLib/Obj/ObjFileMtlImporter.h b/libs/assimp/code/AssetLib/Obj/ObjFileMtlImporter.h new file mode 100644 index 0000000..cda7415 --- /dev/null +++ b/libs/assimp/code/AssetLib/Obj/ObjFileMtlImporter.h @@ -0,0 +1,113 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, 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. + +----------------------------------------------------------------------*/ +#ifndef OBJFILEMTLIMPORTER_H_INC +#define OBJFILEMTLIMPORTER_H_INC + +#include <assimp/defs.h> +#include <string> +#include <vector> + +struct aiColor3D; +struct aiString; + +namespace Assimp { + +namespace ObjFile { +struct Model; +struct Material; +} // namespace ObjFile + +/** + * @class ObjFileMtlImporter + * @brief Loads the material description from a mtl file. + */ +class ObjFileMtlImporter { +public: + static const size_t BUFFERSIZE = 2048; + using DataArray = std::vector<char>; + using DataArrayIt = std::vector<char>::iterator; + using ConstDataArrayIt = std::vector<char>::const_iterator; + + //! \brief The class default constructor + ObjFileMtlImporter(std::vector<char> &buffer, const std::string &strAbsPath, + ObjFile::Model *pModel); + + //! \brief The class destructor + ~ObjFileMtlImporter(); + + ObjFileMtlImporter(const ObjFileMtlImporter &rOther) = delete; + ObjFileMtlImporter &operator=(const ObjFileMtlImporter &rOther) = delete; + +private: + /// Copy constructor, empty. + /// Load the whole material description + void load(); + /// Get color data. + void getColorRGBA(aiColor3D *pColor); + /// Get illumination model from loaded data + void getIlluminationModel(int &illum_model); + /// Gets a float value from data. + void getFloatValue(ai_real &value); + /// Creates a new material from loaded data. + void createMaterial(); + /// Get texture name from loaded data. + void getTexture(); + void getTextureOption(bool &clamp, int &clampIndex, aiString *&out); + +private: + //! Absolute pathname + std::string m_strAbsPath; + //! Data iterator showing to the current position in data buffer + DataArrayIt m_DataIt; + //! Data iterator to end of buffer + DataArrayIt m_DataItEnd; + //! USed model instance + ObjFile::Model *m_pModel; + //! Current line in file + unsigned int m_uiLine; + //! Helper buffer + std::vector<char> m_buffer; +}; + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif // OBJFILEMTLIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/Obj/ObjFileParser.cpp b/libs/assimp/code/AssetLib/Obj/ObjFileParser.cpp new file mode 100644 index 0000000..2e998a8 --- /dev/null +++ b/libs/assimp/code/AssetLib/Obj/ObjFileParser.cpp @@ -0,0 +1,838 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2020, 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. +--------------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER + +#include "ObjFileParser.h" +#include "ObjFileData.h" +#include "ObjFileMtlImporter.h" +#include "ObjTools.h" +#include <assimp/BaseImporter.h> +#include <assimp/DefaultIOSystem.h> +#include <assimp/ParsingUtils.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> +#include <cstdlib> +#include <memory> +#include <utility> + +namespace Assimp { + +constexpr char ObjFileParser::DEFAULT_MATERIAL[]; + +ObjFileParser::ObjFileParser() : + m_DataIt(), + m_DataItEnd(), + m_pModel(nullptr), + m_uiLine(0), + m_buffer(), + m_pIO(nullptr), + m_progress(nullptr), + m_originalObjFileName() { + std::fill_n(m_buffer, Buffersize, '\0'); +} + +ObjFileParser::ObjFileParser(IOStreamBuffer<char> &streamBuffer, const std::string &modelName, + IOSystem *io, ProgressHandler *progress, + const std::string &originalObjFileName) : + m_DataIt(), + m_DataItEnd(), + m_pModel(nullptr), + m_uiLine(0), + m_buffer(), + m_pIO(io), + m_progress(progress), + m_originalObjFileName(originalObjFileName) { + std::fill_n(m_buffer, Buffersize, '\0'); + + // Create the model instance to store all the data + m_pModel.reset(new ObjFile::Model()); + m_pModel->m_ModelName = modelName; + + // create default material and store it + m_pModel->m_pDefaultMaterial = new ObjFile::Material; + m_pModel->m_pDefaultMaterial->MaterialName.Set(DEFAULT_MATERIAL); + m_pModel->m_MaterialLib.push_back(DEFAULT_MATERIAL); + m_pModel->m_MaterialMap[DEFAULT_MATERIAL] = m_pModel->m_pDefaultMaterial; + + // Start parsing the file + parseFile(streamBuffer); +} + +ObjFileParser::~ObjFileParser() { +} + +void ObjFileParser::setBuffer(std::vector<char> &buffer) { + m_DataIt = buffer.begin(); + m_DataItEnd = buffer.end(); +} + +ObjFile::Model *ObjFileParser::GetModel() const { + return m_pModel.get(); +} + +void ObjFileParser::parseFile(IOStreamBuffer<char> &streamBuffer) { + // only update every 100KB or it'll be too slow + //const unsigned int updateProgressEveryBytes = 100 * 1024; + unsigned int progressCounter = 0; + const unsigned int bytesToProcess = static_cast<unsigned int>(streamBuffer.size()); + const unsigned int progressTotal = bytesToProcess; + unsigned int processed = 0; + size_t lastFilePos(0); + + std::vector<char> buffer; + while (streamBuffer.getNextDataLine(buffer, '\\')) { + m_DataIt = buffer.begin(); + m_DataItEnd = buffer.end(); + + // Handle progress reporting + const size_t filePos(streamBuffer.getFilePos()); + if (lastFilePos < filePos) { + processed = static_cast<unsigned int>(filePos); + lastFilePos = filePos; + progressCounter++; + m_progress->UpdateFileRead(processed, progressTotal); + } + + // parse line + switch (*m_DataIt) { + case 'v': // Parse a vertex texture coordinate + { + ++m_DataIt; + if (*m_DataIt == ' ' || *m_DataIt == '\t') { + size_t numComponents = getNumComponentsInDataDefinition(); + if (numComponents == 3) { + // read in vertex definition + getVector3(m_pModel->m_Vertices); + } else if (numComponents == 4) { + // read in vertex definition (homogeneous coords) + getHomogeneousVector3(m_pModel->m_Vertices); + } else if (numComponents == 6) { + // read vertex and vertex-color + getTwoVectors3(m_pModel->m_Vertices, m_pModel->m_VertexColors); + } + } else if (*m_DataIt == 't') { + // read in texture coordinate ( 2D or 3D ) + ++m_DataIt; + size_t dim = getTexCoordVector(m_pModel->m_TextureCoord); + m_pModel->m_TextureCoordDim = std::max(m_pModel->m_TextureCoordDim, (unsigned int)dim); + } else if (*m_DataIt == 'n') { + // Read in normal vector definition + ++m_DataIt; + getVector3(m_pModel->m_Normals); + } + } break; + + case 'p': // Parse a face, line or point statement + case 'l': + case 'f': { + getFace(*m_DataIt == 'f' ? aiPrimitiveType_POLYGON : (*m_DataIt == 'l' ? aiPrimitiveType_LINE : aiPrimitiveType_POINT)); + } break; + + case '#': // Parse a comment + { + getComment(); + } break; + + case 'u': // Parse a material desc. setter + { + std::string name; + + getNameNoSpace(m_DataIt, m_DataItEnd, name); + + size_t nextSpace = name.find(' '); + if (nextSpace != std::string::npos) + name = name.substr(0, nextSpace); + + if (name == "usemtl") { + getMaterialDesc(); + } + } break; + + case 'm': // Parse a material library or merging group ('mg') + { + std::string name; + + getNameNoSpace(m_DataIt, m_DataItEnd, name); + + size_t nextSpace = name.find(' '); + if (nextSpace != std::string::npos) + name = name.substr(0, nextSpace); + + if (name == "mg") + getGroupNumberAndResolution(); + else if (name == "mtllib") + getMaterialLib(); + else + goto pf_skip_line; + } break; + + case 'g': // Parse group name + { + getGroupName(); + } break; + + case 's': // Parse group number + { + getGroupNumber(); + } break; + + case 'o': // Parse object name + { + getObjectName(); + } break; + + default: { + pf_skip_line: + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + } break; + } + } +} + +void ObjFileParser::copyNextWord(char *pBuffer, size_t length) { + size_t index = 0; + m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd); + if (*m_DataIt == '\\') { + ++m_DataIt; + ++m_DataIt; + m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd); + } + while (m_DataIt != m_DataItEnd && !IsSpaceOrNewLine(*m_DataIt)) { + pBuffer[index] = *m_DataIt; + index++; + if (index == length - 1) { + break; + } + ++m_DataIt; + } + + ai_assert(index < length); + pBuffer[index] = '\0'; +} + +static bool isDataDefinitionEnd(const char *tmp) { + if (*tmp == '\\') { + tmp++; + if (IsLineEnd(*tmp)) { + return true; + } + } + return false; +} + +static bool isNanOrInf(const char *in) { + // Look for "nan" or "inf", case insensitive + return ((in[0] == 'N' || in[0] == 'n') && ASSIMP_strincmp(in, "nan", 3) == 0) || + ((in[0] == 'I' || in[0] == 'i') && ASSIMP_strincmp(in, "inf", 3) == 0); +} + +size_t ObjFileParser::getNumComponentsInDataDefinition() { + size_t numComponents(0); + const char *tmp(&m_DataIt[0]); + bool end_of_definition = false; + while (!end_of_definition) { + if (isDataDefinitionEnd(tmp)) { + tmp += 2; + } else if (IsLineEnd(*tmp)) { + end_of_definition = true; + } + if (!SkipSpaces(&tmp)) { + break; + } + const bool isNum(IsNumeric(*tmp) || isNanOrInf(tmp)); + SkipToken(tmp); + if (isNum) { + ++numComponents; + } + if (!SkipSpaces(&tmp)) { + break; + } + } + return numComponents; +} + +size_t ObjFileParser::getTexCoordVector(std::vector<aiVector3D> &point3d_array) { + size_t numComponents = getNumComponentsInDataDefinition(); + ai_real x, y, z; + if (2 == numComponents) { + copyNextWord(m_buffer, Buffersize); + x = (ai_real)fast_atof(m_buffer); + + copyNextWord(m_buffer, Buffersize); + y = (ai_real)fast_atof(m_buffer); + z = 0.0; + } else if (3 == numComponents) { + copyNextWord(m_buffer, Buffersize); + x = (ai_real)fast_atof(m_buffer); + + copyNextWord(m_buffer, Buffersize); + y = (ai_real)fast_atof(m_buffer); + + copyNextWord(m_buffer, Buffersize); + z = (ai_real)fast_atof(m_buffer); + } else { + throw DeadlyImportError("OBJ: Invalid number of components"); + } + + // Coerce nan and inf to 0 as is the OBJ default value + if (!std::isfinite(x)) + x = 0; + + if (!std::isfinite(y)) + y = 0; + + if (!std::isfinite(z)) + z = 0; + + point3d_array.emplace_back(x, y, z); + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + return numComponents; +} + +void ObjFileParser::getVector3(std::vector<aiVector3D> &point3d_array) { + ai_real x, y, z; + copyNextWord(m_buffer, Buffersize); + x = (ai_real)fast_atof(m_buffer); + + copyNextWord(m_buffer, Buffersize); + y = (ai_real)fast_atof(m_buffer); + + copyNextWord(m_buffer, Buffersize); + z = (ai_real)fast_atof(m_buffer); + + point3d_array.emplace_back(x, y, z); + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); +} + +void ObjFileParser::getHomogeneousVector3(std::vector<aiVector3D> &point3d_array) { + ai_real x, y, z, w; + copyNextWord(m_buffer, Buffersize); + x = (ai_real)fast_atof(m_buffer); + + copyNextWord(m_buffer, Buffersize); + y = (ai_real)fast_atof(m_buffer); + + copyNextWord(m_buffer, Buffersize); + z = (ai_real)fast_atof(m_buffer); + + copyNextWord(m_buffer, Buffersize); + w = (ai_real)fast_atof(m_buffer); + + if (w == 0) + throw DeadlyImportError("OBJ: Invalid component in homogeneous vector (Division by zero)"); + + point3d_array.emplace_back(x / w, y / w, z / w); + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); +} + +void ObjFileParser::getTwoVectors3(std::vector<aiVector3D> &point3d_array_a, std::vector<aiVector3D> &point3d_array_b) { + ai_real x, y, z; + copyNextWord(m_buffer, Buffersize); + x = (ai_real)fast_atof(m_buffer); + + copyNextWord(m_buffer, Buffersize); + y = (ai_real)fast_atof(m_buffer); + + copyNextWord(m_buffer, Buffersize); + z = (ai_real)fast_atof(m_buffer); + + point3d_array_a.emplace_back(x, y, z); + + copyNextWord(m_buffer, Buffersize); + x = (ai_real)fast_atof(m_buffer); + + copyNextWord(m_buffer, Buffersize); + y = (ai_real)fast_atof(m_buffer); + + copyNextWord(m_buffer, Buffersize); + z = (ai_real)fast_atof(m_buffer); + + point3d_array_b.emplace_back(x, y, z); + + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); +} + +void ObjFileParser::getVector2(std::vector<aiVector2D> &point2d_array) { + ai_real x, y; + copyNextWord(m_buffer, Buffersize); + x = (ai_real)fast_atof(m_buffer); + + copyNextWord(m_buffer, Buffersize); + y = (ai_real)fast_atof(m_buffer); + + point2d_array.emplace_back(x, y); + + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); +} + +static const std::string DefaultObjName = "defaultobject"; + +void ObjFileParser::getFace(aiPrimitiveType type) { + m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); + if (m_DataIt == m_DataItEnd || *m_DataIt == '\0') { + return; + } + + ObjFile::Face *face = new ObjFile::Face(type); + bool hasNormal = false; + + const int vSize = static_cast<unsigned int>(m_pModel->m_Vertices.size()); + const int vtSize = static_cast<unsigned int>(m_pModel->m_TextureCoord.size()); + const int vnSize = static_cast<unsigned int>(m_pModel->m_Normals.size()); + + const bool vt = (!m_pModel->m_TextureCoord.empty()); + const bool vn = (!m_pModel->m_Normals.empty()); + int iPos = 0; + while (m_DataIt != m_DataItEnd) { + int iStep = 1; + + if (IsLineEnd(*m_DataIt)) { + break; + } + + if (*m_DataIt == '/') { + if (type == aiPrimitiveType_POINT) { + ASSIMP_LOG_ERROR("Obj: Separator unexpected in point statement"); + } + iPos++; + } else if (IsSpaceOrNewLine(*m_DataIt)) { + iPos = 0; + } else { + //OBJ USES 1 Base ARRAYS!!!! + const int iVal(::atoi(&(*m_DataIt))); + + // increment iStep position based off of the sign and # of digits + int tmp = iVal; + if (iVal < 0) { + ++iStep; + } + while ((tmp = tmp / 10) != 0) { + ++iStep; + } + + if (iPos == 1 && !vt && vn) + iPos = 2; // skip texture coords for normals if there are no tex coords + + if (iVal > 0) { + // Store parsed index + if (0 == iPos) { + face->m_vertices.push_back(iVal - 1); + } else if (1 == iPos) { + face->m_texturCoords.push_back(iVal - 1); + } else if (2 == iPos) { + face->m_normals.push_back(iVal - 1); + hasNormal = true; + } else { + reportErrorTokenInFace(); + } + } else if (iVal < 0) { + // Store relatively index + if (0 == iPos) { + face->m_vertices.push_back(vSize + iVal); + } else if (1 == iPos) { + face->m_texturCoords.push_back(vtSize + iVal); + } else if (2 == iPos) { + face->m_normals.push_back(vnSize + iVal); + hasNormal = true; + } else { + reportErrorTokenInFace(); + } + } else { + //On error, std::atoi will return 0 which is not a valid value + delete face; + throw DeadlyImportError("OBJ: Invalid face indice"); + } + } + m_DataIt += iStep; + } + + if (face->m_vertices.empty()) { + ASSIMP_LOG_ERROR("Obj: Ignoring empty face"); + // skip line and clean up + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + delete face; + return; + } + + // Set active material, if one set + if (nullptr != m_pModel->m_pCurrentMaterial) { + face->m_pMaterial = m_pModel->m_pCurrentMaterial; + } else { + face->m_pMaterial = m_pModel->m_pDefaultMaterial; + } + + // Create a default object, if nothing is there + if (nullptr == m_pModel->m_pCurrent) { + createObject(DefaultObjName); + } + + // Assign face to mesh + if (nullptr == m_pModel->m_pCurrentMesh) { + createMesh(DefaultObjName); + } + + // Store the face + m_pModel->m_pCurrentMesh->m_Faces.push_back(face); + m_pModel->m_pCurrentMesh->m_uiNumIndices += (unsigned int)face->m_vertices.size(); + m_pModel->m_pCurrentMesh->m_uiUVCoordinates[0] += (unsigned int)face->m_texturCoords.size(); + if (!m_pModel->m_pCurrentMesh->m_hasNormals && hasNormal) { + m_pModel->m_pCurrentMesh->m_hasNormals = true; + } + // Skip the rest of the line + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); +} + +void ObjFileParser::getMaterialDesc() { + // Get next data for material data + m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); + if (m_DataIt == m_DataItEnd) { + return; + } + + char *pStart = &(*m_DataIt); + while (m_DataIt != m_DataItEnd && !IsLineEnd(*m_DataIt)) { + ++m_DataIt; + } + + // In some cases we should ignore this 'usemtl' command, this variable helps us to do so + bool skip = false; + + // Get name + std::string strName(pStart, &(*m_DataIt)); + strName = trim_whitespaces(strName); + if (strName.empty()) + skip = true; + + // If the current mesh has the same material, we simply ignore that 'usemtl' command + // There is no need to create another object or even mesh here + if (m_pModel->m_pCurrentMaterial && m_pModel->m_pCurrentMaterial->MaterialName == aiString(strName)) { + skip = true; + } + + if (!skip) { + // Search for material + std::map<std::string, ObjFile::Material *>::iterator it = m_pModel->m_MaterialMap.find(strName); + if (it == m_pModel->m_MaterialMap.end()) { + // Not found, so we don't know anything about the material except for its name. + // This may be the case if the material library is missing. We don't want to lose all + // materials if that happens, so create a new named material instead of discarding it + // completely. + ASSIMP_LOG_ERROR("OBJ: failed to locate material ", strName, ", creating new material"); + m_pModel->m_pCurrentMaterial = new ObjFile::Material(); + m_pModel->m_pCurrentMaterial->MaterialName.Set(strName); + m_pModel->m_MaterialLib.push_back(strName); + m_pModel->m_MaterialMap[strName] = m_pModel->m_pCurrentMaterial; + } else { + // Found, using detected material + m_pModel->m_pCurrentMaterial = (*it).second; + } + + if (needsNewMesh(strName)) { + createMesh(strName); + } + + m_pModel->m_pCurrentMesh->m_uiMaterialIndex = getMaterialIndex(strName); + } + + // Skip rest of line + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); +} + +// ------------------------------------------------------------------- +// Get a comment, values will be skipped +void ObjFileParser::getComment() { + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); +} + +// ------------------------------------------------------------------- +// Get material library from file. +void ObjFileParser::getMaterialLib() { + // Translate tuple + m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); + if (m_DataIt == m_DataItEnd) { + return; + } + + char *pStart = &(*m_DataIt); + while (m_DataIt != m_DataItEnd && !IsLineEnd(*m_DataIt)) { + ++m_DataIt; + } + + // Check for existence + const std::string strMatName(pStart, &(*m_DataIt)); + std::string absName; + + // Check if directive is valid. + if (0 == strMatName.length()) { + ASSIMP_LOG_WARN("OBJ: no name for material library specified."); + return; + } + + if (m_pIO->StackSize() > 0) { + std::string path = m_pIO->CurrentDirectory(); + if ('/' != *path.rbegin()) { + path += '/'; + } + absName += path; + absName += strMatName; + } else { + absName = strMatName; + } + + IOStream *pFile = m_pIO->Open(absName); + if (nullptr == pFile) { + ASSIMP_LOG_ERROR("OBJ: Unable to locate material file ", strMatName); + std::string strMatFallbackName = m_originalObjFileName.substr(0, m_originalObjFileName.length() - 3) + "mtl"; + ASSIMP_LOG_INFO("OBJ: Opening fallback material file ", strMatFallbackName); + pFile = m_pIO->Open(strMatFallbackName); + if (!pFile) { + ASSIMP_LOG_ERROR("OBJ: Unable to locate fallback material file ", strMatFallbackName); + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + return; + } + } + + // Import material library data from file. + // Some exporters (e.g. Silo) will happily write out empty + // material files if the model doesn't use any materials, so we + // allow that. + std::vector<char> buffer; + BaseImporter::TextFileToBuffer(pFile, buffer, BaseImporter::ALLOW_EMPTY); + m_pIO->Close(pFile); + + // Importing the material library + ObjFileMtlImporter mtlImporter(buffer, strMatName, m_pModel.get()); +} + +// ------------------------------------------------------------------- +// Set a new material definition as the current material. +void ObjFileParser::getNewMaterial() { + m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); + m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd); + if (m_DataIt == m_DataItEnd) { + return; + } + + char *pStart = &(*m_DataIt); + std::string strMat(pStart, *m_DataIt); + while (m_DataIt != m_DataItEnd && IsSpaceOrNewLine(*m_DataIt)) { + ++m_DataIt; + } + std::map<std::string, ObjFile::Material *>::iterator it = m_pModel->m_MaterialMap.find(strMat); + if (it == m_pModel->m_MaterialMap.end()) { + // Show a warning, if material was not found + ASSIMP_LOG_WARN("OBJ: Unsupported material requested: ", strMat); + m_pModel->m_pCurrentMaterial = m_pModel->m_pDefaultMaterial; + } else { + // Set new material + if (needsNewMesh(strMat)) { + createMesh(strMat); + } + m_pModel->m_pCurrentMesh->m_uiMaterialIndex = getMaterialIndex(strMat); + } + + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); +} + +// ------------------------------------------------------------------- +int ObjFileParser::getMaterialIndex(const std::string &strMaterialName) { + int mat_index = -1; + if (strMaterialName.empty()) { + return mat_index; + } + for (size_t index = 0; index < m_pModel->m_MaterialLib.size(); ++index) { + if (strMaterialName == m_pModel->m_MaterialLib[index]) { + mat_index = (int)index; + break; + } + } + return mat_index; +} + +// ------------------------------------------------------------------- +// Getter for a group name. +void ObjFileParser::getGroupName() { + std::string groupName; + + // here we skip 'g ' from line + m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); + m_DataIt = getName<DataArrayIt>(m_DataIt, m_DataItEnd, groupName); + if (isEndOfBuffer(m_DataIt, m_DataItEnd)) { + return; + } + + // Change active group, if necessary + if (m_pModel->m_strActiveGroup != groupName) { + // Search for already existing entry + ObjFile::Model::ConstGroupMapIt it = m_pModel->m_Groups.find(groupName); + + // We are mapping groups into the object structure + createObject(groupName); + + // New group name, creating a new entry + if (it == m_pModel->m_Groups.end()) { + std::vector<unsigned int> *pFaceIDArray = new std::vector<unsigned int>; + m_pModel->m_Groups[groupName] = pFaceIDArray; + m_pModel->m_pGroupFaceIDs = (pFaceIDArray); + } else { + m_pModel->m_pGroupFaceIDs = (*it).second; + } + m_pModel->m_strActiveGroup = groupName; + } + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); +} + +// ------------------------------------------------------------------- +// Not supported +void ObjFileParser::getGroupNumber() { + // Not used + + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); +} + +// ------------------------------------------------------------------- +// Not supported +void ObjFileParser::getGroupNumberAndResolution() { + // Not used + + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); +} + +// ------------------------------------------------------------------- +// Stores values for a new object instance, name will be used to +// identify it. +void ObjFileParser::getObjectName() { + m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); + if (m_DataIt == m_DataItEnd) { + return; + } + char *pStart = &(*m_DataIt); + while (m_DataIt != m_DataItEnd && !IsSpaceOrNewLine(*m_DataIt)) { + ++m_DataIt; + } + + std::string strObjectName(pStart, &(*m_DataIt)); + if (!strObjectName.empty()) { + // Reset current object + m_pModel->m_pCurrent = nullptr; + + // Search for actual object + for (std::vector<ObjFile::Object *>::const_iterator it = m_pModel->m_Objects.begin(); + it != m_pModel->m_Objects.end(); + ++it) { + if ((*it)->m_strObjName == strObjectName) { + m_pModel->m_pCurrent = *it; + break; + } + } + + // Allocate a new object, if current one was not found before + if (nullptr == m_pModel->m_pCurrent) { + createObject(strObjectName); + } + } + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); +} +// ------------------------------------------------------------------- +// Creates a new object instance +void ObjFileParser::createObject(const std::string &objName) { + ai_assert(nullptr != m_pModel); + + m_pModel->m_pCurrent = new ObjFile::Object; + m_pModel->m_pCurrent->m_strObjName = objName; + m_pModel->m_Objects.push_back(m_pModel->m_pCurrent); + + createMesh(objName); + + if (m_pModel->m_pCurrentMaterial) { + m_pModel->m_pCurrentMesh->m_uiMaterialIndex = + getMaterialIndex(m_pModel->m_pCurrentMaterial->MaterialName.data); + m_pModel->m_pCurrentMesh->m_pMaterial = m_pModel->m_pCurrentMaterial; + } +} +// ------------------------------------------------------------------- +// Creates a new mesh +void ObjFileParser::createMesh(const std::string &meshName) { + ai_assert(nullptr != m_pModel); + + m_pModel->m_pCurrentMesh = new ObjFile::Mesh(meshName); + m_pModel->m_Meshes.push_back(m_pModel->m_pCurrentMesh); + unsigned int meshId = static_cast<unsigned int>(m_pModel->m_Meshes.size() - 1); + if (nullptr != m_pModel->m_pCurrent) { + m_pModel->m_pCurrent->m_Meshes.push_back(meshId); + } else { + ASSIMP_LOG_ERROR("OBJ: No object detected to attach a new mesh instance."); + } +} + +// ------------------------------------------------------------------- +// Returns true, if a new mesh must be created. +bool ObjFileParser::needsNewMesh(const std::string &materialName) { + // If no mesh data yet + if (m_pModel->m_pCurrentMesh == nullptr) { + return true; + } + bool newMat = false; + int matIdx = getMaterialIndex(materialName); + int curMatIdx = m_pModel->m_pCurrentMesh->m_uiMaterialIndex; + if (curMatIdx != int(ObjFile::Mesh::NoMaterial) && curMatIdx != matIdx + // no need create a new mesh if no faces in current + // lets say 'usemtl' goes straight after 'g' + && !m_pModel->m_pCurrentMesh->m_Faces.empty()) { + // New material -> only one material per mesh, so we need to create a new + // material + newMat = true; + } + return newMat; +} + +// ------------------------------------------------------------------- +// Shows an error in parsing process. +void ObjFileParser::reportErrorTokenInFace() { + m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); + ASSIMP_LOG_ERROR("OBJ: Not supported token in face description detected"); +} + +// ------------------------------------------------------------------- + +} // Namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_OBJ_IMPORTER diff --git a/libs/assimp/code/AssetLib/Obj/ObjFileParser.h b/libs/assimp/code/AssetLib/Obj/ObjFileParser.h new file mode 100644 index 0000000..fbd2f2c --- /dev/null +++ b/libs/assimp/code/AssetLib/Obj/ObjFileParser.h @@ -0,0 +1,165 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, 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. + +---------------------------------------------------------------------- +*/ +#ifndef OBJ_FILEPARSER_H_INC +#define OBJ_FILEPARSER_H_INC + +#include <assimp/IOStreamBuffer.h> +#include <assimp/material.h> +#include <assimp/mesh.h> +#include <assimp/vector2.h> +#include <assimp/vector3.h> +#include <map> +#include <memory> +#include <string> +#include <vector> + +namespace Assimp { + +namespace ObjFile { +struct Model; +struct Object; +struct Material; +struct Point3; +struct Point2; +} // namespace ObjFile + +class ObjFileImporter; +class IOSystem; +class ProgressHandler; + +/// \class ObjFileParser +/// \brief Parser for a obj waveform file +class ASSIMP_API ObjFileParser { +public: + static const size_t Buffersize = 4096; + typedef std::vector<char> DataArray; + typedef std::vector<char>::iterator DataArrayIt; + typedef std::vector<char>::const_iterator ConstDataArrayIt; + + /// @brief The default constructor. + ObjFileParser(); + /// @brief Constructor with data array. + ObjFileParser(IOStreamBuffer<char> &streamBuffer, const std::string &modelName, IOSystem *io, ProgressHandler *progress, const std::string &originalObjFileName); + /// @brief Destructor + ~ObjFileParser(); + /// @brief If you want to load in-core data. + void setBuffer(std::vector<char> &buffer); + /// @brief Model getter. + ObjFile::Model *GetModel() const; + + ObjFileParser(const ObjFileParser&) = delete; + ObjFileParser &operator=(const ObjFileParser& ) = delete; + +protected: + /// Parse the loaded file + void parseFile(IOStreamBuffer<char> &streamBuffer); + /// Method to copy the new delimited word in the current line. + void copyNextWord(char *pBuffer, size_t length); + /// Method to copy the new line. + // void copyNextLine(char *pBuffer, size_t length); + /// Get the number of components in a line. + size_t getNumComponentsInDataDefinition(); + /// Stores the vector + size_t getTexCoordVector(std::vector<aiVector3D> &point3d_array); + /// Stores the following 3d vector. + void getVector3(std::vector<aiVector3D> &point3d_array); + /// Stores the following homogeneous vector as a 3D vector + void getHomogeneousVector3(std::vector<aiVector3D> &point3d_array); + /// Stores the following two 3d vectors on the line. + void getTwoVectors3(std::vector<aiVector3D> &point3d_array_a, std::vector<aiVector3D> &point3d_array_b); + /// Stores the following 3d vector. + void getVector2(std::vector<aiVector2D> &point2d_array); + /// Stores the following face. + void getFace(aiPrimitiveType type); + /// Reads the material description. + void getMaterialDesc(); + /// Gets a comment. + void getComment(); + /// Gets a a material library. + void getMaterialLib(); + /// Creates a new material. + void getNewMaterial(); + /// Gets the group name from file. + void getGroupName(); + /// Gets the group number from file. + void getGroupNumber(); + /// Gets the group number and resolution from file. + void getGroupNumberAndResolution(); + /// Returns the index of the material. Is -1 if not material was found. + int getMaterialIndex(const std::string &strMaterialName); + /// Parse object name + void getObjectName(); + /// Creates a new object. + void createObject(const std::string &strObjectName); + /// Creates a new mesh. + void createMesh(const std::string &meshName); + /// Returns true, if a new mesh instance must be created. + bool needsNewMesh(const std::string &rMaterialName); + /// Error report in token + void reportErrorTokenInFace(); + +private: + // Copy and assignment constructor should be private + // because the class contains pointer to allocated memory + + /// Default material name + static constexpr char DEFAULT_MATERIAL[] = AI_DEFAULT_MATERIAL_NAME; + //! Iterator to current position in buffer + DataArrayIt m_DataIt; + //! Iterator to end position of buffer + DataArrayIt m_DataItEnd; + //! Pointer to model instance + std::unique_ptr<ObjFile::Model> m_pModel; + //! Current line (for debugging) + unsigned int m_uiLine; + //! Helper buffer + char m_buffer[Buffersize]; + /// Pointer to IO system instance. + IOSystem *m_pIO; + //! Pointer to progress handler + ProgressHandler *m_progress; + /// Path to the current model, name of the obj file where the buffer comes from + const std::string m_originalObjFileName; +}; + +} // Namespace Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/Obj/ObjTools.h b/libs/assimp/code/AssetLib/Obj/ObjTools.h new file mode 100644 index 0000000..9e57a1c --- /dev/null +++ b/libs/assimp/code/AssetLib/Obj/ObjTools.h @@ -0,0 +1,284 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2021, 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 ObjTools.h + * @brief Some helpful templates for text parsing + */ +#ifndef OBJ_TOOLS_H_INC +#define OBJ_TOOLS_H_INC + +#include <assimp/ParsingUtils.h> +#include <assimp/fast_atof.h> +#include <vector> + +namespace Assimp { + +/** + * @brief Returns true, if the last entry of the buffer is reached. + * @param[in] it Iterator of current position. + * @param[in] end Iterator with end of buffer. + * @return true, if the end of the buffer is reached. + */ +template <class char_t> +inline bool isEndOfBuffer(char_t it, char_t end) { + if (it == end) { + return true; + } + --end; + + return (it == end); +} + +/** + * @brief Returns next word separated by a space + * @param[in] pBuffer Pointer to data buffer + * @param[in] pEnd Pointer to end of buffer + * @return Pointer to next space + */ +template <class Char_T> +inline Char_T getNextWord(Char_T pBuffer, Char_T pEnd) { + while (!isEndOfBuffer(pBuffer, pEnd)) { + if (!IsSpaceOrNewLine(*pBuffer) || IsLineEnd(*pBuffer)) { + break; + } + ++pBuffer; + } + + return pBuffer; +} + +/** + * @brief Returns pointer a next token + * @param[in] pBuffer Pointer to data buffer + * @param[in] pEnd Pointer to end of buffer + * @return Pointer to next token + */ +template <class Char_T> +inline Char_T getNextToken(Char_T pBuffer, Char_T pEnd) { + while (!isEndOfBuffer(pBuffer, pEnd)) { + if (IsSpaceOrNewLine(*pBuffer)) { + break; + } + ++pBuffer; + } + return getNextWord(pBuffer, pEnd); +} + +/** + * @brief Skips a line + * @param[in] it Iterator set to current position + * @param[in] end Iterator set to end of scratch buffer for readout + * @param[out] uiLine Current line number in format + * @return Current-iterator with new position + */ +template <class char_t> +inline char_t skipLine(char_t it, char_t end, unsigned int &uiLine) { + while (!isEndOfBuffer(it, end) && !IsLineEnd(*it)) { + ++it; + } + + if (it != end) { + ++it; + ++uiLine; + } + // fix .. from time to time there are spaces at the beginning of a material line + while (it != end && (*it == '\t' || *it == ' ')) { + ++it; + } + + return it; +} + +/** + * @brief Get a name from the current line. Preserve space in the middle, + * but trim it at the end. + * @param[in] it set to current position + * @param[in] end set to end of scratch buffer for readout + * @param[out] name Separated name + * @return Current-iterator with new position + */ +template <class char_t> +inline char_t getName(char_t it, char_t end, std::string &name) { + name = ""; + if (isEndOfBuffer(it, end)) { + return end; + } + + char *pStart = &(*it); + while (!isEndOfBuffer(it, end) && !IsLineEnd(*it)) { + ++it; + } + + while (IsSpace(*it)) { + --it; + } + // Get name + // if there is no name, and the previous char is a separator, come back to start + while (&(*it) < pStart) { + ++it; + } + std::string strName(pStart, &(*it)); + if (!strName.empty()) { + name = strName; + } + + + return it; +} + +/** + * @brief Get a name from the current line. Do not preserve space + * in the middle, but trim it at the end. + * @param it set to current position + * @param end set to end of scratch buffer for readout + * @param name Separated name + * @return Current-iterator with new position + */ +template <class char_t> +inline char_t getNameNoSpace(char_t it, char_t end, std::string &name) { + name = ""; + if (isEndOfBuffer(it, end)) { + return end; + } + + char *pStart = &(*it); + while (!isEndOfBuffer(it, end) && !IsLineEnd(*it) && !IsSpaceOrNewLine(*it)) { + ++it; + } + + while (isEndOfBuffer(it, end) || IsLineEnd(*it) || IsSpaceOrNewLine(*it)) { + --it; + } + ++it; + + // Get name + // if there is no name, and the previous char is a separator, come back to start + while (&(*it) < pStart) { + ++it; + } + std::string strName(pStart, &(*it)); + if (!strName.empty()) { + name = strName; + } + + return it; +} + +/** + * @brief Get next word from given line + * @param[in] it set to current position + * @param[in] end set to end of scratch buffer for readout + * @param[in] pBuffer Buffer for next word + * @param[in] length Buffer length + * @return Current-iterator with new position + */ +template <class char_t> +inline char_t CopyNextWord(char_t it, char_t end, char *pBuffer, size_t length) { + size_t index = 0; + it = getNextWord<char_t>(it, end); + while (!IsSpaceOrNewLine(*it) && !isEndOfBuffer(it, end)) { + pBuffer[index] = *it; + ++index; + if (index == length - 1) { + break; + } + ++it; + } + pBuffer[index] = '\0'; + return it; +} + +/** + * @brief Get next float from given line + * @param[in] it set to current position + * @param[in] end set to end of scratch buffer for readout + * @param[out] value Separated float value. + * @return Current-iterator with new position + */ +template <class char_t> +inline char_t getFloat(char_t it, char_t end, ai_real &value) { + static const size_t BUFFERSIZE = 1024; + char buffer[BUFFERSIZE]; + it = CopyNextWord<char_t>(it, end, buffer, BUFFERSIZE); + value = (ai_real)fast_atof(buffer); + + return it; +} + +/** + * @brief Will remove white-spaces for a string. + * @param[in] str The string to clean + * @return The trimmed string. + */ +template <class string_type> +inline string_type trim_whitespaces(string_type str) { + while (!str.empty() && IsSpace(str[0])) { + str.erase(0); + } + while (!str.empty() && IsSpace(str[str.length() - 1])) { + str.erase(str.length() - 1); + } + return str; +} + +/** + * @brief Checks for a line-end. + * @param[in] it Current iterator in string. + * @param[in] end End of the string. + * @return The trimmed string. + */ +template <class T> +bool hasLineEnd(T it, T end) { + bool hasLineEnd = false; + while (!isEndOfBuffer(it, end)) { + ++it; + if (IsLineEnd(it)) { + hasLineEnd = true; + break; + } + } + + return hasLineEnd; +} + +} // Namespace Assimp + +#endif // OBJ_TOOLS_H_INC diff --git a/libs/assimp/code/AssetLib/Ogre/OgreBinarySerializer.cpp b/libs/assimp/code/AssetLib/Ogre/OgreBinarySerializer.cpp new file mode 100644 index 0000000..738e118 --- /dev/null +++ b/libs/assimp/code/AssetLib/Ogre/OgreBinarySerializer.cpp @@ -0,0 +1,973 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#include "OgreBinarySerializer.h" +#include "OgreParsingUtils.h" +#include "OgreXmlSerializer.h" + +#include <assimp/TinyFormatter.h> +#include <assimp/DefaultLogger.hpp> + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + +// Define as 1 to get verbose logging. +#define OGRE_BINARY_SERIALIZER_DEBUG 0 + +namespace Assimp { +namespace Ogre { + +static constexpr auto MESH_VERSION_1_8 = "[MeshSerializer_v1.8]"; +static constexpr auto SKELETON_VERSION_1_8 = "[Serializer_v1.80]"; +static constexpr auto SKELETON_VERSION_1_1 = "[Serializer_v1.10]"; + +const unsigned short HEADER_CHUNK_ID = 0x1000; + +const long MSTREAM_OVERHEAD_SIZE = sizeof(uint16_t) + sizeof(uint32_t); +const long MSTREAM_BONE_SIZE_WITHOUT_SCALE = MSTREAM_OVERHEAD_SIZE + sizeof(unsigned short) + (sizeof(float) * 7); +const long MSTREAM_KEYFRAME_SIZE_WITHOUT_SCALE = MSTREAM_OVERHEAD_SIZE + (sizeof(float) * 8); + +template <> +inline bool OgreBinarySerializer::Read<bool>() { + return (m_reader->GetU1() > 0); +} + +template <> +inline char OgreBinarySerializer::Read<char>() { + return static_cast<char>(m_reader->GetU1()); +} + +template <> +inline uint8_t OgreBinarySerializer::Read<uint8_t>() { + return m_reader->GetU1(); +} + +template <> +inline uint16_t OgreBinarySerializer::Read<uint16_t>() { + return m_reader->GetU2(); +} + +template <> +inline uint32_t OgreBinarySerializer::Read<uint32_t>() { + return m_reader->GetU4(); +} + +template <> +inline float OgreBinarySerializer::Read<float>() { + return m_reader->GetF4(); +} + +void OgreBinarySerializer::ReadBytes(char *dest, size_t numBytes) { + ReadBytes(static_cast<void *>(dest), numBytes); +} + +void OgreBinarySerializer::ReadBytes(uint8_t *dest, size_t numBytes) { + ReadBytes(static_cast<void *>(dest), numBytes); +} + +void OgreBinarySerializer::ReadBytes(void *dest, size_t numBytes) { + m_reader->CopyAndAdvance(dest, numBytes); +} + +uint8_t *OgreBinarySerializer::ReadBytes(size_t numBytes) { + uint8_t *bytes = new uint8_t[numBytes]; + ReadBytes(bytes, numBytes); + return bytes; +} + +void OgreBinarySerializer::ReadVector(aiVector3D &vec) { + m_reader->CopyAndAdvance(&vec.x, sizeof(float) * 3); +} + +void OgreBinarySerializer::ReadQuaternion(aiQuaternion &quat) { + float temp[4]; + m_reader->CopyAndAdvance(temp, sizeof(float) * 4); + quat.x = temp[0]; + quat.y = temp[1]; + quat.z = temp[2]; + quat.w = temp[3]; +} + +bool OgreBinarySerializer::AtEnd() const { + return (m_reader->GetRemainingSize() == 0); +} + +std::string OgreBinarySerializer::ReadString(size_t len) { + std::string str; + str.resize(len); + ReadBytes(&str[0], len); + return str; +} + +std::string OgreBinarySerializer::ReadLine() { + std::string str; + while (!AtEnd()) { + char c = Read<char>(); + if (c == '\n') + break; + str += c; + } + return str; +} + +uint16_t OgreBinarySerializer::ReadHeader(bool readLen) { + uint16_t id = Read<uint16_t>(); + if (readLen) + m_currentLen = Read<uint32_t>(); + +#if (OGRE_BINARY_SERIALIZER_DEBUG == 1) + if (id != HEADER_CHUNK_ID) { + ASSIMP_LOG_DEBUG((assetMode == AM_Mesh ? MeshHeaderToString(static_cast<MeshChunkId>(id)) : SkeletonHeaderToString(static_cast<SkeletonChunkId>(id)))); + } +#endif + + return id; +} + +void OgreBinarySerializer::RollbackHeader() { + m_reader->IncPtr(-MSTREAM_OVERHEAD_SIZE); +} + +void OgreBinarySerializer::SkipBytes(size_t numBytes) { +#if (OGRE_BINARY_SERIALIZER_DEBUG == 1) + ASSIMP_LOG_VERBOSE_DEBUG("Skipping ", numBytes, " bytes"); +#endif + + m_reader->IncPtr(numBytes); +} + +// Mesh + +Mesh *OgreBinarySerializer::ImportMesh(MemoryStreamReader *stream) { + OgreBinarySerializer serializer(stream, OgreBinarySerializer::AM_Mesh); + + uint16_t id = serializer.ReadHeader(false); + if (id != HEADER_CHUNK_ID) { + throw DeadlyImportError("Invalid Ogre Mesh file header."); + } + + /// @todo Check what we can actually support. + std::string version = serializer.ReadLine(); + if (version != MESH_VERSION_1_8) { + throw DeadlyImportError("Mesh version ", version, " not supported by this importer. Run OgreMeshUpgrader tool on the file and try again.", + " Supported versions: ", MESH_VERSION_1_8); + } + + Mesh *mesh = new Mesh(); + while (!serializer.AtEnd()) { + id = serializer.ReadHeader(); + switch (id) { + case M_MESH: { + serializer.ReadMesh(mesh); + break; + } + } + } + return mesh; +} + +void OgreBinarySerializer::ReadMesh(Mesh *mesh) { + mesh->hasSkeletalAnimations = Read<bool>(); + + ASSIMP_LOG_VERBOSE_DEBUG("Reading Mesh"); + ASSIMP_LOG_VERBOSE_DEBUG(" - Skeletal animations: ", mesh->hasSkeletalAnimations ? "true" : "false"); + + if (!AtEnd()) { + uint16_t id = ReadHeader(); + while (!AtEnd() && + (id == M_GEOMETRY || + id == M_SUBMESH || + id == M_MESH_SKELETON_LINK || + id == M_MESH_BONE_ASSIGNMENT || + id == M_MESH_LOD || + id == M_MESH_BOUNDS || + id == M_SUBMESH_NAME_TABLE || + id == M_EDGE_LISTS || + id == M_POSES || + id == M_ANIMATIONS || + id == M_TABLE_EXTREMES)) { + switch (id) { + case M_GEOMETRY: { + mesh->sharedVertexData = new VertexData(); + ReadGeometry(mesh->sharedVertexData); + break; + } + case M_SUBMESH: { + ReadSubMesh(mesh); + break; + } + case M_MESH_SKELETON_LINK: { + ReadMeshSkeletonLink(mesh); + break; + } + case M_MESH_BONE_ASSIGNMENT: { + ReadBoneAssignment(mesh->sharedVertexData); + break; + } + case M_MESH_LOD: { + ReadMeshLodInfo(mesh); + break; + } + case M_MESH_BOUNDS: { + ReadMeshBounds(mesh); + break; + } + case M_SUBMESH_NAME_TABLE: { + ReadSubMeshNames(mesh); + break; + } + case M_EDGE_LISTS: { + ReadEdgeList(mesh); + break; + } + case M_POSES: { + ReadPoses(mesh); + break; + } + case M_ANIMATIONS: { + ReadAnimations(mesh); + break; + } + case M_TABLE_EXTREMES: { + ReadMeshExtremes(mesh); + break; + } + } + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } + + NormalizeBoneWeights(mesh->sharedVertexData); +} + +void OgreBinarySerializer::ReadMeshLodInfo(Mesh *mesh) { + // Assimp does not acknowledge LOD levels as far as I can see it. This info is just skipped. + // @todo Put this stuff to scene/mesh custom properties. If manual mesh the app can use the information. + ReadLine(); // strategy name + uint16_t numLods = Read<uint16_t>(); + bool manual = Read<bool>(); + + /// @note Main mesh is considered as LOD 0, start from index 1. + for (size_t i = 1; i < numLods; ++i) { + uint16_t id = ReadHeader(); + if (id != M_MESH_LOD_USAGE) { + throw DeadlyImportError("M_MESH_LOD does not contain a M_MESH_LOD_USAGE for each LOD level"); + } + + m_reader->IncPtr(sizeof(float)); // user value + + if (manual) { + id = ReadHeader(); + if (id != M_MESH_LOD_MANUAL) { + throw DeadlyImportError("Manual M_MESH_LOD_USAGE does not contain M_MESH_LOD_MANUAL"); + } + + ReadLine(); // manual mesh name (ref to another mesh) + } else { + for (size_t si = 0, silen = mesh->NumSubMeshes(); si < silen; ++si) { + id = ReadHeader(); + if (id != M_MESH_LOD_GENERATED) { + throw DeadlyImportError("Generated M_MESH_LOD_USAGE does not contain M_MESH_LOD_GENERATED"); + } + + uint32_t indexCount = Read<uint32_t>(); + bool is32bit = Read<bool>(); + + if (indexCount > 0) { + uint32_t len = indexCount * (is32bit ? sizeof(uint32_t) : sizeof(uint16_t)); + m_reader->IncPtr(len); + } + } + } + } +} + +void OgreBinarySerializer::ReadMeshSkeletonLink(Mesh *mesh) { + mesh->skeletonRef = ReadLine(); +} + +void OgreBinarySerializer::ReadMeshBounds(Mesh * /*mesh*/) { + // Skip bounds, not compatible with Assimp. + // 2x float vec3 + 1x float sphere radius + SkipBytes(sizeof(float) * 7); +} + +void OgreBinarySerializer::ReadMeshExtremes(Mesh * /*mesh*/) { + // Skip extremes, not compatible with Assimp. + size_t numBytes = m_currentLen - MSTREAM_OVERHEAD_SIZE; + SkipBytes(numBytes); +} + +void OgreBinarySerializer::ReadBoneAssignment(VertexData *dest) { + if (!dest) { + throw DeadlyImportError("Cannot read bone assignments, vertex data is null."); + } + + VertexBoneAssignment ba; + ba.vertexIndex = Read<uint32_t>(); + ba.boneIndex = Read<uint16_t>(); + ba.weight = Read<float>(); + + dest->boneAssignments.push_back(ba); +} + +void OgreBinarySerializer::ReadSubMesh(Mesh *mesh) { + uint16_t id = 0; + + SubMesh *submesh = new SubMesh(); + submesh->materialRef = ReadLine(); + submesh->usesSharedVertexData = Read<bool>(); + + submesh->indexData->count = Read<uint32_t>(); + submesh->indexData->faceCount = static_cast<uint32_t>(submesh->indexData->count / 3); + submesh->indexData->is32bit = Read<bool>(); + + ASSIMP_LOG_VERBOSE_DEBUG("Reading SubMesh ", mesh->subMeshes.size()); + ASSIMP_LOG_VERBOSE_DEBUG(" - Material: '", submesh->materialRef, "'"); + ASSIMP_LOG_VERBOSE_DEBUG(" - Uses shared geometry: ", submesh->usesSharedVertexData ? "true" : "false"); + + // Index buffer + if (submesh->indexData->count > 0) { + uint32_t numBytes = submesh->indexData->count * (submesh->indexData->is32bit ? sizeof(uint32_t) : sizeof(uint16_t)); + uint8_t *indexBuffer = ReadBytes(numBytes); + submesh->indexData->buffer = MemoryStreamPtr(new Assimp::MemoryIOStream(indexBuffer, numBytes, true)); + + ASSIMP_LOG_VERBOSE_DEBUG(" - ", submesh->indexData->faceCount, + " faces from ", submesh->indexData->count, (submesh->indexData->is32bit ? " 32bit" : " 16bit"), + " indexes of ", numBytes, " bytes"); + } + + // Vertex buffer if not referencing the shared geometry + if (!submesh->usesSharedVertexData) { + id = ReadHeader(); + if (id != M_GEOMETRY) { + throw DeadlyImportError("M_SUBMESH does not contain M_GEOMETRY, but shader geometry is set to false"); + } + + submesh->vertexData = new VertexData(); + ReadGeometry(submesh->vertexData); + } + + // Bone assignment, submesh operation and texture aliases + if (!AtEnd()) { + id = ReadHeader(); + while (!AtEnd() && + (id == M_SUBMESH_OPERATION || + id == M_SUBMESH_BONE_ASSIGNMENT || + id == M_SUBMESH_TEXTURE_ALIAS)) { + switch (id) { + case M_SUBMESH_OPERATION: { + ReadSubMeshOperation(submesh); + break; + } + case M_SUBMESH_BONE_ASSIGNMENT: { + ReadBoneAssignment(submesh->vertexData); + break; + } + case M_SUBMESH_TEXTURE_ALIAS: { + ReadSubMeshTextureAlias(submesh); + break; + } + } + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } + + NormalizeBoneWeights(submesh->vertexData); + + submesh->index = static_cast<unsigned int>(mesh->subMeshes.size()); + mesh->subMeshes.push_back(submesh); +} + +void OgreBinarySerializer::NormalizeBoneWeights(VertexData *vertexData) const { + if (!vertexData || vertexData->boneAssignments.empty()) + return; + + std::set<uint32_t> influencedVertices; + for (VertexBoneAssignmentList::const_iterator baIter = vertexData->boneAssignments.begin(), baEnd = vertexData->boneAssignments.end(); baIter != baEnd; ++baIter) { + influencedVertices.insert(baIter->vertexIndex); + } + + /** Normalize bone weights. + Some exporters won't care if the sum of all bone weights + for a single vertex equals 1 or not, so validate here. */ + const float epsilon = 0.05f; + for (const uint32_t vertexIndex : influencedVertices) { + float sum = 0.0f; + for (VertexBoneAssignmentList::const_iterator baIter = vertexData->boneAssignments.begin(), baEnd = vertexData->boneAssignments.end(); baIter != baEnd; ++baIter) { + if (baIter->vertexIndex == vertexIndex) + sum += baIter->weight; + } + if ((sum < (1.0f - epsilon)) || (sum > (1.0f + epsilon))) { + for (auto &boneAssign : vertexData->boneAssignments) { + if (boneAssign.vertexIndex == vertexIndex) + boneAssign.weight /= sum; + } + } + } +} + +void OgreBinarySerializer::ReadSubMeshOperation(SubMesh *submesh) { + submesh->operationType = static_cast<SubMesh::OperationType>(Read<uint16_t>()); +} + +void OgreBinarySerializer::ReadSubMeshTextureAlias(SubMesh *submesh) { + submesh->textureAliasName = ReadLine(); + submesh->textureAliasRef = ReadLine(); +} + +void OgreBinarySerializer::ReadSubMeshNames(Mesh *mesh) { + uint16_t id = 0; + + if (!AtEnd()) { + id = ReadHeader(); + while (!AtEnd() && id == M_SUBMESH_NAME_TABLE_ELEMENT) { + uint16_t submeshIndex = Read<uint16_t>(); + SubMesh *submesh = mesh->GetSubMesh(submeshIndex); + if (!submesh) { + throw DeadlyImportError("Ogre Mesh does not include submesh ", submeshIndex, " referenced in M_SUBMESH_NAME_TABLE_ELEMENT. Invalid mesh file."); + } + + submesh->name = ReadLine(); + ASSIMP_LOG_VERBOSE_DEBUG(" - SubMesh ", submesh->index, " name '", submesh->name, "'"); + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } +} + +void OgreBinarySerializer::ReadGeometry(VertexData *dest) { + dest->count = Read<uint32_t>(); + + ASSIMP_LOG_VERBOSE_DEBUG(" - Reading geometry of ", dest->count, " vertices"); + + if (!AtEnd()) { + uint16_t id = ReadHeader(); + while (!AtEnd() && + (id == M_GEOMETRY_VERTEX_DECLARATION || + id == M_GEOMETRY_VERTEX_BUFFER)) { + switch (id) { + case M_GEOMETRY_VERTEX_DECLARATION: { + ReadGeometryVertexDeclaration(dest); + break; + } + case M_GEOMETRY_VERTEX_BUFFER: { + ReadGeometryVertexBuffer(dest); + break; + } + } + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } +} + +void OgreBinarySerializer::ReadGeometryVertexDeclaration(VertexData *dest) { + if (!AtEnd()) { + uint16_t id = ReadHeader(); + while (!AtEnd() && id == M_GEOMETRY_VERTEX_ELEMENT) { + ReadGeometryVertexElement(dest); + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } +} + +void OgreBinarySerializer::ReadGeometryVertexElement(VertexData *dest) { + VertexElement element; + element.source = Read<uint16_t>(); + element.type = static_cast<VertexElement::Type>(Read<uint16_t>()); + element.semantic = static_cast<VertexElement::Semantic>(Read<uint16_t>()); + element.offset = Read<uint16_t>(); + element.index = Read<uint16_t>(); + + ASSIMP_LOG_VERBOSE_DEBUG(" - Vertex element ", element.SemanticToString(), " of type ", + element.TypeToString(), " index=", element.index, " source=", element.source); + + dest->vertexElements.push_back(element); +} + +void OgreBinarySerializer::ReadGeometryVertexBuffer(VertexData *dest) { + uint16_t bindIndex = Read<uint16_t>(); + uint16_t vertexSize = Read<uint16_t>(); + + uint16_t id = ReadHeader(); + if (id != M_GEOMETRY_VERTEX_BUFFER_DATA) + throw DeadlyImportError("M_GEOMETRY_VERTEX_BUFFER_DATA not found in M_GEOMETRY_VERTEX_BUFFER"); + + if (dest->VertexSize(bindIndex) != vertexSize) + throw DeadlyImportError("Vertex buffer size does not agree with vertex declaration in M_GEOMETRY_VERTEX_BUFFER"); + + size_t numBytes = dest->count * vertexSize; + uint8_t *vertexBuffer = ReadBytes(numBytes); + dest->vertexBindings[bindIndex] = MemoryStreamPtr(new Assimp::MemoryIOStream(vertexBuffer, numBytes, true)); + + ASSIMP_LOG_VERBOSE_DEBUG(" - Read vertex buffer for source ", bindIndex, " of ", numBytes, " bytes"); +} + +void OgreBinarySerializer::ReadEdgeList(Mesh * /*mesh*/) { + // Assimp does not acknowledge LOD levels as far as I can see it. This info is just skipped. + + if (!AtEnd()) { + uint16_t id = ReadHeader(); + while (!AtEnd() && id == M_EDGE_LIST_LOD) { + m_reader->IncPtr(sizeof(uint16_t)); // lod index + bool manual = Read<bool>(); + + if (!manual) { + m_reader->IncPtr(sizeof(uint8_t)); + uint32_t numTriangles = Read<uint32_t>(); + uint32_t numEdgeGroups = Read<uint32_t>(); + + size_t skipBytes = (sizeof(uint32_t) * 8 + sizeof(float) * 4) * numTriangles; + m_reader->IncPtr(skipBytes); + + for (size_t i = 0; i < numEdgeGroups; ++i) { + uint16_t curId = ReadHeader(); + if (curId != M_EDGE_GROUP) + throw DeadlyImportError("M_EDGE_GROUP not found in M_EDGE_LIST_LOD"); + + m_reader->IncPtr(sizeof(uint32_t) * 3); + uint32_t numEdges = Read<uint32_t>(); + for (size_t j = 0; j < numEdges; ++j) { + m_reader->IncPtr(sizeof(uint32_t) * 6 + sizeof(uint8_t)); + } + } + } + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } +} + +void OgreBinarySerializer::ReadPoses(Mesh *mesh) { + if (!AtEnd()) { + uint16_t id = ReadHeader(); + while (!AtEnd() && id == M_POSE) { + Pose *pose = new Pose(); + pose->name = ReadLine(); + pose->target = Read<uint16_t>(); + pose->hasNormals = Read<bool>(); + + ReadPoseVertices(pose); + + mesh->poses.push_back(pose); + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } +} + +void OgreBinarySerializer::ReadPoseVertices(Pose *pose) { + if (!AtEnd()) { + uint16_t id = ReadHeader(); + while (!AtEnd() && id == M_POSE_VERTEX) { + Pose::Vertex v; + v.index = Read<uint32_t>(); + ReadVector(v.offset); + if (pose->hasNormals) + ReadVector(v.normal); + + pose->vertices[v.index] = v; + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } +} + +void OgreBinarySerializer::ReadAnimations(Mesh *mesh) { + if (!AtEnd()) { + uint16_t id = ReadHeader(); + while (!AtEnd() && id == M_ANIMATION) { + Animation *anim = new Animation(mesh); + anim->name = ReadLine(); + anim->length = Read<float>(); + + ReadAnimation(anim); + + mesh->animations.push_back(anim); + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } +} + +void OgreBinarySerializer::ReadAnimation(Animation *anim) { + if (!AtEnd()) { + uint16_t id = ReadHeader(); + if (id == M_ANIMATION_BASEINFO) { + anim->baseName = ReadLine(); + anim->baseTime = Read<float>(); + + // Advance to first track + id = ReadHeader(); + } + + while (!AtEnd() && id == M_ANIMATION_TRACK) { + VertexAnimationTrack track; + track.type = static_cast<VertexAnimationTrack::Type>(Read<uint16_t>()); + track.target = Read<uint16_t>(); + + ReadAnimationKeyFrames(anim, &track); + + anim->tracks.push_back(track); + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } +} + +void OgreBinarySerializer::ReadAnimationKeyFrames(Animation *anim, VertexAnimationTrack *track) { + if (!AtEnd()) { + uint16_t id = ReadHeader(); + while (!AtEnd() && + (id == M_ANIMATION_MORPH_KEYFRAME || + id == M_ANIMATION_POSE_KEYFRAME)) { + if (id == M_ANIMATION_MORPH_KEYFRAME) { + MorphKeyFrame kf; + kf.timePos = Read<float>(); + bool hasNormals = Read<bool>(); + + size_t vertexCount = anim->AssociatedVertexData(track)->count; + size_t vertexSize = sizeof(float) * (hasNormals ? 6 : 3); + size_t numBytes = vertexCount * vertexSize; + + uint8_t *morphBuffer = ReadBytes(numBytes); + kf.buffer = MemoryStreamPtr(new Assimp::MemoryIOStream(morphBuffer, numBytes, true)); + + track->morphKeyFrames.push_back(kf); + } else if (id == M_ANIMATION_POSE_KEYFRAME) { + PoseKeyFrame kf; + kf.timePos = Read<float>(); + + if (!AtEnd()) { + id = ReadHeader(); + while (!AtEnd() && id == M_ANIMATION_POSE_REF) { + PoseRef pr; + pr.index = Read<uint16_t>(); + pr.influence = Read<float>(); + kf.references.push_back(pr); + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } + + track->poseKeyFrames.push_back(kf); + } + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } +} + +// Skeleton + +bool OgreBinarySerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, Mesh *mesh) { + if (!mesh || mesh->skeletonRef.empty()) + return false; + + // Highly unusual to see in read world cases but support + // binary mesh referencing a XML skeleton file. + if (EndsWith(mesh->skeletonRef, ".skeleton.xml", false)) { + OgreXmlSerializer::ImportSkeleton(pIOHandler, mesh); + return false; + } + + MemoryStreamReaderPtr reader = OpenReader(pIOHandler, mesh->skeletonRef); + if (!reader) + return false; + + Skeleton *skeleton = new Skeleton(); + OgreBinarySerializer serializer(reader.get(), OgreBinarySerializer::AM_Skeleton); + serializer.ReadSkeleton(skeleton); + mesh->skeleton = skeleton; + return true; +} + +bool OgreBinarySerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh) { + if (!mesh || mesh->skeletonRef.empty()) + return false; + + MemoryStreamReaderPtr reader = OpenReader(pIOHandler, mesh->skeletonRef); + if (!reader.get()) + return false; + + Skeleton *skeleton = new Skeleton(); + OgreBinarySerializer serializer(reader.get(), OgreBinarySerializer::AM_Skeleton); + serializer.ReadSkeleton(skeleton); + mesh->skeleton = skeleton; + return true; +} + +MemoryStreamReaderPtr OgreBinarySerializer::OpenReader(Assimp::IOSystem *pIOHandler, const std::string &filename) { + if (!EndsWith(filename, ".skeleton", false)) { + ASSIMP_LOG_ERROR("Imported Mesh is referencing to unsupported '", filename, "' skeleton file."); + return MemoryStreamReaderPtr(); + } + + if (!pIOHandler->Exists(filename)) { + ASSIMP_LOG_ERROR("Failed to find skeleton file '", filename, "' that is referenced by imported Mesh."); + return MemoryStreamReaderPtr(); + } + + IOStream *f = pIOHandler->Open(filename, "rb"); + if (!f) { + throw DeadlyImportError("Failed to open skeleton file ", filename); + } + + return MemoryStreamReaderPtr(new MemoryStreamReader(f)); +} + +void OgreBinarySerializer::ReadSkeleton(Skeleton *skeleton) { + uint16_t id = ReadHeader(false); + if (id != HEADER_CHUNK_ID) { + throw DeadlyImportError("Invalid Ogre Skeleton file header."); + } + + // This deserialization supports both versions of the skeleton spec + std::string version = ReadLine(); + if (version != SKELETON_VERSION_1_8 && version != SKELETON_VERSION_1_1) { + throw DeadlyImportError("Skeleton version ", version, " not supported by this importer.", + " Supported versions: ", SKELETON_VERSION_1_8, " and ", SKELETON_VERSION_1_1); + } + + ASSIMP_LOG_VERBOSE_DEBUG("Reading Skeleton"); + + bool firstBone = true; + bool firstAnim = true; + + while (!AtEnd()) { + id = ReadHeader(); + switch (id) { + case SKELETON_BLENDMODE: { + skeleton->blendMode = static_cast<Skeleton::BlendMode>(Read<uint16_t>()); + break; + } + case SKELETON_BONE: { + if (firstBone) { + ASSIMP_LOG_VERBOSE_DEBUG(" - Bones"); + firstBone = false; + } + + ReadBone(skeleton); + break; + } + case SKELETON_BONE_PARENT: { + ReadBoneParent(skeleton); + break; + } + case SKELETON_ANIMATION: { + if (firstAnim) { + ASSIMP_LOG_VERBOSE_DEBUG(" - Animations"); + firstAnim = false; + } + + ReadSkeletonAnimation(skeleton); + break; + } + case SKELETON_ANIMATION_LINK: { + ReadSkeletonAnimationLink(skeleton); + break; + } + } + } + + // Calculate bone matrices for root bones. Recursively calculates their children. + for (size_t i = 0, len = skeleton->bones.size(); i < len; ++i) { + Bone *bone = skeleton->bones[i]; + if (!bone->IsParented()) + bone->CalculateWorldMatrixAndDefaultPose(skeleton); + } +} + +void OgreBinarySerializer::ReadBone(Skeleton *skeleton) { + Bone *bone = new Bone(); + bone->name = ReadLine(); + bone->id = Read<uint16_t>(); + + // Pos and rot + ReadVector(bone->position); + ReadQuaternion(bone->rotation); + + // Scale (optional) + if (m_currentLen > MSTREAM_BONE_SIZE_WITHOUT_SCALE) + ReadVector(bone->scale); + + // Bone indexes need to start from 0 and be contiguous + if (bone->id != skeleton->bones.size()) { + throw DeadlyImportError("Ogre Skeleton bone indexes not contiguous. Error at bone index ", bone->id); + } + + ASSIMP_LOG_VERBOSE_DEBUG(" ", bone->id, " ", bone->name); + + skeleton->bones.push_back(bone); +} + +void OgreBinarySerializer::ReadBoneParent(Skeleton *skeleton) { + uint16_t childId = Read<uint16_t>(); + uint16_t parentId = Read<uint16_t>(); + + Bone *child = skeleton->BoneById(childId); + Bone *parent = skeleton->BoneById(parentId); + + if (child && parent) + parent->AddChild(child); + else + throw DeadlyImportError("Failed to find bones for parenting: Child id ", childId, " for parent id ", parentId); +} + +void OgreBinarySerializer::ReadSkeletonAnimation(Skeleton *skeleton) { + Animation *anim = new Animation(skeleton); + anim->name = ReadLine(); + anim->length = Read<float>(); + + if (!AtEnd()) { + uint16_t id = ReadHeader(); + if (id == SKELETON_ANIMATION_BASEINFO) { + anim->baseName = ReadLine(); + anim->baseTime = Read<float>(); + + // Advance to first track + id = ReadHeader(); + } + + while (!AtEnd() && id == SKELETON_ANIMATION_TRACK) { + ReadSkeletonAnimationTrack(skeleton, anim); + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } + + skeleton->animations.push_back(anim); + + ASSIMP_LOG_VERBOSE_DEBUG(" ", anim->name, " (", anim->length, " sec, ", anim->tracks.size(), " tracks)"); +} + +void OgreBinarySerializer::ReadSkeletonAnimationTrack(Skeleton * /*skeleton*/, Animation *dest) { + uint16_t boneId = Read<uint16_t>(); + Bone *bone = dest->parentSkeleton->BoneById(boneId); + if (!bone) { + throw DeadlyImportError("Cannot read animation track, target bone ", boneId, " not in target Skeleton"); + } + + VertexAnimationTrack track; + track.type = VertexAnimationTrack::VAT_TRANSFORM; + track.boneName = bone->name; + + uint16_t id = ReadHeader(); + while (!AtEnd() && id == SKELETON_ANIMATION_TRACK_KEYFRAME) { + ReadSkeletonAnimationKeyFrame(&track); + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + + dest->tracks.push_back(track); +} + +void OgreBinarySerializer::ReadSkeletonAnimationKeyFrame(VertexAnimationTrack *dest) { + TransformKeyFrame keyframe; + keyframe.timePos = Read<float>(); + + // Rot and pos + ReadQuaternion(keyframe.rotation); + ReadVector(keyframe.position); + + // Scale (optional) + if (m_currentLen > MSTREAM_KEYFRAME_SIZE_WITHOUT_SCALE) + ReadVector(keyframe.scale); + + dest->transformKeyFrames.push_back(keyframe); +} + +void OgreBinarySerializer::ReadSkeletonAnimationLink(Skeleton * /*skeleton*/) { + // Skip bounds, not compatible with Assimp. + ReadLine(); // skeleton name + SkipBytes(sizeof(float) * 3); // scale +} + +} // namespace Ogre +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER diff --git a/libs/assimp/code/AssetLib/Ogre/OgreBinarySerializer.h b/libs/assimp/code/AssetLib/Ogre/OgreBinarySerializer.h new file mode 100644 index 0000000..eb026ea --- /dev/null +++ b/libs/assimp/code/AssetLib/Ogre/OgreBinarySerializer.h @@ -0,0 +1,415 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef AI_OGREBINARYSERIALIZER_H_INC +#define AI_OGREBINARYSERIALIZER_H_INC + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + +#include "OgreStructs.h" +#include <assimp/StreamReader.h> + +namespace Assimp { +namespace Ogre { + +typedef Assimp::StreamReaderLE MemoryStreamReader; +typedef std::shared_ptr<MemoryStreamReader> MemoryStreamReaderPtr; + +class OgreBinarySerializer { +public: + /// Imports mesh and returns the result. + /** @note Fatal unrecoverable errors will throw a DeadlyImportError. */ + static Mesh *ImportMesh(MemoryStreamReader *reader); + + /// Imports skeleton to @c mesh into Mesh::skeleton. + /** If mesh does not have a skeleton reference or the skeleton file + cannot be found it is not a fatal DeadlyImportError. + @return If skeleton import was successful. */ + static bool ImportSkeleton(Assimp::IOSystem *pIOHandler, Mesh *mesh); + static bool ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh); + +private: + enum AssetMode { + AM_Mesh, + AM_Skeleton + }; + + OgreBinarySerializer(MemoryStreamReader *reader, AssetMode mode) : + m_currentLen(0), + m_reader(reader), + assetMode(mode) { + } + + static MemoryStreamReaderPtr OpenReader(Assimp::IOSystem *pIOHandler, const std::string &filename); + + // Header + + uint16_t ReadHeader(bool readLen = true); + void RollbackHeader(); + + // Mesh + + void ReadMesh(Mesh *mesh); + void ReadMeshLodInfo(Mesh *mesh); + void ReadMeshSkeletonLink(Mesh *mesh); + void ReadMeshBounds(Mesh *mesh); + void ReadMeshExtremes(Mesh *mesh); + + void ReadSubMesh(Mesh *mesh); + void ReadSubMeshNames(Mesh *mesh); + void ReadSubMeshOperation(SubMesh *submesh); + void ReadSubMeshTextureAlias(SubMesh *submesh); + + void ReadBoneAssignment(VertexData *dest); + + void ReadGeometry(VertexData *dest); + void ReadGeometryVertexDeclaration(VertexData *dest); + void ReadGeometryVertexElement(VertexData *dest); + void ReadGeometryVertexBuffer(VertexData *dest); + + void ReadEdgeList(Mesh *mesh); + void ReadPoses(Mesh *mesh); + void ReadPoseVertices(Pose *pose); + + void ReadAnimations(Mesh *mesh); + void ReadAnimation(Animation *anim); + void ReadAnimationKeyFrames(Animation *anim, VertexAnimationTrack *track); + + void NormalizeBoneWeights(VertexData *vertexData) const; + + // Skeleton + + void ReadSkeleton(Skeleton *skeleton); + + void ReadBone(Skeleton *skeleton); + void ReadBoneParent(Skeleton *skeleton); + + void ReadSkeletonAnimation(Skeleton *skeleton); + void ReadSkeletonAnimationTrack(Skeleton *skeleton, Animation *dest); + void ReadSkeletonAnimationKeyFrame(VertexAnimationTrack *dest); + void ReadSkeletonAnimationLink(Skeleton *skeleton); + + // Reader utils + bool AtEnd() const; + + template <typename T> + inline T Read(); + + void ReadBytes(char *dest, size_t numBytes); + void ReadBytes(uint8_t *dest, size_t numBytes); + void ReadBytes(void *dest, size_t numBytes); + uint8_t *ReadBytes(size_t numBytes); + + void ReadVector(aiVector3D &vec); + void ReadQuaternion(aiQuaternion &quat); + + std::string ReadString(size_t len); + std::string ReadLine(); + + void SkipBytes(size_t numBytes); + + uint32_t m_currentLen; + MemoryStreamReader *m_reader; + + AssetMode assetMode; +}; + +enum MeshChunkId { + M_HEADER = 0x1000, + // char* version : Version number check + M_MESH = 0x3000, + // bool skeletallyAnimated // important flag which affects h/w buffer policies + // Optional M_GEOMETRY chunk + M_SUBMESH = 0x4000, + // char* materialName + // bool useSharedVertices + // unsigned int indexCount + // bool indexes32Bit + // unsigned int* faceVertexIndices (indexCount) + // OR + // unsigned short* faceVertexIndices (indexCount) + // M_GEOMETRY chunk (Optional: present only if useSharedVertices = false) + M_SUBMESH_OPERATION = 0x4010, // optional, trilist assumed if missing + // unsigned short operationType + M_SUBMESH_BONE_ASSIGNMENT = 0x4100, + // Optional bone weights (repeating section) + // unsigned int vertexIndex; + // unsigned short boneIndex; + // float weight; + // Optional chunk that matches a texture name to an alias + // a texture alias is sent to the submesh material to use this texture name + // instead of the one in the texture unit with a matching alias name + M_SUBMESH_TEXTURE_ALIAS = 0x4200, // Repeating section + // char* aliasName; + // char* textureName; + + M_GEOMETRY = 0x5000, // NB this chunk is embedded within M_MESH and M_SUBMESH + // unsigned int vertexCount + M_GEOMETRY_VERTEX_DECLARATION = 0x5100, + M_GEOMETRY_VERTEX_ELEMENT = 0x5110, // Repeating section + // unsigned short source; // buffer bind source + // unsigned short type; // VertexElementType + // unsigned short semantic; // VertexElementSemantic + // unsigned short offset; // start offset in buffer in bytes + // unsigned short index; // index of the semantic (for colours and texture coords) + M_GEOMETRY_VERTEX_BUFFER = 0x5200, // Repeating section + // unsigned short bindIndex; // Index to bind this buffer to + // unsigned short vertexSize; // Per-vertex size, must agree with declaration at this index + M_GEOMETRY_VERTEX_BUFFER_DATA = 0x5210, + // raw buffer data + M_MESH_SKELETON_LINK = 0x6000, + // Optional link to skeleton + // char* skeletonName : name of .skeleton to use + M_MESH_BONE_ASSIGNMENT = 0x7000, + // Optional bone weights (repeating section) + // unsigned int vertexIndex; + // unsigned short boneIndex; + // float weight; + M_MESH_LOD = 0x8000, + // Optional LOD information + // string strategyName; + // unsigned short numLevels; + // bool manual; (true for manual alternate meshes, false for generated) + M_MESH_LOD_USAGE = 0x8100, + // Repeating section, ordered in increasing depth + // NB LOD 0 (full detail from 0 depth) is omitted + // LOD value - this is a distance, a pixel count etc, based on strategy + // float lodValue; + M_MESH_LOD_MANUAL = 0x8110, + // Required if M_MESH_LOD section manual = true + // String manualMeshName; + M_MESH_LOD_GENERATED = 0x8120, + // Required if M_MESH_LOD section manual = false + // Repeating section (1 per submesh) + // unsigned int indexCount; + // bool indexes32Bit + // unsigned short* faceIndexes; (indexCount) + // OR + // unsigned int* faceIndexes; (indexCount) + M_MESH_BOUNDS = 0x9000, + // float minx, miny, minz + // float maxx, maxy, maxz + // float radius + + // Added By DrEvil + // optional chunk that contains a table of submesh indexes and the names of + // the sub-meshes. + M_SUBMESH_NAME_TABLE = 0xA000, + // Subchunks of the name table. Each chunk contains an index & string + M_SUBMESH_NAME_TABLE_ELEMENT = 0xA100, + // short index + // char* name + // Optional chunk which stores precomputed edge data + M_EDGE_LISTS = 0xB000, + // Each LOD has a separate edge list + M_EDGE_LIST_LOD = 0xB100, + // unsigned short lodIndex + // bool isManual // If manual, no edge data here, loaded from manual mesh + // bool isClosed + // unsigned long numTriangles + // unsigned long numEdgeGroups + // Triangle* triangleList + // unsigned long indexSet + // unsigned long vertexSet + // unsigned long vertIndex[3] + // unsigned long sharedVertIndex[3] + // float normal[4] + + M_EDGE_GROUP = 0xB110, + // unsigned long vertexSet + // unsigned long triStart + // unsigned long triCount + // unsigned long numEdges + // Edge* edgeList + // unsigned long triIndex[2] + // unsigned long vertIndex[2] + // unsigned long sharedVertIndex[2] + // bool degenerate + // Optional poses section, referred to by pose keyframes + M_POSES = 0xC000, + M_POSE = 0xC100, + // char* name (may be blank) + // unsigned short target // 0 for shared geometry, + // 1+ for submesh index + 1 + // bool includesNormals [1.8+] + M_POSE_VERTEX = 0xC111, + // unsigned long vertexIndex + // float xoffset, yoffset, zoffset + // float xnormal, ynormal, znormal (optional, 1.8+) + // Optional vertex animation chunk + M_ANIMATIONS = 0xD000, + M_ANIMATION = 0xD100, + // char* name + // float length + M_ANIMATION_BASEINFO = 0xD105, + // [Optional] base keyframe information (pose animation only) + // char* baseAnimationName (blank for self) + // float baseKeyFrameTime + M_ANIMATION_TRACK = 0xD110, + // unsigned short type // 1 == morph, 2 == pose + // unsigned short target // 0 for shared geometry, + // 1+ for submesh index + 1 + M_ANIMATION_MORPH_KEYFRAME = 0xD111, + // float time + // bool includesNormals [1.8+] + // float x,y,z // repeat by number of vertices in original geometry + M_ANIMATION_POSE_KEYFRAME = 0xD112, + // float time + M_ANIMATION_POSE_REF = 0xD113, // repeat for number of referenced poses + // unsigned short poseIndex + // float influence + // Optional submesh extreme vertex list chink + M_TABLE_EXTREMES = 0xE000 + // unsigned short submesh_index; + // float extremes [n_extremes][3]; +}; + +/* +static std::string MeshHeaderToString(MeshChunkId id) +{ + switch(id) + { + case M_HEADER: return "HEADER"; + case M_MESH: return "MESH"; + case M_SUBMESH: return "SUBMESH"; + case M_SUBMESH_OPERATION: return "SUBMESH_OPERATION"; + case M_SUBMESH_BONE_ASSIGNMENT: return "SUBMESH_BONE_ASSIGNMENT"; + case M_SUBMESH_TEXTURE_ALIAS: return "SUBMESH_TEXTURE_ALIAS"; + case M_GEOMETRY: return "GEOMETRY"; + case M_GEOMETRY_VERTEX_DECLARATION: return "GEOMETRY_VERTEX_DECLARATION"; + case M_GEOMETRY_VERTEX_ELEMENT: return "GEOMETRY_VERTEX_ELEMENT"; + case M_GEOMETRY_VERTEX_BUFFER: return "GEOMETRY_VERTEX_BUFFER"; + case M_GEOMETRY_VERTEX_BUFFER_DATA: return "GEOMETRY_VERTEX_BUFFER_DATA"; + case M_MESH_SKELETON_LINK: return "MESH_SKELETON_LINK"; + case M_MESH_BONE_ASSIGNMENT: return "MESH_BONE_ASSIGNMENT"; + case M_MESH_LOD: return "MESH_LOD"; + case M_MESH_LOD_USAGE: return "MESH_LOD_USAGE"; + case M_MESH_LOD_MANUAL: return "MESH_LOD_MANUAL"; + case M_MESH_LOD_GENERATED: return "MESH_LOD_GENERATED"; + case M_MESH_BOUNDS: return "MESH_BOUNDS"; + case M_SUBMESH_NAME_TABLE: return "SUBMESH_NAME_TABLE"; + case M_SUBMESH_NAME_TABLE_ELEMENT: return "SUBMESH_NAME_TABLE_ELEMENT"; + case M_EDGE_LISTS: return "EDGE_LISTS"; + case M_EDGE_LIST_LOD: return "EDGE_LIST_LOD"; + case M_EDGE_GROUP: return "EDGE_GROUP"; + case M_POSES: return "POSES"; + case M_POSE: return "POSE"; + case M_POSE_VERTEX: return "POSE_VERTEX"; + case M_ANIMATIONS: return "ANIMATIONS"; + case M_ANIMATION: return "ANIMATION"; + case M_ANIMATION_BASEINFO: return "ANIMATION_BASEINFO"; + case M_ANIMATION_TRACK: return "ANIMATION_TRACK"; + case M_ANIMATION_MORPH_KEYFRAME: return "ANIMATION_MORPH_KEYFRAME"; + case M_ANIMATION_POSE_KEYFRAME: return "ANIMATION_POSE_KEYFRAME"; + case M_ANIMATION_POSE_REF: return "ANIMATION_POSE_REF"; + case M_TABLE_EXTREMES: return "TABLE_EXTREMES"; + } + return "Unknown_MeshChunkId"; +} +*/ + +enum SkeletonChunkId { + SKELETON_HEADER = 0x1000, + // char* version : Version number check + SKELETON_BLENDMODE = 0x1010, // optional + // unsigned short blendmode : SkeletonAnimationBlendMode + SKELETON_BONE = 0x2000, + // Repeating section defining each bone in the system. + // Bones are assigned indexes automatically based on their order of declaration + // starting with 0. + // char* name : name of the bone + // unsigned short handle : handle of the bone, should be contiguous & start at 0 + // Vector3 position : position of this bone relative to parent + // Quaternion orientation : orientation of this bone relative to parent + // Vector3 scale : scale of this bone relative to parent + SKELETON_BONE_PARENT = 0x3000, + // Record of the parent of a single bone, used to build the node tree + // Repeating section, listed in Bone Index order, one per Bone + // unsigned short handle : child bone + // unsigned short parentHandle : parent bone + SKELETON_ANIMATION = 0x4000, + // A single animation for this skeleton + // char* name : Name of the animation + // float length : Length of the animation in seconds + SKELETON_ANIMATION_BASEINFO = 0x4010, + // [Optional] base keyframe information + // char* baseAnimationName (blank for self) + // float baseKeyFrameTime + SKELETON_ANIMATION_TRACK = 0x4100, + // A single animation track (relates to a single bone) + // Repeating section (within SKELETON_ANIMATION) + // unsigned short boneIndex : Index of bone to apply to + SKELETON_ANIMATION_TRACK_KEYFRAME = 0x4110, + // A single keyframe within the track + // Repeating section + // float time : The time position (seconds) + // Quaternion rotate : Rotation to apply at this keyframe + // Vector3 translate : Translation to apply at this keyframe + // Vector3 scale : Scale to apply at this keyframe + SKELETON_ANIMATION_LINK = 0x5000 + // Link to another skeleton, to re-use its animations + // char* skeletonName : name of skeleton to get animations from + // float scale : scale to apply to trans/scale keys +}; + +/* +static std::string SkeletonHeaderToString(SkeletonChunkId id) +{ + switch(id) + { + case SKELETON_HEADER: return "HEADER"; + case SKELETON_BLENDMODE: return "BLENDMODE"; + case SKELETON_BONE: return "BONE"; + case SKELETON_BONE_PARENT: return "BONE_PARENT"; + case SKELETON_ANIMATION: return "ANIMATION"; + case SKELETON_ANIMATION_BASEINFO: return "ANIMATION_BASEINFO"; + case SKELETON_ANIMATION_TRACK: return "ANIMATION_TRACK"; + case SKELETON_ANIMATION_TRACK_KEYFRAME: return "ANIMATION_TRACK_KEYFRAME"; + case SKELETON_ANIMATION_LINK: return "ANIMATION_LINK"; + } + return "Unknown_SkeletonChunkId"; +} +*/ +} // namespace Ogre +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER +#endif // AI_OGREBINARYSERIALIZER_H_INC diff --git a/libs/assimp/code/AssetLib/Ogre/OgreImporter.cpp b/libs/assimp/code/AssetLib/Ogre/OgreImporter.cpp new file mode 100644 index 0000000..860aed7 --- /dev/null +++ b/libs/assimp/code/AssetLib/Ogre/OgreImporter.cpp @@ -0,0 +1,135 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + +#include "OgreImporter.h" +#include "OgreBinarySerializer.h" +#include "OgreXmlSerializer.h" +#include <assimp/importerdesc.h> +#include <assimp/Importer.hpp> +#include <memory> + +static const aiImporterDesc desc = { + "Ogre3D Mesh Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "mesh mesh.xml" +}; + +namespace Assimp { +namespace Ogre { + +const aiImporterDesc *OgreImporter::GetInfo() const { + return &desc; +} + +void OgreImporter::SetupProperties(const Importer *pImp) { + m_userDefinedMaterialLibFile = pImp->GetPropertyString(AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE, "Scene.material"); + m_detectTextureTypeFromFilename = pImp->GetPropertyBool(AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME, false); +} + +bool OgreImporter::CanRead(const std::string &pFile, Assimp::IOSystem *pIOHandler, bool /*checkSig*/) const { + if (EndsWith(pFile, ".mesh.xml", false)) { + static const char *tokens[] = { "<mesh>" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); + } + + /// @todo Read and validate first header chunk? + return EndsWith(pFile, ".mesh", false); +} + +void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Assimp::IOSystem *pIOHandler) { + // Open source file + IOStream *f = pIOHandler->Open(pFile, "rb"); + if (!f) { + throw DeadlyImportError("Failed to open file ", pFile); + } + + // Binary .mesh import + if (EndsWith(pFile, ".mesh", false)) { + /// @note MemoryStreamReader takes ownership of f. + MemoryStreamReader reader(f); + + // Import mesh + std::unique_ptr<Mesh> mesh(OgreBinarySerializer::ImportMesh(&reader)); + + // Import skeleton + OgreBinarySerializer::ImportSkeleton(pIOHandler, mesh.get()); + + // Import mesh referenced materials + ReadMaterials(pFile, pIOHandler, pScene, mesh.get()); + + // Convert to Assimp + mesh->ConvertToAssimpScene(pScene); + return; + } + // XML .mesh.xml import + /// @note XmlReader does not take ownership of f, hence the scoped ptr. + std::unique_ptr<IOStream> scopedFile(f); + XmlParser xmlParser; + + //std::unique_ptr<CIrrXML_IOStreamReader> xmlStream(new CIrrXML_IOStreamReader(scopedFile.get())); + //std::unique_ptr<XmlReader> reader(irr::io::createIrrXMLReader(xmlStream.get())); + xmlParser.parse(scopedFile.get()); + // Import mesh + std::unique_ptr<MeshXml> mesh(OgreXmlSerializer::ImportMesh(&xmlParser)); + + // Import skeleton + OgreXmlSerializer::ImportSkeleton(pIOHandler, mesh.get()); + + // Import mesh referenced materials + ReadMaterials(pFile, pIOHandler, pScene, mesh.get()); + + // Convert to Assimp + mesh->ConvertToAssimpScene(pScene); +} + +} // namespace Ogre +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER diff --git a/libs/assimp/code/AssetLib/Ogre/OgreImporter.h b/libs/assimp/code/AssetLib/Ogre/OgreImporter.h new file mode 100644 index 0000000..dc8b090 --- /dev/null +++ b/libs/assimp/code/AssetLib/Ogre/OgreImporter.h @@ -0,0 +1,98 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#pragma once +#ifndef AI_OGREIMPORTER_H_INC +#define AI_OGREIMPORTER_H_INC + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + +#include <assimp/BaseImporter.h> +#include <assimp/material.h> + +#include "OgreParsingUtils.h" +#include "OgreStructs.h" + +namespace Assimp { +namespace Ogre { + +/** Importer for Ogre mesh, skeleton and material formats. + @todo Support vertex colors. + @todo Support poses/animations from the mesh file. + Currently only skeleton file animations are supported. */ +class OgreImporter : public BaseImporter { +public: + /// BaseImporter override. + virtual bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + +protected: + /// BaseImporter override. + virtual void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + + /// BaseImporter override. + virtual const aiImporterDesc *GetInfo() const override; + + /// BaseImporter override. + virtual void SetupProperties(const Importer *pImp) override; + +private: + /// Read materials referenced by the @c mesh to @c pScene. + void ReadMaterials(const std::string &pFile, Assimp::IOSystem *pIOHandler, aiScene *pScene, Mesh *mesh); + void ReadMaterials(const std::string &pFile, Assimp::IOSystem *pIOHandler, aiScene *pScene, MeshXml *mesh); + void AssignMaterials(aiScene *pScene, std::vector<aiMaterial *> &materials); + + /// Reads material + aiMaterial *ReadMaterial(const std::string &pFile, Assimp::IOSystem *pIOHandler, const std::string &MaterialName); + + // These functions parse blocks from a material file from @c ss. Starting parsing from "{" and ending it to "}". + bool ReadTechnique(const std::string &techniqueName, std::stringstream &ss, aiMaterial *material); + bool ReadPass(const std::string &passName, std::stringstream &ss, aiMaterial *material); + bool ReadTextureUnit(const std::string &textureUnitName, std::stringstream &ss, aiMaterial *material); + +private: + std::string m_userDefinedMaterialLibFile; + bool m_detectTextureTypeFromFilename; + std::map<aiTextureType, unsigned int> m_textures; +}; +} // namespace Ogre +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER +#endif // AI_OGREIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/Ogre/OgreMaterial.cpp b/libs/assimp/code/AssetLib/Ogre/OgreMaterial.cpp new file mode 100644 index 0000000..7478a80 --- /dev/null +++ b/libs/assimp/code/AssetLib/Ogre/OgreMaterial.cpp @@ -0,0 +1,500 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + +#include "OgreImporter.h" +#include <assimp/StringUtils.h> +#include <assimp/TinyFormatter.h> +#include <assimp/fast_atof.h> +#include <assimp/material.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> + +#include <memory> +#include <sstream> +#include <vector> + +using namespace std; + +namespace Assimp { +namespace Ogre { + +static const string partComment = "//"; +static const string partBlockStart = "{"; +static const string partBlockEnd = "}"; + +void OgreImporter::ReadMaterials(const std::string &pFile, Assimp::IOSystem *pIOHandler, aiScene *pScene, Mesh *mesh) { + std::vector<aiMaterial *> materials; + + // Create materials that can be found and parsed via the IOSystem. + for (size_t i = 0, len = mesh->NumSubMeshes(); i < len; ++i) { + SubMesh *submesh = mesh->GetSubMesh(i); + if (submesh && !submesh->materialRef.empty()) { + aiMaterial *material = ReadMaterial(pFile, pIOHandler, submesh->materialRef); + if (material) { + submesh->materialIndex = static_cast<int>(materials.size()); + materials.push_back(material); + } + } + } + + AssignMaterials(pScene, materials); +} + +void OgreImporter::ReadMaterials(const std::string &pFile, Assimp::IOSystem *pIOHandler, aiScene *pScene, MeshXml *mesh) { + std::vector<aiMaterial *> materials; + + // Create materials that can be found and parsed via the IOSystem. + for (size_t i = 0, len = mesh->NumSubMeshes(); i < len; ++i) { + SubMeshXml *submesh = mesh->GetSubMesh(static_cast<uint16_t>(i)); + if (submesh && !submesh->materialRef.empty()) { + aiMaterial *material = ReadMaterial(pFile, pIOHandler, submesh->materialRef); + if (material) { + submesh->materialIndex = static_cast<int>(materials.size()); + materials.push_back(material); + } + } + } + + AssignMaterials(pScene, materials); +} + +void OgreImporter::AssignMaterials(aiScene *pScene, std::vector<aiMaterial *> &materials) { + pScene->mNumMaterials = static_cast<unsigned int>(materials.size()); + if (pScene->mNumMaterials > 0) { + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + for (size_t i = 0; i < pScene->mNumMaterials; ++i) { + pScene->mMaterials[i] = materials[i]; + } + } +} + +aiMaterial *OgreImporter::ReadMaterial(const std::string &pFile, Assimp::IOSystem *pIOHandler, const std::string &materialName) { + if (materialName.empty()) { + return 0; + } + + // Full reference and examples of Ogre Material Script + // can be found from http://www.ogre3d.org/docs/manual/manual_14.html + + /*and here is another one: + + import * from abstract_base_passes_depth.material + import * from abstract_base.material + import * from mat_shadow_caster.material + import * from mat_character_singlepass.material + + material hero/hair/caster : mat_shadow_caster_skin_areject + { + set $diffuse_map "hero_hair_alpha_c.dds" + } + + material hero/hair_alpha : mat_char_cns_singlepass_areject_4weights + { + set $diffuse_map "hero_hair_alpha_c.dds" + set $specular_map "hero_hair_alpha_s.dds" + set $normal_map "hero_hair_alpha_n.dds" + set $light_map "black_lightmap.dds" + + set $shadow_caster_material "hero/hair/caster" + } + */ + + stringstream ss; + + // Scope for scopre_ptr auto release + { + /* There are three .material options in priority order: + 1) File with the material name (materialName) + 2) File with the mesh files base name (pFile) + 3) Optional user defined material library file (m_userDefinedMaterialLibFile) */ + std::vector<string> potentialFiles; + potentialFiles.push_back(materialName + ".material"); + potentialFiles.push_back(pFile.substr(0, pFile.rfind(".mesh")) + ".material"); + if (!m_userDefinedMaterialLibFile.empty()) + potentialFiles.push_back(m_userDefinedMaterialLibFile); + + IOStream *materialFile = 0; + for (size_t i = 0; i < potentialFiles.size(); ++i) { + materialFile = pIOHandler->Open(potentialFiles[i]); + if (materialFile) { + break; + } + ASSIMP_LOG_VERBOSE_DEBUG("Source file for material '", materialName, "' ", potentialFiles[i], " does not exist"); + } + if (!materialFile) { + ASSIMP_LOG_ERROR("Failed to find source file for material '", materialName, "'"); + return 0; + } + + std::unique_ptr<IOStream> stream(materialFile); + if (stream->FileSize() == 0) { + ASSIMP_LOG_WARN("Source file for material '", materialName, "' is empty (size is 0 bytes)"); + return 0; + } + + // Read bytes + vector<char> data(stream->FileSize()); + stream->Read(&data[0], stream->FileSize(), 1); + + // Convert to UTF-8 and terminate the string for ss + BaseImporter::ConvertToUTF8(data); + data.push_back('\0'); + + ss << &data[0]; + } + + ASSIMP_LOG_VERBOSE_DEBUG("Reading material '", materialName, "'"); + + aiMaterial *material = new aiMaterial(); + m_textures.clear(); + + aiString matName(materialName); + material->AddProperty(&matName, AI_MATKEY_NAME); + + // The stringstream will push words from a line until newline. + // It will also trim whitespace from line start and between words. + string linePart; + ss >> linePart; + + const string partMaterial = "material"; + const string partTechnique = "technique"; + + while (!ss.eof()) { + // Skip commented lines + if (linePart == partComment) { + NextAfterNewLine(ss, linePart); + continue; + } + if (linePart != partMaterial) { + ss >> linePart; + continue; + } + + ss >> linePart; + if (linePart != materialName) { + ss >> linePart; + continue; + } + + NextAfterNewLine(ss, linePart); + if (linePart != partBlockStart) { + ASSIMP_LOG_ERROR("Invalid material: block start missing near index ", ss.tellg()); + return material; + } + + ASSIMP_LOG_VERBOSE_DEBUG("material '", materialName, "'"); + + while (linePart != partBlockEnd) { + // Proceed to the first technique + ss >> linePart; + + if (linePart == partTechnique) { + std::string techniqueName = SkipLine(ss); + ReadTechnique(ai_trim(techniqueName), ss, material); + } + + // Read information from a custom material + /** @todo This "set $x y" does not seem to be a official Ogre material system feature. + Materials can inherit other materials and override texture units by using the (unique) + parent texture unit name in your cloned material. + This is not yet supported and below code is probably some hack from the original + author of this Ogre importer. Should be removed? */ + if (linePart == "set") { + ss >> linePart; + if (linePart == "$specular") //todo load this values: + { + } else if (linePart == "$diffuse") { + } else if (linePart == "$ambient") { + } else if (linePart == "$colormap") { + ss >> linePart; + aiString cm(linePart); + material->AddProperty(&cm, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); + } else if (linePart == "$normalmap") { + ss >> linePart; + aiString nm(linePart); + material->AddProperty(&nm, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0)); + } else if (linePart == "$shininess_strength") { + ss >> linePart; + float Shininess = fast_atof(linePart.c_str()); + material->AddProperty(&Shininess, 1, AI_MATKEY_SHININESS_STRENGTH); + } else if (linePart == "$shininess_exponent") { + ss >> linePart; + float Shininess = fast_atof(linePart.c_str()); + material->AddProperty(&Shininess, 1, AI_MATKEY_SHININESS); + } + //Properties from Venetica: + else if (linePart == "$diffuse_map") { + ss >> linePart; + if (linePart[0] == '"') // "file" -> file + linePart = linePart.substr(1, linePart.size() - 2); + aiString ts(linePart); + material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); + } else if (linePart == "$specular_map") { + ss >> linePart; + if (linePart[0] == '"') // "file" -> file + linePart = linePart.substr(1, linePart.size() - 2); + aiString ts(linePart); + material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_SHININESS, 0)); + } else if (linePart == "$normal_map") { + ss >> linePart; + if (linePart[0] == '"') // "file" -> file + linePart = linePart.substr(1, linePart.size() - 2); + aiString ts(linePart); + material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0)); + } else if (linePart == "$light_map") { + ss >> linePart; + if (linePart[0] == '"') { + linePart = linePart.substr(1, linePart.size() - 2); + } + aiString ts(linePart); + material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_LIGHTMAP, 0)); + } + } + } + ss >> linePart; + } + + return material; +} + +bool OgreImporter::ReadTechnique(const std::string &techniqueName, stringstream &ss, aiMaterial *material) { + string linePart; + ss >> linePart; + + if (linePart != partBlockStart) { + ASSIMP_LOG_ERROR("Invalid material: Technique block start missing near index ", ss.tellg()); + return false; + } + + ASSIMP_LOG_VERBOSE_DEBUG(" technique '", techniqueName, "'"); + + const string partPass = "pass"; + + while (linePart != partBlockEnd) { + ss >> linePart; + + // Skip commented lines + if (linePart == partComment) { + SkipLine(ss); + continue; + } + + /// @todo Techniques have other attributes than just passes. + if (linePart == partPass) { + string passName = SkipLine(ss); + ReadPass(ai_trim(passName), ss, material); + } + } + return true; +} + +bool OgreImporter::ReadPass(const std::string &passName, stringstream &ss, aiMaterial *material) { + string linePart; + ss >> linePart; + + if (linePart != partBlockStart) { + ASSIMP_LOG_ERROR("Invalid material: Pass block start missing near index ", ss.tellg()); + return false; + } + + ASSIMP_LOG_VERBOSE_DEBUG(" pass '", passName, "'"); + + const string partAmbient = "ambient"; + const string partDiffuse = "diffuse"; + const string partSpecular = "specular"; + const string partEmissive = "emissive"; + const string partTextureUnit = "texture_unit"; + + while (linePart != partBlockEnd) { + ss >> linePart; + + // Skip commented lines + if (linePart == partComment) { + SkipLine(ss); + continue; + } + + // Colors + /// @todo Support alpha via aiColor4D. + if (linePart == partAmbient || linePart == partDiffuse || linePart == partSpecular || linePart == partEmissive) { + float r, g, b; + ss >> r >> g >> b; + const aiColor3D color(r, g, b); + + ASSIMP_LOG_VERBOSE_DEBUG(" ", linePart, " ", r, " ", g, " ", b); + + if (linePart == partAmbient) { + material->AddProperty(&color, 1, AI_MATKEY_COLOR_AMBIENT); + } else if (linePart == partDiffuse) { + material->AddProperty(&color, 1, AI_MATKEY_COLOR_DIFFUSE); + } else if (linePart == partSpecular) { + material->AddProperty(&color, 1, AI_MATKEY_COLOR_SPECULAR); + } else if (linePart == partEmissive) { + material->AddProperty(&color, 1, AI_MATKEY_COLOR_EMISSIVE); + } + } else if (linePart == partTextureUnit) { + string textureUnitName = SkipLine(ss); + ReadTextureUnit(ai_trim(textureUnitName), ss, material); + } + } + return true; +} + +bool OgreImporter::ReadTextureUnit(const std::string &textureUnitName, stringstream &ss, aiMaterial *material) { + string linePart; + ss >> linePart; + + if (linePart != partBlockStart) { + ASSIMP_LOG_ERROR("Invalid material: Texture unit block start missing near index ", ss.tellg()); + return false; + } + + ASSIMP_LOG_VERBOSE_DEBUG(" texture_unit '", textureUnitName, "'"); + + const string partTexture = "texture"; + const string partTextCoordSet = "tex_coord_set"; + const string partColorOp = "colour_op"; + + aiTextureType textureType = aiTextureType_NONE; + std::string textureRef; + int uvCoord = 0; + + while (linePart != partBlockEnd) { + ss >> linePart; + + // Skip commented lines + if (linePart == partComment) { + SkipLine(ss); + continue; + } + + if (linePart == partTexture) { + ss >> linePart; + textureRef = linePart; + + // User defined Assimp config property to detect texture type from filename. + if (m_detectTextureTypeFromFilename) { + size_t posSuffix = textureRef.find_last_of('.'); + size_t posUnderscore = textureRef.find_last_of('_'); + + if (posSuffix != string::npos && posUnderscore != string::npos && posSuffix > posUnderscore) { + string identifier = ai_tolower(textureRef.substr(posUnderscore, posSuffix - posUnderscore)); + ASSIMP_LOG_VERBOSE_DEBUG("Detecting texture type from filename postfix '", identifier, "'"); + + if (identifier == "_n" || identifier == "_nrm" || identifier == "_nrml" || identifier == "_normal" || identifier == "_normals" || identifier == "_normalmap") { + textureType = aiTextureType_NORMALS; + } else if (identifier == "_s" || identifier == "_spec" || identifier == "_specular" || identifier == "_specularmap") { + textureType = aiTextureType_SPECULAR; + } else if (identifier == "_l" || identifier == "_light" || identifier == "_lightmap" || identifier == "_occ" || identifier == "_occlusion") { + textureType = aiTextureType_LIGHTMAP; + } else if (identifier == "_disp" || identifier == "_displacement") { + textureType = aiTextureType_DISPLACEMENT; + } else { + textureType = aiTextureType_DIFFUSE; + } + } else { + textureType = aiTextureType_DIFFUSE; + } + } + // Detect from texture unit name. This cannot be too broad as + // authors might give names like "LightSaber" or "NormalNinja". + else { + string unitNameLower = ai_tolower(textureUnitName); + if (unitNameLower.find("normalmap") != string::npos) { + textureType = aiTextureType_NORMALS; + } else if (unitNameLower.find("specularmap") != string::npos) { + textureType = aiTextureType_SPECULAR; + } else if (unitNameLower.find("lightmap") != string::npos) { + textureType = aiTextureType_LIGHTMAP; + } else if (unitNameLower.find("displacementmap") != string::npos) { + textureType = aiTextureType_DISPLACEMENT; + } else { + textureType = aiTextureType_DIFFUSE; + } + } + } else if (linePart == partTextCoordSet) { + ss >> uvCoord; + } + /// @todo Implement + else if (linePart == partColorOp) { + /* + ss >> linePart; + if("replace"==linePart)//I don't think, assimp has something for this... + { + } + else if("modulate"==linePart) + { + //TODO: set value + //material->AddProperty(aiTextureOp_Multiply) + } + */ + } + } + + if (textureRef.empty()) { + ASSIMP_LOG_WARN("Texture reference is empty, ignoring texture_unit."); + return false; + } + if (textureType == aiTextureType_NONE) { + ASSIMP_LOG_WARN("Failed to detect texture type for '", textureRef, "', ignoring texture_unit."); + return false; + } + + unsigned int textureTypeIndex = m_textures[textureType]; + m_textures[textureType]++; + + ASSIMP_LOG_VERBOSE_DEBUG(" texture '", textureRef, "' type ", textureType, + " index ", textureTypeIndex, " UV ", uvCoord); + + aiString assimpTextureRef(textureRef); + material->AddProperty(&assimpTextureRef, AI_MATKEY_TEXTURE(textureType, textureTypeIndex)); + material->AddProperty(&uvCoord, 1, AI_MATKEY_UVWSRC(textureType, textureTypeIndex)); + + return true; +} + +} // namespace Ogre +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER diff --git a/libs/assimp/code/AssetLib/Ogre/OgreParsingUtils.h b/libs/assimp/code/AssetLib/Ogre/OgreParsingUtils.h new file mode 100644 index 0000000..60dd5cf --- /dev/null +++ b/libs/assimp/code/AssetLib/Ogre/OgreParsingUtils.h @@ -0,0 +1,95 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef AI_OGREPARSINGUTILS_H_INC +#define AI_OGREPARSINGUTILS_H_INC + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + +#include <assimp/ParsingUtils.h> +#include <cstdint> +#include <algorithm> +#include <cctype> +#include <functional> +#include <sstream> + +namespace Assimp { +namespace Ogre { + + +/// Returns if @c s ends with @c suffix. If @c caseSensitive is false, both strings will be lower cased before matching. +static inline bool EndsWith(const std::string &s, const std::string &suffix, bool caseSensitive = true) { + if (s.empty() || suffix.empty()) { + return false; + } else if (s.length() < suffix.length()) { + return false; + } + + if (!caseSensitive) { + return EndsWith(ai_tolower(s), ai_tolower(suffix), true); + } + + size_t len = suffix.length(); + std::string sSuffix = s.substr(s.length() - len, len); + + return (ASSIMP_stricmp(sSuffix, suffix) == 0); +} + +// Skips a line from current @ss position until a newline. Returns the skipped part. +static inline std::string SkipLine(std::stringstream &ss) { + std::string skipped; + getline(ss, skipped); + return skipped; +} + +// Skips a line and reads next element from @c ss to @c nextElement. +/** @return Skipped line content until newline. */ +static inline std::string NextAfterNewLine(std::stringstream &ss, std::string &nextElement) { + std::string skipped = SkipLine(ss); + ss >> nextElement; + return skipped; +} + +} // namespace Ogre +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER +#endif // AI_OGREPARSINGUTILS_H_INC diff --git a/libs/assimp/code/AssetLib/Ogre/OgreStructs.cpp b/libs/assimp/code/AssetLib/Ogre/OgreStructs.cpp new file mode 100644 index 0000000..d63dd93 --- /dev/null +++ b/libs/assimp/code/AssetLib/Ogre/OgreStructs.cpp @@ -0,0 +1,1076 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + +#include "OgreStructs.h" +#include <assimp/Exceptional.h> +#include <assimp/TinyFormatter.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> + +namespace Assimp { +namespace Ogre { + +// VertexElement + +VertexElement::VertexElement() : + index(0), + source(0), + offset(0), + type(VET_FLOAT1), + semantic(VES_POSITION) { +} + +size_t VertexElement::Size() const { + return TypeSize(type); +} + +size_t VertexElement::ComponentCount() const { + return ComponentCount(type); +} + +size_t VertexElement::ComponentCount(Type type) { + switch (type) { + case VET_COLOUR: + case VET_COLOUR_ABGR: + case VET_COLOUR_ARGB: + case VET_FLOAT1: + case VET_DOUBLE1: + case VET_SHORT1: + case VET_USHORT1: + case VET_INT1: + case VET_UINT1: + return 1; + case VET_FLOAT2: + case VET_DOUBLE2: + case VET_SHORT2: + case VET_USHORT2: + case VET_INT2: + case VET_UINT2: + return 2; + case VET_FLOAT3: + case VET_DOUBLE3: + case VET_SHORT3: + case VET_USHORT3: + case VET_INT3: + case VET_UINT3: + return 3; + case VET_FLOAT4: + case VET_DOUBLE4: + case VET_SHORT4: + case VET_USHORT4: + case VET_INT4: + case VET_UINT4: + case VET_UBYTE4: + return 4; + } + return 0; +} + +size_t VertexElement::TypeSize(Type type) { + switch (type) { + case VET_COLOUR: + case VET_COLOUR_ABGR: + case VET_COLOUR_ARGB: + return sizeof(unsigned int); + case VET_FLOAT1: + return sizeof(float); + case VET_FLOAT2: + return sizeof(float) * 2; + case VET_FLOAT3: + return sizeof(float) * 3; + case VET_FLOAT4: + return sizeof(float) * 4; + case VET_DOUBLE1: + return sizeof(double); + case VET_DOUBLE2: + return sizeof(double) * 2; + case VET_DOUBLE3: + return sizeof(double) * 3; + case VET_DOUBLE4: + return sizeof(double) * 4; + case VET_SHORT1: + return sizeof(short); + case VET_SHORT2: + return sizeof(short) * 2; + case VET_SHORT3: + return sizeof(short) * 3; + case VET_SHORT4: + return sizeof(short) * 4; + case VET_USHORT1: + return sizeof(unsigned short); + case VET_USHORT2: + return sizeof(unsigned short) * 2; + case VET_USHORT3: + return sizeof(unsigned short) * 3; + case VET_USHORT4: + return sizeof(unsigned short) * 4; + case VET_INT1: + return sizeof(int); + case VET_INT2: + return sizeof(int) * 2; + case VET_INT3: + return sizeof(int) * 3; + case VET_INT4: + return sizeof(int) * 4; + case VET_UINT1: + return sizeof(unsigned int); + case VET_UINT2: + return sizeof(unsigned int) * 2; + case VET_UINT3: + return sizeof(unsigned int) * 3; + case VET_UINT4: + return sizeof(unsigned int) * 4; + case VET_UBYTE4: + return sizeof(unsigned char) * 4; + } + return 0; +} + +std::string VertexElement::TypeToString() { + return TypeToString(type); +} + +std::string VertexElement::TypeToString(Type type) { + switch (type) { + case VET_COLOUR: return "COLOUR"; + case VET_COLOUR_ABGR: return "COLOUR_ABGR"; + case VET_COLOUR_ARGB: return "COLOUR_ARGB"; + case VET_FLOAT1: return "FLOAT1"; + case VET_FLOAT2: return "FLOAT2"; + case VET_FLOAT3: return "FLOAT3"; + case VET_FLOAT4: return "FLOAT4"; + case VET_DOUBLE1: return "DOUBLE1"; + case VET_DOUBLE2: return "DOUBLE2"; + case VET_DOUBLE3: return "DOUBLE3"; + case VET_DOUBLE4: return "DOUBLE4"; + case VET_SHORT1: return "SHORT1"; + case VET_SHORT2: return "SHORT2"; + case VET_SHORT3: return "SHORT3"; + case VET_SHORT4: return "SHORT4"; + case VET_USHORT1: return "USHORT1"; + case VET_USHORT2: return "USHORT2"; + case VET_USHORT3: return "USHORT3"; + case VET_USHORT4: return "USHORT4"; + case VET_INT1: return "INT1"; + case VET_INT2: return "INT2"; + case VET_INT3: return "INT3"; + case VET_INT4: return "INT4"; + case VET_UINT1: return "UINT1"; + case VET_UINT2: return "UINT2"; + case VET_UINT3: return "UINT3"; + case VET_UINT4: return "UINT4"; + case VET_UBYTE4: return "UBYTE4"; + } + return "Uknown_VertexElement::Type"; +} + +std::string VertexElement::SemanticToString() { + return SemanticToString(semantic); +} + +std::string VertexElement::SemanticToString(Semantic semantic) { + switch (semantic) { + case VES_POSITION: return "POSITION"; + case VES_BLEND_WEIGHTS: return "BLEND_WEIGHTS"; + case VES_BLEND_INDICES: return "BLEND_INDICES"; + case VES_NORMAL: return "NORMAL"; + case VES_DIFFUSE: return "DIFFUSE"; + case VES_SPECULAR: return "SPECULAR"; + case VES_TEXTURE_COORDINATES: return "TEXTURE_COORDINATES"; + case VES_BINORMAL: return "BINORMAL"; + case VES_TANGENT: return "TANGENT"; + } + return "Uknown_VertexElement::Semantic"; +} + +// IVertexData + +IVertexData::IVertexData() : + count(0) { +} + +bool IVertexData::HasBoneAssignments() const { + return !boneAssignments.empty(); +} + +void IVertexData::AddVertexMapping(uint32_t oldIndex, uint32_t newIndex) { + BoneAssignmentsForVertex(oldIndex, newIndex, boneAssignmentsMap[newIndex]); + vertexIndexMapping[oldIndex].push_back(newIndex); +} + +void IVertexData::BoneAssignmentsForVertex(uint32_t currentIndex, uint32_t newIndex, VertexBoneAssignmentList &dest) const { + for (const auto &boneAssign : boneAssignments) { + if (boneAssign.vertexIndex == currentIndex) { + VertexBoneAssignment a = boneAssign; + a.vertexIndex = newIndex; + dest.push_back(a); + } + } +} + +AssimpVertexBoneWeightList IVertexData::AssimpBoneWeights(size_t vertices) { + AssimpVertexBoneWeightList weights; + for (size_t vi = 0; vi < vertices; ++vi) { + VertexBoneAssignmentList &vertexWeights = boneAssignmentsMap[static_cast<unsigned int>(vi)]; + for (VertexBoneAssignmentList::const_iterator iter = vertexWeights.begin(), end = vertexWeights.end(); + iter != end; ++iter) { + std::vector<aiVertexWeight> &boneWeights = weights[iter->boneIndex]; + boneWeights.push_back(aiVertexWeight(static_cast<unsigned int>(vi), iter->weight)); + } + } + return weights; +} + +std::set<uint16_t> IVertexData::ReferencedBonesByWeights() const { + std::set<uint16_t> referenced; + for (const auto &boneAssign : boneAssignments) { + referenced.insert(boneAssign.boneIndex); + } + return referenced; +} + +// VertexData + +VertexData::VertexData() { +} + +VertexData::~VertexData() { + Reset(); +} + +void VertexData::Reset() { + // Releases shared ptr memory streams. + vertexBindings.clear(); + vertexElements.clear(); +} + +uint32_t VertexData::VertexSize(uint16_t source) const { + uint32_t size = 0; + for (const auto &element : vertexElements) { + if (element.source == source) + size += static_cast<uint32_t>(element.Size()); + } + return size; +} + +MemoryStream *VertexData::VertexBuffer(uint16_t source) { + if (vertexBindings.find(source) != vertexBindings.end()) + return vertexBindings[source].get(); + return 0; +} + +VertexElement *VertexData::GetVertexElement(VertexElement::Semantic semantic, uint16_t index) { + for (auto &element : vertexElements) { + if (element.semantic == semantic && element.index == index) + return &element; + } + return 0; +} + +// VertexDataXml + +VertexDataXml::VertexDataXml() { +} + +bool VertexDataXml::HasPositions() const { + return !positions.empty(); +} + +bool VertexDataXml::HasNormals() const { + return !normals.empty(); +} + +bool VertexDataXml::HasTangents() const { + return !tangents.empty(); +} + +bool VertexDataXml::HasUvs() const { + return !uvs.empty(); +} + +size_t VertexDataXml::NumUvs() const { + return uvs.size(); +} + +// IndexData + +IndexData::IndexData() : + count(0), + faceCount(0), + is32bit(false) { +} + +IndexData::~IndexData() { + Reset(); +} + +void IndexData::Reset() { + // Release shared ptr memory stream. + buffer.reset(); +} + +size_t IndexData::IndexSize() const { + return (is32bit ? sizeof(uint32_t) : sizeof(uint16_t)); +} + +size_t IndexData::FaceSize() const { + return IndexSize() * 3; +} + +// Mesh + +Mesh::Mesh() : + hasSkeletalAnimations(false), + skeleton(nullptr), + sharedVertexData(nullptr), + subMeshes(), + animations(), + poses() { +} + +Mesh::~Mesh() { + Reset(); +} + +void Mesh::Reset() { + OGRE_SAFE_DELETE(skeleton) + OGRE_SAFE_DELETE(sharedVertexData) + + for (auto &mesh : subMeshes) { + OGRE_SAFE_DELETE(mesh) + } + subMeshes.clear(); + for (auto &anim : animations) { + OGRE_SAFE_DELETE(anim) + } + animations.clear(); + for (auto &pose : poses) { + OGRE_SAFE_DELETE(pose) + } + poses.clear(); +} + +size_t Mesh::NumSubMeshes() const { + return subMeshes.size(); +} + +SubMesh *Mesh::GetSubMesh(size_t index) const { + for (size_t i = 0; i < subMeshes.size(); ++i) { + if (subMeshes[i]->index == index) { + return subMeshes[i]; + } + } + return 0; +} + +void Mesh::ConvertToAssimpScene(aiScene *dest) { + if (nullptr == dest) { + return; + } + + // Setup + dest->mNumMeshes = static_cast<unsigned int>(NumSubMeshes()); + dest->mMeshes = new aiMesh *[dest->mNumMeshes]; + + // Create root node + dest->mRootNode = new aiNode(); + dest->mRootNode->mNumMeshes = dest->mNumMeshes; + dest->mRootNode->mMeshes = new unsigned int[dest->mRootNode->mNumMeshes]; + + // Export meshes + for (size_t i = 0; i < dest->mNumMeshes; ++i) { + dest->mMeshes[i] = subMeshes[i]->ConvertToAssimpMesh(this); + dest->mRootNode->mMeshes[i] = static_cast<unsigned int>(i); + } + + // Export skeleton + if (skeleton) { + // Bones + if (!skeleton->bones.empty()) { + BoneList rootBones = skeleton->RootBones(); + dest->mRootNode->mNumChildren = static_cast<unsigned int>(rootBones.size()); + dest->mRootNode->mChildren = new aiNode *[dest->mRootNode->mNumChildren]; + + for (size_t i = 0, len = rootBones.size(); i < len; ++i) { + dest->mRootNode->mChildren[i] = rootBones[i]->ConvertToAssimpNode(skeleton, dest->mRootNode); + } + } + + // Animations + if (!skeleton->animations.empty()) { + dest->mNumAnimations = static_cast<unsigned int>(skeleton->animations.size()); + dest->mAnimations = new aiAnimation *[dest->mNumAnimations]; + + for (size_t i = 0, len = skeleton->animations.size(); i < len; ++i) { + dest->mAnimations[i] = skeleton->animations[i]->ConvertToAssimpAnimation(); + } + } + } +} + +// ISubMesh + +ISubMesh::ISubMesh() : + index(0), + materialIndex(-1), + usesSharedVertexData(false), + operationType(OT_POINT_LIST) { +} + +// SubMesh + +SubMesh::SubMesh() : + vertexData(0), + indexData(new IndexData()) { +} + +SubMesh::~SubMesh() { + Reset(); +} + +void SubMesh::Reset(){ + OGRE_SAFE_DELETE(vertexData) + OGRE_SAFE_DELETE(indexData) +} + +aiMesh *SubMesh::ConvertToAssimpMesh(Mesh *parent) { + if (operationType != OT_TRIANGLE_LIST) { + throw DeadlyImportError("Only mesh operation type OT_TRIANGLE_LIST is supported. Found ", operationType); + } + + aiMesh *dest = new aiMesh(); + dest->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + if (!name.empty()) + dest->mName = name; + + // Material index + if (materialIndex != -1) + dest->mMaterialIndex = materialIndex; + + // Pick source vertex data from shader geometry or from internal geometry. + VertexData *src = (!usesSharedVertexData ? vertexData : parent->sharedVertexData); + + VertexElement *positionsElement = src->GetVertexElement(VertexElement::VES_POSITION); + VertexElement *normalsElement = src->GetVertexElement(VertexElement::VES_NORMAL); + VertexElement *uv1Element = src->GetVertexElement(VertexElement::VES_TEXTURE_COORDINATES, 0); + VertexElement *uv2Element = src->GetVertexElement(VertexElement::VES_TEXTURE_COORDINATES, 1); + + // Sanity checks + if (!positionsElement) { + throw DeadlyImportError("Failed to import Ogre VertexElement::VES_POSITION. Mesh does not have vertex positions!"); + } else if (positionsElement->type != VertexElement::VET_FLOAT3) { + throw DeadlyImportError("Ogre Mesh position vertex element type != VertexElement::VET_FLOAT3. This is not supported."); + } else if (normalsElement && normalsElement->type != VertexElement::VET_FLOAT3) { + throw DeadlyImportError("Ogre Mesh normal vertex element type != VertexElement::VET_FLOAT3. This is not supported."); + } + + // Faces + dest->mNumFaces = indexData->faceCount; + dest->mFaces = new aiFace[dest->mNumFaces]; + + // Assimp required unique vertices, we need to convert from Ogres shared indexing. + size_t uniqueVertexCount = dest->mNumFaces * 3; + dest->mNumVertices = static_cast<unsigned int>(uniqueVertexCount); + dest->mVertices = new aiVector3D[dest->mNumVertices]; + + // Source streams + MemoryStream *positions = src->VertexBuffer(positionsElement->source); + MemoryStream *normals = (normalsElement ? src->VertexBuffer(normalsElement->source) : 0); + MemoryStream *uv1 = (uv1Element ? src->VertexBuffer(uv1Element->source) : 0); + MemoryStream *uv2 = (uv2Element ? src->VertexBuffer(uv2Element->source) : 0); + + // Element size + const size_t sizePosition = positionsElement->Size(); + const size_t sizeNormal = (normalsElement ? normalsElement->Size() : 0); + const size_t sizeUv1 = (uv1Element ? uv1Element->Size() : 0); + const size_t sizeUv2 = (uv2Element ? uv2Element->Size() : 0); + + // Vertex width + const size_t vWidthPosition = src->VertexSize(positionsElement->source); + const size_t vWidthNormal = (normalsElement ? src->VertexSize(normalsElement->source) : 0); + const size_t vWidthUv1 = (uv1Element ? src->VertexSize(uv1Element->source) : 0); + const size_t vWidthUv2 = (uv2Element ? src->VertexSize(uv2Element->source) : 0); + + bool boneAssignments = src->HasBoneAssignments(); + + // Prepare normals + if (normals) + dest->mNormals = new aiVector3D[dest->mNumVertices]; + + // Prepare UVs, ignoring incompatible UVs. + if (uv1) { + if (uv1Element->type == VertexElement::VET_FLOAT2 || uv1Element->type == VertexElement::VET_FLOAT3) { + dest->mNumUVComponents[0] = static_cast<unsigned int>(uv1Element->ComponentCount()); + dest->mTextureCoords[0] = new aiVector3D[dest->mNumVertices]; + } else { + ASSIMP_LOG_WARN("Ogre imported UV0 type ", uv1Element->TypeToString(), " is not compatible with Assimp. Ignoring UV."); + uv1 = 0; + } + } + if (uv2) { + if (uv2Element->type == VertexElement::VET_FLOAT2 || uv2Element->type == VertexElement::VET_FLOAT3) { + dest->mNumUVComponents[1] = static_cast<unsigned int>(uv2Element->ComponentCount()); + dest->mTextureCoords[1] = new aiVector3D[dest->mNumVertices]; + } else { + ASSIMP_LOG_WARN("Ogre imported UV0 type ", uv2Element->TypeToString(), " is not compatible with Assimp. Ignoring UV."); + uv2 = 0; + } + } + + aiVector3D *uv1Dest = (uv1 ? dest->mTextureCoords[0] : 0); + aiVector3D *uv2Dest = (uv2 ? dest->mTextureCoords[1] : 0); + + MemoryStream *faces = indexData->buffer.get(); + for (size_t fi = 0, isize = indexData->IndexSize(), fsize = indexData->FaceSize(); + fi < dest->mNumFaces; ++fi) { + // Source Ogre face + aiFace ogreFace; + ogreFace.mNumIndices = 3; + ogreFace.mIndices = new unsigned int[3]; + + faces->Seek(fi * fsize, aiOrigin_SET); + if (indexData->is32bit) { + faces->Read(&ogreFace.mIndices[0], isize, 3); + } else { + uint16_t iout = 0; + for (size_t ii = 0; ii < 3; ++ii) { + faces->Read(&iout, isize, 1); + ogreFace.mIndices[ii] = static_cast<unsigned int>(iout); + } + } + + // Destination Assimp face + aiFace &face = dest->mFaces[fi]; + face.mNumIndices = 3; + face.mIndices = new unsigned int[3]; + + const size_t pos = fi * 3; + for (size_t v = 0; v < 3; ++v) { + const size_t newIndex = pos + v; + + // Write face index + face.mIndices[v] = static_cast<unsigned int>(newIndex); + + // Ogres vertex index to ref into the source buffers. + const size_t ogreVertexIndex = ogreFace.mIndices[v]; + src->AddVertexMapping(static_cast<uint32_t>(ogreVertexIndex), static_cast<uint32_t>(newIndex)); + + // Position + positions->Seek((vWidthPosition * ogreVertexIndex) + positionsElement->offset, aiOrigin_SET); + positions->Read(&dest->mVertices[newIndex], sizePosition, 1); + + // Normal + if (normals) { + normals->Seek((vWidthNormal * ogreVertexIndex) + normalsElement->offset, aiOrigin_SET); + normals->Read(&dest->mNormals[newIndex], sizeNormal, 1); + } + // UV0 + if (uv1 && uv1Dest) { + uv1->Seek((vWidthUv1 * ogreVertexIndex) + uv1Element->offset, aiOrigin_SET); + uv1->Read(&uv1Dest[newIndex], sizeUv1, 1); + uv1Dest[newIndex].y = (uv1Dest[newIndex].y * -1) + 1; // Flip UV from Ogre to Assimp form + } + // UV1 + if (uv2 && uv2Dest) { + uv2->Seek((vWidthUv2 * ogreVertexIndex) + uv2Element->offset, aiOrigin_SET); + uv2->Read(&uv2Dest[newIndex], sizeUv2, 1); + uv2Dest[newIndex].y = (uv2Dest[newIndex].y * -1) + 1; // Flip UV from Ogre to Assimp form + } + } + } + + // Bones and bone weights + if (parent->skeleton && boneAssignments) { + AssimpVertexBoneWeightList weights = src->AssimpBoneWeights(dest->mNumVertices); + std::set<uint16_t> referencedBones = src->ReferencedBonesByWeights(); + + dest->mNumBones = static_cast<unsigned int>(referencedBones.size()); + dest->mBones = new aiBone *[dest->mNumBones]; + + size_t assimpBoneIndex = 0; + for (std::set<uint16_t>::const_iterator rbIter = referencedBones.begin(), rbEnd = referencedBones.end(); rbIter != rbEnd; ++rbIter, ++assimpBoneIndex) { + Bone *bone = parent->skeleton->BoneById((*rbIter)); + dest->mBones[assimpBoneIndex] = bone->ConvertToAssimpBone(parent->skeleton, weights[bone->id]); + } + } + + return dest; +} + +// MeshXml + +MeshXml::MeshXml() : + skeleton(0), + sharedVertexData(0) { +} + +MeshXml::~MeshXml() { + Reset(); +} + +void MeshXml::Reset() { + OGRE_SAFE_DELETE(skeleton) + OGRE_SAFE_DELETE(sharedVertexData) + + for (auto &mesh : subMeshes) { + OGRE_SAFE_DELETE(mesh) + } + subMeshes.clear(); +} + +size_t MeshXml::NumSubMeshes() const { + return subMeshes.size(); +} + +SubMeshXml *MeshXml::GetSubMesh(uint16_t index) const { + for (size_t i = 0; i < subMeshes.size(); ++i) + if (subMeshes[i]->index == index) + return subMeshes[i]; + return 0; +} + +void MeshXml::ConvertToAssimpScene(aiScene *dest) { + // Setup + dest->mNumMeshes = static_cast<unsigned int>(NumSubMeshes()); + dest->mMeshes = new aiMesh *[dest->mNumMeshes]; + + // Create root node + dest->mRootNode = new aiNode(); + dest->mRootNode->mNumMeshes = dest->mNumMeshes; + dest->mRootNode->mMeshes = new unsigned int[dest->mRootNode->mNumMeshes]; + + // Export meshes + for (size_t i = 0; i < dest->mNumMeshes; ++i) { + dest->mMeshes[i] = subMeshes[i]->ConvertToAssimpMesh(this); + dest->mRootNode->mMeshes[i] = static_cast<unsigned int>(i); + } + + // Export skeleton + if (skeleton) { + // Bones + if (!skeleton->bones.empty()) { + BoneList rootBones = skeleton->RootBones(); + dest->mRootNode->mNumChildren = static_cast<unsigned int>(rootBones.size()); + dest->mRootNode->mChildren = new aiNode *[dest->mRootNode->mNumChildren]; + + for (size_t i = 0, len = rootBones.size(); i < len; ++i) { + dest->mRootNode->mChildren[i] = rootBones[i]->ConvertToAssimpNode(skeleton, dest->mRootNode); + } + } + + // Animations + if (!skeleton->animations.empty()) { + dest->mNumAnimations = static_cast<unsigned int>(skeleton->animations.size()); + dest->mAnimations = new aiAnimation *[dest->mNumAnimations]; + + for (size_t i = 0, len = skeleton->animations.size(); i < len; ++i) { + dest->mAnimations[i] = skeleton->animations[i]->ConvertToAssimpAnimation(); + } + } + } +} + +// SubMeshXml + +SubMeshXml::SubMeshXml() : + indexData(new IndexDataXml()), + vertexData(0) { +} + +SubMeshXml::~SubMeshXml() { + Reset(); +} + +void SubMeshXml::Reset(){ + OGRE_SAFE_DELETE(indexData) + OGRE_SAFE_DELETE(vertexData) +} + +aiMesh *SubMeshXml::ConvertToAssimpMesh(MeshXml *parent) { + aiMesh *dest = new aiMesh(); + dest->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + if (!name.empty()) + dest->mName = name; + + // Material index + if (materialIndex != -1) + dest->mMaterialIndex = materialIndex; + + // Faces + dest->mNumFaces = indexData->faceCount; + dest->mFaces = new aiFace[dest->mNumFaces]; + + // Assimp required unique vertices, we need to convert from Ogres shared indexing. + size_t uniqueVertexCount = dest->mNumFaces * 3; + dest->mNumVertices = static_cast<unsigned int>(uniqueVertexCount); + dest->mVertices = new aiVector3D[dest->mNumVertices]; + + VertexDataXml *src = (!usesSharedVertexData ? vertexData : parent->sharedVertexData); + bool boneAssignments = src->HasBoneAssignments(); + bool normals = src->HasNormals(); + size_t uvs = src->NumUvs(); + + // Prepare normals + if (normals) + dest->mNormals = new aiVector3D[dest->mNumVertices]; + + // Prepare UVs + for (size_t uvi = 0; uvi < uvs; ++uvi) { + dest->mNumUVComponents[uvi] = 2; + dest->mTextureCoords[uvi] = new aiVector3D[dest->mNumVertices]; + } + + for (size_t fi = 0; fi < dest->mNumFaces; ++fi) { + // Source Ogre face + aiFace &ogreFace = indexData->faces[fi]; + + // Destination Assimp face + aiFace &face = dest->mFaces[fi]; + face.mNumIndices = 3; + face.mIndices = new unsigned int[3]; + + const size_t pos = fi * 3; + for (size_t v = 0; v < 3; ++v) { + const size_t newIndex = pos + v; + + // Write face index + face.mIndices[v] = static_cast<unsigned int>(newIndex); + + // Ogres vertex index to ref into the source buffers. + const size_t ogreVertexIndex = ogreFace.mIndices[v]; + src->AddVertexMapping(static_cast<uint32_t>(ogreVertexIndex), static_cast<uint32_t>(newIndex)); + + // Position + dest->mVertices[newIndex] = src->positions[ogreVertexIndex]; + + // Normal + if (normals) + dest->mNormals[newIndex] = src->normals[ogreVertexIndex]; + + // UVs + for (size_t uvi = 0; uvi < uvs; ++uvi) { + aiVector3D *uvDest = dest->mTextureCoords[uvi]; + std::vector<aiVector3D> &uvSrc = src->uvs[uvi]; + uvDest[newIndex] = uvSrc[ogreVertexIndex]; + } + } + } + + // Bones and bone weights + if (parent->skeleton && boneAssignments) { + AssimpVertexBoneWeightList weights = src->AssimpBoneWeights(dest->mNumVertices); + std::set<uint16_t> referencedBones = src->ReferencedBonesByWeights(); + + dest->mNumBones = static_cast<unsigned int>(referencedBones.size()); + dest->mBones = new aiBone *[dest->mNumBones]; + + size_t assimpBoneIndex = 0; + for (std::set<uint16_t>::const_iterator rbIter = referencedBones.begin(), rbEnd = referencedBones.end(); rbIter != rbEnd; ++rbIter, ++assimpBoneIndex) { + Bone *bone = parent->skeleton->BoneById((*rbIter)); + dest->mBones[assimpBoneIndex] = bone->ConvertToAssimpBone(parent->skeleton, weights[bone->id]); + } + } + + return dest; +} + +// Animation + +Animation::Animation(Skeleton *parent) : + parentMesh(nullptr), + parentSkeleton(parent), + length(0.0f), + baseTime(-1.0f) { + // empty +} + +Animation::Animation(Mesh *parent) : + parentMesh(parent), + parentSkeleton(0), + length(0.0f), + baseTime(-1.0f) { + // empty +} + +VertexData *Animation::AssociatedVertexData(VertexAnimationTrack *track) const { + if (nullptr == parentMesh) { + return nullptr; + } + + bool sharedGeom = (track->target == 0); + if (sharedGeom) { + return parentMesh->sharedVertexData; + } + + return parentMesh->GetSubMesh(track->target - 1)->vertexData; +} + +aiAnimation *Animation::ConvertToAssimpAnimation() { + aiAnimation *anim = new aiAnimation(); + anim->mName = name; + anim->mDuration = static_cast<double>(length); + anim->mTicksPerSecond = 1.0; + + // Tracks + if (!tracks.empty()) { + anim->mNumChannels = static_cast<unsigned int>(tracks.size()); + anim->mChannels = new aiNodeAnim *[anim->mNumChannels]; + + for (size_t i = 0, len = tracks.size(); i < len; ++i) { + anim->mChannels[i] = tracks[i].ConvertToAssimpAnimationNode(parentSkeleton); + } + } + return anim; +} + +// Skeleton + +Skeleton::Skeleton() : + bones(), + animations(), + blendMode(ANIMBLEND_AVERAGE) { +} + +Skeleton::~Skeleton() { + Reset(); +} + +void Skeleton::Reset() { + for (auto &bone : bones) { + OGRE_SAFE_DELETE(bone) + } + bones.clear(); + for (auto &anim : animations) { + OGRE_SAFE_DELETE(anim) + } + animations.clear(); +} + +BoneList Skeleton::RootBones() const { + BoneList rootBones; + for (BoneList::const_iterator iter = bones.begin(); iter != bones.end(); ++iter) { + if (!(*iter)->IsParented()) + rootBones.push_back((*iter)); + } + return rootBones; +} + +size_t Skeleton::NumRootBones() const { + size_t num = 0; + for (BoneList::const_iterator iter = bones.begin(); iter != bones.end(); ++iter) { + if (!(*iter)->IsParented()) + num++; + } + return num; +} + +Bone *Skeleton::BoneByName(const std::string &name) const { + for (BoneList::const_iterator iter = bones.begin(); iter != bones.end(); ++iter) { + if ((*iter)->name == name) + return (*iter); + } + return 0; +} + +Bone *Skeleton::BoneById(uint16_t id) const { + for (BoneList::const_iterator iter = bones.begin(); iter != bones.end(); ++iter) { + if ((*iter)->id == id) + return (*iter); + } + return 0; +} + +// Bone + +Bone::Bone() : + id(0), + parent(0), + parentId(-1), + scale(1.0f, 1.0f, 1.0f) { +} + +bool Bone::IsParented() const { + return (parentId != -1 && parent != 0); +} + +uint16_t Bone::ParentId() const { + return static_cast<uint16_t>(parentId); +} + +void Bone::AddChild(Bone *bone) { + if (!bone) + return; + if (bone->IsParented()) + throw DeadlyImportError("Attaching child Bone that is already parented: ", bone->name); + + bone->parent = this; + bone->parentId = id; + children.push_back(bone->id); +} + +void Bone::CalculateWorldMatrixAndDefaultPose(Skeleton *skeleton) { + if (!IsParented()) + worldMatrix = aiMatrix4x4(scale, rotation, position).Inverse(); + else + worldMatrix = aiMatrix4x4(scale, rotation, position).Inverse() * parent->worldMatrix; + + defaultPose = aiMatrix4x4(scale, rotation, position); + + // Recursively for all children now that the parent matrix has been calculated. + for (auto boneId : children) { + Bone *child = skeleton->BoneById(boneId); + if (!child) { + throw DeadlyImportError("CalculateWorldMatrixAndDefaultPose: Failed to find child bone ", boneId, " for parent ", id, " ", name); + } + child->CalculateWorldMatrixAndDefaultPose(skeleton); + } +} + +aiNode *Bone::ConvertToAssimpNode(Skeleton *skeleton, aiNode *parentNode) { + // Bone node + aiNode *node = new aiNode(name); + node->mParent = parentNode; + node->mTransformation = defaultPose; + + // Children + if (!children.empty()) { + node->mNumChildren = static_cast<unsigned int>(children.size()); + node->mChildren = new aiNode *[node->mNumChildren]; + + for (size_t i = 0, len = children.size(); i < len; ++i) { + Bone *child = skeleton->BoneById(children[i]); + if (!child) { + throw DeadlyImportError("ConvertToAssimpNode: Failed to find child bone ", children[i], " for parent ", id, " ", name); + } + node->mChildren[i] = child->ConvertToAssimpNode(skeleton, node); + } + } + return node; +} + +aiBone *Bone::ConvertToAssimpBone(Skeleton * /*parent*/, const std::vector<aiVertexWeight> &boneWeights) { + aiBone *bone = new aiBone(); + bone->mName = name; + bone->mOffsetMatrix = worldMatrix; + + if (!boneWeights.empty()) { + bone->mNumWeights = static_cast<unsigned int>(boneWeights.size()); + bone->mWeights = new aiVertexWeight[boneWeights.size()]; + memcpy(bone->mWeights, &boneWeights[0], boneWeights.size() * sizeof(aiVertexWeight)); + } + + return bone; +} + +// VertexAnimationTrack + +VertexAnimationTrack::VertexAnimationTrack() : + type(VAT_NONE), + target(0) { +} + +aiNodeAnim *VertexAnimationTrack::ConvertToAssimpAnimationNode(Skeleton *skeleton) { + if (boneName.empty() || type != VAT_TRANSFORM) { + throw DeadlyImportError("VertexAnimationTrack::ConvertToAssimpAnimationNode: Cannot convert track that has no target bone name or is not type of VAT_TRANSFORM"); + } + + aiNodeAnim *nodeAnim = new aiNodeAnim(); + nodeAnim->mNodeName = boneName; + + Bone *bone = skeleton->BoneByName(boneName); + if (!bone) { + throw DeadlyImportError("VertexAnimationTrack::ConvertToAssimpAnimationNode: Failed to find bone ", boneName, " from parent Skeleton"); + } + + // Keyframes + size_t numKeyframes = transformKeyFrames.size(); + + nodeAnim->mPositionKeys = new aiVectorKey[numKeyframes]; + nodeAnim->mRotationKeys = new aiQuatKey[numKeyframes]; + nodeAnim->mScalingKeys = new aiVectorKey[numKeyframes]; + nodeAnim->mNumPositionKeys = static_cast<unsigned int>(numKeyframes); + nodeAnim->mNumRotationKeys = static_cast<unsigned int>(numKeyframes); + nodeAnim->mNumScalingKeys = static_cast<unsigned int>(numKeyframes); + + for (size_t kfi = 0; kfi < numKeyframes; ++kfi) { + TransformKeyFrame &kfSource = transformKeyFrames[kfi]; + + // Calculate the complete transformation from world space to bone space + aiVector3D pos; + aiQuaternion rot; + aiVector3D scale; + + aiMatrix4x4 finalTransform = bone->defaultPose * kfSource.Transform(); + finalTransform.Decompose(scale, rot, pos); + + double t = static_cast<double>(kfSource.timePos); + nodeAnim->mPositionKeys[kfi].mTime = t; + nodeAnim->mRotationKeys[kfi].mTime = t; + nodeAnim->mScalingKeys[kfi].mTime = t; + + nodeAnim->mPositionKeys[kfi].mValue = pos; + nodeAnim->mRotationKeys[kfi].mValue = rot; + nodeAnim->mScalingKeys[kfi].mValue = scale; + } + + return nodeAnim; +} + +// TransformKeyFrame + +TransformKeyFrame::TransformKeyFrame() : + timePos(0.0f), + scale(1.0f, 1.0f, 1.0f) { +} + +aiMatrix4x4 TransformKeyFrame::Transform() { + return aiMatrix4x4(scale, rotation, position); +} + +} // namespace Ogre +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER diff --git a/libs/assimp/code/AssetLib/Ogre/OgreStructs.h b/libs/assimp/code/AssetLib/Ogre/OgreStructs.h new file mode 100644 index 0000000..be9ffa0 --- /dev/null +++ b/libs/assimp/code/AssetLib/Ogre/OgreStructs.h @@ -0,0 +1,696 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef AI_OGRESTRUCTS_H_INC +#define AI_OGRESTRUCTS_H_INC + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + +#include <assimp/MemoryIOWrapper.h> +#include <memory> +#include <assimp/mesh.h> +#include <map> +#include <vector> +#include <set> + +struct aiNodeAnim; +struct aiAnimation; +struct aiNode; +struct aiMaterial; +struct aiScene; + +/** @note Parts of this implementation, for example enums, deserialization constants and logic + has been copied directly with minor modifications from the MIT licensed Ogre3D code base. + See more from https://bitbucket.org/sinbad/ogre. */ + +namespace Assimp +{ +namespace Ogre +{ + +// Forward decl +class Mesh; +class MeshXml; +class SubMesh; +class SubMeshXml; +class Skeleton; + +#define OGRE_SAFE_DELETE(p) delete p; p=0; + +// Typedefs +typedef Assimp::MemoryIOStream MemoryStream; +typedef std::shared_ptr<MemoryStream> MemoryStreamPtr; +typedef std::map<uint16_t, MemoryStreamPtr> VertexBufferBindings; + +// Ogre Vertex Element +class VertexElement +{ +public: + /// Vertex element semantics, used to identify the meaning of vertex buffer contents + enum Semantic { + /// Position, 3 reals per vertex + VES_POSITION = 1, + /// Blending weights + VES_BLEND_WEIGHTS = 2, + /// Blending indices + VES_BLEND_INDICES = 3, + /// Normal, 3 reals per vertex + VES_NORMAL = 4, + /// Diffuse colours + VES_DIFFUSE = 5, + /// Specular colours + VES_SPECULAR = 6, + /// Texture coordinates + VES_TEXTURE_COORDINATES = 7, + /// Binormal (Y axis if normal is Z) + VES_BINORMAL = 8, + /// Tangent (X axis if normal is Z) + VES_TANGENT = 9, + /// The number of VertexElementSemantic elements (note - the first value VES_POSITION is 1) + VES_COUNT = 9 + }; + + /// Vertex element type, used to identify the base types of the vertex contents + enum Type + { + VET_FLOAT1 = 0, + VET_FLOAT2 = 1, + VET_FLOAT3 = 2, + VET_FLOAT4 = 3, + /// alias to more specific colour type - use the current rendersystem's colour packing + VET_COLOUR = 4, + VET_SHORT1 = 5, + VET_SHORT2 = 6, + VET_SHORT3 = 7, + VET_SHORT4 = 8, + VET_UBYTE4 = 9, + /// D3D style compact colour + VET_COLOUR_ARGB = 10, + /// GL style compact colour + VET_COLOUR_ABGR = 11, + VET_DOUBLE1 = 12, + VET_DOUBLE2 = 13, + VET_DOUBLE3 = 14, + VET_DOUBLE4 = 15, + VET_USHORT1 = 16, + VET_USHORT2 = 17, + VET_USHORT3 = 18, + VET_USHORT4 = 19, + VET_INT1 = 20, + VET_INT2 = 21, + VET_INT3 = 22, + VET_INT4 = 23, + VET_UINT1 = 24, + VET_UINT2 = 25, + VET_UINT3 = 26, + VET_UINT4 = 27 + }; + + VertexElement(); + + /// Size of the vertex element in bytes. + size_t Size() const; + + /// Count of components in this element, eg. VET_FLOAT3 return 3. + size_t ComponentCount() const; + + /// Type as string. + std::string TypeToString(); + + /// Semantic as string. + std::string SemanticToString(); + + static size_t TypeSize(Type type); + static size_t ComponentCount(Type type); + static std::string TypeToString(Type type); + static std::string SemanticToString(Semantic semantic); + + uint16_t index; + uint16_t source; + uint16_t offset; + Type type; + Semantic semantic; +}; +typedef std::vector<VertexElement> VertexElementList; + +/// Ogre Vertex Bone Assignment +struct VertexBoneAssignment +{ + uint32_t vertexIndex; + uint16_t boneIndex; + float weight; +}; +typedef std::vector<VertexBoneAssignment> VertexBoneAssignmentList; +typedef std::map<uint32_t, VertexBoneAssignmentList > VertexBoneAssignmentsMap; +typedef std::map<uint16_t, std::vector<aiVertexWeight> > AssimpVertexBoneWeightList; + +// Ogre Vertex Data interface, inherited by the binary and XML implementations. +class IVertexData +{ +public: + IVertexData(); + + /// Returns if bone assignments are available. + bool HasBoneAssignments() const; + + /// Add vertex mapping from old to new index. + void AddVertexMapping(uint32_t oldIndex, uint32_t newIndex); + + /// Returns re-mapped bone assignments. + /** @note Uses mappings added via AddVertexMapping. */ + AssimpVertexBoneWeightList AssimpBoneWeights(size_t vertices); + + /// Returns a set of bone indexes that are referenced by bone assignments (weights). + std::set<uint16_t> ReferencedBonesByWeights() const; + + /// Vertex count. + uint32_t count; + + /// Bone assignments. + VertexBoneAssignmentList boneAssignments; + +private: + void BoneAssignmentsForVertex(uint32_t currentIndex, uint32_t newIndex, VertexBoneAssignmentList &dest) const; + + std::map<uint32_t, std::vector<uint32_t> > vertexIndexMapping; + VertexBoneAssignmentsMap boneAssignmentsMap; +}; + +// Ogre Vertex Data +class VertexData : public IVertexData +{ +public: + VertexData(); + ~VertexData(); + + /// Releases all memory that this data structure owns. + void Reset(); + + /// Get vertex size for @c source. + uint32_t VertexSize(uint16_t source) const; + + /// Get vertex buffer for @c source. + MemoryStream *VertexBuffer(uint16_t source); + + /// Get vertex element for @c semantic for @c index. + VertexElement *GetVertexElement(VertexElement::Semantic semantic, uint16_t index = 0); + + /// Vertex elements. + VertexElementList vertexElements; + + /// Vertex buffers mapped to bind index. + VertexBufferBindings vertexBindings; +}; + +// Ogre Index Data +class IndexData +{ +public: + IndexData(); + ~IndexData(); + + /// Releases all memory that this data structure owns. + void Reset(); + + /// Index size in bytes. + size_t IndexSize() const; + + /// Face size in bytes. + size_t FaceSize() const; + + /// Index count. + uint32_t count; + + /// Face count. + uint32_t faceCount; + + /// If has 32-bit indexes. + bool is32bit; + + /// Index buffer. + MemoryStreamPtr buffer; +}; + +/// Ogre Pose +class Pose +{ +public: + struct Vertex + { + uint32_t index; + aiVector3D offset; + aiVector3D normal; + }; + typedef std::map<uint32_t, Vertex> PoseVertexMap; + + Pose() : target(0), hasNormals(false) {} + + /// Name. + std::string name; + + /// Target. + uint16_t target; + + /// Does vertices map have normals. + bool hasNormals; + + /// Vertex offset and normals. + PoseVertexMap vertices; +}; +typedef std::vector<Pose*> PoseList; + +/// Ogre Pose Key Frame Ref +struct PoseRef +{ + uint16_t index; + float influence; +}; +typedef std::vector<PoseRef> PoseRefList; + +/// Ogre Pose Key Frame +struct PoseKeyFrame +{ + /// Time position in the animation. + float timePos; + + PoseRefList references; +}; +typedef std::vector<PoseKeyFrame> PoseKeyFrameList; + +/// Ogre Morph Key Frame +struct MorphKeyFrame +{ + /// Time position in the animation. + float timePos; + + MemoryStreamPtr buffer; +}; +typedef std::vector<MorphKeyFrame> MorphKeyFrameList; + +/// Ogre animation key frame +struct TransformKeyFrame +{ + TransformKeyFrame(); + + aiMatrix4x4 Transform(); + + float timePos; + + aiQuaternion rotation; + aiVector3D position; + aiVector3D scale; +}; +typedef std::vector<TransformKeyFrame> TransformKeyFrameList; + +/// Ogre Animation Track +struct VertexAnimationTrack +{ + enum Type + { + /// No animation + VAT_NONE = 0, + /// Morph animation is made up of many interpolated snapshot keyframes + VAT_MORPH = 1, + /// Pose animation is made up of a single delta pose keyframe + VAT_POSE = 2, + /// Keyframe that has its on pos, rot and scale for a time position + VAT_TRANSFORM = 3 + }; + + VertexAnimationTrack(); + + /// Convert to Assimp node animation. + aiNodeAnim *ConvertToAssimpAnimationNode(Skeleton *skeleton); + + // Animation type. + Type type; + + /// Vertex data target. + /** 0 == shared geometry + >0 == submesh index + 1 */ + uint16_t target; + + /// Only valid for VAT_TRANSFORM. + std::string boneName; + + /// Only one of these will contain key frames, depending on the type enum. + PoseKeyFrameList poseKeyFrames; + MorphKeyFrameList morphKeyFrames; + TransformKeyFrameList transformKeyFrames; +}; +typedef std::vector<VertexAnimationTrack> VertexAnimationTrackList; + +/// Ogre Animation +class Animation +{ +public: + explicit Animation(Skeleton *parent); + explicit Animation(Mesh *parent); + + /// Returns the associated vertex data for a track in this animation. + /** @note Only valid to call when parent Mesh is set. */ + VertexData *AssociatedVertexData(VertexAnimationTrack *track) const; + + /// Convert to Assimp animation. + aiAnimation *ConvertToAssimpAnimation(); + + /// Parent mesh. + /** @note Set only when animation is read from a mesh. */ + Mesh *parentMesh; + + /// Parent skeleton. + /** @note Set only when animation is read from a skeleton. */ + Skeleton *parentSkeleton; + + /// Animation name. + std::string name; + + /// Base animation name. + std::string baseName; + + /// Length in seconds. + float length; + + /// Base animation key time. + float baseTime; + + /// Animation tracks. + VertexAnimationTrackList tracks; +}; +typedef std::vector<Animation*> AnimationList; + +/// Ogre Bone +class Bone +{ +public: + Bone(); + + /// Returns if this bone is parented. + bool IsParented() const; + + /// Parent index as uint16_t. Internally int32_t as -1 means unparented. + uint16_t ParentId() const; + + /// Add child bone. + void AddChild(Bone *bone); + + /// Calculates the world matrix for bone and its children. + void CalculateWorldMatrixAndDefaultPose(Skeleton *skeleton); + + /// Convert to Assimp node (animation nodes). + aiNode *ConvertToAssimpNode(Skeleton *parent, aiNode *parentNode = 0); + + /// Convert to Assimp bone (mesh bones). + aiBone *ConvertToAssimpBone(Skeleton *parent, const std::vector<aiVertexWeight> &boneWeights); + + uint16_t id; + std::string name; + + Bone *parent; + int32_t parentId; + std::vector<uint16_t> children; + + aiVector3D position; + aiQuaternion rotation; + aiVector3D scale; + + aiMatrix4x4 worldMatrix; + aiMatrix4x4 defaultPose; +}; +typedef std::vector<Bone*> BoneList; + +/// Ogre Skeleton +class Skeleton +{ +public: + enum BlendMode + { + /// Animations are applied by calculating a weighted average of all animations + ANIMBLEND_AVERAGE = 0, + /// Animations are applied by calculating a weighted cumulative total + ANIMBLEND_CUMULATIVE = 1 + }; + + Skeleton(); + ~Skeleton(); + + /// Releases all memory that this data structure owns. + void Reset(); + + /// Returns unparented root bones. + BoneList RootBones() const; + + /// Returns number of unparented root bones. + size_t NumRootBones() const; + + /// Get bone by name. + Bone *BoneByName(const std::string &name) const; + + /// Get bone by id. + Bone *BoneById(uint16_t id) const; + + BoneList bones; + AnimationList animations; + + /// @todo Take blend mode into account, but where? + BlendMode blendMode; +}; + +/// Ogre Sub Mesh interface, inherited by the binary and XML implementations. +class ISubMesh +{ +public: + /// @note Full list of Ogre types, not all of them are supported and exposed to Assimp. + enum OperationType + { + /// A list of points, 1 vertex per point + OT_POINT_LIST = 1, + /// A list of lines, 2 vertices per line + OT_LINE_LIST = 2, + /// A strip of connected lines, 1 vertex per line plus 1 start vertex + OT_LINE_STRIP = 3, + /// A list of triangles, 3 vertices per triangle + OT_TRIANGLE_LIST = 4, + /// A strip of triangles, 3 vertices for the first triangle, and 1 per triangle after that + OT_TRIANGLE_STRIP = 5, + /// A fan of triangles, 3 vertices for the first triangle, and 1 per triangle after that + OT_TRIANGLE_FAN = 6 + }; + + ISubMesh(); + + /// SubMesh index. + unsigned int index; + + /// SubMesh name. + std::string name; + + /// Material used by this submesh. + std::string materialRef; + + /// Texture alias information. + std::string textureAliasName; + std::string textureAliasRef; + + /// Assimp scene material index used by this submesh. + /** -1 if no material or material could not be imported. */ + int materialIndex; + + /// If submesh uses shared geometry from parent mesh. + bool usesSharedVertexData; + + /// Operation type. + OperationType operationType; +}; + +/// Ogre SubMesh +class SubMesh : public ISubMesh +{ +public: + SubMesh(); + ~SubMesh(); + + /// Releases all memory that this data structure owns. + /** @note Vertex and index data contains shared ptrs + that are freed automatically. In practice the ref count + should be 0 after this reset. */ + void Reset(); + + /// Convert to Assimp mesh. + aiMesh *ConvertToAssimpMesh(Mesh *parent); + + /// Vertex data. + VertexData *vertexData; + + /// Index data. + IndexData *indexData; +}; +typedef std::vector<SubMesh*> SubMeshList; + +/// Ogre Mesh +class Mesh +{ +public: + /// Constructor. + Mesh(); + + /// Destructor. + ~Mesh(); + + /// Releases all memory that this data structure owns. + void Reset(); + + /// Returns number of subMeshes. + size_t NumSubMeshes() const; + + /// Returns submesh for @c index. + SubMesh *GetSubMesh( size_t index) const; + + /// Convert mesh to Assimp scene. + void ConvertToAssimpScene(aiScene* dest); + + /// Mesh has skeletal animations. + bool hasSkeletalAnimations; + + /// Skeleton reference. + std::string skeletonRef; + + /// Skeleton. + Skeleton *skeleton; + + /// Vertex data + VertexData *sharedVertexData; + + /// Sub meshes. + SubMeshList subMeshes; + + /// Animations + AnimationList animations; + + /// Poses + PoseList poses; +}; + +/// Ogre XML Vertex Data +class VertexDataXml : public IVertexData +{ +public: + VertexDataXml(); + + bool HasPositions() const; + bool HasNormals() const; + bool HasTangents() const; + bool HasUvs() const; + size_t NumUvs() const; + + std::vector<aiVector3D> positions; + std::vector<aiVector3D> normals; + std::vector<aiVector3D> tangents; + std::vector<std::vector<aiVector3D> > uvs; +}; + +/// Ogre XML Index Data +class IndexDataXml +{ +public: + IndexDataXml() : faceCount(0) {} + + /// Face count. + uint32_t faceCount; + + std::vector<aiFace> faces; +}; + +/// Ogre XML SubMesh +class SubMeshXml : public ISubMesh +{ +public: + SubMeshXml(); + ~SubMeshXml(); + + /// Releases all memory that this data structure owns. + void Reset(); + + aiMesh *ConvertToAssimpMesh(MeshXml *parent); + + IndexDataXml *indexData; + VertexDataXml *vertexData; +}; +typedef std::vector<SubMeshXml*> SubMeshXmlList; + +/// Ogre XML Mesh +class MeshXml +{ +public: + MeshXml(); + ~MeshXml(); + + /// Releases all memory that this data structure owns. + void Reset(); + + /// Returns number of subMeshes. + size_t NumSubMeshes() const; + + /// Returns submesh for @c index. + SubMeshXml *GetSubMesh(uint16_t index) const; + + /// Convert mesh to Assimp scene. + void ConvertToAssimpScene(aiScene* dest); + + /// Skeleton reference. + std::string skeletonRef; + + /// Skeleton. + Skeleton *skeleton; + + /// Vertex data + VertexDataXml *sharedVertexData; + + /// Sub meshes. + SubMeshXmlList subMeshes; +}; + +} // Ogre +} // Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER +#endif // AI_OGRESTRUCTS_H_INC diff --git a/libs/assimp/code/AssetLib/Ogre/OgreXmlSerializer.cpp b/libs/assimp/code/AssetLib/Ogre/OgreXmlSerializer.cpp new file mode 100644 index 0000000..545107e --- /dev/null +++ b/libs/assimp/code/AssetLib/Ogre/OgreXmlSerializer.cpp @@ -0,0 +1,755 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#include "OgreXmlSerializer.h" +#include "OgreBinarySerializer.h" +#include "OgreParsingUtils.h" + +#include <assimp/TinyFormatter.h> +#include <assimp/DefaultLogger.hpp> + +#include <memory> + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + +// Define as 1 to get verbose logging. +#define OGRE_XML_SERIALIZER_DEBUG 0 + +namespace Assimp { +namespace Ogre { + +//AI_WONT_RETURN void ThrowAttibuteError(const XmlParser *reader, const std::string &name, const std::string &error = "") AI_WONT_RETURN_SUFFIX; + +AI_WONT_RETURN void ThrowAttibuteError(const std::string &nodeName, const std::string &name, const std::string &error) { + if (!error.empty()) { + throw DeadlyImportError(error, " in node '", nodeName, "' and attribute '", name, "'"); + } else { + throw DeadlyImportError("Attribute '", name, "' does not exist in node '", nodeName, "'"); + } +} + +template <> +int32_t OgreXmlSerializer::ReadAttribute<int32_t>(XmlNode &xmlNode, const char *name) const { + if (!XmlParser::hasAttribute(xmlNode, name)) { + ThrowAttibuteError(xmlNode.name(), name, "Not found"); + } + pugi::xml_attribute attr = xmlNode.attribute(name); + return static_cast<int32_t>(attr.as_int()); +} + +template <> +uint32_t OgreXmlSerializer::ReadAttribute<uint32_t>(XmlNode &xmlNode, const char *name) const { + if (!XmlParser::hasAttribute(xmlNode, name)) { + ThrowAttibuteError(xmlNode.name(), name, "Not found"); + } + + // @note This is hackish. But we are never expecting unsigned values that go outside the + // int32_t range. Just monitor for negative numbers and kill the import. + int32_t temp = ReadAttribute<int32_t>(xmlNode, name); + if (temp < 0) { + ThrowAttibuteError(xmlNode.name(), name, "Found a negative number value where expecting a uint32_t value"); + } + + return static_cast<uint32_t>(temp); +} + +template <> +uint16_t OgreXmlSerializer::ReadAttribute<uint16_t>(XmlNode &xmlNode, const char *name) const { + if (!XmlParser::hasAttribute(xmlNode, name)) { + ThrowAttibuteError(xmlNode.name(), name, "Not found"); + } + + return static_cast<uint16_t>(xmlNode.attribute(name).as_int()); +} + +template <> +float OgreXmlSerializer::ReadAttribute<float>(XmlNode &xmlNode, const char *name) const { + if (!XmlParser::hasAttribute(xmlNode, name)) { + ThrowAttibuteError(xmlNode.name(), name, "Not found"); + } + + return xmlNode.attribute(name).as_float(); +} + +template <> +std::string OgreXmlSerializer::ReadAttribute<std::string>(XmlNode &xmlNode, const char *name) const { + if (!XmlParser::hasAttribute(xmlNode, name)) { + ThrowAttibuteError(xmlNode.name(), name, "Not found"); + } + + return xmlNode.attribute(name).as_string(); +} + +template <> +bool OgreXmlSerializer::ReadAttribute<bool>(XmlNode &xmlNode, const char *name) const { + std::string value = ai_tolower(ReadAttribute<std::string>(xmlNode, name)); + if (ASSIMP_stricmp(value, "true") == 0) { + return true; + } else if (ASSIMP_stricmp(value, "false") == 0) { + return false; + } + + ThrowAttibuteError(xmlNode.name(), name, "Boolean value is expected to be 'true' or 'false', encountered '" + value + "'"); + return false; +} + +// Mesh XML constants + +// <mesh> +static const char *nnMesh = "mesh"; +static const char *nnSharedGeometry = "sharedgeometry"; +static const char *nnSubMeshes = "submeshes"; +static const char *nnSubMesh = "submesh"; +//static const char *nnSubMeshNames = "submeshnames"; +static const char *nnSkeletonLink = "skeletonlink"; +//static const char *nnLOD = "levelofdetail"; +//static const char *nnExtremes = "extremes"; +//static const char *nnPoses = "poses"; +static const char *nnAnimations = "animations"; + +// <submesh> +static const char *nnFaces = "faces"; +static const char *nnFace = "face"; +static const char *nnGeometry = "geometry"; +//static const char *nnTextures = "textures"; + +// <mesh/submesh> +static const char *nnBoneAssignments = "boneassignments"; + +// <sharedgeometry/geometry> +static const char *nnVertexBuffer = "vertexbuffer"; + +// <vertexbuffer> +//static const char *nnVertex = "vertex"; +static const char *nnPosition = "position"; +static const char *nnNormal = "normal"; +static const char *nnTangent = "tangent"; +//static const char *nnBinormal = "binormal"; +static const char *nnTexCoord = "texcoord"; +//static const char *nnColorDiffuse = "colour_diffuse"; +//static const char *nnColorSpecular = "colour_specular"; + +// <boneassignments> +static const char *nnVertexBoneAssignment = "vertexboneassignment"; + +// Skeleton XML constants + +// <skeleton> +static const char *nnSkeleton = "skeleton"; +static const char *nnBones = "bones"; +static const char *nnBoneHierarchy = "bonehierarchy"; +//static const char *nnAnimationLinks = "animationlinks"; + +// <bones> +static const char *nnBone = "bone"; +static const char *nnRotation = "rotation"; +static const char *nnAxis = "axis"; +static const char *nnScale = "scale"; + +// <bonehierarchy> +static const char *nnBoneParent = "boneparent"; + +// <animations> +static const char *nnAnimation = "animation"; +static const char *nnTracks = "tracks"; + +// <tracks> +static const char *nnTrack = "track"; +static const char *nnKeyFrames = "keyframes"; +static const char *nnKeyFrame = "keyframe"; +static const char *nnTranslate = "translate"; +static const char *nnRotate = "rotate"; + +// Common XML constants +static const char *anX = "x"; +static const char *anY = "y"; +static const char *anZ = "z"; + +// Mesh + +OgreXmlSerializer::OgreXmlSerializer(XmlParser *parser) : + mParser(parser) { + // empty +} + +MeshXml *OgreXmlSerializer::ImportMesh(XmlParser *parser) { + if (nullptr == parser) { + return nullptr; + } + + OgreXmlSerializer serializer(parser); + + MeshXml *mesh = new MeshXml(); + serializer.ReadMesh(mesh); + + return mesh; +} + +void OgreXmlSerializer::ReadMesh(MeshXml *mesh) { + XmlNode root = mParser->getRootNode(); + if (nullptr == root) { + throw DeadlyImportError("Root node is <" + std::string(root.name()) + "> expecting <mesh>"); + } + + XmlNode startNode = root.child(nnMesh); + if (startNode.empty()) { + throw DeadlyImportError("Root node is <" + std::string(root.name()) + "> expecting <mesh>"); + } + for (XmlNode currentNode : startNode.children()) { + const std::string currentName = currentNode.name(); + if (currentName == nnSharedGeometry) { + mesh->sharedVertexData = new VertexDataXml(); + ReadGeometry(currentNode, mesh->sharedVertexData); + } else if (currentName == nnSubMeshes) { + for (XmlNode &subMeshesNode : currentNode.children()) { + const std::string ¤tSMName = subMeshesNode.name(); + if (currentSMName == nnSubMesh) { + ReadSubMesh(subMeshesNode, mesh); + } + } + } else if (currentName == nnBoneAssignments) { + ReadBoneAssignments(currentNode, mesh->sharedVertexData); + } else if (currentName == nnSkeletonLink) { + } + } + + ASSIMP_LOG_VERBOSE_DEBUG("Reading Mesh"); +} + +void OgreXmlSerializer::ReadGeometry(XmlNode &node, VertexDataXml *dest) { + dest->count = ReadAttribute<uint32_t>(node, "vertexcount"); + ASSIMP_LOG_VERBOSE_DEBUG(" - Reading geometry of ", dest->count, " vertices"); + + for (XmlNode currentNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == nnVertexBuffer) { + ReadGeometryVertexBuffer(currentNode, dest); + } + } +} + +void OgreXmlSerializer::ReadGeometryVertexBuffer(XmlNode &node, VertexDataXml *dest) { + bool positions = (XmlParser::hasAttribute(node, "positions") && ReadAttribute<bool>(node, "positions")); + bool normals = (XmlParser::hasAttribute(node, "normals") && ReadAttribute<bool>(node, "normals")); + bool tangents = (XmlParser::hasAttribute(node, "tangents") && ReadAttribute<bool>(node, "tangents")); + uint32_t uvs = (XmlParser::hasAttribute(node, "texture_coords") ? ReadAttribute<uint32_t>(node, "texture_coords") : 0); + + // Not having positions is a error only if a previous vertex buffer did not have them. + if (!positions && !dest->HasPositions()) { + throw DeadlyImportError("Vertex buffer does not contain positions!"); + } + + if (positions) { + ASSIMP_LOG_VERBOSE_DEBUG(" - Contains positions"); + dest->positions.reserve(dest->count); + } + if (normals) { + ASSIMP_LOG_VERBOSE_DEBUG(" - Contains normals"); + dest->normals.reserve(dest->count); + } + if (tangents) { + ASSIMP_LOG_VERBOSE_DEBUG(" - Contains tangents"); + dest->tangents.reserve(dest->count); + } + if (uvs > 0) { + ASSIMP_LOG_VERBOSE_DEBUG(" - Contains ", uvs, " texture coords"); + dest->uvs.resize(uvs); + for (size_t i = 0, len = dest->uvs.size(); i < len; ++i) { + dest->uvs[i].reserve(dest->count); + } + } + + for (XmlNode currentNode : node.children("vertex")) { + for (XmlNode vertexNode : currentNode.children()) { + const std::string ¤tName = vertexNode.name(); + if (positions && currentName == nnPosition) { + aiVector3D pos; + pos.x = ReadAttribute<float>(vertexNode, anX); + pos.y = ReadAttribute<float>(vertexNode, anY); + pos.z = ReadAttribute<float>(vertexNode, anZ); + dest->positions.push_back(pos); + } else if (normals && currentName == nnNormal) { + aiVector3D normal; + normal.x = ReadAttribute<float>(vertexNode, anX); + normal.y = ReadAttribute<float>(vertexNode, anY); + normal.z = ReadAttribute<float>(vertexNode, anZ); + dest->normals.push_back(normal); + } else if (tangents && currentName == nnTangent) { + aiVector3D tangent; + tangent.x = ReadAttribute<float>(vertexNode, anX); + tangent.y = ReadAttribute<float>(vertexNode, anY); + tangent.z = ReadAttribute<float>(vertexNode, anZ); + dest->tangents.push_back(tangent); + } else if (uvs > 0 && currentName == nnTexCoord) { + for (auto &curUvs : dest->uvs) { + aiVector3D uv; + uv.x = ReadAttribute<float>(vertexNode, "u"); + uv.y = (ReadAttribute<float>(vertexNode, "v") * -1) + 1; // Flip UV from Ogre to Assimp form + curUvs.push_back(uv); + } + } + } + } + + // Sanity checks + if (dest->positions.size() != dest->count) { + throw DeadlyImportError("Read only ", dest->positions.size(), " positions when should have read ", dest->count); + } + if (normals && dest->normals.size() != dest->count) { + throw DeadlyImportError("Read only ", dest->normals.size(), " normals when should have read ", dest->count); + } + if (tangents && dest->tangents.size() != dest->count) { + throw DeadlyImportError("Read only ", dest->tangents.size(), " tangents when should have read ", dest->count); + } + for (unsigned int i = 0; i < dest->uvs.size(); ++i) { + if (dest->uvs[i].size() != dest->count) { + throw DeadlyImportError("Read only ", dest->uvs[i].size(), + " uvs for uv index ", i, " when should have read ", dest->count); + } + } +} + +void OgreXmlSerializer::ReadSubMesh(XmlNode &node, MeshXml *mesh) { + static const char *anMaterial = "material"; + static const char *anUseSharedVertices = "usesharedvertices"; + static const char *anCount = "count"; + static const char *anV1 = "v1"; + static const char *anV2 = "v2"; + static const char *anV3 = "v3"; + static const char *anV4 = "v4"; + + SubMeshXml *submesh = new SubMeshXml(); + + if (XmlParser::hasAttribute(node, anMaterial)) { + submesh->materialRef = ReadAttribute<std::string>(node, anMaterial); + } + if (XmlParser::hasAttribute(node, anUseSharedVertices)) { + submesh->usesSharedVertexData = ReadAttribute<bool>(node, anUseSharedVertices); + } + + ASSIMP_LOG_VERBOSE_DEBUG("Reading SubMesh ", mesh->subMeshes.size()); + ASSIMP_LOG_VERBOSE_DEBUG(" - Material: '", submesh->materialRef, "'"); + ASSIMP_LOG_VERBOSE_DEBUG(" - Uses shared geometry: ", (submesh->usesSharedVertexData ? "true" : "false")); + + // TODO: maybe we have always just 1 faces and 1 geometry and always in this order. this loop will only work correct, when the order + // of faces and geometry changed, and not if we have more than one of one + /// @todo Fix above comment with better read logic below + + bool quadWarned = false; + + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == nnFaces) { + submesh->indexData->faceCount = ReadAttribute<uint32_t>(currentNode, anCount); + submesh->indexData->faces.reserve(submesh->indexData->faceCount); + for (XmlNode currentChildNode : currentNode.children()) { + const std::string ¤tChildName = currentChildNode.name(); + if (currentChildName == nnFace) { + aiFace face; + face.mNumIndices = 3; + face.mIndices = new unsigned int[3]; + face.mIndices[0] = ReadAttribute<uint32_t>(currentChildNode, anV1); + face.mIndices[1] = ReadAttribute<uint32_t>(currentChildNode, anV2); + face.mIndices[2] = ReadAttribute<uint32_t>(currentChildNode, anV3); + /// @todo Support quads if Ogre even supports them in XML (I'm not sure but I doubt it) + if (!quadWarned && XmlParser::hasAttribute(currentChildNode, anV4)) { + ASSIMP_LOG_WARN("Submesh <face> has quads with <v4>, only triangles are supported at the moment!"); + quadWarned = true; + } + submesh->indexData->faces.push_back(face); + } + } + if (submesh->indexData->faces.size() == submesh->indexData->faceCount) { + ASSIMP_LOG_VERBOSE_DEBUG(" - Faces ", submesh->indexData->faceCount); + } else { + throw DeadlyImportError("Read only ", submesh->indexData->faces.size(), " faces when should have read ", submesh->indexData->faceCount); + } + } else if (currentName == nnGeometry) { + if (submesh->usesSharedVertexData) { + throw DeadlyImportError("Found <geometry> in <submesh> when use shared geometry is true. Invalid mesh file."); + } + + submesh->vertexData = new VertexDataXml(); + ReadGeometry(currentNode, submesh->vertexData); + } else if (currentName == nnBoneAssignments) { + ReadBoneAssignments(currentNode, submesh->vertexData); + } + } + + submesh->index = static_cast<unsigned int>(mesh->subMeshes.size()); + mesh->subMeshes.push_back(submesh); +} + +void OgreXmlSerializer::ReadBoneAssignments(XmlNode &node, VertexDataXml *dest) { + if (!dest) { + throw DeadlyImportError("Cannot read bone assignments, vertex data is null."); + } + + static const char *anVertexIndex = "vertexindex"; + static const char *anBoneIndex = "boneindex"; + static const char *anWeight = "weight"; + + std::set<uint32_t> influencedVertices; + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == nnVertexBoneAssignment) { + VertexBoneAssignment ba; + ba.vertexIndex = ReadAttribute<uint32_t>(currentNode, anVertexIndex); + ba.boneIndex = ReadAttribute<uint16_t>(currentNode, anBoneIndex); + ba.weight = ReadAttribute<float>(currentNode, anWeight); + + dest->boneAssignments.push_back(ba); + influencedVertices.insert(ba.vertexIndex); + } + } + + /** Normalize bone weights. + Some exporters won't care if the sum of all bone weights + for a single vertex equals 1 or not, so validate here. */ + const float epsilon = 0.05f; + for (const uint32_t vertexIndex : influencedVertices) { + float sum = 0.0f; + for (VertexBoneAssignmentList::const_iterator baIter = dest->boneAssignments.begin(), baEnd = dest->boneAssignments.end(); baIter != baEnd; ++baIter) { + if (baIter->vertexIndex == vertexIndex) + sum += baIter->weight; + } + if ((sum < (1.0f - epsilon)) || (sum > (1.0f + epsilon))) { + for (auto &boneAssign : dest->boneAssignments) { + if (boneAssign.vertexIndex == vertexIndex) + boneAssign.weight /= sum; + } + } + } + + ASSIMP_LOG_VERBOSE_DEBUG(" - ", dest->boneAssignments.size(), " bone assignments"); +} + +// Skeleton + +bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh) { + if (!mesh || mesh->skeletonRef.empty()) + return false; + + // Highly unusual to see in read world cases but support + // XML mesh referencing a binary skeleton file. + if (EndsWith(mesh->skeletonRef, ".skeleton", false)) { + if (OgreBinarySerializer::ImportSkeleton(pIOHandler, mesh)) + return true; + + /** Last fallback if .skeleton failed to be read. Try reading from + .skeleton.xml even if the XML file referenced a binary skeleton. + @note This logic was in the previous version and I don't want to break + old code that might depends on it. */ + mesh->skeletonRef = mesh->skeletonRef + ".xml"; + } + + XmlParserPtr xmlParser = OpenXmlParser(pIOHandler, mesh->skeletonRef); + if (!xmlParser.get()) + return false; + + Skeleton *skeleton = new Skeleton(); + OgreXmlSerializer serializer(xmlParser.get()); + XmlNode root = xmlParser->getRootNode(); + serializer.ReadSkeleton(root, skeleton); + mesh->skeleton = skeleton; + return true; +} + +bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, Mesh *mesh) { + if (!mesh || mesh->skeletonRef.empty()) { + return false; + } + + XmlParserPtr xmlParser = OpenXmlParser(pIOHandler, mesh->skeletonRef); + if (!xmlParser.get()) { + return false; + } + + Skeleton *skeleton = new Skeleton(); + OgreXmlSerializer serializer(xmlParser.get()); + XmlNode root = xmlParser->getRootNode(); + + serializer.ReadSkeleton(root, skeleton); + mesh->skeleton = skeleton; + + return true; +} + +XmlParserPtr OgreXmlSerializer::OpenXmlParser(Assimp::IOSystem *pIOHandler, const std::string &filename) { + if (!EndsWith(filename, ".skeleton.xml", false)) { + ASSIMP_LOG_ERROR("Imported Mesh is referencing to unsupported '", filename, "' skeleton file."); + return XmlParserPtr(); + } + + if (!pIOHandler->Exists(filename)) { + ASSIMP_LOG_ERROR("Failed to find skeleton file '", filename, "' that is referenced by imported Mesh."); + return XmlParserPtr(); + } + + std::unique_ptr<IOStream> file(pIOHandler->Open(filename)); + if (!file.get()) { + throw DeadlyImportError("Failed to open skeleton file ", filename); + } + + XmlParserPtr xmlParser = std::make_shared<XmlParser>(); + if (!xmlParser->parse(file.get())) { + throw DeadlyImportError("Failed to create XML reader for skeleton file " + filename); + } + return xmlParser; +} + +void OgreXmlSerializer::ReadSkeleton(XmlNode &node, Skeleton *skeleton) { + if (node.name() != nnSkeleton) { + throw DeadlyImportError("Root node is <" + std::string(node.name()) + "> expecting <skeleton>"); + } + + ASSIMP_LOG_VERBOSE_DEBUG("Reading Skeleton"); + + // Optional blend mode from root node + if (XmlParser::hasAttribute(node, "blendmode")) { + skeleton->blendMode = (ai_tolower(ReadAttribute<std::string>(node, "blendmode")) == "cumulative" ? Skeleton::ANIMBLEND_CUMULATIVE : Skeleton::ANIMBLEND_AVERAGE); + } + + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == nnBones) { + ReadBones(currentNode, skeleton); + } else if (currentName == nnBoneHierarchy) { + ReadBoneHierarchy(currentNode, skeleton); + } else if (currentName == nnAnimations) { + ReadAnimations(currentNode, skeleton); + } + } +} + +void OgreXmlSerializer::ReadAnimations(XmlNode &node, Skeleton *skeleton) { + if (skeleton->bones.empty()) { + throw DeadlyImportError("Cannot read <animations> for a Skeleton without bones"); + } + + ASSIMP_LOG_VERBOSE_DEBUG(" - Animations"); + + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == nnAnimation) { + Animation *anim = new Animation(skeleton); + anim->name = ReadAttribute<std::string>(currentNode, "name"); + anim->length = ReadAttribute<float>(currentNode, "length"); + for (XmlNode ¤tChildNode : currentNode.children()) { + const std::string currentChildName = currentNode.name(); + if (currentChildName == nnTracks) { + ReadAnimationTracks(currentChildNode, anim); + skeleton->animations.push_back(anim); + } else { + throw DeadlyImportError("No <tracks> found in <animation> ", anim->name); + } + } + } + } +} + +void OgreXmlSerializer::ReadAnimationTracks(XmlNode &node, Animation *dest) { + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == nnTrack) { + VertexAnimationTrack track; + track.type = VertexAnimationTrack::VAT_TRANSFORM; + track.boneName = ReadAttribute<std::string>(currentNode, "bone"); + for (XmlNode ¤tChildNode : currentNode.children()) { + const std::string currentChildName = currentNode.name(); + if (currentChildName == nnKeyFrames) { + ReadAnimationKeyFrames(currentChildNode, dest, &track); + dest->tracks.push_back(track); + } else { + throw DeadlyImportError("No <keyframes> found in <track> ", dest->name); + } + } + } + } +} + +void OgreXmlSerializer::ReadAnimationKeyFrames(XmlNode &node, Animation *anim, VertexAnimationTrack *dest) { + const aiVector3D zeroVec(0.f, 0.f, 0.f); + for (XmlNode ¤tNode : node.children()) { + TransformKeyFrame keyframe; + const std::string currentName = currentNode.name(); + if (currentName == nnKeyFrame) { + keyframe.timePos = ReadAttribute<float>(currentNode, "time"); + for (XmlNode ¤tChildNode : currentNode.children()) { + const std::string currentChildName = currentNode.name(); + if (currentChildName == nnTranslate) { + keyframe.position.x = ReadAttribute<float>(currentChildNode, anX); + keyframe.position.y = ReadAttribute<float>(currentChildNode, anY); + keyframe.position.z = ReadAttribute<float>(currentChildNode, anZ); + } else if (currentChildName == nnRotate) { + float angle = ReadAttribute<float>(currentChildNode, "angle"); + for (XmlNode ¤tChildChildNode : currentNode.children()) { + const std::string currentChildChildName = currentNode.name(); + if (currentChildChildName == nnAxis) { + aiVector3D axis; + axis.x = ReadAttribute<float>(currentChildChildNode, anX); + axis.y = ReadAttribute<float>(currentChildChildNode, anY); + axis.z = ReadAttribute<float>(currentChildChildNode, anZ); + if (axis.Equal(zeroVec)) { + axis.x = 1.0f; + if (angle != 0) { + ASSIMP_LOG_WARN("Found invalid a key frame with a zero rotation axis in animation: ", anim->name); + } + } + keyframe.rotation = aiQuaternion(axis, angle); + } + } + } else if (currentChildName == nnScale) { + keyframe.scale.x = ReadAttribute<float>(currentChildNode, anX); + keyframe.scale.y = ReadAttribute<float>(currentChildNode, anY); + keyframe.scale.z = ReadAttribute<float>(currentChildNode, anZ); + } + } + } + dest->transformKeyFrames.push_back(keyframe); + } +} + +void OgreXmlSerializer::ReadBoneHierarchy(XmlNode &node, Skeleton *skeleton) { + if (skeleton->bones.empty()) { + throw DeadlyImportError("Cannot read <bonehierarchy> for a Skeleton without bones"); + } + + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == nnBoneParent) { + const std::string name = ReadAttribute<std::string>(currentNode, "bone"); + const std::string parentName = ReadAttribute<std::string>(currentNode, "parent"); + + Bone *bone = skeleton->BoneByName(name); + Bone *parent = skeleton->BoneByName(parentName); + + if (bone && parent) { + parent->AddChild(bone); + } else { + throw DeadlyImportError("Failed to find bones for parenting: Child ", name, " for parent ", parentName); + } + } + } + + // Calculate bone matrices for root bones. Recursively calculates their children. + for (size_t i = 0, len = skeleton->bones.size(); i < len; ++i) { + Bone *bone = skeleton->bones[i]; + if (!bone->IsParented()) + bone->CalculateWorldMatrixAndDefaultPose(skeleton); + } +} + +static bool BoneCompare(Bone *a, Bone *b) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + + return (a->id < b->id); +} + +void OgreXmlSerializer::ReadBones(XmlNode &node, Skeleton *skeleton) { + ASSIMP_LOG_VERBOSE_DEBUG(" - Bones"); + + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == nnBone) { + Bone *bone = new Bone(); + bone->id = ReadAttribute<uint16_t>(currentNode, "id"); + bone->name = ReadAttribute<std::string>(currentNode, "name"); + for (XmlNode ¤tChildNode : currentNode.children()) { + const std::string currentChildName = currentNode.name(); + if (currentChildName == nnRotation) { + bone->position.x = ReadAttribute<float>(currentChildNode, anX); + bone->position.y = ReadAttribute<float>(currentChildNode, anY); + bone->position.z = ReadAttribute<float>(currentChildNode, anZ); + } else if (currentChildName == nnScale) { + float angle = ReadAttribute<float>(currentChildNode, "angle"); + for (XmlNode currentChildChildNode : currentChildNode.children()) { + const std::string ¤tChildChildName = currentChildChildNode.name(); + if (currentChildChildName == nnAxis) { + aiVector3D axis; + axis.x = ReadAttribute<float>(currentChildChildNode, anX); + axis.y = ReadAttribute<float>(currentChildChildNode, anY); + axis.z = ReadAttribute<float>(currentChildChildNode, anZ); + + bone->rotation = aiQuaternion(axis, angle); + } else { + throw DeadlyImportError("No axis specified for bone rotation in bone ", bone->id); + } + } + } else if (currentChildName == nnScale) { + if (XmlParser::hasAttribute(currentChildNode, "factor")) { + float factor = ReadAttribute<float>(currentChildNode, "factor"); + bone->scale.Set(factor, factor, factor); + } else { + if (XmlParser::hasAttribute(currentChildNode, anX)) + bone->scale.x = ReadAttribute<float>(currentChildNode, anX); + if (XmlParser::hasAttribute(currentChildNode, anY)) + bone->scale.y = ReadAttribute<float>(currentChildNode, anY); + if (XmlParser::hasAttribute(currentChildNode, anZ)) + bone->scale.z = ReadAttribute<float>(currentChildNode, anZ); + } + } + } + skeleton->bones.push_back(bone); + } + } + + // Order bones by Id + std::sort(skeleton->bones.begin(), skeleton->bones.end(), BoneCompare); + + // Validate that bone indexes are not skipped. + /** @note Left this from original authors code, but not sure if this is strictly necessary + as per the Ogre skeleton spec. It might be more that other (later) code in this imported does not break. */ + for (size_t i = 0, len = skeleton->bones.size(); i < len; ++i) { + Bone *b = skeleton->bones[i]; + ASSIMP_LOG_VERBOSE_DEBUG(" ", b->id, " ", b->name); + + if (b->id != static_cast<uint16_t>(i)) { + throw DeadlyImportError("Bone ids are not in sequence starting from 0. Missing index ", i); + } + } +} + +} // namespace Ogre +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER diff --git a/libs/assimp/code/AssetLib/Ogre/OgreXmlSerializer.h b/libs/assimp/code/AssetLib/Ogre/OgreXmlSerializer.h new file mode 100644 index 0000000..406681f --- /dev/null +++ b/libs/assimp/code/AssetLib/Ogre/OgreXmlSerializer.h @@ -0,0 +1,102 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef AI_OGREXMLSERIALIZER_H_INC +#define AI_OGREXMLSERIALIZER_H_INC + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + +#include "OgreStructs.h" +#include <assimp/XmlParser.h> + +namespace Assimp { + +namespace Ogre { + +using XmlParserPtr = std::shared_ptr<::Assimp::XmlParser> ; + +class OgreXmlSerializer { +public: + /// Imports mesh and returns the result. + /// @note Fatal unrecoverable errors will throw a DeadlyImportError. + static MeshXml *ImportMesh(XmlParser *parser); + + /// Imports skeleton to @c mesh. + /// If mesh does not have a skeleton reference or the skeleton file + /// cannot be found it is not a fatal DeadlyImportError. + /// @return If skeleton import was successful. + static bool ImportSkeleton(IOSystem *pIOHandler, MeshXml *mesh); + static bool ImportSkeleton(IOSystem *pIOHandler, Mesh *mesh); + +private: + explicit OgreXmlSerializer(XmlParser *xmlParser); + + static XmlParserPtr OpenXmlParser(Assimp::IOSystem *pIOHandler, const std::string &filename); + + // Mesh + void ReadMesh(MeshXml *mesh); + void ReadSubMesh(XmlNode &node, MeshXml *mesh); + void ReadGeometry(XmlNode &node, VertexDataXml *dest); + void ReadGeometryVertexBuffer(XmlNode &node, VertexDataXml *dest); + void ReadBoneAssignments(XmlNode &node, VertexDataXml *dest); + + // Skeleton + void ReadSkeleton(XmlNode &node, Skeleton *skeleton); + void ReadBones(XmlNode &node, Skeleton *skeleton); + void ReadBoneHierarchy(XmlNode &node, Skeleton *skeleton); + void ReadAnimations(XmlNode &node, Skeleton *skeleton); + void ReadAnimationTracks(XmlNode &node, Animation *dest); + void ReadAnimationKeyFrames(XmlNode &node, Animation *anim, VertexAnimationTrack *dest); + + template <typename T> + T ReadAttribute(XmlNode &xmlNode, const char *name) const; + +private: + XmlParser *mParser; +}; + + +} // namespace Ogre +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER +#endif // AI_OGREXMLSERIALIZER_H_INC diff --git a/libs/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.cpp b/libs/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.cpp new file mode 100644 index 0000000..328c3c4 --- /dev/null +++ b/libs/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.cpp @@ -0,0 +1,62 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#include "OpenGEXExporter.h" + +namespace Assimp { +namespace OpenGEX { + +#ifndef ASSIMP_BUILD_NO_OPENGEX_EXPORTER + +OpenGEXExporter::OpenGEXExporter() { +} + +OpenGEXExporter::~OpenGEXExporter() { +} + +bool OpenGEXExporter::exportScene( const char * /*filename*/, const aiScene* /*pScene*/ ) { + return true; +} + +#endif // ASSIMP_BUILD_NO_OPENGEX_EXPORTER + +} // Namespace OpenGEX +} // Namespace Assimp diff --git a/libs/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.h b/libs/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.h new file mode 100644 index 0000000..cc22c7d --- /dev/null +++ b/libs/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.h @@ -0,0 +1,68 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#pragma once +#ifndef AI_OPENGEX_EXPORTER_H +#define AI_OPENGEX_EXPORTER_H + +#include <assimp/types.h> + +#ifndef ASSIMP_BUILD_NO_OPENGEX_EXPORTER + +namespace Assimp { + +struct aiScene; + +namespace OpenGEX { + +class OpenGEXExporter { +public: + OpenGEXExporter(); + ~OpenGEXExporter(); + bool exportScene( const char *filename, const aiScene* pScene ); +}; + +} // Namespace OpenGEX +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_OPENGEX_EXPORTER + +#endif // AI_OPENGEX_EXPORTER_H + diff --git a/libs/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.cpp b/libs/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.cpp new file mode 100644 index 0000000..c8e4793 --- /dev/null +++ b/libs/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.cpp @@ -0,0 +1,1326 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_OPENGEX_IMPORTER + +#include "AssetLib/OpenGEX/OpenGEXImporter.h" +#include "PostProcessing/MakeVerboseFormat.h" + +#include <assimp/DefaultIOSystem.h> +#include <assimp/StringComparison.h> +#include <assimp/StringUtils.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/ai_assert.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <openddlparser/OpenDDLParser.h> + +static const aiImporterDesc desc = { + "Open Game Engine Exchange", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "ogex" +}; + +namespace Grammar { + static const char* MetricType = "Metric"; + static const char *Metric_DistanceType = "distance"; + static const char *Metric_AngleType = "angle"; + static const char *Metric_TimeType = "time"; + static const char *Metric_UpType = "up"; + static const char *NameType = "Name"; + static const char *ObjectRefType = "ObjectRef"; + static const char *MaterialRefType = "MaterialRef"; + static const char *MetricKeyType = "key"; + static const char *GeometryNodeType = "GeometryNode"; + static const char *CameraNodeType = "CameraNode"; + static const char *LightNodeType = "LightNode"; + static const char *GeometryObjectType = "GeometryObject"; + static const char *CameraObjectType = "CameraObject"; + static const char *LightObjectType = "LightObject"; + static const char *TransformType = "Transform"; + static const char *MeshType = "Mesh"; + static const char *VertexArrayType = "VertexArray"; + static const char *IndexArrayType = "IndexArray"; + static const char *MaterialType = "Material"; + static const char *ColorType = "Color"; + static const char *ParamType = "Param"; + static const char *TextureType = "Texture"; + static const char *AttenType = "Atten"; + + static const char *DiffuseColorToken = "diffuse"; + static const char *SpecularColorToken = "specular"; + static const char *EmissionColorToken = "emission"; + + static const char *DiffuseTextureToken = "diffuse"; + static const char *DiffuseSpecularTextureToken = "specular"; + static const char *SpecularPowerTextureToken = "specular_power"; + static const char *EmissionTextureToken = "emission"; + static const char *OpacyTextureToken = "opacity"; + static const char *TransparencyTextureToken = "transparency"; + static const char *NormalTextureToken = "normal"; + + enum TokenType { + NoneType = -1, + MetricToken, + NameToken, + ObjectRefToken, + MaterialRefToken, + MetricKeyToken, + GeometryNodeToken, + CameraNodeToken, + LightNodeToken, + GeometryObjectToken, + CameraObjectToken, + LightObjectToken, + TransformToken, + MeshToken, + VertexArrayToken, + IndexArrayToken, + MaterialToken, + ColorToken, + ParamToken, + TextureToken, + AttenToken + }; + + static const std::string ValidMetricToken[4] = { + Metric_DistanceType, + Metric_AngleType, + Metric_TimeType, + Metric_UpType + }; + + static int isValidMetricType(const char *token) { + if (nullptr == token) { + return false; + } + + int idx(-1); + for (size_t i = 0; i < 4; i++) { + if (ValidMetricToken[i] == token) { + idx = (int)i; + break; + } + } + + return idx; + } + + static TokenType matchTokenType(const char *tokenType) { + if (MetricType == tokenType) { + return MetricToken; + } else if (NameType == tokenType) { + return NameToken; + } else if (ObjectRefType == tokenType) { + return ObjectRefToken; + } else if (MaterialRefType == tokenType) { + return MaterialRefToken; + } else if (MetricKeyType == tokenType) { + return MetricKeyToken; + } else if (GeometryNodeType == tokenType) { + return GeometryNodeToken; + } else if (CameraNodeType == tokenType) { + return CameraNodeToken; + } else if (LightNodeType == tokenType) { + return LightNodeToken; + } else if (GeometryObjectType == tokenType) { + return GeometryObjectToken; + } else if (CameraObjectType == tokenType) { + return CameraObjectToken; + } else if (LightObjectType == tokenType) { + return LightObjectToken; + } else if (TransformType == tokenType) { + return TransformToken; + } else if (MeshType == tokenType) { + return MeshToken; + } else if (VertexArrayType == tokenType) { + return VertexArrayToken; + } else if (IndexArrayType == tokenType) { + return IndexArrayToken; + } else if (MaterialType == tokenType) { + return MaterialToken; + } else if (ColorType == tokenType) { + return ColorToken; + } else if (ParamType == tokenType) { + return ParamToken; + } else if (TextureType == tokenType) { + return TextureToken; + } else if (AttenType == tokenType) { + return AttenToken; + } + + return NoneType; + } +} // Namespace Grammar + +namespace Assimp { +namespace OpenGEX { + +USE_ODDLPARSER_NS + +//------------------------------------------------------------------------------------------------ +static void propId2StdString(Property *prop, std::string &name, std::string &key) { + name = key = std::string(); + if (nullptr == prop) { + return; + } + + if (nullptr != prop->m_key) { +#ifdef ASSIMP_USE_HUNTER + name = prop->m_key->m_text.m_buffer; +#else + name = prop->m_key->m_buffer; +#endif + if (Value::ValueType::ddl_string == prop->m_value->m_type) { + key = prop->m_value->getString(); + } + } +} + +//------------------------------------------------------------------------------------------------ +static void logDDLParserMessage (LogSeverity severity, const std::string &rawmsg) { + std::string msg = ai_str_toprintable(rawmsg); + switch (severity) { + case ddl_debug_msg: ASSIMP_LOG_DEBUG(msg); break; + case ddl_info_msg: ASSIMP_LOG_INFO(msg); break; + case ddl_warn_msg: ASSIMP_LOG_WARN(msg); break; + case ddl_error_msg: ASSIMP_LOG_ERROR(msg); break; + default: ASSIMP_LOG_VERBOSE_DEBUG(msg); break; + } +} + +//------------------------------------------------------------------------------------------------ +OpenGEXImporter::VertexContainer::VertexContainer() : + m_numColors(0), m_colors(nullptr), m_numUVComps(), m_textureCoords() { + // empty +} + +//------------------------------------------------------------------------------------------------ +OpenGEXImporter::VertexContainer::~VertexContainer() { + delete[] m_colors; + + for (auto &texcoords : m_textureCoords) { + delete[] texcoords; + } +} + +//------------------------------------------------------------------------------------------------ +OpenGEXImporter::RefInfo::RefInfo(aiNode *node, Type type, std::vector<std::string> &names) : + m_node(node), + m_type(type), + m_Names(names) { + // empty +} + +//------------------------------------------------------------------------------------------------ +OpenGEXImporter::RefInfo::~RefInfo() { + // empty +} + +//------------------------------------------------------------------------------------------------ +OpenGEXImporter::OpenGEXImporter() : + m_root(nullptr), + m_nodeChildMap(), + m_meshCache(), + m_mesh2refMap(), + m_material2refMap(), + m_ctx(nullptr), + m_metrics(), + m_currentNode(nullptr), + m_currentVertices(), + m_currentMesh(nullptr), + m_currentMaterial(nullptr), + m_currentLight(nullptr), + m_currentCamera(nullptr), + m_tokenType(Grammar::NoneType), + m_materialCache(), + m_cameraCache(), + m_lightCache(), + m_nodeStack(), + m_unresolvedRefStack() { + // empty +} + +//------------------------------------------------------------------------------------------------ +OpenGEXImporter::~OpenGEXImporter() { +} + +//------------------------------------------------------------------------------------------------ +bool OpenGEXImporter::CanRead(const std::string &file, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "Metric", "GeometryNode", "VertexArray (attrib", "IndexArray" }; + return SearchFileHeaderForToken(pIOHandler, file, tokens, AI_COUNT_OF(tokens)); +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::InternReadFile(const std::string &filename, aiScene *pScene, IOSystem *pIOHandler) { + // open source file + IOStream *file = pIOHandler->Open(filename, "rb"); + if (!file) { + throw DeadlyImportError("Failed to open file ", filename); + } + + std::vector<char> buffer; + TextFileToBuffer(file, buffer); + pIOHandler->Close(file); + + OpenDDLParser myParser; + myParser.setLogCallback(&logDDLParserMessage); + myParser.setBuffer(&buffer[0], buffer.size()); + bool success(myParser.parse()); + if (success) { + m_ctx = myParser.getContext(); + pScene->mRootNode = new aiNode; + pScene->mRootNode->mName.Set(filename); + handleNodes(m_ctx->m_root, pScene); + } + + copyMeshes(pScene); + copyCameras(pScene); + copyLights(pScene); + copyMaterials(pScene); + resolveReferences(); + createNodeTree(pScene); +} + +//------------------------------------------------------------------------------------------------ +const aiImporterDesc *OpenGEXImporter::GetInfo() const { + return &desc; +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::SetupProperties(const Importer *pImp) { + if (nullptr == pImp) { + return; + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleNodes(DDLNode *node, aiScene *pScene) { + if (nullptr == node) { + return; + } + + DDLNode::DllNodeList children = node->getChildNodeList(); + for (DDLNode::DllNodeList::iterator it = children.begin(); it != children.end(); ++it) { + Grammar::TokenType tokenType(Grammar::matchTokenType((*it)->getType().c_str())); + switch (tokenType) { + case Grammar::MetricToken: + handleMetricNode(*it, pScene); + break; + + case Grammar::NameToken: + handleNameNode(*it, pScene); + break; + + case Grammar::ObjectRefToken: + handleObjectRefNode(*it, pScene); + break; + + case Grammar::MaterialRefToken: + handleMaterialRefNode(*it, pScene); + break; + + case Grammar::MetricKeyToken: + break; + + case Grammar::GeometryNodeToken: + handleGeometryNode(*it, pScene); + break; + + case Grammar::CameraNodeToken: + handleCameraNode(*it, pScene); + break; + + case Grammar::LightNodeToken: + handleLightNode(*it, pScene); + break; + + case Grammar::GeometryObjectToken: + handleGeometryObject(*it, pScene); + break; + + case Grammar::CameraObjectToken: + handleCameraObject(*it, pScene); + break; + + case Grammar::LightObjectToken: + handleLightObject(*it, pScene); + break; + + case Grammar::TransformToken: + handleTransformNode(*it, pScene); + break; + + case Grammar::MeshToken: + handleMeshNode(*it, pScene); + break; + + case Grammar::VertexArrayToken: + handleVertexArrayNode(*it, pScene); + break; + + case Grammar::IndexArrayToken: + handleIndexArrayNode(*it, pScene); + break; + + case Grammar::MaterialToken: + handleMaterialNode(*it, pScene); + break; + + case Grammar::ColorToken: + handleColorNode(*it, pScene); + break; + + case Grammar::ParamToken: + handleParamNode(*it, pScene); + break; + + case Grammar::TextureToken: + handleTextureNode(*it, pScene); + break; + + default: + break; + } + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleMetricNode(DDLNode *node, aiScene * /*pScene*/) { + if (nullptr == node || nullptr == m_ctx) { + return; + } + + if (m_ctx->m_root != node->getParent()) { + return; + } + + Property *prop(node->getProperties()); + while (nullptr != prop) { + if (nullptr != prop->m_key) { + if (Value::ValueType::ddl_string == prop->m_value->m_type) { + std::string valName((char *)prop->m_value->m_data); + int type(Grammar::isValidMetricType(valName.c_str())); + if (Grammar::NoneType != type) { + Value *val(node->getValue()); + if (nullptr != val) { + if (Value::ValueType::ddl_float == val->m_type) { + m_metrics[type].m_floatValue = val->getFloat(); + } else if (Value::ValueType::ddl_int32 == val->m_type) { + m_metrics[type].m_intValue = val->getInt32(); + } else if (Value::ValueType::ddl_string == val->m_type) { + m_metrics[type].m_stringValue = std::string(val->getString()); + } else { + throw DeadlyImportError("OpenGEX: invalid data type for Metric node."); + } + } + } + } + } + prop = prop->m_next; + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleNameNode(DDLNode *node, aiScene * /*pScene*/) { + if (nullptr == m_currentNode) { + throw DeadlyImportError("No current node for name."); + return; + } + + Value *val(node->getValue()); + if (nullptr != val) { + if (Value::ValueType::ddl_string != val->m_type) { + throw DeadlyImportError("OpenGEX: invalid data type for value in node name."); + return; + } + + const std::string name(val->getString()); + if (m_tokenType == Grammar::GeometryNodeToken || m_tokenType == Grammar::LightNodeToken || m_tokenType == Grammar::CameraNodeToken) { + m_currentNode->mName.Set(name.c_str()); + } else if (m_tokenType == Grammar::MaterialToken) { + aiString aiName; + aiName.Set(name); + m_currentMaterial->AddProperty(&aiName, AI_MATKEY_NAME); + m_material2refMap[name] = m_materialCache.size() - 1; + } + } +} + +//------------------------------------------------------------------------------------------------ +static void getRefNames(DDLNode *node, std::vector<std::string> &names) { + ai_assert(nullptr != node); + + Reference *ref = node->getReferences(); + if (nullptr != ref) { + for (size_t i = 0; i < ref->m_numRefs; i++) { + Name *currentName(ref->m_referencedName[i]); + if (nullptr != currentName && nullptr != currentName->m_id) { +#ifdef ASSIMP_USE_HUNTER + const std::string name(currentName->m_id->m_text.m_buffer); +#else + const std::string name(currentName->m_id->m_buffer); +#endif + if (!name.empty()) { + names.push_back(name); + } + } + } + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleObjectRefNode(DDLNode *node, aiScene * /*pScene*/) { + if (nullptr == m_currentNode) { + throw DeadlyImportError("No parent node for name."); + return; + } + + std::vector<std::string> objRefNames; + getRefNames(node, objRefNames); + + // when we are dealing with a geometry node prepare the mesh cache + if (m_tokenType == Grammar::GeometryNodeToken) { + m_currentNode->mNumMeshes = static_cast<unsigned int>(objRefNames.size()); + m_currentNode->mMeshes = new unsigned int[objRefNames.size()]; + if (!objRefNames.empty()) { + m_unresolvedRefStack.push_back(std::unique_ptr<RefInfo>(new RefInfo(m_currentNode, RefInfo::MeshRef, objRefNames))); + } + } else if (m_tokenType == Grammar::LightNodeToken) { + // TODO! + } else if (m_tokenType == Grammar::CameraNodeToken) { + // TODO! + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleMaterialRefNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) { + if (nullptr == m_currentNode) { + throw DeadlyImportError("No parent node for name."); + return; + } + + std::vector<std::string> matRefNames; + getRefNames(node, matRefNames); + if (!matRefNames.empty()) { + m_unresolvedRefStack.push_back(std::unique_ptr<RefInfo>(new RefInfo(m_currentNode, RefInfo::MaterialRef, matRefNames))); + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleGeometryNode(DDLNode *node, aiScene *pScene) { + aiNode *newNode = new aiNode; + pushNode(newNode, pScene); + m_tokenType = Grammar::GeometryNodeToken; + m_currentNode = newNode; + handleNodes(node, pScene); + + popNode(); +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleCameraNode(DDLNode *node, aiScene *pScene) { + aiCamera *camera(new aiCamera); + m_cameraCache.push_back(camera); + m_currentCamera = camera; + + aiNode *newNode = new aiNode; + pushNode(newNode, pScene); + m_tokenType = Grammar::CameraNodeToken; + m_currentNode = newNode; + + handleNodes(node, pScene); + + popNode(); + + m_currentCamera->mName.Set(newNode->mName.C_Str()); +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleLightNode(ODDLParser::DDLNode *node, aiScene *pScene) { + aiLight *light(new aiLight); + m_lightCache.push_back(light); + m_currentLight = light; + + aiNode *newNode = new aiNode; + m_tokenType = Grammar::LightNodeToken; + m_currentNode = newNode; + pushNode(newNode, pScene); + + handleNodes(node, pScene); + + popNode(); + + m_currentLight->mName.Set(newNode->mName.C_Str()); +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleGeometryObject(DDLNode *node, aiScene *pScene) { + // parameters will be parsed normally in the tree, so just go for it + handleNodes(node, pScene); +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleCameraObject(ODDLParser::DDLNode *node, aiScene *pScene) { + // parameters will be parsed normally in the tree, so just go for it + + handleNodes(node, pScene); +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleLightObject(ODDLParser::DDLNode *node, aiScene *pScene) { + aiLight *light(new aiLight); + m_lightCache.push_back(light); + std::string objName = node->getName(); + if (!objName.empty()) { + light->mName.Set(objName); + } + m_currentLight = light; + + Property *prop(node->findPropertyByName("type")); + if (nullptr != prop) { + if (nullptr != prop->m_value) { + std::string typeStr(prop->m_value->getString()); + if ("point" == typeStr) { + m_currentLight->mType = aiLightSource_POINT; + } else if ("spot" == typeStr) { + m_currentLight->mType = aiLightSource_SPOT; + } else if ("infinite" == typeStr) { + m_currentLight->mType = aiLightSource_DIRECTIONAL; + } + } + } + + // parameters will be parsed normally in the tree, so just go for it + handleNodes(node, pScene); +} + +//------------------------------------------------------------------------------------------------ +static void setMatrix(aiNode *node, DataArrayList *transformData) { + ai_assert(nullptr != node); + ai_assert(nullptr != transformData); + + float m[16]; + size_t i(1); + Value *next(transformData->m_dataList->m_next); + m[0] = transformData->m_dataList->getFloat(); + while (next != nullptr) { + m[i] = next->getFloat(); + next = next->m_next; + i++; + } + + ai_assert(i == 16); + + node->mTransformation.a1 = m[0]; + node->mTransformation.a2 = m[4]; + node->mTransformation.a3 = m[8]; + node->mTransformation.a4 = m[12]; + + node->mTransformation.b1 = m[1]; + node->mTransformation.b2 = m[5]; + node->mTransformation.b3 = m[9]; + node->mTransformation.b4 = m[13]; + + node->mTransformation.c1 = m[2]; + node->mTransformation.c2 = m[6]; + node->mTransformation.c3 = m[10]; + node->mTransformation.c4 = m[14]; + + node->mTransformation.d1 = m[3]; + node->mTransformation.d2 = m[7]; + node->mTransformation.d3 = m[11]; + node->mTransformation.d4 = m[15]; +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleTransformNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) { + if (nullptr == m_currentNode) { + throw DeadlyImportError("No parent node for name."); + return; + } + + DataArrayList *transformData(node->getDataArrayList()); + if (nullptr != transformData) { + if (transformData->m_numItems != 16) { + throw DeadlyImportError("Invalid number of data for transform matrix."); + return; + } + setMatrix(m_currentNode, transformData); + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleMeshNode(ODDLParser::DDLNode *node, aiScene *pScene) { + m_currentMesh = new aiMesh; + const size_t meshidx(m_meshCache.size()); + // ownership is transferred but a reference remains in m_currentMesh + m_meshCache.emplace_back(m_currentMesh); + + Property *prop = node->getProperties(); + if (nullptr != prop) { + std::string propName, propKey; + propId2StdString(prop, propName, propKey); + if ("primitive" == propName) { + if ("points" == propKey) { + m_currentMesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + } else if ("lines" == propKey) { + m_currentMesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + } else if ("triangles" == propKey) { + m_currentMesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + } else if ("quads" == propKey) { + m_currentMesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + } else { + ASSIMP_LOG_WARN(propKey, " is not supported primitive type."); + } + } + } + + handleNodes(node, pScene); + + DDLNode *parent(node->getParent()); + if (nullptr != parent) { + const std::string &name = parent->getName(); + m_mesh2refMap[name] = meshidx; + } +} + +//------------------------------------------------------------------------------------------------ +enum MeshAttribute { + None, + Position, + Color, + Normal, + TexCoord +}; + +constexpr auto PosToken = "position"; +constexpr auto ColToken = "color"; +constexpr auto NormalToken = "normal"; +constexpr auto TexCoordToken = "texcoord"; + +//------------------------------------------------------------------------------------------------ +static MeshAttribute getAttributeByName(const char *attribName) { + ai_assert(nullptr != attribName); + + if (0 == strcmp(PosToken, attribName)) { + return Position; + } else if (0 == strcmp(ColToken, attribName)) { + return Color; + } else if (0 == strcmp(NormalToken, attribName)) { + return Normal; + } else if (0 == strcmp(TexCoordToken, attribName)) { + return TexCoord; + } + + return None; +} + +//------------------------------------------------------------------------------------------------ +static void fillVector3(aiVector3D *vec3, Value *vals) { + ai_assert(nullptr != vec3); + ai_assert(nullptr != vals); + + float x(0.0f), y(0.0f), z(0.0f); + Value *next(vals); + x = next->getFloat(); + next = next->m_next; + y = next->getFloat(); + next = next->m_next; + if (nullptr != next) { + z = next->getFloat(); + } + + vec3->Set(x, y, z); +} + +//------------------------------------------------------------------------------------------------ +static void fillColor4(aiColor4D *col4, Value *vals) { + ai_assert(nullptr != col4); + ai_assert(nullptr != vals); + + Value *next(vals); + col4->r = next->getFloat(); + next = next->m_next; + if (!next) { + throw DeadlyImportError("OpenGEX: Not enough values to fill 4-element color, only 1"); + } + + col4->g = next->getFloat(); + next = next->m_next; + if (!next) { + throw DeadlyImportError("OpenGEX: Not enough values to fill 4-element color, only 2"); + } + + col4->b = next->getFloat(); + next = next->m_next; + if (!next) { + throw DeadlyImportError("OpenGEX: Not enough values to fill 4-element color, only 3"); + } + + col4->a = next->getFloat(); +} + +//------------------------------------------------------------------------------------------------ +static size_t countDataArrayListItems(DataArrayList *vaList) { + size_t numItems(0); + if (nullptr == vaList) { + return numItems; + } + + DataArrayList *next(vaList); + while (nullptr != next) { + if (nullptr != vaList->m_dataList) { + numItems++; + } + next = next->m_next; + } + + return numItems; +} + +//------------------------------------------------------------------------------------------------ +static void copyVectorArray(size_t numItems, DataArrayList *vaList, aiVector3D *vectorArray) { + for (size_t i = 0; i < numItems; i++) { + Value *next(vaList->m_dataList); + fillVector3(&vectorArray[i], next); + vaList = vaList->m_next; + } +} + +//------------------------------------------------------------------------------------------------ +static void copyColor4DArray(size_t numItems, DataArrayList *vaList, aiColor4D *colArray) { + for (size_t i = 0; i < numItems; i++) { + Value *next(vaList->m_dataList); + fillColor4(&colArray[i], next); + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleVertexArrayNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) { + if (nullptr == node) { + throw DeadlyImportError("No parent node for name."); + return; + } + + Property *prop = node->getProperties(); + if (nullptr != prop) { + std::string propName, propKey; + propId2StdString(prop, propName, propKey); + MeshAttribute attribType(getAttributeByName(propKey.c_str())); + if (None == attribType) { + return; + } + + DataArrayList *vaList = node->getDataArrayList(); + if (nullptr == vaList) { + return; + } + + const size_t numItems(countDataArrayListItems(vaList)); + + if (Position == attribType) { + m_currentVertices.m_vertices.resize(numItems); + copyVectorArray(numItems, vaList, m_currentVertices.m_vertices.data()); + } else if (Color == attribType) { + m_currentVertices.m_numColors = numItems; + m_currentVertices.m_colors = new aiColor4D[numItems]; + copyColor4DArray(numItems, vaList, m_currentVertices.m_colors); + } else if (Normal == attribType) { + m_currentVertices.m_normals.resize(numItems); + copyVectorArray(numItems, vaList, m_currentVertices.m_normals.data()); + } else if (TexCoord == attribType) { + m_currentVertices.m_numUVComps[0] = numItems; + m_currentVertices.m_textureCoords[0] = new aiVector3D[numItems]; + copyVectorArray(numItems, vaList, m_currentVertices.m_textureCoords[0]); + } + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleIndexArrayNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) { + if (nullptr == node) { + throw DeadlyImportError("No parent node for name."); + return; + } + + if (nullptr == m_currentMesh) { + throw DeadlyImportError("No current mesh for index data found."); + return; + } + + DataArrayList *vaList = node->getDataArrayList(); + if (nullptr == vaList) { + return; + } + + const size_t numItems(countDataArrayListItems(vaList)); + m_currentMesh->mNumFaces = static_cast<unsigned int>(numItems); + m_currentMesh->mFaces = new aiFace[numItems]; + m_currentMesh->mNumVertices = static_cast<unsigned int>(numItems * 3); + m_currentMesh->mVertices = new aiVector3D[m_currentMesh->mNumVertices]; + bool hasColors(false); + if (m_currentVertices.m_numColors > 0) { + m_currentMesh->mColors[0] = new aiColor4D[m_currentVertices.m_numColors]; + hasColors = true; + } + bool hasNormalCoords(false); + if (!m_currentVertices.m_normals.empty()) { + m_currentMesh->mNormals = new aiVector3D[m_currentMesh->mNumVertices]; + hasNormalCoords = true; + } + bool hasTexCoords(false); + if (m_currentVertices.m_numUVComps[0] > 0) { + m_currentMesh->mTextureCoords[0] = new aiVector3D[m_currentMesh->mNumVertices]; + hasTexCoords = true; + } + + unsigned int index(0); + for (size_t i = 0; i < m_currentMesh->mNumFaces; i++) { + aiFace ¤t(m_currentMesh->mFaces[i]); + current.mNumIndices = 3; + current.mIndices = new unsigned int[current.mNumIndices]; + Value *next(vaList->m_dataList); + for (size_t indices = 0; indices < current.mNumIndices; indices++) { + const int idx(next->getUnsignedInt32()); + ai_assert(static_cast<size_t>(idx) <= m_currentVertices.m_vertices.size()); + ai_assert(index < m_currentMesh->mNumVertices); + aiVector3D &pos = (m_currentVertices.m_vertices[idx]); + m_currentMesh->mVertices[index].Set(pos.x, pos.y, pos.z); + if (hasColors) { + aiColor4D &col = m_currentVertices.m_colors[idx]; + m_currentMesh->mColors[0][index] = col; + } + if (hasNormalCoords) { + aiVector3D &normal = (m_currentVertices.m_normals[idx]); + m_currentMesh->mNormals[index].Set(normal.x, normal.y, normal.z); + } + if (hasTexCoords) { + aiVector3D &tex = (m_currentVertices.m_textureCoords[0][idx]); + m_currentMesh->mTextureCoords[0][index].Set(tex.x, tex.y, tex.z); + } + current.mIndices[indices] = index; + index++; + + next = next->m_next; + } + vaList = vaList->m_next; + } +} + +//------------------------------------------------------------------------------------------------ +static void getColorRGB3(aiColor3D *pColor, DataArrayList *colList) { + if (nullptr == pColor || nullptr == colList) { + return; + } + + ai_assert(3 == colList->m_numItems); + Value *val(colList->m_dataList); + pColor->r = val->getFloat(); + val = val->getNext(); + pColor->g = val->getFloat(); + val = val->getNext(); + pColor->b = val->getFloat(); +} + +//------------------------------------------------------------------------------------------------ +static void getColorRGB4(aiColor4D *pColor, DataArrayList *colList) { + if (nullptr == pColor || nullptr == colList) { + return; + } + + ai_assert(4 == colList->m_numItems); + Value *val(colList->m_dataList); + pColor->r = val->getFloat(); + val = val->getNext(); + pColor->g = val->getFloat(); + val = val->getNext(); + pColor->b = val->getFloat(); + val = val->getNext(); + pColor->a = val->getFloat(); +} + +//------------------------------------------------------------------------------------------------ +enum ColorType { + NoneColor = 0, + DiffuseColor, + SpecularColor, + EmissionColor, + LightColor +}; + +//------------------------------------------------------------------------------------------------ +static ColorType getColorType(Text *id) { + if (nullptr == id) { + return NoneColor; + } + + if (*id == Grammar::DiffuseColorToken) { + return DiffuseColor; + } else if (*id == Grammar::SpecularColorToken) { + return SpecularColor; + } else if (*id == Grammar::EmissionColorToken) { + return EmissionColor; + } else if (*id == "light") { + return LightColor; + } + + return NoneColor; +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleMaterialNode(ODDLParser::DDLNode *node, aiScene *pScene) { + m_currentMaterial = new aiMaterial; + m_materialCache.push_back(m_currentMaterial); + m_tokenType = Grammar::MaterialToken; + handleNodes(node, pScene); +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleColorNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) { + if (nullptr == node) { + return; + } + + Property *prop = node->findPropertyByName("attrib"); + if (nullptr != prop) { + if (nullptr != prop->m_value) { + DataArrayList *colList(node->getDataArrayList()); + if (nullptr == colList) { + return; + } + aiColor3D col; + if (3 == colList->m_numItems) { + aiColor3D col3; + getColorRGB3(&col3, colList); + col = col3; + } else { + aiColor4D col4; + getColorRGB4(&col4, colList); + col.r = col4.r; + col.g = col4.g; + col.b = col4.b; + } +#ifdef ASSIMP_USE_HUNTER + const ColorType colType(getColorType(&prop->m_key->m_text)); +#else + const ColorType colType(getColorType(prop->m_key)); +#endif + if (DiffuseColor == colType) { + m_currentMaterial->AddProperty(&col, 1, AI_MATKEY_COLOR_DIFFUSE); + } else if (SpecularColor == colType) { + m_currentMaterial->AddProperty(&col, 1, AI_MATKEY_COLOR_SPECULAR); + } else if (EmissionColor == colType) { + m_currentMaterial->AddProperty(&col, 1, AI_MATKEY_COLOR_EMISSIVE); + } else if (LightColor == colType) { + m_currentLight->mColorDiffuse = col; + } + } + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleTextureNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) { + if (nullptr == node) { + return; + } + + Property *prop = node->findPropertyByName("attrib"); + if (nullptr != prop) { + if (nullptr != prop->m_value) { + Value *val(node->getValue()); + if (nullptr != val) { + aiString tex; + tex.Set(val->getString()); + if (prop->m_value->getString() == Grammar::DiffuseTextureToken) { + m_currentMaterial->AddProperty(&tex, AI_MATKEY_TEXTURE_DIFFUSE(0)); + } else if (prop->m_value->getString() == Grammar::DiffuseSpecularTextureToken) { + m_currentMaterial->AddProperty(&tex, AI_MATKEY_TEXTURE_SPECULAR(0)); + } else if (prop->m_value->getString() == Grammar::SpecularPowerTextureToken) { + m_currentMaterial->AddProperty(&tex, AI_MATKEY_TEXTURE_SPECULAR(0)); + } else if (prop->m_value->getString() == Grammar::EmissionTextureToken) { + m_currentMaterial->AddProperty(&tex, AI_MATKEY_TEXTURE_EMISSIVE(0)); + } else if (prop->m_value->getString() == Grammar::OpacyTextureToken) { + m_currentMaterial->AddProperty(&tex, AI_MATKEY_TEXTURE_OPACITY(0)); + } else if (prop->m_value->getString() == Grammar::TransparencyTextureToken) { + // ToDo! + // m_currentMaterial->AddProperty( &tex, AI_MATKEY_TEXTURE_DIFFUSE( 0 ) ); + } else if (prop->m_value->getString() == Grammar::NormalTextureToken) { + m_currentMaterial->AddProperty(&tex, AI_MATKEY_TEXTURE_NORMALS(0)); + } else { + ai_assert(false); + } + } + } + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleParamNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) { + if (nullptr == node) { + return; + } + + Property *prop = node->findPropertyByName("attrib"); + if (nullptr == prop) { + return; + } + + if (nullptr != prop->m_value) { + Value *val(node->getValue()); + if (nullptr == val) { + return; + } + const float floatVal(val->getFloat()); + if (0 == ASSIMP_strincmp("fov", prop->m_value->getString(), 3)) { + m_currentCamera->mHorizontalFOV = floatVal; + } else if (0 == ASSIMP_strincmp("near", prop->m_value->getString(), 4)) { + m_currentCamera->mClipPlaneNear = floatVal; + } else if (0 == ASSIMP_strincmp("far", prop->m_value->getString(), 3)) { + m_currentCamera->mClipPlaneFar = floatVal; + } + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::handleAttenNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) { + if (nullptr == node) { + return; + } + + Property *prop = node->findPropertyByName("curve"); + if (nullptr != prop) { + if (nullptr != prop->m_value) { + Value *val(node->getValue()); + const float floatVal(val->getFloat()); + if (0 == strncmp("scale", prop->m_value->getString(), strlen("scale"))) { + m_currentLight->mAttenuationQuadratic = floatVal; + } + } + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::copyMeshes(aiScene *pScene) { + ai_assert(nullptr != pScene); + + if (m_meshCache.empty()) { + return; + } + + pScene->mNumMeshes = static_cast<unsigned int>(m_meshCache.size()); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + for (unsigned int i = 0; i < pScene->mNumMeshes; i++) { + pScene->mMeshes[i] = m_meshCache[i].release(); + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::copyCameras(aiScene *pScene) { + ai_assert(nullptr != pScene); + + if (m_cameraCache.empty()) { + return; + } + + pScene->mNumCameras = static_cast<unsigned int>(m_cameraCache.size()); + pScene->mCameras = new aiCamera *[pScene->mNumCameras]; + std::copy(m_cameraCache.begin(), m_cameraCache.end(), pScene->mCameras); +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::copyLights(aiScene *pScene) { + ai_assert(nullptr != pScene); + + if (m_lightCache.empty()) { + return; + } + + pScene->mNumLights = static_cast<unsigned int>(m_lightCache.size()); + pScene->mLights = new aiLight *[pScene->mNumLights]; + std::copy(m_lightCache.begin(), m_lightCache.end(), pScene->mLights); +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::copyMaterials(aiScene *pScene) { + ai_assert(nullptr != pScene); + + if (m_materialCache.empty()) { + return; + } + + pScene->mNumMaterials = static_cast<unsigned int>(m_materialCache.size()); + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + std::copy(m_materialCache.begin(), m_materialCache.end(), pScene->mMaterials); +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::resolveReferences() { + if (m_unresolvedRefStack.empty()) { + return; + } + + RefInfo *currentRefInfo(nullptr); + for (auto it = m_unresolvedRefStack.begin(); it != m_unresolvedRefStack.end(); ++it) { + currentRefInfo = it->get(); + if (nullptr != currentRefInfo) { + aiNode *node(currentRefInfo->m_node); + if (RefInfo::MeshRef == currentRefInfo->m_type) { + for (size_t i = 0; i < currentRefInfo->m_Names.size(); ++i) { + const std::string &name(currentRefInfo->m_Names[i]); + ReferenceMap::const_iterator curIt(m_mesh2refMap.find(name)); + if (m_mesh2refMap.end() != curIt) { + unsigned int meshIdx = static_cast<unsigned int>(m_mesh2refMap[name]); + node->mMeshes[i] = meshIdx; + } + } + } else if (RefInfo::MaterialRef == currentRefInfo->m_type) { + for (size_t i = 0; i < currentRefInfo->m_Names.size(); ++i) { + const std::string name(currentRefInfo->m_Names[i]); + ReferenceMap::const_iterator curIt(m_material2refMap.find(name)); + if (m_material2refMap.end() != curIt) { + if (nullptr != m_currentMesh) { + unsigned int matIdx = static_cast<unsigned int>(m_material2refMap[name]); + if (m_currentMesh->mMaterialIndex != 0) { + ASSIMP_LOG_WARN("Override of material reference in current mesh by material reference."); + } + m_currentMesh->mMaterialIndex = matIdx; + } else { + ASSIMP_LOG_WARN("Cannot resolve material reference, because no current mesh is there."); + } + } + } + } else { + throw DeadlyImportError("Unknown reference info to resolve."); + } + } + } +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::createNodeTree(aiScene *pScene) { + if (nullptr == m_root) { + return; + } + + if (m_root->m_children.empty()) { + return; + } + + pScene->mRootNode->mNumChildren = static_cast<unsigned int>(m_root->m_children.size()); + pScene->mRootNode->mChildren = new aiNode *[pScene->mRootNode->mNumChildren]; + std::copy(m_root->m_children.begin(), m_root->m_children.end(), pScene->mRootNode->mChildren); +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::pushNode(aiNode *node, aiScene *pScene) { + ai_assert(nullptr != pScene); + + if (nullptr == node) { + return; + } + + ChildInfo *info(nullptr); + if (m_nodeStack.empty()) { + node->mParent = pScene->mRootNode; + NodeChildMap::iterator it(m_nodeChildMap.find(node->mParent)); + if (m_nodeChildMap.end() == it) { + info = new ChildInfo; + m_root = info; + m_nodeChildMap[node->mParent] = std::unique_ptr<ChildInfo>(info); + } else { + info = it->second.get(); + } + info->m_children.push_back(node); + } else { + aiNode *parent(m_nodeStack.back()); + ai_assert(nullptr != parent); + node->mParent = parent; + NodeChildMap::iterator it(m_nodeChildMap.find(node->mParent)); + if (m_nodeChildMap.end() == it) { + info = new ChildInfo; + m_nodeChildMap[node->mParent] = std::unique_ptr<ChildInfo>(info); + } else { + info = it->second.get(); + } + info->m_children.push_back(node); + } + m_nodeStack.push_back(node); +} + +//------------------------------------------------------------------------------------------------ +aiNode *OpenGEXImporter::popNode() { + if (m_nodeStack.empty()) { + return nullptr; + } + + aiNode *node(top()); + m_nodeStack.pop_back(); + + return node; +} + +//------------------------------------------------------------------------------------------------ +aiNode *OpenGEXImporter::top() const { + if (m_nodeStack.empty()) { + return nullptr; + } + + return m_nodeStack.back(); +} + +//------------------------------------------------------------------------------------------------ +void OpenGEXImporter::clearNodeStack() { + m_nodeStack.clear(); +} + +//------------------------------------------------------------------------------------------------ + +} // Namespace OpenGEX +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_OPENGEX_IMPORTER diff --git a/libs/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.h b/libs/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.h new file mode 100644 index 0000000..fca3eb1 --- /dev/null +++ b/libs/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.h @@ -0,0 +1,213 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#pragma once +#ifndef AI_OPENGEX_IMPORTER_H +#define AI_OPENGEX_IMPORTER_H + +#ifndef ASSIMP_BUILD_NO_OPENGEX_IMPORTER + +#include <assimp/BaseImporter.h> +#include <assimp/mesh.h> + +#include <vector> +#include <list> +#include <map> +#include <memory> + +namespace ODDLParser { + class DDLNode; + struct Context; +} + +struct aiNode; +struct aiMaterial; +struct aiCamera; +struct aiLight; + +namespace Assimp { +namespace OpenGEX { + +struct MetricInfo { + enum Type { + Distance = 0, + Angle, + Time, + Up, + Max + }; + + std::string m_stringValue; + float m_floatValue; + int m_intValue; + + MetricInfo() + : m_stringValue( ) + , m_floatValue( 0.0f ) + , m_intValue( -1 ) { + // empty + } +}; + +/** @brief This class is used to implement the OpenGEX importer + * + * See http://opengex.org/OpenGEX.pdf for spec. + */ +class OpenGEXImporter : public BaseImporter { +public: + /// The class constructor. + OpenGEXImporter(); + + /// The class destructor. + ~OpenGEXImporter() override; + + /// BaseImporter override. + bool CanRead( const std::string &file, IOSystem *pIOHandler, bool checkSig ) const override; + +protected: + /// BaseImporter override. + void InternReadFile( const std::string &file, aiScene *pScene, IOSystem *pIOHandler ) override; + + /// BaseImporter override. + virtual const aiImporterDesc *GetInfo() const override; + + /// BaseImporter override. + virtual void SetupProperties( const Importer *pImp ) override; + + void handleNodes( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleMetricNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleNameNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleObjectRefNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleMaterialRefNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleGeometryNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleCameraNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleLightNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleGeometryObject( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleCameraObject( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleLightObject( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleTransformNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleMeshNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleVertexArrayNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleIndexArrayNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleMaterialNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleColorNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleTextureNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleParamNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void handleAttenNode( ODDLParser::DDLNode *node, aiScene *pScene ); + void copyMeshes( aiScene *pScene ); + void copyCameras( aiScene *pScene ); + void copyLights( aiScene *pScene ); + void copyMaterials( aiScene *pScene ); + void resolveReferences(); + void pushNode( aiNode *node, aiScene *pScene ); + aiNode *popNode(); + aiNode *top() const; + void clearNodeStack(); + void createNodeTree( aiScene *pScene ); + +private: + struct VertexContainer { + std::vector<aiVector3D> m_vertices; + size_t m_numColors; + aiColor4D *m_colors; + std::vector<aiVector3D> m_normals; + size_t m_numUVComps[ AI_MAX_NUMBER_OF_TEXTURECOORDS ]; + aiVector3D *m_textureCoords[ AI_MAX_NUMBER_OF_TEXTURECOORDS ]; + + VertexContainer(); + ~VertexContainer(); + + VertexContainer( const VertexContainer & ) = delete; + VertexContainer &operator = ( const VertexContainer & ) = delete; + }; + + struct RefInfo { + enum Type { + MeshRef, + MaterialRef + }; + + aiNode *m_node; + Type m_type; + std::vector<std::string> m_Names; + + RefInfo( aiNode *node, Type type, std::vector<std::string> &names ); + ~RefInfo(); + + RefInfo( const RefInfo & ) = delete; + RefInfo &operator = ( const RefInfo & ) = delete; + }; + + struct ChildInfo { + using NodeList = std::list<aiNode*>; + std::list<aiNode*> m_children; + }; + ChildInfo *m_root; + using NodeChildMap = std::map<aiNode*, std::unique_ptr<ChildInfo> >; + NodeChildMap m_nodeChildMap; + + std::vector<std::unique_ptr<aiMesh> > m_meshCache; + using ReferenceMap = std::map<std::string, size_t>; + std::map<std::string, size_t> m_mesh2refMap; + std::map<std::string, size_t> m_material2refMap; + + ODDLParser::Context *m_ctx; + MetricInfo m_metrics[ MetricInfo::Max ]; + aiNode *m_currentNode; + VertexContainer m_currentVertices; + aiMesh *m_currentMesh; // not owned, target is owned by m_meshCache + aiMaterial *m_currentMaterial; + aiLight *m_currentLight; + aiCamera *m_currentCamera; + int m_tokenType; + std::vector<aiMaterial*> m_materialCache; + std::vector<aiCamera*> m_cameraCache; + std::vector<aiLight*> m_lightCache; + std::vector<aiNode*> m_nodeStack; + std::vector<std::unique_ptr<RefInfo> > m_unresolvedRefStack; +}; + +} // Namespace OpenGEX +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_OPENGEX_IMPORTER + +#endif // AI_OPENGEX_IMPORTER_H diff --git a/libs/assimp/code/AssetLib/OpenGEX/OpenGEXStructs.h b/libs/assimp/code/AssetLib/OpenGEX/OpenGEXStructs.h new file mode 100644 index 0000000..5f84548 --- /dev/null +++ b/libs/assimp/code/AssetLib/OpenGEX/OpenGEXStructs.h @@ -0,0 +1,264 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef AI_OPENGEXSTRUCTS_H_INC +#define AI_OPENGEXSTRUCTS_H_INC + +#include <string> +#include <map> + +namespace Assimp { +namespace OpenGEX { + +struct Skin; +struct Object; +struct LightObject; +struct CameraObject; +struct Material; +struct BoneNode; +struct BoneCountArray; +struct BoneIndexArray; +struct BoneWeightArray; + +struct Metric { + float m_distance; + float m_angle; + float m_time; + float m_up; +}; + +struct VertexArray { + std::string arrayAttrib; + unsigned int morphIndex; +}; + +struct IndexArray { + unsigned int materialIndex; + unsigned int restartIndex; + std::string frontFace; +}; + +struct Mesh { + unsigned int meshLevel; + std::string meshPrimitive; + Skin *skinStructure; +}; + +struct Node { + std::string nodeName; +}; + +struct GeometryNode { + bool visibleFlag[ 2 ]; + bool shadowFlag[ 2 ]; + bool motionBlurFlag[ 2 ]; +}; + +struct LightNode { + bool shadowFlag[ 2 ]; + const LightObject *lightObjectStructure; +}; + +struct CameraNode { + const CameraObject *cameraObjectStructure; +}; + +struct GeometryObject { + Object *object; + bool visibleFlag; + bool shadowFlag; + bool motionBlurFlag; + std::map<std::string, Mesh*> meshMap; +}; + +struct LightObject { + Object *object; + std::string typeString; + bool shadowFlag; +}; + +struct CameraObject { + float focalLength; + float nearDepth; + float farDepth; +}; + +struct Matrix { + bool objectFlag; +}; + +struct Transform { + Matrix *matrix; + int transformCount; + const float *transformArray; +}; + +struct Translation { + std::string translationKind; +}; + +struct Rotation { + std::string rotationKind; +}; + +struct Scale { + std::string scaleKind; +}; + +struct Name { + std::string name; +}; + +struct ObjectRef { + Object *targetStructure; +}; + +struct MaterialRef { + unsigned int materialIndex; + const Material *targetStructure; +}; + +struct BoneRefArray { + int boneCount; + const BoneNode **boneNodeArray; +}; + +struct BoneCount { + int vertexCount; + const unsigned short *boneCountArray; + unsigned short *arrayStorage; +}; + +struct BoneIndex { + int boneIndexCount; + const unsigned short *boneIndexArray; + unsigned short *arrayStorage; +}; + +struct BoneWeight { + int boneWeightCount; + const float *boneWeightArray; +}; + +struct Skeleton { + const BoneRefArray *boneRefArrayStructure; + const Transform *transformStructure; +}; + +struct Skin { + const Skeleton *skeletonStructure; + const BoneCountArray *boneCountArrayStructure; + const BoneIndexArray *boneIndexArrayStructure; + const BoneWeightArray *boneWeightArrayStructure; +}; + +struct Material { + bool twoSidedFlag; + const char *materialName; +}; + +struct Attrib { + std::string attribString; +}; + +struct Param { + float param; +}; + +struct Color { + float color[ 4 ]; +}; + +struct Texture { + std::string textureName; + unsigned int texcoordIndex; +}; + +struct Atten { + std::string attenKind; + std::string curveType; + + float beginParam; + float endParam; + + float scaleParam; + float offsetParam; + + float constantParam; + float linearParam; + float quadraticParam; + + float powerParam; +}; + +struct Key { + std::string keyKind; + bool scalarFlag; +}; + +struct Curve { + std::string curveType; + const Key *keyValueStructure; + const Key *keyControlStructure[ 2 ]; + const Key *keyTensionStructure; + const Key *keyContinuityStructure; + const Key *keyBiasStructure; +}; + +struct Animation { + int clipIndex; + bool beginFlag; + bool endFlag; + float beginTime; + float endTime; +}; + +struct OpenGexDataDescription { + float distanceScale; + float angleScale; + float timeScale; + int upDirection; +}; + +} // Namespace OpenGEX +} // Namespace Assimp + +#endif // AI_OPENGEXSTRUCTS_H_INC diff --git a/libs/assimp/code/AssetLib/Ply/PlyExporter.cpp b/libs/assimp/code/AssetLib/Ply/PlyExporter.cpp new file mode 100644 index 0000000..a98b83f --- /dev/null +++ b/libs/assimp/code/AssetLib/Ply/PlyExporter.cpp @@ -0,0 +1,406 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + + + +#if !defined(ASSIMP_BUILD_NO_EXPORT) && !defined(ASSIMP_BUILD_NO_PLY_EXPORTER) + +#include "PlyExporter.h" +#include <memory> +#include <cmath> +#include <assimp/Exceptional.h> +#include <assimp/scene.h> +#include <assimp/version.h> +#include <assimp/IOSystem.hpp> +#include <assimp/Exporter.hpp> +#include <assimp/qnan.h> + + +//using namespace Assimp; +namespace Assimp { + +// make sure type_of returns consistent output across different platforms +// also consider using: typeid(VAR).name() +template <typename T> const char* type_of(T&) { return "unknown"; } +template<> const char* type_of(float&) { return "float"; } +template<> const char* type_of(double&) { return "double"; } + +// ------------------------------------------------------------------------------------------------ +// Worker function for exporting a scene to PLY. Prototyped and registered in Exporter.cpp +void ExportScenePly(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) +{ + // invoke the exporter + PlyExporter exporter(pFile, pScene); + + if (exporter.mOutput.fail()) { + throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); + } + + // we're still here - export successfully completed. Write the file. + std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt")); + if (outfile == nullptr) { + throw DeadlyExportError("could not open output .ply file: " + std::string(pFile)); + } + + outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1); +} + +void ExportScenePlyBinary(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) +{ + // invoke the exporter + PlyExporter exporter(pFile, pScene, true); + + // we're still here - export successfully completed. Write the file. + std::unique_ptr<IOStream> outfile(pIOSystem->Open(pFile, "wb")); + if (outfile == nullptr) { + throw DeadlyExportError("could not open output .ply file: " + std::string(pFile)); + } + + outfile->Write(exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()), 1); +} + +#define PLY_EXPORT_HAS_NORMALS 0x1 +#define PLY_EXPORT_HAS_TANGENTS_BITANGENTS 0x2 +#define PLY_EXPORT_HAS_TEXCOORDS 0x4 +#define PLY_EXPORT_HAS_COLORS (PLY_EXPORT_HAS_TEXCOORDS << AI_MAX_NUMBER_OF_TEXTURECOORDS) + +// ------------------------------------------------------------------------------------------------ +PlyExporter::PlyExporter(const char* _filename, const aiScene* pScene, bool binary) +: filename(_filename) +, endl("\n") +{ + // make sure that all formatting happens using the standard, C locale and not the user's current locale + const std::locale& l = std::locale("C"); + mOutput.imbue(l); + mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); + + unsigned int faces = 0u, vertices = 0u, components = 0u; + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + const aiMesh& m = *pScene->mMeshes[i]; + faces += m.mNumFaces; + vertices += m.mNumVertices; + + if (m.HasNormals()) { + components |= PLY_EXPORT_HAS_NORMALS; + } + if (m.HasTangentsAndBitangents()) { + components |= PLY_EXPORT_HAS_TANGENTS_BITANGENTS; + } + for (unsigned int t = 0; m.HasTextureCoords(t); ++t) { + components |= PLY_EXPORT_HAS_TEXCOORDS << t; + } + for (unsigned int t = 0; m.HasVertexColors(t); ++t) { + components |= PLY_EXPORT_HAS_COLORS << t; + } + } + + mOutput << "ply" << endl; + if (binary) { +#if (defined AI_BUILD_BIG_ENDIAN) + mOutput << "format binary_big_endian 1.0" << endl; +#else + mOutput << "format binary_little_endian 1.0" << endl; +#endif + } + else { + mOutput << "format ascii 1.0" << endl; + } + mOutput << "comment Created by Open Asset Import Library - http://assimp.sf.net (v" + << aiGetVersionMajor() << '.' << aiGetVersionMinor() << '.' + << aiGetVersionRevision() << ")" << endl; + + // Look through materials for a diffuse texture, and add it if found + for ( unsigned int i = 0; i < pScene->mNumMaterials; ++i ) + { + const aiMaterial* const mat = pScene->mMaterials[i]; + aiString s; + if ( AI_SUCCESS == mat->Get( AI_MATKEY_TEXTURE_DIFFUSE( 0 ), s ) ) + { + mOutput << "comment TextureFile " << s.data << endl; + } + } + + // TODO: probably want to check here rather than just assume something + // definitely not good to always write float even if we might have double precision + + ai_real tmp = 0.0; + const char * typeName = type_of(tmp); + + mOutput << "element vertex " << vertices << endl; + mOutput << "property " << typeName << " x" << endl; + mOutput << "property " << typeName << " y" << endl; + mOutput << "property " << typeName << " z" << endl; + + if(components & PLY_EXPORT_HAS_NORMALS) { + mOutput << "property " << typeName << " nx" << endl; + mOutput << "property " << typeName << " ny" << endl; + mOutput << "property " << typeName << " nz" << endl; + } + + // write texcoords first, just in case an importer does not support tangents + // bitangents and just skips over the rest of the line upon encountering + // unknown fields (Ply leaves pretty much every vertex component open, + // but in reality most importers only know about vertex positions, normals + // and texture coordinates). + for (unsigned int n = PLY_EXPORT_HAS_TEXCOORDS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_TEXTURECOORDS; n <<= 1, ++c) { + if (!c) { + mOutput << "property " << typeName << " s" << endl; + mOutput << "property " << typeName << " t" << endl; + } + else { + mOutput << "property " << typeName << " s" << c << endl; + mOutput << "property " << typeName << " t" << c << endl; + } + } + + for (unsigned int n = PLY_EXPORT_HAS_COLORS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_COLOR_SETS; n <<= 1, ++c) { + if (!c) { + mOutput << "property " << "uchar" << " red" << endl; + mOutput << "property " << "uchar" << " green" << endl; + mOutput << "property " << "uchar" << " blue" << endl; + mOutput << "property " << "uchar" << " alpha" << endl; + } + else { + mOutput << "property " << "uchar" << " red" << c << endl; + mOutput << "property " << "uchar" << " green" << c << endl; + mOutput << "property " << "uchar" << " blue" << c << endl; + mOutput << "property " << "uchar" << " alpha" << c << endl; + } + } + + if(components & PLY_EXPORT_HAS_TANGENTS_BITANGENTS) { + mOutput << "property " << typeName << " tx" << endl; + mOutput << "property " << typeName << " ty" << endl; + mOutput << "property " << typeName << " tz" << endl; + mOutput << "property " << typeName << " bx" << endl; + mOutput << "property " << typeName << " by" << endl; + mOutput << "property " << typeName << " bz" << endl; + } + + mOutput << "element face " << faces << endl; + + // uchar seems to be the most common type for the number of indices per polygon and int seems to be most common for the vertex indices. + // For instance, MeshLab fails to load meshes in which both types are uint. Houdini seems to have problems as well. + // Obviously, using uchar will not work for meshes with polygons with more than 255 indices, but how realistic is this case? + mOutput << "property list uchar int vertex_index" << endl; + + mOutput << "end_header" << endl; + + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + if (binary) { + WriteMeshVertsBinary(pScene->mMeshes[i], components); + } + else { + WriteMeshVerts(pScene->mMeshes[i], components); + } + } + for (unsigned int i = 0, ofs = 0; i < pScene->mNumMeshes; ++i) { + if (binary) { + WriteMeshIndicesBinary(pScene->mMeshes[i], ofs); + } + else { + WriteMeshIndices(pScene->mMeshes[i], ofs); + } + ofs += pScene->mMeshes[i]->mNumVertices; + } +} + +// ------------------------------------------------------------------------------------------------ +PlyExporter::~PlyExporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +void PlyExporter::WriteMeshVerts(const aiMesh* m, unsigned int components) +{ + static const ai_real inf = std::numeric_limits<ai_real>::infinity(); + + // If a component (for instance normal vectors) is present in at least one mesh in the scene, + // then default values are written for meshes that do not contain this component. + for (unsigned int i = 0; i < m->mNumVertices; ++i) { + mOutput << + m->mVertices[i].x << " " << + m->mVertices[i].y << " " << + m->mVertices[i].z + ; + if(components & PLY_EXPORT_HAS_NORMALS) { + if (m->HasNormals() && is_not_qnan(m->mNormals[i].x) && std::fabs(m->mNormals[i].x) != inf) { + mOutput << + " " << m->mNormals[i].x << + " " << m->mNormals[i].y << + " " << m->mNormals[i].z; + } + else { + mOutput << " 0.0 0.0 0.0"; + } + } + + for (unsigned int n = PLY_EXPORT_HAS_TEXCOORDS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_TEXTURECOORDS; n <<= 1, ++c) { + if (m->HasTextureCoords(c)) { + mOutput << + " " << m->mTextureCoords[c][i].x << + " " << m->mTextureCoords[c][i].y; + } + else { + mOutput << " -1.0 -1.0"; + } + } + + for (unsigned int n = PLY_EXPORT_HAS_COLORS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_COLOR_SETS; n <<= 1, ++c) { + if (m->HasVertexColors(c)) { + mOutput << + " " << (int)(m->mColors[c][i].r * 255) << + " " << (int)(m->mColors[c][i].g * 255) << + " " << (int)(m->mColors[c][i].b * 255) << + " " << (int)(m->mColors[c][i].a * 255); + } + else { + mOutput << " 0 0 0"; + } + } + + if(components & PLY_EXPORT_HAS_TANGENTS_BITANGENTS) { + if (m->HasTangentsAndBitangents()) { + mOutput << + " " << m->mTangents[i].x << + " " << m->mTangents[i].y << + " " << m->mTangents[i].z << + " " << m->mBitangents[i].x << + " " << m->mBitangents[i].y << + " " << m->mBitangents[i].z + ; + } + else { + mOutput << " 0.0 0.0 0.0 0.0 0.0 0.0"; + } + } + + mOutput << endl; + } +} + +// ------------------------------------------------------------------------------------------------ +void PlyExporter::WriteMeshVertsBinary(const aiMesh* m, unsigned int components) +{ + // If a component (for instance normal vectors) is present in at least one mesh in the scene, + // then default values are written for meshes that do not contain this component. + aiVector3D defaultNormal(0, 0, 0); + aiVector2D defaultUV(-1, -1); + aiColor4D defaultColor(-1, -1, -1, -1); + for (unsigned int i = 0; i < m->mNumVertices; ++i) { + mOutput.write(reinterpret_cast<const char*>(&m->mVertices[i].x), 12); + if (components & PLY_EXPORT_HAS_NORMALS) { + if (m->HasNormals()) { + mOutput.write(reinterpret_cast<const char*>(&m->mNormals[i].x), 12); + } + else { + mOutput.write(reinterpret_cast<const char*>(&defaultNormal.x), 12); + } + } + + for (unsigned int n = PLY_EXPORT_HAS_TEXCOORDS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_TEXTURECOORDS; n <<= 1, ++c) { + if (m->HasTextureCoords(c)) { + mOutput.write(reinterpret_cast<const char*>(&m->mTextureCoords[c][i].x), 8); + } + else { + mOutput.write(reinterpret_cast<const char*>(&defaultUV.x), 8); + } + } + + for (unsigned int n = PLY_EXPORT_HAS_COLORS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_COLOR_SETS; n <<= 1, ++c) { + if (m->HasVertexColors(c)) { + mOutput.write(reinterpret_cast<const char*>(&m->mColors[c][i].r), 16); + } + else { + mOutput.write(reinterpret_cast<const char*>(&defaultColor.r), 16); + } + } + + if (components & PLY_EXPORT_HAS_TANGENTS_BITANGENTS) { + if (m->HasTangentsAndBitangents()) { + mOutput.write(reinterpret_cast<const char*>(&m->mTangents[i].x), 12); + mOutput.write(reinterpret_cast<const char*>(&m->mBitangents[i].x), 12); + } + else { + mOutput.write(reinterpret_cast<const char*>(&defaultNormal.x), 12); + mOutput.write(reinterpret_cast<const char*>(&defaultNormal.x), 12); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void PlyExporter::WriteMeshIndices(const aiMesh* m, unsigned int offset) +{ + for (unsigned int i = 0; i < m->mNumFaces; ++i) { + const aiFace& f = m->mFaces[i]; + mOutput << f.mNumIndices; + for(unsigned int c = 0; c < f.mNumIndices; ++c) { + mOutput << " " << (f.mIndices[c] + offset); + } + mOutput << endl; + } +} + +// Generic method in case we want to use different data types for the indices or make this configurable. +template<typename NumIndicesType, typename IndexType> +void WriteMeshIndicesBinary_Generic(const aiMesh* m, unsigned int offset, std::ostringstream& output) +{ + for (unsigned int i = 0; i < m->mNumFaces; ++i) { + const aiFace& f = m->mFaces[i]; + NumIndicesType numIndices = static_cast<NumIndicesType>(f.mNumIndices); + output.write(reinterpret_cast<const char*>(&numIndices), sizeof(NumIndicesType)); + for (unsigned int c = 0; c < f.mNumIndices; ++c) { + IndexType index = f.mIndices[c] + offset; + output.write(reinterpret_cast<const char*>(&index), sizeof(IndexType)); + } + } +} + +void PlyExporter::WriteMeshIndicesBinary(const aiMesh* m, unsigned int offset) +{ + WriteMeshIndicesBinary_Generic<unsigned char, int>(m, offset, mOutput); +} + +} // end of namespace Assimp + +#endif // !defined(ASSIMP_BUILD_NO_EXPORT) && !defined(ASSIMP_BUILD_NO_PLY_EXPORTER) diff --git a/libs/assimp/code/AssetLib/Ply/PlyExporter.h b/libs/assimp/code/AssetLib/Ply/PlyExporter.h new file mode 100644 index 0000000..ff3a54c --- /dev/null +++ b/libs/assimp/code/AssetLib/Ply/PlyExporter.h @@ -0,0 +1,88 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 PlyExporter.h + * Declares the exporter class to write a scene to a Polygon Library (ply) + */ +#ifndef AI_PLYEXPORTER_H_INC +#define AI_PLYEXPORTER_H_INC + +#include <sstream> + +struct aiScene; +struct aiNode; +struct aiMesh; + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +/** Helper class to export a given scene to a Stanford Ply file. */ +// ------------------------------------------------------------------------------------------------ +class PlyExporter { +public: + /// The class constructor for a specific scene to export + PlyExporter(const char* filename, const aiScene* pScene, bool binary = false); + /// The class destructor, empty. + ~PlyExporter(); + +public: + /// public string-streams to write all output into: + std::ostringstream mOutput; + +private: + void WriteMeshVerts(const aiMesh* m, unsigned int components); + void WriteMeshIndices(const aiMesh* m, unsigned int ofs); + void WriteMeshVertsBinary(const aiMesh* m, unsigned int components); + void WriteMeshIndicesBinary(const aiMesh* m, unsigned int offset); + +private: + const std::string filename; // tHE FILENAME + const std::string endl; // obviously, this endl() doesn't flush() the stream + +private: + PlyExporter( const PlyExporter & ); + PlyExporter &operator = ( const PlyExporter & ); +}; + +} // Namespace Assimp + +#endif // AI_PLYEXPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/Ply/PlyLoader.cpp b/libs/assimp/code/AssetLib/Ply/PlyLoader.cpp new file mode 100644 index 0000000..6cf1a1c --- /dev/null +++ b/libs/assimp/code/AssetLib/Ply/PlyLoader.cpp @@ -0,0 +1,927 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 PlyLoader.cpp + * @brief Implementation of the PLY importer class + */ + +#ifndef ASSIMP_BUILD_NO_PLY_IMPORTER + +// internal headers +#include "PlyLoader.h" +#include <assimp/IOStreamBuffer.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/IOSystem.hpp> +#include <memory> + +using namespace ::Assimp; + +static const aiImporterDesc desc = { + "Stanford Polygon Library (PLY) Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "ply" +}; + +// ------------------------------------------------------------------------------------------------ +// Internal stuff +namespace { +// ------------------------------------------------------------------------------------------------ +// Checks that property index is within range +template <class T> +inline const T &GetProperty(const std::vector<T> &props, int idx) { + if (static_cast<size_t>(idx) >= props.size()) { + throw DeadlyImportError("Invalid .ply file: Property index is out of range."); + } + + return props[idx]; +} +} // namespace + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +PLYImporter::PLYImporter() : + mBuffer(nullptr), + pcDOM(nullptr), + mGeneratedMesh(nullptr) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +PLYImporter::~PLYImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool PLYImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "ply" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc *PLYImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +static bool isBigEndian(const char *szMe) { + ai_assert(nullptr != szMe); + + // binary_little_endian + // binary_big_endian + bool isBigEndian(false); +#if (defined AI_BUILD_BIG_ENDIAN) + if ('l' == *szMe || 'L' == *szMe) { + isBigEndian = true; + } +#else + if ('b' == *szMe || 'B' == *szMe) { + isBigEndian = true; + } +#endif // ! AI_BUILD_BIG_ENDIAN + + return isBigEndian; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + const std::string mode = "rb"; + std::unique_ptr<IOStream> fileStream(pIOHandler->Open(pFile, mode)); + if (!fileStream.get()) { + throw DeadlyImportError("Failed to open file ", pFile, "."); + } + + // Get the file-size + const size_t fileSize(fileStream->FileSize()); + if (0 == fileSize) { + throw DeadlyImportError("File ", pFile, " is empty."); + } + + IOStreamBuffer<char> streamedBuffer(1024 * 1024); + streamedBuffer.open(fileStream.get()); + + // the beginning of the file must be PLY - magic, magic + std::vector<char> headerCheck; + streamedBuffer.getNextLine(headerCheck); + + if ((headerCheck.size() < 3) || + (headerCheck[0] != 'P' && headerCheck[0] != 'p') || + (headerCheck[1] != 'L' && headerCheck[1] != 'l') || + (headerCheck[2] != 'Y' && headerCheck[2] != 'y')) { + streamedBuffer.close(); + throw DeadlyImportError("Invalid .ply file: Incorrect magic number (expected 'ply' or 'PLY')."); + } + + std::vector<char> mBuffer2; + streamedBuffer.getNextLine(mBuffer2); + mBuffer = (unsigned char *)&mBuffer2[0]; + + char *szMe = (char *)&this->mBuffer[0]; + SkipSpacesAndLineEnd(szMe, (const char **)&szMe); + + // determine the format of the file data and construct the aiMesh + PLY::DOM sPlyDom; + this->pcDOM = &sPlyDom; + + if (TokenMatch(szMe, "format", 6)) { + if (TokenMatch(szMe, "ascii", 5)) { + SkipLine(szMe, (const char **)&szMe); + if (!PLY::DOM::ParseInstance(streamedBuffer, &sPlyDom, this)) { + if (mGeneratedMesh != nullptr) { + delete (mGeneratedMesh); + mGeneratedMesh = nullptr; + } + + streamedBuffer.close(); + throw DeadlyImportError("Invalid .ply file: Unable to build DOM (#1)"); + } + } else if (!::strncmp(szMe, "binary_", 7)) { + szMe += 7; + const bool bIsBE(isBigEndian(szMe)); + + // skip the line, parse the rest of the header and build the DOM + if (!PLY::DOM::ParseInstanceBinary(streamedBuffer, &sPlyDom, this, bIsBE)) { + if (mGeneratedMesh != nullptr) { + delete (mGeneratedMesh); + mGeneratedMesh = nullptr; + } + + streamedBuffer.close(); + throw DeadlyImportError("Invalid .ply file: Unable to build DOM (#2)"); + } + } else { + if (mGeneratedMesh != nullptr) { + delete (mGeneratedMesh); + mGeneratedMesh = nullptr; + } + + streamedBuffer.close(); + throw DeadlyImportError("Invalid .ply file: Unknown file format"); + } + } else { + AI_DEBUG_INVALIDATE_PTR(this->mBuffer); + if (mGeneratedMesh != nullptr) { + delete (mGeneratedMesh); + mGeneratedMesh = nullptr; + } + + streamedBuffer.close(); + throw DeadlyImportError("Invalid .ply file: Missing format specification"); + } + + //free the file buffer + streamedBuffer.close(); + + if (mGeneratedMesh == nullptr) { + throw DeadlyImportError("Invalid .ply file: Unable to extract mesh data "); + } + + // if no face list is existing we assume that the vertex + // list is containing a list of points + bool pointsOnly = mGeneratedMesh->mFaces == nullptr ? true : false; + if (pointsOnly) { + mGeneratedMesh->mPrimitiveTypes = aiPrimitiveType::aiPrimitiveType_POINT; + } + + // now load a list of all materials + std::vector<aiMaterial *> avMaterials; + std::string defaultTexture; + LoadMaterial(&avMaterials, defaultTexture, pointsOnly); + + // now generate the output scene object. Fill the material list + pScene->mNumMaterials = (unsigned int)avMaterials.size(); + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) { + pScene->mMaterials[i] = avMaterials[i]; + } + + // fill the mesh list + pScene->mNumMeshes = 1; + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + pScene->mMeshes[0] = mGeneratedMesh; + mGeneratedMesh = nullptr; + + // generate a simple node structure + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; + pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; + + for (unsigned int i = 0; i < pScene->mRootNode->mNumMeshes; ++i) { + pScene->mRootNode->mMeshes[i] = i; + } +} + +void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementInstance *instElement, unsigned int pos) { + ai_assert(nullptr != pcElement); + ai_assert(nullptr != instElement); + + ai_uint aiPositions[3] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + PLY::EDataType aiTypes[3] = { EDT_Char, EDT_Char, EDT_Char }; + + ai_uint aiNormal[3] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + PLY::EDataType aiNormalTypes[3] = { EDT_Char, EDT_Char, EDT_Char }; + + unsigned int aiColors[4] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + PLY::EDataType aiColorsTypes[4] = { EDT_Char, EDT_Char, EDT_Char, EDT_Char }; + + unsigned int aiTexcoord[2] = { 0xFFFFFFFF, 0xFFFFFFFF }; + PLY::EDataType aiTexcoordTypes[2] = { EDT_Char, EDT_Char }; + + // now check whether which normal components are available + unsigned int _a(0), cnt(0); + for (std::vector<PLY::Property>::const_iterator a = pcElement->alProperties.begin(); + a != pcElement->alProperties.end(); ++a, ++_a) { + if ((*a).bIsList) { + continue; + } + + // Positions + if (PLY::EST_XCoord == (*a).Semantic) { + ++cnt; + aiPositions[0] = _a; + aiTypes[0] = (*a).eType; + } else if (PLY::EST_YCoord == (*a).Semantic) { + ++cnt; + aiPositions[1] = _a; + aiTypes[1] = (*a).eType; + } else if (PLY::EST_ZCoord == (*a).Semantic) { + ++cnt; + aiPositions[2] = _a; + aiTypes[2] = (*a).eType; + } else if (PLY::EST_XNormal == (*a).Semantic) { + // Normals + ++cnt; + aiNormal[0] = _a; + aiNormalTypes[0] = (*a).eType; + } else if (PLY::EST_YNormal == (*a).Semantic) { + ++cnt; + aiNormal[1] = _a; + aiNormalTypes[1] = (*a).eType; + } else if (PLY::EST_ZNormal == (*a).Semantic) { + ++cnt; + aiNormal[2] = _a; + aiNormalTypes[2] = (*a).eType; + } else if (PLY::EST_Red == (*a).Semantic) { + // Colors + ++cnt; + aiColors[0] = _a; + aiColorsTypes[0] = (*a).eType; + } else if (PLY::EST_Green == (*a).Semantic) { + ++cnt; + aiColors[1] = _a; + aiColorsTypes[1] = (*a).eType; + } else if (PLY::EST_Blue == (*a).Semantic) { + ++cnt; + aiColors[2] = _a; + aiColorsTypes[2] = (*a).eType; + } else if (PLY::EST_Alpha == (*a).Semantic) { + ++cnt; + aiColors[3] = _a; + aiColorsTypes[3] = (*a).eType; + } else if (PLY::EST_UTextureCoord == (*a).Semantic) { + // Texture coordinates + ++cnt; + aiTexcoord[0] = _a; + aiTexcoordTypes[0] = (*a).eType; + } else if (PLY::EST_VTextureCoord == (*a).Semantic) { + ++cnt; + aiTexcoord[1] = _a; + aiTexcoordTypes[1] = (*a).eType; + } + } + + // check whether we have a valid source for the vertex data + if (0 != cnt) { + // Position + aiVector3D vOut; + if (0xFFFFFFFF != aiPositions[0]) { + vOut.x = PLY::PropertyInstance::ConvertTo<ai_real>( + GetProperty(instElement->alProperties, aiPositions[0]).avList.front(), aiTypes[0]); + } + + if (0xFFFFFFFF != aiPositions[1]) { + vOut.y = PLY::PropertyInstance::ConvertTo<ai_real>( + GetProperty(instElement->alProperties, aiPositions[1]).avList.front(), aiTypes[1]); + } + + if (0xFFFFFFFF != aiPositions[2]) { + vOut.z = PLY::PropertyInstance::ConvertTo<ai_real>( + GetProperty(instElement->alProperties, aiPositions[2]).avList.front(), aiTypes[2]); + } + + // Normals + aiVector3D nOut; + bool haveNormal = false; + if (0xFFFFFFFF != aiNormal[0]) { + nOut.x = PLY::PropertyInstance::ConvertTo<ai_real>( + GetProperty(instElement->alProperties, aiNormal[0]).avList.front(), aiNormalTypes[0]); + haveNormal = true; + } + + if (0xFFFFFFFF != aiNormal[1]) { + nOut.y = PLY::PropertyInstance::ConvertTo<ai_real>( + GetProperty(instElement->alProperties, aiNormal[1]).avList.front(), aiNormalTypes[1]); + haveNormal = true; + } + + if (0xFFFFFFFF != aiNormal[2]) { + nOut.z = PLY::PropertyInstance::ConvertTo<ai_real>( + GetProperty(instElement->alProperties, aiNormal[2]).avList.front(), aiNormalTypes[2]); + haveNormal = true; + } + + //Colors + aiColor4D cOut; + bool haveColor = false; + if (0xFFFFFFFF != aiColors[0]) { + cOut.r = NormalizeColorValue(GetProperty(instElement->alProperties, + aiColors[0]) + .avList.front(), + aiColorsTypes[0]); + haveColor = true; + } + + if (0xFFFFFFFF != aiColors[1]) { + cOut.g = NormalizeColorValue(GetProperty(instElement->alProperties, + aiColors[1]) + .avList.front(), + aiColorsTypes[1]); + haveColor = true; + } + + if (0xFFFFFFFF != aiColors[2]) { + cOut.b = NormalizeColorValue(GetProperty(instElement->alProperties, + aiColors[2]) + .avList.front(), + aiColorsTypes[2]); + haveColor = true; + } + + // assume 1.0 for the alpha channel if it is not set + if (0xFFFFFFFF == aiColors[3]) { + cOut.a = 1.0; + } else { + cOut.a = NormalizeColorValue(GetProperty(instElement->alProperties, + aiColors[3]) + .avList.front(), + aiColorsTypes[3]); + + haveColor = true; + } + + //Texture coordinates + aiVector3D tOut; + tOut.z = 0; + bool haveTextureCoords = false; + if (0xFFFFFFFF != aiTexcoord[0]) { + tOut.x = PLY::PropertyInstance::ConvertTo<ai_real>( + GetProperty(instElement->alProperties, aiTexcoord[0]).avList.front(), aiTexcoordTypes[0]); + haveTextureCoords = true; + } + + if (0xFFFFFFFF != aiTexcoord[1]) { + tOut.y = PLY::PropertyInstance::ConvertTo<ai_real>( + GetProperty(instElement->alProperties, aiTexcoord[1]).avList.front(), aiTexcoordTypes[1]); + haveTextureCoords = true; + } + + //create aiMesh if needed + if (nullptr == mGeneratedMesh) { + mGeneratedMesh = new aiMesh(); + mGeneratedMesh->mMaterialIndex = 0; + } + + if (nullptr == mGeneratedMesh->mVertices) { + mGeneratedMesh->mNumVertices = pcElement->NumOccur; + mGeneratedMesh->mVertices = new aiVector3D[mGeneratedMesh->mNumVertices]; + } + + mGeneratedMesh->mVertices[pos] = vOut; + + if (haveNormal) { + if (nullptr == mGeneratedMesh->mNormals) + mGeneratedMesh->mNormals = new aiVector3D[mGeneratedMesh->mNumVertices]; + mGeneratedMesh->mNormals[pos] = nOut; + } + + if (haveColor) { + if (nullptr == mGeneratedMesh->mColors[0]) + mGeneratedMesh->mColors[0] = new aiColor4D[mGeneratedMesh->mNumVertices]; + mGeneratedMesh->mColors[0][pos] = cOut; + } + + if (haveTextureCoords) { + if (nullptr == mGeneratedMesh->mTextureCoords[0]) { + mGeneratedMesh->mNumUVComponents[0] = 2; + mGeneratedMesh->mTextureCoords[0] = new aiVector3D[mGeneratedMesh->mNumVertices]; + } + mGeneratedMesh->mTextureCoords[0][pos] = tOut; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Convert a color component to [0...1] +ai_real PLYImporter::NormalizeColorValue(PLY::PropertyInstance::ValueUnion val, PLY::EDataType eType) { + switch (eType) { + case EDT_Float: + return val.fFloat; + case EDT_Double: + return (ai_real)val.fDouble; + case EDT_UChar: + return (ai_real)val.iUInt / (ai_real)0xFF; + case EDT_Char: + return (ai_real)(val.iInt + (0xFF / 2)) / (ai_real)0xFF; + case EDT_UShort: + return (ai_real)val.iUInt / (ai_real)0xFFFF; + case EDT_Short: + return (ai_real)(val.iInt + (0xFFFF / 2)) / (ai_real)0xFFFF; + case EDT_UInt: + return (ai_real)val.iUInt / (ai_real)0xFFFF; + case EDT_Int: + return ((ai_real)val.iInt / (ai_real)0xFF) + 0.5f; + default: + break; + } + + return 0.0f; +} + +// ------------------------------------------------------------------------------------------------ +// Try to extract proper faces from the PLY DOM +void PLYImporter::LoadFace(const PLY::Element *pcElement, const PLY::ElementInstance *instElement, + unsigned int pos) { + ai_assert(nullptr != pcElement); + ai_assert(nullptr != instElement); + + if (mGeneratedMesh == nullptr) { + throw DeadlyImportError("Invalid .ply file: Vertices should be declared before faces"); + } + + bool bOne = false; + + // index of the vertex index list + unsigned int iProperty = 0xFFFFFFFF; + PLY::EDataType eType = EDT_Char; + bool bIsTriStrip = false; + + // index of the material index property + //unsigned int iMaterialIndex = 0xFFFFFFFF; + //PLY::EDataType eType2 = EDT_Char; + + // texture coordinates + unsigned int iTextureCoord = 0xFFFFFFFF; + PLY::EDataType eType3 = EDT_Char; + + // face = unique number of vertex indices + if (PLY::EEST_Face == pcElement->eSemantic) { + unsigned int _a = 0; + for (std::vector<PLY::Property>::const_iterator a = pcElement->alProperties.begin(); + a != pcElement->alProperties.end(); ++a, ++_a) { + if (PLY::EST_VertexIndex == (*a).Semantic) { + // must be a dynamic list! + if (!(*a).bIsList) { + continue; + } + + iProperty = _a; + bOne = true; + eType = (*a).eType; + } else if (PLY::EST_TextureCoordinates == (*a).Semantic) { + // must be a dynamic list! + if (!(*a).bIsList) { + continue; + } + iTextureCoord = _a; + bOne = true; + eType3 = (*a).eType; + } + } + } + // triangle strip + // TODO: triangle strip and material index support??? + else if (PLY::EEST_TriStrip == pcElement->eSemantic) { + unsigned int _a = 0; + for (std::vector<PLY::Property>::const_iterator a = pcElement->alProperties.begin(); + a != pcElement->alProperties.end(); ++a, ++_a) { + // must be a dynamic list! + if (!(*a).bIsList) { + continue; + } + iProperty = _a; + bOne = true; + bIsTriStrip = true; + eType = (*a).eType; + break; + } + } + + // check whether we have at least one per-face information set + if (bOne) { + if (mGeneratedMesh->mFaces == nullptr) { + mGeneratedMesh->mNumFaces = pcElement->NumOccur; + mGeneratedMesh->mFaces = new aiFace[mGeneratedMesh->mNumFaces]; + } + + if (!bIsTriStrip) { + // parse the list of vertex indices + if (0xFFFFFFFF != iProperty) { + const unsigned int iNum = (unsigned int)GetProperty(instElement->alProperties, iProperty).avList.size(); + mGeneratedMesh->mFaces[pos].mNumIndices = iNum; + mGeneratedMesh->mFaces[pos].mIndices = new unsigned int[iNum]; + + std::vector<PLY::PropertyInstance::ValueUnion>::const_iterator p = + GetProperty(instElement->alProperties, iProperty).avList.begin(); + + for (unsigned int a = 0; a < iNum; ++a, ++p) { + mGeneratedMesh->mFaces[pos].mIndices[a] = PLY::PropertyInstance::ConvertTo<unsigned int>(*p, eType); + } + } + + // parse the material index + // cannot be handled without processing the whole file first + /*if (0xFFFFFFFF != iMaterialIndex) + { + mGeneratedMesh->mFaces[pos]. = PLY::PropertyInstance::ConvertTo<unsigned int>( + GetProperty(instElement->alProperties, iMaterialIndex).avList.front(), eType2); + }*/ + + if (0xFFFFFFFF != iTextureCoord) { + const unsigned int iNum = (unsigned int)GetProperty(instElement->alProperties, iTextureCoord).avList.size(); + + //should be 6 coords + std::vector<PLY::PropertyInstance::ValueUnion>::const_iterator p = + GetProperty(instElement->alProperties, iTextureCoord).avList.begin(); + + if ((iNum / 3) == 2) // X Y coord + { + for (unsigned int a = 0; a < iNum; ++a, ++p) { + unsigned int vindex = mGeneratedMesh->mFaces[pos].mIndices[a / 2]; + if (vindex < mGeneratedMesh->mNumVertices) { + if (mGeneratedMesh->mTextureCoords[0] == nullptr) { + mGeneratedMesh->mNumUVComponents[0] = 2; + mGeneratedMesh->mTextureCoords[0] = new aiVector3D[mGeneratedMesh->mNumVertices]; + } + + if (a % 2 == 0) { + mGeneratedMesh->mTextureCoords[0][vindex].x = PLY::PropertyInstance::ConvertTo<ai_real>(*p, eType3); + } else { + mGeneratedMesh->mTextureCoords[0][vindex].y = PLY::PropertyInstance::ConvertTo<ai_real>(*p, eType3); + } + + mGeneratedMesh->mTextureCoords[0][vindex].z = 0; + } + } + } + } + } else { // triangle strips + // normally we have only one triangle strip instance where + // a value of -1 indicates a restart of the strip + bool flip = false; + const std::vector<PLY::PropertyInstance::ValueUnion> &quak = GetProperty(instElement->alProperties, iProperty).avList; + //pvOut->reserve(pvOut->size() + quak.size() + (quak.size()>>2u)); //Limits memory consumption + + int aiTable[2] = { -1, -1 }; + for (std::vector<PLY::PropertyInstance::ValueUnion>::const_iterator a = quak.begin(); a != quak.end(); ++a) { + const int p = PLY::PropertyInstance::ConvertTo<int>(*a, eType); + + if (-1 == p) { + // restart the strip ... + aiTable[0] = aiTable[1] = -1; + flip = false; + continue; + } + if (-1 == aiTable[0]) { + aiTable[0] = p; + continue; + } + if (-1 == aiTable[1]) { + aiTable[1] = p; + continue; + } + + if (mGeneratedMesh->mFaces == nullptr) { + mGeneratedMesh->mNumFaces = pcElement->NumOccur; + mGeneratedMesh->mFaces = new aiFace[mGeneratedMesh->mNumFaces]; + } + + mGeneratedMesh->mFaces[pos].mNumIndices = 3; + mGeneratedMesh->mFaces[pos].mIndices = new unsigned int[3]; + mGeneratedMesh->mFaces[pos].mIndices[0] = aiTable[0]; + mGeneratedMesh->mFaces[pos].mIndices[1] = aiTable[1]; + mGeneratedMesh->mFaces[pos].mIndices[2] = p; + + // every second pass swap the indices. + flip = !flip; + if (flip) { + std::swap(mGeneratedMesh->mFaces[pos].mIndices[0], mGeneratedMesh->mFaces[pos].mIndices[1]); + } + + aiTable[0] = aiTable[1]; + aiTable[1] = p; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Get a RGBA color in [0...1] range +void PLYImporter::GetMaterialColor(const std::vector<PLY::PropertyInstance> &avList, + unsigned int aiPositions[4], + PLY::EDataType aiTypes[4], + aiColor4D *clrOut) { + ai_assert(nullptr != clrOut); + + if (0xFFFFFFFF == aiPositions[0]) + clrOut->r = 0.0f; + else { + clrOut->r = NormalizeColorValue(GetProperty(avList, + aiPositions[0]) + .avList.front(), + aiTypes[0]); + } + + if (0xFFFFFFFF == aiPositions[1]) + clrOut->g = 0.0f; + else { + clrOut->g = NormalizeColorValue(GetProperty(avList, + aiPositions[1]) + .avList.front(), + aiTypes[1]); + } + + if (0xFFFFFFFF == aiPositions[2]) + clrOut->b = 0.0f; + else { + clrOut->b = NormalizeColorValue(GetProperty(avList, + aiPositions[2]) + .avList.front(), + aiTypes[2]); + } + + // assume 1.0 for the alpha channel ifit is not set + if (0xFFFFFFFF == aiPositions[3]) + clrOut->a = 1.0f; + else { + clrOut->a = NormalizeColorValue(GetProperty(avList, + aiPositions[3]) + .avList.front(), + aiTypes[3]); + } +} + +// ------------------------------------------------------------------------------------------------ +// Extract a material from the PLY DOM +void PLYImporter::LoadMaterial(std::vector<aiMaterial *> *pvOut, std::string &defaultTexture, const bool pointsOnly) { + ai_assert(nullptr != pvOut); + + // diffuse[4], specular[4], ambient[4] + // rgba order + unsigned int aaiPositions[3][4] = { + + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + }; + + PLY::EDataType aaiTypes[3][4] = { + { EDT_Char, EDT_Char, EDT_Char, EDT_Char }, + { EDT_Char, EDT_Char, EDT_Char, EDT_Char }, + { EDT_Char, EDT_Char, EDT_Char, EDT_Char } + }; + PLY::ElementInstanceList *pcList = nullptr; + + unsigned int iPhong = 0xFFFFFFFF; + PLY::EDataType ePhong = EDT_Char; + + unsigned int iOpacity = 0xFFFFFFFF; + PLY::EDataType eOpacity = EDT_Char; + + // search in the DOM for a vertex entry + unsigned int _i = 0; + for (std::vector<PLY::Element>::const_iterator i = this->pcDOM->alElements.begin(); + i != this->pcDOM->alElements.end(); ++i, ++_i) { + if (PLY::EEST_Material == (*i).eSemantic) { + pcList = &this->pcDOM->alElementData[_i]; + + // now check whether which coordinate sets are available + unsigned int _a = 0; + for (std::vector<PLY::Property>::const_iterator + a = (*i).alProperties.begin(); + a != (*i).alProperties.end(); ++a, ++_a) { + if ((*a).bIsList) continue; + + // pohng specularity ----------------------------------- + if (PLY::EST_PhongPower == (*a).Semantic) { + iPhong = _a; + ePhong = (*a).eType; + } + + // general opacity ----------------------------------- + if (PLY::EST_Opacity == (*a).Semantic) { + iOpacity = _a; + eOpacity = (*a).eType; + } + + // diffuse color channels ----------------------------------- + if (PLY::EST_DiffuseRed == (*a).Semantic) { + aaiPositions[0][0] = _a; + aaiTypes[0][0] = (*a).eType; + } else if (PLY::EST_DiffuseGreen == (*a).Semantic) { + aaiPositions[0][1] = _a; + aaiTypes[0][1] = (*a).eType; + } else if (PLY::EST_DiffuseBlue == (*a).Semantic) { + aaiPositions[0][2] = _a; + aaiTypes[0][2] = (*a).eType; + } else if (PLY::EST_DiffuseAlpha == (*a).Semantic) { + aaiPositions[0][3] = _a; + aaiTypes[0][3] = (*a).eType; + } + // specular color channels ----------------------------------- + else if (PLY::EST_SpecularRed == (*a).Semantic) { + aaiPositions[1][0] = _a; + aaiTypes[1][0] = (*a).eType; + } else if (PLY::EST_SpecularGreen == (*a).Semantic) { + aaiPositions[1][1] = _a; + aaiTypes[1][1] = (*a).eType; + } else if (PLY::EST_SpecularBlue == (*a).Semantic) { + aaiPositions[1][2] = _a; + aaiTypes[1][2] = (*a).eType; + } else if (PLY::EST_SpecularAlpha == (*a).Semantic) { + aaiPositions[1][3] = _a; + aaiTypes[1][3] = (*a).eType; + } + // ambient color channels ----------------------------------- + else if (PLY::EST_AmbientRed == (*a).Semantic) { + aaiPositions[2][0] = _a; + aaiTypes[2][0] = (*a).eType; + } else if (PLY::EST_AmbientGreen == (*a).Semantic) { + aaiPositions[2][1] = _a; + aaiTypes[2][1] = (*a).eType; + } else if (PLY::EST_AmbientBlue == (*a).Semantic) { + aaiPositions[2][2] = _a; + aaiTypes[2][2] = (*a).eType; + } else if (PLY::EST_AmbientAlpha == (*a).Semantic) { + aaiPositions[2][3] = _a; + aaiTypes[2][3] = (*a).eType; + } + } + break; + } else if (PLY::EEST_TextureFile == (*i).eSemantic) { + defaultTexture = (*i).szName; + } + } + // check whether we have a valid source for the material data + if (nullptr != pcList) { + for (std::vector<ElementInstance>::const_iterator i = pcList->alInstances.begin(); i != pcList->alInstances.end(); ++i) { + aiColor4D clrOut; + aiMaterial *pcHelper = new aiMaterial(); + + // build the diffuse material color + GetMaterialColor((*i).alProperties, aaiPositions[0], aaiTypes[0], &clrOut); + pcHelper->AddProperty<aiColor4D>(&clrOut, 1, AI_MATKEY_COLOR_DIFFUSE); + + // build the specular material color + GetMaterialColor((*i).alProperties, aaiPositions[1], aaiTypes[1], &clrOut); + pcHelper->AddProperty<aiColor4D>(&clrOut, 1, AI_MATKEY_COLOR_SPECULAR); + + // build the ambient material color + GetMaterialColor((*i).alProperties, aaiPositions[2], aaiTypes[2], &clrOut); + pcHelper->AddProperty<aiColor4D>(&clrOut, 1, AI_MATKEY_COLOR_AMBIENT); + + // handle phong power and shading mode + int iMode = (int)aiShadingMode_Gouraud; + if (0xFFFFFFFF != iPhong) { + ai_real fSpec = PLY::PropertyInstance::ConvertTo<ai_real>(GetProperty((*i).alProperties, iPhong).avList.front(), ePhong); + + // if shininess is 0 (and the pow() calculation would therefore always + // become 1, not depending on the angle), use gouraud lighting + if (fSpec) { + // scale this with 15 ... hopefully this is correct + fSpec *= 15; + pcHelper->AddProperty<ai_real>(&fSpec, 1, AI_MATKEY_SHININESS); + + iMode = (int)aiShadingMode_Phong; + } + } + pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + // handle opacity + if (0xFFFFFFFF != iOpacity) { + ai_real fOpacity = PLY::PropertyInstance::ConvertTo<ai_real>(GetProperty((*i).alProperties, iPhong).avList.front(), eOpacity); + pcHelper->AddProperty<ai_real>(&fOpacity, 1, AI_MATKEY_OPACITY); + } + + // The face order is absolutely undefined for PLY, so we have to + // use two-sided rendering to be sure it's ok. + const int two_sided = 1; + pcHelper->AddProperty(&two_sided, 1, AI_MATKEY_TWOSIDED); + + //default texture + if (!defaultTexture.empty()) { + const aiString name(defaultTexture.c_str()); + pcHelper->AddProperty(&name, _AI_MATKEY_TEXTURE_BASE, aiTextureType_DIFFUSE, 0); + } + + if (!pointsOnly) { + pcHelper->AddProperty(&two_sided, 1, AI_MATKEY_TWOSIDED); + } + + //set to wireframe, so when using this material info we can switch to points rendering + if (pointsOnly) { + const int wireframe = 1; + pcHelper->AddProperty(&wireframe, 1, AI_MATKEY_ENABLE_WIREFRAME); + } + + // add the newly created material instance to the list + pvOut->push_back(pcHelper); + } + } else { + // generate a default material + aiMaterial *pcHelper = new aiMaterial(); + + // fill in a default material + int iMode = (int)aiShadingMode_Gouraud; + pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + //generate white material most 3D engine just multiply ambient / diffuse color with actual ambient / light color + aiColor3D clr; + clr.b = clr.g = clr.r = 1.0f; + pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_SPECULAR); + + clr.b = clr.g = clr.r = 1.0f; + pcHelper->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_AMBIENT); + + // The face order is absolutely undefined for PLY, so we have to + // use two-sided rendering to be sure it's ok. + if (!pointsOnly) { + const int two_sided = 1; + pcHelper->AddProperty(&two_sided, 1, AI_MATKEY_TWOSIDED); + } + + //default texture + if (!defaultTexture.empty()) { + const aiString name(defaultTexture.c_str()); + pcHelper->AddProperty(&name, _AI_MATKEY_TEXTURE_BASE, aiTextureType_DIFFUSE, 0); + } + + //set to wireframe, so when using this material info we can switch to points rendering + if (pointsOnly) { + const int wireframe = 1; + pcHelper->AddProperty(&wireframe, 1, AI_MATKEY_ENABLE_WIREFRAME); + } + + pvOut->push_back(pcHelper); + } +} + +#endif // !! ASSIMP_BUILD_NO_PLY_IMPORTER diff --git a/libs/assimp/code/AssetLib/Ply/PlyLoader.h b/libs/assimp/code/AssetLib/Ply/PlyLoader.h new file mode 100644 index 0000000..e29da1d --- /dev/null +++ b/libs/assimp/code/AssetLib/Ply/PlyLoader.h @@ -0,0 +1,135 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 PLYLoader.h + * @brief Declaration of the .ply importer class. + */ +#pragma once +#ifndef AI_PLYLOADER_H_INCLUDED +#define AI_PLYLOADER_H_INCLUDED + +#include "PlyParser.h" +#include <assimp/BaseImporter.h> +#include <assimp/types.h> +#include <vector> + +struct aiNode; +struct aiMaterial; +struct aiMesh; + +namespace Assimp { + +using namespace PLY; + +// --------------------------------------------------------------------------- +/** Importer class to load the stanford PLY file format +*/ +class PLYImporter : public BaseImporter { +public: + PLYImporter(); + ~PLYImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + + // ------------------------------------------------------------------- + /** Extract a vertex from the DOM + */ + void LoadVertex(const PLY::Element *pcElement, const PLY::ElementInstance *instElement, unsigned int pos); + + // ------------------------------------------------------------------- + /** Extract a face from the DOM + */ + void LoadFace(const PLY::Element *pcElement, const PLY::ElementInstance *instElement, unsigned int pos); + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + + // ------------------------------------------------------------------- + /** Extract a material list from the DOM + */ + void LoadMaterial(std::vector<aiMaterial *> *pvOut, std::string &defaultTexture, const bool pointsOnly); + + // ------------------------------------------------------------------- + /** Static helper to parse a color from four single channels in + */ + static void GetMaterialColor( + const std::vector<PLY::PropertyInstance> &avList, + unsigned int aiPositions[4], + PLY::EDataType aiTypes[4], + aiColor4D *clrOut); + + // ------------------------------------------------------------------- + /** Static helper to parse a color channel value. The input value + * is normalized to 0-1. + */ + static ai_real NormalizeColorValue( + PLY::PropertyInstance::ValueUnion val, + PLY::EDataType eType); + + /** Buffer to hold the loaded file */ + unsigned char *mBuffer; + + /** Document object model representation extracted from the file */ + PLY::DOM *pcDOM; + + /** Mesh generated by loader */ + aiMesh *mGeneratedMesh; +}; + +} // end of namespace Assimp + +#endif // AI_3DSIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/Ply/PlyParser.cpp b/libs/assimp/code/AssetLib/Ply/PlyParser.cpp new file mode 100644 index 0000000..df93d5a --- /dev/null +++ b/libs/assimp/code/AssetLib/Ply/PlyParser.cpp @@ -0,0 +1,963 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the PLY parser class */ + +#ifndef ASSIMP_BUILD_NO_PLY_IMPORTER + +#include "PlyLoader.h" +#include <assimp/ByteSwapper.h> +#include <assimp/fast_atof.h> +#include <assimp/DefaultLogger.hpp> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +PLY::EDataType PLY::Property::ParseDataType(std::vector<char> &buffer) { + ai_assert(!buffer.empty()); + + PLY::EDataType eOut = PLY::EDT_INVALID; + + if (PLY::DOM::TokenMatch(buffer, "char", 4) || + PLY::DOM::TokenMatch(buffer, "int8", 4)) { + eOut = PLY::EDT_Char; + } else if (PLY::DOM::TokenMatch(buffer, "uchar", 5) || + PLY::DOM::TokenMatch(buffer, "uint8", 5)) { + eOut = PLY::EDT_UChar; + } else if (PLY::DOM::TokenMatch(buffer, "short", 5) || + PLY::DOM::TokenMatch(buffer, "int16", 5)) { + eOut = PLY::EDT_Short; + } else if (PLY::DOM::TokenMatch(buffer, "ushort", 6) || + PLY::DOM::TokenMatch(buffer, "uint16", 6)) { + eOut = PLY::EDT_UShort; + } else if (PLY::DOM::TokenMatch(buffer, "int32", 5) || PLY::DOM::TokenMatch(buffer, "int", 3)) { + eOut = PLY::EDT_Int; + } else if (PLY::DOM::TokenMatch(buffer, "uint32", 6) || PLY::DOM::TokenMatch(buffer, "uint", 4)) { + eOut = PLY::EDT_UInt; + } else if (PLY::DOM::TokenMatch(buffer, "float", 5) || PLY::DOM::TokenMatch(buffer, "float32", 7)) { + eOut = PLY::EDT_Float; + } else if (PLY::DOM::TokenMatch(buffer, "double64", 8) || PLY::DOM::TokenMatch(buffer, "double", 6) || + PLY::DOM::TokenMatch(buffer, "float64", 7)) { + eOut = PLY::EDT_Double; + } + if (PLY::EDT_INVALID == eOut) { + ASSIMP_LOG_INFO("Found unknown data type in PLY file. This is OK"); + } + + return eOut; +} + +// ------------------------------------------------------------------------------------------------ +PLY::ESemantic PLY::Property::ParseSemantic(std::vector<char> &buffer) { + ai_assert(!buffer.empty()); + + PLY::ESemantic eOut = PLY::EST_INVALID; + if (PLY::DOM::TokenMatch(buffer, "red", 3)) { + eOut = PLY::EST_Red; + } else if (PLY::DOM::TokenMatch(buffer, "green", 5)) { + eOut = PLY::EST_Green; + } else if (PLY::DOM::TokenMatch(buffer, "blue", 4)) { + eOut = PLY::EST_Blue; + } else if (PLY::DOM::TokenMatch(buffer, "alpha", 5)) { + eOut = PLY::EST_Alpha; + } else if (PLY::DOM::TokenMatch(buffer, "vertex_index", 12) || PLY::DOM::TokenMatch(buffer, "vertex_indices", 14)) { + eOut = PLY::EST_VertexIndex; + } else if (PLY::DOM::TokenMatch(buffer, "texcoord", 8)) // Manage uv coords on faces + { + eOut = PLY::EST_TextureCoordinates; + } else if (PLY::DOM::TokenMatch(buffer, "material_index", 14)) { + eOut = PLY::EST_MaterialIndex; + } else if (PLY::DOM::TokenMatch(buffer, "ambient_red", 11)) { + eOut = PLY::EST_AmbientRed; + } else if (PLY::DOM::TokenMatch(buffer, "ambient_green", 13)) { + eOut = PLY::EST_AmbientGreen; + } else if (PLY::DOM::TokenMatch(buffer, "ambient_blue", 12)) { + eOut = PLY::EST_AmbientBlue; + } else if (PLY::DOM::TokenMatch(buffer, "ambient_alpha", 13)) { + eOut = PLY::EST_AmbientAlpha; + } else if (PLY::DOM::TokenMatch(buffer, "diffuse_red", 11)) { + eOut = PLY::EST_DiffuseRed; + } else if (PLY::DOM::TokenMatch(buffer, "diffuse_green", 13)) { + eOut = PLY::EST_DiffuseGreen; + } else if (PLY::DOM::TokenMatch(buffer, "diffuse_blue", 12)) { + eOut = PLY::EST_DiffuseBlue; + } else if (PLY::DOM::TokenMatch(buffer, "diffuse_alpha", 13)) { + eOut = PLY::EST_DiffuseAlpha; + } else if (PLY::DOM::TokenMatch(buffer, "specular_red", 12)) { + eOut = PLY::EST_SpecularRed; + } else if (PLY::DOM::TokenMatch(buffer, "specular_green", 14)) { + eOut = PLY::EST_SpecularGreen; + } else if (PLY::DOM::TokenMatch(buffer, "specular_blue", 13)) { + eOut = PLY::EST_SpecularBlue; + } else if (PLY::DOM::TokenMatch(buffer, "specular_alpha", 14)) { + eOut = PLY::EST_SpecularAlpha; + } else if (PLY::DOM::TokenMatch(buffer, "opacity", 7)) { + eOut = PLY::EST_Opacity; + } else if (PLY::DOM::TokenMatch(buffer, "specular_power", 14)) { + eOut = PLY::EST_PhongPower; + } else if (PLY::DOM::TokenMatch(buffer, "r", 1)) { + eOut = PLY::EST_Red; + } else if (PLY::DOM::TokenMatch(buffer, "g", 1)) { + eOut = PLY::EST_Green; + } else if (PLY::DOM::TokenMatch(buffer, "b", 1)) { + eOut = PLY::EST_Blue; + } + + // NOTE: Blender3D exports texture coordinates as s,t tuples + else if (PLY::DOM::TokenMatch(buffer, "u", 1) || PLY::DOM::TokenMatch(buffer, "s", 1) || PLY::DOM::TokenMatch(buffer, "tx", 2) || PLY::DOM::TokenMatch(buffer, "texture_u", 9)) { + eOut = PLY::EST_UTextureCoord; + } else if (PLY::DOM::TokenMatch(buffer, "v", 1) || PLY::DOM::TokenMatch(buffer, "t", 1) || PLY::DOM::TokenMatch(buffer, "ty", 2) || PLY::DOM::TokenMatch(buffer, "texture_v", 9)) { + eOut = PLY::EST_VTextureCoord; + } else if (PLY::DOM::TokenMatch(buffer, "x", 1)) { + eOut = PLY::EST_XCoord; + } else if (PLY::DOM::TokenMatch(buffer, "y", 1)) { + eOut = PLY::EST_YCoord; + } else if (PLY::DOM::TokenMatch(buffer, "z", 1)) { + eOut = PLY::EST_ZCoord; + } else if (PLY::DOM::TokenMatch(buffer, "nx", 2)) { + eOut = PLY::EST_XNormal; + } else if (PLY::DOM::TokenMatch(buffer, "ny", 2)) { + eOut = PLY::EST_YNormal; + } else if (PLY::DOM::TokenMatch(buffer, "nz", 2)) { + eOut = PLY::EST_ZNormal; + } else { + ASSIMP_LOG_INFO("Found unknown property semantic in file. This is ok"); + PLY::DOM::SkipLine(buffer); + } + return eOut; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::Property::ParseProperty(std::vector<char> &buffer, PLY::Property *pOut) { + ai_assert(!buffer.empty()); + + // Forms supported: + // "property float x" + // "property list uchar int vertex_index" + + // skip leading spaces + if (!PLY::DOM::SkipSpaces(buffer)) { + return false; + } + + // skip the "property" string at the beginning + if (!PLY::DOM::TokenMatch(buffer, "property", 8)) { + // seems not to be a valid property entry + return false; + } + // get next word + if (!PLY::DOM::SkipSpaces(buffer)) { + return false; + } + if (PLY::DOM::TokenMatch(buffer, "list", 4)) { + pOut->bIsList = true; + + // seems to be a list. + if (EDT_INVALID == (pOut->eFirstType = PLY::Property::ParseDataType(buffer))) { + // unable to parse list size data type + PLY::DOM::SkipLine(buffer); + return false; + } + if (!PLY::DOM::SkipSpaces(buffer)) return false; + if (EDT_INVALID == (pOut->eType = PLY::Property::ParseDataType(buffer))) { + // unable to parse list data type + PLY::DOM::SkipLine(buffer); + return false; + } + } else { + if (EDT_INVALID == (pOut->eType = PLY::Property::ParseDataType(buffer))) { + // unable to parse data type. Skip the property + PLY::DOM::SkipLine(buffer); + return false; + } + } + + if (!PLY::DOM::SkipSpaces(buffer)) + return false; + + pOut->Semantic = PLY::Property::ParseSemantic(buffer); + + if (PLY::EST_INVALID == pOut->Semantic) { + ASSIMP_LOG_INFO("Found unknown semantic in PLY file. This is OK"); + std::string(&buffer[0], &buffer[0] + strlen(&buffer[0])); + } + + PLY::DOM::SkipSpacesAndLineEnd(buffer); + return true; +} + +// ------------------------------------------------------------------------------------------------ +PLY::EElementSemantic PLY::Element::ParseSemantic(std::vector<char> &buffer) { + ai_assert(!buffer.empty()); + + PLY::EElementSemantic eOut = PLY::EEST_INVALID; + if (PLY::DOM::TokenMatch(buffer, "vertex", 6)) { + eOut = PLY::EEST_Vertex; + } else if (PLY::DOM::TokenMatch(buffer, "face", 4)) { + eOut = PLY::EEST_Face; + } else if (PLY::DOM::TokenMatch(buffer, "tristrips", 9)) { + eOut = PLY::EEST_TriStrip; + } +#if 0 + // TODO: maybe implement this? + else if (PLY::DOM::TokenMatch(buffer,"range_grid",10)) + { + eOut = PLY::EEST_Face; + } +#endif + else if (PLY::DOM::TokenMatch(buffer, "edge", 4)) { + eOut = PLY::EEST_Edge; + } else if (PLY::DOM::TokenMatch(buffer, "material", 8)) { + eOut = PLY::EEST_Material; + } else if (PLY::DOM::TokenMatch(buffer, "TextureFile", 11)) { + eOut = PLY::EEST_TextureFile; + } + + return eOut; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::Element::ParseElement(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, PLY::Element *pOut) { + ai_assert(nullptr != pOut); + // Example format: "element vertex 8" + + // skip leading spaces + if (!PLY::DOM::SkipSpaces(buffer)) { + return false; + } + + // skip the "element" string at the beginning + if (!PLY::DOM::TokenMatch(buffer, "element", 7) && !PLY::DOM::TokenMatch(buffer, "comment", 7)) { + // seems not to be a valid property entry + return false; + } + // get next word + if (!PLY::DOM::SkipSpaces(buffer)) + return false; + + // parse the semantic of the element + pOut->eSemantic = PLY::Element::ParseSemantic(buffer); + if (PLY::EEST_INVALID == pOut->eSemantic) { + // if the exact semantic can't be determined, just store + // the original string identifier + pOut->szName = std::string(&buffer[0], &buffer[0] + strlen(&buffer[0])); + } + + if (!PLY::DOM::SkipSpaces(buffer)) + return false; + + if (PLY::EEST_TextureFile == pOut->eSemantic) { + char *endPos = &buffer[0] + (strlen(&buffer[0]) - 1); + pOut->szName = std::string(&buffer[0], endPos); + + // go to the next line + PLY::DOM::SkipSpacesAndLineEnd(buffer); + + return true; + } + + //parse the number of occurrences of this element + const char *pCur = (char *)&buffer[0]; + pOut->NumOccur = strtoul10(pCur, &pCur); + + // go to the next line + PLY::DOM::SkipSpacesAndLineEnd(buffer); + + // now parse all properties of the element + while (true) { + streamBuffer.getNextLine(buffer); + pCur = (char *)&buffer[0]; + + // skip all comments + PLY::DOM::SkipComments(buffer); + + PLY::Property prop; + if (!PLY::Property::ParseProperty(buffer, &prop)) + break; + + pOut->alProperties.push_back(prop); + } + + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::DOM::SkipSpaces(std::vector<char> &buffer) { + const char *pCur = buffer.empty() ? nullptr : (char *)&buffer[0]; + bool ret = false; + if (pCur) { + const char *szCur = pCur; + ret = Assimp::SkipSpaces(pCur, &pCur); + + uintptr_t iDiff = (uintptr_t)pCur - (uintptr_t)szCur; + buffer.erase(buffer.begin(), buffer.begin() + iDiff); + return ret; + } + + return ret; +} + +bool PLY::DOM::SkipLine(std::vector<char> &buffer) { + const char *pCur = buffer.empty() ? nullptr : (char *)&buffer[0]; + bool ret = false; + if (pCur) { + const char *szCur = pCur; + ret = Assimp::SkipLine(pCur, &pCur); + + uintptr_t iDiff = (uintptr_t)pCur - (uintptr_t)szCur; + buffer.erase(buffer.begin(), buffer.begin() + iDiff); + return ret; + } + + return ret; +} + +bool PLY::DOM::TokenMatch(std::vector<char> &buffer, const char *token, unsigned int len) { + const char *pCur = buffer.empty() ? nullptr : (char *)&buffer[0]; + bool ret = false; + if (pCur) { + const char *szCur = pCur; + ret = Assimp::TokenMatch(pCur, token, len); + + uintptr_t iDiff = (uintptr_t)pCur - (uintptr_t)szCur; + buffer.erase(buffer.begin(), buffer.begin() + iDiff); + return ret; + } + + return ret; +} + +bool PLY::DOM::SkipSpacesAndLineEnd(std::vector<char> &buffer) { + const char *pCur = buffer.empty() ? nullptr : (char *)&buffer[0]; + bool ret = false; + if (pCur) { + const char *szCur = pCur; + ret = Assimp::SkipSpacesAndLineEnd(pCur, &pCur); + + uintptr_t iDiff = (uintptr_t)pCur - (uintptr_t)szCur; + buffer.erase(buffer.begin(), buffer.begin() + iDiff); + return ret; + } + + return ret; +} + +bool PLY::DOM::SkipComments(std::vector<char> &buffer) { + ai_assert(!buffer.empty()); + + std::vector<char> nbuffer = buffer; + // skip spaces + if (!SkipSpaces(nbuffer)) { + return false; + } + + if (TokenMatch(nbuffer, "comment", 7)) { + if (!SkipSpaces(nbuffer)) + SkipLine(nbuffer); + + if (!TokenMatch(nbuffer, "TextureFile", 11)) { + SkipLine(nbuffer); + buffer = nbuffer; + return true; + } + + return true; + } + + return false; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::DOM::ParseHeader(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, bool isBinary) { + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseHeader() begin"); + + // parse all elements + while (!buffer.empty()) { + // skip all comments + PLY::DOM::SkipComments(buffer); + + PLY::Element out; + if (PLY::Element::ParseElement(streamBuffer, buffer, &out)) { + // add the element to the list of elements + alElements.push_back(out); + } else if (TokenMatch(buffer, "end_header", 10)) { //checks for /n ending, if it doesn't end with /r/n + // we have reached the end of the header + break; + } else { + // ignore unknown header elements + streamBuffer.getNextLine(buffer); + } + } + + if (!isBinary) // it would occur an error, if binary data start with values as space or line end. + SkipSpacesAndLineEnd(buffer); + + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseHeader() succeeded"); + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::DOM::ParseElementInstanceLists(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, PLYImporter *loader) { + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseElementInstanceLists() begin"); + alElementData.resize(alElements.size()); + + std::vector<PLY::Element>::const_iterator i = alElements.begin(); + std::vector<PLY::ElementInstanceList>::iterator a = alElementData.begin(); + + // parse all element instances + //construct vertices and faces + for (; i != alElements.end(); ++i, ++a) { + if ((*i).eSemantic == EEST_Vertex || (*i).eSemantic == EEST_Face || (*i).eSemantic == EEST_TriStrip) { + PLY::ElementInstanceList::ParseInstanceList(streamBuffer, buffer, &(*i), nullptr, loader); + } else { + (*a).alInstances.resize((*i).NumOccur); + PLY::ElementInstanceList::ParseInstanceList(streamBuffer, buffer, &(*i), &(*a), nullptr); + } + } + + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseElementInstanceLists() succeeded"); + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::DOM::ParseElementInstanceListsBinary(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, + const char *&pCur, + unsigned int &bufferSize, + PLYImporter *loader, + bool p_bBE) { + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseElementInstanceListsBinary() begin"); + alElementData.resize(alElements.size()); + + std::vector<PLY::Element>::const_iterator i = alElements.begin(); + std::vector<PLY::ElementInstanceList>::iterator a = alElementData.begin(); + + // parse all element instances + for (; i != alElements.end(); ++i, ++a) { + if ((*i).eSemantic == EEST_Vertex || (*i).eSemantic == EEST_Face || (*i).eSemantic == EEST_TriStrip) { + PLY::ElementInstanceList::ParseInstanceListBinary(streamBuffer, buffer, pCur, bufferSize, &(*i), nullptr, loader, p_bBE); + } else { + (*a).alInstances.resize((*i).NumOccur); + PLY::ElementInstanceList::ParseInstanceListBinary(streamBuffer, buffer, pCur, bufferSize, &(*i), &(*a), nullptr, p_bBE); + } + } + + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseElementInstanceListsBinary() succeeded"); + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::DOM::ParseInstanceBinary(IOStreamBuffer<char> &streamBuffer, DOM *p_pcOut, PLYImporter *loader, bool p_bBE) { + ai_assert(nullptr != p_pcOut); + ai_assert(nullptr != loader); + + std::vector<char> buffer; + streamBuffer.getNextLine(buffer); + + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseInstanceBinary() begin"); + + if (!p_pcOut->ParseHeader(streamBuffer, buffer, true)) { + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseInstanceBinary() failure"); + return false; + } + + streamBuffer.getNextBlock(buffer); + + // remove first char if it's /n in case of file with /r/n + if (((char *)&buffer[0])[0] == '\n') + buffer.erase(buffer.begin(), buffer.begin() + 1); + + unsigned int bufferSize = static_cast<unsigned int>(buffer.size()); + const char *pCur = (char *)&buffer[0]; + if (!p_pcOut->ParseElementInstanceListsBinary(streamBuffer, buffer, pCur, bufferSize, loader, p_bBE)) { + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseInstanceBinary() failure"); + return false; + } + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseInstanceBinary() succeeded"); + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::DOM::ParseInstance(IOStreamBuffer<char> &streamBuffer, DOM *p_pcOut, PLYImporter *loader) { + ai_assert(nullptr != p_pcOut); + ai_assert(nullptr != loader); + + std::vector<char> buffer; + streamBuffer.getNextLine(buffer); + + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseInstance() begin"); + + if (!p_pcOut->ParseHeader(streamBuffer, buffer, false)) { + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseInstance() failure"); + return false; + } + + //get next line after header + streamBuffer.getNextLine(buffer); + if (!p_pcOut->ParseElementInstanceLists(streamBuffer, buffer, loader)) { + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseInstance() failure"); + return false; + } + ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseInstance() succeeded"); + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::ElementInstanceList::ParseInstanceList( + IOStreamBuffer<char> &streamBuffer, + std::vector<char> &buffer, + const PLY::Element *pcElement, + PLY::ElementInstanceList *p_pcOut, + PLYImporter *loader) { + ai_assert(nullptr != pcElement); + + // parse all elements + if (EEST_INVALID == pcElement->eSemantic || pcElement->alProperties.empty()) { + // if the element has an unknown semantic we can skip all lines + // However, there could be comments + for (unsigned int i = 0; i < pcElement->NumOccur; ++i) { + PLY::DOM::SkipComments(buffer); + PLY::DOM::SkipLine(buffer); + streamBuffer.getNextLine(buffer); + } + } else { + const char *pCur = (const char *)&buffer[0]; + // be sure to have enough storage + for (unsigned int i = 0; i < pcElement->NumOccur; ++i) { + if (p_pcOut) + PLY::ElementInstance::ParseInstance(pCur, pcElement, &p_pcOut->alInstances[i]); + else { + ElementInstance elt; + PLY::ElementInstance::ParseInstance(pCur, pcElement, &elt); + + // Create vertex or face + if (pcElement->eSemantic == EEST_Vertex) { + //call loader instance from here + loader->LoadVertex(pcElement, &elt, i); + } else if (pcElement->eSemantic == EEST_Face) { + //call loader instance from here + loader->LoadFace(pcElement, &elt, i); + } else if (pcElement->eSemantic == EEST_TriStrip) { + //call loader instance from here + loader->LoadFace(pcElement, &elt, i); + } + } + + streamBuffer.getNextLine(buffer); + pCur = (buffer.empty()) ? nullptr : (const char *)&buffer[0]; + } + } + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::ElementInstanceList::ParseInstanceListBinary( + IOStreamBuffer<char> &streamBuffer, + std::vector<char> &buffer, + const char *&pCur, + unsigned int &bufferSize, + const PLY::Element *pcElement, + PLY::ElementInstanceList *p_pcOut, + PLYImporter *loader, + bool p_bBE /* = false */) { + ai_assert(nullptr != pcElement); + + // we can add special handling code for unknown element semantics since + // we can't skip it as a whole block (we don't know its exact size + // due to the fact that lists could be contained in the property list + // of the unknown element) + for (unsigned int i = 0; i < pcElement->NumOccur; ++i) { + if (p_pcOut) + PLY::ElementInstance::ParseInstanceBinary(streamBuffer, buffer, pCur, bufferSize, pcElement, &p_pcOut->alInstances[i], p_bBE); + else { + ElementInstance elt; + PLY::ElementInstance::ParseInstanceBinary(streamBuffer, buffer, pCur, bufferSize, pcElement, &elt, p_bBE); + + // Create vertex or face + if (pcElement->eSemantic == EEST_Vertex) { + //call loader instance from here + loader->LoadVertex(pcElement, &elt, i); + } else if (pcElement->eSemantic == EEST_Face) { + //call loader instance from here + loader->LoadFace(pcElement, &elt, i); + } else if (pcElement->eSemantic == EEST_TriStrip) { + //call loader instance from here + loader->LoadFace(pcElement, &elt, i); + } + } + } + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::ElementInstance::ParseInstance(const char *&pCur, + const PLY::Element *pcElement, + PLY::ElementInstance *p_pcOut) { + ai_assert(nullptr != pcElement); + ai_assert(nullptr != p_pcOut); + + // allocate enough storage + p_pcOut->alProperties.resize(pcElement->alProperties.size()); + + std::vector<PLY::PropertyInstance>::iterator i = p_pcOut->alProperties.begin(); + std::vector<PLY::Property>::const_iterator a = pcElement->alProperties.begin(); + for (; i != p_pcOut->alProperties.end(); ++i, ++a) { + if (!(PLY::PropertyInstance::ParseInstance(pCur, &(*a), &(*i)))) { + ASSIMP_LOG_WARN("Unable to parse property instance. " + "Skipping this element instance"); + + PLY::PropertyInstance::ValueUnion v = PLY::PropertyInstance::DefaultValue((*a).eType); + (*i).avList.push_back(v); + } + } + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::ElementInstance::ParseInstanceBinary( + IOStreamBuffer<char> &streamBuffer, + std::vector<char> &buffer, + const char *&pCur, + unsigned int &bufferSize, + const PLY::Element *pcElement, + PLY::ElementInstance *p_pcOut, + bool p_bBE /* = false */) { + ai_assert(nullptr != pcElement); + ai_assert(nullptr != p_pcOut); + + // allocate enough storage + p_pcOut->alProperties.resize(pcElement->alProperties.size()); + + std::vector<PLY::PropertyInstance>::iterator i = p_pcOut->alProperties.begin(); + std::vector<PLY::Property>::const_iterator a = pcElement->alProperties.begin(); + for (; i != p_pcOut->alProperties.end(); ++i, ++a) { + if (!(PLY::PropertyInstance::ParseInstanceBinary(streamBuffer, buffer, pCur, bufferSize, &(*a), &(*i), p_bBE))) { + ASSIMP_LOG_WARN("Unable to parse binary property instance. " + "Skipping this element instance"); + + (*i).avList.push_back(PLY::PropertyInstance::DefaultValue((*a).eType)); + } + } + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::PropertyInstance::ParseInstance(const char *&pCur, + const PLY::Property *prop, PLY::PropertyInstance *p_pcOut) { + ai_assert(nullptr != prop); + ai_assert(nullptr != p_pcOut); + + // skip spaces at the beginning + if (!SkipSpaces(&pCur)) { + return false; + } + + if (prop->bIsList) { + // parse the number of elements in the list + PLY::PropertyInstance::ValueUnion v; + PLY::PropertyInstance::ParseValue(pCur, prop->eFirstType, &v); + + // convert to unsigned int + unsigned int iNum = PLY::PropertyInstance::ConvertTo<unsigned int>(v, prop->eFirstType); + + // parse all list elements + p_pcOut->avList.resize(iNum); + for (unsigned int i = 0; i < iNum; ++i) { + if (!SkipSpaces(&pCur)) + return false; + + PLY::PropertyInstance::ParseValue(pCur, prop->eType, &p_pcOut->avList[i]); + } + } else { + // parse the property + PLY::PropertyInstance::ValueUnion v; + + PLY::PropertyInstance::ParseValue(pCur, prop->eType, &v); + p_pcOut->avList.push_back(v); + } + SkipSpacesAndLineEnd(&pCur); + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::PropertyInstance::ParseInstanceBinary(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, + const char *&pCur, + unsigned int &bufferSize, + const PLY::Property *prop, + PLY::PropertyInstance *p_pcOut, + bool p_bBE) { + ai_assert(nullptr != prop); + ai_assert(nullptr != p_pcOut); + + // parse all elements + if (prop->bIsList) { + // parse the number of elements in the list + PLY::PropertyInstance::ValueUnion v; + PLY::PropertyInstance::ParseValueBinary(streamBuffer, buffer, pCur, bufferSize, prop->eFirstType, &v, p_bBE); + + // convert to unsigned int + unsigned int iNum = PLY::PropertyInstance::ConvertTo<unsigned int>(v, prop->eFirstType); + + // parse all list elements + p_pcOut->avList.resize(iNum); + for (unsigned int i = 0; i < iNum; ++i) { + PLY::PropertyInstance::ParseValueBinary(streamBuffer, buffer, pCur, bufferSize, prop->eType, &p_pcOut->avList[i], p_bBE); + } + } else { + // parse the property + PLY::PropertyInstance::ValueUnion v; + PLY::PropertyInstance::ParseValueBinary(streamBuffer, buffer, pCur, bufferSize, prop->eType, &v, p_bBE); + p_pcOut->avList.push_back(v); + } + return true; +} + +// ------------------------------------------------------------------------------------------------ +PLY::PropertyInstance::ValueUnion PLY::PropertyInstance::DefaultValue(PLY::EDataType eType) { + PLY::PropertyInstance::ValueUnion out; + + switch (eType) { + case EDT_Float: + out.fFloat = 0.f; + return out; + + case EDT_Double: + out.fDouble = 0.; + return out; + + default:; + }; + out.iUInt = 0; + return out; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::PropertyInstance::ParseValue(const char *&pCur, + PLY::EDataType eType, + PLY::PropertyInstance::ValueUnion *out) { + ai_assert(nullptr != pCur); + ai_assert(nullptr != out); + + //calc element size + bool ret = true; + switch (eType) { + case EDT_UInt: + case EDT_UShort: + case EDT_UChar: + + out->iUInt = (uint32_t)strtoul10(pCur, &pCur); + break; + + case EDT_Int: + case EDT_Short: + case EDT_Char: + + out->iInt = (int32_t)strtol10(pCur, &pCur); + break; + + case EDT_Float: + // technically this should cast to float, but people tend to use float descriptors for double data + // this is the best way to not risk losing precision on import and it doesn't hurt to do this + ai_real f; + pCur = fast_atoreal_move<ai_real>(pCur, f); + out->fFloat = (ai_real)f; + break; + + case EDT_Double: + double d; + pCur = fast_atoreal_move<double>(pCur, d); + out->fDouble = (double)d; + break; + + case EDT_INVALID: + default: + ret = false; + break; + } + + return ret; +} + +// ------------------------------------------------------------------------------------------------ +bool PLY::PropertyInstance::ParseValueBinary(IOStreamBuffer<char> &streamBuffer, + std::vector<char> &buffer, + const char *&pCur, + unsigned int &bufferSize, + PLY::EDataType eType, + PLY::PropertyInstance::ValueUnion *out, + bool p_bBE) { + ai_assert(nullptr != out); + + //calc element size + unsigned int lsize = 0; + switch (eType) { + case EDT_Char: + case EDT_UChar: + lsize = 1; + break; + + case EDT_UShort: + case EDT_Short: + lsize = 2; + break; + + case EDT_UInt: + case EDT_Int: + case EDT_Float: + lsize = 4; + break; + + case EDT_Double: + lsize = 8; + break; + + case EDT_INVALID: + default: + break; + } + + //read the next file block if needed + if (bufferSize < lsize) { + std::vector<char> nbuffer; + if (streamBuffer.getNextBlock(nbuffer)) { + //concat buffer contents + buffer = std::vector<char>(buffer.end() - bufferSize, buffer.end()); + buffer.insert(buffer.end(), nbuffer.begin(), nbuffer.end()); + nbuffer.clear(); + bufferSize = static_cast<unsigned int>(buffer.size()); + pCur = (char *)&buffer[0]; + } else { + throw DeadlyImportError("Invalid .ply file: File corrupted"); + } + } + + bool ret = true; + switch (eType) { + case EDT_UInt: { + uint32_t t; + memcpy(&t, pCur, sizeof(uint32_t)); + pCur += sizeof(uint32_t); + + // Swap endianness + if (p_bBE) ByteSwap::Swap(&t); + out->iUInt = t; + break; + } + + case EDT_UShort: { + uint16_t t; + memcpy(&t, pCur, sizeof(uint16_t)); + pCur += sizeof(uint16_t); + + // Swap endianness + if (p_bBE) ByteSwap::Swap(&t); + out->iUInt = t; + break; + } + + case EDT_UChar: { + uint8_t t; + memcpy(&t, pCur, sizeof(uint8_t)); + pCur += sizeof(uint8_t); + out->iUInt = t; + break; + } + + case EDT_Int: { + int32_t t; + memcpy(&t, pCur, sizeof(int32_t)); + pCur += sizeof(int32_t); + + // Swap endianness + if (p_bBE) ByteSwap::Swap(&t); + out->iInt = t; + break; + } + + case EDT_Short: { + int16_t t; + memcpy(&t, pCur, sizeof(int16_t)); + pCur += sizeof(int16_t); + + // Swap endianness + if (p_bBE) ByteSwap::Swap(&t); + out->iInt = t; + break; + } + + case EDT_Char: { + int8_t t; + memcpy(&t, pCur, sizeof(int8_t)); + pCur += sizeof(int8_t); + out->iInt = t; + break; + } + + case EDT_Float: { + float t; + memcpy(&t, pCur, sizeof(float)); + pCur += sizeof(float); + + // Swap endianness + if (p_bBE) ByteSwap::Swap(&t); + out->fFloat = t; + break; + } + case EDT_Double: { + double t; + memcpy(&t, pCur, sizeof(double)); + pCur += sizeof(double); + + // Swap endianness + if (p_bBE) ByteSwap::Swap(&t); + out->fDouble = t; + break; + } + default: + ret = false; + } + + bufferSize -= lsize; + + return ret; +} + +#endif // !! ASSIMP_BUILD_NO_PLY_IMPORTER diff --git a/libs/assimp/code/AssetLib/Ply/PlyParser.h b/libs/assimp/code/AssetLib/Ply/PlyParser.h new file mode 100644 index 0000000..8634929 --- /dev/null +++ b/libs/assimp/code/AssetLib/Ply/PlyParser.h @@ -0,0 +1,488 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines the helper data structures for importing PLY files */ +#pragma once +#ifndef AI_PLYFILEHELPER_H_INC +#define AI_PLYFILEHELPER_H_INC + +#include <assimp/ParsingUtils.h> +#include <assimp/IOStreamBuffer.h> +#include <vector> + +namespace Assimp { + +//pre-declaration +class PLYImporter; + +// http://local.wasp.uwa.edu.au/~pbourke/dataformats/ply/ +// http://w3.impa.br/~lvelho/outgoing/sossai/old/ViHAP_D4.4.2_PLY_format_v1.1.pdf +// http://www.okino.com/conv/exp_ply.htm +namespace PLY { + +// --------------------------------------------------------------------------------- +/* +name type number of bytes +--------------------------------------- +char character 1 +uchar unsigned character 1 +short short integer 2 +ushort unsigned short integer 2 +int integer 4 +uint unsigned integer 4 +float single-precision float 4 +double double-precision float 8 + +int8 +int16 +uint8 ... forms are also used +*/ +enum EDataType { + EDT_Char = 0x0u, + EDT_UChar, + EDT_Short, + EDT_UShort, + EDT_Int, + EDT_UInt, + EDT_Float, + EDT_Double, + + // Marks invalid entries + EDT_INVALID +}; + +// --------------------------------------------------------------------------------- +/** \brief Specifies semantics for PLY element properties + * + * Semantics define the usage of a property, e.g. x coordinate +*/ +enum ESemantic { + //! vertex position x coordinate + EST_XCoord = 0x0u, + //! vertex position x coordinate + EST_YCoord, + //! vertex position x coordinate + EST_ZCoord, + + //! vertex normal x coordinate + EST_XNormal, + //! vertex normal y coordinate + EST_YNormal, + //! vertex normal z coordinate + EST_ZNormal, + + //! u texture coordinate + EST_UTextureCoord, + //! v texture coordinate + EST_VTextureCoord, + + //! vertex colors, red channel + EST_Red, + //! vertex colors, green channel + EST_Green, + //! vertex colors, blue channel + EST_Blue, + //! vertex colors, alpha channel + EST_Alpha, + + //! vertex index list + EST_VertexIndex, + + //! texture index + EST_TextureIndex, + + //! texture coordinates (stored as element of a face) + EST_TextureCoordinates, + + //! material index + EST_MaterialIndex, + + //! ambient color, red channel + EST_AmbientRed, + //! ambient color, green channel + EST_AmbientGreen, + //! ambient color, blue channel + EST_AmbientBlue, + //! ambient color, alpha channel + EST_AmbientAlpha, + + //! diffuse color, red channel + EST_DiffuseRed, + //! diffuse color, green channel + EST_DiffuseGreen, + //! diffuse color, blue channel + EST_DiffuseBlue, + //! diffuse color, alpha channel + EST_DiffuseAlpha, + + //! specular color, red channel + EST_SpecularRed, + //! specular color, green channel + EST_SpecularGreen, + //! specular color, blue channel + EST_SpecularBlue, + //! specular color, alpha channel + EST_SpecularAlpha, + + //! specular power for phong shading + EST_PhongPower, + + //! opacity between 0 and 1 + EST_Opacity, + + //! Marks invalid entries + EST_INVALID +}; + +// --------------------------------------------------------------------------------- +/** \brief Specifies semantics for PLY elements + * + * Semantics define the usage of an element, e.g. vertex or material +*/ +enum EElementSemantic { + //! The element is a vertex + EEST_Vertex = 0x0u, + + //! The element is a face description (index table) + EEST_Face, + + //! The element is a triangle-strip description (index table) + EEST_TriStrip, + + //! The element is an edge description (ignored) + EEST_Edge, + + //! The element is a material description + EEST_Material, + + //! texture path + EEST_TextureFile, + + //! Marks invalid entries + EEST_INVALID +}; + +// --------------------------------------------------------------------------------- +/** \brief Helper class for a property in a PLY file. + * + * This can e.g. be a part of the vertex declaration + */ +class Property { +public: + //! Default constructor + Property() AI_NO_EXCEPT + : eType (EDT_Int) + , Semantic() + , bIsList(false) + , eFirstType(EDT_UChar) { + // empty + } + + //! Data type of the property + EDataType eType; + + //! Semantical meaning of the property + ESemantic Semantic; + + //! Of the semantic of the property could not be parsed: + //! Contains the semantic specified in the file + std::string szName; + + //! Specifies whether the data type is a list where + //! the first element specifies the size of the list + bool bIsList; + EDataType eFirstType; + + // ------------------------------------------------------------------- + //! Parse a property from a string. The end of the + //! string is either '\n', '\r' or '\0'. Return value is false + //! if the input string is NOT a valid property (E.g. does + //! not start with the "property" keyword) + static bool ParseProperty(std::vector<char> &buffer, Property* pOut); + + // ------------------------------------------------------------------- + //! Parse a data type from a string + static EDataType ParseDataType(std::vector<char> &buffer); + + // ------------------------------------------------------------------- + //! Parse a semantic from a string + static ESemantic ParseSemantic(std::vector<char> &buffer); +}; + +// --------------------------------------------------------------------------------- +/** \brief Helper class for an element in a PLY file. + * + * This can e.g. be the vertex declaration. Elements contain a + * well-defined number of properties. + */ +class Element { +public: + //! Default constructor + Element() AI_NO_EXCEPT + : eSemantic (EEST_INVALID) + , NumOccur(0) { + // empty + } + + //! List of properties assigned to the element + //! std::vector to support operator[] + std::vector<Property> alProperties; + + //! Semantic of the element + EElementSemantic eSemantic; + + //! Of the semantic of the element could not be parsed: + //! Contains the semantic specified in the file + std::string szName; + + //! How many times will the element occur? + unsigned int NumOccur; + + + // ------------------------------------------------------------------- + //! Parse an element from a string. + //! The function will parse all properties contained in the + //! element, too. + static bool ParseElement(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, Element* pOut); + + // ------------------------------------------------------------------- + //! Parse a semantic from a string + static EElementSemantic ParseSemantic(std::vector<char> &buffer); +}; + +// --------------------------------------------------------------------------------- +/** \brief Instance of a property in a PLY file + */ +class PropertyInstance +{ +public: + + //! Default constructor + PropertyInstance() AI_NO_EXCEPT { + // empty + } + + union ValueUnion + { + + //! uInt32 representation of the property. All + // uint types are automatically converted to uint32 + uint32_t iUInt; + + //! Int32 representation of the property. All + // int types are automatically converted to int32 + int32_t iInt; + + //! Float32 representation of the property + float fFloat; + + //! Float64 representation of the property + double fDouble; + + }; + + // ------------------------------------------------------------------- + //! List of all values parsed. Contains only one value + // for non-list properties + std::vector<ValueUnion> avList; + + // ------------------------------------------------------------------- + //! Parse a property instance + static bool ParseInstance(const char* &pCur, + const Property* prop, PropertyInstance* p_pcOut); + + // ------------------------------------------------------------------- + //! Parse a property instance in binary format + static bool ParseInstanceBinary(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, + const char* &pCur, unsigned int &bufferSize, const Property* prop, PropertyInstance* p_pcOut, bool p_bBE); + + // ------------------------------------------------------------------- + //! Get the default value for a given data type + static ValueUnion DefaultValue(EDataType eType); + + // ------------------------------------------------------------------- + //! Parse a value + static bool ParseValue(const char* &pCur, EDataType eType, ValueUnion* out); + + // ------------------------------------------------------------------- + //! Parse a binary value + static bool ParseValueBinary(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, + const char* &pCur, unsigned int &bufferSize, EDataType eType, ValueUnion* out, bool p_bBE); + + // ------------------------------------------------------------------- + //! Convert a property value to a given type TYPE + template <typename TYPE> + static TYPE ConvertTo(ValueUnion v, EDataType eType); +}; + +// --------------------------------------------------------------------------------- +/** \brief Class for an element instance in a PLY file + */ +class ElementInstance { +public: + //! Default constructor + ElementInstance() AI_NO_EXCEPT + : alProperties() { + // empty + } + + //! List of all parsed properties + std::vector< PropertyInstance > alProperties; + + // ------------------------------------------------------------------- + //! Parse an element instance + static bool ParseInstance(const char* &pCur, + const Element* pcElement, ElementInstance* p_pcOut); + + // ------------------------------------------------------------------- + //! Parse a binary element instance + static bool ParseInstanceBinary(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, + const char* &pCur, unsigned int &bufferSize, const Element* pcElement, ElementInstance* p_pcOut, bool p_bBE); +}; + +// --------------------------------------------------------------------------------- +/** \brief Class for an element instance list in a PLY file + */ +class ElementInstanceList +{ +public: + + //! Default constructor + ElementInstanceList() AI_NO_EXCEPT + : alInstances() { + // empty + } + + //! List of all element instances + std::vector< ElementInstance > alInstances; + + // ------------------------------------------------------------------- + //! Parse an element instance list + static bool ParseInstanceList(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, + const Element* pcElement, ElementInstanceList* p_pcOut, PLYImporter* loader); + + // ------------------------------------------------------------------- + //! Parse a binary element instance list + static bool ParseInstanceListBinary(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, + const char* &pCur, unsigned int &bufferSize, const Element* pcElement, ElementInstanceList* p_pcOut, PLYImporter* loader, bool p_bBE); +}; +// --------------------------------------------------------------------------------- +/** \brief Class to represent the document object model of an ASCII or binary + * (both little and big-endian) PLY file + */ +class DOM +{ +public: + + //! Default constructor + DOM() AI_NO_EXCEPT + : alElements() + , alElementData() { + + } + + + //! Contains all elements of the file format + std::vector<Element> alElements; + //! Contains the real data of each element's instance list + std::vector<ElementInstanceList> alElementData; + + //! Parse the DOM for a PLY file. The input string is assumed + //! to be terminated with zero + static bool ParseInstance(IOStreamBuffer<char> &streamBuffer, DOM* p_pcOut, PLYImporter* loader); + static bool ParseInstanceBinary(IOStreamBuffer<char> &streamBuffer, DOM* p_pcOut, PLYImporter* loader, bool p_bBE); + + //! Skip all comment lines after this + static bool SkipComments(std::vector<char> &buffer); + + static bool SkipSpaces(std::vector<char> &buffer); + + static bool SkipLine(std::vector<char> &buffer); + + static bool TokenMatch(std::vector<char> &buffer, const char* token, unsigned int len); + + static bool SkipSpacesAndLineEnd(std::vector<char> &buffer); + +private: + + // ------------------------------------------------------------------- + //! Handle the file header and read all element descriptions + bool ParseHeader(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, bool p_bBE); + + // ------------------------------------------------------------------- + //! Read in all element instance lists + bool ParseElementInstanceLists(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, PLYImporter* loader); + + // ------------------------------------------------------------------- + //! Read in all element instance lists for a binary file format + bool ParseElementInstanceListsBinary(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, const char* &pCur, unsigned int &bufferSize, PLYImporter* loader, bool p_bBE); +}; + +// --------------------------------------------------------------------------------- +template <typename TYPE> +inline TYPE PLY::PropertyInstance::ConvertTo( + PLY::PropertyInstance::ValueUnion v, PLY::EDataType eType) +{ + switch (eType) + { + case EDT_Float: + return (TYPE)v.fFloat; + case EDT_Double: + return (TYPE)v.fDouble; + + case EDT_UInt: + case EDT_UShort: + case EDT_UChar: + return (TYPE)v.iUInt; + + case EDT_Int: + case EDT_Short: + case EDT_Char: + return (TYPE)v.iInt; + default: ; + }; + return (TYPE)0; +} + +} // Namespace PLY +} // Namespace AssImp + +#endif // !! include guard diff --git a/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileData.h b/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileData.h new file mode 100644 index 0000000..8ccee0b --- /dev/null +++ b/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileData.h @@ -0,0 +1,214 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_Q3BSPFILEDATA_H_INC +#define ASSIMP_Q3BSPFILEDATA_H_INC + +#include <vector> +#include <string.h> +#include <string> + +namespace Assimp { +namespace Q3BSP { + +static const unsigned int CE_BSP_LIGHTMAPWIDTH = 128; +static const unsigned int CE_BSP_LIGHTMAPHEIGHT = 128; + +static const unsigned int CE_BSP_LIGHTMAPSIZE = 128*128*3; ///< = 128( width ) * 128 ( height ) * 3 ( channels / RGB ). +static const int VERION_Q3LEVEL = 46; ///< Supported version. + +/// Geometric type enumeration +enum Q3BSPGeoType { + Polygon = 1, + Patch, + TriangleMesh, + Billboard +}; + +/// Integer vector. +struct ceVec3i { + int x, y, z; + ceVec3i(): x( 0 ), y( 0 ), z( 0 ) { /* empty */ } + ceVec3i( int iX, int iY=0, int iZ=0) : x( iX ), y( iY ), z( iZ ) { /* empty */ } +}; + +/// the file header +struct sQ3BSPHeader { + char strID[ 4 ]; ///< Should be "IBSP" + int iVersion; ///< 46 for standard levels +}; + +/// Describes an entry. +struct sQ3BSPLump { + int iOffset; ///< Offset from start pointer of file + int iSize; ///< Size of part +}; + +struct vec2f { + float x,y; +}; + +struct vec3f { + float x, y, z; +}; + +/// Vertex of a Q3 level +struct sQ3BSPVertex { + vec3f vPosition; ///< Position of vertex + vec2f vTexCoord; ///< (u,v) Texturecoordinate of detailtexture + vec2f vLightmap; ///< (u,v) Texturecoordinate of lightmap + vec3f vNormal; ///< vertex normal + unsigned char bColor[ 4 ]; ///< Color in RGBA +}; + +/// A face in bsp format info +struct sQ3BSPFace { + int iTextureID; ///< Index in texture array + int iEffect; ///< Index in effect array (-1 = no effect) + int iType; ///< 1=Polygon, 2=Patch, 3=Mesh, 4=Billboard + int iVertexIndex; ///< Start index of polygon + int iNumOfVerts; ///< Number of vertices + int iFaceVertexIndex; ///< Index of first mesh vertex + int iNumOfFaceVerts; ///< number of mesh vertices + int iLightmapID; ///< Index to the light-map array + int iLMapCorner[ 2 ]; ///< edge of the light-map in texture + int iLMapSize[ 2 ]; ///< Size of the light-map stored on the texture + vec3f vLMapPos; ///< 3D origin of the light-map + vec3f vLMapVecs[ 2 ]; ///< 3D-s-t-vectors + vec3f vNormal; ///< Polygon normals + int patchWidth, patchHeight; ///< bezier patch +}; + +/// A quake3 texture name. +struct sQ3BSPTexture { + char strName[ 64 ]; ///< Name of the texture without extension + int iFlags; ///< Not used + int iContents; ///< Not used +}; + +/// A light-map of the level, size 128 x 128, RGB components. +struct sQ3BSPLightmap { + unsigned char bLMapData[ CE_BSP_LIGHTMAPSIZE ]; + sQ3BSPLightmap() { + ::memset(bLMapData, 0, CE_BSP_LIGHTMAPSIZE ); + } +}; + +struct SubPatch { + std::vector<size_t> indices; + int lightmapID; +}; + +enum eLumps { + kEntities = 0, + kTextures, + kPlanes, + kNodes, + kLeafs, + kLeafFaces, + kLeafBrushes, + kModels, + kBrushes, + kBrushSides, + kVertices, + kMeshVerts, + kShaders, + kFaces, + kLightmaps, + kLightVolumes, + kVisData, + kMaxLumps +}; + +struct Q3BSPModel { + std::vector<unsigned char> m_Data; + std::vector<sQ3BSPLump*> m_Lumps; + std::vector<sQ3BSPVertex*> m_Vertices; + std::vector<sQ3BSPFace*> m_Faces; + std::vector<int> m_Indices; + std::vector<sQ3BSPTexture*> m_Textures; + std::vector<sQ3BSPLightmap*> m_Lightmaps; + std::vector<char> m_EntityData; + std::string m_ModelName; + + Q3BSPModel() : + m_Data(), + m_Lumps(), + m_Vertices(), + m_Faces(), + m_Indices(), + m_Textures(), + m_Lightmaps(), + m_EntityData(), + m_ModelName() + { + // empty + } + + ~Q3BSPModel() { + for ( unsigned int i=0; i<m_Lumps.size(); i++ ) { + delete m_Lumps[ i ]; + } + for ( unsigned int i=0; i<m_Vertices.size(); i++ ) { + delete m_Vertices[ i ]; + } + for ( unsigned int i=0; i<m_Faces.size(); i++ ) { + delete m_Faces[ i ]; + } + for ( unsigned int i=0; i<m_Textures.size(); i++ ) { + delete m_Textures[ i ]; + } + for ( unsigned int i=0; i<m_Lightmaps.size(); i++ ) { + delete m_Lightmaps[ i ]; + } + + m_Lumps.clear(); + m_Vertices.clear(); + m_Faces.clear(); + m_Textures.clear(); + m_Lightmaps.clear(); + } +}; + +} // Namespace Q3BSP +} // Namespace Assimp + +#endif // ASSIMP_Q3BSPFILEDATA_H_INC diff --git a/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp b/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp new file mode 100644 index 0000000..db9a4a0 --- /dev/null +++ b/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp @@ -0,0 +1,680 @@ +/* +Open Asset Import Library (assimp) +--------------------------------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +--------------------------------------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_Q3BSP_IMPORTER + +#include "Q3BSPFileImporter.h" +#include "Q3BSPFileData.h" +#include "Q3BSPFileParser.h" + +#include <assimp/DefaultLogger.hpp> + +#ifdef ASSIMP_BUILD_NO_OWN_ZLIB +#include <zlib.h> +#else +#include "../contrib/zlib/zlib.h" +#endif + +#include <assimp/DefaultIOSystem.h> +#include <assimp/StringComparison.h> +#include <assimp/ZipArchiveIOSystem.h> +#include <assimp/ai_assert.h> +#include <assimp/importerdesc.h> +#include <assimp/mesh.h> +#include <assimp/scene.h> +#include <assimp/types.h> +#include <sstream> +#include <vector> + +static const aiImporterDesc desc = { + "Quake III BSP Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "bsp pk3" +}; + +namespace Assimp { + +using namespace Q3BSP; + +// ------------------------------------------------------------------------------------------------ +// Local function to create a material key name. +static void createKey(int id1, int id2, std::string &key) { + std::ostringstream str; + str << id1 << "." << id2; + key = str.str(); +} + +// ------------------------------------------------------------------------------------------------ +// Local function to extract the texture ids from a material key-name. +static void extractIds(const std::string &key, int &id1, int &id2) { + id1 = -1; + id2 = -1; + if (key.empty()) { + return; + } + + const std::string::size_type pos = key.find('.'); + if (std::string::npos == pos) { + return; + } + + std::string tmp1 = key.substr(0, pos); + std::string tmp2 = key.substr(pos + 1, key.size() - pos - 1); + id1 = atoi(tmp1.c_str()); + id2 = atoi(tmp2.c_str()); +} + +// ------------------------------------------------------------------------------------------------ +// Local helper function to normalize filenames. +static void normalizePathName(const std::string &rPath, std::string &normalizedPath) { + normalizedPath = std::string(); + if (rPath.empty()) { + return; + } + +#ifdef _WIN32 + std::string sep = "\\"; +#else + std::string sep = "/"; +#endif + + static const unsigned int numDelimiters = 2; + const char delimiters[numDelimiters] = { '/', '\\' }; + normalizedPath = rPath; + for (const char delimiter : delimiters) { + for (size_t j = 0; j < normalizedPath.size(); ++j) { + if (normalizedPath[j] == delimiter) { + normalizedPath[j] = sep[0]; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Constructor. +Q3BSPFileImporter::Q3BSPFileImporter() : + m_pCurrentMesh(nullptr), m_pCurrentFace(nullptr), m_MaterialLookupMap(), mTextures() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor. +Q3BSPFileImporter::~Q3BSPFileImporter() { + // Clear face-to-material map + for (FaceMap::iterator it = m_MaterialLookupMap.begin(); it != m_MaterialLookupMap.end(); ++it) { + const std::string &matName = it->first; + if (!matName.empty()) { + delete it->second; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Returns true if the loader can read this. +bool Q3BSPFileImporter::CanRead(const std::string &filename, IOSystem * /*pIOHandler*/, bool checkSig) const { + if (!checkSig) { + return SimpleExtensionCheck(filename, "pk3", "bsp"); + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +// Adds extensions. +const aiImporterDesc *Q3BSPFileImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Import method. +void Q3BSPFileImporter::InternReadFile(const std::string &rFile, aiScene *scene, IOSystem *ioHandler) { + ZipArchiveIOSystem Archive(ioHandler, rFile); + if (!Archive.isOpen()) { + throw DeadlyImportError("Failed to open file ", rFile, "."); + } + + std::string archiveName, mapName; + separateMapName(rFile, archiveName, mapName); + + if (mapName.empty()) { + if (!findFirstMapInArchive(Archive, mapName)) { + return; + } + } + + Q3BSPFileParser fileParser(mapName, &Archive); + Q3BSPModel *pBSPModel = fileParser.getModel(); + if (nullptr != pBSPModel) { + CreateDataFromImport(pBSPModel, scene, &Archive); + } +} + +// ------------------------------------------------------------------------------------------------ +// Separates the map name from the import name. +void Q3BSPFileImporter::separateMapName(const std::string &importName, std::string &archiveName, std::string &mapName) { + archiveName = std::string(); + mapName = std::string(); + if (importName.empty()) { + return; + } + + const std::string::size_type pos = importName.rfind(','); + if (std::string::npos == pos) { + archiveName = importName; + return; + } + + archiveName = importName.substr(0, pos); + mapName = importName.substr(pos, importName.size() - pos - 1); +} + +// ------------------------------------------------------------------------------------------------ +// Returns the first map in the map archive. +bool Q3BSPFileImporter::findFirstMapInArchive(ZipArchiveIOSystem &bspArchive, std::string &mapName) { + mapName = std::string(); + std::vector<std::string> fileList; + bspArchive.getFileListExtension(fileList, "bsp"); + if (fileList.empty()) { + return false; + } + + std::vector<std::string>::iterator it(fileList.begin()); + for (; it != fileList.end(); ++it) { + const std::string::size_type pos = (*it).find("maps/"); + if (std::string::npos != pos) { + std::string::size_type extPos = (*it).find(".bsp"); + if (std::string::npos != extPos) { + mapName = *it; + return true; + } + } + } + + return false; +} + +// ------------------------------------------------------------------------------------------------ +// Creates the assimp specific data. +void Q3BSPFileImporter::CreateDataFromImport(const Q3BSP::Q3BSPModel *pModel, aiScene *pScene, + ZipArchiveIOSystem *pArchive) { + if (nullptr == pModel || nullptr == pScene) { + return; + } + + pScene->mRootNode = new aiNode; + if (!pModel->m_ModelName.empty()) { + pScene->mRootNode->mName.Set(pModel->m_ModelName); + } + + // Create the face to material relation map + createMaterialMap(pModel); + + // Create all nodes + CreateNodes(pModel, pScene, pScene->mRootNode); + + // Create the assigned materials + createMaterials(pModel, pScene, pArchive); +} + +// ------------------------------------------------------------------------------------------------ +// Creates all assimp nodes. +void Q3BSPFileImporter::CreateNodes(const Q3BSP::Q3BSPModel *pModel, aiScene *pScene, + aiNode *pParent) { + if (nullptr == pModel) { + return; + } + + unsigned int matIdx(0); + std::vector<aiMesh *> MeshArray; + std::vector<aiNode *> NodeArray; + for (FaceMapIt it = m_MaterialLookupMap.begin(); it != m_MaterialLookupMap.end(); ++it) { + std::vector<Q3BSP::sQ3BSPFace *> *pArray = (*it).second; + size_t numVerts = countData(*pArray); + if (0 != numVerts) { + aiMesh *pMesh(nullptr); + aiNode *pNode = CreateTopology(pModel, matIdx, *pArray, &pMesh); + if (nullptr != pNode) { + NodeArray.push_back(pNode); + MeshArray.push_back(pMesh); + } + } + matIdx++; + } + + pScene->mNumMeshes = static_cast<unsigned int>(MeshArray.size()); + if (pScene->mNumMeshes > 0) { + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + for (size_t i = 0; i < MeshArray.size(); i++) { + aiMesh *pMesh = MeshArray[i]; + if (nullptr != pMesh) { + pScene->mMeshes[i] = pMesh; + } + } + } + + pParent->mNumChildren = static_cast<unsigned int>(MeshArray.size()); + pParent->mChildren = new aiNode *[pScene->mRootNode->mNumChildren]; + for (size_t i = 0; i < NodeArray.size(); i++) { + aiNode *pNode = NodeArray[i]; + pNode->mParent = pParent; + pParent->mChildren[i] = pNode; + pParent->mChildren[i]->mMeshes[0] = static_cast<unsigned int>(i); + } +} + +// ------------------------------------------------------------------------------------------------ +// Creates the topology. +aiNode *Q3BSPFileImporter::CreateTopology(const Q3BSP::Q3BSPModel *pModel, unsigned int materialIdx, + std::vector<sQ3BSPFace *> &rArray, aiMesh **pMesh) { + size_t numVerts = countData(rArray); + if (0 == numVerts) { + return nullptr; + } + + size_t numFaces = countFaces(rArray); + if (0 == numFaces) { + return nullptr; + } + + aiMesh *mesh = new aiMesh; + size_t numTriangles = countTriangles(rArray); + mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + mesh->mFaces = new aiFace[numTriangles]; + mesh->mNumFaces = static_cast<unsigned int>(numTriangles); + + mesh->mNumVertices = static_cast<unsigned int>(numVerts); + mesh->mVertices = new aiVector3D[numVerts]; + mesh->mNormals = new aiVector3D[numVerts]; + mesh->mTextureCoords[0] = new aiVector3D[numVerts]; + mesh->mTextureCoords[1] = new aiVector3D[numVerts]; + mesh->mMaterialIndex = materialIdx; + + unsigned int faceIdx = 0; + unsigned int vertIdx = 0; + mesh->mNumUVComponents[0] = 2; + mesh->mNumUVComponents[1] = 2; + for (std::vector<sQ3BSPFace *>::const_iterator it = rArray.begin(); it != rArray.end(); ++it) { + Q3BSP::sQ3BSPFace *pQ3BSPFace = *it; + ai_assert(nullptr != pQ3BSPFace); + if (nullptr == pQ3BSPFace) { + continue; + } + + if (pQ3BSPFace->iNumOfFaceVerts > 0) { + if (pQ3BSPFace->iType == Polygon || pQ3BSPFace->iType == TriangleMesh) { + createTriangleTopology(pModel, pQ3BSPFace, mesh, faceIdx, vertIdx); + } + } + } + + aiNode *pNode = new aiNode; + pNode->mNumMeshes = 1; + pNode->mMeshes = new unsigned int[1]; + *pMesh = mesh; + + return pNode; +} + +// ------------------------------------------------------------------------------------------------ +// Creates the triangle topology from a face array. +void Q3BSPFileImporter::createTriangleTopology(const Q3BSP::Q3BSPModel *pModel, sQ3BSPFace *pQ3BSPFace, + aiMesh *pMesh, unsigned int &faceIdx, unsigned int &vertIdx) { + ai_assert(faceIdx < pMesh->mNumFaces); + + m_pCurrentFace = getNextFace(pMesh, faceIdx); + if (nullptr == m_pCurrentFace) { + return; + } + + m_pCurrentFace->mNumIndices = 3; + m_pCurrentFace->mIndices = new unsigned int[m_pCurrentFace->mNumIndices]; + + size_t idx(0); + for (size_t i = 0; i < (size_t)pQ3BSPFace->iNumOfFaceVerts; ++i) { + const size_t index = pQ3BSPFace->iVertexIndex + pModel->m_Indices[pQ3BSPFace->iFaceVertexIndex + i]; + if (index >= pModel->m_Vertices.size()) { + continue; + } + + sQ3BSPVertex *pVertex = pModel->m_Vertices[index]; + if (nullptr == pVertex) { + continue; + } + if (idx > 2) { + idx = 0; + m_pCurrentFace = getNextFace(pMesh, faceIdx); + if (nullptr != m_pCurrentFace) { + m_pCurrentFace->mNumIndices = 3; + m_pCurrentFace->mIndices = new unsigned int[3]; + m_pCurrentFace->mIndices[idx] = vertIdx; + } + } + + pMesh->mVertices[vertIdx].Set(pVertex->vPosition.x, pVertex->vPosition.y, pVertex->vPosition.z); + pMesh->mNormals[vertIdx].Set(pVertex->vNormal.x, pVertex->vNormal.y, pVertex->vNormal.z); + + pMesh->mTextureCoords[0][vertIdx].Set(pVertex->vTexCoord.x, pVertex->vTexCoord.y, 0.0f); + pMesh->mTextureCoords[1][vertIdx].Set(pVertex->vLightmap.x, pVertex->vLightmap.y, 0.0f); + + vertIdx++; + idx++; + } +} + +// ------------------------------------------------------------------------------------------------ +// Creates all referenced materials. +void Q3BSPFileImporter::createMaterials(const Q3BSP::Q3BSPModel *pModel, aiScene *pScene, + ZipArchiveIOSystem *pArchive) { + if (m_MaterialLookupMap.empty()) { + return; + } + + pScene->mMaterials = new aiMaterial *[m_MaterialLookupMap.size()]; + aiString aiMatName; + int textureId(-1), lightmapId(-1); + for (FaceMapIt it = m_MaterialLookupMap.begin(); it != m_MaterialLookupMap.end(); + ++it) { + const std::string matName(it->first); + if (matName.empty()) { + continue; + } + + aiMatName.Set(matName); + aiMaterial *pMatHelper = new aiMaterial; + pMatHelper->AddProperty(&aiMatName, AI_MATKEY_NAME); + + extractIds(matName, textureId, lightmapId); + + // Adding the texture + if (-1 != textureId) { + sQ3BSPTexture *pTexture = pModel->m_Textures[textureId]; + if (nullptr != pTexture) { + std::string tmp("*"), texName; + tmp += pTexture->strName; + tmp += ".jpg"; + normalizePathName(tmp, texName); + + if (!importTextureFromArchive(pModel, pArchive, pScene, pMatHelper, textureId)) { + ASSIMP_LOG_ERROR("Cannot import texture from archive ", texName); + } + } + } + if (-1 != lightmapId) { + importLightmap(pModel, pScene, pMatHelper, lightmapId); + } + pScene->mMaterials[pScene->mNumMaterials] = pMatHelper; + pScene->mNumMaterials++; + } + pScene->mNumTextures = static_cast<unsigned int>(mTextures.size()); + pScene->mTextures = new aiTexture *[pScene->mNumTextures]; + std::copy(mTextures.begin(), mTextures.end(), pScene->mTextures); +} + +// ------------------------------------------------------------------------------------------------ +// Counts the number of referenced vertices. +size_t Q3BSPFileImporter::countData(const std::vector<sQ3BSPFace *> &faceArray) const { + size_t numVerts(0); + for (std::vector<sQ3BSPFace *>::const_iterator it = faceArray.begin(); it != faceArray.end(); + ++it) { + sQ3BSPFace *pQ3BSPFace = *it; + if (pQ3BSPFace->iType == Polygon || pQ3BSPFace->iType == TriangleMesh) { + Q3BSP::sQ3BSPFace *face = *it; + if (nullptr != face) { + numVerts += face->iNumOfFaceVerts; + } + } + } + + return numVerts; +} + +// ------------------------------------------------------------------------------------------------ +// Counts the faces with vertices. +size_t Q3BSPFileImporter::countFaces(const std::vector<Q3BSP::sQ3BSPFace *> &rArray) const { + size_t numFaces = 0; + for (std::vector<sQ3BSPFace *>::const_iterator it = rArray.begin(); it != rArray.end(); + ++it) { + Q3BSP::sQ3BSPFace *pQ3BSPFace = *it; + if (pQ3BSPFace->iNumOfFaceVerts > 0) { + numFaces++; + } + } + + return numFaces; +} + +// ------------------------------------------------------------------------------------------------ +// Counts the number of triangles in a Q3-face-array. +size_t Q3BSPFileImporter::countTriangles(const std::vector<Q3BSP::sQ3BSPFace *> &rArray) const { + size_t numTriangles = 0; + for (std::vector<Q3BSP::sQ3BSPFace *>::const_iterator it = rArray.begin(); it != rArray.end(); + ++it) { + const Q3BSP::sQ3BSPFace *pQ3BSPFace = *it; + if (nullptr != pQ3BSPFace) { + numTriangles += pQ3BSPFace->iNumOfFaceVerts / 3; + } + } + + return numTriangles; +} + +// ------------------------------------------------------------------------------------------------ +// Creates the faces-to-material map. +void Q3BSPFileImporter::createMaterialMap(const Q3BSP::Q3BSPModel *pModel) { + std::string key; + std::vector<sQ3BSPFace *> *pCurFaceArray = nullptr; + for (size_t idx = 0; idx < pModel->m_Faces.size(); idx++) { + Q3BSP::sQ3BSPFace *pQ3BSPFace = pModel->m_Faces[idx]; + const int texId = pQ3BSPFace->iTextureID; + const int lightMapId = pQ3BSPFace->iLightmapID; + createKey(texId, lightMapId, key); + FaceMapIt it = m_MaterialLookupMap.find(key); + if (m_MaterialLookupMap.end() == it) { + pCurFaceArray = new std::vector<Q3BSP::sQ3BSPFace *>; + m_MaterialLookupMap[key] = pCurFaceArray; + } else { + pCurFaceArray = (*it).second; + } + ai_assert(nullptr != pCurFaceArray); + if (nullptr != pCurFaceArray) { + pCurFaceArray->push_back(pQ3BSPFace); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Returns the next face. +aiFace *Q3BSPFileImporter::getNextFace(aiMesh *mesh, unsigned int &faceIdx) { + aiFace *face(nullptr); + if (faceIdx < mesh->mNumFaces) { + face = &mesh->mFaces[faceIdx]; + ++faceIdx; + } + + return face; +} + +// ------------------------------------------------------------------------------------------------ +// Imports a texture file. +bool Q3BSPFileImporter::importTextureFromArchive(const Q3BSP::Q3BSPModel *model, + ZipArchiveIOSystem *archive, aiScene *, + aiMaterial *pMatHelper, int textureId) { + if (nullptr == archive || nullptr == pMatHelper) { + return false; + } + + if (textureId < 0 || textureId >= static_cast<int>(model->m_Textures.size())) { + return false; + } + + bool res = true; + sQ3BSPTexture *pTexture = model->m_Textures[textureId]; + if (!pTexture) { + return false; + } + + std::vector<std::string> supportedExtensions; + supportedExtensions.push_back(".jpg"); + supportedExtensions.push_back(".png"); + supportedExtensions.push_back(".tga"); + std::string textureName, ext; + if (expandFile(archive, pTexture->strName, supportedExtensions, textureName, ext)) { + IOStream *pTextureStream = archive->Open(textureName.c_str()); + if (pTextureStream) { + size_t texSize = pTextureStream->FileSize(); + aiTexture *curTexture = new aiTexture; + curTexture->mHeight = 0; + curTexture->mWidth = static_cast<unsigned int>(texSize); + unsigned char *pData = new unsigned char[curTexture->mWidth]; + size_t readSize = pTextureStream->Read(pData, sizeof(unsigned char), curTexture->mWidth); + (void)readSize; + ai_assert(readSize == curTexture->mWidth); + curTexture->pcData = reinterpret_cast<aiTexel *>(pData); + curTexture->achFormatHint[0] = ext[1]; + curTexture->achFormatHint[1] = ext[2]; + curTexture->achFormatHint[2] = ext[3]; + curTexture->achFormatHint[3] = '\0'; + res = true; + + aiString name; + name.data[0] = '*'; + name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast<unsigned int>(MAXLEN - 1), static_cast<int32_t>(mTextures.size())); + + archive->Close(pTextureStream); + + pMatHelper->AddProperty(&name, AI_MATKEY_TEXTURE_DIFFUSE(0)); + mTextures.push_back(curTexture); + } else { + // If it doesn't exist in the archive, it is probably just a reference to an external file. + // We'll leave it up to the user to figure out which extension the file has. + aiString name; + strncpy(name.data, pTexture->strName, sizeof name.data); + name.length = static_cast<ai_uint32>(strlen(name.data)); + pMatHelper->AddProperty(&name, AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + } + + return res; +} + +// ------------------------------------------------------------------------------------------------ +// Imports a light map file. +bool Q3BSPFileImporter::importLightmap(const Q3BSP::Q3BSPModel *pModel, aiScene *pScene, + aiMaterial *pMatHelper, int lightmapId) { + if (nullptr == pModel || nullptr == pScene || nullptr == pMatHelper) { + return false; + } + + if (lightmapId < 0 || lightmapId >= static_cast<int>(pModel->m_Lightmaps.size())) { + return false; + } + + sQ3BSPLightmap *pLightMap = pModel->m_Lightmaps[lightmapId]; + if (nullptr == pLightMap) { + return false; + } + + aiTexture *pTexture = new aiTexture; + + pTexture->mWidth = CE_BSP_LIGHTMAPWIDTH; + pTexture->mHeight = CE_BSP_LIGHTMAPHEIGHT; + pTexture->pcData = new aiTexel[CE_BSP_LIGHTMAPWIDTH * CE_BSP_LIGHTMAPHEIGHT]; + + ::memcpy(pTexture->pcData, pLightMap->bLMapData, pTexture->mWidth); + size_t p = 0; + for (size_t i = 0; i < CE_BSP_LIGHTMAPWIDTH * CE_BSP_LIGHTMAPHEIGHT; ++i) { + pTexture->pcData[i].r = pLightMap->bLMapData[p++]; + pTexture->pcData[i].g = pLightMap->bLMapData[p++]; + pTexture->pcData[i].b = pLightMap->bLMapData[p++]; + pTexture->pcData[i].a = 0xFF; + } + + aiString name; + name.data[0] = '*'; + name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast<unsigned int>(MAXLEN - 1), static_cast<int32_t>(mTextures.size())); + + pMatHelper->AddProperty(&name, AI_MATKEY_TEXTURE_LIGHTMAP(1)); + mTextures.push_back(pTexture); + + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Will search for a supported extension. +bool Q3BSPFileImporter::expandFile(ZipArchiveIOSystem *pArchive, const std::string &rFilename, + const std::vector<std::string> &rExtList, std::string &rFile, + std::string &rExt) { + ai_assert(nullptr != pArchive); + ai_assert(!rFilename.empty()); + + if (rExtList.empty()) { + rFile = rFilename; + rExt = std::string(); + return true; + } + + bool found = false; + for (std::vector<std::string>::const_iterator it = rExtList.begin(); it != rExtList.end(); ++it) { + const std::string textureName = rFilename + *it; + if (pArchive->Exists(textureName.c_str())) { + rExt = *it; + rFile = textureName; + found = true; + break; + } + } + + return found; +} + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_Q3BSP_IMPORTER diff --git a/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileImporter.h b/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileImporter.h new file mode 100644 index 0000000..fdcfff8 --- /dev/null +++ b/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileImporter.h @@ -0,0 +1,118 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_Q3BSPFILEIMPORTER_H_INC +#define ASSIMP_Q3BSPFILEIMPORTER_H_INC + +#include <assimp/BaseImporter.h> + +#include <map> +#include <string> + +struct aiMesh; +struct aiNode; +struct aiFace; +struct aiMaterial; +struct aiTexture; + +namespace Assimp { + class ZipArchiveIOSystem; + +namespace Q3BSP { + struct Q3BSPModel; + struct sQ3BSPFace; +} + +// ------------------------------------------------------------------------------------------------ +/** Loader to import BSP-levels from a PK3 archive or from a unpacked BSP-level. + */ +// ------------------------------------------------------------------------------------------------ +class Q3BSPFileImporter : public BaseImporter { +public: + /// @brief Default constructor. + Q3BSPFileImporter(); + + /// @brief Destructor. + ~Q3BSPFileImporter() override; + + /// @brief Returns whether the class can handle the format of the given file. + /// @remark See BaseImporter::CanRead() for details. + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig ) const override; + +protected: + using FaceMap = std::map<std::string, std::vector<Q3BSP::sQ3BSPFace*>*>; + using FaceMapIt = std::map<std::string, std::vector<Q3BSP::sQ3BSPFace*>* >::iterator; + using FaceMapConstIt = std::map<std::string, std::vector<Q3BSP::sQ3BSPFace*>*>::const_iterator; + + const aiImporterDesc* GetInfo () const override; + void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) override; + void separateMapName( const std::string &rImportName, std::string &rArchiveName, std::string &rMapName ); + bool findFirstMapInArchive(ZipArchiveIOSystem &rArchive, std::string &rMapName ); + void CreateDataFromImport( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, ZipArchiveIOSystem *pArchive ); + void CreateNodes( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, aiNode *pParent ); + aiNode *CreateTopology( const Q3BSP::Q3BSPModel *pModel, unsigned int materialIdx, + std::vector<Q3BSP::sQ3BSPFace*> &rArray, aiMesh **pMesh ); + void createTriangleTopology( const Q3BSP::Q3BSPModel *pModel, Q3BSP::sQ3BSPFace *pQ3BSPFace, aiMesh* pMesh, unsigned int &rFaceIdx, + unsigned int &rVertIdx ); + void createMaterials( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, ZipArchiveIOSystem *pArchive ); + size_t countData( const std::vector<Q3BSP::sQ3BSPFace*> &rArray ) const; + size_t countFaces( const std::vector<Q3BSP::sQ3BSPFace*> &rArray ) const; + size_t countTriangles( const std::vector<Q3BSP::sQ3BSPFace*> &rArray ) const; + void createMaterialMap( const Q3BSP::Q3BSPModel *pModel); + aiFace *getNextFace( aiMesh *pMesh, unsigned int &rFaceIdx ); + bool importTextureFromArchive( const Q3BSP::Q3BSPModel *pModel, ZipArchiveIOSystem *pArchive, aiScene* pScene, + aiMaterial *pMatHelper, int textureId ); + bool importLightmap( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, aiMaterial *pMatHelper, int lightmapId ); + bool importEntities( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene ); + bool expandFile(ZipArchiveIOSystem *pArchive, const std::string &rFilename, const std::vector<std::string> &rExtList, + std::string &rFile, std::string &rExt ); + +private: + aiMesh *m_pCurrentMesh; + aiFace *m_pCurrentFace; + FaceMap m_MaterialLookupMap; + std::vector<aiTexture*> mTextures; +}; + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif // ASSIMP_Q3BSPFILEIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.cpp b/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.cpp new file mode 100644 index 0000000..910da5b --- /dev/null +++ b/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.cpp @@ -0,0 +1,272 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + + +#ifndef ASSIMP_BUILD_NO_Q3BSP_IMPORTER + +#include "Q3BSPFileParser.h" +#include "Q3BSPFileData.h" +#include <vector> +#include <assimp/DefaultIOSystem.h> +#include <assimp/ZipArchiveIOSystem.h> +#include <assimp/ai_assert.h> + +namespace Assimp { + +using namespace Q3BSP; + +// ------------------------------------------------------------------------------------------------ +Q3BSPFileParser::Q3BSPFileParser( const std::string &mapName, ZipArchiveIOSystem *pZipArchive ) : + m_sOffset( 0 ), + m_Data(), + m_pModel(nullptr), + m_pZipArchive( pZipArchive ) +{ + ai_assert(nullptr != m_pZipArchive ); + ai_assert( !mapName.empty() ); + + if ( !readData( mapName ) ) + return; + + m_pModel = new Q3BSPModel; + m_pModel->m_ModelName = mapName; + if ( !parseFile() ) { + delete m_pModel; + m_pModel = nullptr; + } +} + +// ------------------------------------------------------------------------------------------------ +Q3BSPFileParser::~Q3BSPFileParser() { + delete m_pModel; +} + +// ------------------------------------------------------------------------------------------------ +Q3BSP::Q3BSPModel *Q3BSPFileParser::getModel() const { + return m_pModel; +} + +// ------------------------------------------------------------------------------------------------ +bool Q3BSPFileParser::readData( const std::string &rMapName ) { + if ( !m_pZipArchive->Exists( rMapName.c_str() ) ) + return false; + + IOStream *pMapFile = m_pZipArchive->Open( rMapName.c_str() ); + if ( nullptr == pMapFile ) + return false; + + const size_t size = pMapFile->FileSize(); + m_Data.resize( size ); + + const size_t readSize = pMapFile->Read( &m_Data[0], sizeof( char ), size ); + if ( readSize != size ) { + m_Data.clear(); + m_pZipArchive->Close(pMapFile); + return false; + } + m_pZipArchive->Close( pMapFile ); + + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool Q3BSPFileParser::parseFile() { + if ( m_Data.empty() ) { + return false; + } + + if ( !validateFormat() ) + { + return false; + } + + // Imports the dictionary of the level + getLumps(); + + // Count data and prepare model data + countLumps(); + + // Read in Vertices + getVertices(); + + // Read in Indices + getIndices(); + + // Read Faces + getFaces(); + + // Read Textures + getTextures(); + + // Read Lightmaps + getLightMaps(); + + // Load the entities + getEntities(); + + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool Q3BSPFileParser::validateFormat() +{ + sQ3BSPHeader *pHeader = (sQ3BSPHeader*) &m_Data[ 0 ]; + m_sOffset += sizeof( sQ3BSPHeader ); + + // Version and identify string validation + if (pHeader->strID[ 0 ] != 'I' || pHeader->strID[ 1 ] != 'B' || pHeader->strID[ 2 ] != 'S' + || pHeader->strID[ 3 ] != 'P') + { + return false; + } + + return true; +} + +// ------------------------------------------------------------------------------------------------ +void Q3BSPFileParser::getLumps() +{ + size_t Offset = m_sOffset; + m_pModel->m_Lumps.resize( kMaxLumps ); + for ( size_t idx=0; idx < kMaxLumps; idx++ ) + { + sQ3BSPLump *pLump = new sQ3BSPLump; + memcpy( pLump, &m_Data[ Offset ], sizeof( sQ3BSPLump ) ); + Offset += sizeof( sQ3BSPLump ); + m_pModel->m_Lumps[ idx ] = pLump; + } +} + +// ------------------------------------------------------------------------------------------------ +void Q3BSPFileParser::countLumps() +{ + m_pModel->m_Vertices.resize( m_pModel->m_Lumps[ kVertices ]->iSize / sizeof( sQ3BSPVertex ) ); + m_pModel->m_Indices.resize( m_pModel->m_Lumps[ kMeshVerts ]->iSize / sizeof( int ) ); + m_pModel->m_Faces.resize( m_pModel->m_Lumps[ kFaces ]->iSize / sizeof( sQ3BSPFace ) ); + m_pModel->m_Textures.resize( m_pModel->m_Lumps[ kTextures ]->iSize / sizeof( sQ3BSPTexture ) ); + m_pModel->m_Lightmaps.resize( m_pModel->m_Lumps[ kLightmaps ]->iSize / sizeof( sQ3BSPLightmap ) ); +} + +// ------------------------------------------------------------------------------------------------ +void Q3BSPFileParser::getVertices() +{ + size_t Offset = m_pModel->m_Lumps[ kVertices ]->iOffset; + for ( size_t idx = 0; idx < m_pModel->m_Vertices.size(); idx++ ) + { + sQ3BSPVertex *pVertex = new sQ3BSPVertex; + memcpy( pVertex, &m_Data[ Offset ], sizeof( sQ3BSPVertex ) ); + Offset += sizeof( sQ3BSPVertex ); + m_pModel->m_Vertices[ idx ] = pVertex; + } +} + +// ------------------------------------------------------------------------------------------------ +void Q3BSPFileParser::getIndices() +{ + ai_assert(nullptr != m_pModel ); + + sQ3BSPLump *lump = m_pModel->m_Lumps[ kMeshVerts ]; + size_t Offset = (size_t) lump->iOffset; + const size_t nIndices = lump->iSize / sizeof( int ); + m_pModel->m_Indices.resize( nIndices ); + memcpy( &m_pModel->m_Indices[ 0 ], &m_Data[ Offset ], lump->iSize ); +} + +// ------------------------------------------------------------------------------------------------ +void Q3BSPFileParser::getFaces() +{ + ai_assert(nullptr != m_pModel ); + + size_t Offset = m_pModel->m_Lumps[ kFaces ]->iOffset; + for ( size_t idx = 0; idx < m_pModel->m_Faces.size(); idx++ ) + { + sQ3BSPFace *pFace = new sQ3BSPFace; + memcpy( pFace, &m_Data[ Offset ], sizeof( sQ3BSPFace ) ); + m_pModel->m_Faces[ idx ] = pFace; + Offset += sizeof( sQ3BSPFace ); + } +} + +// ------------------------------------------------------------------------------------------------ +void Q3BSPFileParser::getTextures() +{ + ai_assert(nullptr != m_pModel ); + + size_t Offset = m_pModel->m_Lumps[ kTextures ]->iOffset; + for ( size_t idx=0; idx < m_pModel->m_Textures.size(); idx++ ) + { + sQ3BSPTexture *pTexture = new sQ3BSPTexture; + memcpy( pTexture, &m_Data[ Offset ], sizeof(sQ3BSPTexture) ); + m_pModel->m_Textures[ idx ] = pTexture; + Offset += sizeof(sQ3BSPTexture); + } +} + +// ------------------------------------------------------------------------------------------------ +void Q3BSPFileParser::getLightMaps() +{ + ai_assert(nullptr != m_pModel ); + + size_t Offset = m_pModel->m_Lumps[kLightmaps]->iOffset; + for ( size_t idx=0; idx < m_pModel->m_Lightmaps.size(); idx++ ) + { + sQ3BSPLightmap *pLightmap = new sQ3BSPLightmap; + memcpy( pLightmap, &m_Data[ Offset ], sizeof( sQ3BSPLightmap ) ); + Offset += sizeof( sQ3BSPLightmap ); + m_pModel->m_Lightmaps[ idx ] = pLightmap; + } +} + +// ------------------------------------------------------------------------------------------------ +void Q3BSPFileParser::getEntities() { + const int size = m_pModel->m_Lumps[ kEntities ]->iSize; + m_pModel->m_EntityData.resize( size ); + if ( size > 0 ) { + size_t Offset = m_pModel->m_Lumps[ kEntities ]->iOffset; + memcpy( &m_pModel->m_EntityData[ 0 ], &m_Data[ Offset ], sizeof( char ) * size ); + } +} + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_Q3BSP_IMPORTER diff --git a/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.h b/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.h new file mode 100644 index 0000000..15cc751 --- /dev/null +++ b/libs/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.h @@ -0,0 +1,89 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_Q3BSPFILEPARSER_H_INC +#define ASSIMP_Q3BSPFILEPARSER_H_INC + +#include <assimp/BaseImporter.h> +#include <string> + +namespace Assimp +{ + class ZipArchiveIOSystem; + +namespace Q3BSP +{ + struct Q3BSPModel; + class ZipFile; +} + +// ------------------------------------------------------------------- +/// @brief This class implements th Q3DSP file parsing. +// ------------------------------------------------------------------- +class Q3BSPFileParser { +public: + Q3BSPFileParser( const std::string &rMapName, ZipArchiveIOSystem *pZipArchive ); + ~Q3BSPFileParser(); + Q3BSP::Q3BSPModel *getModel() const; + +protected: + bool readData(const std::string &rMapName); + bool parseFile(); + bool validateFormat(); + void getLumps(); + void countLumps(); + void getVertices(); + void getIndices(); + void getFaces(); + void getTextures(); + void getLightMaps(); + void getEntities(); + +private: + size_t m_sOffset; + std::vector<char> m_Data; + Q3BSP::Q3BSPModel *m_pModel; + ZipArchiveIOSystem *m_pZipArchive; +}; + +} // Namespace Assimp + +#endif // ASSIMP_Q3BSPFILEPARSER_H_INC diff --git a/libs/assimp/code/AssetLib/Q3D/Q3DLoader.cpp b/libs/assimp/code/AssetLib/Q3D/Q3DLoader.cpp new file mode 100644 index 0000000..c773bbf --- /dev/null +++ b/libs/assimp/code/AssetLib/Q3D/Q3DLoader.cpp @@ -0,0 +1,575 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Q3DLoader.cpp + * @brief Implementation of the Q3D importer class + */ + +#ifndef ASSIMP_BUILD_NO_Q3D_IMPORTER + +// internal headers +#include "Q3DLoader.h" +#include <assimp/StringUtils.h> +#include <assimp/StreamReader.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Quick3D Importer", + "", + "", + "http://www.quick3d.com/", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "q3o q3s" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +Q3DImporter::Q3DImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +Q3DImporter::~Q3DImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool Q3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "quick3Do", "quick3Ds" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc *Q3DImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void Q3DImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, IOSystem *pIOHandler) { + + auto file = pIOHandler->Open(pFile, "rb"); + if (!file) + throw DeadlyImportError("Quick3D: Could not open ", pFile); + + StreamReaderLE stream(file); + + // The header is 22 bytes large + if (stream.GetRemainingSize() < 22) + throw DeadlyImportError("File is either empty or corrupt: ", pFile); + + // Check the file's signature + if (ASSIMP_strincmp((const char *)stream.GetPtr(), "quick3Do", 8) && + ASSIMP_strincmp((const char *)stream.GetPtr(), "quick3Ds", 8)) { + throw DeadlyImportError("Not a Quick3D file. Signature string is: ", ai_str_toprintable((const char *)stream.GetPtr(), 8)); + } + + // Print the file format version + ASSIMP_LOG_INFO("Quick3D File format version: ", + std::string(&((const char *)stream.GetPtr())[8], 2)); + + // ... an store it + char major = ((const char *)stream.GetPtr())[8]; + char minor = ((const char *)stream.GetPtr())[9]; + + stream.IncPtr(10); + unsigned int numMeshes = (unsigned int)stream.GetI4(); + unsigned int numMats = (unsigned int)stream.GetI4(); + unsigned int numTextures = (unsigned int)stream.GetI4(); + + std::vector<Material> materials; + materials.reserve(numMats); + + std::vector<Mesh> meshes; + meshes.reserve(numMeshes); + + // Allocate the scene root node + pScene->mRootNode = new aiNode(); + + aiColor3D fgColor(0.6f, 0.6f, 0.6f); + + // Now read all file chunks + while (true) { + if (stream.GetRemainingSize() < 1) break; + char c = stream.GetI1(); + switch (c) { + // Meshes chunk + case 'm': { + for (unsigned int quak = 0; quak < numMeshes; ++quak) { + meshes.push_back(Mesh()); + Mesh &mesh = meshes.back(); + + // read all vertices + unsigned int numVerts = (unsigned int)stream.GetI4(); + if (!numVerts) + throw DeadlyImportError("Quick3D: Found mesh with zero vertices"); + + std::vector<aiVector3D> &verts = mesh.verts; + verts.resize(numVerts); + + for (unsigned int i = 0; i < numVerts; ++i) { + verts[i].x = stream.GetF4(); + verts[i].y = stream.GetF4(); + verts[i].z = stream.GetF4(); + } + + // read all faces + numVerts = (unsigned int)stream.GetI4(); + if (!numVerts) + throw DeadlyImportError("Quick3D: Found mesh with zero faces"); + + std::vector<Face> &faces = mesh.faces; + faces.reserve(numVerts); + + // number of indices + for (unsigned int i = 0; i < numVerts; ++i) { + faces.push_back(Face(stream.GetI2())); + if (faces.back().indices.empty()) + throw DeadlyImportError("Quick3D: Found face with zero indices"); + } + + // indices + for (unsigned int i = 0; i < numVerts; ++i) { + Face &vec = faces[i]; + for (unsigned int a = 0; a < (unsigned int)vec.indices.size(); ++a) + vec.indices[a] = stream.GetI4(); + } + + // material indices + for (unsigned int i = 0; i < numVerts; ++i) { + faces[i].mat = (unsigned int)stream.GetI4(); + } + + // read all normals + numVerts = (unsigned int)stream.GetI4(); + std::vector<aiVector3D> &normals = mesh.normals; + normals.resize(numVerts); + + for (unsigned int i = 0; i < numVerts; ++i) { + normals[i].x = stream.GetF4(); + normals[i].y = stream.GetF4(); + normals[i].z = stream.GetF4(); + } + + numVerts = (unsigned int)stream.GetI4(); + if (numTextures && numVerts) { + // read all texture coordinates + std::vector<aiVector3D> &uv = mesh.uv; + uv.resize(numVerts); + + for (unsigned int i = 0; i < numVerts; ++i) { + uv[i].x = stream.GetF4(); + uv[i].y = stream.GetF4(); + } + + // UV indices + for (unsigned int i = 0; i < (unsigned int)faces.size(); ++i) { + Face &vec = faces[i]; + for (unsigned int a = 0; a < (unsigned int)vec.indices.size(); ++a) { + vec.uvindices[a] = stream.GetI4(); + if (!i && !a) + mesh.prevUVIdx = vec.uvindices[a]; + else if (vec.uvindices[a] != mesh.prevUVIdx) + mesh.prevUVIdx = UINT_MAX; + } + } + } + + // we don't need the rest, but we need to get to the next chunk + stream.IncPtr(36); + if (minor > '0' && major == '3') + stream.IncPtr(mesh.faces.size()); + } + // stream.IncPtr(4); // unknown value here + } break; + + // materials chunk + case 'c': + + for (unsigned int i = 0; i < numMats; ++i) { + materials.push_back(Material()); + Material &mat = materials.back(); + + // read the material name + c = stream.GetI1(); + while (c) { + mat.name.data[mat.name.length++] = c; + c = stream.GetI1(); + } + + // add the terminal character + mat.name.data[mat.name.length] = '\0'; + + // read the ambient color + mat.ambient.r = stream.GetF4(); + mat.ambient.g = stream.GetF4(); + mat.ambient.b = stream.GetF4(); + + // read the diffuse color + mat.diffuse.r = stream.GetF4(); + mat.diffuse.g = stream.GetF4(); + mat.diffuse.b = stream.GetF4(); + + // read the ambient color + mat.specular.r = stream.GetF4(); + mat.specular.g = stream.GetF4(); + mat.specular.b = stream.GetF4(); + + // read the transparency + mat.transparency = stream.GetF4(); + + // unknown value here + // stream.IncPtr(4); + // FIX: it could be the texture index ... + mat.texIdx = (unsigned int)stream.GetI4(); + } + + break; + + // texture chunk + case 't': + + pScene->mNumTextures = numTextures; + if (!numTextures) { + break; + } + pScene->mTextures = new aiTexture *[pScene->mNumTextures]; + // to make sure we won't crash if we leave through an exception + ::memset(pScene->mTextures, 0, sizeof(void *) * pScene->mNumTextures); + for (unsigned int i = 0; i < pScene->mNumTextures; ++i) { + aiTexture *tex = pScene->mTextures[i] = new aiTexture; + + // skip the texture name + while (stream.GetI1()) + ; + + // read texture width and height + tex->mWidth = (unsigned int)stream.GetI4(); + tex->mHeight = (unsigned int)stream.GetI4(); + + if (!tex->mWidth || !tex->mHeight) { + throw DeadlyImportError("Quick3D: Invalid texture. Width or height is zero"); + } + + unsigned int mul = tex->mWidth * tex->mHeight; + aiTexel *begin = tex->pcData = new aiTexel[mul]; + aiTexel *const end = &begin[mul - 1] + 1; + + for (; begin != end; ++begin) { + begin->r = stream.GetI1(); + begin->g = stream.GetI1(); + begin->b = stream.GetI1(); + begin->a = 0xff; + } + } + + break; + + // scene chunk + case 's': { + // skip position and rotation + stream.IncPtr(12); + + for (unsigned int i = 0; i < 4; ++i) + for (unsigned int a = 0; a < 4; ++a) + pScene->mRootNode->mTransformation[i][a] = stream.GetF4(); + + stream.IncPtr(16); + + // now setup a single camera + pScene->mNumCameras = 1; + pScene->mCameras = new aiCamera *[1]; + aiCamera *cam = pScene->mCameras[0] = new aiCamera(); + cam->mPosition.x = stream.GetF4(); + cam->mPosition.y = stream.GetF4(); + cam->mPosition.z = stream.GetF4(); + cam->mName.Set("Q3DCamera"); + + // skip eye rotation for the moment + stream.IncPtr(12); + + // read the default material color + fgColor.r = stream.GetF4(); + fgColor.g = stream.GetF4(); + fgColor.b = stream.GetF4(); + + // skip some unimportant properties + stream.IncPtr(29); + + // setup a single point light with no attenuation + pScene->mNumLights = 1; + pScene->mLights = new aiLight *[1]; + aiLight *light = pScene->mLights[0] = new aiLight(); + light->mName.Set("Q3DLight"); + light->mType = aiLightSource_POINT; + + light->mAttenuationConstant = 1; + light->mAttenuationLinear = 0; + light->mAttenuationQuadratic = 0; + + light->mColorDiffuse.r = stream.GetF4(); + light->mColorDiffuse.g = stream.GetF4(); + light->mColorDiffuse.b = stream.GetF4(); + + light->mColorSpecular = light->mColorDiffuse; + + // We don't need the rest, but we need to know where this chunk ends. + unsigned int temp = (unsigned int)(stream.GetI4() * stream.GetI4()); + + // skip the background file name + while (stream.GetI1()) + ; + + // skip background texture data + the remaining fields + stream.IncPtr(temp * 3 + 20); // 4 bytes of unknown data here + + // TODO + goto outer; + } break; + + default: + throw DeadlyImportError("Quick3D: Unknown chunk"); + break; + }; + } +outer: + + // If we have no mesh loaded - break here + if (meshes.empty()) + throw DeadlyImportError("Quick3D: No meshes loaded"); + + // If we have no materials loaded - generate a default mat + if (materials.empty()) { + ASSIMP_LOG_INFO("Quick3D: No material found, generating one"); + materials.push_back(Material()); + materials.back().diffuse = fgColor; + } + + // find out which materials we'll need + typedef std::pair<unsigned int, unsigned int> FaceIdx; + typedef std::vector<FaceIdx> FaceIdxArray; + FaceIdxArray *fidx = new FaceIdxArray[materials.size()]; + + unsigned int p = 0; + for (std::vector<Mesh>::iterator it = meshes.begin(), end = meshes.end(); + it != end; ++it, ++p) { + unsigned int q = 0; + for (std::vector<Face>::iterator fit = (*it).faces.begin(), fend = (*it).faces.end(); + fit != fend; ++fit, ++q) { + if ((*fit).mat >= materials.size()) { + ASSIMP_LOG_WARN("Quick3D: Material index overflow"); + (*fit).mat = 0; + } + if (fidx[(*fit).mat].empty()) ++pScene->mNumMeshes; + fidx[(*fit).mat].push_back(FaceIdx(p, q)); + } + } + pScene->mNumMaterials = pScene->mNumMeshes; + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + pScene->mMeshes = new aiMesh *[pScene->mNumMaterials]; + + for (unsigned int i = 0, real = 0; i < (unsigned int)materials.size(); ++i) { + if (fidx[i].empty()) continue; + + // Allocate a mesh and a material + aiMesh *mesh = pScene->mMeshes[real] = new aiMesh(); + aiMaterial *mat = new aiMaterial(); + pScene->mMaterials[real] = mat; + + mesh->mMaterialIndex = real; + + // Build the output material + Material &srcMat = materials[i]; + mat->AddProperty(&srcMat.diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty(&srcMat.specular, 1, AI_MATKEY_COLOR_SPECULAR); + mat->AddProperty(&srcMat.ambient, 1, AI_MATKEY_COLOR_AMBIENT); + + // NOTE: Ignore transparency for the moment - it seems + // unclear how to interpret the data +#if 0 + if (!(minor > '0' && major == '3')) + srcMat.transparency = 1.0f - srcMat.transparency; + mat->AddProperty(&srcMat.transparency, 1, AI_MATKEY_OPACITY); +#endif + + // add shininess - Quick3D seems to use it ins its viewer + srcMat.transparency = 16.f; + mat->AddProperty(&srcMat.transparency, 1, AI_MATKEY_SHININESS); + + int m = (int)aiShadingMode_Phong; + mat->AddProperty(&m, 1, AI_MATKEY_SHADING_MODEL); + + if (srcMat.name.length) + mat->AddProperty(&srcMat.name, AI_MATKEY_NAME); + + // Add a texture + if (srcMat.texIdx < pScene->mNumTextures || real < pScene->mNumTextures) { + srcMat.name.data[0] = '*'; + srcMat.name.length = ASSIMP_itoa10(&srcMat.name.data[1], 1000, + (srcMat.texIdx < pScene->mNumTextures ? srcMat.texIdx : real)); + mat->AddProperty(&srcMat.name, AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + + mesh->mNumFaces = (unsigned int)fidx[i].size(); + aiFace *faces = mesh->mFaces = new aiFace[mesh->mNumFaces]; + + // Now build the output mesh. First find out how many + // vertices we'll need + for (FaceIdxArray::const_iterator it = fidx[i].begin(), end = fidx[i].end(); + it != end; ++it) { + mesh->mNumVertices += (unsigned int)meshes[(*it).first].faces[(*it).second].indices.size(); + } + + aiVector3D *verts = mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + aiVector3D *norms = mesh->mNormals = new aiVector3D[mesh->mNumVertices]; + aiVector3D *uv = nullptr; + if (real < pScene->mNumTextures) { + uv = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; + mesh->mNumUVComponents[0] = 2; + } + + // Build the final array + unsigned int cnt = 0; + for (FaceIdxArray::const_iterator it = fidx[i].begin(), end = fidx[i].end(); + it != end; ++it, ++faces) { + Mesh &curMesh = meshes[(*it).first]; + Face &face = curMesh.faces[(*it).second]; + faces->mNumIndices = (unsigned int)face.indices.size(); + faces->mIndices = new unsigned int[faces->mNumIndices]; + + aiVector3D faceNormal; + bool fnOK = false; + + for (unsigned int n = 0; n < faces->mNumIndices; ++n, ++cnt, ++norms, ++verts) { + if (face.indices[n] >= curMesh.verts.size()) { + ASSIMP_LOG_WARN("Quick3D: Vertex index overflow"); + face.indices[n] = 0; + } + + // copy vertices + *verts = curMesh.verts[face.indices[n]]; + + if (face.indices[n] >= curMesh.normals.size() && faces->mNumIndices >= 3) { + // we have no normal here - assign the face normal + if (!fnOK) { + const aiVector3D &pV1 = curMesh.verts[face.indices[0]]; + const aiVector3D &pV2 = curMesh.verts[face.indices[1]]; + const aiVector3D &pV3 = curMesh.verts[face.indices.size() - 1]; + faceNormal = (pV2 - pV1) ^ (pV3 - pV1).Normalize(); + fnOK = true; + } + *norms = faceNormal; + } else { + *norms = curMesh.normals[face.indices[n]]; + } + + // copy texture coordinates + if (uv && curMesh.uv.size()) { + if (curMesh.prevUVIdx != 0xffffffff && curMesh.uv.size() >= curMesh.verts.size()) // workaround + { + *uv = curMesh.uv[face.indices[n]]; + } else { + if (face.uvindices[n] >= curMesh.uv.size()) { + ASSIMP_LOG_WARN("Quick3D: Texture coordinate index overflow"); + face.uvindices[n] = 0; + } + *uv = curMesh.uv[face.uvindices[n]]; + } + uv->y = 1.f - uv->y; + ++uv; + } + + // setup the new vertex index + faces->mIndices[n] = cnt; + } + } + ++real; + } + + // Delete our nice helper array + delete[] fidx; + + // Now we need to attach the meshes to the root node of the scene + pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; + pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) + pScene->mRootNode->mMeshes[i] = i; + + /*pScene->mRootNode->mTransformation *= aiMatrix4x4( + 1.f, 0.f, 0.f, 0.f, + 0.f, -1.f,0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, 0.f, 0.f, 1.f);*/ + + // Add cameras and light sources to the scene root node + pScene->mRootNode->mNumChildren = pScene->mNumLights + pScene->mNumCameras; + if (pScene->mRootNode->mNumChildren) { + pScene->mRootNode->mChildren = new aiNode *[pScene->mRootNode->mNumChildren]; + + // the light source + aiNode *nd = pScene->mRootNode->mChildren[0] = new aiNode(); + nd->mParent = pScene->mRootNode; + nd->mName.Set("Q3DLight"); + nd->mTransformation = pScene->mRootNode->mTransformation; + nd->mTransformation.Inverse(); + + // camera + nd = pScene->mRootNode->mChildren[1] = new aiNode(); + nd->mParent = pScene->mRootNode; + nd->mName.Set("Q3DCamera"); + nd->mTransformation = pScene->mRootNode->mChildren[0]->mTransformation; + } +} + +#endif // !! ASSIMP_BUILD_NO_Q3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/Q3D/Q3DLoader.h b/libs/assimp/code/AssetLib/Q3D/Q3DLoader.h new file mode 100644 index 0000000..54af86d --- /dev/null +++ b/libs/assimp/code/AssetLib/Q3D/Q3DLoader.h @@ -0,0 +1,116 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Q3DLoader.h + * @brief Declaration of the Q3D importer class. + */ +#pragma once +#ifndef AI_Q3DLOADER_H_INCLUDED +#define AI_Q3DLOADER_H_INCLUDED + +#include <assimp/BaseImporter.h> +#include <assimp/types.h> +#include <vector> +#include <cstdint> + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** Importer class for the Quick3D Object and Scene formats. +*/ +class Q3DImporter : public BaseImporter { +public: + Q3DImporter(); + ~Q3DImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc* GetInfo () const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler) override; + +private: + struct Material { + Material() : diffuse(0.6f,0.6f,0.6f), transparency(0.f), texIdx(UINT_MAX) { + // empty + } + + aiString name; + aiColor3D ambient, diffuse, specular; + float transparency; + unsigned int texIdx; + }; + + struct Face { + explicit Face(unsigned int s) : indices(s), uvindices(s), mat(0) { + // empty + } + + std::vector<unsigned int> indices; + std::vector<unsigned int> uvindices; + unsigned int mat; + }; + + struct Mesh { + std::vector<aiVector3D> verts; + std::vector<aiVector3D> normals; + std::vector<aiVector3D> uv; + std::vector<Face> faces; + uint32_t prevUVIdx; + }; +}; + +} // end of namespace Assimp + +#endif // AI_Q3DIMPORTER_H_IN diff --git a/libs/assimp/code/AssetLib/Raw/RawLoader.cpp b/libs/assimp/code/AssetLib/Raw/RawLoader.cpp new file mode 100644 index 0000000..bfd2e6d --- /dev/null +++ b/libs/assimp/code/AssetLib/Raw/RawLoader.cpp @@ -0,0 +1,302 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 RawLoader.cpp + * @brief Implementation of the RAW importer class + */ + +#ifndef ASSIMP_BUILD_NO_RAW_IMPORTER + +// internal headers +#include "RawLoader.h" +#include <assimp/ParsingUtils.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <memory> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Raw Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "raw" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +RAWImporter::RAWImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +RAWImporter::~RAWImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool RAWImporter::CanRead(const std::string &filename, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const { + return SimpleExtensionCheck(filename, "raw"); +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc *RAWImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void RAWImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb")); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open RAW file ", pFile, "."); + } + + // allocate storage and copy the contents of the file to a memory buffer + // (terminate it with zero) + std::vector<char> mBuffer2; + TextFileToBuffer(file.get(), mBuffer2); + const char *buffer = &mBuffer2[0]; + + // list of groups loaded from the file + std::vector<GroupInformation> outGroups(1, GroupInformation("<default>")); + std::vector<GroupInformation>::iterator curGroup = outGroups.begin(); + + // now read all lines + char line[4096]; + while (GetNextLine(buffer, line)) { + // if the line starts with a non-numeric identifier, it marks + // the beginning of a new group + const char *sz = line; + SkipSpaces(&sz); + if (IsLineEnd(*sz)) continue; + if (!IsNumeric(*sz)) { + const char *sz2 = sz; + while (!IsSpaceOrNewLine(*sz2)) + ++sz2; + const unsigned int length = (unsigned int)(sz2 - sz); + + // find an existing group with this name + for (std::vector<GroupInformation>::iterator it = outGroups.begin(), end = outGroups.end(); + it != end; ++it) { + if (length == (*it).name.length() && !::strcmp(sz, (*it).name.c_str())) { + curGroup = it; + sz2 = nullptr; + break; + } + } + if (sz2) { + outGroups.push_back(GroupInformation(std::string(sz, length))); + curGroup = outGroups.end() - 1; + } + } else { + // there can be maximally 12 floats plus an extra texture file name + float data[12]; + unsigned int num; + for (num = 0; num < 12; ++num) { + if (!SkipSpaces(&sz) || !IsNumeric(*sz)) break; + sz = fast_atoreal_move<float>(sz, data[num]); + } + if (num != 12 && num != 9) { + ASSIMP_LOG_ERROR("A line may have either 9 or 12 floats and an optional texture"); + continue; + } + + MeshInformation *output = nullptr; + + const char *sz2 = sz; + unsigned int length; + if (!IsLineEnd(*sz)) { + while (!IsSpaceOrNewLine(*sz2)) + ++sz2; + length = (unsigned int)(sz2 - sz); + } else if (9 == num) { + sz = "%default%"; + length = 9; + } else { + sz = ""; + length = 0; + } + + // search in the list of meshes whether we have one with this texture + for (auto &mesh : (*curGroup).meshes) { + if (length == mesh.name.length() && (length ? !::strcmp(sz, mesh.name.c_str()) : true)) { + output = &mesh; + break; + } + } + // if we don't have the mesh, create it + if (!output) { + (*curGroup).meshes.push_back(MeshInformation(std::string(sz, length))); + output = &((*curGroup).meshes.back()); + } + if (12 == num) { + aiColor4D v(data[0], data[1], data[2], 1.0f); + output->colors.push_back(v); + output->colors.push_back(v); + output->colors.push_back(v); + + output->vertices.push_back(aiVector3D(data[3], data[4], data[5])); + output->vertices.push_back(aiVector3D(data[6], data[7], data[8])); + output->vertices.push_back(aiVector3D(data[9], data[10], data[11])); + } else { + output->vertices.push_back(aiVector3D(data[0], data[1], data[2])); + output->vertices.push_back(aiVector3D(data[3], data[4], data[5])); + output->vertices.push_back(aiVector3D(data[6], data[7], data[8])); + } + } + } + + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mName.Set("<RawRoot>"); + + // count the number of valid groups + // (meshes can't be empty) + for (auto &outGroup : outGroups) { + if (!outGroup.meshes.empty()) { + ++pScene->mRootNode->mNumChildren; + pScene->mNumMeshes += (unsigned int)outGroup.meshes.size(); + } + } + + if (!pScene->mNumMeshes) { + throw DeadlyImportError("RAW: No meshes loaded. The file seems to be corrupt or empty."); + } + + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + aiNode **cc; + if (1 == pScene->mRootNode->mNumChildren) { + cc = &pScene->mRootNode; + pScene->mRootNode->mNumChildren = 0; + } else { + cc = new aiNode *[pScene->mRootNode->mNumChildren]; + memset(cc, 0, sizeof(aiNode *) * pScene->mRootNode->mNumChildren); + pScene->mRootNode->mChildren = cc; + } + + pScene->mNumMaterials = pScene->mNumMeshes; + aiMaterial **mats = pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + + unsigned int meshIdx = 0; + for (auto &outGroup : outGroups) { + if (outGroup.meshes.empty()) continue; + + aiNode *node; + if (pScene->mRootNode->mNumChildren) { + node = *cc = new aiNode(); + node->mParent = pScene->mRootNode; + } else + node = *cc; + node->mName.Set(outGroup.name); + + // add all meshes + node->mNumMeshes = (unsigned int)outGroup.meshes.size(); + unsigned int *pi = node->mMeshes = new unsigned int[node->mNumMeshes]; + for (std::vector<MeshInformation>::iterator it2 = outGroup.meshes.begin(), + end2 = outGroup.meshes.end(); + it2 != end2; ++it2) { + ai_assert(!(*it2).vertices.empty()); + + // allocate the mesh + *pi++ = meshIdx; + aiMesh *mesh = pScene->mMeshes[meshIdx] = new aiMesh(); + mesh->mMaterialIndex = meshIdx++; + + mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + // allocate storage for the vertex components and copy them + mesh->mNumVertices = (unsigned int)(*it2).vertices.size(); + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + ::memcpy(mesh->mVertices, &(*it2).vertices[0], sizeof(aiVector3D) * mesh->mNumVertices); + + if ((*it2).colors.size()) { + ai_assert((*it2).colors.size() == mesh->mNumVertices); + + mesh->mColors[0] = new aiColor4D[mesh->mNumVertices]; + ::memcpy(mesh->mColors[0], &(*it2).colors[0], sizeof(aiColor4D) * mesh->mNumVertices); + } + + // generate triangles + ai_assert(0 == mesh->mNumVertices % 3); + aiFace *fc = mesh->mFaces = new aiFace[mesh->mNumFaces = mesh->mNumVertices / 3]; + aiFace *const fcEnd = fc + mesh->mNumFaces; + unsigned int n = 0; + while (fc != fcEnd) { + aiFace &f = *fc++; + f.mIndices = new unsigned int[f.mNumIndices = 3]; + for (unsigned int m = 0; m < 3; ++m) + f.mIndices[m] = n++; + } + + // generate a material for the mesh + aiMaterial *mat = new aiMaterial(); + + aiColor4D clr(1.0f, 1.0f, 1.0f, 1.0f); + if ("%default%" == (*it2).name) // a gray default material + { + clr.r = clr.g = clr.b = 0.6f; + } else if ((*it2).name.length() > 0) // a texture + { + aiString s; + s.Set((*it2).name); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + mat->AddProperty<aiColor4D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); + *mats++ = mat; + } + } +} + +#endif // !! ASSIMP_BUILD_NO_RAW_IMPORTER diff --git a/libs/assimp/code/AssetLib/Raw/RawLoader.h b/libs/assimp/code/AssetLib/Raw/RawLoader.h new file mode 100644 index 0000000..6e4c4d1 --- /dev/null +++ b/libs/assimp/code/AssetLib/Raw/RawLoader.h @@ -0,0 +1,108 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 RAWLoader.h + * @brief Declaration of the RAW importer class. + */ +#pragma once +#ifndef AI_RAWLOADER_H_INCLUDED +#define AI_RAWLOADER_H_INCLUDED + +#include <assimp/BaseImporter.h> +#include <assimp/types.h> +#include <vector> + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** Importer class for the PovRay RAW triangle format +*/ +class RAWImporter : public BaseImporter { +public: + RAWImporter(); + ~RAWImporter(); + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc* GetInfo () const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler) override; + +private: + struct MeshInformation { + explicit MeshInformation(const std::string& _name) : name(_name) { + vertices.reserve(100); + colors.reserve(100); + } + + std::string name; + std::vector<aiVector3D> vertices; + std::vector<aiColor4D> colors; + }; + + struct GroupInformation { + explicit GroupInformation(const std::string& _name) : name(_name) { + meshes.reserve(10); + } + + std::string name; + std::vector<MeshInformation> meshes; + }; +}; + +} // end of namespace Assimp + +#endif // AI_3DSIMPORTER_H_IN diff --git a/libs/assimp/code/AssetLib/SIB/SIBImporter.cpp b/libs/assimp/code/AssetLib/SIB/SIBImporter.cpp new file mode 100644 index 0000000..7b66afa --- /dev/null +++ b/libs/assimp/code/AssetLib/SIB/SIBImporter.cpp @@ -0,0 +1,889 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 SIBImporter.cpp + * @brief Implementation of the SIB importer class. + * + * The Nevercenter Silo SIB format is undocumented. + * All details here have been reverse engineered from + * studying the binary files output by Silo. + * + * Nevertheless, this implementation is reasonably complete. + */ + +#ifndef ASSIMP_BUILD_NO_SIB_IMPORTER + +// internal headers +#include "SIBImporter.h" +#include <assimp/ByteSwapper.h> +#include <assimp/StreamReader.h> +#include <assimp/TinyFormatter.h> +#ifdef ASSIMP_USE_HUNTER +#include <utf8.h> +#else +#include "../contrib/utf8cpp/source/utf8.h" +#endif +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/StringUtils.h> + +#include <map> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Silo SIB Importer", + "Richard Mitton (http://www.codersnotes.com/about)", + "", + "Does not apply subdivision.", + aiImporterFlags_SupportBinaryFlavour, + 0, 0, + 0, 0, + "sib" +}; + +struct SIBChunk { + uint32_t Tag; + uint32_t Size; +} PACK_STRUCT; + +enum { + POS, + NRM, + UV, + N +}; + +typedef std::pair<uint32_t, uint32_t> SIBPair; + +struct SIBEdge { + uint32_t faceA, faceB; + bool creased; +}; + +struct SIBMesh { + aiMatrix4x4 axis; + uint32_t numPts; + std::vector<aiVector3D> pos, nrm, uv; + std::vector<uint32_t> idx; + std::vector<uint32_t> faceStart; + std::vector<uint32_t> mtls; + std::vector<SIBEdge> edges; + std::map<SIBPair, uint32_t> edgeMap; +}; + +struct SIBObject { + aiString name; + aiMatrix4x4 axis; + size_t meshIdx, meshCount; +}; + +struct SIB { + std::vector<aiMaterial *> mtls; + std::vector<aiMesh *> meshes; + std::vector<aiLight *> lights; + std::vector<SIBObject> objs, insts; +}; + +// ------------------------------------------------------------------------------------------------ +static SIBEdge &GetEdge(SIBMesh *mesh, uint32_t posA, uint32_t posB) { + SIBPair pair = (posA < posB) ? SIBPair(posA, posB) : SIBPair(posB, posA); + std::map<SIBPair, uint32_t>::iterator it = mesh->edgeMap.find(pair); + if (it != mesh->edgeMap.end()) + return mesh->edges[it->second]; + + SIBEdge edge; + edge.creased = false; + edge.faceA = edge.faceB = 0xffffffff; + mesh->edgeMap[pair] = static_cast<uint32_t>(mesh->edges.size()); + mesh->edges.push_back(edge); + return mesh->edges.back(); +} + +// ------------------------------------------------------------------------------------------------ +// Helpers for reading chunked data. + +#define TAG(A, B, C, D) ((A << 24) | (B << 16) | (C << 8) | D) + +static SIBChunk ReadChunk(StreamReaderLE *stream) { + SIBChunk chunk; + chunk.Tag = stream->GetU4(); + chunk.Size = stream->GetU4(); + if (chunk.Size > stream->GetRemainingSizeToLimit()) + ASSIMP_LOG_ERROR("SIB: Chunk overflow"); + ByteSwap::Swap4(&chunk.Tag); + return chunk; +} + +static aiColor3D ReadColor(StreamReaderLE *stream) { + float r = stream->GetF4(); + float g = stream->GetF4(); + float b = stream->GetF4(); + stream->GetU4(); // Colors have an unused(?) 4th component. + return aiColor3D(r, g, b); +} + +static void UnknownChunk(StreamReaderLE * /*stream*/, const SIBChunk &chunk) { + char temp[4] = { + static_cast<char>((chunk.Tag >> 24) & 0xff), + static_cast<char>((chunk.Tag >> 16) & 0xff), + static_cast<char>((chunk.Tag >> 8) & 0xff), + static_cast<char>(chunk.Tag & 0xff) + }; + + ASSIMP_LOG_WARN("SIB: Skipping unknown '", ai_str_toprintable(temp, 4), "' chunk."); +} + +// Reads a UTF-16LE string and returns it at UTF-8. +static aiString ReadString(StreamReaderLE *stream, uint32_t numWChars) { + if (nullptr == stream || 0 == numWChars) { + return aiString(); + } + + // Allocate buffers (max expansion is 1 byte -> 4 bytes for UTF-8) + std::vector<unsigned char> str; + str.reserve(numWChars * 4 + 1); + uint16_t *temp = new uint16_t[numWChars]; + for (uint32_t n = 0; n < numWChars; ++n) { + temp[n] = stream->GetU2(); + } + + // Convert it and NUL-terminate. + const uint16_t *start(temp), *end(temp + numWChars); + utf8::utf16to8(start, end, back_inserter(str)); + str[str.size() - 1] = '\0'; + + // Return the final string. + aiString result = aiString((const char *)&str[0]); + delete[] temp; + + return result; +} + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +SIBImporter::SIBImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +SIBImporter::~SIBImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool SIBImporter::CanRead(const std::string &filename, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const { + return SimpleExtensionCheck(filename, "sib"); +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc *SIBImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +static void ReadVerts(SIBMesh *mesh, StreamReaderLE *stream, uint32_t count) { + if (nullptr == mesh || nullptr == stream) { + return; + } + + mesh->pos.resize(count); + for (uint32_t n = 0; n < count; ++n) { + mesh->pos[n].x = stream->GetF4(); + mesh->pos[n].y = stream->GetF4(); + mesh->pos[n].z = stream->GetF4(); + } +} + +// ------------------------------------------------------------------------------------------------ +static void ReadFaces(SIBMesh *mesh, StreamReaderLE *stream) { + uint32_t ptIdx = 0; + while (stream->GetRemainingSizeToLimit() > 0) { + uint32_t numPoints = stream->GetU4(); + + // Store room for the N index channels, plus the point count. + size_t pos = mesh->idx.size() + 1; + mesh->idx.resize(pos + numPoints * N); + mesh->idx[pos - 1] = numPoints; + uint32_t *idx = &mesh->idx[pos]; + + mesh->faceStart.push_back(static_cast<uint32_t>(pos - 1)); + mesh->mtls.push_back(0); + + // Read all the position data. + // UV/normals will be supplied later. + // Positions are supplied indexed already, so we preserve that + // mapping. UVs are supplied uniquely, so we allocate unique indices. + for (uint32_t n = 0; n < numPoints; n++, idx += N, ptIdx++) { + uint32_t p = stream->GetU4(); + if (p >= mesh->pos.size()) + throw DeadlyImportError("Vertex index is out of range."); + idx[POS] = p; + idx[NRM] = ptIdx; + idx[UV] = ptIdx; + } + } + + // Allocate data channels for normals/UVs. + mesh->nrm.resize(ptIdx, aiVector3D(0, 0, 0)); + mesh->uv.resize(ptIdx, aiVector3D(0, 0, 0)); + + mesh->numPts = ptIdx; +} + +// ------------------------------------------------------------------------------------------------ +static void ReadUVs(SIBMesh *mesh, StreamReaderLE *stream) { + while (stream->GetRemainingSizeToLimit() > 0) { + uint32_t faceIdx = stream->GetU4(); + uint32_t numPoints = stream->GetU4(); + + if (faceIdx >= mesh->faceStart.size()) + throw DeadlyImportError("Invalid face index."); + + uint32_t pos = mesh->faceStart[faceIdx]; + uint32_t *idx = &mesh->idx[pos + 1]; + + for (uint32_t n = 0; n < numPoints; n++, idx += N) { + uint32_t id = idx[UV]; + mesh->uv[id].x = stream->GetF4(); + mesh->uv[id].y = stream->GetF4(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +static void ReadMtls(SIBMesh *mesh, StreamReaderLE *stream) { + // Material assignments are stored run-length encoded. + // Also, we add 1 to each material so that we can use mtl #0 + // as the default material. + uint32_t prevFace = stream->GetU4(); + uint32_t prevMtl = stream->GetU4() + 1; + while (stream->GetRemainingSizeToLimit() > 0) { + uint32_t face = stream->GetU4(); + uint32_t mtl = stream->GetU4() + 1; + while (prevFace < face) { + if (prevFace >= mesh->mtls.size()) + throw DeadlyImportError("Invalid face index."); + mesh->mtls[prevFace++] = prevMtl; + } + + prevFace = face; + prevMtl = mtl; + } + + while (prevFace < mesh->mtls.size()) + mesh->mtls[prevFace++] = prevMtl; +} + +// ------------------------------------------------------------------------------------------------ +static void ReadAxis(aiMatrix4x4 &axis, StreamReaderLE *stream) { + axis.a4 = stream->GetF4(); + axis.b4 = stream->GetF4(); + axis.c4 = stream->GetF4(); + axis.d4 = 1; + axis.a1 = stream->GetF4(); + axis.b1 = stream->GetF4(); + axis.c1 = stream->GetF4(); + axis.d1 = 0; + axis.a2 = stream->GetF4(); + axis.b2 = stream->GetF4(); + axis.c2 = stream->GetF4(); + axis.d2 = 0; + axis.a3 = stream->GetF4(); + axis.b3 = stream->GetF4(); + axis.c3 = stream->GetF4(); + axis.d3 = 0; +} + +// ------------------------------------------------------------------------------------------------ +static void ReadEdges(SIBMesh *mesh, StreamReaderLE *stream) { + while (stream->GetRemainingSizeToLimit() > 0) { + uint32_t posA = stream->GetU4(); + uint32_t posB = stream->GetU4(); + GetEdge(mesh, posA, posB); + } +} + +// ------------------------------------------------------------------------------------------------ +static void ReadCreases(SIBMesh *mesh, StreamReaderLE *stream) { + while (stream->GetRemainingSizeToLimit() > 0) { + uint32_t edge = stream->GetU4(); + if (edge >= mesh->edges.size()) + throw DeadlyImportError("SIB: Invalid edge index."); + mesh->edges[edge].creased = true; + } +} + +// ------------------------------------------------------------------------------------------------ +static void ConnectFaces(SIBMesh *mesh) { + // Find faces connected to each edge. + size_t numFaces = mesh->faceStart.size(); + for (size_t faceIdx = 0; faceIdx < numFaces; faceIdx++) { + uint32_t *idx = &mesh->idx[mesh->faceStart[faceIdx]]; + uint32_t numPoints = *idx++; + uint32_t prev = idx[(numPoints - 1) * N + POS]; + + for (uint32_t i = 0; i < numPoints; i++, idx += N) { + uint32_t next = idx[POS]; + + // Find this edge. + SIBEdge &edge = GetEdge(mesh, prev, next); + + // Link this face onto it. + // This gives potentially undesirable normals when used + // with non-2-manifold surfaces, but then so does Silo to begin with. + if (edge.faceA == 0xffffffff) + edge.faceA = static_cast<uint32_t>(faceIdx); + else if (edge.faceB == 0xffffffff) + edge.faceB = static_cast<uint32_t>(faceIdx); + + prev = next; + } + } +} + +// ------------------------------------------------------------------------------------------------ +static aiVector3D CalculateVertexNormal(SIBMesh *mesh, uint32_t faceIdx, uint32_t pos, + const std::vector<aiVector3D> &faceNormals) { + // Creased edges complicate this. We need to find the start/end range of the + // ring of faces that touch this position. + // We do this in two passes. The first pass is to find the end of the range, + // the second is to work backwards to the start and calculate the final normal. + aiVector3D vtxNormal; + for (int pass = 0; pass < 2; pass++) { + vtxNormal = aiVector3D(0, 0, 0); + uint32_t startFaceIdx = faceIdx; + uint32_t prevFaceIdx = faceIdx; + + // Process each connected face. + while (true) { + // Accumulate the face normal. + vtxNormal += faceNormals[faceIdx]; + + uint32_t nextFaceIdx = 0xffffffff; + + // Move to the next edge sharing this position. + uint32_t *idx = &mesh->idx[mesh->faceStart[faceIdx]]; + uint32_t numPoints = *idx++; + uint32_t posA = idx[(numPoints - 1) * N + POS]; + for (uint32_t n = 0; n < numPoints; n++, idx += N) { + uint32_t posB = idx[POS]; + + // Test if this edge shares our target position. + if (posA == pos || posB == pos) { + SIBEdge &edge = GetEdge(mesh, posA, posB); + + // Non-manifold meshes can produce faces which share + // positions but have no edge entry, so check it. + if (edge.faceA == faceIdx || edge.faceB == faceIdx) { + // Move to whichever side we didn't just come from. + if (!edge.creased) { + if (edge.faceA != prevFaceIdx && edge.faceA != faceIdx && edge.faceA != 0xffffffff) + nextFaceIdx = edge.faceA; + else if (edge.faceB != prevFaceIdx && edge.faceB != faceIdx && edge.faceB != 0xffffffff) + nextFaceIdx = edge.faceB; + } + } + } + + posA = posB; + } + + // Stop once we hit either an creased/unconnected edge, or we + // wrapped around and hit our start point. + if (nextFaceIdx == 0xffffffff || nextFaceIdx == startFaceIdx) + break; + + prevFaceIdx = faceIdx; + faceIdx = nextFaceIdx; + } + } + + // Normalize it. + float len = vtxNormal.Length(); + if (len > 0.000000001f) + vtxNormal /= len; + return vtxNormal; +} + +// ------------------------------------------------------------------------------------------------ +static void CalculateNormals(SIBMesh *mesh) { + size_t numFaces = mesh->faceStart.size(); + + // Calculate face normals. + std::vector<aiVector3D> faceNormals(numFaces); + for (size_t faceIdx = 0; faceIdx < numFaces; faceIdx++) { + uint32_t *idx = &mesh->idx[mesh->faceStart[faceIdx]]; + uint32_t numPoints = *idx++; + + aiVector3D faceNormal(0, 0, 0); + + uint32_t *prev = &idx[(numPoints - 1) * N]; + + for (uint32_t i = 0; i < numPoints; i++) { + uint32_t *next = &idx[i * N]; + + faceNormal += mesh->pos[prev[POS]] ^ mesh->pos[next[POS]]; + prev = next; + } + + faceNormals[faceIdx] = faceNormal; + } + + // Calculate vertex normals. + for (size_t faceIdx = 0; faceIdx < numFaces; faceIdx++) { + uint32_t *idx = &mesh->idx[mesh->faceStart[faceIdx]]; + uint32_t numPoints = *idx++; + + for (uint32_t i = 0; i < numPoints; i++) { + uint32_t pos = idx[i * N + POS]; + uint32_t nrm = idx[i * N + NRM]; + aiVector3D vtxNorm = CalculateVertexNormal(mesh, static_cast<uint32_t>(faceIdx), pos, faceNormals); + mesh->nrm[nrm] = vtxNorm; + } + } +} + +// ------------------------------------------------------------------------------------------------ +struct TempMesh { + std::vector<aiVector3D> vtx; + std::vector<aiVector3D> nrm; + std::vector<aiVector3D> uv; + std::vector<aiFace> faces; +}; + +static void ReadShape(SIB *sib, StreamReaderLE *stream) { + SIBMesh smesh; + aiString name; + + while (stream->GetRemainingSizeToLimit() >= sizeof(SIBChunk)) { + SIBChunk chunk = ReadChunk(stream); + unsigned oldLimit = stream->SetReadLimit(stream->GetCurrentPos() + chunk.Size); + + switch (chunk.Tag) { + case TAG('M', 'I', 'R', 'P'): break; // mirror plane maybe? + case TAG('I', 'M', 'R', 'P'): break; // instance mirror? (not supported here yet) + case TAG('D', 'I', 'N', 'F'): break; // display info, not needed + case TAG('P', 'I', 'N', 'F'): break; // ? + case TAG('V', 'M', 'I', 'R'): break; // ? + case TAG('F', 'M', 'I', 'R'): break; // ? + case TAG('T', 'X', 'S', 'M'): break; // ? + case TAG('F', 'A', 'H', 'S'): break; // ? + case TAG('V', 'R', 'T', 'S'): ReadVerts(&smesh, stream, chunk.Size / 12); break; + case TAG('F', 'A', 'C', 'S'): ReadFaces(&smesh, stream); break; + case TAG('F', 'T', 'V', 'S'): ReadUVs(&smesh, stream); break; + case TAG('S', 'N', 'A', 'M'): name = ReadString(stream, chunk.Size / 2); break; + case TAG('F', 'A', 'M', 'A'): ReadMtls(&smesh, stream); break; + case TAG('A', 'X', 'I', 'S'): ReadAxis(smesh.axis, stream); break; + case TAG('E', 'D', 'G', 'S'): ReadEdges(&smesh, stream); break; + case TAG('E', 'C', 'R', 'S'): ReadCreases(&smesh, stream); break; + default: UnknownChunk(stream, chunk); break; + } + + stream->SetCurrentPos(stream->GetReadLimit()); + stream->SetReadLimit(oldLimit); + } + + ai_assert(smesh.faceStart.size() == smesh.mtls.size()); // sanity check + + // Silo doesn't store any normals in the file - we need to compute + // them ourselves. We can't let AssImp handle it as AssImp doesn't + // know about our creased edges. + ConnectFaces(&smesh); + CalculateNormals(&smesh); + + // Construct the transforms. + aiMatrix4x4 worldToLocal = smesh.axis; + worldToLocal.Inverse(); + aiMatrix4x4 worldToLocalN = worldToLocal; + worldToLocalN.a4 = worldToLocalN.b4 = worldToLocalN.c4 = 0.0f; + worldToLocalN.Inverse().Transpose(); + + // Allocate final mesh data. + // We'll allocate one mesh for each material. (we'll strip unused ones after) + std::vector<TempMesh> meshes(sib->mtls.size()); + + // Un-index the source data and apply to each vertex. + for (unsigned fi = 0; fi < smesh.faceStart.size(); fi++) { + uint32_t start = smesh.faceStart[fi]; + uint32_t mtl = smesh.mtls[fi]; + uint32_t *idx = &smesh.idx[start]; + + if (mtl >= meshes.size()) { + ASSIMP_LOG_ERROR("SIB: Face material index is invalid."); + mtl = 0; + } + + TempMesh &dest = meshes[mtl]; + + aiFace face; + face.mNumIndices = *idx++; + face.mIndices = new unsigned[face.mNumIndices]; + for (unsigned pt = 0; pt < face.mNumIndices; pt++, idx += N) { + size_t vtxIdx = dest.vtx.size(); + face.mIndices[pt] = static_cast<unsigned int>(vtxIdx); + + // De-index it. We don't need to validate here as + // we did it when creating the data. + aiVector3D pos = smesh.pos[idx[POS]]; + aiVector3D nrm = smesh.nrm[idx[NRM]]; + aiVector3D uv = smesh.uv[idx[UV]]; + + // The verts are supplied in world-space, so let's + // transform them back into the local space of this mesh: + pos = worldToLocal * pos; + nrm = worldToLocalN * nrm; + + dest.vtx.push_back(pos); + dest.nrm.push_back(nrm); + dest.uv.push_back(uv); + } + dest.faces.push_back(face); + } + + SIBObject obj; + obj.name = name; + obj.axis = smesh.axis; + obj.meshIdx = sib->meshes.size(); + + // Now that we know the size of everything, + // we can build the final one-material-per-mesh data. + for (size_t n = 0; n < meshes.size(); n++) { + TempMesh &src = meshes[n]; + if (src.faces.empty()) + continue; + + aiMesh *mesh = new aiMesh; + mesh->mName = name; + mesh->mNumFaces = static_cast<unsigned int>(src.faces.size()); + mesh->mFaces = new aiFace[mesh->mNumFaces]; + mesh->mNumVertices = static_cast<unsigned int>(src.vtx.size()); + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + mesh->mNormals = new aiVector3D[mesh->mNumVertices]; + mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; + mesh->mNumUVComponents[0] = 2; + mesh->mMaterialIndex = static_cast<unsigned int>(n); + + for (unsigned i = 0; i < mesh->mNumVertices; i++) { + mesh->mVertices[i] = src.vtx[i]; + mesh->mNormals[i] = src.nrm[i]; + mesh->mTextureCoords[0][i] = src.uv[i]; + } + for (unsigned i = 0; i < mesh->mNumFaces; i++) { + mesh->mFaces[i] = src.faces[i]; + } + + sib->meshes.push_back(mesh); + } + + obj.meshCount = sib->meshes.size() - obj.meshIdx; + sib->objs.push_back(obj); +} + +// ------------------------------------------------------------------------------------------------ +static void ReadMaterial(SIB *sib, StreamReaderLE *stream) { + aiColor3D diff = ReadColor(stream); + aiColor3D ambi = ReadColor(stream); + aiColor3D spec = ReadColor(stream); + aiColor3D emis = ReadColor(stream); + float shiny = (float)stream->GetU4(); + + uint32_t nameLen = stream->GetU4(); + aiString name = ReadString(stream, nameLen / 2); + uint32_t texLen = stream->GetU4(); + aiString tex = ReadString(stream, texLen / 2); + + aiMaterial *mtl = new aiMaterial(); + mtl->AddProperty(&diff, 1, AI_MATKEY_COLOR_DIFFUSE); + mtl->AddProperty(&ambi, 1, AI_MATKEY_COLOR_AMBIENT); + mtl->AddProperty(&spec, 1, AI_MATKEY_COLOR_SPECULAR); + mtl->AddProperty(&emis, 1, AI_MATKEY_COLOR_EMISSIVE); + mtl->AddProperty(&shiny, 1, AI_MATKEY_SHININESS); + mtl->AddProperty(&name, AI_MATKEY_NAME); + if (tex.length > 0) { + mtl->AddProperty(&tex, AI_MATKEY_TEXTURE_DIFFUSE(0)); + mtl->AddProperty(&tex, AI_MATKEY_TEXTURE_AMBIENT(0)); + } + + sib->mtls.push_back(mtl); +} + +// ------------------------------------------------------------------------------------------------ +static void ReadLightInfo(aiLight *light, StreamReaderLE *stream) { + uint32_t type = stream->GetU4(); + switch (type) { + case 0: light->mType = aiLightSource_POINT; break; + case 1: light->mType = aiLightSource_SPOT; break; + case 2: light->mType = aiLightSource_DIRECTIONAL; break; + default: light->mType = aiLightSource_UNDEFINED; break; + } + + light->mPosition.x = stream->GetF4(); + light->mPosition.y = stream->GetF4(); + light->mPosition.z = stream->GetF4(); + light->mDirection.x = stream->GetF4(); + light->mDirection.y = stream->GetF4(); + light->mDirection.z = stream->GetF4(); + light->mColorDiffuse = ReadColor(stream); + light->mColorAmbient = ReadColor(stream); + light->mColorSpecular = ReadColor(stream); + ai_real spotExponent = stream->GetF4(); + ai_real spotCutoff = stream->GetF4(); + light->mAttenuationConstant = stream->GetF4(); + light->mAttenuationLinear = stream->GetF4(); + light->mAttenuationQuadratic = stream->GetF4(); + + // Silo uses the OpenGL default lighting model for it's + // spot cutoff/exponent. AssImp unfortunately, does not. + // Let's try and approximate it by solving for the + // 99% and 1% percentiles. + // OpenGL: I = cos(angle)^E + // Solving: angle = acos(I^(1/E)) + ai_real E = ai_real(1.0) / std::max(spotExponent, (ai_real)0.00001); + ai_real inner = std::acos(std::pow((ai_real)0.99, E)); + ai_real outer = std::acos(std::pow((ai_real)0.01, E)); + + // Apply the cutoff. + outer = std::min(outer, AI_DEG_TO_RAD(spotCutoff)); + + light->mAngleInnerCone = std::min(inner, outer); + light->mAngleOuterCone = outer; +} + +static void ReadLight(SIB *sib, StreamReaderLE *stream) { + aiLight *light = new aiLight(); + + while (stream->GetRemainingSizeToLimit() >= sizeof(SIBChunk)) { + SIBChunk chunk = ReadChunk(stream); + unsigned oldLimit = stream->SetReadLimit(stream->GetCurrentPos() + chunk.Size); + + switch (chunk.Tag) { + case TAG('L', 'N', 'F', 'O'): ReadLightInfo(light, stream); break; + case TAG('S', 'N', 'A', 'M'): light->mName = ReadString(stream, chunk.Size / 2); break; + default: UnknownChunk(stream, chunk); break; + } + + stream->SetCurrentPos(stream->GetReadLimit()); + stream->SetReadLimit(oldLimit); + } + + sib->lights.push_back(light); +} + +// ------------------------------------------------------------------------------------------------ +static void ReadScale(aiMatrix4x4 &axis, StreamReaderLE *stream) { + aiMatrix4x4 scale; + scale.a1 = stream->GetF4(); + scale.b1 = stream->GetF4(); + scale.c1 = stream->GetF4(); + scale.d1 = stream->GetF4(); + scale.a2 = stream->GetF4(); + scale.b2 = stream->GetF4(); + scale.c2 = stream->GetF4(); + scale.d2 = stream->GetF4(); + scale.a3 = stream->GetF4(); + scale.b3 = stream->GetF4(); + scale.c3 = stream->GetF4(); + scale.d3 = stream->GetF4(); + scale.a4 = stream->GetF4(); + scale.b4 = stream->GetF4(); + scale.c4 = stream->GetF4(); + scale.d4 = stream->GetF4(); + + axis = axis * scale; +} + +static void ReadInstance(SIB *sib, StreamReaderLE *stream) { + SIBObject inst; + uint32_t shapeIndex = 0; + + while (stream->GetRemainingSizeToLimit() >= sizeof(SIBChunk)) { + SIBChunk chunk = ReadChunk(stream); + unsigned oldLimit = stream->SetReadLimit(stream->GetCurrentPos() + chunk.Size); + + switch (chunk.Tag) { + case TAG('D', 'I', 'N', 'F'): break; // display info, not needed + case TAG('P', 'I', 'N', 'F'): break; // ? + case TAG('A', 'X', 'I', 'S'): ReadAxis(inst.axis, stream); break; + case TAG('I', 'N', 'S', 'I'): shapeIndex = stream->GetU4(); break; + case TAG('S', 'M', 'T', 'X'): ReadScale(inst.axis, stream); break; + case TAG('S', 'N', 'A', 'M'): inst.name = ReadString(stream, chunk.Size / 2); break; + default: UnknownChunk(stream, chunk); break; + } + + stream->SetCurrentPos(stream->GetReadLimit()); + stream->SetReadLimit(oldLimit); + } + + if (shapeIndex >= sib->objs.size()) { + throw DeadlyImportError("SIB: Invalid shape index."); + } + + const SIBObject &src = sib->objs[shapeIndex]; + inst.meshIdx = src.meshIdx; + inst.meshCount = src.meshCount; + sib->insts.push_back(inst); +} + +// ------------------------------------------------------------------------------------------------ +static void CheckVersion(StreamReaderLE *stream) { + uint32_t version = stream->GetU4(); + if (version < 1 || version > 2) { + throw DeadlyImportError("SIB: Unsupported file version."); + } +} + +static void ReadScene(SIB *sib, StreamReaderLE *stream) { + // Parse each chunk in turn. + while (stream->GetRemainingSizeToLimit() >= sizeof(SIBChunk)) { + SIBChunk chunk = ReadChunk(stream); + unsigned oldLimit = stream->SetReadLimit(stream->GetCurrentPos() + chunk.Size); + + switch (chunk.Tag) { + case TAG('H', 'E', 'A', 'D'): CheckVersion(stream); break; + case TAG('S', 'H', 'A', 'P'): ReadShape(sib, stream); break; + case TAG('G', 'R', 'P', 'S'): break; // group assignment, we don't import this + case TAG('T', 'E', 'X', 'P'): break; // ? + case TAG('I', 'N', 'S', 'T'): ReadInstance(sib, stream); break; + case TAG('M', 'A', 'T', 'R'): ReadMaterial(sib, stream); break; + case TAG('L', 'G', 'H', 'T'): ReadLight(sib, stream); break; + default: UnknownChunk(stream, chunk); break; + } + + stream->SetCurrentPos(stream->GetReadLimit()); + stream->SetReadLimit(oldLimit); + } +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void SIBImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, IOSystem *pIOHandler) { + + auto file = pIOHandler->Open(pFile, "rb"); + if (!file) + throw DeadlyImportError("SIB: Could not open ", pFile); + + StreamReaderLE stream(file); + + // We should have at least one chunk + if (stream.GetRemainingSize() < 16) + throw DeadlyImportError("SIB file is either empty or corrupt: ", pFile); + + SIB sib; + + // Default material. + aiMaterial *defmtl = new aiMaterial; + aiString defname = aiString(AI_DEFAULT_MATERIAL_NAME); + defmtl->AddProperty(&defname, AI_MATKEY_NAME); + sib.mtls.push_back(defmtl); + + // Read it all. + ReadScene(&sib, &stream); + + // Join the instances and objects together. + size_t firstInst = sib.objs.size(); + sib.objs.insert(sib.objs.end(), sib.insts.begin(), sib.insts.end()); + sib.insts.clear(); + + // Transfer to the aiScene. + pScene->mNumMaterials = static_cast<unsigned int>(sib.mtls.size()); + pScene->mNumMeshes = static_cast<unsigned int>(sib.meshes.size()); + pScene->mNumLights = static_cast<unsigned int>(sib.lights.size()); + pScene->mMaterials = pScene->mNumMaterials ? new aiMaterial *[pScene->mNumMaterials] : nullptr; + pScene->mMeshes = pScene->mNumMeshes ? new aiMesh *[pScene->mNumMeshes] : nullptr; + pScene->mLights = pScene->mNumLights ? new aiLight *[pScene->mNumLights] : nullptr; + if (pScene->mNumMaterials) + memcpy(pScene->mMaterials, &sib.mtls[0], sizeof(aiMaterial *) * pScene->mNumMaterials); + if (pScene->mNumMeshes) + memcpy(pScene->mMeshes, &sib.meshes[0], sizeof(aiMesh *) * pScene->mNumMeshes); + if (pScene->mNumLights) + memcpy(pScene->mLights, &sib.lights[0], sizeof(aiLight *) * pScene->mNumLights); + + // Construct the root node. + size_t childIdx = 0; + aiNode *root = new aiNode(); + root->mName.Set("<SIBRoot>"); + root->mNumChildren = static_cast<unsigned int>(sib.objs.size() + sib.lights.size()); + root->mChildren = root->mNumChildren ? new aiNode *[root->mNumChildren] : nullptr; + pScene->mRootNode = root; + + // Add nodes for each object. + for (size_t n = 0; n < sib.objs.size(); n++) { + ai_assert(root->mChildren); + SIBObject &obj = sib.objs[n]; + aiNode *node = new aiNode; + root->mChildren[childIdx++] = node; + node->mName = obj.name; + node->mParent = root; + node->mTransformation = obj.axis; + + node->mNumMeshes = static_cast<unsigned int>(obj.meshCount); + node->mMeshes = node->mNumMeshes ? new unsigned[node->mNumMeshes] : nullptr; + for (unsigned i = 0; i < node->mNumMeshes; i++) + node->mMeshes[i] = static_cast<unsigned int>(obj.meshIdx + i); + + // Mark instanced objects as being so. + if (n >= firstInst) { + node->mMetaData = aiMetadata::Alloc(1); + node->mMetaData->Set(0, "IsInstance", true); + } + } + + // Add nodes for each light. + // (no transformation as the light is already in world space) + for (size_t n = 0; n < sib.lights.size(); n++) { + ai_assert(root->mChildren); + aiLight *light = sib.lights[n]; + if (nullptr != light) { + aiNode *node = new aiNode; + root->mChildren[childIdx++] = node; + node->mName = light->mName; + node->mParent = root; + } + } +} + +#endif // !! ASSIMP_BUILD_NO_SIB_IMPORTER diff --git a/libs/assimp/code/AssetLib/SIB/SIBImporter.h b/libs/assimp/code/AssetLib/SIB/SIBImporter.h new file mode 100644 index 0000000..2b197dd --- /dev/null +++ b/libs/assimp/code/AssetLib/SIB/SIBImporter.h @@ -0,0 +1,108 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 SIBImporter.h + * @brief Declaration of the SIB importer class. + */ +#pragma once +#ifndef AI_SIBIMPORTER_H_INCLUDED +#define AI_SIBIMPORTER_H_INCLUDED + +#include <assimp/BaseImporter.h> +#include <assimp/types.h> +#include <vector> + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** Importer class for the Nevercenter Silo SIB scene format +*/ +class ASSIMP_API SIBImporter : public BaseImporter { +public: + SIBImporter(); + ~SIBImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc* GetInfo () const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler) override; + +private: + struct MeshInformation { + explicit MeshInformation(const std::string& _name) : name(_name) { + vertices.reserve(100); + colors.reserve(100); + } + + std::string name; + std::vector<aiVector3D> vertices; + std::vector<aiColor4D> colors; + }; + + struct GroupInformation { + explicit GroupInformation(const std::string& _name) : name(_name) { + meshes.reserve(10); + } + + std::string name; + std::vector<MeshInformation> meshes; + }; +}; + +} // end of namespace Assimp + +#endif // AI_SIBIMPORTER_H_INCLUDED diff --git a/libs/assimp/code/AssetLib/SMD/SMDLoader.cpp b/libs/assimp/code/AssetLib/SMD/SMDLoader.cpp new file mode 100644 index 0000000..46fd968 --- /dev/null +++ b/libs/assimp/code/AssetLib/SMD/SMDLoader.cpp @@ -0,0 +1,1098 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 SMDLoader.cpp + * @brief Implementation of the SMD importer class + */ + + +#ifndef ASSIMP_BUILD_NO_SMD_IMPORTER + +#include <assimp/fast_atof.h> +#include <assimp/SkeletonMeshBuilder.h> +#include <assimp/Importer.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/importerdesc.h> +#include <memory> +#include <assimp/DefaultIOSystem.h> +#include <tuple> + +// internal headers +#include "SMDLoader.h" + +#ifndef _MSC_VER +#define strtok_s strtok_r +#endif + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Valve SMD Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "smd vta" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +SMDImporter::SMDImporter() : + configFrameID(), + mBuffer(), + pScene( nullptr ), + iFileSize( 0 ), + iSmallestFrame( INT_MAX ), + dLengthOfAnim( 0.0 ), + bHasUVs(false ), + iLineNumber((unsigned int)-1) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +SMDImporter::~SMDImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool SMDImporter::CanRead( const std::string& filename, IOSystem* /*pIOHandler*/, bool) const { + return SimpleExtensionCheck(filename, "smd", "vta"); +} + +// ------------------------------------------------------------------------------------------------ +// Get a list of all supported file extensions +const aiImporterDesc* SMDImporter::GetInfo () const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties +void SMDImporter::SetupProperties(const Importer* pImp) { + // The + // AI_CONFIG_IMPORT_SMD_KEYFRAME option overrides the + // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option. + configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_SMD_KEYFRAME,-1); + if(static_cast<unsigned int>(-1) == configFrameID) { + configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0); + } + + bLoadAnimationList = pImp->GetPropertyBool(AI_CONFIG_IMPORT_SMD_LOAD_ANIMATION_LIST, true); + noSkeletonMesh = pImp->GetPropertyBool(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, false); +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void SMDImporter::InternReadFile( const std::string& pFile, aiScene* scene, IOSystem* pIOHandler) { + this->pScene = scene; + ReadSmd(pFile, pIOHandler); + + // If there are no triangles it seems to be an animation SMD, + // containing only the animation skeleton. + if (asTriangles.empty()) { + if (asBones.empty()) { + throw DeadlyImportError("SMD: No triangles and no bones have " + "been found in the file. This file seems to be invalid."); + } + + // Set the flag in the scene structure which indicates + // that there is nothing than an animation skeleton + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } + + if (!asBones.empty()) { + // Check whether all bones have been initialized + for (std::vector<SMD::Bone>::const_iterator + i = asBones.begin(); + i != asBones.end();++i) { + if (!(*i).mName.length()) { + ASSIMP_LOG_WARN("SMD: Not all bones have been initialized"); + break; + } + } + + // now fix invalid time values and make sure the animation starts at frame 0 + FixTimeValues(); + } + + // build output nodes (bones are added as empty dummy nodes) + CreateOutputNodes(); + + if (!(pScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE)) { + // create output meshes + CreateOutputMeshes(); + + // build an output material list + CreateOutputMaterials(); + + // use root node that renders all meshes + pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; + pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + pScene->mRootNode->mMeshes[i] = i; + } + } + + // build the output animation + CreateOutputAnimations(pFile, pIOHandler); + + if ((pScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) && !noSkeletonMesh) { + SkeletonMeshBuilder skeleton(pScene); + } +} + +// ------------------------------------------------------------------------------------------------ +// Write an error message with line number to the log file +void SMDImporter::LogErrorNoThrow(const char* msg) { + const size_t _BufferSize = 1024; + char szTemp[_BufferSize]; + ai_snprintf(szTemp,_BufferSize,"Line %u: %s",iLineNumber,msg); + DefaultLogger::get()->error(szTemp); +} + +// ------------------------------------------------------------------------------------------------ +// Write a warning with line number to the log file +void SMDImporter::LogWarning(const char* msg) { + const size_t _BufferSize = 1024; + char szTemp[_BufferSize]; + ai_assert(strlen(msg) < 1000); + ai_snprintf(szTemp,_BufferSize,"Line %u: %s",iLineNumber,msg); + ASSIMP_LOG_WARN(szTemp); +} + +// ------------------------------------------------------------------------------------------------ +// Fix invalid time values in the file +void SMDImporter::FixTimeValues() { + double dDelta = (double)iSmallestFrame; + double dMax = 0.0f; + for (std::vector<SMD::Bone>::iterator + iBone = asBones.begin(); + iBone != asBones.end();++iBone) { + for (std::vector<SMD::Bone::Animation::MatrixKey>::iterator + iKey = (*iBone).sAnim.asKeys.begin(); + iKey != (*iBone).sAnim.asKeys.end();++iKey) { + (*iKey).dTime -= dDelta; + dMax = std::max(dMax, (*iKey).dTime); + } + } + dLengthOfAnim = dMax; +} + +// ------------------------------------------------------------------------------------------------ +// create output meshes +void SMDImporter::CreateOutputMeshes() { + if (aszTextures.empty()) { + aszTextures.push_back(std::string()); + } + + // we need to sort all faces by their material index + // in opposition to other loaders we can be sure that each + // material is at least used once. + pScene->mNumMeshes = (unsigned int) aszTextures.size(); + pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; + + typedef std::vector<unsigned int> FaceList; + FaceList* aaiFaces = new FaceList[pScene->mNumMeshes]; + + // approximate the space that will be required + unsigned int iNum = (unsigned int)asTriangles.size() / pScene->mNumMeshes; + iNum += iNum >> 1; + for (unsigned int i = 0; i < pScene->mNumMeshes;++i) { + aaiFaces[i].reserve(iNum); + } + + // collect all faces + iNum = 0; + for (std::vector<SMD::Face>::const_iterator + iFace = asTriangles.begin(); + iFace != asTriangles.end();++iFace,++iNum) { + if (UINT_MAX == (*iFace).iTexture) { + aaiFaces[(*iFace).iTexture].push_back( 0 ); + } else if ((*iFace).iTexture >= aszTextures.size()) { + ASSIMP_LOG_INFO("[SMD/VTA] Material index overflow in face"); + aaiFaces[(*iFace).iTexture].push_back((unsigned int)aszTextures.size()-1); + } else { + aaiFaces[(*iFace).iTexture].push_back(iNum); + } + } + + // now create the output meshes + for (unsigned int i = 0; i < pScene->mNumMeshes;++i) { + aiMesh*& pcMesh = pScene->mMeshes[i] = new aiMesh(); + ai_assert(!aaiFaces[i].empty()); // should not be empty ... + + pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + pcMesh->mNumVertices = (unsigned int)aaiFaces[i].size()*3; + pcMesh->mNumFaces = (unsigned int)aaiFaces[i].size(); + pcMesh->mMaterialIndex = i; + + // storage for bones + typedef std::pair<unsigned int,float> TempWeightListEntry; + typedef std::vector< TempWeightListEntry > TempBoneWeightList; + + TempBoneWeightList* aaiBones = new TempBoneWeightList[asBones.size()](); + + // try to reserve enough memory without wasting too much + for (unsigned int iBone = 0; iBone < asBones.size();++iBone) { + aaiBones[iBone].reserve(pcMesh->mNumVertices/asBones.size()); + } + + // allocate storage + pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; + aiVector3D* pcNormals = pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; + aiVector3D* pcVerts = pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; + + aiVector3D* pcUVs = nullptr; + if (bHasUVs) { + pcUVs = pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNumUVComponents[0] = 2; + } + + iNum = 0; + for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces;++iFace) { + pcMesh->mFaces[iFace].mIndices = new unsigned int[3]; + pcMesh->mFaces[iFace].mNumIndices = 3; + + // fill the vertices + unsigned int iSrcFace = aaiFaces[i][iFace]; + SMD::Face& face = asTriangles[iSrcFace]; + + *pcVerts++ = face.avVertices[0].pos; + *pcVerts++ = face.avVertices[1].pos; + *pcVerts++ = face.avVertices[2].pos; + + // fill the normals + *pcNormals++ = face.avVertices[0].nor; + *pcNormals++ = face.avVertices[1].nor; + *pcNormals++ = face.avVertices[2].nor; + + // fill the texture coordinates + if (pcUVs) { + *pcUVs++ = face.avVertices[0].uv; + *pcUVs++ = face.avVertices[1].uv; + *pcUVs++ = face.avVertices[2].uv; + } + + for (unsigned int iVert = 0; iVert < 3;++iVert) { + float fSum = 0.0f; + for (unsigned int iBone = 0;iBone < face.avVertices[iVert].aiBoneLinks.size();++iBone) { + TempWeightListEntry& pairval = face.avVertices[iVert].aiBoneLinks[iBone]; + + // FIX: The second check is here just to make sure we won't + // assign more than one weight to a single vertex index + if (pairval.first >= asBones.size() || pairval.first == face.avVertices[iVert].iParentNode) { + ASSIMP_LOG_ERROR("[SMD/VTA] Bone index overflow. " + "The bone index will be ignored, the weight will be assigned " + "to the vertex' parent node"); + continue; + } + aaiBones[pairval.first].push_back(TempWeightListEntry(iNum,pairval.second)); + fSum += pairval.second; + } + // ****************************************************************** + // If the sum of all vertex weights is not 1.0 we must assign + // the rest to the vertex' parent node. Well, at least the doc says + // we should ... + // FIX: We use 0.975 as limit, floating-point inaccuracies seem to + // be very strong in some SMD exporters. Furthermore it is possible + // that the parent of a vertex is 0xffffffff (if the corresponding + // entry in the file was unreadable) + // ****************************************************************** + if (fSum < 0.975f && face.avVertices[iVert].iParentNode != UINT_MAX) { + if (face.avVertices[iVert].iParentNode >= asBones.size()) { + ASSIMP_LOG_ERROR("[SMD/VTA] Bone index overflow. " + "The index of the vertex parent bone is invalid. " + "The remaining weights will be normalized to 1.0"); + + if (fSum) { + fSum = 1 / fSum; + for (unsigned int iBone = 0;iBone < face.avVertices[iVert].aiBoneLinks.size();++iBone) { + TempWeightListEntry& pairval = face.avVertices[iVert].aiBoneLinks[iBone]; + if (pairval.first >= asBones.size()) { + continue; + } + aaiBones[pairval.first].back().second *= fSum; + } + } + } else { + aaiBones[face.avVertices[iVert].iParentNode].push_back( + TempWeightListEntry(iNum,1.0f-fSum)); + } + } + pcMesh->mFaces[iFace].mIndices[iVert] = iNum++; + } + } + + // now build all bones of the mesh + iNum = 0; + for (unsigned int iBone = 0; iBone < asBones.size();++iBone) { + if (!aaiBones[iBone].empty())++iNum; + } + + if (iNum) { + pcMesh->mNumBones = iNum; + pcMesh->mBones = new aiBone*[pcMesh->mNumBones]; + iNum = 0; + for (unsigned int iBone = 0; iBone < asBones.size();++iBone) { + if (aaiBones[iBone].empty()) { + continue; + } + aiBone*& bone = pcMesh->mBones[iNum] = new aiBone(); + + bone->mNumWeights = (unsigned int)aaiBones[iBone].size(); + bone->mWeights = new aiVertexWeight[bone->mNumWeights]; + bone->mOffsetMatrix = asBones[iBone].mOffsetMatrix; + bone->mName.Set( asBones[iBone].mName ); + + asBones[iBone].bIsUsed = true; + + for (unsigned int iWeight = 0; iWeight < bone->mNumWeights;++iWeight) { + bone->mWeights[iWeight].mVertexId = aaiBones[iBone][iWeight].first; + bone->mWeights[iWeight].mWeight = aaiBones[iBone][iWeight].second; + } + ++iNum; + } + } + delete[] aaiBones; + } + delete[] aaiFaces; +} + +// ------------------------------------------------------------------------------------------------ +// add bone child nodes +void SMDImporter::AddBoneChildren(aiNode* pcNode, uint32_t iParent) { + ai_assert( nullptr != pcNode ); + ai_assert( 0 == pcNode->mNumChildren ); + ai_assert( nullptr == pcNode->mChildren); + + // first count ... + for (unsigned int i = 0; i < asBones.size();++i) { + SMD::Bone& bone = asBones[i]; + if (bone.iParent == iParent) { + ++pcNode->mNumChildren; + } + } + + // now allocate the output array + pcNode->mChildren = new aiNode*[pcNode->mNumChildren]; + + // and fill all subnodes + unsigned int qq( 0 ); + for (unsigned int i = 0; i < asBones.size();++i) { + SMD::Bone& bone = asBones[i]; + if (bone.iParent != iParent) { + continue; + } + + aiNode* pc = pcNode->mChildren[qq++] = new aiNode(); + pc->mName.Set(bone.mName); + + // store the local transformation matrix of the bind pose + if (bone.sAnim.asKeys.size()) { + pc->mTransformation = bone.sAnim.asKeys[0].matrix; + } + + if (bone.iParent == static_cast<uint32_t>(-1)) { + bone.mOffsetMatrix = pc->mTransformation; + } else { + bone.mOffsetMatrix = asBones[bone.iParent].mOffsetMatrix * pc->mTransformation; + } + + pc->mParent = pcNode; + + // add children to this node, too + AddBoneChildren(pc,i); + } +} + +// ------------------------------------------------------------------------------------------------ +// create output nodes +void SMDImporter::CreateOutputNodes() { + pScene->mRootNode = new aiNode(); + + // now add all bones as dummy sub nodes to the graph + AddBoneChildren(pScene->mRootNode,(uint32_t)-1); + for (auto &bone : asBones) { + bone.mOffsetMatrix.Inverse(); + } + + // if we have only one bone we can even remove the root node + if (pScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE && 1 == pScene->mRootNode->mNumChildren) { + aiNode* pcOldRoot = pScene->mRootNode; + pScene->mRootNode = pcOldRoot->mChildren[0]; + pcOldRoot->mChildren[0] = nullptr; + delete pcOldRoot; + + pScene->mRootNode->mParent = nullptr; + } + else + { + ::strcpy(pScene->mRootNode->mName.data, "<SMD_root>"); + pScene->mRootNode->mName.length = 10; + } +} + +// ------------------------------------------------------------------------------------------------ +// create output animations +void SMDImporter::CreateOutputAnimations(const std::string &pFile, IOSystem* pIOHandler) { + std::vector<std::tuple<std::string, std::string>> animFileList; + + if (bLoadAnimationList) { + GetAnimationFileList(pFile, pIOHandler, animFileList); + } + int animCount = static_cast<int>( animFileList.size() + 1u ); + pScene->mNumAnimations = 1; + pScene->mAnimations = new aiAnimation*[animCount]; + memset(pScene->mAnimations, 0, sizeof(aiAnimation*)*animCount); + CreateOutputAnimation(0, ""); + + for (auto &animFile : animFileList) { + ReadSmd(std::get<1>(animFile), pIOHandler); + if (asBones.empty()) { + continue; + } + + FixTimeValues(); + CreateOutputAnimation(pScene->mNumAnimations++, std::get<0>(animFile)); + } +} + +void SMDImporter::CreateOutputAnimation(int index, const std::string &name) { + aiAnimation*& anim = pScene->mAnimations[index] = new aiAnimation(); + + if (name.length()) { + anim->mName.Set(name.c_str()); + } + anim->mDuration = dLengthOfAnim; + anim->mNumChannels = static_cast<unsigned int>( asBones.size() ); + anim->mTicksPerSecond = 25.0; // FIXME: is this correct? + + aiNodeAnim** pp = anim->mChannels = new aiNodeAnim*[anim->mNumChannels]; + + // now build valid keys + unsigned int a = 0; + for (std::vector<SMD::Bone>::const_iterator i = asBones.begin(); i != asBones.end(); ++i) { + aiNodeAnim* p = pp[a] = new aiNodeAnim(); + + // copy the name of the bone + p->mNodeName.Set(i->mName); + + p->mNumRotationKeys = (unsigned int)(*i).sAnim.asKeys.size(); + if (p->mNumRotationKeys){ + p->mNumPositionKeys = p->mNumRotationKeys; + aiVectorKey* pVecKeys = p->mPositionKeys = new aiVectorKey[p->mNumRotationKeys]; + aiQuatKey* pRotKeys = p->mRotationKeys = new aiQuatKey[p->mNumRotationKeys]; + + for (std::vector<SMD::Bone::Animation::MatrixKey>::const_iterator + qq = (*i).sAnim.asKeys.begin(); + qq != (*i).sAnim.asKeys.end(); ++qq) { + pRotKeys->mTime = pVecKeys->mTime = (*qq).dTime; + + // compute the rotation quaternion from the euler angles + // aiQuaternion: The order of the parameters is yzx? + pRotKeys->mValue = aiQuaternion((*qq).vRot.y, (*qq).vRot.z, (*qq).vRot.x); + pVecKeys->mValue = (*qq).vPos; + + ++pVecKeys; ++pRotKeys; + } + } + ++a; + + // there are no scaling keys ... + } +} + +void SMDImporter::GetAnimationFileList(const std::string &pFile, IOSystem* pIOHandler, std::vector<std::tuple<std::string, std::string>>& outList) { + auto base = DefaultIOSystem::absolutePath(pFile); + auto name = DefaultIOSystem::completeBaseName(pFile); + auto path = base + "/" + name + "_animation.txt"; + + std::unique_ptr<IOStream> file(pIOHandler->Open(path.c_str(), "rb")); + if (file.get() == nullptr) { + return; + } + + // Allocate storage and copy the contents of the file to a memory buffer + std::vector<char> buf; + size_t fileSize = file->FileSize(); + buf.resize(fileSize + 1); + TextFileToBuffer(file.get(), buf); + + /* + *_animation.txt format: + name path + idle idle.smd + jump anim/jump.smd + walk.smd + ... + */ + std::string animName, animPath; + char *tok1, *tok2; + char *context1, *context2; + + tok1 = strtok_s(&buf[0], "\r\n", &context1); + while (tok1 != nullptr) { + tok2 = strtok_s(tok1, " \t", &context2); + if (tok2) { + char *p = tok2; + tok2 = strtok_s(nullptr, " \t", &context2); + if (tok2) { + animPath = tok2; + animName = p; + } else { + // No name + animPath = p; + animName = DefaultIOSystem::completeBaseName(animPath); + } + outList.push_back(std::make_tuple(animName, base + "/" + animPath)); + } + tok1 = strtok_s(nullptr, "\r\n", &context1); + } +} + +// ------------------------------------------------------------------------------------------------ +// create output materials +void SMDImporter::CreateOutputMaterials() { + ai_assert( nullptr != pScene ); + + pScene->mNumMaterials = (unsigned int)aszTextures.size(); + pScene->mMaterials = new aiMaterial*[std::max(1u, pScene->mNumMaterials)]; + + for (unsigned int iMat = 0; iMat < pScene->mNumMaterials; ++iMat) { + aiMaterial* pcMat = new aiMaterial(); + ai_assert( nullptr != pcMat ); + pScene->mMaterials[iMat] = pcMat; + + aiString szName; + szName.length = (size_t)ai_snprintf(szName.data,MAXLEN,"Texture_%u",iMat); + pcMat->AddProperty(&szName,AI_MATKEY_NAME); + + if (aszTextures[iMat].length()) + { + ::strncpy(szName.data, aszTextures[iMat].c_str(),MAXLEN-1); + szName.length = static_cast<ai_uint32>( aszTextures[iMat].length() ); + pcMat->AddProperty(&szName,AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + } + + // create a default material if necessary + if (0 == pScene->mNumMaterials) { + pScene->mNumMaterials = 1; + + aiMaterial* pcHelper = new aiMaterial(); + pScene->mMaterials[0] = pcHelper; + + int iMode = static_cast<int>(aiShadingMode_Gouraud); + pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + aiColor3D clr; + clr.b = clr.g = clr.r = 0.7f; + pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR); + + clr.b = clr.g = clr.r = 0.05f; + pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + + aiString szName; + szName.Set(AI_DEFAULT_MATERIAL_NAME); + pcHelper->AddProperty(&szName,AI_MATKEY_NAME); + } +} + +// ------------------------------------------------------------------------------------------------ +// Parse the file +void SMDImporter::ParseFile() { + const char* szCurrent = &mBuffer[0]; + + // read line per line ... + for ( ;; ) { + if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) { + break; + } + + // "version <n> \n", <n> should be 1 for hl and hl2 SMD files + if (TokenMatch(szCurrent,"version",7)) { + if(!SkipSpaces(szCurrent,&szCurrent)) break; + if (1 != strtoul10(szCurrent,&szCurrent)) { + ASSIMP_LOG_WARN("SMD.version is not 1. This " + "file format is not known. Continuing happily ..."); + } + continue; + } + // "nodes\n" - Starts the node section + if (TokenMatch(szCurrent,"nodes",5)) { + ParseNodesSection(szCurrent,&szCurrent); + continue; + } + // "triangles\n" - Starts the triangle section + if (TokenMatch(szCurrent,"triangles",9)) { + ParseTrianglesSection(szCurrent,&szCurrent); + continue; + } + // "vertexanimation\n" - Starts the vertex animation section + if (TokenMatch(szCurrent,"vertexanimation",15)) { + bHasUVs = false; + ParseVASection(szCurrent,&szCurrent); + continue; + } + // "skeleton\n" - Starts the skeleton section + if (TokenMatch(szCurrent,"skeleton",8)) { + ParseSkeletonSection(szCurrent,&szCurrent); + continue; + } + SkipLine(szCurrent,&szCurrent); + } +} + +void SMDImporter::ReadSmd(const std::string &pFile, IOSystem* pIOHandler) { + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb")); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open SMD/VTA file ", pFile, "."); + } + + iFileSize = (unsigned int)file->FileSize(); + + // Allocate storage and copy the contents of the file to a memory buffer + mBuffer.resize(iFileSize + 1); + TextFileToBuffer(file.get(), mBuffer); + + iSmallestFrame = INT_MAX; + bHasUVs = true; + iLineNumber = 1; + + // Reserve enough space for ... hm ... 10 textures + aszTextures.reserve(10); + + // Reserve enough space for ... hm ... 1000 triangles + asTriangles.reserve(1000); + + // Reserve enough space for ... hm ... 20 bones + asBones.reserve(20); + + aszTextures.clear(); + asTriangles.clear(); + asBones.clear(); + + // parse the file ... + ParseFile(); +} + +// ------------------------------------------------------------------------------------------------ +unsigned int SMDImporter::GetTextureIndex(const std::string& filename) { + unsigned int iIndex = 0; + for (std::vector<std::string>::const_iterator + i = aszTextures.begin(); + i != aszTextures.end();++i,++iIndex) { + // case-insensitive ... it's a path + if (0 == ASSIMP_stricmp ( filename.c_str(),(*i).c_str())) { + return iIndex; + } + } + iIndex = (unsigned int)aszTextures.size(); + aszTextures.push_back(filename); + return iIndex; +} + +// ------------------------------------------------------------------------------------------------ +// Parse the nodes section of the file +void SMDImporter::ParseNodesSection(const char* szCurrent, const char** szCurrentOut) { + for ( ;; ) { + // "end\n" - Ends the nodes section + if (0 == ASSIMP_strincmp(szCurrent,"end",3) && IsSpaceOrNewLine(*(szCurrent+3))) { + szCurrent += 4; + break; + } + ParseNodeInfo(szCurrent,&szCurrent); + } + SkipSpacesAndLineEnd(szCurrent,&szCurrent); + *szCurrentOut = szCurrent; +} + +// ------------------------------------------------------------------------------------------------ +// Parse the triangles section of the file +void SMDImporter::ParseTrianglesSection(const char* szCurrent, const char** szCurrentOut) { + // Parse a triangle, parse another triangle, parse the next triangle ... + // and so on until we reach a token that looks quite similar to "end" + for ( ;; ) { + if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) { + break; + } + + // "end\n" - Ends the triangles section + if (TokenMatch(szCurrent,"end",3)) { + break; + } + ParseTriangle(szCurrent,&szCurrent); + } + SkipSpacesAndLineEnd(szCurrent,&szCurrent); + *szCurrentOut = szCurrent; +} +// ------------------------------------------------------------------------------------------------ +// Parse the vertex animation section of the file +void SMDImporter::ParseVASection(const char* szCurrent, const char** szCurrentOut) { + unsigned int iCurIndex = 0; + for ( ;; ) { + if (!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) { + break; + } + + // "end\n" - Ends the "vertexanimation" section + if (TokenMatch(szCurrent,"end",3)) { + break; + } + + // "time <n>\n" + if (TokenMatch(szCurrent,"time",4)) { + // NOTE: The doc says that time values COULD be negative ... + // NOTE2: this is the shape key -> valve docs + int iTime = 0; + if(!ParseSignedInt(szCurrent,&szCurrent,iTime) || configFrameID != (unsigned int)iTime) { + break; + } + SkipLine(szCurrent,&szCurrent); + } else { + if(0 == iCurIndex) { + asTriangles.push_back(SMD::Face()); + } + if (++iCurIndex == 3) { + iCurIndex = 0; + } + ParseVertex(szCurrent,&szCurrent,asTriangles.back().avVertices[iCurIndex],true); + } + } + + if (iCurIndex != 2 && !asTriangles.empty()) { + // we want to no degenerates, so throw this triangle away + asTriangles.pop_back(); + } + + SkipSpacesAndLineEnd(szCurrent,&szCurrent); + *szCurrentOut = szCurrent; +} + +// ------------------------------------------------------------------------------------------------ +// Parse the skeleton section of the file +void SMDImporter::ParseSkeletonSection(const char* szCurrent, const char** szCurrentOut) { + int iTime = 0; + for ( ;; ) { + if (!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) { + break; + } + + // "end\n" - Ends the skeleton section + if (TokenMatch(szCurrent,"end",3)) { + break; + } else if (TokenMatch(szCurrent,"time",4)) { + // "time <n>\n" - Specifies the current animation frame + if(!ParseSignedInt(szCurrent,&szCurrent,iTime)) { + break; + } + + iSmallestFrame = std::min(iSmallestFrame,iTime); + SkipLine(szCurrent,&szCurrent); + } else { + ParseSkeletonElement(szCurrent,&szCurrent,iTime); + } + } + *szCurrentOut = szCurrent; +} + +// ------------------------------------------------------------------------------------------------ +#define SMDI_PARSE_RETURN { \ + SkipLine(szCurrent,&szCurrent); \ + *szCurrentOut = szCurrent; \ + return; \ +} +// ------------------------------------------------------------------------------------------------ +// Parse a node line +void SMDImporter::ParseNodeInfo(const char* szCurrent, const char** szCurrentOut) { + unsigned int iBone = 0; + SkipSpacesAndLineEnd(szCurrent,&szCurrent); + if ( !ParseUnsignedInt(szCurrent,&szCurrent,iBone) || !SkipSpaces(szCurrent,&szCurrent)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing bone index"); + SMDI_PARSE_RETURN; + } + // add our bone to the list + if (iBone >= asBones.size()) { + asBones.resize(iBone+1); + } + SMD::Bone& bone = asBones[iBone]; + + bool bQuota = true; + if ('\"' != *szCurrent) { + LogWarning("Bone name is expected to be enclosed in " + "double quotation marks. "); + bQuota = false; + } else { + ++szCurrent; + } + + const char* szEnd = szCurrent; + for ( ;; ) { + if (bQuota && '\"' == *szEnd) { + iBone = (unsigned int)(szEnd - szCurrent); + ++szEnd; + break; + } else if (!bQuota && IsSpaceOrNewLine(*szEnd)) { + iBone = (unsigned int)(szEnd - szCurrent); + break; + } else if (!(*szEnd)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing bone name"); + SMDI_PARSE_RETURN; + } + ++szEnd; + } + bone.mName = std::string(szCurrent,iBone); + szCurrent = szEnd; + + // the only negative bone parent index that could occur is -1 AFAIK + if(!ParseSignedInt(szCurrent,&szCurrent,(int&)bone.iParent)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing bone parent index. Assuming -1"); + SMDI_PARSE_RETURN; + } + + // go to the beginning of the next line + SMDI_PARSE_RETURN; +} + +// ------------------------------------------------------------------------------------------------ +// Parse a skeleton element +void SMDImporter::ParseSkeletonElement(const char* szCurrent, const char** szCurrentOut,int iTime) { + aiVector3D vPos; + aiVector3D vRot; + + unsigned int iBone = 0; + if(!ParseUnsignedInt(szCurrent,&szCurrent,iBone)) { + ASSIMP_LOG_ERROR("Unexpected EOF/EOL while parsing bone index"); + SMDI_PARSE_RETURN; + } + if (iBone >= asBones.size()) { + LogErrorNoThrow("Bone index in skeleton section is out of range"); + SMDI_PARSE_RETURN; + } + SMD::Bone& bone = asBones[iBone]; + + bone.sAnim.asKeys.push_back(SMD::Bone::Animation::MatrixKey()); + SMD::Bone::Animation::MatrixKey& key = bone.sAnim.asKeys.back(); + + key.dTime = (double)iTime; + if(!ParseFloat(szCurrent,&szCurrent,(float&)vPos.x)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.pos.x"); + SMDI_PARSE_RETURN; + } + if(!ParseFloat(szCurrent,&szCurrent,(float&)vPos.y)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.pos.y"); + SMDI_PARSE_RETURN; + } + if(!ParseFloat(szCurrent,&szCurrent,(float&)vPos.z)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.pos.z"); + SMDI_PARSE_RETURN; + } + if(!ParseFloat(szCurrent,&szCurrent,(float&)vRot.x)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.rot.x"); + SMDI_PARSE_RETURN; + } + if(!ParseFloat(szCurrent,&szCurrent,(float&)vRot.y)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.rot.y"); + SMDI_PARSE_RETURN; + } + if(!ParseFloat(szCurrent,&szCurrent,(float&)vRot.z)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.rot.z"); + SMDI_PARSE_RETURN; + } + // build the transformation matrix of the key + key.matrix.FromEulerAnglesXYZ(vRot.x,vRot.y,vRot.z); { + aiMatrix4x4 mTemp; + mTemp.a4 = vPos.x; + mTemp.b4 = vPos.y; + mTemp.c4 = vPos.z; + key.matrix = mTemp * key.matrix; + } + key.vPos = vPos; + key.vRot = vRot; + // go to the beginning of the next line + SMDI_PARSE_RETURN; +} + +// ------------------------------------------------------------------------------------------------ +// Parse a triangle +void SMDImporter::ParseTriangle(const char* szCurrent, const char** szCurrentOut) { + asTriangles.push_back(SMD::Face()); + SMD::Face& face = asTriangles.back(); + + if(!SkipSpaces(szCurrent,&szCurrent)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing a triangle"); + return; + } + + // read the texture file name + const char* szLast = szCurrent; + while (!IsSpaceOrNewLine(*++szCurrent)); + + // ... and get the index that belongs to this file name + face.iTexture = GetTextureIndex(std::string(szLast,(uintptr_t)szCurrent-(uintptr_t)szLast)); + + SkipSpacesAndLineEnd(szCurrent,&szCurrent); + + // load three vertices + for (unsigned int iVert = 0; iVert < 3;++iVert) { + ParseVertex(szCurrent,&szCurrent, face.avVertices[iVert]); + } + *szCurrentOut = szCurrent; +} + +// ------------------------------------------------------------------------------------------------ +// Parse a float +bool SMDImporter::ParseFloat(const char* szCurrent, const char** szCurrentOut, float& out) { + if(!SkipSpaces(&szCurrent)) { + return false; + } + + *szCurrentOut = fast_atoreal_move<float>(szCurrent,out); + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Parse an unsigned int +bool SMDImporter::ParseUnsignedInt(const char* szCurrent, const char** szCurrentOut, unsigned int& out) { + if(!SkipSpaces(&szCurrent)) { + return false; + } + + out = strtoul10(szCurrent,szCurrentOut); + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Parse a signed int +bool SMDImporter::ParseSignedInt(const char* szCurrent, const char** szCurrentOut, int& out) { + if(!SkipSpaces(&szCurrent)) { + return false; + } + + out = strtol10(szCurrent,szCurrentOut); + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Parse a vertex +void SMDImporter::ParseVertex(const char* szCurrent, + const char** szCurrentOut, SMD::Vertex& vertex, + bool bVASection /*= false*/) { + if (SkipSpaces(&szCurrent) && IsLineEnd(*szCurrent)) { + SkipSpacesAndLineEnd(szCurrent,&szCurrent); + return ParseVertex(szCurrent,szCurrentOut,vertex,bVASection); + } + if(!ParseSignedInt(szCurrent,&szCurrent,(int&)vertex.iParentNode)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.parent"); + SMDI_PARSE_RETURN; + } + if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.pos.x)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.pos.x"); + SMDI_PARSE_RETURN; + } + if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.pos.y)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.pos.y"); + SMDI_PARSE_RETURN; + } + if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.pos.z)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.pos.z"); + SMDI_PARSE_RETURN; + } + if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.nor.x)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.nor.x"); + SMDI_PARSE_RETURN; + } + if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.nor.y)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.nor.y"); + SMDI_PARSE_RETURN; + } + if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.nor.z)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.nor.z"); + SMDI_PARSE_RETURN; + } + + if (bVASection) { + SMDI_PARSE_RETURN; + } + + if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.uv.x)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.uv.x"); + SMDI_PARSE_RETURN; + } + if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.uv.y)) { + LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.uv.y"); + SMDI_PARSE_RETURN; + } + + // now read the number of bones affecting this vertex + // all elements from now are fully optional, we don't need them + unsigned int iSize = 0; + if(!ParseUnsignedInt(szCurrent,&szCurrent,iSize)) { + SMDI_PARSE_RETURN; + } + vertex.aiBoneLinks.resize(iSize,std::pair<unsigned int, float>(0,0.0f)); + + for (std::vector<std::pair<unsigned int, float> >::iterator + i = vertex.aiBoneLinks.begin(); + i != vertex.aiBoneLinks.end();++i) { + if(!ParseUnsignedInt(szCurrent,&szCurrent,(*i).first)) { + SMDI_PARSE_RETURN; + } + if(!ParseFloat(szCurrent,&szCurrent,(*i).second)) { + SMDI_PARSE_RETURN; + } + } + + // go to the beginning of the next line + SMDI_PARSE_RETURN; +} + +#endif // !! ASSIMP_BUILD_NO_SMD_IMPORTER diff --git a/libs/assimp/code/AssetLib/SMD/SMDLoader.h b/libs/assimp/code/AssetLib/SMD/SMDLoader.h new file mode 100644 index 0000000..db882a2 --- /dev/null +++ b/libs/assimp/code/AssetLib/SMD/SMDLoader.h @@ -0,0 +1,395 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 SMDLoader.h + * @brief Definition of the Valve SMD file format + */ + +#pragma once +#ifndef AI_SMDLOADER_H_INCLUDED +#define AI_SMDLOADER_H_INCLUDED + +#include <assimp/BaseImporter.h> +#include <assimp/ParsingUtils.h> +#include <assimp/types.h> +#include <assimp/texture.h> +#include <assimp/anim.h> +#include <assimp/material.h> + +#include <vector> + +struct aiNode; + +namespace Assimp { +namespace SMD { + +// --------------------------------------------------------------------------- +/** Data structure for a vertex in a SMD file +*/ +struct Vertex { + Vertex() AI_NO_EXCEPT : iParentNode(UINT_MAX) { + // empty + } + + //! Vertex position, normal and texture coordinate + aiVector3D pos,nor,uv; + + //! Vertex parent node + unsigned int iParentNode; + + //! Links to bones: pair.first is the bone index, + //! pair.second is the vertex weight. + //! WARN: The remaining weight (to reach 1.0f) is assigned + //! to the parent node/bone + std::vector<std::pair<unsigned int, float> > aiBoneLinks; +}; + +// --------------------------------------------------------------------------- +/** Data structure for a face in a SMD file +*/ +struct Face { + Face() AI_NO_EXCEPT : + iTexture(0x0), avVertices{} { + // empty + } + + //! Texture index for the face + unsigned int iTexture; + + //! The three vertices of the face + Vertex avVertices[3]; +}; + +// --------------------------------------------------------------------------- +/** Data structure for a bone in a SMD file +*/ +struct Bone { + //! Default constructor + Bone() AI_NO_EXCEPT : iParent(UINT_MAX), bIsUsed(false) { + // empty + } + + //! Name of the bone + std::string mName; + + //! Parent of the bone + uint32_t iParent; + + //! Animation of the bone + struct Animation { + //! Public default constructor + Animation() AI_NO_EXCEPT : iFirstTimeKey() { + asKeys.reserve(20); + } + + //! Data structure for a matrix key + struct MatrixKey { + //! Matrix at this time + aiMatrix4x4 matrix; + + //! Absolute transformation matrix + aiMatrix4x4 matrixAbsolute; + + //! Position + aiVector3D vPos; + + //! Rotation (euler angles) + aiVector3D vRot; + + //! Current time. may be negative, this + //! will be fixed later + double dTime; + }; + + //! Index of the key with the smallest time value + uint32_t iFirstTimeKey; + + //! Array of matrix keys + std::vector<MatrixKey> asKeys; + + } sAnim; + + //! Offset matrix of the bone + aiMatrix4x4 mOffsetMatrix; + + //! true if the bone is referenced by at least one mesh + bool bIsUsed; +}; + +} //! namespace SMD + +// --------------------------------------------------------------------------- +/** Used to load Half-life 1 and 2 SMD models +*/ +class ASSIMP_API SMDImporter : public BaseImporter { +public: + SMDImporter(); + ~SMDImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool checkSig) const override; + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer* pImp) override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc* GetInfo () const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler) override; + + // ------------------------------------------------------------------- + /** Parse the SMD file and create the output scene + */ + void ParseFile(); + void ReadSmd(const std::string &pFile, IOSystem* pIOHandler); + + // ------------------------------------------------------------------- + /** Parse the triangles section of the SMD file + * \param szCurrent Current position in the file. Points to the first + * data line of the section. + * \param szCurrentOut Receives a pointer to the heading line of + * the next section (or to EOF) + */ + void ParseTrianglesSection(const char* szCurrent, + const char** szCurrentOut); + + // ------------------------------------------------------------------- + /** Parse the vertex animation section in VTA files + * \param szCurrent Current position in the file. Points to the first + * data line of the section. + * \param szCurrentOut Receives a pointer to the heading line of + * the next section (or to EOF) + */ + void ParseVASection(const char* szCurrent, + const char** szCurrentOut); + + // ------------------------------------------------------------------- + /** Parse the nodes section of the SMD file + * \param szCurrent Current position in the file. Points to the first + * data line of the section. + * \param szCurrentOut Receives a pointer to the heading line of + * the next section (or to EOF) + */ + void ParseNodesSection(const char* szCurrent, + const char** szCurrentOut); + + // ------------------------------------------------------------------- + /** Parse the skeleton section of the SMD file + * \param szCurrent Current position in the file. Points to the first + * data line of the section. + * \param szCurrentOut Receives a pointer to the heading line of + * the next section (or to EOF) + */ + void ParseSkeletonSection(const char* szCurrent, + const char** szCurrentOut); + + // ------------------------------------------------------------------- + /** Parse a single triangle in the SMD file + * \param szCurrent Current position in the file. Points to the first + * data line of the section. + * \param szCurrentOut Receives the output cursor position + */ + void ParseTriangle(const char* szCurrent, + const char** szCurrentOut); + + + // ------------------------------------------------------------------- + /** Parse a single vertex in the SMD file + * \param szCurrent Current position in the file. Points to the first + * data line of the section. + * \param szCurrentOut Receives the output cursor position + * \param vertex Vertex to be filled + */ + void ParseVertex(const char* szCurrent, + const char** szCurrentOut, SMD::Vertex& vertex, + bool bVASection = false); + + // ------------------------------------------------------------------- + /** Get the index of a texture. If the texture was not yet known + * it will be added to the internal texture list. + * \param filename Name of the texture + * \return Value texture index + */ + unsigned int GetTextureIndex(const std::string& filename); + + // ------------------------------------------------------------------- + /** Parse a line in the skeleton section + */ + void ParseSkeletonElement(const char* szCurrent, + const char** szCurrentOut,int iTime); + + // ------------------------------------------------------------------- + /** Parse a line in the nodes section + */ + void ParseNodeInfo(const char* szCurrent, + const char** szCurrentOut); + + + // ------------------------------------------------------------------- + /** Parse a floating-point value + */ + bool ParseFloat(const char* szCurrent, + const char** szCurrentOut, float& out); + + // ------------------------------------------------------------------- + /** Parse an unsigned integer. There may be no sign! + */ + bool ParseUnsignedInt(const char* szCurrent, + const char** szCurrentOut, unsigned int& out); + + // ------------------------------------------------------------------- + /** Parse a signed integer. Signs (+,-) are handled. + */ + bool ParseSignedInt(const char* szCurrent, + const char** szCurrentOut, int& out); + + // ------------------------------------------------------------------- + /** Fix invalid time values in the file + */ + void FixTimeValues(); + + // ------------------------------------------------------------------- + /** Add all children of a bone as subnodes to a node + * \param pcNode Parent node + * \param iParent Parent bone index + */ + void AddBoneChildren(aiNode* pcNode, uint32_t iParent); + + // ------------------------------------------------------------------- + /** Build output meshes/materials/nodes/animations + */ + void CreateOutputMeshes(); + void CreateOutputNodes(); + void CreateOutputAnimations(const std::string &pFile, IOSystem* pIOHandler); + void CreateOutputAnimation(int index, const std::string &name); + void GetAnimationFileList(const std::string &pFile, IOSystem* pIOHandler, std::vector<std::tuple<std::string, std::string>>& outList); + void CreateOutputMaterials(); + + + // ------------------------------------------------------------------- + /** Print a log message together with the current line number + */ + void LogErrorNoThrow(const char* msg); + void LogWarning(const char* msg); + + + // ------------------------------------------------------------------- + inline bool SkipLine( const char* in, const char** out) + { + Assimp::SkipLine(in,out); + ++iLineNumber; + return true; + } + // ------------------------------------------------------------------- + inline bool SkipSpacesAndLineEnd( const char* in, const char** out) + { + ++iLineNumber; + return Assimp::SkipSpacesAndLineEnd(in,out); + } + +private: + + /** Configuration option: frame to be loaded */ + unsigned int configFrameID; + + /** Buffer to hold the loaded file */ + std::vector<char> mBuffer; + + /** Output scene to be filled + */ + aiScene* pScene; + + /** Size of the input file in bytes + */ + unsigned int iFileSize; + + /** Array of textures found in the file + */ + std::vector<std::string> aszTextures; + + /** Array of triangles found in the file + */ + std::vector<SMD::Face> asTriangles; + + /** Array of bones found in the file + */ + std::vector<SMD::Bone> asBones; + + /** Smallest frame index found in the skeleton + */ + int iSmallestFrame; + + /** Length of the whole animation, in frames + */ + double dLengthOfAnim; + + /** Do we have texture coordinates? + */ + bool bHasUVs; + + /** Current line number + */ + unsigned int iLineNumber; + + bool bLoadAnimationList = true; + bool noSkeletonMesh = false; +}; + +} // end of namespace Assimp + +#endif // AI_SMDIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/STEPParser/STEPFileEncoding.cpp b/libs/assimp/code/AssetLib/STEPParser/STEPFileEncoding.cpp new file mode 100644 index 0000000..d4456e6 --- /dev/null +++ b/libs/assimp/code/AssetLib/STEPParser/STEPFileEncoding.cpp @@ -0,0 +1,432 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 STEPFileEncoding.cpp + * @brief STEP character handling, string un-escaping + */ +#include "STEPFileEncoding.h" +#include <assimp/fast_atof.h> +#ifdef ASSIMP_USE_HUNTER +# include <utf8.h> +#else +# include <contrib/utf8cpp/source/utf8.h> +#endif + +#include <memory> + +using namespace Assimp; + +// roman1 to utf16 table +static const uint16_t mac_codetable[] = { + // 0x20 unassig./nonprint. slots + 0x0020 , + 0x0021 , + 0x0022 , + 0x0023 , + 0x0024 , + 0x0025 , + 0x0026 , + 0x0027 , + 0x0028 , + 0x0029 , + 0x002A , + 0x002B , + 0x002C , + 0x002D , + 0x002E , + 0x002F , + 0x0030 , + 0x0031 , + 0x0032 , + 0x0033 , + 0x0034 , + 0x0035 , + 0x0036 , + 0x0037 , + 0x0038 , + 0x0039 , + 0x003A , + 0x003B , + 0x003C , + 0x003D , + 0x003E , + 0x003F , + 0x0040 , + 0x0041 , + 0x0042 , + 0x0043 , + 0x0044 , + 0x0045 , + 0x0046 , + 0x0047 , + 0x0048 , + 0x0049 , + 0x004A , + 0x004B , + 0x004C , + 0x004D , + 0x004E , + 0x004F , + 0x0050 , + 0x0051 , + 0x0052 , + 0x0053 , + 0x0054 , + 0x0055 , + 0x0056 , + 0x0057 , + 0x0058 , + 0x0059 , + 0x005A , + 0x005B , + 0x005C , + 0x005D , + 0x005E , + 0x005F , + 0x0060 , + 0x0061 , + 0x0062 , + 0x0063 , + 0x0064 , + 0x0065 , + 0x0066 , + 0x0067 , + 0x0068 , + 0x0069 , + 0x006A , + 0x006B , + 0x006C , + 0x006D , + 0x006E , + 0x006F , + 0x0070 , + 0x0071 , + 0x0072 , + 0x0073 , + 0x0074 , + 0x0075 , + 0x0076 , + 0x0077 , + 0x0078 , + 0x0079 , + 0x007A , + 0x007B , + 0x007C , + 0x007D , + 0x007E , + 0x0000 , // unassig. + 0x00C4 , + 0x00C5 , + 0x00C7 , + 0x00C9 , + 0x00D1 , + 0x00D6 , + 0x00DC , + 0x00E1 , + 0x00E0 , + 0x00E2 , + 0x00E4 , + 0x00E3 , + 0x00E5 , + 0x00E7 , + 0x00E9 , + 0x00E8 , + 0x00EA , + 0x00EB , + 0x00ED , + 0x00EC , + 0x00EE , + 0x00EF , + 0x00F1 , + 0x00F3 , + 0x00F2 , + 0x00F4 , + 0x00F6 , + 0x00F5 , + 0x00FA , + 0x00F9 , + 0x00FB , + 0x00FC , + 0x2020 , + 0x00B0 , + 0x00A2 , + 0x00A3 , + 0x00A7 , + 0x2022 , + 0x00B6 , + 0x00DF , + 0x00AE , + 0x00A9 , + 0x2122 , + 0x00B4 , + 0x00A8 , + 0x2260 , + 0x00C6 , + 0x00D8 , + 0x221E , + 0x00B1 , + 0x2264 , + 0x2265 , + 0x00A5 , + 0x00B5 , + 0x2202 , + 0x2211 , + 0x220F , + 0x03C0 , + 0x222B , + 0x00AA , + 0x00BA , + 0x03A9 , + 0x00E6 , + 0x00F8 , + 0x00BF , + 0x00A1 , + 0x00AC , + 0x221A , + 0x0192 , + 0x2248 , + 0x2206 , + 0x00AB , + 0x00BB , + 0x2026 , + 0x00A0 , + 0x00C0 , + 0x00C3 , + 0x00D5 , + 0x0152 , + 0x0153 , + 0x2013 , + 0x2014 , + 0x201C , + 0x201D , + 0x2018 , + 0x2019 , + 0x00F7 , + 0x25CA , + 0x00FF , + 0x0178 , + 0x2044 , + 0x20AC , + 0x2039 , + 0x203A , + 0xFB01 , + 0xFB02 , + 0x2021 , + 0x00B7 , + 0x201A , + 0x201E , + 0x2030 , + 0x00C2 , + 0x00CA , + 0x00C1 , + 0x00CB , + 0x00C8 , + 0x00CD , + 0x00CE , + 0x00CF , + 0x00CC , + 0x00D3 , + 0x00D4 , + 0xF8FF , + 0x00D2 , + 0x00DA , + 0x00DB , + 0x00D9 , + 0x0131 , + 0x02C6 , + 0x02DC , + 0x00AF , + 0x02D8 , + 0x02D9 , + 0x02DA , + 0x00B8 , + 0x02DD , + 0x02DB , + 0x02C7 +}; + +// ------------------------------------------------------------------------------------------------ +bool STEP::StringToUTF8(std::string& s) +{ + // very basic handling for escaped string sequences + // http://doc.spatial.com/index.php?title=InterOp:Connect/STEP&redirect=no + + for (size_t i = 0; i < s.size(); ) { + if (s[i] == '\\') { + // \S\X - cp1252 (X is the character remapped to [0,127]) + if (i+3 < s.size() && s[i+1] == 'S' && s[i+2] == '\\') { + // http://stackoverflow.com/questions/5586214/how-to-convert-char-from-iso-8859-1-to-utf-8-in-c-multiplatformly + ai_assert((uint8_t)s[i+3] < 0x80); + const uint8_t ch = s[i+3] + 0x80; + + s[i] = 0xc0 | (ch & 0xc0) >> 6; + s[i+1] = 0x80 | (ch & 0x3f); + + s.erase(i + 2,2); + ++i; + } + // \X\xx - mac/roman (xx is a hex sequence) + else if (i+4 < s.size() && s[i+1] == 'X' && s[i+2] == '\\') { + + const uint8_t macval = HexOctetToDecimal(s.c_str() + i + 3); + if(macval < 0x20) { + return false; + } + + ai_assert(sizeof(mac_codetable) / sizeof(mac_codetable[0]) == 0x100-0x20); + + const uint32_t unival = mac_codetable[macval - 0x20], *univalp = &unival; + + unsigned char temp[5], *tempp = temp; + ai_assert(sizeof( unsigned char ) == 1); + + utf8::utf32to8( univalp, univalp + 1, tempp ); + + const size_t outcount = static_cast<size_t>(tempp-temp); + + s.erase(i,5); + s.insert(i, reinterpret_cast<char*>(temp), outcount); + i += outcount; + } + // \Xn\ .. \X0\ - various unicode encodings (n=2: utf16; n=4: utf32) + else if (i+3 < s.size() && s[i+1] == 'X' && s[i+2] >= '0' && s[i+2] <= '9') { + switch(s[i+2]) { + // utf16 + case '2': + // utf32 + case '4': + if (s[i+3] == '\\') { + const size_t basei = i+4; + size_t j = basei, jend = s.size()-3; + + for (; j < jend; ++j) { + if (s[j] == '\\' && s[j+1] == 'X' && s[j+2] == '0' && s[j+3] == '\\') { + break; + } + } + if (j == jend) { + return false; + } + + if (j == basei) { + s.erase(i,8); + continue; + } + + if (s[i+2] == '2') { + if (((j - basei) % 4) != 0) { + return false; + } + + const size_t count = (j-basei)/4; + std::unique_ptr<uint16_t[]> src(new uint16_t[count]); + + const char* cur = s.c_str() + basei; + for (size_t k = 0; k < count; ++k, cur += 4) { + src[k] = (static_cast<uint16_t>(HexOctetToDecimal(cur)) << 8u) | + static_cast<uint16_t>(HexOctetToDecimal(cur+2)); + } + + const size_t dcount = count * 3; // this is enough to hold all possible outputs + std::unique_ptr<unsigned char[]> dest(new unsigned char[dcount]); + + const uint16_t* srct = src.get(); + unsigned char* destt = dest.get(); + utf8::utf16to8( srct, srct + count, destt ); + + const size_t outcount = static_cast<size_t>(destt-dest.get()); + + s.erase(i,(j+4-i)); + + ai_assert(sizeof(unsigned char) == 1); + s.insert(i, reinterpret_cast<char*>(dest.get()), outcount); + + i += outcount; + continue; + } + else if (s[i+2] == '4') { + if (((j - basei) % 8) != 0) { + return false; + } + + const size_t count = (j-basei)/8; + std::unique_ptr<uint32_t[]> src(new uint32_t[count]); + + const char* cur = s.c_str() + basei; + for (size_t k = 0; k < count; ++k, cur += 8) { + src[k] = (static_cast<uint32_t>(HexOctetToDecimal(cur )) << 24u) | + (static_cast<uint32_t>(HexOctetToDecimal(cur+2)) << 16u) | + (static_cast<uint32_t>(HexOctetToDecimal(cur+4)) << 8u) | + (static_cast<uint32_t>(HexOctetToDecimal(cur+6))); + } + + const size_t dcount = count * 5; // this is enough to hold all possible outputs + std::unique_ptr<unsigned char[]> dest(new unsigned char[dcount]); + + const uint32_t* srct = src.get(); + unsigned char* destt = dest.get(); + utf8::utf32to8( srct, srct + count, destt ); + + const size_t outcount = static_cast<size_t>(destt-dest.get()); + + s.erase(i,(j+4-i)); + + ai_assert(sizeof(unsigned char) == 1); + s.insert(i, reinterpret_cast<char*>(dest.get()), outcount); + + i += outcount; + continue; + } + } + break; + + // TODO: other encoding patterns? + + default: + return false; + } + } + } + ++i; + } + return true; +} diff --git a/libs/assimp/code/AssetLib/STEPParser/STEPFileEncoding.h b/libs/assimp/code/AssetLib/STEPParser/STEPFileEncoding.h new file mode 100644 index 0000000..950c9b3 --- /dev/null +++ b/libs/assimp/code/AssetLib/STEPParser/STEPFileEncoding.h @@ -0,0 +1,65 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef INCLUDED_AI_STEPFILEENCODING_H +#define INCLUDED_AI_STEPFILEENCODING_H + +#include <string> + +namespace Assimp { +namespace STEP { + + + // -------------------------------------------------------------------------- + // Convert an ASCII STEP identifier with possibly escaped character + // sequences using foreign encodings to plain UTF8. + // + // Return false if an error occurs, s may or may not be modified in + // this case and could still contain escape sequences (even partly + // escaped ones). + bool StringToUTF8(std::string& s); + + +} // ! STEP +} // ! Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/STEPParser/STEPFileReader.cpp b/libs/assimp/code/AssetLib/STEPParser/STEPFileReader.cpp new file mode 100644 index 0000000..2bcfa17 --- /dev/null +++ b/libs/assimp/code/AssetLib/STEPParser/STEPFileReader.cpp @@ -0,0 +1,563 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 STEPFileReader.cpp + * @brief Implementation of the STEP file parser, which fills a + * STEP::DB with data read from a file. + */ + +#include "STEPFileReader.h" +#include "STEPFileEncoding.h" +#include <assimp/TinyFormatter.h> +#include <assimp/fast_atof.h> +#include <functional> +#include <memory> +#include <utility> + +using namespace Assimp; + +namespace EXPRESS = STEP::EXPRESS; + +// ------------------------------------------------------------------------------------------------ +std::string AddLineNumber(const std::string& s,uint64_t line /*= LINE_NOT_SPECIFIED*/, const std::string& prefix = std::string()) +{ + return line == STEP::SyntaxError::LINE_NOT_SPECIFIED ? prefix+s : static_cast<std::string>( (Formatter::format(),prefix,"(line ",line,") ",s) ); +} + +// ------------------------------------------------------------------------------------------------ +std::string AddEntityID(const std::string& s,uint64_t entity /*= ENTITY_NOT_SPECIFIED*/, const std::string& prefix = std::string()) +{ + return entity == STEP::TypeError::ENTITY_NOT_SPECIFIED ? prefix+s : static_cast<std::string>( (Formatter::format(),prefix,"(entity #",entity,") ",s)); +} + + +// ------------------------------------------------------------------------------------------------ +STEP::SyntaxError::SyntaxError (const std::string& s,uint64_t line /* = LINE_NOT_SPECIFIED */) +: DeadlyImportError(AddLineNumber(s,line)) +{ + +} + +// ------------------------------------------------------------------------------------------------ +STEP::TypeError::TypeError (const std::string& s,uint64_t entity /* = ENTITY_NOT_SPECIFIED */,uint64_t line /*= LINE_NOT_SPECIFIED*/) +: DeadlyImportError(AddLineNumber(AddEntityID(s,entity),line)) +{ + +} + +static const char *ISO_Token = "ISO-10303-21;"; +static const char *FILE_SCHEMA_Token = "FILE_SCHEMA"; +// ------------------------------------------------------------------------------------------------ +STEP::DB* STEP::ReadFileHeader(std::shared_ptr<IOStream> stream) { + std::shared_ptr<StreamReaderLE> reader = std::shared_ptr<StreamReaderLE>(new StreamReaderLE(std::move(stream))); + std::unique_ptr<STEP::DB> db = std::unique_ptr<STEP::DB>(new STEP::DB(reader)); + + LineSplitter &splitter = db->GetSplitter(); + if (!splitter || *splitter != ISO_Token ) { + throw STEP::SyntaxError("expected magic token: " + std::string( ISO_Token ), 1); + } + + HeaderInfo& head = db->GetHeader(); + for(++splitter; splitter; ++splitter) { + const std::string& s = *splitter; + if (s == "DATA;") { + // here we go, header done, start of data section + ++splitter; + break; + } + + // want one-based line numbers for human readers, so +1 + const uint64_t line = splitter.get_index()+1; + + if (s.substr(0,11) == FILE_SCHEMA_Token) { + const char* sz = s.c_str()+11; + SkipSpaces(sz,&sz); + std::shared_ptr< const EXPRESS::DataType > schema = EXPRESS::DataType::Parse(sz); + + // the file schema should be a regular list entity, although it usually contains exactly one entry + // since the list itself is contained in a regular parameter list, we actually have + // two nested lists. + const EXPRESS::LIST* list = dynamic_cast<const EXPRESS::LIST*>(schema.get()); + if (list && list->GetSize()) { + list = dynamic_cast<const EXPRESS::LIST*>( (*list)[0].get() ); + if (!list) { + throw STEP::SyntaxError("expected FILE_SCHEMA to be a list",line); + } + + // XXX need support for multiple schemas? + if (list->GetSize() > 1) { + ASSIMP_LOG_WARN(AddLineNumber("multiple schemas currently not supported",line)); + } + const EXPRESS::STRING *string = dynamic_cast<const EXPRESS::STRING *>((*list)[0].get()); + if (!list->GetSize() || nullptr == string ) { + throw STEP::SyntaxError("expected FILE_SCHEMA to contain a single string literal",line); + } + head.fileSchema = *string; + } + } + + // XXX handle more header fields + } + + return db.release(); +} + + +namespace { + +// ------------------------------------------------------------------------------------------------ +// check whether the given line contains an entity definition (i.e. starts with "#<number>=") +bool IsEntityDef(const std::string& snext) +{ + if (snext[0] == '#') { + // it is only a new entity if it has a '=' after the + // entity ID. + for(std::string::const_iterator it = snext.begin()+1; it != snext.end(); ++it) { + if (*it == '=') { + return true; + } + if ((*it < '0' || *it > '9') && *it != ' ') { + break; + } + } + } + return false; +} + +} + + +// ------------------------------------------------------------------------------------------------ +void STEP::ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, + const char* const* types_to_track, size_t len, + const char* const* inverse_indices_to_track, size_t len2) +{ + db.SetSchema(scheme); + db.SetTypesToTrack(types_to_track,len); + db.SetInverseIndicesToTrack(inverse_indices_to_track,len2); + + const DB::ObjectMap& map = db.GetObjects(); + LineSplitter& splitter = db.GetSplitter(); + + while (splitter) { + bool has_next = false; + std::string s = *splitter; + if (s == "ENDSEC;") { + break; + } + s.erase(std::remove(s.begin(), s.end(), ' '), s.end()); + + // want one-based line numbers for human readers, so +1 + const uint64_t line = splitter.get_index()+1; + // LineSplitter already ignores empty lines + ai_assert(s.length()); + if (s[0] != '#') { + ASSIMP_LOG_WARN(AddLineNumber("expected token \'#\'",line)); + ++splitter; + continue; + } + // --- + // extract id, entity class name and argument string, + // but don't create the actual object yet. + // --- + const std::string::size_type n0 = s.find_first_of('='); + if (n0 == std::string::npos) { + ASSIMP_LOG_WARN(AddLineNumber("expected token \'=\'",line)); + ++splitter; + continue; + } + + const uint64_t id = strtoul10_64(s.substr(1,n0-1).c_str()); + if (!id) { + ASSIMP_LOG_WARN(AddLineNumber("expected positive, numeric entity id",line)); + ++splitter; + continue; + } + std::string::size_type n1 = s.find_first_of('(',n0); + if (n1 == std::string::npos) { + has_next = true; + bool ok = false; + for( ++splitter; splitter; ++splitter) { + const std::string& snext = *splitter; + if (snext.empty()) { + continue; + } + + // the next line doesn't start an entity, so maybe it is + // just a continuation for this line, keep going + if (!IsEntityDef(snext)) { + s.append(snext); + n1 = s.find_first_of('(',n0); + ok = (n1 != std::string::npos); + } + else { + break; + } + } + + if(!ok) { + ASSIMP_LOG_WARN(AddLineNumber("expected token \'(\'",line)); + continue; + } + } + + std::string::size_type n2 = s.find_last_of(')'); + if (n2 == std::string::npos || n2 < n1 || n2 == s.length() - 1 || s[n2 + 1] != ';') { + has_next = true; + bool ok = false; + for( ++splitter; splitter; ++splitter) { + const std::string& snext = *splitter; + if (snext.empty()) { + continue; + } + + // the next line doesn't start an entity, so maybe it is + // just a continuation for this line, keep going + if (!IsEntityDef(snext)) { + s.append(snext); + n2 = s.find_last_of(')'); + ok = !(n2 == std::string::npos || n2 < n1 || n2 == s.length() - 1 || s[n2 + 1] != ';'); + } else { + break; + } + } + if(!ok) { + ASSIMP_LOG_WARN(AddLineNumber("expected token \')\'",line)); + continue; + } + } + + if (map.find(id) != map.end()) { + ASSIMP_LOG_WARN(AddLineNumber((Formatter::format(),"an object with the id #",id," already exists"),line)); + } + + std::string::size_type ns = n0; + do { + ++ns; + } while (IsSpace(s.at(ns))); + std::string::size_type ne = n1; + do { + --ne; + } while (IsSpace(s.at(ne))); + std::string type = s.substr(ns, ne - ns + 1); + type = ai_tolower(type); + const char* sz = scheme.GetStaticStringForToken(type); + if(sz) { + const std::string::size_type szLen = n2-n1+1; + char* const copysz = new char[szLen+1]; + std::copy(s.c_str()+n1,s.c_str()+n2+1,copysz); + copysz[szLen] = '\0'; + db.InternInsert(new LazyObject(db,id,line,sz,copysz)); + } + if(!has_next) { + ++splitter; + } + } + + if (!splitter) { + ASSIMP_LOG_WARN("STEP: ignoring unexpected EOF"); + } + + if ( !DefaultLogger::isNullLogger()){ + ASSIMP_LOG_DEBUG("STEP: got ",map.size()," object records with ", + db.GetRefs().size()," inverse index entries"); + } +} + +// ------------------------------------------------------------------------------------------------ +std::shared_ptr<const EXPRESS::DataType> EXPRESS::DataType::Parse(const char*& inout,uint64_t line, const EXPRESS::ConversionSchema* schema /*= nullptr*/) +{ + const char* cur = inout; + SkipSpaces(&cur); + if (*cur == ',' || IsSpaceOrNewLine(*cur)) { + throw STEP::SyntaxError("unexpected token, expected parameter",line); + } + + // just skip over constructions such as IFCPLANEANGLEMEASURE(0.01) and read only the value + if (schema) { + bool ok = false; + for(const char* t = cur; *t && *t != ')' && *t != ','; ++t) { + if (*t=='(') { + if (!ok) { + break; + } + for(--t;IsSpace(*t);--t); + std::string s(cur,static_cast<size_t>(t-cur+1)); + std::transform(s.begin(),s.end(),s.begin(),&ai_tolower<char> ); + if (schema->IsKnownToken(s)) { + for(cur = t+1;*cur++ != '(';); + std::shared_ptr<const EXPRESS::DataType> dt = Parse(cur); + inout = *cur ? cur+1 : cur; + return dt; + } + break; + } + else if (!IsSpace(*t)) { + ok = true; + } + } + } + + if (*cur == '*' ) { + inout = cur+1; + return std::make_shared<EXPRESS::ISDERIVED>(); + } + else if (*cur == '$' ) { + inout = cur+1; + return std::make_shared<EXPRESS::UNSET>(); + } + else if (*cur == '(' ) { + // start of an aggregate, further parsing is done by the LIST factory constructor + inout = cur; + return EXPRESS::LIST::Parse(inout,line,schema); + } + else if (*cur == '.' ) { + // enum (includes boolean) + const char* start = ++cur; + for(;*cur != '.';++cur) { + if (*cur == '\0') { + throw STEP::SyntaxError("enum not closed",line); + } + } + inout = cur+1; + return std::make_shared<EXPRESS::ENUMERATION>(std::string(start, static_cast<size_t>(cur-start) )); + } + else if (*cur == '#' ) { + // object reference + return std::make_shared<EXPRESS::ENTITY>(strtoul10_64(++cur,&inout)); + } + else if (*cur == '\'' ) { + // string literal + const char* start = ++cur; + + for(;*cur != '\'';++cur) { + if (*cur == '\0') { + throw STEP::SyntaxError("string literal not closed",line); + } + } + + if (cur[1] == '\'') { + // Vesanen: more than 2 escaped ' in one literal! + do { + for(cur += 2;*cur != '\'';++cur) { + if (*cur == '\0') { + throw STEP::SyntaxError("string literal not closed",line); + } + } + } + while(cur[1] == '\''); + } + + inout = cur + 1; + + // assimp is supposed to output UTF8 strings, so we have to deal + // with foreign encodings. + std::string stemp = std::string(start, static_cast<size_t>(cur - start)); + if(!StringToUTF8(stemp)) { + // TODO: route this to a correct logger with line numbers etc., better error messages + ASSIMP_LOG_ERROR("an error occurred reading escape sequences in ASCII text"); + } + + return std::make_shared<EXPRESS::STRING>(stemp); + } + else if (*cur == '\"' ) { + throw STEP::SyntaxError("binary data not supported yet",line); + } + + // else -- must be a number. if there is a decimal dot in it, + // parse it as real value, otherwise as integer. + const char* start = cur; + for(;*cur && *cur != ',' && *cur != ')' && !IsSpace(*cur);++cur) { + if (*cur == '.') { + double f; + inout = fast_atoreal_move<double>(start,f); + return std::make_shared<EXPRESS::REAL>(f); + } + } + + bool neg = false; + if (*start == '-') { + neg = true; + ++start; + } + else if (*start == '+') { + ++start; + } + int64_t num = static_cast<int64_t>( strtoul10_64(start,&inout) ); + return std::make_shared<EXPRESS::INTEGER>(neg?-num:num); +} + +// ------------------------------------------------------------------------------------------------ +std::shared_ptr<const EXPRESS::LIST> EXPRESS::LIST::Parse(const char*& inout,uint64_t line, const EXPRESS::ConversionSchema* schema /*= nullptr*/) { + const std::shared_ptr<EXPRESS::LIST> list = std::make_shared<EXPRESS::LIST>(); + EXPRESS::LIST::MemberList& members = list->members; + + const char* cur = inout; + if (*cur++ != '(') { + throw STEP::SyntaxError("unexpected token, expected \'(\' token at beginning of list",line); + } + + // estimate the number of items upfront - lists can grow large + size_t count = 1; + for(const char* c=cur; *c && *c != ')'; ++c) { + count += (*c == ',' ? 1 : 0); + } + + members.reserve(count); + + for(;;++cur) { + if (!*cur) { + throw STEP::SyntaxError("unexpected end of line while reading list"); + } + SkipSpaces(cur,&cur); + if (*cur == ')') { + break; + } + + members.push_back( EXPRESS::DataType::Parse(cur,line,schema)); + SkipSpaces(cur,&cur); + + if (*cur != ',') { + if (*cur == ')') { + break; + } + throw STEP::SyntaxError("unexpected token, expected \',\' or \')\' token after list element",line); + } + } + + inout = cur+1; + return list; +} + +// ------------------------------------------------------------------------------------------------ +static void handleSkippedDepthFromToken(const char *a, int64_t &skip_depth ) { + if (*a == '(') { + ++skip_depth; + } else if (*a == ')') { + --skip_depth; + } +} + +// ------------------------------------------------------------------------------------------------ +static int64_t getIdFromToken(const char *a) { + const char *tmp; + const int64_t num = static_cast<int64_t>(strtoul10_64(a + 1, &tmp)); + + return num; +} + +// ------------------------------------------------------------------------------------------------ +STEP::LazyObject::LazyObject(DB& db, uint64_t id,uint64_t /*line*/, const char* const type,const char* args) +: id(id) +, type(type) +, db(db) +, args(args) +, obj() { + // find any external references and store them in the database. + // this helps us emulate STEPs INVERSE fields. + if (!db.KeepInverseIndicesForType(type)) { + return; + } + + // do a quick scan through the argument tuple and watch out for entity references + const char *a( args ); + int64_t skip_depth( 0 ); + while ( *a ) { + handleSkippedDepthFromToken(a, skip_depth); + /*if (*a == '(') { + ++skip_depth; + } else if (*a == ')') { + --skip_depth; + }*/ + + if (skip_depth >= 1 && *a=='#') { + if (*(a + 1) != '#') { + /*const char *tmp; + const int64_t num = static_cast<int64_t>(strtoul10_64(a + 1, &tmp)); + db.MarkRef(num, id);*/ + db.MarkRef(getIdFromToken(a), id); + } else { + ++a; + } + } + ++a; + } +} + +// ------------------------------------------------------------------------------------------------ +STEP::LazyObject::~LazyObject() { + // make sure the right dtor/operator delete get called + if (obj) { + delete obj; + } else { + delete[] args; + } +} + +// ------------------------------------------------------------------------------------------------ +void STEP::LazyObject::LazyInit() const { + const EXPRESS::ConversionSchema& schema = db.GetSchema(); + STEP::ConvertObjectProc proc = schema.GetConverterProc(type); + + if (!proc) { + throw STEP::TypeError("unknown object type: " + std::string(type),id); + } + + const char* acopy = args; + std::shared_ptr<const EXPRESS::LIST> conv_args = EXPRESS::LIST::Parse(acopy,(uint64_t)STEP::SyntaxError::LINE_NOT_SPECIFIED,&db.GetSchema()); + delete[] args; + args = nullptr; + + // if the converter fails, it should throw an exception, but it should never return nullptr + try { + obj = proc(db,*conv_args); + } + catch(const TypeError& t) { + // augment line and entity information + throw TypeError(t.what(),id); + } + ++db.evaluated_count; + ai_assert(obj); + + // store the original id in the object instance + obj->SetID(id); +} diff --git a/libs/assimp/code/AssetLib/STEPParser/STEPFileReader.h b/libs/assimp/code/AssetLib/STEPParser/STEPFileReader.h new file mode 100644 index 0000000..8a57937 --- /dev/null +++ b/libs/assimp/code/AssetLib/STEPParser/STEPFileReader.h @@ -0,0 +1,71 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef INCLUDED_AI_STEPFILEREADER_H +#define INCLUDED_AI_STEPFILEREADER_H + +#include "AssetLib/Step/STEPFile.h" + +namespace Assimp { +namespace STEP { + +// -------------------------------------------------------------------------- +/// @brief Parsing a STEP file is a twofold procedure. +/// 1) read file header and return to caller, who checks if the +/// file is of a supported schema .. +DB* ReadFileHeader(std::shared_ptr<IOStream> stream); + +/// 2) read the actual file contents using a user-supplied set of +/// conversion functions to interpret the data. +void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, const char* const* types_to_track, size_t len, const char* const* inverse_indices_to_track, size_t len2); + +/// @brief Helper to read a file. +template <size_t N, size_t N2> +inline +void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, const char* const (&arr)[N], const char* const (&arr2)[N2]) { + return ReadFile(db,scheme,arr,N,arr2,N2); +} + +} // ! STEP +} // ! Assimp + +#endif // INCLUDED_AI_STEPFILEREADER_H diff --git a/libs/assimp/code/AssetLib/STL/STLExporter.cpp b/libs/assimp/code/AssetLib/STL/STLExporter.cpp new file mode 100644 index 0000000..9bbc206 --- /dev/null +++ b/libs/assimp/code/AssetLib/STL/STLExporter.cpp @@ -0,0 +1,233 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + + + +#if !defined(ASSIMP_BUILD_NO_EXPORT) && !defined(ASSIMP_BUILD_NO_STL_EXPORTER) + +#include "STLExporter.h" +#include <assimp/version.h> +#include <assimp/IOSystem.hpp> +#include <assimp/scene.h> +#include <assimp/Exporter.hpp> +#include <memory> +#include <assimp/Exceptional.h> +#include <assimp/ByteSwapper.h> + +using namespace Assimp; + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +// Worker function for exporting a scene to Stereolithograpy. Prototyped and registered in Exporter.cpp +void ExportSceneSTL(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties ) +{ + bool exportPointClouds = pProperties->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS); + + // invoke the exporter + STLExporter exporter(pFile, pScene, exportPointClouds ); + + if (exporter.mOutput.fail()) { + throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); + } + + // we're still here - export successfully completed. Write the file. + std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt")); + if (outfile == nullptr) { + throw DeadlyExportError("could not open output .stl file: " + std::string(pFile)); + } + + outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1); +} +void ExportSceneSTLBinary(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties ) +{ + bool exportPointClouds = pProperties->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS); + + // invoke the exporter + STLExporter exporter(pFile, pScene, exportPointClouds, true); + + if (exporter.mOutput.fail()) { + throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); + } + + // we're still here - export successfully completed. Write the file. + std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wb")); + if (outfile == nullptr) { + throw DeadlyExportError("could not open output .stl file: " + std::string(pFile)); + } + + outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1); +} + +} // end of namespace Assimp + +static const char *SolidToken = "solid"; +static const char *EndSolidToken = "endsolid"; + +// ------------------------------------------------------------------------------------------------ +STLExporter::STLExporter(const char* _filename, const aiScene* pScene, bool exportPointClouds, bool binary) +: filename(_filename) +, endl("\n") +{ + // make sure that all formatting happens using the standard, C locale and not the user's current locale + const std::locale& l = std::locale("C"); + mOutput.imbue(l); + mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); + if (binary) { + char buf[80] = {0} ; + buf[0] = 'A'; buf[1] = 's'; buf[2] = 's'; buf[3] = 'i'; buf[4] = 'm'; buf[5] = 'p'; + buf[6] = 'S'; buf[7] = 'c'; buf[8] = 'e'; buf[9] = 'n'; buf[10] = 'e'; + mOutput.write(buf, 80); + unsigned int meshnum = 0; + for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + for (unsigned int j = 0; j < pScene->mMeshes[i]->mNumFaces; ++j) { + meshnum++; + } + } + AI_SWAP4(meshnum); + mOutput.write((char *)&meshnum, 4); + + if (exportPointClouds) { + throw DeadlyExportError("This functionality is not yet implemented for binary output."); + } + + for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + WriteMeshBinary(pScene->mMeshes[i]); + } + } else { + + // Exporting only point clouds + if (exportPointClouds) { + WritePointCloud("Assimp_Pointcloud", pScene ); + return; + } + + // Export the assimp mesh + const std::string name = "AssimpScene"; + mOutput << SolidToken << " " << name << endl; + for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + WriteMesh(pScene->mMeshes[ i ]); + } + mOutput << EndSolidToken << " " << name << endl; + } +} + +// ------------------------------------------------------------------------------------------------ +void STLExporter::WritePointCloud(const std::string &name, const aiScene* pScene) { + mOutput << " " << SolidToken << " " << name << endl; + aiVector3D nor; + mOutput << " facet normal " << nor.x << " " << nor.y << " " << nor.z << endl; + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + aiMesh *mesh = pScene->mMeshes[i]; + if (nullptr == mesh) { + continue; + } + + for (unsigned int a = 0; a < mesh->mNumVertices; ++a) { + const aiVector3D& v = mesh->mVertices[a]; + mOutput << " vertex " << v.x << " " << v.y << " " << v.z << endl; + mOutput << " vertex " << v.x << " " << v.y << " " << v.z << endl; + mOutput << " vertex " << v.x << " " << v.y << " " << v.z << endl; + } + } + mOutput << EndSolidToken << " " << name << endl; +} + +// ------------------------------------------------------------------------------------------------ +void STLExporter::WriteMesh(const aiMesh* m) +{ + for (unsigned int i = 0; i < m->mNumFaces; ++i) { + const aiFace& f = m->mFaces[i]; + + // we need per-face normals. We specified aiProcess_GenNormals as pre-requisite for this exporter, + // but nonetheless we have to expect per-vertex normals. + aiVector3D nor; + if (m->mNormals) { + for(unsigned int a = 0; a < f.mNumIndices; ++a) { + nor += m->mNormals[f.mIndices[a]]; + } + nor.NormalizeSafe(); + } + mOutput << " facet normal " << nor.x << " " << nor.y << " " << nor.z << endl; + mOutput << " outer loop" << endl; + for(unsigned int a = 0; a < f.mNumIndices; ++a) { + const aiVector3D& v = m->mVertices[f.mIndices[a]]; + mOutput << " vertex " << v.x << " " << v.y << " " << v.z << endl; + } + + mOutput << " endloop" << endl; + mOutput << " endfacet" << endl << endl; + } +} + +void STLExporter::WriteMeshBinary(const aiMesh* m) +{ + for (unsigned int i = 0; i < m->mNumFaces; ++i) { + const aiFace& f = m->mFaces[i]; + // we need per-face normals. We specified aiProcess_GenNormals as pre-requisite for this exporter, + // but nonetheless we have to expect per-vertex normals. + aiVector3D nor; + if (m->mNormals) { + for(unsigned int a = 0; a < f.mNumIndices; ++a) { + nor += m->mNormals[f.mIndices[a]]; + } + nor.Normalize(); + } + // STL binary files use 4-byte floats. This may possibly cause loss of precision + // for clients using 8-byte doubles + float nx = (float) nor.x; + float ny = (float) nor.y; + float nz = (float) nor.z; + AI_SWAP4(nx); AI_SWAP4(ny); AI_SWAP4(nz); + mOutput.write((char *)&nx, 4); mOutput.write((char *)&ny, 4); mOutput.write((char *)&nz, 4); + for(unsigned int a = 0; a < f.mNumIndices; ++a) { + const aiVector3D& v = m->mVertices[f.mIndices[a]]; + float vx = (float) v.x, vy = (float) v.y, vz = (float) v.z; + AI_SWAP4(vx); AI_SWAP4(vy); AI_SWAP4(vz); + mOutput.write((char *)&vx, 4); mOutput.write((char *)&vy, 4); mOutput.write((char *)&vz, 4); + } + char dummy[2] = {0}; + mOutput.write(dummy, 2); + } +} + +#endif diff --git a/libs/assimp/code/AssetLib/STL/STLExporter.h b/libs/assimp/code/AssetLib/STL/STLExporter.h new file mode 100644 index 0000000..066dcfe --- /dev/null +++ b/libs/assimp/code/AssetLib/STL/STLExporter.h @@ -0,0 +1,81 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 STLExporter.h + * Declares the exporter class to write a scene to a Stereolithography (STL) file + */ +#pragma once +#ifndef AI_STLEXPORTER_H_INC +#define AI_STLEXPORTER_H_INC + +#include <sstream> + +struct aiScene; +struct aiNode; +struct aiMesh; + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +/** Helper class to export a given scene to a STL file. */ +// ------------------------------------------------------------------------------------------------ +class STLExporter { +public: + /// Constructor for a specific scene to export + STLExporter(const char *filename, const aiScene *pScene, bool exportPOintClouds, bool binary = false); + + /// public string-streams to write all output into + std::ostringstream mOutput; + +private: + void WritePointCloud(const std::string &name, const aiScene *pScene); + void WriteMesh(const aiMesh *m); + void WriteMeshBinary(const aiMesh *m); + +private: + const std::string filename; + const std::string endl; +}; + +} // namespace Assimp + +#endif diff --git a/libs/assimp/code/AssetLib/STL/STLLoader.cpp b/libs/assimp/code/AssetLib/STL/STLLoader.cpp new file mode 100644 index 0000000..8de57f1 --- /dev/null +++ b/libs/assimp/code/AssetLib/STL/STLLoader.cpp @@ -0,0 +1,575 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the STL importer class */ + +#ifndef ASSIMP_BUILD_NO_STL_IMPORTER + +#include "STLLoader.h" +#include <assimp/ParsingUtils.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <memory> + +using namespace Assimp; + +namespace { + +static const aiImporterDesc desc = { + "Stereolithography (STL) Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "stl" +}; + +// A valid binary STL buffer should consist of the following elements, in order: +// 1) 80 byte header +// 2) 4 byte face count +// 3) 50 bytes per face +static bool IsBinarySTL(const char *buffer, unsigned int fileSize) { + if (fileSize < 84) { + return false; + } + + const char *facecount_pos = buffer + 80; + uint32_t faceCount(0); + ::memcpy(&faceCount, facecount_pos, sizeof(uint32_t)); + const uint32_t expectedBinaryFileSize = faceCount * 50 + 84; + + return expectedBinaryFileSize == fileSize; +} + +static const size_t BufferSize = 500; +static const char UnicodeBoundary = 127; + +// An ascii STL buffer will begin with "solid NAME", where NAME is optional. +// Note: The "solid NAME" check is necessary, but not sufficient, to determine +// if the buffer is ASCII; a binary header could also begin with "solid NAME". +static bool IsAsciiSTL(const char *buffer, unsigned int fileSize) { + if (IsBinarySTL(buffer, fileSize)) + return false; + + const char *bufferEnd = buffer + fileSize; + + if (!SkipSpaces(&buffer)) { + return false; + } + + if (buffer + 5 >= bufferEnd) { + return false; + } + + bool isASCII(strncmp(buffer, "solid", 5) == 0); + if (isASCII) { + // A lot of importers are write solid even if the file is binary. So we have to check for ASCII-characters. + if (fileSize >= BufferSize) { + isASCII = true; + for (unsigned int i = 0; i < BufferSize; i++) { + if (buffer[i] > UnicodeBoundary) { + isASCII = false; + break; + } + } + } + } + return isASCII; +} +} // namespace + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +STLImporter::STLImporter() : + mBuffer(), + mFileSize(0), + mScene() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +STLImporter::~STLImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool STLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "STL", "solid" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc *STLImporter::GetInfo() const { + return &desc; +} + +void addFacesToMesh(aiMesh *pMesh) { + pMesh->mFaces = new aiFace[pMesh->mNumFaces]; + for (unsigned int i = 0, p = 0; i < pMesh->mNumFaces; ++i) { + + aiFace &face = pMesh->mFaces[i]; + face.mIndices = new unsigned int[face.mNumIndices = 3]; + for (unsigned int o = 0; o < 3; ++o, ++p) { + face.mIndices[o] = p; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void STLImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb")); + + // Check whether we can read from the file + if (file.get() == nullptr) { + throw DeadlyImportError("Failed to open STL file ", pFile, "."); + } + + mFileSize = (unsigned int)file->FileSize(); + + // allocate storage and copy the contents of the file to a memory buffer + // (terminate it with zero) + std::vector<char> buffer2; + TextFileToBuffer(file.get(), buffer2); + + mScene = pScene; + mBuffer = &buffer2[0]; + + // the default vertex color is light gray. + mClrColorDefault.r = mClrColorDefault.g = mClrColorDefault.b = mClrColorDefault.a = (ai_real)0.6; + + // allocate a single node + mScene->mRootNode = new aiNode(); + + bool bMatClr = false; + + if (IsBinarySTL(mBuffer, mFileSize)) { + bMatClr = LoadBinaryFile(); + } else if (IsAsciiSTL(mBuffer, mFileSize)) { + LoadASCIIFile(mScene->mRootNode); + } else { + throw DeadlyImportError("Failed to determine STL storage representation for ", pFile, "."); + } + + // create a single default material, using a white diffuse color for consistency with + // other geometric types (e.g., PLY). + aiMaterial *pcMat = new aiMaterial(); + aiString s; + s.Set(AI_DEFAULT_MATERIAL_NAME); + pcMat->AddProperty(&s, AI_MATKEY_NAME); + + aiColor4D clrDiffuse(ai_real(1.0), ai_real(1.0), ai_real(1.0), ai_real(1.0)); + if (bMatClr) { + clrDiffuse = mClrColorDefault; + } + pcMat->AddProperty(&clrDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + pcMat->AddProperty(&clrDiffuse, 1, AI_MATKEY_COLOR_SPECULAR); + clrDiffuse = aiColor4D(ai_real(0.05), ai_real(0.05), ai_real(0.05), ai_real(1.0)); + pcMat->AddProperty(&clrDiffuse, 1, AI_MATKEY_COLOR_AMBIENT); + + mScene->mNumMaterials = 1; + mScene->mMaterials = new aiMaterial *[1]; + mScene->mMaterials[0] = pcMat; + + mBuffer = nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// Read an ASCII STL file +void STLImporter::LoadASCIIFile(aiNode *root) { + std::vector<aiMesh *> meshes; + std::vector<aiNode *> nodes; + const char *sz = mBuffer; + const char *bufferEnd = mBuffer + mFileSize; + std::vector<aiVector3D> positionBuffer; + std::vector<aiVector3D> normalBuffer; + + // try to guess how many vertices we could have + // assume we'll need 160 bytes for each face + size_t sizeEstimate = std::max(1u, mFileSize / 160u) * 3; + positionBuffer.reserve(sizeEstimate); + normalBuffer.reserve(sizeEstimate); + + while (IsAsciiSTL(sz, static_cast<unsigned int>(bufferEnd - sz))) { + std::vector<unsigned int> meshIndices; + aiMesh *pMesh = new aiMesh(); + pMesh->mMaterialIndex = 0; + meshIndices.push_back((unsigned int)meshes.size()); + meshes.push_back(pMesh); + aiNode *node = new aiNode; + node->mParent = root; + nodes.push_back(node); + SkipSpaces(&sz); + ai_assert(!IsLineEnd(sz)); + + sz += 5; // skip the "solid" + SkipSpaces(&sz); + const char *szMe = sz; + while (!::IsSpaceOrNewLine(*sz)) { + sz++; + } + + size_t temp = (size_t)(sz - szMe); + // setup the name of the node + if ( temp ) { + if (temp >= MAXLEN) { + throw DeadlyImportError("STL: Node name too long"); + } + std::string name(szMe, temp); + node->mName.Set(name.c_str()); + pMesh->mName.Set(name.c_str()); + } else { + mScene->mRootNode->mName.Set("<STL_ASCII>"); + } + + unsigned int faceVertexCounter = 3; + for (;;) { + // go to the next token + if (!SkipSpacesAndLineEnd(&sz)) { + // seems we're finished although there was no end marker + ASSIMP_LOG_WARN("STL: unexpected EOF. \'endsolid\' keyword was expected"); + break; + } + // facet normal -0.13 -0.13 -0.98 + if (!strncmp(sz, "facet", 5) && IsSpaceOrNewLine(*(sz + 5)) && *(sz + 5) != '\0') { + + if (faceVertexCounter != 3) { + ASSIMP_LOG_WARN("STL: A new facet begins but the old is not yet complete"); + } + faceVertexCounter = 0; + normalBuffer.push_back(aiVector3D()); + aiVector3D *vn = &normalBuffer.back(); + + sz += 6; + SkipSpaces(&sz); + if (strncmp(sz, "normal", 6)) { + ASSIMP_LOG_WARN("STL: a facet normal vector was expected but not found"); + } else { + if (sz[6] == '\0') { + throw DeadlyImportError("STL: unexpected EOF while parsing facet"); + } + sz += 7; + SkipSpaces(&sz); + sz = fast_atoreal_move<ai_real>(sz, (ai_real &)vn->x); + SkipSpaces(&sz); + sz = fast_atoreal_move<ai_real>(sz, (ai_real &)vn->y); + SkipSpaces(&sz); + sz = fast_atoreal_move<ai_real>(sz, (ai_real &)vn->z); + normalBuffer.push_back(*vn); + normalBuffer.push_back(*vn); + } + } else if (!strncmp(sz, "vertex", 6) && ::IsSpaceOrNewLine(*(sz + 6))) { // vertex 1.50000 1.50000 0.00000 + if (faceVertexCounter >= 3) { + ASSIMP_LOG_ERROR("STL: a facet with more than 3 vertices has been found"); + ++sz; + } else { + if (sz[6] == '\0') { + throw DeadlyImportError("STL: unexpected EOF while parsing facet"); + } + sz += 7; + SkipSpaces(&sz); + positionBuffer.push_back(aiVector3D()); + aiVector3D *vn = &positionBuffer.back(); + sz = fast_atoreal_move<ai_real>(sz, (ai_real &)vn->x); + SkipSpaces(&sz); + sz = fast_atoreal_move<ai_real>(sz, (ai_real &)vn->y); + SkipSpaces(&sz); + sz = fast_atoreal_move<ai_real>(sz, (ai_real &)vn->z); + faceVertexCounter++; + } + } else if (!::strncmp(sz, "endsolid", 8)) { + do { + ++sz; + } while (!::IsLineEnd(*sz)); + SkipSpacesAndLineEnd(&sz); + // finished! + break; + } else { // else skip the whole identifier + do { + ++sz; + } while (!::IsSpaceOrNewLine(*sz)); + } + } + + if (positionBuffer.empty()) { + pMesh->mNumFaces = 0; + ASSIMP_LOG_WARN("STL: mesh is empty or invalid; no data loaded"); + } + if (positionBuffer.size() % 3 != 0) { + pMesh->mNumFaces = 0; + throw DeadlyImportError("STL: Invalid number of vertices"); + } + if (normalBuffer.size() != positionBuffer.size()) { + pMesh->mNumFaces = 0; + throw DeadlyImportError("Normal buffer size does not match position buffer size"); + } + + // only process positionbuffer when filled, else exception when accessing with index operator + // see line 353: only warning is triggered + // see line 373(now): access to empty positionbuffer with index operator forced exception + if (!positionBuffer.empty()) { + pMesh->mNumFaces = static_cast<unsigned int>(positionBuffer.size() / 3); + pMesh->mNumVertices = static_cast<unsigned int>(positionBuffer.size()); + pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; + for (size_t i=0; i<pMesh->mNumVertices; ++i ) { + pMesh->mVertices[i].x = positionBuffer[i].x; + pMesh->mVertices[i].y = positionBuffer[i].y; + pMesh->mVertices[i].z = positionBuffer[i].z; + } + positionBuffer.clear(); + } + // also only process normalBuffer when filled, else exception when accessing with index operator + if (!normalBuffer.empty()) { + pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + for (size_t i=0; i<pMesh->mNumVertices; ++i ) { + pMesh->mNormals[i].x = normalBuffer[i].x; + pMesh->mNormals[i].y = normalBuffer[i].y; + pMesh->mNormals[i].z = normalBuffer[i].z; + } + normalBuffer.clear(); + } + + // now copy faces + addFacesToMesh(pMesh); + + // assign the meshes to the current node + pushMeshesToNode(meshIndices, node); + } + + // now add the loaded meshes + mScene->mNumMeshes = (unsigned int)meshes.size(); + mScene->mMeshes = new aiMesh *[mScene->mNumMeshes]; + for (size_t i = 0; i < meshes.size(); i++) { + mScene->mMeshes[i] = meshes[i]; + } + + root->mNumChildren = (unsigned int)nodes.size(); + root->mChildren = new aiNode *[root->mNumChildren]; + for (size_t i = 0; i < nodes.size(); ++i) { + root->mChildren[i] = nodes[i]; + } +} + +// ------------------------------------------------------------------------------------------------ +// Read a binary STL file +bool STLImporter::LoadBinaryFile() { + // allocate one mesh + mScene->mNumMeshes = 1; + mScene->mMeshes = new aiMesh *[1]; + aiMesh *pMesh = mScene->mMeshes[0] = new aiMesh(); + pMesh->mMaterialIndex = 0; + + // skip the first 80 bytes + if (mFileSize < 84) { + throw DeadlyImportError("STL: file is too small for the header"); + } + bool bIsMaterialise = false; + + // search for an occurrence of "COLOR=" in the header + const unsigned char *sz2 = (const unsigned char *)mBuffer; + const unsigned char *const szEnd = sz2 + 80; + while (sz2 < szEnd) { + + if ('C' == *sz2++ && 'O' == *sz2++ && 'L' == *sz2++ && + 'O' == *sz2++ && 'R' == *sz2++ && '=' == *sz2++) { + + // read the default vertex color for facets + bIsMaterialise = true; + ASSIMP_LOG_INFO("STL: Taking code path for Materialise files"); + const ai_real invByte = (ai_real)1.0 / (ai_real)255.0; + mClrColorDefault.r = (*sz2++) * invByte; + mClrColorDefault.g = (*sz2++) * invByte; + mClrColorDefault.b = (*sz2++) * invByte; + mClrColorDefault.a = (*sz2++) * invByte; + break; + } + } + const unsigned char *sz = (const unsigned char *)mBuffer + 80; + + // now read the number of facets + mScene->mRootNode->mName.Set("<STL_BINARY>"); + + pMesh->mNumFaces = *((uint32_t *)sz); + sz += 4; + + if (mFileSize < 84 + pMesh->mNumFaces * 50) { + throw DeadlyImportError("STL: file is too small to hold all facets"); + } + + if (!pMesh->mNumFaces) { + throw DeadlyImportError("STL: file is empty. There are no facets defined"); + } + + pMesh->mNumVertices = pMesh->mNumFaces * 3; + + aiVector3D *vp = pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; + aiVector3D *vn = pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + + typedef aiVector3t<float> aiVector3F; + aiVector3F *theVec; + aiVector3F theVec3F; + + for (unsigned int i = 0; i < pMesh->mNumFaces; ++i) { + // NOTE: Blender sometimes writes empty normals ... this is not + // our fault ... the RemoveInvalidData helper step should fix that + + // There's one normal for the face in the STL; use it three times + // for vertex normals + theVec = (aiVector3F *)sz; + ::memcpy(&theVec3F, theVec, sizeof(aiVector3F)); + vn->x = theVec3F.x; + vn->y = theVec3F.y; + vn->z = theVec3F.z; + *(vn + 1) = *vn; + *(vn + 2) = *vn; + ++theVec; + vn += 3; + + // vertex 1 + ::memcpy(&theVec3F, theVec, sizeof(aiVector3F)); + vp->x = theVec3F.x; + vp->y = theVec3F.y; + vp->z = theVec3F.z; + ++theVec; + ++vp; + + // vertex 2 + ::memcpy(&theVec3F, theVec, sizeof(aiVector3F)); + vp->x = theVec3F.x; + vp->y = theVec3F.y; + vp->z = theVec3F.z; + ++theVec; + ++vp; + + // vertex 3 + ::memcpy(&theVec3F, theVec, sizeof(aiVector3F)); + vp->x = theVec3F.x; + vp->y = theVec3F.y; + vp->z = theVec3F.z; + ++theVec; + ++vp; + + sz = (const unsigned char *)theVec; + + uint16_t color = *((uint16_t *)sz); + sz += 2; + + if (color & (1 << 15)) { + // seems we need to take the color + if (!pMesh->mColors[0]) { + pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices]; + for (unsigned int j = 0; j < pMesh->mNumVertices; ++j) { + *pMesh->mColors[0]++ = mClrColorDefault; + } + pMesh->mColors[0] -= pMesh->mNumVertices; + + ASSIMP_LOG_INFO("STL: Mesh has vertex colors"); + } + aiColor4D *clr = &pMesh->mColors[0][i * 3]; + clr->a = 1.0; + const ai_real invVal((ai_real)1.0 / (ai_real)31.0); + if (bIsMaterialise) // this is reversed + { + clr->r = (color & 0x31u) * invVal; + clr->g = ((color & (0x31u << 5)) >> 5u) * invVal; + clr->b = ((color & (0x31u << 10)) >> 10u) * invVal; + } else { + clr->b = (color & 0x31u) * invVal; + clr->g = ((color & (0x31u << 5)) >> 5u) * invVal; + clr->r = ((color & (0x31u << 10)) >> 10u) * invVal; + } + // assign the color to all vertices of the face + *(clr + 1) = *clr; + *(clr + 2) = *clr; + } + } + + // now copy faces + addFacesToMesh(pMesh); + + aiNode *root = mScene->mRootNode; + + // allocate one node + aiNode *node = new aiNode(); + node->mParent = root; + + root->mNumChildren = 1u; + root->mChildren = new aiNode *[root->mNumChildren]; + root->mChildren[0] = node; + + // add all created meshes to the single node + node->mNumMeshes = mScene->mNumMeshes; + node->mMeshes = new unsigned int[mScene->mNumMeshes]; + for (unsigned int i = 0; i < mScene->mNumMeshes; ++i) { + node->mMeshes[i] = i; + } + + if (bIsMaterialise && !pMesh->mColors[0]) { + // use the color as diffuse material color + return true; + } + return false; +} + +void STLImporter::pushMeshesToNode(std::vector<unsigned int> &meshIndices, aiNode *node) { + ai_assert(nullptr != node); + if (meshIndices.empty()) { + return; + } + + node->mNumMeshes = static_cast<unsigned int>(meshIndices.size()); + node->mMeshes = new unsigned int[meshIndices.size()]; + for (size_t i = 0; i < meshIndices.size(); ++i) { + node->mMeshes[i] = meshIndices[i]; + } + meshIndices.clear(); +} + +#endif // !! ASSIMP_BUILD_NO_STL_IMPORTER diff --git a/libs/assimp/code/AssetLib/STL/STLLoader.h b/libs/assimp/code/AssetLib/STL/STLLoader.h new file mode 100644 index 0000000..a340abe --- /dev/null +++ b/libs/assimp/code/AssetLib/STL/STLLoader.h @@ -0,0 +1,123 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 STLLoader.h + * Declaration of the STL importer class. + */ +#ifndef AI_STLLOADER_H_INCLUDED +#define AI_STLLOADER_H_INCLUDED + +#include <assimp/BaseImporter.h> +#include <assimp/types.h> + +// Forward declarations +struct aiNode; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** + * @brief Importer class for the sterolithography STL file format. + */ +class STLImporter : public BaseImporter { +public: + /** + * @brief STLImporter, the class default constructor. + */ + STLImporter(); + + /** + * @brief The class destructor. + */ + ~STLImporter() override; + + /** + * @brief Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override; + +protected: + + /** + * @brief Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc* GetInfo () const override; + + /** + * @brief Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler) override; + + /** + * @brief Loads a binary .stl file + * @return true if the default vertex color must be used as material color + */ + bool LoadBinaryFile(); + + /** + * @brief Loads a ASCII text .stl file + */ + void LoadASCIIFile( aiNode *root ); + + void pushMeshesToNode( std::vector<unsigned int> &meshIndices, aiNode *node ); + +protected: + + /** Buffer to hold the loaded file */ + const char* mBuffer; + + /** Size of the file, in bytes */ + unsigned int mFileSize; + + /** Output scene */ + aiScene* mScene; + + /** Default vertex color */ + aiColor4D mClrColorDefault; +}; + +} // end of namespace Assimp + +#endif // AI_3DSIMPORTER_H_IN diff --git a/libs/assimp/code/AssetLib/Step/STEPFile.h b/libs/assimp/code/AssetLib/Step/STEPFile.h new file mode 100644 index 0000000..e09faad --- /dev/null +++ b/libs/assimp/code/AssetLib/Step/STEPFile.h @@ -0,0 +1,971 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef INCLUDED_AI_STEPFILE_H +#define INCLUDED_AI_STEPFILE_H + +#include <bitset> +#include <map> +#include <memory> +#include <set> +#include <typeinfo> +#include <vector> + +#include "AssetLib/FBX/FBXDocument.h" //ObjectMap::value_type + +#include <assimp/DefaultLogger.hpp> + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4127 4456 4245 4512 ) +#endif // _MSC_VER + +// +#if _MSC_VER > 1500 || (defined __GNUC___) +# define ASSIMP_STEP_USE_UNORDERED_MULTIMAP +#else +# define step_unordered_map map +# define step_unordered_multimap multimap +#endif + +#ifdef ASSIMP_STEP_USE_UNORDERED_MULTIMAP +# include <unordered_map> +# if defined(_MSC_VER) && _MSC_VER <= 1600 +# define step_unordered_map tr1::unordered_map +# define step_unordered_multimap tr1::unordered_multimap +# else +# define step_unordered_map unordered_map +# define step_unordered_multimap unordered_multimap +# endif +#endif + +#include <assimp/LineSplitter.h> + +// uncomment this to have the loader evaluate all entities upon loading. +// this is intended as stress test - by default, entities are evaluated +// lazily and therefore not unless needed. + +//#define ASSIMP_IFC_TEST + +namespace Assimp { + +// ******************************************************************************** +// before things get complicated, this is the basic outline: + +namespace STEP { + +namespace EXPRESS { + +// base data types known by EXPRESS schemata - any custom data types will derive one of those +class DataType; +class UNSET; /*: public DataType */ +class ISDERIVED; /*: public DataType */ +// class REAL; /*: public DataType */ +class ENUM; /*: public DataType */ +// class STRING; /*: public DataType */ +// class INTEGER; /*: public DataType */ +class ENTITY; /*: public DataType */ +class LIST; /*: public DataType */ +// class SELECT; /*: public DataType */ + +// a conversion schema is not exactly an EXPRESS schema, rather it +// is a list of pointers to conversion functions to build up the +// object tree from an input file. +class ConversionSchema; +} // namespace EXPRESS + +struct HeaderInfo; +class Object; +class LazyObject; +class DB; + +typedef Object *(*ConvertObjectProc)(const DB &db, const EXPRESS::LIST ¶ms); +} // namespace STEP + +// ******************************************************************************** + +namespace STEP { + +// ------------------------------------------------------------------------------- +/** Exception class used by the STEP loading & parsing code. It is typically + * coupled with a line number. */ +// ------------------------------------------------------------------------------- +struct SyntaxError : DeadlyImportError { + enum : uint64_t { + LINE_NOT_SPECIFIED = 0xfffffffffffffffLL + }; + + SyntaxError(const std::string &s, uint64_t line = LINE_NOT_SPECIFIED); +}; + +// ------------------------------------------------------------------------------- +/** Exception class used by the STEP loading & parsing code when a type + * error (i.e. an entity expects a string but receives a bool) occurs. + * It is typically coupled with both an entity id and a line number.*/ +// ------------------------------------------------------------------------------- +struct TypeError : DeadlyImportError { + enum : uint64_t { + ENTITY_NOT_SPECIFIED = 0xffffffffffffffffUL, + ENTITY_NOT_SPECIFIED_32 = 0x00000000ffffffff + }; + + TypeError(const std::string &s, uint64_t entity = ENTITY_NOT_SPECIFIED, uint64_t line = SyntaxError::LINE_NOT_SPECIFIED); +}; + +// hack to make a given member template-dependent +template <typename T, typename T2> +T2 &Couple(T2 &in) { + return in; +} + +namespace EXPRESS { + +// ------------------------------------------------------------------------------- +//** Base class for all STEP data types */ +// ------------------------------------------------------------------------------- +class DataType { +public: + typedef std::shared_ptr<const DataType> Out; + +public: + virtual ~DataType() { + } + +public: + template <typename T> + const T &To() const { + return dynamic_cast<const T &>(*this); + } + + template <typename T> + T &To() { + return dynamic_cast<T &>(*this); + } + + template <typename T> + const T *ToPtr() const { + return dynamic_cast<const T *>(this); + } + + template <typename T> + T *ToPtr() { + return dynamic_cast<T *>(this); + } + + // utilities to deal with SELECT entities, which currently lack automatic + // conversion support. + template <typename T> + const T &ResolveSelect(const DB &db) const { + return Couple<T>(db).MustGetObject(To<EXPRESS::ENTITY>())->template To<T>(); + } + + template <typename T> + const T *ResolveSelectPtr(const DB &db) const { + const EXPRESS::ENTITY *e = ToPtr<EXPRESS::ENTITY>(); + return e ? Couple<T>(db).MustGetObject(*e)->template ToPtr<T>() : (const T *)0; + } + +public: + /** parse a variable from a string and set 'inout' to the character + * behind the last consumed character. An optional schema enables, + * if specified, automatic conversion of custom data types. + * + * @throw SyntaxError + */ + static std::shared_ptr<const EXPRESS::DataType> Parse(const char *&inout, + uint64_t line = SyntaxError::LINE_NOT_SPECIFIED, + const EXPRESS::ConversionSchema *schema = NULL); + +public: +}; + +typedef DataType SELECT; +typedef DataType LOGICAL; + +// ------------------------------------------------------------------------------- +/** Sentinel class to represent explicitly unset (optional) fields ($) */ +// ------------------------------------------------------------------------------- +class UNSET : public DataType { +public: +private: +}; + +// ------------------------------------------------------------------------------- +/** Sentinel class to represent explicitly derived fields (*) */ +// ------------------------------------------------------------------------------- +class ISDERIVED : public DataType { +public: +private: +}; + +// ------------------------------------------------------------------------------- +/** Shared implementation for some of the primitive data type, i.e. int, float */ +// ------------------------------------------------------------------------------- +template <typename T> +class PrimitiveDataType : public DataType { +public: + // This is the type that will cd ultimatively be used to + // expose this data type to the user. + typedef T Out; + + PrimitiveDataType() {} + PrimitiveDataType(const T &val) : + val(val) {} + + PrimitiveDataType(const PrimitiveDataType &o) { + (*this) = o; + } + + operator const T &() const { + return val; + } + + PrimitiveDataType &operator=(const PrimitiveDataType &o) { + val = o.val; + return *this; + } + +protected: + T val; +}; + +typedef PrimitiveDataType<int64_t> INTEGER; +typedef PrimitiveDataType<double> REAL; +typedef PrimitiveDataType<double> NUMBER; +typedef PrimitiveDataType<std::string> STRING; + +// ------------------------------------------------------------------------------- +/** Generic base class for all enumerated types */ +// ------------------------------------------------------------------------------- +class ENUMERATION : public STRING { +public: + ENUMERATION(const std::string &val) : + STRING(val) {} + +private: +}; + +typedef ENUMERATION BOOLEAN; + +// ------------------------------------------------------------------------------- +/** This is just a reference to an entity/object somewhere else */ +// ------------------------------------------------------------------------------- +class ENTITY : public PrimitiveDataType<uint64_t> { +public: + ENTITY(uint64_t val) : + PrimitiveDataType<uint64_t>(val) { + ai_assert(val != 0); + } + + ENTITY() : + PrimitiveDataType<uint64_t>(TypeError::ENTITY_NOT_SPECIFIED) { + // empty + } + +private: +}; + +// ------------------------------------------------------------------------------- +/** Wrap any STEP aggregate: LIST, SET, ... */ +// ------------------------------------------------------------------------------- +class LIST : public DataType { +public: + // access a particular list index, throw std::range_error for wrong indices + std::shared_ptr<const DataType> operator[](size_t index) const { + return members[index]; + } + + size_t GetSize() const { + return members.size(); + } + +public: + /** @see DaraType::Parse */ + static std::shared_ptr<const EXPRESS::LIST> Parse(const char *&inout, + uint64_t line = SyntaxError::LINE_NOT_SPECIFIED, + const EXPRESS::ConversionSchema *schema = NULL); + +private: + typedef std::vector<std::shared_ptr<const DataType>> MemberList; + MemberList members; +}; + +class BINARY : public PrimitiveDataType<uint32_t> { +public: + BINARY(uint32_t val) : + PrimitiveDataType<uint32_t>(val) { + // empty + } + + BINARY() : + PrimitiveDataType<uint32_t>(TypeError::ENTITY_NOT_SPECIFIED_32) { + // empty + } +}; + +// ------------------------------------------------------------------------------- +/* Not exactly a full EXPRESS schema but rather a list of conversion functions + * to extract valid C++ objects out of a STEP file. Those conversion functions + * may, however, perform further schema validations. */ +// ------------------------------------------------------------------------------- +class ConversionSchema { +public: + struct SchemaEntry { + SchemaEntry(const char *name, ConvertObjectProc func) : + mName(name), mFunc(func) { + // empty + } + + const char *mName; + ConvertObjectProc mFunc; + }; + + typedef std::map<std::string, ConvertObjectProc> ConverterMap; + + template <size_t N> + explicit ConversionSchema(const SchemaEntry (&schemas)[N]) { + *this = schemas; + } + + ConversionSchema() { + } + + ConvertObjectProc GetConverterProc(const std::string &name) const { + ConverterMap::const_iterator it = converters.find(name); + return it == converters.end() ? nullptr : (*it).second; + } + + bool IsKnownToken(const std::string &name) const { + return converters.find(name) != converters.end(); + } + + const char *GetStaticStringForToken(const std::string &token) const { + ConverterMap::const_iterator it = converters.find(token); + return it == converters.end() ? nullptr : (*it).first.c_str(); + } + + template <size_t N> + const ConversionSchema &operator=(const SchemaEntry (&schemas)[N]) { + for (size_t i = 0; i < N; ++i) { + const SchemaEntry &schema = schemas[i]; + converters[schema.mName] = schema.mFunc; + } + return *this; + } + +private: + ConverterMap converters; +}; +} // namespace EXPRESS + +// ------------------------------------------------------------------------------ +/** Bundle all the relevant info from a STEP header, parts of which may later + * be plainly dumped to the logfile, whereas others may help the caller pick an + * appropriate loading strategy.*/ +// ------------------------------------------------------------------------------ +struct HeaderInfo { + std::string timestamp; + std::string app; + std::string fileSchema; +}; + +// ------------------------------------------------------------------------------ +/** Base class for all concrete object instances */ +// ------------------------------------------------------------------------------ +class Object { +public: + Object(const char *classname = "unknown") : + id(0), classname(classname) { + // empty + } + + virtual ~Object() { + // empty + } + + // utilities to simplify casting to concrete types + template <typename T> + const T &To() const { + return dynamic_cast<const T &>(*this); + } + + template <typename T> + T &To() { + return dynamic_cast<T &>(*this); + } + + template <typename T> + const T *ToPtr() const { + return dynamic_cast<const T *>(this); + } + + template <typename T> + T *ToPtr() { + return dynamic_cast<T *>(this); + } + + uint64_t GetID() const { + return id; + } + + std::string GetClassName() const { + return classname; + } + + void SetID(uint64_t newval) { + id = newval; + } + +private: + uint64_t id; + const char *const classname; +}; + +template <typename T> +size_t GenericFill(const STEP::DB &db, const EXPRESS::LIST ¶ms, T *in); +// (intentionally undefined) + +// ------------------------------------------------------------------------------ +/** CRTP shared base class for use by concrete entity implementation classes */ +// ------------------------------------------------------------------------------ +template <typename TDerived, size_t arg_count> +struct ObjectHelper : virtual Object { + ObjectHelper() : + aux_is_derived(0) { + // empty + } + + static Object *Construct(const STEP::DB &db, const EXPRESS::LIST ¶ms) { + // make sure we don't leak if Fill() throws an exception + std::unique_ptr<TDerived> impl(new TDerived()); + + // GenericFill<T> is undefined so we need to have a specialization + const size_t num_args = GenericFill<TDerived>(db, params, &*impl); + (void)num_args; + + // the following check is commented because it will always trigger if + // parts of the entities are generated with dummy wrapper code. + // This is currently done to reduce the size of the loader + // code. + //if (num_args != params.GetSize() && impl->GetClassName() != "NotImplemented") { + // DefaultLogger::get()->debug("STEP: not all parameters consumed"); + //} + return impl.release(); + } + + // note that this member always exists multiple times within the hierarchy + // of an individual object, so any access to it must be disambiguated. + std::bitset<arg_count> aux_is_derived; +}; + +// ------------------------------------------------------------------------------ +/** Class template used to represent OPTIONAL data members in the converted schema */ +// ------------------------------------------------------------------------------ +template <typename T> +struct Maybe { + Maybe() : + have() { + // empty + } + + explicit Maybe(const T &ptr) : + ptr(ptr), have(true) { + // empty + } + + void flag_invalid() { + have = false; + } + + void flag_valid() { + have = true; + } + + bool operator!() const { + return !have; + } + + operator bool() const { + return have; + } + + operator const T &() const { + return Get(); + } + + const T &Get() const { + ai_assert(have); + return ptr; + } + + Maybe &operator=(const T &_ptr) { + ptr = _ptr; + have = true; + return *this; + } + +private: + template <typename T2> + friend struct InternGenericConvert; + + operator T &() { + return ptr; + } + + T ptr; + bool have; +}; + +// ------------------------------------------------------------------------------ +/** A LazyObject is created when needed. Before this happens, we just keep + the text line that contains the object definition. */ +// ------------------------------------------------------------------------------- +class LazyObject { + friend class DB; + +public: + LazyObject(DB &db, uint64_t id, uint64_t line, const char *type, const char *args); + ~LazyObject(); + + Object &operator*() { + if (!obj) { + LazyInit(); + ai_assert(obj); + } + return *obj; + } + + const Object &operator*() const { + if (!obj) { + LazyInit(); + ai_assert(obj); + } + return *obj; + } + + template <typename T> + const T &To() const { + return dynamic_cast<const T &>(**this); + } + + template <typename T> + T &To() { + return dynamic_cast<T &>(**this); + } + + template <typename T> + const T *ToPtr() const { + return dynamic_cast<const T *>(&**this); + } + + template <typename T> + T *ToPtr() { + return dynamic_cast<T *>(&**this); + } + + Object *operator->() { + return &**this; + } + + const Object *operator->() const { + return &**this; + } + + bool operator==(const std::string &atype) const { + return type == atype; + } + + bool operator!=(const std::string &atype) const { + return type != atype; + } + + uint64_t GetID() const { + return id; + } + +private: + void LazyInit() const; + +private: + mutable uint64_t id; + const char *const type; + DB &db; + mutable const char *args; + mutable Object *obj; +}; + +template <typename T> +inline bool operator==(const std::shared_ptr<LazyObject> &lo, T whatever) { + return *lo == whatever; // XXX use std::forward if we have 0x +} + +template <typename T> +inline bool operator==(const std::pair<uint64_t, std::shared_ptr<LazyObject>> &lo, T whatever) { + return *(lo.second) == whatever; // XXX use std::forward if we have 0x +} + +// ------------------------------------------------------------------------------ +/** Class template used to represent lazily evaluated object references in the converted schema */ +// ------------------------------------------------------------------------------ +template <typename T> +struct Lazy { + typedef Lazy Out; + Lazy(const LazyObject *obj = nullptr) : + obj(obj) { + // empty + } + + operator const T *() const { + return obj->ToPtr<T>(); + } + + operator const T &() const { + return obj->To<T>(); + } + + const T &operator*() const { + return obj->To<T>(); + } + + const T *operator->() const { + return &obj->To<T>(); + } + + const LazyObject *obj; +}; + +// ------------------------------------------------------------------------------ +/** Class template used to represent LIST and SET data members in the converted schema */ +// ------------------------------------------------------------------------------ +template <typename T, uint64_t min_cnt, uint64_t max_cnt = 0uL> +struct ListOf : public std::vector<typename T::Out> { + typedef typename T::Out OutScalar; + typedef ListOf Out; + + ListOf() { + static_assert(min_cnt <= max_cnt || !max_cnt, "min_cnt <= max_cnt || !max_cnt"); + } +}; + +// ------------------------------------------------------------------------------ +template <typename TOut> +struct PickBaseType { + typedef EXPRESS::PrimitiveDataType<TOut> Type; +}; + +template <typename TOut> +struct PickBaseType<Lazy<TOut>> { + typedef EXPRESS::ENTITY Type; +}; + +template <> +struct PickBaseType<std::shared_ptr<const EXPRESS::DataType>>; + +// ------------------------------------------------------------------------------ +template <typename T> +struct InternGenericConvert { + void operator()(T &out, const std::shared_ptr<const EXPRESS::DataType> &in, const STEP::DB & /*db*/) { + try { + out = dynamic_cast<const typename PickBaseType<T>::Type &>(*in); + } catch (std::bad_cast &) { + throw TypeError("type error reading literal field"); + } + } +}; + +template <> +struct InternGenericConvert<std::shared_ptr<const EXPRESS::DataType>> { + void operator()(std::shared_ptr<const EXPRESS::DataType> &out, const std::shared_ptr<const EXPRESS::DataType> &in, const STEP::DB & /*db*/) { + out = in; + } +}; + +template <typename T> +struct InternGenericConvert<Maybe<T>> { + void operator()(Maybe<T> &out, const std::shared_ptr<const EXPRESS::DataType> &in, const STEP::DB &db) { + GenericConvert((T &)out, in, db); + out.flag_valid(); + } +}; + +#if _MSC_VER > 1920 +#pragma warning(push) +#pragma warning(disable : 4127) +#endif // _WIN32 + +template <typename T, uint64_t min_cnt, uint64_t max_cnt> +struct InternGenericConvertList { + void operator()(ListOf<T, min_cnt, max_cnt> &out, const std::shared_ptr<const EXPRESS::DataType> &inp_base, const STEP::DB &db) { + + const EXPRESS::LIST *inp = dynamic_cast<const EXPRESS::LIST *>(inp_base.get()); + if (!inp) { + throw TypeError("type error reading aggregate"); + } + + // XXX is this really how the EXPRESS notation ([?:3],[1:3]) is intended? + const size_t len = inp->GetSize(); + if (0 != max_cnt && len > max_cnt) { + ASSIMP_LOG_WARN("too many aggregate elements"); + } else if (len < min_cnt) { + ASSIMP_LOG_WARN("too few aggregate elements"); + } + + out.reserve(inp->GetSize()); + for (size_t i = 0; i < inp->GetSize(); ++i) { + + out.push_back(typename ListOf<T, min_cnt, max_cnt>::OutScalar()); + try { + GenericConvert(out.back(), (*inp)[i], db); + } catch (const TypeError &t) { + throw TypeError(t.what() + std::string(" of aggregate")); + } + } + } +}; + +template <typename T> +struct InternGenericConvert<Lazy<T>> { + void operator()(Lazy<T> &out, const std::shared_ptr<const EXPRESS::DataType> &in_base, const STEP::DB &db) { + const EXPRESS::ENTITY *in = dynamic_cast<const EXPRESS::ENTITY *>(in_base.get()); + if (!in) { + throw TypeError("type error reading entity"); + } + out = Couple<T>(db).GetObject(*in); + } +}; + +template <typename T1> +inline void GenericConvert(T1 &a, const std::shared_ptr<const EXPRESS::DataType> &b, const STEP::DB &db) { + return InternGenericConvert<T1>()(a, b, db); +} + +template <typename T1, uint64_t N1, uint64_t N2> +inline void GenericConvert(ListOf<T1, N1, N2> &a, const std::shared_ptr<const EXPRESS::DataType> &b, const STEP::DB &db) { + return InternGenericConvertList<T1, N1, N2>()(a, b, db); +} + +// ------------------------------------------------------------------------------ +/** Lightweight manager class that holds the map of all objects in a + * STEP file. DB's are exclusively maintained by the functions in + * STEPFileReader.h*/ +// ------------------------------------------------------------------------------- +class DB { + friend DB *ReadFileHeader(std::shared_ptr<IOStream> stream); + friend void ReadFile(DB &db, const EXPRESS::ConversionSchema &scheme, + const char *const *types_to_track, size_t len, + const char *const *inverse_indices_to_track, size_t len2); + + friend class LazyObject; + +public: + // objects indexed by ID - this can grow pretty large (i.e some hundred million + // entries), so use raw pointers to avoid *any* overhead. + typedef std::map<uint64_t, const LazyObject *> ObjectMap; + + // objects indexed by their declarative type, but only for those that we truly want + typedef std::set<const LazyObject *> ObjectSet; + typedef std::map<std::string, ObjectSet> ObjectMapByType; + + // list of types for which to keep inverse indices for all references + // that the respective objects keep. + // the list keeps pointers to strings in static storage + typedef std::set<const char *> InverseWhitelist; + + // references - for each object id the ids of all objects which reference it + // this is used to simulate STEP inverse indices for selected types. + typedef std::step_unordered_multimap<uint64_t, uint64_t> RefMap; + typedef std::pair<RefMap::const_iterator, RefMap::const_iterator> RefMapRange; + +private: + DB(const std::shared_ptr<StreamReaderLE> &reader) : + reader(reader), splitter(*reader, true, true), evaluated_count(), schema(nullptr) {} + +public: + ~DB() { + for (ObjectMap::value_type &o : objects) { + delete o.second; + } + } + + uint64_t GetObjectCount() const { + return objects.size(); + } + + uint64_t GetEvaluatedObjectCount() const { + return evaluated_count; + } + + const HeaderInfo &GetHeader() const { + return header; + } + + const EXPRESS::ConversionSchema &GetSchema() const { + return *schema; + } + + const ObjectMap &GetObjects() const { + return objects; + } + + const ObjectMapByType &GetObjectsByType() const { + return objects_bytype; + } + + const RefMap &GetRefs() const { + return refs; + } + + bool KeepInverseIndicesForType(const char *const type) const { + return inv_whitelist.find(type) != inv_whitelist.end(); + } + + // get the yet unevaluated object record with a given id + const LazyObject *GetObject(uint64_t id) const { + const ObjectMap::const_iterator it = objects.find(id); + if (it != objects.end()) { + return (*it).second; + } + return nullptr; + } + + // get an arbitrary object out of the soup with the only restriction being its type. + const LazyObject *GetObject(const std::string &type) const { + const ObjectMapByType::const_iterator it = objects_bytype.find(type); + if (it != objects_bytype.end() && (*it).second.size()) { + return *(*it).second.begin(); + } + return NULL; + } + + // same, but raise an exception if the object doesn't exist and return a reference + const LazyObject &MustGetObject(uint64_t id) const { + const LazyObject *o = GetObject(id); + if (!o) { + throw TypeError("requested entity is not present", id); + } + return *o; + } + + const LazyObject &MustGetObject(const std::string &type) const { + const LazyObject *o = GetObject(type); + if (!o) { + throw TypeError("requested entity of type " + type + "is not present"); + } + return *o; + } + +#ifdef ASSIMP_IFC_TEST + + // evaluate *all* entities in the file. this is a power test for the loader + void EvaluateAll() { + for (ObjectMap::value_type &e : objects) { + **e.second; + } + ai_assert(evaluated_count == objects.size()); + } + +#endif + +private: + // full access only offered to close friends - they should + // use the provided getters rather than messing around with + // the members directly. + LineSplitter &GetSplitter() { + return splitter; + } + + void InternInsert(const LazyObject *lz) { + objects[lz->GetID()] = lz; + + const ObjectMapByType::iterator it = objects_bytype.find(lz->type); + if (it != objects_bytype.end()) { + (*it).second.insert(lz); + } + } + + void SetSchema(const EXPRESS::ConversionSchema &_schema) { + schema = &_schema; + } + + void SetTypesToTrack(const char *const *types, size_t N) { + for (size_t i = 0; i < N; ++i) { + objects_bytype[types[i]] = ObjectSet(); + } + } + + void SetInverseIndicesToTrack(const char *const *types, size_t N) { + for (size_t i = 0; i < N; ++i) { + const char *const sz = schema->GetStaticStringForToken(types[i]); + ai_assert(sz); + inv_whitelist.insert(sz); + } + } + + HeaderInfo &GetHeader() { + return header; + } + + void MarkRef(uint64_t who, uint64_t by_whom) { + refs.insert(std::make_pair(who, by_whom)); + } + +private: + HeaderInfo header; + ObjectMap objects; + ObjectMapByType objects_bytype; + RefMap refs; + InverseWhitelist inv_whitelist; + std::shared_ptr<StreamReaderLE> reader; + LineSplitter splitter; + uint64_t evaluated_count; + const EXPRESS::ConversionSchema *schema; +}; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + +} // namespace STEP + +} // namespace Assimp + +#endif // INCLUDED_AI_STEPFILE_H diff --git a/libs/assimp/code/AssetLib/Step/StepExporter.cpp b/libs/assimp/code/AssetLib/Step/StepExporter.cpp new file mode 100644 index 0000000..e13c9ed --- /dev/null +++ b/libs/assimp/code/AssetLib/Step/StepExporter.cpp @@ -0,0 +1,411 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +@author: Richard Steffen, 2015 +---------------------------------------------------------------------- +*/ + + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_STEP_EXPORTER + +#include "AssetLib/Step/StepExporter.h" +#include "PostProcessing/ConvertToLHProcess.h" + +#include <assimp/Bitmap.h> +#include <assimp/BaseImporter.h> +#include <assimp/fast_atof.h> +#include <assimp/SceneCombiner.h> +#include <assimp/Exceptional.h> +#include <assimp/DefaultIOSystem.h> +#include <assimp/IOSystem.hpp> +#include <assimp/scene.h> +#include <assimp/light.h> + +#include <iostream> +#include <ctime> +#include <set> +#include <map> +#include <list> +#include <memory> + +// +#if _MSC_VER > 1500 || (defined __GNUC___) +# define ASSIMP_STEP_USE_UNORDERED_MULTIMAP +# else +# define step_unordered_map map +# define step_unordered_multimap multimap +#endif + +#ifdef ASSIMP_STEP_USE_UNORDERED_MULTIMAP +# include <unordered_map> +# if defined(_MSC_VER) && _MSC_VER <= 1600 +# define step_unordered_map tr1::unordered_map +# define step_unordered_multimap tr1::unordered_multimap +# else +# define step_unordered_map unordered_map +# define step_unordered_multimap unordered_multimap +# endif +#endif + +typedef std::step_unordered_map<aiVector3D*, int> VectorIndexUMap; + +/* Tested with Step viewer v4 from www.ida-step.net */ + +using namespace Assimp; + +namespace Assimp +{ + +// ------------------------------------------------------------------------------------------------ +// Worker function for exporting a scene to Collada. Prototyped and registered in Exporter.cpp +void ExportSceneStep(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties) +{ + std::string path = DefaultIOSystem::absolutePath(std::string(pFile)); + std::string file = DefaultIOSystem::completeBaseName(std::string(pFile)); + + // create/copy Properties + ExportProperties props(*pProperties); + + // invoke the exporter + StepExporter iDoTheExportThing( pScene, pIOSystem, path, file, &props); + + // we're still here - export successfully completed. Write result to the given IOSYstem + std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt")); + if (outfile == nullptr) { + throw DeadlyExportError("could not open output .stp file: " + std::string(pFile)); + } + + // XXX maybe use a small wrapper around IOStream that behaves like std::stringstream in order to avoid the extra copy. + outfile->Write( iDoTheExportThing.mOutput.str().c_str(), static_cast<size_t>(iDoTheExportThing.mOutput.tellp()),1); +} + +} // end of namespace Assimp + + +namespace { + // Collect world transformations for each node + void CollectTrafos(const aiNode* node, std::map<const aiNode*, aiMatrix4x4>& trafos) { + const aiMatrix4x4& parent = node->mParent ? trafos[node->mParent] : aiMatrix4x4(); + trafos[node] = parent * node->mTransformation; + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + CollectTrafos(node->mChildren[i], trafos); + } + } + + // Generate a flat list of the meshes (by index) assigned to each node + void CollectMeshes(const aiNode* node, std::multimap<const aiNode*, unsigned int>& meshes) { + for (unsigned int i = 0; i < node->mNumMeshes; ++i) { + meshes.insert(std::make_pair(node, node->mMeshes[i])); + } + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + CollectMeshes(node->mChildren[i], meshes); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Constructor for a specific scene to export +StepExporter::StepExporter(const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, + const std::string& file, const ExportProperties* pProperties) : + mProperties(pProperties), mIOSystem(pIOSystem), mFile(file), mPath(path), + mScene(pScene), endstr(";\n") { + CollectTrafos(pScene->mRootNode, trafos); + CollectMeshes(pScene->mRootNode, meshes); + + // make sure that all formatting happens using the standard, C locale and not the user's current locale + mOutput.imbue(std::locale("C")); + mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); + + // start writing + WriteFile(); +} + +// ------------------------------------------------------------------------------------------------ +// Starts writing the contents +void StepExporter::WriteFile() +{ + // see http://shodhganga.inflibnet.ac.in:8080/jspui/bitstream/10603/14116/11/11_chapter%203.pdf + // note, that all realnumber values must be comma separated in x files + mOutput.setf(std::ios::fixed); + // precision for double + // see http://stackoverflow.com/questions/554063/how-do-i-print-a-double-value-with-full-precision-using-cout + mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); + + // standard color + aiColor4D fColor; + fColor.r = 0.8f; + fColor.g = 0.8f; + fColor.b = 0.8f; + + int ind = 100; // the start index to be used + std::vector<int> faceEntryLen; // numbers of entries for a triangle/face + // prepare unique (count triangles and vertices) + + VectorIndexUMap uniqueVerts; // use a map to reduce find complexity to log(n) + VectorIndexUMap::iterator it; + + for (unsigned int i=0; i<mScene->mNumMeshes; ++i) + { + aiMesh* mesh = mScene->mMeshes[i]; + for (unsigned int j=0; j<mesh->mNumFaces; ++j) + { + aiFace* face = &(mesh->mFaces[j]); + + if (face->mNumIndices >= 3) faceEntryLen.push_back(15 + 5 * face->mNumIndices); + } + for (unsigned int j=0; j<mesh->mNumVertices; ++j) + { + aiVector3D* v = &(mesh->mVertices[j]); + it =uniqueVerts.find(v); + if (it == uniqueVerts.end()) + { + uniqueVerts[v] = -1; // first mark the vector as not transformed + } + } + } + + static const unsigned int date_nb_chars = 20; + char date_str[date_nb_chars]; + std::time_t date = std::time(nullptr); + std::strftime(date_str, date_nb_chars, "%Y-%m-%dT%H:%M:%S", std::localtime(&date)); + + // write the header + mOutput << "ISO-10303-21" << endstr; + mOutput << "HEADER" << endstr; + mOutput << "FILE_DESCRIPTION(('STEP AP214'),'1')" << endstr; + mOutput << "FILE_NAME('" << mFile << ".stp','" << date_str << "',(' '),(' '),'Spatial InterOp 3D',' ',' ')" << endstr; + mOutput << "FILE_SCHEMA(('automotive_design'))" << endstr; + mOutput << "ENDSEC" << endstr; + + // write the top of data + mOutput << "DATA" << endstr; + mOutput << "#1=MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION(' ',("; + size_t countFace = faceEntryLen.size(); + size_t faceLenIndex = ind + 2 * uniqueVerts.size(); + for (size_t i=0; i<countFace; ++i) + { + mOutput << "#" << faceLenIndex; + if (i!=countFace-1) mOutput << ","; + faceLenIndex += faceEntryLen[i]; + } + mOutput << "),#6)" << endstr; + + mOutput << "#2=PRODUCT_DEFINITION_CONTEXT('',#7,'design')" << endstr; + mOutput << "#3=APPLICATION_PROTOCOL_DEFINITION('INTERNATIONAL STANDARD','automotive_design',1994,#7)" << endstr; + mOutput << "#4=PRODUCT_CATEGORY_RELATIONSHIP('NONE','NONE',#8,#9)" << endstr; + mOutput << "#5=SHAPE_DEFINITION_REPRESENTATION(#10,#11)" << endstr; + mOutput << "#6= (GEOMETRIC_REPRESENTATION_CONTEXT(3)GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#12))GLOBAL_UNIT_ASSIGNED_CONTEXT((#13,#14,#15))REPRESENTATION_CONTEXT('NONE','WORKSPACE'))" << endstr; + mOutput << "#7=APPLICATION_CONTEXT(' ')" << endstr; + mOutput << "#8=PRODUCT_CATEGORY('part','NONE')" << endstr; + mOutput << "#9=PRODUCT_RELATED_PRODUCT_CATEGORY('detail',' ',(#17))" << endstr; + mOutput << "#10=PRODUCT_DEFINITION_SHAPE('NONE','NONE',#18)" << endstr; + mOutput << "#11=MANIFOLD_SURFACE_SHAPE_REPRESENTATION('Root',(#16,#19),#6)" << endstr; + mOutput << "#12=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.0E-006),#13,'','')" << endstr; + mOutput << "#13=(CONVERSION_BASED_UNIT('METRE',#20)LENGTH_UNIT()NAMED_UNIT(#21))" << endstr; + mOutput << "#14=(NAMED_UNIT(#22)PLANE_ANGLE_UNIT()SI_UNIT($,.RADIAN.))" << endstr; + mOutput << "#15=(NAMED_UNIT(#22)SOLID_ANGLE_UNIT()SI_UNIT($,.STERADIAN.))" << endstr; + mOutput << "#16=SHELL_BASED_SURFACE_MODEL('Root',(#29))" << endstr; + mOutput << "#17=PRODUCT('Root','Root','Root',(#23))" << endstr; + mOutput << "#18=PRODUCT_DEFINITION('NONE','NONE',#24,#2)" << endstr; + mOutput << "#19=AXIS2_PLACEMENT_3D('',#25,#26,#27)" << endstr; + mOutput << "#20=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.0),#28)" << endstr; + mOutput << "#21=DIMENSIONAL_EXPONENTS(1.0,0.0,0.0,0.0,0.0,0.0,0.0)" << endstr; + mOutput << "#22=DIMENSIONAL_EXPONENTS(0.0,0.0,0.0,0.0,0.0,0.0,0.0)" << endstr; + mOutput << "#23=PRODUCT_CONTEXT('',#7,'mechanical')" << endstr; + mOutput << "#24=PRODUCT_DEFINITION_FORMATION_WITH_SPECIFIED_SOURCE(' ','NONE',#17,.NOT_KNOWN.)" << endstr; + mOutput << "#25=CARTESIAN_POINT('',(0.0,0.0,0.0))" << endstr; + mOutput << "#26=DIRECTION('',(0.0,0.0,1.0))" << endstr; + mOutput << "#27=DIRECTION('',(1.0,0.0,0.0))" << endstr; + mOutput << "#28= (NAMED_UNIT(#21)LENGTH_UNIT()SI_UNIT(.MILLI.,.METRE.))" << endstr; + mOutput << "#29=CLOSED_SHELL('',("; + faceLenIndex = ind + 2 * uniqueVerts.size() + 8; + for (size_t i=0; i<countFace; ++i) + { + mOutput << "#" << faceLenIndex; + if (i!=countFace-1) mOutput << ","; + faceLenIndex += faceEntryLen[i]; + } + mOutput << "))" << endstr; + + // write all the unique transformed CARTESIAN and VERTEX + for (MeshesByNodeMap::const_iterator it2 = meshes.begin(); it2 != meshes.end(); ++it2) + { + const aiNode& node = *(*it2).first; + unsigned int mesh_idx = (*it2).second; + + const aiMesh* mesh = mScene->mMeshes[mesh_idx]; + aiMatrix4x4& trafo = trafos[&node]; + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) + { + aiVector3D* v = &(mesh->mVertices[i]); + it = uniqueVerts.find(v); + if (it->second >=0 ) continue; + it->second = ind; // this one is new, so set the index (ind) + aiVector3D vt = trafo * (*v); // transform the coordinate + mOutput << "#" << it->second << "=CARTESIAN_POINT('',(" << vt.x << "," << vt.y << "," << vt.z << "))" << endstr; + mOutput << "#" << it->second+1 << "=VERTEX_POINT('',#" << it->second << ")" << endstr; + ind += 2; + } + } + + // write the triangles + for (unsigned int i=0; i<mScene->mNumMeshes; ++i) + { + aiMesh* mesh = mScene->mMeshes[i]; + for (unsigned int j=0; j<mesh->mNumFaces; ++j) + { + aiFace* face = &(mesh->mFaces[j]); + + const int numIndices = face->mNumIndices; + if (numIndices < 3) continue; + + std::vector<int> pidArray(numIndices, -1); // vertex id + std::vector<aiVector3D> dvArray(numIndices); // edge dir + for (int k = 0; k < numIndices; ++k) + { + aiVector3D *v1 = &(mesh->mVertices[face->mIndices[k]]); + pidArray[k] = uniqueVerts.find(v1)->second; + + aiVector3D *v2 = nullptr; + if (k + 1 == numIndices) + v2 = &(mesh->mVertices[face->mIndices[0]]); + else + v2 = &(mesh->mVertices[face->mIndices[k + 1]]); + dvArray[k] = *v2 - *v1; + dvArray[k].Normalize(); + } + + aiVector3D dvY = dvArray[1]; + aiVector3D dvX = dvY ^ dvArray[0]; + dvX.Normalize(); + + // mean vertex color for the face if available + if (mesh->HasVertexColors(0)) + { + fColor.r = 0.0; + fColor.g = 0.0; + fColor.b = 0.0; + fColor += mesh->mColors[0][face->mIndices[0]]; + fColor += mesh->mColors[0][face->mIndices[1]]; + fColor += mesh->mColors[0][face->mIndices[2]]; + fColor /= 3.0f; + } + + int sid = ind; // the sub index + mOutput << "#" << sid << "=STYLED_ITEM('',(#" << sid+1 << "),#" << sid+8 << ")" << endstr; /* the item that must be referenced in #1 */ + /* This is the color information of the Triangle */ + mOutput << "#" << sid+1 << "=PRESENTATION_STYLE_ASSIGNMENT((#" << sid+2 << "))" << endstr; + mOutput << "#" << sid+2 << "=SURFACE_STYLE_USAGE(.BOTH.,#" << sid+3 << ")" << endstr; + mOutput << "#" << sid+3 << "=SURFACE_SIDE_STYLE('',(#" << sid+4 << "))" << endstr; + mOutput << "#" << sid+4 << "=SURFACE_STYLE_FILL_AREA(#" << sid+5 << ")" << endstr; + mOutput << "#" << sid+5 << "=FILL_AREA_STYLE('',(#" << sid+6 << "))" << endstr; + mOutput << "#" << sid+6 << "=FILL_AREA_STYLE_COLOUR('',#" << sid+7 << ")" << endstr; + mOutput << "#" << sid+7 << "=COLOUR_RGB(''," << fColor.r << "," << fColor.g << "," << fColor.b << ")" << endstr; + + /* this is the geometry */ + mOutput << "#" << sid+8 << "=FACE_SURFACE('',(#" << sid+13 << "),#" << sid+9<< ",.T.)" << endstr; /* the face that must be referenced in 29 */ + + /* 2 directions of the plane */ + mOutput << "#" << sid+9 << "=PLANE('',#" << sid+10 << ")" << endstr; + mOutput << "#" << sid+10 << "=AXIS2_PLACEMENT_3D('',#" << pidArray[0] << ",#" << sid+11 << ",#" << sid+12 << ")" << endstr; + + mOutput << "#" << sid + 11 << "=DIRECTION('',(" << dvX.x << "," << dvX.y << "," << dvX.z << "))" << endstr; + mOutput << "#" << sid + 12 << "=DIRECTION('',(" << dvY.x << "," << dvY.y << "," << dvY.z << "))" << endstr; + + mOutput << "#" << sid+13 << "=FACE_BOUND('',#" << sid+14 << ",.T.)" << endstr; + mOutput << "#" << sid+14 << "=EDGE_LOOP('',("; + int edgeLoopStart = sid + 15; + for (int k = 0; k < numIndices; ++k) + { + if (k == 0) + mOutput << "#"; + else + mOutput << ",#"; + mOutput << edgeLoopStart + k; + } + mOutput << "))" << endstr; + + /* edge loop */ + int orientedEdgesStart = edgeLoopStart + numIndices; + for (int k=0; k < numIndices; k++) + { + mOutput << "#" << edgeLoopStart+k << "=ORIENTED_EDGE('',*,*,#" << orientedEdgesStart + k << ",.T.)" << endstr; + } + + /* oriented edges */ + int lineStart = orientedEdgesStart + numIndices; + for (int k=0; k < numIndices; ++k) + { + if (k == 0) + mOutput << "#" << orientedEdgesStart+k << "=EDGE_CURVE('',#" << pidArray[k]+1 << ",#" << pidArray[k+1]+1 << ",#" << lineStart+k << ",.F.)" << endstr; + else if (k+1 == numIndices) + mOutput << "#" << orientedEdgesStart+k << "=EDGE_CURVE('',#" << pidArray[k]+1 << ",#" << pidArray[0]+1 << ",#" << lineStart+k << ",.T.)" << endstr; + else + mOutput << "#" << orientedEdgesStart+k << "=EDGE_CURVE('',#" << pidArray[k]+1 << ",#" << pidArray[k+1]+1 << ",#" << lineStart+k << ",.T.)" << endstr; + } + + /* n lines and n vectors for the lines for the n edge curves */ + int vectorStart = lineStart + numIndices; + for (int k=0; k < numIndices; ++k) + { + mOutput << "#" << lineStart+k << "=LINE('',#" << pidArray[k] << ",#" << vectorStart+k << ")" << endstr; + } + + int directionStart = vectorStart + numIndices; + for (int k=0; k < numIndices; ++k) + { + mOutput << "#" << vectorStart+k << "=VECTOR('',#" << directionStart+k << ",1.0)" << endstr; + } + + for (int k=0; k < numIndices; ++k) + { + const aiVector3D &dv = dvArray[k]; + mOutput << "#" << directionStart + k << "=DIRECTION('',(" << dv.x << "," << dv.y << "," << dv.z << "))" << endstr; + } + ind += 15 + 5*numIndices; // increase counter + } + } + + mOutput << "ENDSEC" << endstr; // end of data section + mOutput << "END-ISO-10303-21" << endstr; // end of file +} + +#endif +#endif diff --git a/libs/assimp/code/AssetLib/Step/StepExporter.h b/libs/assimp/code/AssetLib/Step/StepExporter.h new file mode 100644 index 0000000..a022626 --- /dev/null +++ b/libs/assimp/code/AssetLib/Step/StepExporter.h @@ -0,0 +1,111 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +@author: Richard Steffen, 2014 + +---------------------------------------------------------------------- +*/ + +/** @file StepExporter.h + * Declares the exporter class to write a scene to a Collada file + */ +#ifndef AI_STEPEXPORTER_H_INC +#define AI_STEPEXPORTER_H_INC + +#include <assimp/ai_assert.h> +#include <assimp/matrix4x4.h> +#include <assimp/Exporter.hpp> +#include <sstream> + + +struct aiScene; +struct aiNode; + +namespace Assimp +{ + +/// Helper class to export a given scene to a StepFile. +/// Note: an StepFile uses a left hand system. Assimp used a right hand system (OpenGL), therefore we have to transform everything +class StepExporter +{ +public: + /// Constructor for a specific scene to export + StepExporter(const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file, const ExportProperties* pProperties); + +protected: + /// Starts writing the contents + void WriteFile(); + +public: + + /// Stringstream to write all output into + std::stringstream mOutput; + +protected: + + /// hold the properties pointer + const ExportProperties* mProperties; + + /// The IOSystem for output + IOSystem* mIOSystem; + + /// Name of the file (without extension) where the scene will be exported + std::string mFile; + + /// Path of the directory where the scene will be exported + std::string mPath; + + /// The scene to be written + const aiScene* mScene; + + /// current line end string for simple stream insertion + std::string endstr; + + /// accumultated transformations for nodes + std::map<const aiNode*, aiMatrix4x4> trafos; + + /// map to all meshed of nodes + typedef std::multimap<const aiNode*, unsigned int> MeshesByNodeMap; + MeshesByNodeMap meshes; + +}; + +} + +#endif // !! AI_STEPEXPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/Terragen/TerragenLoader.cpp b/libs/assimp/code/AssetLib/Terragen/TerragenLoader.cpp new file mode 100644 index 0000000..c9c91db --- /dev/null +++ b/libs/assimp/code/AssetLib/Terragen/TerragenLoader.cpp @@ -0,0 +1,249 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the Terragen importer class */ + +#ifndef ASSIMP_BUILD_NO_TERRAGEN_IMPORTER + +#include "TerragenLoader.h" +#include <assimp/StreamReader.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/Importer.hpp> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Terragen Heightmap Importer", + "", + "", + "http://www.planetside.co.uk/", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "ter" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +TerragenImporter::TerragenImporter() : + configComputeUVs(false) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +TerragenImporter::~TerragenImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool TerragenImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "terragen" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Build a string of all file extensions supported +const aiImporterDesc *TerragenImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup import properties +void TerragenImporter::SetupProperties(const Importer *pImp) { + // AI_CONFIG_IMPORT_TER_MAKE_UVS + configComputeUVs = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_TER_MAKE_UVS, 0)); +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void TerragenImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, IOSystem *pIOHandler) { + IOStream *file = pIOHandler->Open(pFile, "rb"); + + // Check whether we can read from the file + if (file == nullptr) + throw DeadlyImportError("Failed to open TERRAGEN TERRAIN file ", pFile, "."); + + // Construct a stream reader to read all data in the correct endianness + StreamReaderLE reader(file); + if (reader.GetRemainingSize() < 16) + throw DeadlyImportError("TER: file is too small"); + + // Check for the existence of the two magic strings 'TERRAGEN' and 'TERRAIN ' + if (::strncmp((const char *)reader.GetPtr(), AI_TERR_BASE_STRING, 8)) + throw DeadlyImportError("TER: Magic string \'TERRAGEN\' not found"); + + if (::strncmp((const char *)reader.GetPtr() + 8, AI_TERR_TERRAIN_STRING, 8)) + throw DeadlyImportError("TER: Magic string \'TERRAIN\' not found"); + + unsigned int x = 0, y = 0, mode = 0; + + aiNode *root = pScene->mRootNode = new aiNode(); + root->mName.Set("<TERRAGEN.TERRAIN>"); + + // Default scaling is 30 + root->mTransformation.a1 = root->mTransformation.b2 = root->mTransformation.c3 = 30.f; + + // Now read all chunks until we're finished or an EOF marker is encountered + reader.IncPtr(16); + while (reader.GetRemainingSize() >= 4) { + const char *head = (const char *)reader.GetPtr(); + reader.IncPtr(4); + + // EOF, break in every case + if (!::strncmp(head, AI_TERR_EOF_STRING, 4)) + break; + + // Number of x-data points + if (!::strncmp(head, AI_TERR_CHUNK_XPTS, 4)) { + x = (uint16_t)reader.GetI2(); + } + // Number of y-data points + else if (!::strncmp(head, AI_TERR_CHUNK_YPTS, 4)) { + y = (uint16_t)reader.GetI2(); + } + // Squared terrains width-1. + else if (!::strncmp(head, AI_TERR_CHUNK_SIZE, 4)) { + x = y = (uint16_t)reader.GetI2() + 1; + } + // terrain scaling + else if (!::strncmp(head, AI_TERR_CHUNK_SCAL, 4)) { + root->mTransformation.a1 = reader.GetF4(); + root->mTransformation.b2 = reader.GetF4(); + root->mTransformation.c3 = reader.GetF4(); + } + // mapping == 1: earth radius + else if (!::strncmp(head, AI_TERR_CHUNK_CRAD, 4)) { + reader.GetF4(); + } + // mapping mode + else if (!::strncmp(head, AI_TERR_CHUNK_CRVM, 4)) { + mode = reader.GetI1(); + if (0 != mode) + ASSIMP_LOG_ERROR("TER: Unsupported mapping mode, a flat terrain is returned"); + } + // actual terrain data + else if (!::strncmp(head, AI_TERR_CHUNK_ALTW, 4)) { + float hscale = (float)reader.GetI2() / 65536; + float bheight = (float)reader.GetI2(); + + if (!hscale) hscale = 1; + + // Ensure we have enough data + if (reader.GetRemainingSize() < x * y * 2) + throw DeadlyImportError("TER: ALTW chunk is too small"); + + if (x <= 1 || y <= 1) + throw DeadlyImportError("TER: Invalid terrain size"); + + // Allocate the output mesh + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes = 1]; + aiMesh *m = pScene->mMeshes[0] = new aiMesh(); + + // We return quads + aiFace *f = m->mFaces = new aiFace[m->mNumFaces = (x - 1) * (y - 1)]; + aiVector3D *pv = m->mVertices = new aiVector3D[m->mNumVertices = m->mNumFaces * 4]; + + aiVector3D *uv(nullptr); + float step_y(0.0f), step_x(0.0f); + if (configComputeUVs) { + uv = m->mTextureCoords[0] = new aiVector3D[m->mNumVertices]; + step_y = 1.f / y; + step_x = 1.f / x; + } + const int16_t *data = (const int16_t *)reader.GetPtr(); + + for (unsigned int yy = 0, t = 0; yy < y - 1; ++yy) { + for (unsigned int xx = 0; xx < x - 1; ++xx, ++f) { + + // make verts + const float fy = (float)yy, fx = (float)xx; + unsigned tmp, tmp2; + *pv++ = aiVector3D(fx, fy, (float)data[(tmp2 = x * yy) + xx] * hscale + bheight); + *pv++ = aiVector3D(fx, fy + 1, (float)data[(tmp = x * (yy + 1)) + xx] * hscale + bheight); + *pv++ = aiVector3D(fx + 1, fy + 1, (float)data[tmp + xx + 1] * hscale + bheight); + *pv++ = aiVector3D(fx + 1, fy, (float)data[tmp2 + xx + 1] * hscale + bheight); + + // also make texture coordinates, if necessary + if (configComputeUVs) { + *uv++ = aiVector3D(step_x * xx, step_y * yy, 0.f); + *uv++ = aiVector3D(step_x * xx, step_y * (yy + 1), 0.f); + *uv++ = aiVector3D(step_x * (xx + 1), step_y * (yy + 1), 0.f); + *uv++ = aiVector3D(step_x * (xx + 1), step_y * yy, 0.f); + } + + // make indices + f->mIndices = new unsigned int[f->mNumIndices = 4]; + for (unsigned int i = 0; i < 4; ++i) { + f->mIndices[i] = t; + t++; + } + } + } + + // Add the mesh to the root node + root->mMeshes = new unsigned int[root->mNumMeshes = 1]; + root->mMeshes[0] = 0; + } + + // Get to the next chunk (4 byte aligned) + unsigned dtt = reader.GetCurrentPos(); + if (dtt & 0x3) { + reader.IncPtr(4 - dtt); + } + } + + // Check whether we have a mesh now + if (pScene->mNumMeshes != 1) + throw DeadlyImportError("TER: Unable to load terrain"); + + // Set the AI_SCENE_FLAGS_TERRAIN bit + pScene->mFlags |= AI_SCENE_FLAGS_TERRAIN; +} + +#endif // !! ASSIMP_BUILD_NO_TERRAGEN_IMPORTER diff --git a/libs/assimp/code/AssetLib/Terragen/TerragenLoader.h b/libs/assimp/code/AssetLib/Terragen/TerragenLoader.h new file mode 100644 index 0000000..cb9ff91 --- /dev/null +++ b/libs/assimp/code/AssetLib/Terragen/TerragenLoader.h @@ -0,0 +1,100 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 TerragenLoader.h + * @brief Declaration of the .ter importer class. + */ +#pragma once +#ifndef INCLUDED_AI_TERRAGEN_TERRAIN_LOADER_H +#define INCLUDED_AI_TERRAGEN_TERRAIN_LOADER_H + +#include <assimp/BaseImporter.h> + +namespace Assimp { + +// Magic strings +#define AI_TERR_BASE_STRING "TERRAGEN" +#define AI_TERR_TERRAIN_STRING "TERRAIN " +#define AI_TERR_EOF_STRING "EOF " + +// Chunka +#define AI_TERR_CHUNK_XPTS "XPTS" +#define AI_TERR_CHUNK_YPTS "YPTS" +#define AI_TERR_CHUNK_SIZE "SIZE" +#define AI_TERR_CHUNK_SCAL "SCAL" +#define AI_TERR_CHUNK_CRAD "CRAD" +#define AI_TERR_CHUNK_CRVM "CRVM" +#define AI_TERR_CHUNK_ALTW "ALTW" + +// --------------------------------------------------------------------------- +/** @brief Importer class to load Terragen (0.9) terrain files. + * + * The loader is basing on the information found here: + * http://www.planetside.co.uk/terragen/dev/tgterrain.html#chunks +*/ +class TerragenImporter : public BaseImporter { +public: + TerragenImporter(); + ~TerragenImporter() override; + + // ------------------------------------------------------------------- + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + + // ------------------------------------------------------------------- + void SetupProperties(const Importer *pImp) override; + +private: + bool configComputeUVs; + +}; //! class TerragenImporter + +} // end of namespace Assimp + +#endif // AI_AC3DIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/Unreal/UnrealLoader.cpp b/libs/assimp/code/AssetLib/Unreal/UnrealLoader.cpp new file mode 100644 index 0000000..661952b --- /dev/null +++ b/libs/assimp/code/AssetLib/Unreal/UnrealLoader.cpp @@ -0,0 +1,521 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 UnrealLoader.cpp + * @brief Implementation of the UNREAL (*.3D) importer class + * + * Sources: + * http://local.wasp.uwa.edu.au/~pbourke/dataformats/unreal/ + */ + +#ifndef ASSIMP_BUILD_NO_3D_IMPORTER + +#include "AssetLib/Unreal/UnrealLoader.h" +#include "PostProcessing/ConvertToLHProcess.h" + +#include <assimp/ParsingUtils.h> +#include <assimp/StreamReader.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/Importer.hpp> + +#include <cstdint> +#include <memory> + +using namespace Assimp; + +namespace Unreal { + +/* + 0 = Normal one-sided + 1 = Normal two-sided + 2 = Translucent two-sided + 3 = Masked two-sided + 4 = Modulation blended two-sided + 8 = Placeholder triangle for weapon positioning (invisible) +*/ +enum MeshFlags { + MF_NORMAL_OS = 0, + MF_NORMAL_TS = 1, + MF_NORMAL_TRANS_TS = 2, + MF_NORMAL_MASKED_TS = 3, + MF_NORMAL_MOD_TS = 4, + MF_WEAPON_PLACEHOLDER = 8 +}; + +// a single triangle +struct Triangle { + uint16_t mVertex[3]; // Vertex indices + char mType; // James' Mesh Type + char mColor; // Color for flat and Gourand Shaded + unsigned char mTex[3][2]; // Texture UV coordinates + unsigned char mTextureNum; // Source texture offset + char mFlags; // Unreal Mesh Flags (unused) + unsigned int matIndex; +}; + +// temporary representation for a material +struct TempMat { + TempMat() : + type(MF_NORMAL_OS), tex(), numFaces(0) {} + + explicit TempMat(const Triangle &in) : + type((Unreal::MeshFlags)in.mType), tex(in.mTextureNum), numFaces(0) {} + + // type of mesh + Unreal::MeshFlags type; + + // index of texture + unsigned int tex; + + // number of faces using us + unsigned int numFaces; + + // for std::find + bool operator==(const TempMat &o) { + return (tex == o.tex && type == o.type); + } +}; + +struct Vertex { + int32_t X : 11; + int32_t Y : 11; + int32_t Z : 10; +}; + +// UNREAL vertex compression +inline void CompressVertex(const aiVector3D &v, uint32_t &out) { + union { + Vertex n; + int32_t t; + }; + t = 0; + n.X = (int32_t)v.x; + n.Y = (int32_t)v.y; + n.Z = (int32_t)v.z; + ::memcpy(&out, &t, sizeof(int32_t)); +} + +// UNREAL vertex decompression +inline void DecompressVertex(aiVector3D &v, int32_t in) { + union { + Vertex n; + int32_t i; + }; + i = in; + + v.x = (float)n.X; + v.y = (float)n.Y; + v.z = (float)n.Z; +} + +} // end namespace Unreal + +static const aiImporterDesc desc = { + "Unreal Mesh Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "3d uc" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +UnrealImporter::UnrealImporter() : + mConfigFrameID(0), mConfigHandleFlags(true) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +UnrealImporter::~UnrealImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool UnrealImporter::CanRead(const std::string & filename, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const { + return SimpleExtensionCheck(filename, "3d", "uc"); +} + +// ------------------------------------------------------------------------------------------------ +// Build a string of all file extensions supported +const aiImporterDesc *UnrealImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties for the loader +void UnrealImporter::SetupProperties(const Importer *pImp) { + // The + // AI_CONFIG_IMPORT_UNREAL_KEYFRAME option overrides the + // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option. + mConfigFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_UNREAL_KEYFRAME, -1); + if (static_cast<unsigned int>(-1) == mConfigFrameID) { + mConfigFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME, 0); + } + + // AI_CONFIG_IMPORT_UNREAL_HANDLE_FLAGS, default is true + mConfigHandleFlags = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_UNREAL_HANDLE_FLAGS, 1)); +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void UnrealImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, IOSystem *pIOHandler) { + // For any of the 3 files being passed get the three correct paths + // First of all, determine file extension + std::string::size_type pos = pFile.find_last_of('.'); + std::string extension = GetExtension(pFile); + + std::string d_path, a_path, uc_path; + if (extension == "3d") { + // jjjj_d.3d + // jjjj_a.3d + pos = pFile.find_last_of('_'); + if (std::string::npos == pos) { + throw DeadlyImportError("UNREAL: Unexpected naming scheme"); + } + extension = pFile.substr(0, pos); + } else { + extension = pFile.substr(0, pos); + } + + // build proper paths + d_path = extension + "_d.3d"; + a_path = extension + "_a.3d"; + uc_path = extension + ".uc"; + + ASSIMP_LOG_DEBUG("UNREAL: data file is ", d_path); + ASSIMP_LOG_DEBUG("UNREAL: aniv file is ", a_path); + ASSIMP_LOG_DEBUG("UNREAL: uc file is ", uc_path); + + // and open the files ... we can't live without them + std::unique_ptr<IOStream> p(pIOHandler->Open(d_path)); + if (!p) + throw DeadlyImportError("UNREAL: Unable to open _d file"); + StreamReaderLE d_reader(pIOHandler->Open(d_path)); + + const uint16_t numTris = d_reader.GetI2(); + const uint16_t numVert = d_reader.GetI2(); + d_reader.IncPtr(44); + if (!numTris || numVert < 3) + throw DeadlyImportError("UNREAL: Invalid number of vertices/triangles"); + + // maximum texture index + unsigned int maxTexIdx = 0; + + // collect triangles + std::vector<Unreal::Triangle> triangles(numTris); + for (auto &tri : triangles) { + for (unsigned int i = 0; i < 3; ++i) { + + tri.mVertex[i] = d_reader.GetI2(); + if (tri.mVertex[i] >= numTris) { + ASSIMP_LOG_WARN("UNREAL: vertex index out of range"); + tri.mVertex[i] = 0; + } + } + tri.mType = d_reader.GetI1(); + + // handle mesh flagss? + if (mConfigHandleFlags) + tri.mType = Unreal::MF_NORMAL_OS; + else { + // ignore MOD and MASKED for the moment, treat them as two-sided + if (tri.mType == Unreal::MF_NORMAL_MOD_TS || tri.mType == Unreal::MF_NORMAL_MASKED_TS) + tri.mType = Unreal::MF_NORMAL_TS; + } + d_reader.IncPtr(1); + + for (unsigned int i = 0; i < 3; ++i) + for (unsigned int i2 = 0; i2 < 2; ++i2) + tri.mTex[i][i2] = d_reader.GetI1(); + + tri.mTextureNum = d_reader.GetI1(); + maxTexIdx = std::max(maxTexIdx, (unsigned int)tri.mTextureNum); + d_reader.IncPtr(1); + } + + p.reset(pIOHandler->Open(a_path)); + if (!p) + throw DeadlyImportError("UNREAL: Unable to open _a file"); + StreamReaderLE a_reader(pIOHandler->Open(a_path)); + + // read number of frames + const uint32_t numFrames = a_reader.GetI2(); + if (mConfigFrameID >= numFrames) { + throw DeadlyImportError("UNREAL: The requested frame does not exist"); + } + + uint32_t st = a_reader.GetI2(); + if (st != numVert * 4u) + throw DeadlyImportError("UNREAL: Unexpected aniv file length"); + + // skip to our frame + a_reader.IncPtr(mConfigFrameID * numVert * 4); + + // collect vertices + std::vector<aiVector3D> vertices(numVert); + for (auto &vertex : vertices) { + int32_t val = a_reader.GetI4(); + Unreal::DecompressVertex(vertex, val); + } + + // list of textures. + std::vector<std::pair<unsigned int, std::string>> textures; + + // allocate the output scene + aiNode *nd = pScene->mRootNode = new aiNode(); + nd->mName.Set("<UnrealRoot>"); + + // we can live without the uc file if necessary + std::unique_ptr<IOStream> pb(pIOHandler->Open(uc_path)); + if (pb.get()) { + + std::vector<char> _data; + TextFileToBuffer(pb.get(), _data); + const char *data = &_data[0]; + + std::vector<std::pair<std::string, std::string>> tempTextures; + + // do a quick search in the UC file for some known, usually texture-related, tags + for (; *data; ++data) { + if (TokenMatchI(data, "#exec", 5)) { + SkipSpacesAndLineEnd(&data); + + // #exec TEXTURE IMPORT [...] NAME=jjjjj [...] FILE=jjjj.pcx [...] + if (TokenMatchI(data, "TEXTURE", 7)) { + SkipSpacesAndLineEnd(&data); + + if (TokenMatchI(data, "IMPORT", 6)) { + tempTextures.push_back(std::pair<std::string, std::string>()); + std::pair<std::string, std::string> &me = tempTextures.back(); + for (; !IsLineEnd(*data); ++data) { + if (!::ASSIMP_strincmp(data, "NAME=", 5)) { + const char *d = data += 5; + for (; !IsSpaceOrNewLine(*data); ++data) + ; + me.first = std::string(d, (size_t)(data - d)); + } else if (!::ASSIMP_strincmp(data, "FILE=", 5)) { + const char *d = data += 5; + for (; !IsSpaceOrNewLine(*data); ++data) + ; + me.second = std::string(d, (size_t)(data - d)); + } + } + if (!me.first.length() || !me.second.length()) + tempTextures.pop_back(); + } + } + // #exec MESHMAP SETTEXTURE MESHMAP=box NUM=1 TEXTURE=Jtex1 + // #exec MESHMAP SCALE MESHMAP=box X=0.1 Y=0.1 Z=0.2 + else if (TokenMatchI(data, "MESHMAP", 7)) { + SkipSpacesAndLineEnd(&data); + + if (TokenMatchI(data, "SETTEXTURE", 10)) { + + textures.push_back(std::pair<unsigned int, std::string>()); + std::pair<unsigned int, std::string> &me = textures.back(); + + for (; !IsLineEnd(*data); ++data) { + if (!::ASSIMP_strincmp(data, "NUM=", 4)) { + data += 4; + me.first = strtoul10(data, &data); + } else if (!::ASSIMP_strincmp(data, "TEXTURE=", 8)) { + data += 8; + const char *d = data; + for (; !IsSpaceOrNewLine(*data); ++data) + ; + me.second = std::string(d, (size_t)(data - d)); + + // try to find matching path names, doesn't care if we don't find them + for (std::vector<std::pair<std::string, std::string>>::const_iterator it = tempTextures.begin(); + it != tempTextures.end(); ++it) { + if ((*it).first == me.second) { + me.second = (*it).second; + break; + } + } + } + } + } else if (TokenMatchI(data, "SCALE", 5)) { + + for (; !IsLineEnd(*data); ++data) { + if (data[0] == 'X' && data[1] == '=') { + data = fast_atoreal_move<float>(data + 2, (float &)nd->mTransformation.a1); + } else if (data[0] == 'Y' && data[1] == '=') { + data = fast_atoreal_move<float>(data + 2, (float &)nd->mTransformation.b2); + } else if (data[0] == 'Z' && data[1] == '=') { + data = fast_atoreal_move<float>(data + 2, (float &)nd->mTransformation.c3); + } + } + } + } + } + } + } else { + ASSIMP_LOG_ERROR("Unable to open .uc file"); + } + + std::vector<Unreal::TempMat> materials; + materials.reserve(textures.size() * 2 + 5); + + // find out how many output meshes and materials we'll have and build material indices + for (Unreal::Triangle &tri : triangles) { + Unreal::TempMat mat(tri); + std::vector<Unreal::TempMat>::iterator nt = std::find(materials.begin(), materials.end(), mat); + if (nt == materials.end()) { + // add material + tri.matIndex = static_cast<unsigned int>(materials.size()); + mat.numFaces = 1; + materials.push_back(mat); + + ++pScene->mNumMeshes; + } else { + tri.matIndex = static_cast<unsigned int>(nt - materials.begin()); + ++nt->numFaces; + } + } + + if (!pScene->mNumMeshes) { + throw DeadlyImportError("UNREAL: Unable to find valid mesh data"); + } + + // allocate meshes and bind them to the node graph + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials = pScene->mNumMeshes]; + + nd->mNumMeshes = pScene->mNumMeshes; + nd->mMeshes = new unsigned int[nd->mNumMeshes]; + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + aiMesh *m = pScene->mMeshes[i] = new aiMesh(); + m->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + const unsigned int num = materials[i].numFaces; + m->mFaces = new aiFace[num]; + m->mVertices = new aiVector3D[num * 3]; + m->mTextureCoords[0] = new aiVector3D[num * 3]; + + nd->mMeshes[i] = i; + + // create materials, too + aiMaterial *mat = new aiMaterial(); + pScene->mMaterials[i] = mat; + + // all white by default - texture rulez + aiColor3D color(1.f, 1.f, 1.f); + + aiString s; + ::ai_snprintf(s.data, MAXLEN, "mat%u_tx%u_", i, materials[i].tex); + + // set the two-sided flag + if (materials[i].type == Unreal::MF_NORMAL_TS) { + const int twosided = 1; + mat->AddProperty(&twosided, 1, AI_MATKEY_TWOSIDED); + ::strcat(s.data, "ts_"); + } else + ::strcat(s.data, "os_"); + + // make TRANS faces 90% opaque that RemRedundantMaterials won't catch us + if (materials[i].type == Unreal::MF_NORMAL_TRANS_TS) { + const float opac = 0.9f; + mat->AddProperty(&opac, 1, AI_MATKEY_OPACITY); + ::strcat(s.data, "tran_"); + } else + ::strcat(s.data, "opaq_"); + + // a special name for the weapon attachment point + if (materials[i].type == Unreal::MF_WEAPON_PLACEHOLDER) { + s.length = ::ai_snprintf(s.data, MAXLEN, "$WeaponTag$"); + color = aiColor3D(0.f, 0.f, 0.f); + } + + // set color and name + mat->AddProperty(&color, 1, AI_MATKEY_COLOR_DIFFUSE); + s.length = static_cast<ai_uint32>(::strlen(s.data)); + mat->AddProperty(&s, AI_MATKEY_NAME); + + // set texture, if any + const unsigned int tex = materials[i].tex; + for (std::vector<std::pair<unsigned int, std::string>>::const_iterator it = textures.begin(); it != textures.end(); ++it) { + if ((*it).first == tex) { + s.Set((*it).second); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0)); + break; + } + } + } + + // fill them. + for (const Unreal::Triangle &tri : triangles) { + Unreal::TempMat mat(tri); + std::vector<Unreal::TempMat>::iterator nt = std::find(materials.begin(), materials.end(), mat); + + aiMesh *mesh = pScene->mMeshes[nt - materials.begin()]; + aiFace &f = mesh->mFaces[mesh->mNumFaces++]; + f.mIndices = new unsigned int[f.mNumIndices = 3]; + + for (unsigned int i = 0; i < 3; ++i, mesh->mNumVertices++) { + f.mIndices[i] = mesh->mNumVertices; + + mesh->mVertices[mesh->mNumVertices] = vertices[tri.mVertex[i]]; + mesh->mTextureCoords[0][mesh->mNumVertices] = aiVector3D(tri.mTex[i][0] / 255.f, 1.f - tri.mTex[i][1] / 255.f, 0.f); + } + } + + // convert to RH + MakeLeftHandedProcess hero; + hero.Execute(pScene); + + FlipWindingOrderProcess flipper; + flipper.Execute(pScene); +} + +#endif // !! ASSIMP_BUILD_NO_3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/Unreal/UnrealLoader.h b/libs/assimp/code/AssetLib/Unreal/UnrealLoader.h new file mode 100644 index 0000000..a931a86 --- /dev/null +++ b/libs/assimp/code/AssetLib/Unreal/UnrealLoader.h @@ -0,0 +1,102 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 UnrealLoader.h + * @brief Declaration of the .3d (UNREAL) importer class. + */ +#ifndef INCLUDED_AI_3D_LOADER_H +#define INCLUDED_AI_3D_LOADER_H + +#include <assimp/BaseImporter.h> + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** + * @brief Importer class to load UNREAL files (*.3d) + */ +class UnrealImporter : public BaseImporter { +public: + UnrealImporter(); + ~UnrealImporter(); + + // ------------------------------------------------------------------- + /** @brief Returns whether we can handle the format of the given file + * + * See BaseImporter::CanRead() for details. + **/ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** @brief Called by Importer::GetExtensionList() + * + * @see #BaseImporter::GetInfo for the details + */ + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** @brief Setup properties for the importer + * + * See BaseImporter::SetupProperties() for details + */ + void SetupProperties(const Importer *pImp) override; + + // ------------------------------------------------------------------- + /** @brief Imports the given file into the given scene structure. + * + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + +private: + //! frame to be loaded + uint32_t mConfigFrameID; + + //! process surface flags + bool mConfigHandleFlags; + +}; // !class UnrealImporter + +} // end of namespace Assimp + +#endif // AI_UNREALIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/X/XFileExporter.cpp b/libs/assimp/code/AssetLib/X/XFileExporter.cpp new file mode 100644 index 0000000..f0b1608 --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileExporter.cpp @@ -0,0 +1,541 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +@author: Richard Steffen, 2014 +---------------------------------------------------------------------- +*/ + + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_X_EXPORTER + +#include "AssetLib/X/XFileExporter.h" +#include "PostProcessing/ConvertToLHProcess.h" + +#include <assimp/Bitmap.h> +#include <assimp/BaseImporter.h> +#include <assimp/fast_atof.h> +#include <assimp/SceneCombiner.h> +#include <assimp/DefaultIOSystem.h> +#include <assimp/Exceptional.h> +#include <assimp/IOSystem.hpp> +#include <assimp/scene.h> +#include <assimp/light.h> + +#include <ctime> +#include <set> +#include <memory> + +using namespace Assimp; + +namespace Assimp +{ + +// ------------------------------------------------------------------------------------------------ +// Worker function for exporting a scene to Collada. Prototyped and registered in Exporter.cpp +void ExportSceneXFile(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties) +{ + std::string path = DefaultIOSystem::absolutePath(std::string(pFile)); + std::string file = DefaultIOSystem::completeBaseName(std::string(pFile)); + + // create/copy Properties + ExportProperties props(*pProperties); + + // set standard properties if not set + if (!props.HasPropertyBool(AI_CONFIG_EXPORT_XFILE_64BIT)) props.SetPropertyBool(AI_CONFIG_EXPORT_XFILE_64BIT, false); + + // invoke the exporter + XFileExporter iDoTheExportThing( pScene, pIOSystem, path, file, &props); + + if (iDoTheExportThing.mOutput.fail()) { + throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); + } + + // we're still here - export successfully completed. Write result to the given IOSYstem + std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt")); + if (outfile == nullptr) { + throw DeadlyExportError("could not open output .x file: " + std::string(pFile)); + } + + // XXX maybe use a small wrapper around IOStream that behaves like std::stringstream in order to avoid the extra copy. + outfile->Write( iDoTheExportThing.mOutput.str().c_str(), static_cast<size_t>(iDoTheExportThing.mOutput.tellp()),1); +} + +} // end of namespace Assimp + + +// ------------------------------------------------------------------------------------------------ +// Constructor for a specific scene to export +XFileExporter::XFileExporter(const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file, const ExportProperties* pProperties) + : mProperties(pProperties), + mIOSystem(pIOSystem), + mPath(path), + mFile(file), + mScene(pScene), + mSceneOwned(false), + endstr("\n") +{ + // make sure that all formatting happens using the standard, C locale and not the user's current locale + mOutput.imbue( std::locale("C") ); + mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); + + // start writing + WriteFile(); +} + +// ------------------------------------------------------------------------------------------------ +// Destructor +XFileExporter::~XFileExporter() +{ + if(mSceneOwned) { + delete mScene; + } +} + +// ------------------------------------------------------------------------------------------------ +// Starts writing the contents +void XFileExporter::WriteFile() +{ + // note, that all realnumber values must be comma separated in x files + mOutput.setf(std::ios::fixed); + mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); // precision for ai_real + + // entry of writing the file + WriteHeader(); + + mOutput << startstr << "Frame DXCC_ROOT {" << endstr; + PushTag(); + + aiMatrix4x4 I; // identity + WriteFrameTransform(I); + + WriteNode(mScene->mRootNode); + PopTag(); + + mOutput << startstr << "}" << endstr; + +} + +// ------------------------------------------------------------------------------------------------ +// Writes the asset header +void XFileExporter::WriteHeader() +{ + if (mProperties->GetPropertyBool(AI_CONFIG_EXPORT_XFILE_64BIT) == true) + mOutput << startstr << "xof 0303txt 0064" << endstr; + else + mOutput << startstr << "xof 0303txt 0032" << endstr; + mOutput << endstr; + mOutput << startstr << "template Frame {" << endstr; + PushTag(); + mOutput << startstr << "<3d82ab46-62da-11cf-ab39-0020af71e433>" << endstr; + mOutput << startstr << "[...]" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template Matrix4x4 {" << endstr; + PushTag(); + mOutput << startstr << "<f6f23f45-7686-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "array FLOAT matrix[16];" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template FrameTransformMatrix {" << endstr; + PushTag(); + mOutput << startstr << "<f6f23f41-7686-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "Matrix4x4 frameMatrix;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template Vector {" << endstr; + PushTag(); + mOutput << startstr << "<3d82ab5e-62da-11cf-ab39-0020af71e433>" << endstr; + mOutput << startstr << "FLOAT x;" << endstr; + mOutput << startstr << "FLOAT y;" << endstr; + mOutput << startstr << "FLOAT z;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template MeshFace {" << endstr; + PushTag(); + mOutput << startstr << "<3d82ab5f-62da-11cf-ab39-0020af71e433>" << endstr; + mOutput << startstr << "DWORD nFaceVertexIndices;" << endstr; + mOutput << startstr << "array DWORD faceVertexIndices[nFaceVertexIndices];" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template Mesh {" << endstr; + PushTag(); + mOutput << startstr << "<3d82ab44-62da-11cf-ab39-0020af71e433>" << endstr; + mOutput << startstr << "DWORD nVertices;" << endstr; + mOutput << startstr << "array Vector vertices[nVertices];" << endstr; + mOutput << startstr << "DWORD nFaces;" << endstr; + mOutput << startstr << "array MeshFace faces[nFaces];" << endstr; + mOutput << startstr << "[...]" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template MeshNormals {" << endstr; + PushTag(); + mOutput << startstr << "<f6f23f43-7686-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "DWORD nNormals;" << endstr; + mOutput << startstr << "array Vector normals[nNormals];" << endstr; + mOutput << startstr << "DWORD nFaceNormals;" << endstr; + mOutput << startstr << "array MeshFace faceNormals[nFaceNormals];" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template Coords2d {" << endstr; + PushTag(); + mOutput << startstr << "<f6f23f44-7686-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "FLOAT u;" << endstr; + mOutput << startstr << "FLOAT v;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template MeshTextureCoords {" << endstr; + PushTag(); + mOutput << startstr << "<f6f23f40-7686-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "DWORD nTextureCoords;" << endstr; + mOutput << startstr << "array Coords2d textureCoords[nTextureCoords];" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template ColorRGBA {" << endstr; + PushTag(); + mOutput << startstr << "<35ff44e0-6c7c-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "FLOAT red;" << endstr; + mOutput << startstr << "FLOAT green;" << endstr; + mOutput << startstr << "FLOAT blue;" << endstr; + mOutput << startstr << "FLOAT alpha;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template IndexedColor {" << endstr; + PushTag(); + mOutput << startstr << "<1630b820-7842-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "DWORD index;" << endstr; + mOutput << startstr << "ColorRGBA indexColor;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template MeshVertexColors {" << endstr; + PushTag(); + mOutput << startstr << "<1630b821-7842-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "DWORD nVertexColors;" << endstr; + mOutput << startstr << "array IndexedColor vertexColors[nVertexColors];" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template VertexElement {" << endstr; + PushTag(); + mOutput << startstr << "<f752461c-1e23-48f6-b9f8-8350850f336f>" << endstr; + mOutput << startstr << "DWORD Type;" << endstr; + mOutput << startstr << "DWORD Method;" << endstr; + mOutput << startstr << "DWORD Usage;" << endstr; + mOutput << startstr << "DWORD UsageIndex;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template DeclData {" << endstr; + PushTag(); + mOutput << startstr << "<bf22e553-292c-4781-9fea-62bd554bdd93>" << endstr; + mOutput << startstr << "DWORD nElements;" << endstr; + mOutput << startstr << "array VertexElement Elements[nElements];" << endstr; + mOutput << startstr << "DWORD nDWords;" << endstr; + mOutput << startstr << "array DWORD data[nDWords];" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; +} + + +// Writes the material setup +void XFileExporter::WriteFrameTransform(aiMatrix4x4& m) +{ + mOutput << startstr << "FrameTransformMatrix {" << endstr << " "; + PushTag(); + mOutput << startstr << m.a1 << ", " << m.b1 << ", " << m.c1 << ", " << m.d1 << "," << endstr; + mOutput << startstr << m.a2 << ", " << m.b2 << ", " << m.c2 << ", " << m.d2 << "," << endstr; + mOutput << startstr << m.a3 << ", " << m.b3 << ", " << m.c3 << ", " << m.d3 << "," << endstr; + mOutput << startstr << m.a4 << ", " << m.b4 << ", " << m.c4 << ", " << m.d4 << ";;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr << endstr; +} + + +// ------------------------------------------------------------------------------------------------ +// Recursively writes the given node +void XFileExporter::WriteNode( aiNode* pNode) +{ + if (pNode->mName.length==0) + { + std::stringstream ss; + ss << "Node_" << pNode; + pNode->mName.Set(ss.str()); + } + mOutput << startstr << "Frame " << toXFileString(pNode->mName) << " {" << endstr; + + PushTag(); + + aiMatrix4x4 m = pNode->mTransformation; + + WriteFrameTransform(m); + + for (size_t i = 0; i < pNode->mNumMeshes; ++i) + WriteMesh(mScene->mMeshes[pNode->mMeshes[i]]); + + // recursive call the Nodes + for (size_t i = 0; i < pNode->mNumChildren; ++i) + WriteNode(pNode->mChildren[i]); + + PopTag(); + + mOutput << startstr << "}" << endstr << endstr; +} + +void XFileExporter::WriteMesh(aiMesh* mesh) +{ + mOutput << startstr << "Mesh " << toXFileString(mesh->mName) << "_mShape" << " {" << endstr; + + PushTag(); + + // write all the vertices + mOutput << startstr << mesh->mNumVertices << ";" << endstr; + for (size_t a = 0; a < mesh->mNumVertices; a++) + { + aiVector3D &v = mesh->mVertices[a]; + mOutput << startstr << v[0] << ";"<< v[1] << ";" << v[2] << ";"; + if (a < mesh->mNumVertices - 1) + mOutput << "," << endstr; + else + mOutput << ";" << endstr; + } + + // write all the faces + mOutput << startstr << mesh->mNumFaces << ";" << endstr; + for( size_t a = 0; a < mesh->mNumFaces; ++a ) + { + const aiFace& face = mesh->mFaces[a]; + mOutput << startstr << face.mNumIndices << ";"; + // must be counter clockwise triangle + //for(int b = face.mNumIndices - 1; b >= 0 ; --b) + for(size_t b = 0; b < face.mNumIndices ; ++b) + { + mOutput << face.mIndices[b]; + //if (b > 0) + if (b<face.mNumIndices-1) + mOutput << ","; + else + mOutput << ";"; + } + + if (a < mesh->mNumFaces - 1) + mOutput << "," << endstr; + else + mOutput << ";" << endstr; + } + + mOutput << endstr; + + if (mesh->HasTextureCoords(0)) + { + const aiMaterial* mat = mScene->mMaterials[mesh->mMaterialIndex]; + aiString relpath; + mat->Get(_AI_MATKEY_TEXTURE_BASE, aiTextureType_DIFFUSE, 0, relpath); + + mOutput << startstr << "MeshMaterialList {" << endstr; + PushTag(); + mOutput << startstr << "1;" << endstr; // number of materials + mOutput << startstr << mesh->mNumFaces << ";" << endstr; // number of faces + mOutput << startstr; + for( size_t a = 0; a < mesh->mNumFaces; ++a ) + { + mOutput << "0"; // the material index + if (a < mesh->mNumFaces - 1) + mOutput << ", "; + else + mOutput << ";" << endstr; + } + mOutput << startstr << "Material {" << endstr; + PushTag(); + mOutput << startstr << "1.0; 1.0; 1.0; 1.000000;;" << endstr; + mOutput << startstr << "1.000000;" << endstr; // power + mOutput << startstr << "0.000000; 0.000000; 0.000000;;" << endstr; // specularity + mOutput << startstr << "0.000000; 0.000000; 0.000000;;" << endstr; // emission + mOutput << startstr << "TextureFilename { \""; + + writePath(relpath); + + mOutput << "\"; }" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + } + + // write normals (every vertex has one) + if (mesh->HasNormals()) + { + mOutput << endstr << startstr << "MeshNormals {" << endstr; + mOutput << startstr << mesh->mNumVertices << ";" << endstr; + for (size_t a = 0; a < mesh->mNumVertices; a++) + { + aiVector3D &v = mesh->mNormals[a]; + // because we have a LHS and also changed wth winding, we need to invert the normals again + mOutput << startstr << -v[0] << ";"<< -v[1] << ";" << -v[2] << ";"; + if (a < mesh->mNumVertices - 1) + mOutput << "," << endstr; + else + mOutput << ";" << endstr; + } + + mOutput << startstr << mesh->mNumFaces << ";" << endstr; + for (size_t a = 0; a < mesh->mNumFaces; a++) + { + const aiFace& face = mesh->mFaces[a]; + mOutput << startstr << face.mNumIndices << ";"; + + //for(int b = face.mNumIndices-1; b >= 0 ; --b) + for(size_t b = 0; b < face.mNumIndices ; ++b) + { + mOutput << face.mIndices[b]; + //if (b > 0) + if (b<face.mNumIndices-1) + mOutput << ","; + else + mOutput << ";"; + } + + if (a < mesh->mNumFaces-1) + mOutput << "," << endstr; + else + mOutput << ";" << endstr; + } + mOutput << startstr << "}" << endstr; + } + + // write texture UVs if available + if (mesh->HasTextureCoords(0)) + { + mOutput << endstr << startstr << "MeshTextureCoords {" << endstr; + mOutput << startstr << mesh->mNumVertices << ";" << endstr; + for (size_t a = 0; a < mesh->mNumVertices; a++) + //for (int a = (int)mesh->mNumVertices-1; a >=0 ; a--) + { + aiVector3D& uv = mesh->mTextureCoords[0][a]; // uv of first uv layer for the vertex + mOutput << startstr << uv.x << ";" << uv.y; + if (a < mesh->mNumVertices-1) + //if (a >0 ) + mOutput << ";," << endstr; + else + mOutput << ";;" << endstr; + } + mOutput << startstr << "}" << endstr; + } + + // write color channel if available + if (mesh->HasVertexColors(0)) + { + mOutput << endstr << startstr << "MeshVertexColors {" << endstr; + mOutput << startstr << mesh->mNumVertices << ";" << endstr; + for (size_t a = 0; a < mesh->mNumVertices; a++) + { + aiColor4D& mColors = mesh->mColors[0][a]; // color of first vertex color set for the vertex + mOutput << startstr << a << ";" << mColors.r << ";" << mColors.g << ";" << mColors.b << ";" << mColors.a << ";;"; + if (a < mesh->mNumVertices-1) + mOutput << "," << endstr; + else + mOutput << ";" << endstr; + } + mOutput << startstr << "}" << endstr; + } + /* + else + { + mOutput << endstr << startstr << "MeshVertexColors {" << endstr; + mOutput << startstr << mesh->mNumVertices << ";" << endstr; + for (size_t a = 0; a < mesh->mNumVertices; a++) + { + aiColor4D* mColors = mesh->mColors[a]; + mOutput << startstr << a << ";0.500000;0.000000;0.000000;0.500000;;"; + if (a < mesh->mNumVertices-1) + mOutput << "," << endstr; + else + mOutput << ";" << endstr; + } + mOutput << startstr << "}" << endstr; + } + */ + PopTag(); + mOutput << startstr << "}" << endstr << endstr; + +} + +std::string XFileExporter::toXFileString(aiString &name) +{ + std::string pref = ""; // node name prefix to prevent unexpected start of string + std::string str = pref + std::string(name.C_Str()); + for (int i=0; i < (int) str.length(); ++i) + { + if ((str[i] >= '0' && str[i] <= '9') || // 0-9 + (str[i] >= 'A' && str[i] <= 'Z') || // A-Z + (str[i] >= 'a' && str[i] <= 'z')) // a-z + continue; + str[i] = '_'; + } + return str; +} + +void XFileExporter::writePath(const aiString &path) +{ + std::string str = std::string(path.C_Str()); + BaseImporter::ConvertUTF8toISO8859_1(str); + + while( str.find( "\\\\") != std::string::npos) + str.replace( str.find( "\\\\"), 2, "\\"); + + while (str.find('\\') != std::string::npos) + str.replace(str.find('\\'), 1, "/"); + + mOutput << str; + +} + +#endif +#endif diff --git a/libs/assimp/code/AssetLib/X/XFileExporter.h b/libs/assimp/code/AssetLib/X/XFileExporter.h new file mode 100644 index 0000000..1d9a5ae --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileExporter.h @@ -0,0 +1,140 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +@author: Richard Steffen, 2014 + +---------------------------------------------------------------------- +*/ + +/** @file XFileExporter.h + * Declares the exporter class to write a scene to a Collada file + */ +#ifndef AI_XFILEEXPORTER_H_INC +#define AI_XFILEEXPORTER_H_INC + +#include <assimp/ai_assert.h> +#include <assimp/matrix4x4.h> +#include <assimp/Exporter.hpp> +#include <sstream> + +struct aiScene; +struct aiNode; +struct aiMesh; +struct aiString; + +namespace Assimp { + +class IOSystem; + + +/// Helper class to export a given scene to a X-file. +/// Note: an xFile uses a left hand system. Assimp used a right hand system (OpenGL), therefore we have to transform everything +class XFileExporter +{ +public: + /// Constructor for a specific scene to export + XFileExporter(const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file, const ExportProperties* pProperties); + + /// Destructor + virtual ~XFileExporter(); + +protected: + /// Starts writing the contents + void WriteFile(); + + /// Writes the asset header + void WriteHeader(); + + /// write a frame transform + void WriteFrameTransform(aiMatrix4x4& m); + + /// Recursively writes the given node + void WriteNode( aiNode* pNode ); + + /// write a mesh entry of the scene + void WriteMesh( aiMesh* mesh); + + /// Enters a new xml element, which increases the indentation + void PushTag() { startstr.append( " "); } + + /// Leaves an element, decreasing the indentation + void PopTag() { + ai_assert( startstr.length() > 1); + startstr.erase( startstr.length() - 2); + } + +public: + /// Stringstream to write all output into + std::stringstream mOutput; + +protected: + + /// normalize the name to be accepted by xfile readers + std::string toXFileString(aiString &name); + + /// hold the properties pointer + const ExportProperties* mProperties; + + /// write a path + void writePath(const aiString &path); + + /// The IOSystem for output + IOSystem* mIOSystem; + + /// Path of the directory where the scene will be exported + const std::string mPath; + + /// Name of the file (without extension) where the scene will be exported + const std::string mFile; + + /// The scene to be written + const aiScene* mScene; + bool mSceneOwned; + + /// current line start string, contains the current indentation for simple stream insertion + std::string startstr; + + /// current line end string for simple stream insertion + std::string endstr; + +}; + +} + +#endif // !! AI_XFILEEXPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/X/XFileHelper.h b/libs/assimp/code/AssetLib/X/XFileHelper.h new file mode 100644 index 0000000..3830eb4 --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileHelper.h @@ -0,0 +1,234 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines the helper data structures for importing XFiles */ +#ifndef AI_XFILEHELPER_H_INC +#define AI_XFILEHELPER_H_INC + +#include <cstdint> +#include <string> +#include <vector> + +#include <assimp/anim.h> +#include <assimp/mesh.h> +#include <assimp/quaternion.h> +#include <assimp/types.h> + +namespace Assimp { +namespace XFile { + +/** Helper structure representing a XFile mesh face */ +struct Face { + std::vector<unsigned int> mIndices; +}; + +/** Helper structure representing a texture filename inside a material and its potential source */ +struct TexEntry { + std::string mName; + bool mIsNormalMap; // true if the texname was specified in a NormalmapFilename tag + + TexEntry() AI_NO_EXCEPT : + mName(), + mIsNormalMap(false) { + // empty + } + TexEntry(const std::string &pName, bool pIsNormalMap = false) : + mName(pName), mIsNormalMap(pIsNormalMap) { + // empty + } +}; + +/** Helper structure representing a XFile material */ +struct Material { + std::string mName; + bool mIsReference; // if true, mName holds a name by which the actual material can be found in the material list + aiColor4D mDiffuse; + ai_real mSpecularExponent; + aiColor3D mSpecular; + aiColor3D mEmissive; + std::vector<TexEntry> mTextures; + size_t sceneIndex; ///< the index under which it was stored in the scene's material list + + Material() AI_NO_EXCEPT : + mIsReference(false), + mSpecularExponent(), + sceneIndex(SIZE_MAX) { + // empty + } +}; + +/** Helper structure to represent a bone weight */ +struct BoneWeight { + unsigned int mVertex; + ai_real mWeight; +}; + +/** Helper structure to represent a bone in a mesh */ +struct Bone { + std::string mName; + std::vector<BoneWeight> mWeights; + aiMatrix4x4 mOffsetMatrix; +}; + +/** Helper structure to represent an XFile mesh */ +struct Mesh { + std::string mName; + std::vector<aiVector3D> mPositions; + std::vector<Face> mPosFaces; + std::vector<aiVector3D> mNormals; + std::vector<Face> mNormFaces; + unsigned int mNumTextures; + std::vector<aiVector2D> mTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + unsigned int mNumColorSets; + std::vector<aiColor4D> mColors[AI_MAX_NUMBER_OF_COLOR_SETS]; + + std::vector<unsigned int> mFaceMaterials; + std::vector<Material> mMaterials; + + std::vector<Bone> mBones; + + explicit Mesh(const std::string &pName = std::string()) AI_NO_EXCEPT + : mName(pName), + mPositions(), + mPosFaces(), + mNormals(), + mNormFaces(), + mNumTextures(0), + mTexCoords{}, + mNumColorSets(0), + mColors{}, + mFaceMaterials(), + mMaterials(), + mBones() { + // empty + } +}; + +/** Helper structure to represent a XFile frame */ +struct Node { + std::string mName; + aiMatrix4x4 mTrafoMatrix; + Node *mParent; + std::vector<Node *> mChildren; + std::vector<Mesh *> mMeshes; + + Node() AI_NO_EXCEPT + : mName(), + mTrafoMatrix(), + mParent(nullptr), + mChildren(), + mMeshes() { + // empty + } + explicit Node(Node *pParent) : + mName(), mTrafoMatrix(), mParent(pParent), mChildren(), mMeshes() { + // empty + } + + ~Node() { + for (unsigned int a = 0; a < mChildren.size(); ++a) { + delete mChildren[a]; + } + for (unsigned int a = 0; a < mMeshes.size(); ++a) { + delete mMeshes[a]; + } + } +}; + +struct MatrixKey { + double mTime; + aiMatrix4x4 mMatrix; +}; + +/** Helper structure representing a single animated bone in a XFile */ +struct AnimBone { + std::string mBoneName; + std::vector<aiVectorKey> mPosKeys; // either three separate key sequences for position, rotation, scaling + std::vector<aiQuatKey> mRotKeys; + std::vector<aiVectorKey> mScaleKeys; + std::vector<MatrixKey> mTrafoKeys; // or a combined key sequence of transformation matrices. +}; + +/** Helper structure to represent an animation set in a XFile */ +struct Animation { + std::string mName; + std::vector<AnimBone *> mAnims; + + ~Animation() { + for (unsigned int a = 0; a < mAnims.size(); a++) + delete mAnims[a]; + } +}; + +/** Helper structure analogue to aiScene */ +struct Scene { + Node *mRootNode; + + std::vector<Mesh *> mGlobalMeshes; // global meshes found outside of any frames + std::vector<Material> mGlobalMaterials; // global materials found outside of any meshes. + + std::vector<Animation *> mAnims; + unsigned int mAnimTicksPerSecond; + + Scene() AI_NO_EXCEPT + : mRootNode(nullptr), + mGlobalMeshes(), + mGlobalMaterials(), + mAnimTicksPerSecond(0) { + // empty + } + ~Scene() { + delete mRootNode; + mRootNode = nullptr; + for (unsigned int a = 0; a < mGlobalMeshes.size(); ++a) { + delete mGlobalMeshes[a]; + } + for (unsigned int a = 0; a < mAnims.size(); ++a) { + delete mAnims[a]; + } + } +}; + +} // end of namespace XFile +} // end of namespace Assimp + +#endif // AI_XFILEHELPER_H_INC diff --git a/libs/assimp/code/AssetLib/X/XFileImporter.cpp b/libs/assimp/code/AssetLib/X/XFileImporter.cpp new file mode 100644 index 0000000..d23eb57 --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileImporter.cpp @@ -0,0 +1,691 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 XFileImporter.cpp + * @brief Implementation of the XFile importer class + */ + +#ifndef ASSIMP_BUILD_NO_X_IMPORTER + +#include "AssetLib/X/XFileImporter.h" +#include "AssetLib/X/XFileParser.h" +#include "PostProcessing/ConvertToLHProcess.h" + +#include <assimp/TinyFormatter.h> +#include <assimp/IOSystem.hpp> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/importerdesc.h> + +#include <cctype> +#include <memory> + +using namespace Assimp; +using namespace Assimp::Formatter; + +static const aiImporterDesc desc = { + "Direct3D XFile Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour, + 1, + 3, + 1, + 5, + "x" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +XFileImporter::XFileImporter() +: mBuffer() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +XFileImporter::~XFileImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool XFileImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const { + static const uint32_t token[] = { AI_MAKE_MAGIC("xof ") }; + return CheckMagicToken(pIOHandler,pFile,token,AI_COUNT_OF(token)); +} + +// ------------------------------------------------------------------------------------------------ +// Get file extension list +const aiImporterDesc* XFileImporter::GetInfo () const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void XFileImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) { + // read file into memory + std::unique_ptr<IOStream> file( pIOHandler->Open( pFile)); + if ( file.get() == nullptr ) { + throw DeadlyImportError( "Failed to open file ", pFile, "." ); + } + + static const size_t MinSize = 16; + size_t fileSize = file->FileSize(); + if ( fileSize < MinSize ) { + throw DeadlyImportError( "XFile is too small." ); + } + + // in the hope that binary files will never start with a BOM ... + mBuffer.resize( fileSize + 1); + file->Read( &mBuffer.front(), 1, fileSize); + ConvertToUTF8(mBuffer); + + // parse the file into a temporary representation + XFileParser parser( mBuffer); + + // and create the proper return structures out of it + CreateDataRepresentationFromImport( pScene, parser.GetImportedData()); + + // if nothing came from it, report it as error + if ( !pScene->mRootNode ) { + throw DeadlyImportError( "XFile is ill-formatted - no content imported." ); + } +} + +// ------------------------------------------------------------------------------------------------ +// Constructs the return data structure out of the imported data. +void XFileImporter::CreateDataRepresentationFromImport( aiScene* pScene, XFile::Scene* pData) +{ + // Read the global materials first so that meshes referring to them can find them later + ConvertMaterials( pScene, pData->mGlobalMaterials); + + // copy nodes, extracting meshes and materials on the way + pScene->mRootNode = CreateNodes( pScene, nullptr, pData->mRootNode); + + // extract animations + CreateAnimations( pScene, pData); + + // read the global meshes that were stored outside of any node + if( !pData->mGlobalMeshes.empty() ) { + // create a root node to hold them if there isn't any, yet + if( pScene->mRootNode == nullptr ) { + pScene->mRootNode = new aiNode; + pScene->mRootNode->mName.Set( "$dummy_node"); + } + + // convert all global meshes and store them in the root node. + // If there was one before, the global meshes now suddenly have its transformation matrix... + // Don't know what to do there, I don't want to insert another node under the present root node + // just to avoid this. + CreateMeshes( pScene, pScene->mRootNode, pData->mGlobalMeshes); + } + + if (!pScene->mRootNode) { + throw DeadlyImportError( "No root node" ); + } + + // Convert everything to OpenGL space... it's the same operation as the conversion back, so we can reuse the step directly + MakeLeftHandedProcess convertProcess; + convertProcess.Execute( pScene); + + FlipWindingOrderProcess flipper; + flipper.Execute(pScene); + + // finally: create a dummy material if not material was imported + if( pScene->mNumMaterials == 0) { + pScene->mNumMaterials = 1; + // create the Material + aiMaterial* mat = new aiMaterial; + int shadeMode = (int) aiShadingMode_Gouraud; + mat->AddProperty<int>( &shadeMode, 1, AI_MATKEY_SHADING_MODEL); + // material colours + int specExp = 1; + + aiColor3D clr = aiColor3D( 0, 0, 0); + mat->AddProperty( &clr, 1, AI_MATKEY_COLOR_EMISSIVE); + mat->AddProperty( &clr, 1, AI_MATKEY_COLOR_SPECULAR); + + clr = aiColor3D( 0.5f, 0.5f, 0.5f); + mat->AddProperty( &clr, 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty( &specExp, 1, AI_MATKEY_SHININESS); + + pScene->mMaterials = new aiMaterial*[1]; + pScene->mMaterials[0] = mat; + } +} + +// ------------------------------------------------------------------------------------------------ +// Recursively creates scene nodes from the imported hierarchy. +aiNode* XFileImporter::CreateNodes( aiScene* pScene, aiNode* pParent, const XFile::Node* pNode) { + if ( !pNode ) { + return nullptr; + } + + // create node + aiNode* node = new aiNode; + node->mName.length = (ai_uint32)pNode->mName.length(); + node->mParent = pParent; + memcpy( node->mName.data, pNode->mName.c_str(), pNode->mName.length()); + node->mName.data[node->mName.length] = 0; + node->mTransformation = pNode->mTrafoMatrix; + + // convert meshes from the source node + CreateMeshes( pScene, node, pNode->mMeshes); + + // handle children + if( !pNode->mChildren.empty() ) { + node->mNumChildren = (unsigned int)pNode->mChildren.size(); + node->mChildren = new aiNode* [node->mNumChildren]; + + for ( unsigned int a = 0; a < pNode->mChildren.size(); ++a ) { + node->mChildren[ a ] = CreateNodes( pScene, node, pNode->mChildren[ a ] ); + } + } + + return node; +} + +// ------------------------------------------------------------------------------------------------ +// Creates the meshes for the given node. +void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vector<XFile::Mesh*>& pMeshes) { + if (pMeshes.empty()) { + return; + } + + // create a mesh for each mesh-material combination in the source node + std::vector<aiMesh*> meshes; + for( unsigned int a = 0; a < pMeshes.size(); ++a ) { + XFile::Mesh* sourceMesh = pMeshes[a]; + if ( nullptr == sourceMesh ) { + continue; + } + + // first convert its materials so that we can find them with their index afterwards + ConvertMaterials( pScene, sourceMesh->mMaterials); + + unsigned int numMaterials = std::max( (unsigned int)sourceMesh->mMaterials.size(), 1u); + for( unsigned int b = 0; b < numMaterials; ++b ) { + // collect the faces belonging to this material + std::vector<unsigned int> faces; + unsigned int numVertices = 0; + if( !sourceMesh->mFaceMaterials.empty() ) { + // if there is a per-face material defined, select the faces with the corresponding material + for( unsigned int c = 0; c < sourceMesh->mFaceMaterials.size(); ++c ) { + if( sourceMesh->mFaceMaterials[c] == b) { + faces.push_back( c); + numVertices += (unsigned int)sourceMesh->mPosFaces[c].mIndices.size(); + } + } + } else { + // if there is no per-face material, place everything into one mesh + for( unsigned int c = 0; c < sourceMesh->mPosFaces.size(); ++c ) { + faces.push_back( c); + numVertices += (unsigned int)sourceMesh->mPosFaces[c].mIndices.size(); + } + } + + // no faces/vertices using this material? strange... + if ( numVertices == 0 ) { + continue; + } + + // create a submesh using this material + aiMesh* mesh = new aiMesh; + meshes.push_back( mesh); + + // find the material in the scene's material list. Either own material + // or referenced material, it should already have a valid index + if( !sourceMesh->mFaceMaterials.empty() ) { + mesh->mMaterialIndex = static_cast<unsigned int>(sourceMesh->mMaterials[b].sceneIndex); + } else { + mesh->mMaterialIndex = 0; + } + + // Create properly sized data arrays in the mesh. We store unique vertices per face, + // as specified + mesh->mNumVertices = numVertices; + mesh->mVertices = new aiVector3D[numVertices]; + mesh->mNumFaces = (unsigned int)faces.size(); + mesh->mFaces = new aiFace[mesh->mNumFaces]; + + // name + mesh->mName.Set(sourceMesh->mName); + + // normals? + if ( sourceMesh->mNormals.size() > 0 ) { + mesh->mNormals = new aiVector3D[ numVertices ]; + } + // texture coords + for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c ) { + if ( !sourceMesh->mTexCoords[ c ].empty() ) { + mesh->mTextureCoords[ c ] = new aiVector3D[ numVertices ]; + } + } + // vertex colors + for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c ) { + if ( !sourceMesh->mColors[ c ].empty() ) { + mesh->mColors[ c ] = new aiColor4D[ numVertices ]; + } + } + + // now collect the vertex data of all data streams present in the imported mesh + unsigned int newIndex( 0 ); + std::vector<unsigned int> orgPoints; // from which original point each new vertex stems + orgPoints.resize( numVertices, 0); + + for( unsigned int c = 0; c < faces.size(); ++c ) { + unsigned int f = faces[c]; // index of the source face + const XFile::Face& pf = sourceMesh->mPosFaces[f]; // position source face + + // create face. either triangle or triangle fan depending on the index count + aiFace& df = mesh->mFaces[c]; // destination face + df.mNumIndices = (unsigned int)pf.mIndices.size(); + df.mIndices = new unsigned int[ df.mNumIndices]; + + // collect vertex data for indices of this face + for( unsigned int d = 0; d < df.mNumIndices; ++d ) { + df.mIndices[ d ] = newIndex; + const unsigned int newIdx( pf.mIndices[ d ] ); + if ( newIdx > sourceMesh->mPositions.size() ) { + continue; + } + + orgPoints[newIndex] = pf.mIndices[d]; + + // Position + mesh->mVertices[newIndex] = sourceMesh->mPositions[pf.mIndices[d]]; + // Normal, if present + if ( mesh->HasNormals() ) { + if ( sourceMesh->mNormFaces[ f ].mIndices.size() > d ) { + const size_t idx( sourceMesh->mNormFaces[ f ].mIndices[ d ] ); + mesh->mNormals[ newIndex ] = sourceMesh->mNormals[ idx ]; + } + } + + // texture coord sets + for( unsigned int e = 0; e < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++e ) { + if( mesh->HasTextureCoords( e)) { + aiVector2D tex = sourceMesh->mTexCoords[e][pf.mIndices[d]]; + mesh->mTextureCoords[e][newIndex] = aiVector3D( tex.x, 1.0f - tex.y, 0.0f); + } + } + // vertex color sets + for ( unsigned int e = 0; e < AI_MAX_NUMBER_OF_COLOR_SETS; ++e ) { + if ( mesh->HasVertexColors( e ) ) { + mesh->mColors[ e ][ newIndex ] = sourceMesh->mColors[ e ][ pf.mIndices[ d ] ]; + } + } + + newIndex++; + } + } + + // there should be as much new vertices as we calculated before + ai_assert( newIndex == numVertices); + + // convert all bones of the source mesh which influence vertices in this newly created mesh + const std::vector<XFile::Bone>& bones = sourceMesh->mBones; + std::vector<aiBone*> newBones; + for( unsigned int c = 0; c < bones.size(); ++c ) { + const XFile::Bone& obone = bones[c]; + // set up a vertex-linear array of the weights for quick searching if a bone influences a vertex + std::vector<ai_real> oldWeights( sourceMesh->mPositions.size(), 0.0); + for ( unsigned int d = 0; d < obone.mWeights.size(); ++d ) { + oldWeights[ obone.mWeights[ d ].mVertex ] = obone.mWeights[ d ].mWeight; + } + + // collect all vertex weights that influence a vertex in the new mesh + std::vector<aiVertexWeight> newWeights; + newWeights.reserve( numVertices); + for( unsigned int d = 0; d < orgPoints.size(); ++d ) { + // does the new vertex stem from an old vertex which was influenced by this bone? + ai_real w = oldWeights[orgPoints[d]]; + if ( w > 0.0 ) { + newWeights.push_back( aiVertexWeight( d, w ) ); + } + } + + // if the bone has no weights in the newly created mesh, ignore it + if ( newWeights.empty() ) { + continue; + } + + // create + aiBone* nbone = new aiBone; + newBones.push_back( nbone); + // copy name and matrix + nbone->mName.Set( obone.mName); + nbone->mOffsetMatrix = obone.mOffsetMatrix; + nbone->mNumWeights = (unsigned int)newWeights.size(); + nbone->mWeights = new aiVertexWeight[nbone->mNumWeights]; + for ( unsigned int d = 0; d < newWeights.size(); ++d ) { + nbone->mWeights[ d ] = newWeights[ d ]; + } + } + + // store the bones in the mesh + mesh->mNumBones = (unsigned int)newBones.size(); + if( !newBones.empty()) { + mesh->mBones = new aiBone*[mesh->mNumBones]; + std::copy( newBones.begin(), newBones.end(), mesh->mBones); + } + } + } + + // reallocate scene mesh array to be large enough + aiMesh** prevArray = pScene->mMeshes; + pScene->mMeshes = new aiMesh*[pScene->mNumMeshes + meshes.size()]; + if( prevArray) { + memcpy( pScene->mMeshes, prevArray, pScene->mNumMeshes * sizeof( aiMesh*)); + delete [] prevArray; + } + + // allocate mesh index array in the node + pNode->mNumMeshes = (unsigned int)meshes.size(); + pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; + + // store all meshes in the mesh library of the scene and store their indices in the node + for( unsigned int a = 0; a < meshes.size(); a++) { + pScene->mMeshes[pScene->mNumMeshes] = meshes[a]; + pNode->mMeshes[a] = pScene->mNumMeshes; + pScene->mNumMeshes++; + } +} + +// ------------------------------------------------------------------------------------------------ +// Converts the animations from the given imported data and creates them in the scene. +void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData) { + std::vector<aiAnimation*> newAnims; + + for( unsigned int a = 0; a < pData->mAnims.size(); ++a ) { + const XFile::Animation* anim = pData->mAnims[a]; + // some exporters mock me with empty animation tags. + if ( anim->mAnims.empty() ) { + continue; + } + + // create a new animation to hold the data + aiAnimation* nanim = new aiAnimation; + newAnims.push_back( nanim); + nanim->mName.Set( anim->mName); + // duration will be determined by the maximum length + nanim->mDuration = 0; + nanim->mTicksPerSecond = pData->mAnimTicksPerSecond; + nanim->mNumChannels = (unsigned int)anim->mAnims.size(); + nanim->mChannels = new aiNodeAnim*[nanim->mNumChannels]; + + for( unsigned int b = 0; b < anim->mAnims.size(); ++b ) { + const XFile::AnimBone* bone = anim->mAnims[b]; + aiNodeAnim* nbone = new aiNodeAnim; + nbone->mNodeName.Set( bone->mBoneName); + nanim->mChannels[b] = nbone; + + // key-frames are given as combined transformation matrix keys + if( !bone->mTrafoKeys.empty() ) + { + nbone->mNumPositionKeys = (unsigned int)bone->mTrafoKeys.size(); + nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys]; + nbone->mNumRotationKeys = (unsigned int)bone->mTrafoKeys.size(); + nbone->mRotationKeys = new aiQuatKey[nbone->mNumRotationKeys]; + nbone->mNumScalingKeys = (unsigned int)bone->mTrafoKeys.size(); + nbone->mScalingKeys = new aiVectorKey[nbone->mNumScalingKeys]; + + for( unsigned int c = 0; c < bone->mTrafoKeys.size(); ++c) { + // deconstruct each matrix into separate position, rotation and scaling + double time = bone->mTrafoKeys[c].mTime; + aiMatrix4x4 trafo = bone->mTrafoKeys[c].mMatrix; + + // extract position + aiVector3D pos( trafo.a4, trafo.b4, trafo.c4); + + nbone->mPositionKeys[c].mTime = time; + nbone->mPositionKeys[c].mValue = pos; + + // extract scaling + aiVector3D scale; + scale.x = aiVector3D( trafo.a1, trafo.b1, trafo.c1).Length(); + scale.y = aiVector3D( trafo.a2, trafo.b2, trafo.c2).Length(); + scale.z = aiVector3D( trafo.a3, trafo.b3, trafo.c3).Length(); + nbone->mScalingKeys[c].mTime = time; + nbone->mScalingKeys[c].mValue = scale; + + // reconstruct rotation matrix without scaling + aiMatrix3x3 rotmat( + trafo.a1 / scale.x, trafo.a2 / scale.y, trafo.a3 / scale.z, + trafo.b1 / scale.x, trafo.b2 / scale.y, trafo.b3 / scale.z, + trafo.c1 / scale.x, trafo.c2 / scale.y, trafo.c3 / scale.z); + + // and convert it into a quaternion + nbone->mRotationKeys[c].mTime = time; + nbone->mRotationKeys[c].mValue = aiQuaternion( rotmat); + } + + // longest lasting key sequence determines duration + nanim->mDuration = std::max( nanim->mDuration, bone->mTrafoKeys.back().mTime); + } else { + // separate key sequences for position, rotation, scaling + nbone->mNumPositionKeys = (unsigned int)bone->mPosKeys.size(); + if (nbone->mNumPositionKeys != 0) { + nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys]; + for( unsigned int c = 0; c < nbone->mNumPositionKeys; ++c ) { + aiVector3D pos = bone->mPosKeys[c].mValue; + + nbone->mPositionKeys[c].mTime = bone->mPosKeys[c].mTime; + nbone->mPositionKeys[c].mValue = pos; + } + } + + // rotation + nbone->mNumRotationKeys = (unsigned int)bone->mRotKeys.size(); + if (nbone->mNumRotationKeys != 0) { + nbone->mRotationKeys = new aiQuatKey[nbone->mNumRotationKeys]; + for( unsigned int c = 0; c < nbone->mNumRotationKeys; ++c ) { + aiMatrix3x3 rotmat = bone->mRotKeys[c].mValue.GetMatrix(); + + nbone->mRotationKeys[c].mTime = bone->mRotKeys[c].mTime; + nbone->mRotationKeys[c].mValue = aiQuaternion( rotmat); + nbone->mRotationKeys[c].mValue.w *= -1.0f; // needs quat inversion + } + } + + // scaling + nbone->mNumScalingKeys = (unsigned int)bone->mScaleKeys.size(); + if (nbone->mNumScalingKeys != 0) { + nbone->mScalingKeys = new aiVectorKey[nbone->mNumScalingKeys]; + for( unsigned int c = 0; c < nbone->mNumScalingKeys; c++) + nbone->mScalingKeys[c] = bone->mScaleKeys[c]; + } + + // longest lasting key sequence determines duration + if( bone->mPosKeys.size() > 0) + nanim->mDuration = std::max( nanim->mDuration, bone->mPosKeys.back().mTime); + if( bone->mRotKeys.size() > 0) + nanim->mDuration = std::max( nanim->mDuration, bone->mRotKeys.back().mTime); + if( bone->mScaleKeys.size() > 0) + nanim->mDuration = std::max( nanim->mDuration, bone->mScaleKeys.back().mTime); + } + } + } + + // store all converted animations in the scene + if( newAnims.size() > 0) + { + pScene->mNumAnimations = (unsigned int)newAnims.size(); + pScene->mAnimations = new aiAnimation* [pScene->mNumAnimations]; + for( unsigned int a = 0; a < newAnims.size(); a++) + pScene->mAnimations[a] = newAnims[a]; + } +} + +// ------------------------------------------------------------------------------------------------ +// Converts all materials in the given array and stores them in the scene's material list. +void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector<XFile::Material>& pMaterials) +{ + // count the non-referrer materials in the array + unsigned int numNewMaterials( 0 ); + for ( unsigned int a = 0; a < pMaterials.size(); ++a ) { + if ( !pMaterials[ a ].mIsReference ) { + ++numNewMaterials; + } + } + + // resize the scene's material list to offer enough space for the new materials + if( numNewMaterials > 0 ) { + aiMaterial** prevMats = pScene->mMaterials; + pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials + numNewMaterials]; + if( nullptr != prevMats) { + ::memcpy( pScene->mMaterials, prevMats, pScene->mNumMaterials * sizeof( aiMaterial*)); + delete [] prevMats; + } + } + + // convert all the materials given in the array + for( unsigned int a = 0; a < pMaterials.size(); ++a ) { + XFile::Material& oldMat = pMaterials[a]; + if( oldMat.mIsReference) { + // find the material it refers to by name, and store its index + for( size_t b = 0; b < pScene->mNumMaterials; ++b ) { + aiString name; + pScene->mMaterials[b]->Get( AI_MATKEY_NAME, name); + if( strcmp( name.C_Str(), oldMat.mName.data()) == 0 ) { + oldMat.sceneIndex = a; + break; + } + } + + if( oldMat.sceneIndex == SIZE_MAX ) { + ASSIMP_LOG_WARN( "Could not resolve global material reference \"", oldMat.mName, "\"" ); + oldMat.sceneIndex = 0; + } + + continue; + } + + aiMaterial* mat = new aiMaterial; + aiString name; + name.Set( oldMat.mName); + mat->AddProperty( &name, AI_MATKEY_NAME); + + // Shading model: hard-coded to PHONG, there is no such information in an XFile + // FIX (aramis): If the specular exponent is 0, use gouraud shading. This is a bugfix + // for some models in the SDK (e.g. good old tiny.x) + int shadeMode = (int)oldMat.mSpecularExponent == 0.0f + ? aiShadingMode_Gouraud : aiShadingMode_Phong; + + mat->AddProperty<int>( &shadeMode, 1, AI_MATKEY_SHADING_MODEL); + // material colours + // Unclear: there's no ambient colour, but emissive. What to put for ambient? + // Probably nothing at all, let the user select a suitable default. + mat->AddProperty( &oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); + mat->AddProperty( &oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty( &oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); + mat->AddProperty( &oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS); + + + // texture, if there is one + if (1 == oldMat.mTextures.size() ) { + const XFile::TexEntry& otex = oldMat.mTextures.back(); + if (otex.mName.length()) { + // if there is only one texture assume it contains the diffuse color + aiString tex( otex.mName); + if ( otex.mIsNormalMap ) { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_NORMALS( 0 ) ); + } else { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_DIFFUSE( 0 ) ); + } + } + } else { + // Otherwise ... try to search for typical strings in the + // texture's file name like 'bump' or 'diffuse' + unsigned int iHM = 0,iNM = 0,iDM = 0,iSM = 0,iAM = 0,iEM = 0; + for( unsigned int b = 0; b < oldMat.mTextures.size(); ++b ) { + const XFile::TexEntry& otex = oldMat.mTextures[b]; + std::string sz = otex.mName; + if ( !sz.length() ) { + continue; + } + + // find the file name + std::string::size_type s = sz.find_last_of("\\/"); + if ( std::string::npos == s ) { + s = 0; + } + + // cut off the file extension + std::string::size_type sExt = sz.find_last_of('.'); + if (std::string::npos != sExt){ + sz[sExt] = '\0'; + } + + // convert to lower case for easier comparison + for ( unsigned int c = 0; c < sz.length(); ++c ) { + sz[ c ] = (char) tolower( (unsigned char) sz[ c ] ); + } + + // Place texture filename property under the corresponding name + aiString tex( oldMat.mTextures[b].mName); + + // bump map + if (std::string::npos != sz.find("bump", s) || std::string::npos != sz.find("height", s)) { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_HEIGHT(iHM++)); + } else if (otex.mIsNormalMap || std::string::npos != sz.find( "normal", s) || std::string::npos != sz.find("nm", s)) { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_NORMALS(iNM++)); + } else if (std::string::npos != sz.find( "spec", s) || std::string::npos != sz.find( "glanz", s)) { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_SPECULAR(iSM++)); + } else if (std::string::npos != sz.find( "ambi", s) || std::string::npos != sz.find( "env", s)) { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_AMBIENT(iAM++)); + } else if (std::string::npos != sz.find( "emissive", s) || std::string::npos != sz.find( "self", s)) { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_EMISSIVE(iEM++)); + } else { + // Assume it is a diffuse texture + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_DIFFUSE(iDM++)); + } + } + } + + pScene->mMaterials[pScene->mNumMaterials] = mat; + oldMat.sceneIndex = pScene->mNumMaterials; + pScene->mNumMaterials++; + } +} + +#endif // !! ASSIMP_BUILD_NO_X_IMPORTER diff --git a/libs/assimp/code/AssetLib/X/XFileImporter.h b/libs/assimp/code/AssetLib/X/XFileImporter.h new file mode 100644 index 0000000..6481390 --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileImporter.h @@ -0,0 +1,148 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 XFileImporter.h + * @brief Definition of the XFile importer class. + */ +#pragma once +#ifndef AI_XFILEIMPORTER_H_INC +#define AI_XFILEIMPORTER_H_INC + +#include <map> + +#include "XFileHelper.h" +#include <assimp/BaseImporter.h> + +#include <assimp/types.h> + +struct aiNode; + +namespace Assimp { + +namespace XFile { + struct Scene; + struct Node; +} + +// --------------------------------------------------------------------------- +/** The XFileImporter is a worker class capable of importing a scene from a + * DirectX file .x + */ +class XFileImporter : public BaseImporter { +public: + XFileImporter(); + ~XFileImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool CheckSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc* GetInfo () const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler) override; + + // ------------------------------------------------------------------- + /** Constructs the return data structure out of the imported data. + * @param pScene The scene to construct the return data in. + * @param pData The imported data in the internal temporary + * representation. + */ + void CreateDataRepresentationFromImport( aiScene* pScene, XFile::Scene* pData); + + // ------------------------------------------------------------------- + /** Recursively creates scene nodes from the imported hierarchy. + * The meshes and materials of the nodes will be extracted on the way. + * @param pScene The scene to construct the return data in. + * @param pParent The parent node where to create new child nodes + * @param pNode The temporary node to copy. + * @return The created node + */ + aiNode* CreateNodes( aiScene* pScene, aiNode* pParent, + const XFile::Node* pNode); + + // ------------------------------------------------------------------- + /** Converts all meshes in the given mesh array. Each mesh is split + * up per material, the indices of the generated meshes are stored in + * the node structure. + * @param pScene The scene to construct the return data in. + * @param pNode The target node structure that references the + * constructed meshes. + * @param pMeshes The array of meshes to convert + */ + void CreateMeshes( aiScene* pScene, aiNode* pNode, + const std::vector<XFile::Mesh*>& pMeshes); + + // ------------------------------------------------------------------- + /** Converts the animations from the given imported data and creates + * them in the scene. + * @param pScene The scene to hold to converted animations + * @param pData The data to read the animations from + */ + void CreateAnimations( aiScene* pScene, const XFile::Scene* pData); + + // ------------------------------------------------------------------- + /** Converts all materials in the given array and stores them in the + * scene's material list. + * @param pScene The scene to hold the converted materials. + * @param pMaterials The material array to convert. + */ + void ConvertMaterials( aiScene* pScene, std::vector<XFile::Material>& pMaterials); + +protected: + /// Buffer to hold the loaded file + std::vector<char> mBuffer; +}; + +} // end of namespace Assimp + +#endif // AI_BASEIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/X/XFileParser.cpp b/libs/assimp/code/AssetLib/X/XFileParser.cpp new file mode 100644 index 0000000..558b979 --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileParser.cpp @@ -0,0 +1,1360 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the XFile parser helper class */ + +#ifndef ASSIMP_BUILD_NO_X_IMPORTER + +#include "XFileParser.h" +#include "XFileHelper.h" +#include <assimp/ByteSwapper.h> +#include <assimp/Exceptional.h> +#include <assimp/StringUtils.h> +#include <assimp/TinyFormatter.h> +#include <assimp/fast_atof.h> +#include <assimp/DefaultLogger.hpp> + +using namespace Assimp; +using namespace Assimp::XFile; +using namespace Assimp::Formatter; + +#ifndef ASSIMP_BUILD_NO_COMPRESSED_X + +#include "Common/Compression.h" + +// Magic identifier for MSZIP compressed data +constexpr unsigned int MSZIP_MAGIC = 0x4B43; +constexpr size_t MSZIP_BLOCK = 32786l; + +#endif // !! ASSIMP_BUILD_NO_COMPRESSED_X + +// ------------------------------------------------------------------------------------------------ +// Throws an exception with a line number and the given text. +template<typename... T> +AI_WONT_RETURN void XFileParser::ThrowException(T&&... args) { + if (mIsBinaryFormat) { + throw DeadlyImportError(args...); + } else { + throw DeadlyImportError("Line ", mLineNumber, ": ", args...); + } +} + +// ------------------------------------------------------------------------------------------------ +// Constructor. Creates a data structure out of the XFile given in the memory block. +XFileParser::XFileParser(const std::vector<char> &pBuffer) : + mMajorVersion(0), mMinorVersion(0), mIsBinaryFormat(false), mBinaryNumCount(0), mP(nullptr), mEnd(nullptr), mLineNumber(0), mScene(nullptr) { + // vector to store uncompressed file for INFLATE'd X files + std::vector<char> uncompressed; + + // set up memory pointers + mP = &pBuffer.front(); + mEnd = mP + pBuffer.size() - 1; + + // check header + if (0 != strncmp(mP, "xof ", 4)) { + throw DeadlyImportError("Header mismatch, file is not an XFile."); + } + + // read version. It comes in a four byte format such as "0302" + mMajorVersion = (unsigned int)(mP[4] - 48) * 10 + (unsigned int)(mP[5] - 48); + mMinorVersion = (unsigned int)(mP[6] - 48) * 10 + (unsigned int)(mP[7] - 48); + + bool compressed = false; + + // txt - pure ASCII text format + if (strncmp(mP + 8, "txt ", 4) == 0) + mIsBinaryFormat = false; + + // bin - Binary format + else if (strncmp(mP + 8, "bin ", 4) == 0) + mIsBinaryFormat = true; + + // tzip - Inflate compressed text format + else if (strncmp(mP + 8, "tzip", 4) == 0) { + mIsBinaryFormat = false; + compressed = true; + } + // bzip - Inflate compressed binary format + else if (strncmp(mP + 8, "bzip", 4) == 0) { + mIsBinaryFormat = true; + compressed = true; + } else + ThrowException("Unsupported x-file format '", mP[8], mP[9], mP[10], mP[11], "'"); + + // float size + mBinaryFloatSize = (unsigned int)(mP[12] - 48) * 1000 + (unsigned int)(mP[13] - 48) * 100 + (unsigned int)(mP[14] - 48) * 10 + (unsigned int)(mP[15] - 48); + + if (mBinaryFloatSize != 32 && mBinaryFloatSize != 64) + ThrowException("Unknown float size ", mBinaryFloatSize, " specified in x-file header."); + + // The x format specifies size in bits, but we work in bytes + mBinaryFloatSize /= 8; + + mP += 16; + + // If this is a compressed X file, apply the inflate algorithm to it + if (compressed) { +#ifdef ASSIMP_BUILD_NO_COMPRESSED_X + throw DeadlyImportError("Assimp was built without compressed X support"); +#else + /* /////////////////////////////////////////////////////////////////////// + * COMPRESSED X FILE FORMAT + * /////////////////////////////////////////////////////////////////////// + * [xhead] + * 2 major + * 2 minor + * 4 type // bzip,tzip + * [mszip_master_head] + * 4 unkn // checksum? + * 2 unkn // flags? (seems to be constant) + * [mszip_head] + * 2 ofs // offset to next section + * 2 magic // 'CK' + * ... ofs bytes of data + * ... next mszip_head + * + * http://www.kdedevelopers.org/node/3181 has been very helpful. + * /////////////////////////////////////////////////////////////////////// + */ + + // skip unknown data (checksum, flags?) + mP += 6; + + // First find out how much storage we'll need. Count sections. + const char *P1 = mP; + unsigned int est_out = 0; + + while (P1 + 3 < mEnd) { + // read next offset + uint16_t ofs = *((uint16_t *)P1); + AI_SWAP2(ofs); + P1 += 2; + + if (ofs >= MSZIP_BLOCK) + throw DeadlyImportError("X: Invalid offset to next MSZIP compressed block"); + + // check magic word + uint16_t magic = *((uint16_t *)P1); + AI_SWAP2(magic); + P1 += 2; + + if (magic != MSZIP_MAGIC) + throw DeadlyImportError("X: Unsupported compressed format, expected MSZIP header"); + + // and advance to the next offset + P1 += ofs; + est_out += MSZIP_BLOCK; // one decompressed block is 327861 in size + } + + // Allocate storage and terminating zero and do the actual uncompressing + Compression compression; + uncompressed.resize(est_out + 1); + char *out = &uncompressed.front(); + if (compression.open(mIsBinaryFormat ? Compression::Format::Binary : Compression::Format::ASCII, + Compression::FlushMode::SyncFlush, -Compression::MaxWBits)) { + while (mP + 3 < mEnd) { + uint16_t ofs = *((uint16_t *)mP); + AI_SWAP2(ofs); + mP += 4; + + if (mP + ofs > mEnd + 2) { + throw DeadlyImportError("X: Unexpected EOF in compressed chunk"); + } + out += compression.decompressBlock(mP, ofs, out, MSZIP_BLOCK); + mP += ofs; + } + compression.close(); + } + + // ok, update pointers to point to the uncompressed file data + mP = &uncompressed[0]; + mEnd = out; + + // FIXME: we don't need the compressed data anymore, could release + // it already for better memory usage. Consider breaking const-co. + ASSIMP_LOG_INFO("Successfully decompressed MSZIP-compressed file"); +#endif // !! ASSIMP_BUILD_NO_COMPRESSED_X + } else { + // start reading here + ReadUntilEndOfLine(); + } + + mScene = new Scene; + ParseFile(); + + // filter the imported hierarchy for some degenerated cases + if (mScene->mRootNode) { + FilterHierarchy(mScene->mRootNode); + } +} + +// ------------------------------------------------------------------------------------------------ +// Destructor. Destroys all imported data along with it +XFileParser::~XFileParser() { + // kill everything we created + delete mScene; +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseFile() { + bool running = true; + while (running) { + // read name of next object + std::string objectName = GetNextToken(); + if (objectName.length() == 0) { + break; + } + + // parse specific object + if (objectName == "template") { + ParseDataObjectTemplate(); + } else if (objectName == "Frame") { + ParseDataObjectFrame(nullptr); + } else if (objectName == "Mesh") { + // some meshes have no frames at all + Mesh *mesh = new Mesh; + ParseDataObjectMesh(mesh); + mScene->mGlobalMeshes.push_back(mesh); + } else if (objectName == "AnimTicksPerSecond") + ParseDataObjectAnimTicksPerSecond(); + else if (objectName == "AnimationSet") + ParseDataObjectAnimationSet(); + else if (objectName == "Material") { + // Material outside of a mesh or node + Material material; + ParseDataObjectMaterial(&material); + mScene->mGlobalMaterials.push_back(material); + } else if (objectName == "}") { + // whatever? + ASSIMP_LOG_WARN("} found in dataObject"); + } else { + // unknown format + ASSIMP_LOG_WARN("Unknown data object in animation of .x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectTemplate() { + // parse a template data object. Currently not stored. + std::string name; + readHeadOfDataObject(&name); + + // read GUID + std::string guid = GetNextToken(); + + // read and ignore data members + bool running = true; + while (running) { + std::string s = GetNextToken(); + + if (s == "}") { + break; + } + + if (s.length() == 0) { + ThrowException("Unexpected end of file reached while parsing template definition"); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectFrame(Node *pParent) { + // A coordinate frame, or "frame of reference." The Frame template + // is open and can contain any object. The Direct3D extensions (D3DX) + // mesh-loading functions recognize Mesh, FrameTransformMatrix, and + // Frame template instances as child objects when loading a Frame + // instance. + std::string name; + readHeadOfDataObject(&name); + + // create a named node and place it at its parent, if given + Node *node = new Node(pParent); + node->mName = name; + if (pParent) { + pParent->mChildren.push_back(node); + } else { + // there might be multiple root nodes + if (mScene->mRootNode != nullptr) { + // place a dummy root if not there + if (mScene->mRootNode->mName != "$dummy_root") { + Node *exroot = mScene->mRootNode; + mScene->mRootNode = new Node(nullptr); + mScene->mRootNode->mName = "$dummy_root"; + mScene->mRootNode->mChildren.push_back(exroot); + exroot->mParent = mScene->mRootNode; + } + // put the new node as its child instead + mScene->mRootNode->mChildren.push_back(node); + node->mParent = mScene->mRootNode; + } else { + // it's the first node imported. place it as root + mScene->mRootNode = node; + } + } + + // Now inside a frame. + // read tokens until closing brace is reached. + bool running = true; + while (running) { + std::string objectName = GetNextToken(); + if (objectName.size() == 0) + ThrowException("Unexpected end of file reached while parsing frame"); + + if (objectName == "}") + break; // frame finished + else if (objectName == "Frame") + ParseDataObjectFrame(node); // child frame + else if (objectName == "FrameTransformMatrix") + ParseDataObjectTransformationMatrix(node->mTrafoMatrix); + else if (objectName == "Mesh") { + Mesh *mesh = new Mesh(name); + node->mMeshes.push_back(mesh); + ParseDataObjectMesh(mesh); + } else { + ASSIMP_LOG_WARN("Unknown data object in frame in x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectTransformationMatrix(aiMatrix4x4 &pMatrix) { + // read header, we're not interested if it has a name + readHeadOfDataObject(); + + // read its components + pMatrix.a1 = ReadFloat(); + pMatrix.b1 = ReadFloat(); + pMatrix.c1 = ReadFloat(); + pMatrix.d1 = ReadFloat(); + pMatrix.a2 = ReadFloat(); + pMatrix.b2 = ReadFloat(); + pMatrix.c2 = ReadFloat(); + pMatrix.d2 = ReadFloat(); + pMatrix.a3 = ReadFloat(); + pMatrix.b3 = ReadFloat(); + pMatrix.c3 = ReadFloat(); + pMatrix.d3 = ReadFloat(); + pMatrix.a4 = ReadFloat(); + pMatrix.b4 = ReadFloat(); + pMatrix.c4 = ReadFloat(); + pMatrix.d4 = ReadFloat(); + + // trailing symbols + CheckForSemicolon(); + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectMesh(Mesh *pMesh) { + std::string name; + readHeadOfDataObject(&name); + + // read vertex count + unsigned int numVertices = ReadInt(); + pMesh->mPositions.resize(numVertices); + + // read vertices + for (unsigned int a = 0; a < numVertices; a++) + pMesh->mPositions[a] = ReadVector3(); + + // read position faces + unsigned int numPosFaces = ReadInt(); + pMesh->mPosFaces.resize(numPosFaces); + for (unsigned int a = 0; a < numPosFaces; ++a) { + // read indices + unsigned int numIndices = ReadInt(); + Face &face = pMesh->mPosFaces[a]; + for (unsigned int b = 0; b < numIndices; ++b) { + const int idx(ReadInt()); + if (static_cast<unsigned int>(idx) <= numVertices) { + face.mIndices.push_back(idx); + } + } + TestForSeparator(); + } + + // here, other data objects may follow + bool running = true; + while (running) { + std::string objectName = GetNextToken(); + + if (objectName.empty()) + ThrowException("Unexpected end of file while parsing mesh structure"); + else if (objectName == "}") + break; // mesh finished + else if (objectName == "MeshNormals") + ParseDataObjectMeshNormals(pMesh); + else if (objectName == "MeshTextureCoords") + ParseDataObjectMeshTextureCoords(pMesh); + else if (objectName == "MeshVertexColors") + ParseDataObjectMeshVertexColors(pMesh); + else if (objectName == "MeshMaterialList") + ParseDataObjectMeshMaterialList(pMesh); + else if (objectName == "VertexDuplicationIndices") + ParseUnknownDataObject(); // we'll ignore vertex duplication indices + else if (objectName == "XSkinMeshHeader") + ParseDataObjectSkinMeshHeader(pMesh); + else if (objectName == "SkinWeights") + ParseDataObjectSkinWeights(pMesh); + else { + ASSIMP_LOG_WARN("Unknown data object in mesh in x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectSkinWeights(Mesh *pMesh) { + if (nullptr == pMesh) { + return; + } + readHeadOfDataObject(); + + std::string transformNodeName; + GetNextTokenAsString(transformNodeName); + + pMesh->mBones.push_back(Bone()); + Bone &bone = pMesh->mBones.back(); + bone.mName = transformNodeName; + + // read vertex weights + unsigned int numWeights = ReadInt(); + bone.mWeights.reserve(numWeights); + + for (unsigned int a = 0; a < numWeights; a++) { + BoneWeight weight = {}; + weight.mVertex = ReadInt(); + bone.mWeights.push_back(weight); + } + + // read vertex weights + for (unsigned int a = 0; a < numWeights; a++) + bone.mWeights[a].mWeight = ReadFloat(); + + // read matrix offset + bone.mOffsetMatrix.a1 = ReadFloat(); + bone.mOffsetMatrix.b1 = ReadFloat(); + bone.mOffsetMatrix.c1 = ReadFloat(); + bone.mOffsetMatrix.d1 = ReadFloat(); + bone.mOffsetMatrix.a2 = ReadFloat(); + bone.mOffsetMatrix.b2 = ReadFloat(); + bone.mOffsetMatrix.c2 = ReadFloat(); + bone.mOffsetMatrix.d2 = ReadFloat(); + bone.mOffsetMatrix.a3 = ReadFloat(); + bone.mOffsetMatrix.b3 = ReadFloat(); + bone.mOffsetMatrix.c3 = ReadFloat(); + bone.mOffsetMatrix.d3 = ReadFloat(); + bone.mOffsetMatrix.a4 = ReadFloat(); + bone.mOffsetMatrix.b4 = ReadFloat(); + bone.mOffsetMatrix.c4 = ReadFloat(); + bone.mOffsetMatrix.d4 = ReadFloat(); + + CheckForSemicolon(); + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectSkinMeshHeader(Mesh * /*pMesh*/) { + readHeadOfDataObject(); + + /*unsigned int maxSkinWeightsPerVertex =*/ReadInt(); + /*unsigned int maxSkinWeightsPerFace =*/ReadInt(); + /*unsigned int numBonesInMesh = */ ReadInt(); + + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectMeshNormals(Mesh *pMesh) { + readHeadOfDataObject(); + + // read count + unsigned int numNormals = ReadInt(); + pMesh->mNormals.resize(numNormals); + + // read normal vectors + for (unsigned int a = 0; a < numNormals; ++a) { + pMesh->mNormals[a] = ReadVector3(); + } + + // read normal indices + unsigned int numFaces = ReadInt(); + if (numFaces != pMesh->mPosFaces.size()) { + ThrowException("Normal face count does not match vertex face count."); + } + + // do not crah when no face definitions are there + if (numFaces > 0) { + // normal face creation + pMesh->mNormFaces.resize(numFaces); + for (unsigned int a = 0; a < numFaces; ++a) { + unsigned int numIndices = ReadInt(); + pMesh->mNormFaces[a] = Face(); + Face &face = pMesh->mNormFaces[a]; + for (unsigned int b = 0; b < numIndices; ++b) { + face.mIndices.push_back(ReadInt()); + } + + TestForSeparator(); + } + } + + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectMeshTextureCoords(Mesh *pMesh) { + readHeadOfDataObject(); + if (pMesh->mNumTextures + 1 > AI_MAX_NUMBER_OF_TEXTURECOORDS) + ThrowException("Too many sets of texture coordinates"); + + std::vector<aiVector2D> &coords = pMesh->mTexCoords[pMesh->mNumTextures++]; + + unsigned int numCoords = ReadInt(); + if (numCoords != pMesh->mPositions.size()) + ThrowException("Texture coord count does not match vertex count"); + + coords.resize(numCoords); + for (unsigned int a = 0; a < numCoords; a++) + coords[a] = ReadVector2(); + + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectMeshVertexColors(Mesh *pMesh) { + readHeadOfDataObject(); + if (pMesh->mNumColorSets + 1 > AI_MAX_NUMBER_OF_COLOR_SETS) + ThrowException("Too many colorsets"); + std::vector<aiColor4D> &colors = pMesh->mColors[pMesh->mNumColorSets++]; + + unsigned int numColors = ReadInt(); + if (numColors != pMesh->mPositions.size()) + ThrowException("Vertex color count does not match vertex count"); + + colors.resize(numColors, aiColor4D(0, 0, 0, 1)); + for (unsigned int a = 0; a < numColors; a++) { + unsigned int index = ReadInt(); + if (index >= pMesh->mPositions.size()) + ThrowException("Vertex color index out of bounds"); + + colors[index] = ReadRGBA(); + // HACK: (thom) Maxon Cinema XPort plugin puts a third separator here, kwxPort puts a comma. + // Ignore gracefully. + if (!mIsBinaryFormat) { + FindNextNoneWhiteSpace(); + if (*mP == ';' || *mP == ',') + mP++; + } + } + + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectMeshMaterialList(Mesh *pMesh) { + readHeadOfDataObject(); + + // read material count + /*unsigned int numMaterials =*/ReadInt(); + // read non triangulated face material index count + unsigned int numMatIndices = ReadInt(); + + // some models have a material index count of 1... to be able to read them we + // replicate this single material index on every face + if (numMatIndices != pMesh->mPosFaces.size() && numMatIndices != 1) + ThrowException("Per-Face material index count does not match face count."); + + // read per-face material indices + for (unsigned int a = 0; a < numMatIndices; a++) + pMesh->mFaceMaterials.push_back(ReadInt()); + + // in version 03.02, the face indices end with two semicolons. + // commented out version check, as version 03.03 exported from blender also has 2 semicolons + if (!mIsBinaryFormat) // && MajorVersion == 3 && MinorVersion <= 2) + { + if (mP < mEnd && *mP == ';') + ++mP; + } + + // if there was only a single material index, replicate it on all faces + while (pMesh->mFaceMaterials.size() < pMesh->mPosFaces.size()) + pMesh->mFaceMaterials.push_back(pMesh->mFaceMaterials.front()); + + // read following data objects + bool running = true; + while (running) { + std::string objectName = GetNextToken(); + if (objectName.size() == 0) + ThrowException("Unexpected end of file while parsing mesh material list."); + else if (objectName == "}") + break; // material list finished + else if (objectName == "{") { + // template materials + std::string matName = GetNextToken(); + Material material; + material.mIsReference = true; + material.mName = matName; + pMesh->mMaterials.push_back(material); + + CheckForClosingBrace(); // skip } + } else if (objectName == "Material") { + pMesh->mMaterials.push_back(Material()); + ParseDataObjectMaterial(&pMesh->mMaterials.back()); + } else if (objectName == ";") { + // ignore + } else { + ASSIMP_LOG_WARN("Unknown data object in material list in x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectMaterial(Material *pMaterial) { + std::string matName; + readHeadOfDataObject(&matName); + if (matName.empty()) + matName = std::string("material") + ai_to_string(mLineNumber); + pMaterial->mName = matName; + pMaterial->mIsReference = false; + + // read material values + pMaterial->mDiffuse = ReadRGBA(); + pMaterial->mSpecularExponent = ReadFloat(); + pMaterial->mSpecular = ReadRGB(); + pMaterial->mEmissive = ReadRGB(); + + // read other data objects + bool running = true; + while (running) { + std::string objectName = GetNextToken(); + if (objectName.size() == 0) + ThrowException("Unexpected end of file while parsing mesh material"); + else if (objectName == "}") + break; // material finished + else if (objectName == "TextureFilename" || objectName == "TextureFileName") { + // some exporters write "TextureFileName" instead. + std::string texname; + ParseDataObjectTextureFilename(texname); + pMaterial->mTextures.push_back(TexEntry(texname)); + } else if (objectName == "NormalmapFilename" || objectName == "NormalmapFileName") { + // one exporter writes out the normal map in a separate filename tag + std::string texname; + ParseDataObjectTextureFilename(texname); + pMaterial->mTextures.push_back(TexEntry(texname, true)); + } else { + ASSIMP_LOG_WARN("Unknown data object in material in x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectAnimTicksPerSecond() { + readHeadOfDataObject(); + mScene->mAnimTicksPerSecond = ReadInt(); + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectAnimationSet() { + std::string animName; + readHeadOfDataObject(&animName); + + Animation *anim = new Animation; + mScene->mAnims.push_back(anim); + anim->mName = animName; + + bool running = true; + while (running) { + std::string objectName = GetNextToken(); + if (objectName.length() == 0) + ThrowException("Unexpected end of file while parsing animation set."); + else if (objectName == "}") + break; // animation set finished + else if (objectName == "Animation") + ParseDataObjectAnimation(anim); + else { + ASSIMP_LOG_WARN("Unknown data object in animation set in x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectAnimation(Animation *pAnim) { + readHeadOfDataObject(); + AnimBone *banim = new AnimBone; + pAnim->mAnims.push_back(banim); + + bool running = true; + while (running) { + std::string objectName = GetNextToken(); + + if (objectName.length() == 0) + ThrowException("Unexpected end of file while parsing animation."); + else if (objectName == "}") + break; // animation finished + else if (objectName == "AnimationKey") + ParseDataObjectAnimationKey(banim); + else if (objectName == "AnimationOptions") + ParseUnknownDataObject(); // not interested + else if (objectName == "{") { + // read frame name + banim->mBoneName = GetNextToken(); + CheckForClosingBrace(); + } else { + ASSIMP_LOG_WARN("Unknown data object in animation in x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectAnimationKey(AnimBone *pAnimBone) { + readHeadOfDataObject(); + + // read key type + unsigned int keyType = ReadInt(); + + // read number of keys + unsigned int numKeys = ReadInt(); + + for (unsigned int a = 0; a < numKeys; a++) { + // read time + unsigned int time = ReadInt(); + + // read keys + switch (keyType) { + case 0: // rotation quaternion + { + // read count + if (ReadInt() != 4) + ThrowException("Invalid number of arguments for quaternion key in animation"); + + aiQuatKey key; + key.mTime = double(time); + key.mValue.w = ReadFloat(); + key.mValue.x = ReadFloat(); + key.mValue.y = ReadFloat(); + key.mValue.z = ReadFloat(); + pAnimBone->mRotKeys.push_back(key); + + CheckForSemicolon(); + break; + } + + case 1: // scale vector + case 2: // position vector + { + // read count + if (ReadInt() != 3) + ThrowException("Invalid number of arguments for vector key in animation"); + + aiVectorKey key; + key.mTime = double(time); + key.mValue = ReadVector3(); + + if (keyType == 2) + pAnimBone->mPosKeys.push_back(key); + else + pAnimBone->mScaleKeys.push_back(key); + + break; + } + + case 3: // combined transformation matrix + case 4: // denoted both as 3 or as 4 + { + // read count + if (ReadInt() != 16) + ThrowException("Invalid number of arguments for matrix key in animation"); + + // read matrix + MatrixKey key; + key.mTime = double(time); + key.mMatrix.a1 = ReadFloat(); + key.mMatrix.b1 = ReadFloat(); + key.mMatrix.c1 = ReadFloat(); + key.mMatrix.d1 = ReadFloat(); + key.mMatrix.a2 = ReadFloat(); + key.mMatrix.b2 = ReadFloat(); + key.mMatrix.c2 = ReadFloat(); + key.mMatrix.d2 = ReadFloat(); + key.mMatrix.a3 = ReadFloat(); + key.mMatrix.b3 = ReadFloat(); + key.mMatrix.c3 = ReadFloat(); + key.mMatrix.d3 = ReadFloat(); + key.mMatrix.a4 = ReadFloat(); + key.mMatrix.b4 = ReadFloat(); + key.mMatrix.c4 = ReadFloat(); + key.mMatrix.d4 = ReadFloat(); + pAnimBone->mTrafoKeys.push_back(key); + + CheckForSemicolon(); + break; + } + + default: + ThrowException("Unknown key type ", keyType, " in animation."); + break; + } // end switch + + // key separator + CheckForSeparator(); + } + + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectTextureFilename(std::string &pName) { + readHeadOfDataObject(); + GetNextTokenAsString(pName); + CheckForClosingBrace(); + + // FIX: some files (e.g. AnimationTest.x) have "" as texture file name + if (!pName.length()) { + ASSIMP_LOG_WARN("Length of texture file name is zero. Skipping this texture."); + } + + // some exporters write double backslash paths out. We simply replace them if we find them + while (pName.find("\\\\") != std::string::npos) + pName.replace(pName.find("\\\\"), 2, "\\"); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseUnknownDataObject() { + // find opening delimiter + bool running = true; + while (running) { + std::string t = GetNextToken(); + if (t.length() == 0) + ThrowException("Unexpected end of file while parsing unknown segment."); + + if (t == "{") + break; + } + + unsigned int counter = 1; + + // parse until closing delimiter + while (counter > 0) { + std::string t = GetNextToken(); + + if (t.length() == 0) + ThrowException("Unexpected end of file while parsing unknown segment."); + + if (t == "{") + ++counter; + else if (t == "}") + --counter; + } +} + +// ------------------------------------------------------------------------------------------------ +//! checks for closing curly brace +void XFileParser::CheckForClosingBrace() { + if (GetNextToken() != "}") + ThrowException("Closing brace expected."); +} + +// ------------------------------------------------------------------------------------------------ +//! checks for one following semicolon +void XFileParser::CheckForSemicolon() { + if (mIsBinaryFormat) + return; + + if (GetNextToken() != ";") + ThrowException("Semicolon expected."); +} + +// ------------------------------------------------------------------------------------------------ +//! checks for a separator char, either a ',' or a ';' +void XFileParser::CheckForSeparator() { + if (mIsBinaryFormat) + return; + + std::string token = GetNextToken(); + if (token != "," && token != ";") + ThrowException("Separator character (';' or ',') expected."); +} + +// ------------------------------------------------------------------------------------------------ +// tests and possibly consumes a separator char, but does nothing if there was no separator +void XFileParser::TestForSeparator() { + if (mIsBinaryFormat) + return; + + FindNextNoneWhiteSpace(); + if (mP >= mEnd) + return; + + // test and skip + if (*mP == ';' || *mP == ',') + mP++; +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::readHeadOfDataObject(std::string *poName) { + std::string nameOrBrace = GetNextToken(); + if (nameOrBrace != "{") { + if (poName) + *poName = nameOrBrace; + + if (GetNextToken() != "{") { + delete mScene; + ThrowException("Opening brace expected."); + } + } +} + +// ------------------------------------------------------------------------------------------------ +std::string XFileParser::GetNextToken() { + std::string s; + + // process binary-formatted file + if (mIsBinaryFormat) { + // in binary mode it will only return NAME and STRING token + // and (correctly) skip over other tokens. + if (mEnd - mP < 2) { + return s; + } + unsigned int tok = ReadBinWord(); + unsigned int len; + + // standalone tokens + switch (tok) { + case 1: { + // name token + if (mEnd - mP < 4) { + return s; + } + len = ReadBinDWord(); + const int bounds = int(mEnd - mP); + const int iLen = int(len); + if (iLen < 0) { + return s; + } + if (bounds < iLen) { + return s; + } + s = std::string(mP, len); + mP += len; + } + return s; + + case 2: + // string token + if (mEnd - mP < 4) return s; + len = ReadBinDWord(); + if (mEnd - mP < int(len)) return s; + s = std::string(mP, len); + mP += (len + 2); + return s; + case 3: + // integer token + mP += 4; + return "<integer>"; + case 5: + // GUID token + mP += 16; + return "<guid>"; + case 6: + if (mEnd - mP < 4) return s; + len = ReadBinDWord(); + mP += (len * 4); + return "<int_list>"; + case 7: + if (mEnd - mP < 4) return s; + len = ReadBinDWord(); + mP += (len * mBinaryFloatSize); + return "<flt_list>"; + case 0x0a: + return "{"; + case 0x0b: + return "}"; + case 0x0c: + return "("; + case 0x0d: + return ")"; + case 0x0e: + return "["; + case 0x0f: + return "]"; + case 0x10: + return "<"; + case 0x11: + return ">"; + case 0x12: + return "."; + case 0x13: + return ","; + case 0x14: + return ";"; + case 0x1f: + return "template"; + case 0x28: + return "WORD"; + case 0x29: + return "DWORD"; + case 0x2a: + return "FLOAT"; + case 0x2b: + return "DOUBLE"; + case 0x2c: + return "CHAR"; + case 0x2d: + return "UCHAR"; + case 0x2e: + return "SWORD"; + case 0x2f: + return "SDWORD"; + case 0x30: + return "void"; + case 0x31: + return "string"; + case 0x32: + return "unicode"; + case 0x33: + return "cstring"; + case 0x34: + return "array"; + } + } + // process text-formatted file + else { + FindNextNoneWhiteSpace(); + if (mP >= mEnd) + return s; + + while ((mP < mEnd) && !isspace((unsigned char)*mP)) { + // either keep token delimiters when already holding a token, or return if first valid char + if (*mP == ';' || *mP == '}' || *mP == '{' || *mP == ',') { + if (!s.size()) + s.append(mP++, 1); + break; // stop for delimiter + } + s.append(mP++, 1); + } + } + return s; +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::FindNextNoneWhiteSpace() { + if (mIsBinaryFormat) + return; + + bool running = true; + while (running) { + while (mP < mEnd && isspace((unsigned char)*mP)) { + if (*mP == '\n') + mLineNumber++; + ++mP; + } + + if (mP >= mEnd) + return; + + // check if this is a comment + if ((mP[0] == '/' && mP[1] == '/') || mP[0] == '#') + ReadUntilEndOfLine(); + else + break; + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::GetNextTokenAsString(std::string &poString) { + if (mIsBinaryFormat) { + poString = GetNextToken(); + return; + } + + FindNextNoneWhiteSpace(); + if (mP >= mEnd) { + delete mScene; + ThrowException("Unexpected end of file while parsing string"); + } + + if (*mP != '"') { + delete mScene; + ThrowException("Expected quotation mark."); + } + ++mP; + + while (mP < mEnd && *mP != '"') + poString.append(mP++, 1); + + if (mP >= mEnd - 1) { + delete mScene; + ThrowException("Unexpected end of file while parsing string"); + } + + if (mP[1] != ';' || mP[0] != '"') { + delete mScene; + ThrowException("Expected quotation mark and semicolon at the end of a string."); + } + mP += 2; +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ReadUntilEndOfLine() { + if (mIsBinaryFormat) + return; + + while (mP < mEnd) { + if (*mP == '\n' || *mP == '\r') { + ++mP; + mLineNumber++; + return; + } + + ++mP; + } +} + +// ------------------------------------------------------------------------------------------------ +unsigned short XFileParser::ReadBinWord() { + ai_assert(mEnd - mP >= 2); + const unsigned char *q = (const unsigned char *)mP; + unsigned short tmp = q[0] | (q[1] << 8); + mP += 2; + return tmp; +} + +// ------------------------------------------------------------------------------------------------ +unsigned int XFileParser::ReadBinDWord() { + ai_assert(mEnd - mP >= 4); + + const unsigned char *q = (const unsigned char *)mP; + unsigned int tmp = q[0] | (q[1] << 8) | (q[2] << 16) | (q[3] << 24); + mP += 4; + return tmp; +} + +// ------------------------------------------------------------------------------------------------ +unsigned int XFileParser::ReadInt() { + if (mIsBinaryFormat) { + if (mBinaryNumCount == 0 && mEnd - mP >= 2) { + unsigned short tmp = ReadBinWord(); // 0x06 or 0x03 + if (tmp == 0x06 && mEnd - mP >= 4) // array of ints follows + mBinaryNumCount = ReadBinDWord(); + else // single int follows + mBinaryNumCount = 1; + } + + --mBinaryNumCount; + const size_t len(mEnd - mP); + if (len >= 4) { + return ReadBinDWord(); + } else { + mP = mEnd; + return 0; + } + } else { + FindNextNoneWhiteSpace(); + + // TODO: consider using strtol10 instead??? + + // check preceding minus sign + bool isNegative = false; + if (*mP == '-') { + isNegative = true; + mP++; + } + + // at least one digit expected + if (!isdigit((unsigned char)*mP)) + ThrowException("Number expected."); + + // read digits + unsigned int number = 0; + while (mP < mEnd) { + if (!isdigit((unsigned char)*mP)) + break; + number = number * 10 + (*mP - 48); + mP++; + } + + CheckForSeparator(); + + return isNegative ? ((unsigned int)-int(number)) : number; + } +} + +// ------------------------------------------------------------------------------------------------ +ai_real XFileParser::ReadFloat() { + if (mIsBinaryFormat) { + if (mBinaryNumCount == 0 && mEnd - mP >= 2) { + unsigned short tmp = ReadBinWord(); // 0x07 or 0x42 + if (tmp == 0x07 && mEnd - mP >= 4) // array of floats following + mBinaryNumCount = ReadBinDWord(); + else // single float following + mBinaryNumCount = 1; + } + + --mBinaryNumCount; + if (mBinaryFloatSize == 8) { + if (mEnd - mP >= 8) { + double res; + ::memcpy(&res, mP, 8); + mP += 8; + const ai_real result(static_cast<ai_real>(res)); + return result; + } else { + mP = mEnd; + return 0; + } + } else { + if (mEnd - mP >= 4) { + ai_real result; + ::memcpy(&result, mP, 4); + mP += 4; + return result; + } else { + mP = mEnd; + return 0; + } + } + } + + // text version + FindNextNoneWhiteSpace(); + // check for various special strings to allow reading files from faulty exporters + // I mean you, Blender! + // Reading is safe because of the terminating zero + if (strncmp(mP, "-1.#IND00", 9) == 0 || strncmp(mP, "1.#IND00", 8) == 0) { + mP += 9; + CheckForSeparator(); + return 0.0; + } else if (strncmp(mP, "1.#QNAN0", 8) == 0) { + mP += 8; + CheckForSeparator(); + return 0.0; + } + + ai_real result = 0.0; + mP = fast_atoreal_move<ai_real>(mP, result); + + CheckForSeparator(); + + return result; +} + +// ------------------------------------------------------------------------------------------------ +aiVector2D XFileParser::ReadVector2() { + aiVector2D vector; + vector.x = ReadFloat(); + vector.y = ReadFloat(); + TestForSeparator(); + + return vector; +} + +// ------------------------------------------------------------------------------------------------ +aiVector3D XFileParser::ReadVector3() { + aiVector3D vector; + vector.x = ReadFloat(); + vector.y = ReadFloat(); + vector.z = ReadFloat(); + TestForSeparator(); + + return vector; +} + +// ------------------------------------------------------------------------------------------------ +aiColor4D XFileParser::ReadRGBA() { + aiColor4D color; + color.r = ReadFloat(); + color.g = ReadFloat(); + color.b = ReadFloat(); + color.a = ReadFloat(); + TestForSeparator(); + + return color; +} + +// ------------------------------------------------------------------------------------------------ +aiColor3D XFileParser::ReadRGB() { + aiColor3D color; + color.r = ReadFloat(); + color.g = ReadFloat(); + color.b = ReadFloat(); + TestForSeparator(); + + return color; +} + +// ------------------------------------------------------------------------------------------------ +// Filters the imported hierarchy for some degenerated cases that some exporters produce. +void XFileParser::FilterHierarchy(XFile::Node *pNode) { + // if the node has just a single unnamed child containing a mesh, remove + // the anonymous node between. The 3DSMax kwXport plugin seems to produce this + // mess in some cases + if (pNode->mChildren.size() == 1 && pNode->mMeshes.empty()) { + XFile::Node *child = pNode->mChildren.front(); + if (child->mName.length() == 0 && child->mMeshes.size() > 0) { + // transfer its meshes to us + for (unsigned int a = 0; a < child->mMeshes.size(); a++) + pNode->mMeshes.push_back(child->mMeshes[a]); + child->mMeshes.clear(); + + // transfer the transform as well + pNode->mTrafoMatrix = pNode->mTrafoMatrix * child->mTrafoMatrix; + + // then kill it + delete child; + pNode->mChildren.clear(); + } + } + + // recurse + for (unsigned int a = 0; a < pNode->mChildren.size(); a++) + FilterHierarchy(pNode->mChildren[a]); +} + +#endif // !! ASSIMP_BUILD_NO_X_IMPORTER diff --git a/libs/assimp/code/AssetLib/X/XFileParser.h b/libs/assimp/code/AssetLib/X/XFileParser.h new file mode 100644 index 0000000..36eac2a --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileParser.h @@ -0,0 +1,158 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Helper class to parse a XFile into a temporary structure */ +#ifndef AI_XFILEPARSER_H_INC +#define AI_XFILEPARSER_H_INC + +#include <string> +#include <vector> + +#include <assimp/types.h> + +namespace Assimp { + namespace XFile { + struct Node; + struct Mesh; + struct Scene; + struct Material; + struct Animation; + struct AnimBone; + } + +/** + * @brief The XFileParser reads a XFile either in text or binary form and builds a temporary + * data structure out of it. + */ +class XFileParser { +public: + /// Constructor. Creates a data structure out of the XFile given in the memory block. + /// @param pBuffer Null-terminated memory buffer containing the XFile + explicit XFileParser( const std::vector<char>& pBuffer); + + /// Destructor. Destroys all imported data along with it + ~XFileParser(); + + /// Returns the temporary representation of the imported data. + XFile::Scene* GetImportedData() const { return mScene; } + +protected: + void ParseFile(); + void ParseDataObjectTemplate(); + void ParseDataObjectFrame( XFile::Node *pParent); + void ParseDataObjectTransformationMatrix( aiMatrix4x4& pMatrix); + void ParseDataObjectMesh( XFile::Mesh* pMesh); + void ParseDataObjectSkinWeights( XFile::Mesh* pMesh); + void ParseDataObjectSkinMeshHeader( XFile::Mesh* pMesh); + void ParseDataObjectMeshNormals( XFile::Mesh* pMesh); + void ParseDataObjectMeshTextureCoords( XFile::Mesh* pMesh); + void ParseDataObjectMeshVertexColors( XFile::Mesh* pMesh); + void ParseDataObjectMeshMaterialList( XFile::Mesh* pMesh); + void ParseDataObjectMaterial( XFile::Material* pMaterial); + void ParseDataObjectAnimTicksPerSecond(); + void ParseDataObjectAnimationSet(); + void ParseDataObjectAnimation( XFile::Animation* pAnim); + void ParseDataObjectAnimationKey( XFile::AnimBone *pAnimBone); + void ParseDataObjectTextureFilename( std::string& pName); + void ParseUnknownDataObject(); + + //! places pointer to next begin of a token, and ignores comments + void FindNextNoneWhiteSpace(); + + //! returns next valid token. Returns empty string if no token there + std::string GetNextToken(); + + //! reads header of data object including the opening brace. + //! returns false if error happened, and writes name of object + //! if there is one + void readHeadOfDataObject(std::string *poName = nullptr); + + //! checks for closing curly brace, throws exception if not there + void CheckForClosingBrace(); + + //! checks for one following semicolon, throws exception if not there + void CheckForSemicolon(); + + //! checks for a separator char, either a ',' or a ';' + void CheckForSeparator(); + + /// tests and possibly consumes a separator char, but does nothing if there was no separator + void TestForSeparator(); + + //! reads a x file style string + void GetNextTokenAsString( std::string& poString); + + void ReadUntilEndOfLine(); + + unsigned short ReadBinWord(); + unsigned int ReadBinDWord(); + unsigned int ReadInt(); + ai_real ReadFloat(); + aiVector2D ReadVector2(); + aiVector3D ReadVector3(); + aiColor3D ReadRGB(); + aiColor4D ReadRGBA(); + + /** Throws an exception with a line number and the given text. */ + template<typename... T> + AI_WONT_RETURN void ThrowException(T&&... args) AI_WONT_RETURN_SUFFIX; + + /** + * @brief Filters the imported hierarchy for some degenerated cases that some exporters produce. + * @param pData The sub-hierarchy to filter + */ + void FilterHierarchy( XFile::Node* pNode); + +protected: + unsigned int mMajorVersion, mMinorVersion; ///< version numbers + bool mIsBinaryFormat; ///< true if the file is in binary, false if it's in text form + unsigned int mBinaryFloatSize; ///< float size in bytes, either 4 or 8 + unsigned int mBinaryNumCount; /// < counter for number arrays in binary format + const char* mP; + const char* mEnd; + unsigned int mLineNumber; ///< Line number when reading in text format + XFile::Scene* mScene; ///< Imported data +}; + +} //! ns Assimp + +#endif // AI_XFILEPARSER_H_INC diff --git a/libs/assimp/code/AssetLib/X3D/X3DExporter.cpp b/libs/assimp/code/AssetLib/X3D/X3DExporter.cpp new file mode 100644 index 0000000..b3278a5 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DExporter.cpp @@ -0,0 +1,675 @@ +/// \file X3DExporter.cpp +/// \brief X3D-format files exporter for Assimp. Implementation. +/// \date 2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_X3D_EXPORTER + +#include "X3DExporter.hpp" + +// Header files, Assimp. +#include <assimp/Exceptional.h> +#include <assimp/StringUtils.h> +#include <assimp/Exporter.hpp> +#include <assimp/IOSystem.hpp> + +using namespace std; + +namespace Assimp { + +void ExportSceneX3D(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties *pProperties) { + X3DExporter exporter(pFile, pIOSystem, pScene, pProperties); +} + +} // namespace Assimp + +namespace Assimp { + +void X3DExporter::IndentationStringSet(const size_t pNewLevel) { + if (pNewLevel > mIndentationString.size()) { + if (pNewLevel > mIndentationString.capacity()) mIndentationString.reserve(pNewLevel + 1); + + for (size_t i = 0, i_e = pNewLevel - mIndentationString.size(); i < i_e; i++) + mIndentationString.push_back('\t'); + } else if (pNewLevel < mIndentationString.size()) { + mIndentationString.resize(pNewLevel); + } +} + +void X3DExporter::XML_Write(const string &pData) { + if (pData.size() == 0) return; + if (mOutFile->Write((void *)pData.data(), pData.length(), 1) != 1) throw DeadlyExportError("Failed to write scene data!"); +} + +aiMatrix4x4 X3DExporter::Matrix_GlobalToCurrent(const aiNode &pNode) const { + aiNode *cur_node; + std::list<aiMatrix4x4> matr; + aiMatrix4x4 out_matr; + + // starting walk from current element to root + matr.push_back(pNode.mTransformation); + cur_node = pNode.mParent; + if (cur_node != nullptr) { + do { + matr.push_back(cur_node->mTransformation); + cur_node = cur_node->mParent; + } while (cur_node != nullptr); + } + + // multiplicate all matrices in reverse order + for (std::list<aiMatrix4x4>::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); ++rit) + out_matr = out_matr * (*rit); + + return out_matr; +} + +void X3DExporter::AttrHelper_FloatToString(const float pValue, std::string &pTargetString) { + pTargetString = to_string(pValue); + AttrHelper_CommaToPoint(pTargetString); +} + +void X3DExporter::AttrHelper_Vec3DArrToString(const aiVector3D *pArray, const size_t pArray_Size, string &pTargetString) { + pTargetString.clear(); + pTargetString.reserve(pArray_Size * 6); // (Number + space) * 3. + for (size_t idx = 0; idx < pArray_Size; idx++) + pTargetString.append(to_string(pArray[idx].x) + " " + to_string(pArray[idx].y) + " " + to_string(pArray[idx].z) + " "); + + // remove last space symbol. + pTargetString.resize(pTargetString.length() - 1); + AttrHelper_CommaToPoint(pTargetString); +} + +void X3DExporter::AttrHelper_Vec2DArrToString(const aiVector2D *pArray, const size_t pArray_Size, std::string &pTargetString) { + pTargetString.clear(); + pTargetString.reserve(pArray_Size * 4); // (Number + space) * 2. + for (size_t idx = 0; idx < pArray_Size; idx++) + pTargetString.append(to_string(pArray[idx].x) + " " + to_string(pArray[idx].y) + " "); + + // remove last space symbol. + pTargetString.resize(pTargetString.length() - 1); + AttrHelper_CommaToPoint(pTargetString); +} + +void X3DExporter::AttrHelper_Vec3DAsVec2fArrToString(const aiVector3D *pArray, const size_t pArray_Size, string &pTargetString) { + pTargetString.clear(); + pTargetString.reserve(pArray_Size * 4); // (Number + space) * 2. + for (size_t idx = 0; idx < pArray_Size; idx++) + pTargetString.append(to_string(pArray[idx].x) + " " + to_string(pArray[idx].y) + " "); + + // remove last space symbol. + pTargetString.resize(pTargetString.length() - 1); + AttrHelper_CommaToPoint(pTargetString); +} + +void X3DExporter::AttrHelper_Col4DArrToString(const aiColor4D *pArray, const size_t pArray_Size, string &pTargetString) { + pTargetString.clear(); + pTargetString.reserve(pArray_Size * 8); // (Number + space) * 4. + for (size_t idx = 0; idx < pArray_Size; idx++) + pTargetString.append(to_string(pArray[idx].r) + " " + to_string(pArray[idx].g) + " " + to_string(pArray[idx].b) + " " + + to_string(pArray[idx].a) + " "); + + // remove last space symbol. + pTargetString.resize(pTargetString.length() - 1); + AttrHelper_CommaToPoint(pTargetString); +} + +void X3DExporter::AttrHelper_Col3DArrToString(const aiColor3D *pArray, const size_t pArray_Size, std::string &pTargetString) { + pTargetString.clear(); + pTargetString.reserve(pArray_Size * 6); // (Number + space) * 3. + for (size_t idx = 0; idx < pArray_Size; idx++) + pTargetString.append(to_string(pArray[idx].r) + " " + to_string(pArray[idx].g) + " " + to_string(pArray[idx].b) + " "); + + // remove last space symbol. + pTargetString.resize(pTargetString.length() - 1); + AttrHelper_CommaToPoint(pTargetString); +} + +void X3DExporter::AttrHelper_Color3ToAttrList(std::list<SAttribute> &pList, const std::string &pName, const aiColor3D &pValue, const aiColor3D &pDefaultValue) { + string tstr; + + if (pValue == pDefaultValue) return; + + AttrHelper_Col3DArrToString(&pValue, 1, tstr); + pList.push_back({ pName, tstr }); +} + +void X3DExporter::AttrHelper_FloatToAttrList(std::list<SAttribute> &pList, const string &pName, const float pValue, const float pDefaultValue) { + string tstr; + + if (pValue == pDefaultValue) return; + + AttrHelper_FloatToString(pValue, tstr); + pList.push_back({ pName, tstr }); +} + +void X3DExporter::NodeHelper_OpenNode(const string &pNodeName, const size_t pTabLevel, const bool pEmptyElement, const list<SAttribute> &pAttrList) { + // Write indentation. + IndentationStringSet(pTabLevel); + XML_Write(mIndentationString); + // Begin of the element + XML_Write("<" + pNodeName); + // Write attributes + for (const SAttribute &attr : pAttrList) { + XML_Write(" " + attr.Name + "='" + attr.Value + "'"); + } + + // End of the element + if (pEmptyElement) { + XML_Write("/>\n"); + } else { + XML_Write(">\n"); + } +} + +void X3DExporter::NodeHelper_OpenNode(const string &pNodeName, const size_t pTabLevel, const bool pEmptyElement) { + const list<SAttribute> attr_list; + + NodeHelper_OpenNode(pNodeName, pTabLevel, pEmptyElement, attr_list); +} + +void X3DExporter::NodeHelper_CloseNode(const string &pNodeName, const size_t pTabLevel) { + // Write indentation. + IndentationStringSet(pTabLevel); + XML_Write(mIndentationString); + // Write element + XML_Write("</" + pNodeName + ">\n"); +} + +void X3DExporter::Export_Node(const aiNode *pNode, const size_t pTabLevel) { + bool transform = false; + list<SAttribute> attr_list; + + // In Assimp lights is stored in next way: light source store in mScene->mLights and in node tree must present aiNode with name same as + // light source has. Considering it we must compare every aiNode name with light sources names. Why not to look where ligths is present + // and save them to fili? Because corresponding aiNode can be already written to file and we can only add information to file not to edit. + if (CheckAndExport_Light(*pNode, pTabLevel)) return; + + // Check if need DEF. + if (pNode->mName.length) attr_list.push_back({ "DEF", pNode->mName.C_Str() }); + + // Check if need <Transformation> node against <Group>. + if (!pNode->mTransformation.IsIdentity()) { + auto Vector2String = [this](const aiVector3D pVector) -> string { + string tstr = to_string(pVector.x) + " " + to_string(pVector.y) + " " + to_string(pVector.z); + + AttrHelper_CommaToPoint(tstr); + + return tstr; + }; + + auto Rotation2String = [this](const aiVector3D pAxis, const ai_real pAngle) -> string { + string tstr = to_string(pAxis.x) + " " + to_string(pAxis.y) + " " + to_string(pAxis.z) + " " + to_string(pAngle); + + AttrHelper_CommaToPoint(tstr); + + return tstr; + }; + + aiVector3D scale, translate, rotate_axis; + ai_real rotate_angle; + + transform = true; + pNode->mTransformation.Decompose(scale, rotate_axis, rotate_angle, translate); + // Check if values different from default + if ((rotate_angle != 0) && (rotate_axis.Length() > 0)) + attr_list.push_back({ "rotation", Rotation2String(rotate_axis, rotate_angle) }); + + if (!scale.Equal({ 1.0, 1.0, 1.0 })) { + attr_list.push_back({ "scale", Vector2String(scale) }); + } + if (translate.Length() > 0) { + attr_list.push_back({ "translation", Vector2String(translate) }); + } + } + + // Begin node if need. + if (transform) + NodeHelper_OpenNode("Transform", pTabLevel, false, attr_list); + else + NodeHelper_OpenNode("Group", pTabLevel); + + // Export metadata + if (pNode->mMetaData != nullptr) { + for (size_t idx_prop = 0; idx_prop < pNode->mMetaData->mNumProperties; idx_prop++) { + const aiString *key; + const aiMetadataEntry *entry; + + if (pNode->mMetaData->Get(idx_prop, key, entry)) { + switch (entry->mType) { + case AI_BOOL: + Export_MetadataBoolean(*key, *static_cast<bool *>(entry->mData), pTabLevel + 1); + break; + case AI_DOUBLE: + Export_MetadataDouble(*key, *static_cast<double *>(entry->mData), pTabLevel + 1); + break; + case AI_FLOAT: + Export_MetadataFloat(*key, *static_cast<float *>(entry->mData), pTabLevel + 1); + break; + case AI_INT32: + Export_MetadataInteger(*key, *static_cast<int32_t *>(entry->mData), pTabLevel + 1); + break; + case AI_AISTRING: + Export_MetadataString(*key, *static_cast<aiString *>(entry->mData), pTabLevel + 1); + break; + default: + LogError("Unsupported metadata type: " + to_string(entry->mType)); + break; + } // switch(entry->mType) + } + } + } // if(pNode->mMetaData != nullptr) + + // Export meshes. + for (size_t idx_mesh = 0; idx_mesh < pNode->mNumMeshes; idx_mesh++) + Export_Mesh(pNode->mMeshes[idx_mesh], pTabLevel + 1); + // Export children. + for (size_t idx_node = 0; idx_node < pNode->mNumChildren; idx_node++) + Export_Node(pNode->mChildren[idx_node], pTabLevel + 1); + + // End node if need. + if (transform) + NodeHelper_CloseNode("Transform", pTabLevel); + else + NodeHelper_CloseNode("Group", pTabLevel); +} + +void X3DExporter::Export_Mesh(const size_t pIdxMesh, const size_t pTabLevel) { + const char *NodeName_IFS = "IndexedFaceSet"; + const char *NodeName_Shape = "Shape"; + + list<SAttribute> attr_list; + aiMesh &mesh = *mScene->mMeshes[pIdxMesh]; // create alias for convenience. + + // Check if mesh already defined early. + if (mDEF_Map_Mesh.find(pIdxMesh) != mDEF_Map_Mesh.end()) { + // Mesh already defined, just refer to it + attr_list.push_back({ "USE", mDEF_Map_Mesh.at(pIdxMesh) }); + NodeHelper_OpenNode(NodeName_Shape, pTabLevel, true, attr_list); + + return; + } + + string mesh_name(mesh.mName.C_Str() + string("_IDX_") + to_string(pIdxMesh)); // Create mesh name + + // Define mesh name. + attr_list.push_back({ "DEF", mesh_name }); + mDEF_Map_Mesh[pIdxMesh] = mesh_name; + + // + // "Shape" node. + // + NodeHelper_OpenNode(NodeName_Shape, pTabLevel, false, attr_list); + attr_list.clear(); + + // + // "Appearance" node. + // + Export_Material(mesh.mMaterialIndex, pTabLevel + 1); + + // + // "IndexedFaceSet" node. + // + // Fill attributes which differ from default. In Assimp for colors, vertices and normals used one indices set. So, only "coordIndex" must be set. + string coordIndex; + + // fill coordinates index. + coordIndex.reserve(mesh.mNumVertices * 4); // Index + space + Face delimiter + for (size_t idx_face = 0; idx_face < mesh.mNumFaces; idx_face++) { + const aiFace &face_cur = mesh.mFaces[idx_face]; + + for (size_t idx_vert = 0; idx_vert < face_cur.mNumIndices; idx_vert++) { + coordIndex.append(to_string(face_cur.mIndices[idx_vert]) + " "); + } + + coordIndex.append("-1 "); // face delimiter. + } + + // remove last space symbol. + coordIndex.resize(coordIndex.length() - 1); + attr_list.push_back({ "coordIndex", coordIndex }); + // create node + NodeHelper_OpenNode(NodeName_IFS, pTabLevel + 1, false, attr_list); + attr_list.clear(); + // Child nodes for "IndexedFaceSet" needed when used colors, textures or normals. + string attr_value; + + // Export <Coordinate> + AttrHelper_Vec3DArrToString(mesh.mVertices, mesh.mNumVertices, attr_value); + attr_list.push_back({ "point", attr_value }); + NodeHelper_OpenNode("Coordinate", pTabLevel + 2, true, attr_list); + attr_list.clear(); + + // Export <ColorRGBA> + if (mesh.HasVertexColors(0)) { + AttrHelper_Col4DArrToString(mesh.mColors[0], mesh.mNumVertices, attr_value); + attr_list.push_back({ "color", attr_value }); + NodeHelper_OpenNode("ColorRGBA", pTabLevel + 2, true, attr_list); + attr_list.clear(); + } + + // Export <TextureCoordinate> + if (mesh.HasTextureCoords(0)) { + AttrHelper_Vec3DAsVec2fArrToString(mesh.mTextureCoords[0], mesh.mNumVertices, attr_value); + attr_list.push_back({ "point", attr_value }); + NodeHelper_OpenNode("TextureCoordinate", pTabLevel + 2, true, attr_list); + attr_list.clear(); + } + + // Export <Normal> + if (mesh.HasNormals()) { + AttrHelper_Vec3DArrToString(mesh.mNormals, mesh.mNumVertices, attr_value); + attr_list.push_back({ "vector", attr_value }); + NodeHelper_OpenNode("Normal", pTabLevel + 2, true, attr_list); + attr_list.clear(); + } + + // + // Close opened nodes. + // + NodeHelper_CloseNode(NodeName_IFS, pTabLevel + 1); + NodeHelper_CloseNode(NodeName_Shape, pTabLevel); +} + +void X3DExporter::Export_Material(const size_t pIdxMaterial, const size_t pTabLevel) { + const char *NodeName_A = "Appearance"; + + list<SAttribute> attr_list; + aiMaterial &material = *mScene->mMaterials[pIdxMaterial]; // create alias for convenience. + + // Check if material already defined early. + if (mDEF_Map_Material.find(pIdxMaterial) != mDEF_Map_Material.end()) { + // Material already defined, just refer to it + attr_list.push_back({ "USE", mDEF_Map_Material.at(pIdxMaterial) }); + NodeHelper_OpenNode(NodeName_A, pTabLevel, true, attr_list); + + return; + } + + string material_name(string("_IDX_") + to_string(pIdxMaterial)); // Create material name + aiString ai_mat_name; + + if (material.Get(AI_MATKEY_NAME, ai_mat_name) == AI_SUCCESS) material_name.insert(0, ai_mat_name.C_Str()); + + // Define material name. + attr_list.push_back({ "DEF", material_name }); + mDEF_Map_Material[pIdxMaterial] = material_name; + + // + // "Appearance" node. + // + NodeHelper_OpenNode(NodeName_A, pTabLevel, false, attr_list); + attr_list.clear(); + + // + // "Material" node. + // + { + auto Color4ToAttrList = [&](const string &pAttrName, const aiColor4D &pAttrValue, const aiColor3D &pAttrDefaultValue) { + string tstr; + + if (aiColor3D(pAttrValue.r, pAttrValue.g, pAttrValue.b) != pAttrDefaultValue) { + AttrHelper_Col4DArrToString(&pAttrValue, 1, tstr); + attr_list.push_back({ pAttrName, tstr }); + } + }; + + float tvalf; + aiColor3D color3; + aiColor4D color4; + + // ambientIntensity="0.2" SFFloat [inputOutput] + if (material.Get(AI_MATKEY_COLOR_AMBIENT, color3) == AI_SUCCESS) + AttrHelper_FloatToAttrList(attr_list, "ambientIntensity", (color3.r + color3.g + color3.b) / 3.0f, 0.2f); + else if (material.Get(AI_MATKEY_COLOR_AMBIENT, color4) == AI_SUCCESS) + AttrHelper_FloatToAttrList(attr_list, "ambientIntensity", (color4.r + color4.g + color4.b) / 3.0f, 0.2f); + + // diffuseColor="0.8 0.8 0.8" SFColor [inputOutput] + if (material.Get(AI_MATKEY_COLOR_DIFFUSE, color3) == AI_SUCCESS) + AttrHelper_Color3ToAttrList(attr_list, "diffuseColor", color3, aiColor3D(0.8f, 0.8f, 0.8f)); + else if (material.Get(AI_MATKEY_COLOR_DIFFUSE, color4) == AI_SUCCESS) + Color4ToAttrList("diffuseColor", color4, aiColor3D(0.8f, 0.8f, 0.8f)); + + // emissiveColor="0 0 0" SFColor [inputOutput] + if (material.Get(AI_MATKEY_COLOR_EMISSIVE, color3) == AI_SUCCESS) + AttrHelper_Color3ToAttrList(attr_list, "emissiveColor", color3, aiColor3D(0, 0, 0)); + else if (material.Get(AI_MATKEY_COLOR_EMISSIVE, color4) == AI_SUCCESS) + Color4ToAttrList("emissiveColor", color4, aiColor3D(0, 0, 0)); + + // shininess="0.2" SFFloat [inputOutput] + if (material.Get(AI_MATKEY_SHININESS, tvalf) == AI_SUCCESS) AttrHelper_FloatToAttrList(attr_list, "shininess", tvalf, 0.2f); + + // specularColor="0 0 0" SFColor [inputOutput] + if (material.Get(AI_MATKEY_COLOR_SPECULAR, color3) == AI_SUCCESS) + AttrHelper_Color3ToAttrList(attr_list, "specularColor", color3, aiColor3D(0, 0, 0)); + else if (material.Get(AI_MATKEY_COLOR_SPECULAR, color4) == AI_SUCCESS) + Color4ToAttrList("specularColor", color4, aiColor3D(0, 0, 0)); + + // transparency="0" SFFloat [inputOutput] + if (material.Get(AI_MATKEY_OPACITY, tvalf) == AI_SUCCESS) { + if (tvalf > 1) tvalf = 1; + + tvalf = 1.0f - tvalf; + AttrHelper_FloatToAttrList(attr_list, "transparency", tvalf, 0); + } + + NodeHelper_OpenNode("Material", pTabLevel + 1, true, attr_list); + attr_list.clear(); + } // "Material" node. END. + + // + // "ImageTexture" node. + // + { + auto RepeatToAttrList = [&](const string &pAttrName, const bool pAttrValue) { + if (!pAttrValue) attr_list.push_back({ pAttrName, "false" }); + }; + + bool tvalb; + aiString tstring; + + // url="" MFString + if (material.Get(AI_MATKEY_TEXTURE_DIFFUSE(0), tstring) == AI_SUCCESS) { + if (strncmp(tstring.C_Str(), AI_EMBEDDED_TEXNAME_PREFIX, strlen(AI_EMBEDDED_TEXNAME_PREFIX)) == 0) + LogError("Embedded texture is not supported"); + else + attr_list.push_back({ "url", string("\"") + tstring.C_Str() + "\"" }); + } + + // repeatS="true" SFBool + if (material.Get(AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0), tvalb) == AI_SUCCESS) RepeatToAttrList("repeatS", tvalb); + + // repeatT="true" SFBool + if (material.Get(AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0), tvalb) == AI_SUCCESS) RepeatToAttrList("repeatT", tvalb); + + NodeHelper_OpenNode("ImageTexture", pTabLevel + 1, true, attr_list); + attr_list.clear(); + } // "ImageTexture" node. END. + + // + // "TextureTransform" node. + // + { + auto Vec2ToAttrList = [&](const string &pAttrName, const aiVector2D &pAttrValue, const aiVector2D &pAttrDefaultValue) { + string tstr; + + if (pAttrValue != pAttrDefaultValue) { + AttrHelper_Vec2DArrToString(&pAttrValue, 1, tstr); + attr_list.push_back({ pAttrName, tstr }); + } + }; + + aiUVTransform transform; + + if (material.Get(AI_MATKEY_UVTRANSFORM_DIFFUSE(0), transform) == AI_SUCCESS) { + Vec2ToAttrList("translation", transform.mTranslation, aiVector2D(0, 0)); + AttrHelper_FloatToAttrList(attr_list, "rotation", transform.mRotation, 0); + Vec2ToAttrList("scale", transform.mScaling, aiVector2D(1, 1)); + + NodeHelper_OpenNode("TextureTransform", pTabLevel + 1, true, attr_list); + attr_list.clear(); + } + } // "TextureTransform" node. END. + + // + // Close opened nodes. + // + NodeHelper_CloseNode(NodeName_A, pTabLevel); +} + +void X3DExporter::Export_MetadataBoolean(const aiString &pKey, const bool pValue, const size_t pTabLevel) { + list<SAttribute> attr_list; + + attr_list.push_back({ "name", pKey.C_Str() }); + attr_list.push_back({ "value", pValue ? "true" : "false" }); + NodeHelper_OpenNode("MetadataBoolean", pTabLevel, true, attr_list); +} + +void X3DExporter::Export_MetadataDouble(const aiString &pKey, const double pValue, const size_t pTabLevel) { + list<SAttribute> attr_list; + + attr_list.push_back({ "name", pKey.C_Str() }); + attr_list.push_back({ "value", to_string(pValue) }); + NodeHelper_OpenNode("MetadataDouble", pTabLevel, true, attr_list); +} + +void X3DExporter::Export_MetadataFloat(const aiString &pKey, const float pValue, const size_t pTabLevel) { + list<SAttribute> attr_list; + + attr_list.push_back({ "name", pKey.C_Str() }); + attr_list.push_back({ "value", to_string(pValue) }); + NodeHelper_OpenNode("MetadataFloat", pTabLevel, true, attr_list); +} + +void X3DExporter::Export_MetadataInteger(const aiString &pKey, const int32_t pValue, const size_t pTabLevel) { + list<SAttribute> attr_list; + + attr_list.push_back({ "name", pKey.C_Str() }); + attr_list.push_back({ "value", to_string(pValue) }); + NodeHelper_OpenNode("MetadataInteger", pTabLevel, true, attr_list); +} + +void X3DExporter::Export_MetadataString(const aiString &pKey, const aiString &pValue, const size_t pTabLevel) { + list<SAttribute> attr_list; + + attr_list.push_back({ "name", pKey.C_Str() }); + attr_list.push_back({ "value", pValue.C_Str() }); + NodeHelper_OpenNode("MetadataString", pTabLevel, true, attr_list); +} + +bool X3DExporter::CheckAndExport_Light(const aiNode &pNode, const size_t pTabLevel) { + list<SAttribute> attr_list; + + auto Vec3ToAttrList = [&](const string &pAttrName, const aiVector3D &pAttrValue, const aiVector3D &pAttrDefaultValue) { + string tstr; + + if (pAttrValue != pAttrDefaultValue) { + AttrHelper_Vec3DArrToString(&pAttrValue, 1, tstr); + attr_list.push_back({ pAttrName, tstr }); + } + }; + + size_t idx_light; + bool found = false; + + // Name of the light source can not be empty. + if (pNode.mName.length == 0) return false; + + // search for light with name like node has. + for (idx_light = 0; mScene->mNumLights; idx_light++) { + if (pNode.mName == mScene->mLights[idx_light]->mName) { + found = true; + break; + } + } + + if (!found) return false; + + // Light source is found. + const aiLight &light = *mScene->mLights[idx_light]; // Alias for convenience. + + aiMatrix4x4 trafo_mat = Matrix_GlobalToCurrent(pNode).Inverse(); + + attr_list.push_back({ "DEF", light.mName.C_Str() }); + attr_list.push_back({ "global", "true" }); // "false" is not supported. + // ambientIntensity="0" SFFloat [inputOutput] + AttrHelper_FloatToAttrList(attr_list, "ambientIntensity", aiVector3D(light.mColorAmbient.r, light.mColorAmbient.g, light.mColorAmbient.b).Length(), 0); + // color="1 1 1" SFColor [inputOutput] + AttrHelper_Color3ToAttrList(attr_list, "color", light.mColorDiffuse, aiColor3D(1, 1, 1)); + + switch (light.mType) { + case aiLightSource_DIRECTIONAL: { + aiVector3D direction = trafo_mat * light.mDirection; + + Vec3ToAttrList("direction", direction, aiVector3D(0, 0, -1)); + NodeHelper_OpenNode("DirectionalLight", pTabLevel, true, attr_list); + } + + break; + case aiLightSource_POINT: { + aiVector3D attenuation(light.mAttenuationConstant, light.mAttenuationLinear, light.mAttenuationQuadratic); + aiVector3D location = trafo_mat * light.mPosition; + + Vec3ToAttrList("attenuation", attenuation, aiVector3D(1, 0, 0)); + Vec3ToAttrList("location", location, aiVector3D(0, 0, 0)); + NodeHelper_OpenNode("PointLight", pTabLevel, true, attr_list); + } + + break; + case aiLightSource_SPOT: { + aiVector3D attenuation(light.mAttenuationConstant, light.mAttenuationLinear, light.mAttenuationQuadratic); + aiVector3D location = trafo_mat * light.mPosition; + aiVector3D direction = trafo_mat * light.mDirection; + + Vec3ToAttrList("attenuation", attenuation, aiVector3D(1, 0, 0)); + Vec3ToAttrList("location", location, aiVector3D(0, 0, 0)); + Vec3ToAttrList("direction", direction, aiVector3D(0, 0, -1)); + AttrHelper_FloatToAttrList(attr_list, "beamWidth", light.mAngleInnerCone, 0.7854f); + AttrHelper_FloatToAttrList(attr_list, "cutOffAngle", light.mAngleOuterCone, 1.570796f); + NodeHelper_OpenNode("SpotLight", pTabLevel, true, attr_list); + } + + break; + default: + throw DeadlyExportError("Unknown light type: " + to_string(light.mType)); + } // switch(light.mType) + + return true; +} + +X3DExporter::X3DExporter(const char *pFileName, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) : + mScene(pScene) { + list<SAttribute> attr_list; + + mOutFile = pIOSystem->Open(pFileName, "wt"); + if (mOutFile == nullptr) throw DeadlyExportError("Could not open output .x3d file: " + string(pFileName)); + + // Begin document + XML_Write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); + XML_Write("<!DOCTYPE X3D PUBLIC \"ISO//Web3D//DTD X3D 3.3//EN\" \"http://www.web3d.org/specifications/x3d-3.3.dtd\">\n"); + // Root node + attr_list.push_back({ "profile", "Interchange" }); + attr_list.push_back({ "version", "3.3" }); + attr_list.push_back({ "xmlns:xsd", "http://www.w3.org/2001/XMLSchema-instance" }); + attr_list.push_back({ "xsd:noNamespaceSchemaLocation", "http://www.web3d.org/specifications/x3d-3.3.xsd" }); + NodeHelper_OpenNode("X3D", 0, false, attr_list); + attr_list.clear(); + // <head>: meta data. + NodeHelper_OpenNode("head", 1); + XML_Write(mIndentationString + "<!-- All \"meta\" from this section tou will found in <Scene> node as MetadataString nodes. -->\n"); + NodeHelper_CloseNode("head", 1); + // Scene node. + NodeHelper_OpenNode("Scene", 1); + Export_Node(mScene->mRootNode, 2); + NodeHelper_CloseNode("Scene", 1); + // Close Root node. + NodeHelper_CloseNode("X3D", 0); + // Cleanup + pIOSystem->Close(mOutFile); + mOutFile = nullptr; +} + +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_X3D_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/X3D/X3DExporter.hpp b/libs/assimp/code/AssetLib/X3D/X3DExporter.hpp new file mode 100644 index 0000000..fefaba9 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DExporter.hpp @@ -0,0 +1,250 @@ +/// \file X3DExporter.hpp +/// \brief X3D-format files exporter for Assimp. +/// \date 2016 +/// \author smal.root@gmail.com +// Thanks to acorn89 for support. + +#ifndef INCLUDED_AI_X3D_EXPORTER_H +#define INCLUDED_AI_X3D_EXPORTER_H + +// Header files, Assimp. +#include <assimp/material.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Exporter.hpp> + +// Header files, stdlib. +#include <list> +#include <string> + +namespace Assimp { + +/// \class X3DExporter +/// Class which export aiScene to X3D file. +/// +/// Limitations. +/// +/// Pay attention that X3D is format for interactive graphic and simulations for web browsers. aiScene can not contain all features of the X3D format. +/// Also, aiScene contain rasterized-like data. For example, X3D can describe circle all cylinder with one tag, but aiScene contain result of tessellation: +/// vertices, faces etc. Yes, you can use algorithm for detecting figures or shapes, but that's not a good idea at all. +/// +/// Supported nodes: +/// Core component: +/// "MetadataBoolean", "MetadataDouble", "MetadataFloat", "MetadataInteger", "MetadataSet", "MetadataString" +/// Geometry3D component: +/// "IndexedFaceSet" +/// Grouping component: +/// "Group", "Transform" +/// Lighting component: +/// "DirectionalLight", "PointLight", "SpotLight" +/// Rendering component: +/// "ColorRGBA", "Coordinate", "Normal" +/// Shape component: +/// "Shape", "Appearance", "Material" +/// Texturing component: +/// "ImageTexture", "TextureCoordinate", "TextureTransform" +/// +class X3DExporter { + /***********************************************/ + /******************** Types ********************/ + /***********************************************/ + + struct SAttribute { + const std::string Name; + const std::string Value; + SAttribute() : + Name(), + Value() { + // empty + } + SAttribute(const std::string &name, const std::string &value) : + Name(name), + Value(value) { + // empty + } + + SAttribute(SAttribute &&rhs) AI_NO_EXCEPT : + Name(rhs.Name), + Value(rhs.Value) { + // empty + } + }; + + /***********************************************/ + /****************** Constants ******************/ + /***********************************************/ + + const aiScene *const mScene; + + /***********************************************/ + /****************** Variables ******************/ + /***********************************************/ + + IOStream *mOutFile; + std::map<size_t, std::string> mDEF_Map_Mesh; + std::map<size_t, std::string> mDEF_Map_Material; + +private: + std::string mIndentationString; + + /***********************************************/ + /****************** Functions ******************/ + /***********************************************/ + + /// \fn void IndentationStringSet(const size_t pNewLevel) + /// Set value of the indentation string. + /// \param [in] pNewLevel - new level of the indentation. + void IndentationStringSet(const size_t pNewLevel); + + /// \fn void XML_Write(const std::string& pData) + /// Write data to XML-file. + /// \param [in] pData - reference to string which must be written. + void XML_Write(const std::string &pData); + + /// \fn aiMatrix4x4 Matrix_GlobalToCurrent(const aiNode& pNode) const + /// Calculate transformation matrix for transformation from global coordinate system to pointed aiNode. + /// \param [in] pNode - reference to local node. + /// \return calculated matrix. + aiMatrix4x4 Matrix_GlobalToCurrent(const aiNode &pNode) const; + + /// \fn void AttrHelper_CommaToPoint(std::string& pStringWithComma) + /// Convert commas in string to points. That's needed because "std::to_string" result depends on locale (regional settings). + /// \param [in, out] pStringWithComma - reference to string, which must be modified. + void AttrHelper_CommaToPoint(std::string &pStringWithComma) { + for (char &c : pStringWithComma) { + if (c == ',') c = '.'; + } + } + + /// \fn void AttrHelper_FloatToString(const float pValue, std::string& pTargetString) + /// Converts float to string. + /// \param [in] pValue - value for converting. + /// \param [out] pTargetString - reference to string where result will be placed. Will be cleared before using. + void AttrHelper_FloatToString(const float pValue, std::string &pTargetString); + + /// \fn void AttrHelper_Vec3DArrToString(const aiVector3D* pArray, const size_t pArray_Size, std::string& pTargetString) + /// Converts array of vectors to string. + /// \param [in] pArray - pointer to array of vectors. + /// \param [in] pArray_Size - count of elements in array. + /// \param [out] pTargetString - reference to string where result will be placed. Will be cleared before using. + void AttrHelper_Vec3DArrToString(const aiVector3D *pArray, const size_t pArray_Size, std::string &pTargetString); + + /// \fn void AttrHelper_Vec2DArrToString(const aiVector2D* pArray, const size_t pArray_Size, std::string& pTargetString) + /// \overload void AttrHelper_Vec3DArrToString(const aiVector3D* pArray, const size_t pArray_Size, std::string& pTargetString) + void AttrHelper_Vec2DArrToString(const aiVector2D *pArray, const size_t pArray_Size, std::string &pTargetString); + + /// \fn void AttrHelper_Vec3DAsVec2fArrToString(const aiVector3D* pArray, const size_t pArray_Size, std::string& pTargetString) + /// \overload void AttrHelper_Vec3DArrToString(const aiVector3D* pArray, const size_t pArray_Size, std::string& pTargetString) + /// Only x, y is used from aiVector3D. + void AttrHelper_Vec3DAsVec2fArrToString(const aiVector3D *pArray, const size_t pArray_Size, std::string &pTargetString); + + /// \fn void AttrHelper_Col4DArrToString(const aiColor4D* pArray, const size_t pArray_Size, std::string& pTargetString) + /// \overload void AttrHelper_Vec3DArrToString(const aiVector3D* pArray, const size_t pArray_Size, std::string& pTargetString) + /// Converts array of colors to string. + void AttrHelper_Col4DArrToString(const aiColor4D *pArray, const size_t pArray_Size, std::string &pTargetString); + + /// \fn void AttrHelper_Col3DArrToString(const aiColor3D* pArray, const size_t pArray_Size, std::string& pTargetString) + /// \overload void AttrHelper_Col4DArrToString(const aiColor4D* pArray, const size_t pArray_Size, std::string& pTargetString) + /// Converts array of colors to string. + void AttrHelper_Col3DArrToString(const aiColor3D *pArray, const size_t pArray_Size, std::string &pTargetString); + + /// \fn void AttrHelper_FloatToAttrList(std::list<SAttribute> pList, const std::string& pName, const float pValue, const float pDefaultValue) + /// \overload void AttrHelper_Col3DArrToString(const aiColor3D* pArray, const size_t pArray_Size, std::string& pTargetString) + void AttrHelper_FloatToAttrList(std::list<SAttribute> &pList, const std::string &pName, const float pValue, const float pDefaultValue); + + /// \fn void AttrHelper_Color3ToAttrList(std::list<SAttribute> pList, const std::string& pName, const aiColor3D& pValue, const aiColor3D& pDefaultValue) + /// Add attribute to list if value not equal to default. + /// \param [in] pList - target list of the attributes. + /// \param [in] pName - name of new attribute. + /// \param [in] pValue - value of the new attribute. + /// \param [in] pDefaultValue - default value for checking: if pValue is equal to pDefaultValue then attribute will not be added. + void AttrHelper_Color3ToAttrList(std::list<SAttribute> &pList, const std::string &pName, const aiColor3D &pValue, const aiColor3D &pDefaultValue); + + /// \fn void NodeHelper_OpenNode(const std::string& pNodeName, const size_t pTabLevel, const bool pEmptyElement, const std::list<SAttribute>& pAttrList) + /// Begin new XML-node element. + /// \param [in] pNodeName - name of the element. + /// \param [in] pTabLevel - indentation level. + /// \param [in] pEmtyElement - if true then empty element will be created. + /// \param [in] pAttrList - list of the attributes for element. + void NodeHelper_OpenNode(const std::string &pNodeName, const size_t pTabLevel, const bool pEmptyElement, const std::list<SAttribute> &pAttrList); + + /// \fn void NodeHelper_OpenNode(const std::string& pNodeName, const size_t pTabLevel, const bool pEmptyElement = false) + /// \overload void NodeHelper_OpenNode(const std::string& pNodeName, const size_t pTabLevel, const bool pEmptyElement, const std::list<SAttribute>& pAttrList) + void NodeHelper_OpenNode(const std::string &pNodeName, const size_t pTabLevel, const bool pEmptyElement = false); + + /// \fn void NodeHelper_CloseNode(const std::string& pNodeName, const size_t pTabLevel) + /// End XML-node element. + /// \param [in] pNodeName - name of the element. + /// \param [in] pTabLevel - indentation level. + void NodeHelper_CloseNode(const std::string &pNodeName, const size_t pTabLevel); + + /// \fn void Export_Node(const aiNode* pNode, const size_t pTabLevel) + /// Export data from scene to XML-file: aiNode. + /// \param [in] pNode - source aiNode. + /// \param [in] pTabLevel - indentation level. + void Export_Node(const aiNode *pNode, const size_t pTabLevel); + + /// \fn void Export_Mesh(const size_t pIdxMesh, const size_t pTabLevel) + /// Export data from scene to XML-file: aiMesh. + /// \param [in] pMesh - index of the source aiMesh. + /// \param [in] pTabLevel - indentation level. + void Export_Mesh(const size_t pIdxMesh, const size_t pTabLevel); + + /// \fn void Export_Material(const size_t pIdxMaterial, const size_t pTabLevel) + /// Export data from scene to XML-file: aiMaterial. + /// \param [in] pIdxMaterial - index of the source aiMaterial. + /// \param [in] pTabLevel - indentation level. + void Export_Material(const size_t pIdxMaterial, const size_t pTabLevel); + + /// \fn void Export_MetadataBoolean(const aiString& pKey, const bool pValue, const size_t pTabLevel) + /// Export data from scene to XML-file: aiMetadata. + /// \param [in] pKey - source data: value of the metadata key. + /// \param [in] pValue - source data: value of the metadata value. + /// \param [in] pTabLevel - indentation level. + void Export_MetadataBoolean(const aiString &pKey, const bool pValue, const size_t pTabLevel); + + /// \fn void Export_MetadataDouble(const aiString& pKey, const double pValue, const size_t pTabLevel) + /// \overload void Export_MetadataBoolean(const aiString& pKey, const bool pValue, const size_t pTabLevel) + void Export_MetadataDouble(const aiString &pKey, const double pValue, const size_t pTabLevel); + + /// \fn void Export_MetadataFloat(const aiString& pKey, const float pValue, const size_t pTabLevel) + /// \overload void Export_MetadataBoolean(const aiString& pKey, const bool pValue, const size_t pTabLevel) + void Export_MetadataFloat(const aiString &pKey, const float pValue, const size_t pTabLevel); + + /// \fn void Export_MetadataInteger(const aiString& pKey, const int32_t pValue, const size_t pTabLevel) + /// \overload void Export_MetadataBoolean(const aiString& pKey, const bool pValue, const size_t pTabLevel) + void Export_MetadataInteger(const aiString &pKey, const int32_t pValue, const size_t pTabLevel); + + /// \fn void Export_MetadataString(const aiString& pKey, const aiString& pValue, const size_t pTabLevel) + /// \overload void Export_MetadataBoolean(const aiString& pKey, const bool pValue, const size_t pTabLevel) + void Export_MetadataString(const aiString &pKey, const aiString &pValue, const size_t pTabLevel); + + /// \fn bool CheckAndExport_Light(const aiNode& pNode, const size_t pTabLevel) + /// Check if node point to light source. If yes then export light source. + /// \param [in] pNode - reference to node for checking. + /// \param [in] pTabLevel - indentation level. + /// \return true - if node assigned with light and it was exported, else - return false. + bool CheckAndExport_Light(const aiNode &pNode, const size_t pTabLevel); + + /***********************************************/ + /************** Functions: LOG set *************/ + /***********************************************/ + + /// \fn void LogError(const std::string& pMessage) + /// Short variant for calling \ref DefaultLogger::get()->error() + void LogError(const std::string &pMessage) { DefaultLogger::get()->error(pMessage); } + +public: + /// \fn X3DExporter() + /// Default constructor. + X3DExporter(const char *pFileName, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties *pProperties); + + /// \fn ~X3DExporter() + /// Default destructor. + ~X3DExporter() {} + +}; // class X3DExporter + +} // namespace Assimp + +#endif // INCLUDED_AI_X3D_EXPORTER_H diff --git a/libs/assimp/code/AssetLib/X3D/X3DGeoHelper.cpp b/libs/assimp/code/AssetLib/X3D/X3DGeoHelper.cpp new file mode 100644 index 0000000..a9ac57e --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DGeoHelper.cpp @@ -0,0 +1,531 @@ +#include "X3DGeoHelper.h" +#include "X3DImporter.hpp" + +#include <assimp/vector3.h> +#include <assimp/Exceptional.h> +#include <assimp/StringUtils.h> + +#include <vector> + +namespace Assimp { + +aiVector3D X3DGeoHelper::make_point2D(float angle, float radius) { + return aiVector3D(radius * std::cos(angle), radius * std::sin(angle), 0); +} + +void X3DGeoHelper::make_arc2D(float pStartAngle, float pEndAngle, float pRadius, size_t numSegments, std::list<aiVector3D> &pVertices) { + // check argument values ranges. + if ((pStartAngle < -AI_MATH_TWO_PI_F) || (pStartAngle > AI_MATH_TWO_PI_F)) { + throw DeadlyImportError("GeometryHelper_Make_Arc2D.pStartAngle"); + } + if ((pEndAngle < -AI_MATH_TWO_PI_F) || (pEndAngle > AI_MATH_TWO_PI_F)) { + throw DeadlyImportError("GeometryHelper_Make_Arc2D.pEndAngle"); + } + if (pRadius <= 0) { + throw DeadlyImportError("GeometryHelper_Make_Arc2D.pRadius"); + } + + // calculate arc angle and check type of arc + float angle_full = std::fabs(pEndAngle - pStartAngle); + if ((angle_full > AI_MATH_TWO_PI_F) || (angle_full == 0.0f)) { + angle_full = AI_MATH_TWO_PI_F; + } + + // calculate angle for one step - angle to next point of line. + float angle_step = angle_full / (float)numSegments; + // make points + for (size_t pi = 0; pi <= numSegments; pi++) { + float tangle = pStartAngle + pi * angle_step; + pVertices.emplace_back(make_point2D(tangle, pRadius)); + } // for(size_t pi = 0; pi <= pNumSegments; pi++) + + // if we making full circle then add last vertex equal to first vertex + if (angle_full == AI_MATH_TWO_PI_F) pVertices.push_back(*pVertices.begin()); +} + +void X3DGeoHelper::extend_point_to_line(const std::list<aiVector3D> &pPoint, std::list<aiVector3D> &pLine) { + std::list<aiVector3D>::const_iterator pit = pPoint.begin(); + std::list<aiVector3D>::const_iterator pit_last = pPoint.end(); + + --pit_last; + + if (pPoint.size() < 2) { + throw DeadlyImportError("GeometryHelper_Extend_PointToLine.pPoint.size() can not be less than 2."); + } + + // add first point of first line. + pLine.push_back(*pit++); + // add internal points + while (pit != pit_last) { + pLine.push_back(*pit); // second point of previous line + pLine.push_back(*pit); // first point of next line + ++pit; + } + // add last point of last line + pLine.push_back(*pit); +} + +void X3DGeoHelper::polylineIdx_to_lineIdx(const std::list<int32_t> &pPolylineCoordIdx, std::list<int32_t> &pLineCoordIdx) { + std::list<int32_t>::const_iterator plit = pPolylineCoordIdx.begin(); + + while (plit != pPolylineCoordIdx.end()) { + // add first point of polyline + pLineCoordIdx.push_back(*plit++); + while ((*plit != (-1)) && (plit != pPolylineCoordIdx.end())) { + std::list<int32_t>::const_iterator plit_next; + + plit_next = plit, ++plit_next; + pLineCoordIdx.push_back(*plit); // second point of previous line. + pLineCoordIdx.push_back(-1); // delimiter + if ((*plit_next == (-1)) || (plit_next == pPolylineCoordIdx.end())) break; // current polyline is finished + + pLineCoordIdx.push_back(*plit); // first point of next line. + plit = plit_next; + } // while((*plit != (-1)) && (plit != pPolylineCoordIdx.end())) + } // while(plit != pPolylineCoordIdx.end()) +} + +#define MACRO_FACE_ADD_QUAD_FA(pCCW, pOut, pIn, pP1, pP2, pP3, pP4) \ + do { \ + if (pCCW) { \ + pOut.push_back(pIn[pP1]); \ + pOut.push_back(pIn[pP2]); \ + pOut.push_back(pIn[pP3]); \ + pOut.push_back(pIn[pP4]); \ + } else { \ + pOut.push_back(pIn[pP4]); \ + pOut.push_back(pIn[pP3]); \ + pOut.push_back(pIn[pP2]); \ + pOut.push_back(pIn[pP1]); \ + } \ + } while (false) + +#define MESH_RectParallelepiped_CREATE_VERT \ + aiVector3D vert_set[8]; \ + float x1, x2, y1, y2, z1, z2, hs; \ + \ + hs = pSize.x / 2, x1 = -hs, x2 = hs; \ + hs = pSize.y / 2, y1 = -hs, y2 = hs; \ + hs = pSize.z / 2, z1 = -hs, z2 = hs; \ + vert_set[0].Set(x2, y1, z2); \ + vert_set[1].Set(x2, y2, z2); \ + vert_set[2].Set(x2, y2, z1); \ + vert_set[3].Set(x2, y1, z1); \ + vert_set[4].Set(x1, y1, z2); \ + vert_set[5].Set(x1, y2, z2); \ + vert_set[6].Set(x1, y2, z1); \ + vert_set[7].Set(x1, y1, z1) + +void X3DGeoHelper::rect_parallel_epiped(const aiVector3D &pSize, std::list<aiVector3D> &pVertices) { + MESH_RectParallelepiped_CREATE_VERT; + MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 3, 2, 1, 0); // front + MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 6, 7, 4, 5); // back + MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 7, 3, 0, 4); // left + MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 2, 6, 5, 1); // right + MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 0, 1, 5, 4); // top + MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 7, 6, 2, 3); // bottom +} + +#undef MESH_RectParallelepiped_CREATE_VERT + +void X3DGeoHelper::coordIdx_str2faces_arr(const std::vector<int32_t> &pCoordIdx, std::vector<aiFace> &pFaces, unsigned int &pPrimitiveTypes) { + std::vector<int32_t> f_data(pCoordIdx); + std::vector<unsigned int> inds; + unsigned int prim_type = 0; + + if (f_data.back() != (-1)) { + f_data.push_back(-1); + } + + // reserve average size. + pFaces.reserve(f_data.size() / 3); + inds.reserve(4); + //PrintVectorSet("build. ci", pCoordIdx); + for (std::vector<int32_t>::iterator it = f_data.begin(); it != f_data.end(); ++it) { + // when face is got count how many indices in it. + if (*it == (-1)) { + aiFace tface; + size_t ts; + + ts = inds.size(); + switch (ts) { + case 0: + goto mg_m_err; + case 1: + prim_type |= aiPrimitiveType_POINT; + break; + case 2: + prim_type |= aiPrimitiveType_LINE; + break; + case 3: + prim_type |= aiPrimitiveType_TRIANGLE; + break; + default: + prim_type |= aiPrimitiveType_POLYGON; + break; + } + + tface.mNumIndices = static_cast<unsigned int>(ts); + tface.mIndices = new unsigned int[ts]; + memcpy(tface.mIndices, inds.data(), ts * sizeof(unsigned int)); + pFaces.push_back(tface); + inds.clear(); + } // if(*it == (-1)) + else { + inds.push_back(*it); + } // if(*it == (-1)) else + } // for(std::list<int32_t>::iterator it = f_data.begin(); it != f_data.end(); it++) + //PrintVectorSet("build. faces", pCoordIdx); + + pPrimitiveTypes = prim_type; + + return; + +mg_m_err: + for (size_t i = 0, i_e = pFaces.size(); i < i_e; i++) + delete[] pFaces.at(i).mIndices; + + pFaces.clear(); +} + +void X3DGeoHelper::add_color(aiMesh &pMesh, const std::list<aiColor3D> &pColors, const bool pColorPerVertex) { + std::list<aiColor4D> tcol; + + // create RGBA array from RGB. + for (std::list<aiColor3D>::const_iterator it = pColors.begin(); it != pColors.end(); ++it) + tcol.push_back(aiColor4D((*it).r, (*it).g, (*it).b, 1)); + + // call existing function for adding RGBA colors + add_color(pMesh, tcol, pColorPerVertex); +} + +void X3DGeoHelper::add_color(aiMesh &pMesh, const std::list<aiColor4D> &pColors, const bool pColorPerVertex) { + std::list<aiColor4D>::const_iterator col_it = pColors.begin(); + + if (pColorPerVertex) { + if (pColors.size() < pMesh.mNumVertices) { + throw DeadlyImportError("MeshGeometry_AddColor1. Colors count(" + ai_to_string(pColors.size()) + ") can not be less than Vertices count(" + + ai_to_string(pMesh.mNumVertices) + ")."); + } + + // copy colors to mesh + pMesh.mColors[0] = new aiColor4D[pMesh.mNumVertices]; + for (size_t i = 0; i < pMesh.mNumVertices; i++) + pMesh.mColors[0][i] = *col_it++; + } // if(pColorPerVertex) + else { + if (pColors.size() < pMesh.mNumFaces) { + throw DeadlyImportError("MeshGeometry_AddColor1. Colors count(" + ai_to_string(pColors.size()) + ") can not be less than Faces count(" + + ai_to_string(pMesh.mNumFaces) + ")."); + } + + // copy colors to mesh + pMesh.mColors[0] = new aiColor4D[pMesh.mNumVertices]; + for (size_t fi = 0; fi < pMesh.mNumFaces; fi++) { + // apply color to all vertices of face + for (size_t vi = 0, vi_e = pMesh.mFaces[fi].mNumIndices; vi < vi_e; vi++) { + pMesh.mColors[0][pMesh.mFaces[fi].mIndices[vi]] = *col_it; + } + + ++col_it; + } + } // if(pColorPerVertex) else +} + +void X3DGeoHelper::add_color(aiMesh &pMesh, const std::vector<int32_t> &pCoordIdx, const std::vector<int32_t> &pColorIdx, + const std::list<aiColor3D> &pColors, const bool pColorPerVertex) { + std::list<aiColor4D> tcol; + + // create RGBA array from RGB. + for (std::list<aiColor3D>::const_iterator it = pColors.begin(); it != pColors.end(); ++it) { + tcol.push_back(aiColor4D((*it).r, (*it).g, (*it).b, 1)); + } + + // call existing function for adding RGBA colors + add_color(pMesh, pCoordIdx, pColorIdx, tcol, pColorPerVertex); +} + +void X3DGeoHelper::add_color(aiMesh &pMesh, const std::vector<int32_t> &coordIdx, const std::vector<int32_t> &colorIdx, + const std::list<aiColor4D> &colors, bool pColorPerVertex) { + std::vector<aiColor4D> col_tgt_arr; + std::list<aiColor4D> col_tgt_list; + std::vector<aiColor4D> col_arr_copy; + + if (coordIdx.size() == 0) { + throw DeadlyImportError("MeshGeometry_AddColor2. pCoordIdx can not be empty."); + } + + // copy list to array because we are need indexed access to colors. + col_arr_copy.reserve(colors.size()); + for (std::list<aiColor4D>::const_iterator it = colors.begin(); it != colors.end(); ++it) { + col_arr_copy.push_back(*it); + } + + if (pColorPerVertex) { + if (colorIdx.size() > 0) { + // check indices array count. + if (colorIdx.size() < coordIdx.size()) { + throw DeadlyImportError("MeshGeometry_AddColor2. Colors indices count(" + ai_to_string(colorIdx.size()) + + ") can not be less than Coords indices count(" + ai_to_string(coordIdx.size()) + ")."); + } + // create list with colors for every vertex. + col_tgt_arr.resize(pMesh.mNumVertices); + for (std::vector<int32_t>::const_iterator colidx_it = colorIdx.begin(), coordidx_it = coordIdx.begin(); colidx_it != colorIdx.end(); ++colidx_it, ++coordidx_it) { + if (*colidx_it == (-1)) { + continue; // skip faces delimiter + } + if ((unsigned int)(*coordidx_it) > pMesh.mNumVertices) { + throw DeadlyImportError("MeshGeometry_AddColor2. Coordinate idx is out of range."); + } + if ((unsigned int)*colidx_it > pMesh.mNumVertices) { + throw DeadlyImportError("MeshGeometry_AddColor2. Color idx is out of range."); + } + + col_tgt_arr[*coordidx_it] = col_arr_copy[*colidx_it]; + } + } // if(pColorIdx.size() > 0) + else { + // when color indices list is absent use CoordIdx. + // check indices array count. + if (colors.size() < pMesh.mNumVertices) { + throw DeadlyImportError("MeshGeometry_AddColor2. Colors count(" + ai_to_string(colors.size()) + ") can not be less than Vertices count(" + + ai_to_string(pMesh.mNumVertices) + ")."); + } + // create list with colors for every vertex. + col_tgt_arr.resize(pMesh.mNumVertices); + for (size_t i = 0; i < pMesh.mNumVertices; i++) { + col_tgt_arr[i] = col_arr_copy[i]; + } + } // if(pColorIdx.size() > 0) else + } // if(pColorPerVertex) + else { + if (colorIdx.size() > 0) { + // check indices array count. + if (colorIdx.size() < pMesh.mNumFaces) { + throw DeadlyImportError("MeshGeometry_AddColor2. Colors indices count(" + ai_to_string(colorIdx.size()) + + ") can not be less than Faces count(" + ai_to_string(pMesh.mNumFaces) + ")."); + } + // create list with colors for every vertex using faces indices. + col_tgt_arr.resize(pMesh.mNumFaces); + + std::vector<int32_t>::const_iterator colidx_it = colorIdx.begin(); + for (size_t fi = 0; fi < pMesh.mNumFaces; fi++) { + if ((unsigned int)*colidx_it > pMesh.mNumFaces) throw DeadlyImportError("MeshGeometry_AddColor2. Face idx is out of range."); + + col_tgt_arr[fi] = col_arr_copy[*colidx_it++]; + } + } // if(pColorIdx.size() > 0) + else { + // when color indices list is absent use CoordIdx. + // check indices array count. + if (colors.size() < pMesh.mNumFaces) { + throw DeadlyImportError("MeshGeometry_AddColor2. Colors count(" + ai_to_string(colors.size()) + ") can not be less than Faces count(" + + ai_to_string(pMesh.mNumFaces) + ")."); + } + // create list with colors for every vertex using faces indices. + col_tgt_arr.resize(pMesh.mNumFaces); + for (size_t fi = 0; fi < pMesh.mNumFaces; fi++) + col_tgt_arr[fi] = col_arr_copy[fi]; + + } // if(pColorIdx.size() > 0) else + } // if(pColorPerVertex) else + + // copy array to list for calling function that add colors. + for (std::vector<aiColor4D>::const_iterator it = col_tgt_arr.begin(); it != col_tgt_arr.end(); ++it) + col_tgt_list.push_back(*it); + // add prepared colors list to mesh. + add_color(pMesh, col_tgt_list, pColorPerVertex); +} + +void X3DGeoHelper::add_normal(aiMesh &pMesh, const std::vector<int32_t> &pCoordIdx, const std::vector<int32_t> &pNormalIdx, + const std::list<aiVector3D> &pNormals, const bool pNormalPerVertex) { + std::vector<size_t> tind; + std::vector<aiVector3D> norm_arr_copy; + + // copy list to array because we are need indexed access to normals. + norm_arr_copy.reserve(pNormals.size()); + for (std::list<aiVector3D>::const_iterator it = pNormals.begin(); it != pNormals.end(); ++it) { + norm_arr_copy.push_back(*it); + } + + if (pNormalPerVertex) { + if (pNormalIdx.size() > 0) { + // check indices array count. + if (pNormalIdx.size() != pCoordIdx.size()) throw DeadlyImportError("Normals and Coords inidces count must be equal."); + + tind.reserve(pNormalIdx.size()); + for (std::vector<int32_t>::const_iterator it = pNormalIdx.begin(); it != pNormalIdx.end(); ++it) { + if (*it != (-1)) tind.push_back(*it); + } + + // copy normals to mesh + pMesh.mNormals = new aiVector3D[pMesh.mNumVertices]; + for (size_t i = 0; (i < pMesh.mNumVertices) && (i < tind.size()); i++) { + if (tind[i] >= norm_arr_copy.size()) + throw DeadlyImportError("MeshGeometry_AddNormal. Normal index(" + ai_to_string(tind[i]) + + ") is out of range. Normals count: " + ai_to_string(norm_arr_copy.size()) + "."); + + pMesh.mNormals[i] = norm_arr_copy[tind[i]]; + } + } else { + if (pNormals.size() != pMesh.mNumVertices) throw DeadlyImportError("MeshGeometry_AddNormal. Normals and vertices count must be equal."); + + // copy normals to mesh + pMesh.mNormals = new aiVector3D[pMesh.mNumVertices]; + std::list<aiVector3D>::const_iterator norm_it = pNormals.begin(); + for (size_t i = 0; i < pMesh.mNumVertices; i++) + pMesh.mNormals[i] = *norm_it++; + } + } // if(pNormalPerVertex) + else { + if (pNormalIdx.size() > 0) { + if (pMesh.mNumFaces != pNormalIdx.size()) throw DeadlyImportError("Normals faces count must be equal to mesh faces count."); + + std::vector<int32_t>::const_iterator normidx_it = pNormalIdx.begin(); + + tind.reserve(pNormalIdx.size()); + for (size_t i = 0, i_e = pNormalIdx.size(); i < i_e; i++) + tind.push_back(*normidx_it++); + + } else { + tind.reserve(pMesh.mNumFaces); + for (size_t i = 0; i < pMesh.mNumFaces; i++) + tind.push_back(i); + } + + // copy normals to mesh + pMesh.mNormals = new aiVector3D[pMesh.mNumVertices]; + for (size_t fi = 0; fi < pMesh.mNumFaces; fi++) { + aiVector3D tnorm; + + tnorm = norm_arr_copy[tind[fi]]; + for (size_t vi = 0, vi_e = pMesh.mFaces[fi].mNumIndices; vi < vi_e; vi++) + pMesh.mNormals[pMesh.mFaces[fi].mIndices[vi]] = tnorm; + } + } // if(pNormalPerVertex) else +} + +void X3DGeoHelper::add_normal(aiMesh &pMesh, const std::list<aiVector3D> &pNormals, const bool pNormalPerVertex) { + std::list<aiVector3D>::const_iterator norm_it = pNormals.begin(); + + if (pNormalPerVertex) { + if (pNormals.size() != pMesh.mNumVertices) throw DeadlyImportError("MeshGeometry_AddNormal. Normals and vertices count must be equal."); + + // copy normals to mesh + pMesh.mNormals = new aiVector3D[pMesh.mNumVertices]; + for (size_t i = 0; i < pMesh.mNumVertices; i++) + pMesh.mNormals[i] = *norm_it++; + } // if(pNormalPerVertex) + else { + if (pNormals.size() != pMesh.mNumFaces) throw DeadlyImportError("MeshGeometry_AddNormal. Normals and faces count must be equal."); + + // copy normals to mesh + pMesh.mNormals = new aiVector3D[pMesh.mNumVertices]; + for (size_t fi = 0; fi < pMesh.mNumFaces; fi++) { + // apply color to all vertices of face + for (size_t vi = 0, vi_e = pMesh.mFaces[fi].mNumIndices; vi < vi_e; vi++) + pMesh.mNormals[pMesh.mFaces[fi].mIndices[vi]] = *norm_it; + + ++norm_it; + } + } // if(pNormalPerVertex) else +} + +void X3DGeoHelper::add_tex_coord(aiMesh &pMesh, const std::vector<int32_t> &pCoordIdx, const std::vector<int32_t> &pTexCoordIdx, + const std::list<aiVector2D> &pTexCoords) { + std::vector<aiVector3D> texcoord_arr_copy; + std::vector<aiFace> faces; + unsigned int prim_type; + + // copy list to array because we are need indexed access to normals. + texcoord_arr_copy.reserve(pTexCoords.size()); + for (std::list<aiVector2D>::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); ++it) { + texcoord_arr_copy.push_back(aiVector3D((*it).x, (*it).y, 0)); + } + + if (pTexCoordIdx.size() > 0) { + coordIdx_str2faces_arr(pTexCoordIdx, faces, prim_type); + if (faces.empty()) { + throw DeadlyImportError("Failed to add texture coordinates to mesh, faces list is empty."); + } + if (faces.size() != pMesh.mNumFaces) { + throw DeadlyImportError("Texture coordinates faces count must be equal to mesh faces count."); + } + } else { + coordIdx_str2faces_arr(pCoordIdx, faces, prim_type); + } + + pMesh.mTextureCoords[0] = new aiVector3D[pMesh.mNumVertices]; + pMesh.mNumUVComponents[0] = 2; + for (size_t fi = 0, fi_e = faces.size(); fi < fi_e; fi++) { + if (pMesh.mFaces[fi].mNumIndices != faces.at(fi).mNumIndices) + throw DeadlyImportError("Number of indices in texture face and mesh face must be equal. Invalid face index: " + ai_to_string(fi) + "."); + + for (size_t ii = 0; ii < pMesh.mFaces[fi].mNumIndices; ii++) { + size_t vert_idx = pMesh.mFaces[fi].mIndices[ii]; + size_t tc_idx = faces.at(fi).mIndices[ii]; + + pMesh.mTextureCoords[0][vert_idx] = texcoord_arr_copy.at(tc_idx); + } + } // for(size_t fi = 0, fi_e = faces.size(); fi < fi_e; fi++) +} + +void X3DGeoHelper::add_tex_coord(aiMesh &pMesh, const std::list<aiVector2D> &pTexCoords) { + std::vector<aiVector3D> tc_arr_copy; + + if (pTexCoords.size() != pMesh.mNumVertices) { + throw DeadlyImportError("MeshGeometry_AddTexCoord. Texture coordinates and vertices count must be equal."); + } + + // copy list to array because we are need convert aiVector2D to aiVector3D and also get indexed access as a bonus. + tc_arr_copy.reserve(pTexCoords.size()); + for (std::list<aiVector2D>::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); ++it) { + tc_arr_copy.push_back(aiVector3D((*it).x, (*it).y, 0)); + } + + // copy texture coordinates to mesh + pMesh.mTextureCoords[0] = new aiVector3D[pMesh.mNumVertices]; + pMesh.mNumUVComponents[0] = 2; + for (size_t i = 0; i < pMesh.mNumVertices; i++) { + pMesh.mTextureCoords[0][i] = tc_arr_copy[i]; + } +} + +aiMesh *X3DGeoHelper::make_mesh(const std::vector<int32_t> &pCoordIdx, const std::list<aiVector3D> &pVertices) { + std::vector<aiFace> faces; + unsigned int prim_type = 0; + + // create faces array from input string with vertices indices. + X3DGeoHelper::coordIdx_str2faces_arr(pCoordIdx, faces, prim_type); + if (!faces.size()) { + throw DeadlyImportError("Failed to create mesh, faces list is empty."); + } + + // + // Create new mesh and copy geometry data. + // + aiMesh *tmesh = new aiMesh; + size_t ts = faces.size(); + // faces + tmesh->mFaces = new aiFace[ts]; + tmesh->mNumFaces = static_cast<unsigned int>(ts); + for (size_t i = 0; i < ts; i++) + tmesh->mFaces[i] = faces.at(i); + + // vertices + std::list<aiVector3D>::const_iterator vit = pVertices.begin(); + + ts = pVertices.size(); + tmesh->mVertices = new aiVector3D[ts]; + tmesh->mNumVertices = static_cast<unsigned int>(ts); + for (size_t i = 0; i < ts; i++) { + tmesh->mVertices[i] = *vit++; + } + + // set primitives type and return result. + tmesh->mPrimitiveTypes = prim_type; + + return tmesh; +} + +} // namespace Assimp diff --git a/libs/assimp/code/AssetLib/X3D/X3DGeoHelper.h b/libs/assimp/code/AssetLib/X3D/X3DGeoHelper.h new file mode 100644 index 0000000..78e57f9 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DGeoHelper.h @@ -0,0 +1,39 @@ +#pragma once + +#include <assimp/vector2.h> +#include <assimp/vector3.h> +#include <assimp/color4.h> +#include <assimp/types.h> + +#include <list> +#include <vector> + +struct aiFace; +struct aiMesh; + +namespace Assimp { + +class X3DGeoHelper { +public: + static aiVector3D make_point2D(float angle, float radius); + static void make_arc2D(float pStartAngle, float pEndAngle, float pRadius, size_t numSegments, std::list<aiVector3D> &pVertices); + static void extend_point_to_line(const std::list<aiVector3D> &pPoint, std::list<aiVector3D> &pLine); + static void polylineIdx_to_lineIdx(const std::list<int32_t> &pPolylineCoordIdx, std::list<int32_t> &pLineCoordIdx); + static void rect_parallel_epiped(const aiVector3D &pSize, std::list<aiVector3D> &pVertices); + static void coordIdx_str2faces_arr(const std::vector<int32_t> &pCoordIdx, std::vector<aiFace> &pFaces, unsigned int &pPrimitiveTypes); + static void add_color(aiMesh &pMesh, const std::list<aiColor3D> &pColors, const bool pColorPerVertex); + static void add_color(aiMesh &pMesh, const std::list<aiColor4D> &pColors, const bool pColorPerVertex); + static void add_color(aiMesh &pMesh, const std::vector<int32_t> &pCoordIdx, const std::vector<int32_t> &pColorIdx, + const std::list<aiColor3D> &pColors, const bool pColorPerVertex); + static void add_color(aiMesh &pMesh, const std::vector<int32_t> &pCoordIdx, const std::vector<int32_t> &pColorIdx, + const std::list<aiColor4D> &pColors, const bool pColorPerVertex); + static void add_normal(aiMesh &pMesh, const std::vector<int32_t> &pCoordIdx, const std::vector<int32_t> &pNormalIdx, + const std::list<aiVector3D> &pNormals, const bool pNormalPerVertex); + static void add_normal(aiMesh &pMesh, const std::list<aiVector3D> &pNormals, const bool pNormalPerVertex); + static void add_tex_coord(aiMesh &pMesh, const std::vector<int32_t> &pCoordIdx, const std::vector<int32_t> &pTexCoordIdx, + const std::list<aiVector2D> &pTexCoords); + static void add_tex_coord(aiMesh &pMesh, const std::list<aiVector2D> &pTexCoords); + static aiMesh *make_mesh(const std::vector<int32_t> &pCoordIdx, const std::list<aiVector3D> &pVertices); +}; + +} // namespace Assimp diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter.cpp b/libs/assimp/code/AssetLib/X3D/X3DImporter.cpp new file mode 100644 index 0000000..b34f361 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter.cpp @@ -0,0 +1,488 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 X3DImporter.cpp +/// \brief X3D-format files importer for Assimp: main algorithm implementation. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" + +#include <assimp/DefaultIOSystem.h> + +// Header files, stdlib. +#include <iterator> +#include <memory> + +namespace Assimp { + +/// Constant which holds the importer description +const aiImporterDesc X3DImporter::Description = { + "Extensible 3D(X3D) Importer", + "smalcom", + "", + "See documentation in source code. Chapter: Limitations.", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, + 0, + 0, + 0, + 0, + "x3d x3db" +}; + +bool X3DImporter::isNodeEmpty(XmlNode &node) { + return node.first_child().empty(); +} + +void X3DImporter::checkNodeMustBeEmpty(XmlNode &node) { + if (!isNodeEmpty(node)) throw DeadlyImportError(std::string("Node <") + node.name() + "> must be empty."); +} + +void X3DImporter::skipUnsupportedNode(const std::string &pParentNodeName, XmlNode &node) { + static const size_t Uns_Skip_Len = 192; + static const char *Uns_Skip[Uns_Skip_Len] = { + // CAD geometry component + "CADAssembly", "CADFace", "CADLayer", "CADPart", "IndexedQuadSet", "QuadSet", + // Core + "ROUTE", "ExternProtoDeclare", "ProtoDeclare", "ProtoInstance", "ProtoInterface", "WorldInfo", + // Distributed interactive simulation (DIS) component + "DISEntityManager", "DISEntityTypeMapping", "EspduTransform", "ReceiverPdu", "SignalPdu", "TransmitterPdu", + // Cube map environmental texturing component + "ComposedCubeMapTexture", "GeneratedCubeMapTexture", "ImageCubeMapTexture", + // Environmental effects component + "Background", "Fog", "FogCoordinate", "LocalFog", "TextureBackground", + // Environmental sensor component + "ProximitySensor", "TransformSensor", "VisibilitySensor", + // Followers component + "ColorChaser", "ColorDamper", "CoordinateChaser", "CoordinateDamper", "OrientationChaser", "OrientationDamper", "PositionChaser", "PositionChaser2D", + "PositionDamper", "PositionDamper2D", "ScalarChaser", "ScalarDamper", "TexCoordChaser2D", "TexCoordDamper2D", + // Geospatial component + "GeoCoordinate", "GeoElevationGrid", "GeoLocation", "GeoLOD", "GeoMetadata", "GeoOrigin", "GeoPositionInterpolator", "GeoProximitySensor", + "GeoTouchSensor", "GeoTransform", "GeoViewpoint", + // Humanoid Animation (H-Anim) component + "HAnimDisplacer", "HAnimHumanoid", "HAnimJoint", "HAnimSegment", "HAnimSite", + // Interpolation component + "ColorInterpolator", "CoordinateInterpolator", "CoordinateInterpolator2D", "EaseInEaseOut", "NormalInterpolator", "OrientationInterpolator", + "PositionInterpolator", "PositionInterpolator2D", "ScalarInterpolator", "SplinePositionInterpolator", "SplinePositionInterpolator2D", + "SplineScalarInterpolator", "SquadOrientationInterpolator", + // Key device sensor component + "KeySensor", "StringSensor", + // Layering component + "Layer", "LayerSet", "Viewport", + // Layout component + "Layout", "LayoutGroup", "LayoutLayer", "ScreenFontStyle", "ScreenGroup", + // Navigation component + "Billboard", "Collision", "LOD", "NavigationInfo", "OrthoViewpoint", "Viewpoint", "ViewpointGroup", + // Networking component + "EXPORT", "IMPORT", "Anchor", "LoadSensor", + // NURBS component + "Contour2D", "ContourPolyline2D", "CoordinateDouble", "NurbsCurve", "NurbsCurve2D", "NurbsOrientationInterpolator", "NurbsPatchSurface", + "NurbsPositionInterpolator", "NurbsSet", "NurbsSurfaceInterpolator", "NurbsSweptSurface", "NurbsSwungSurface", "NurbsTextureCoordinate", + "NurbsTrimmedSurface", + // Particle systems component + "BoundedPhysicsModel", "ConeEmitter", "ExplosionEmitter", "ForcePhysicsModel", "ParticleSystem", "PointEmitter", "PolylineEmitter", "SurfaceEmitter", + "VolumeEmitter", "WindPhysicsModel", + // Picking component + "LinePickSensor", "PickableGroup", "PointPickSensor", "PrimitivePickSensor", "VolumePickSensor", + // Pointing device sensor component + "CylinderSensor", "PlaneSensor", "SphereSensor", "TouchSensor", + // Rendering component + "ClipPlane", + // Rigid body physics + "BallJoint", "CollidableOffset", "CollidableShape", "CollisionCollection", "CollisionSensor", "CollisionSpace", "Contact", "DoubleAxisHingeJoint", + "MotorJoint", "RigidBody", "RigidBodyCollection", "SingleAxisHingeJoint", "SliderJoint", "UniversalJoint", + // Scripting component + "Script", + // Programmable shaders component + "ComposedShader", "FloatVertexAttribute", "Matrix3VertexAttribute", "Matrix4VertexAttribute", "PackagedShader", "ProgramShader", "ShaderPart", + "ShaderProgram", + // Shape component + "FillProperties", "LineProperties", "TwoSidedMaterial", + // Sound component + "AudioClip", "Sound", + // Text component + "FontStyle", "Text", + // Texturing3D Component + "ComposedTexture3D", "ImageTexture3D", "PixelTexture3D", "TextureCoordinate3D", "TextureCoordinate4D", "TextureTransformMatrix3D", "TextureTransform3D", + // Texturing component + "MovieTexture", "MultiTexture", "MultiTextureCoordinate", "MultiTextureTransform", "PixelTexture", "TextureCoordinateGenerator", "TextureProperties", + // Time component + "TimeSensor", + // Event Utilities component + "BooleanFilter", "BooleanSequencer", "BooleanToggle", "BooleanTrigger", "IntegerSequencer", "IntegerTrigger", "TimeTrigger", + // Volume rendering component + "BlendedVolumeStyle", "BoundaryEnhancementVolumeStyle", "CartoonVolumeStyle", "ComposedVolumeStyle", "EdgeEnhancementVolumeStyle", "IsoSurfaceVolumeData", + "OpacityMapVolumeStyle", "ProjectionVolumeStyle", "SegmentedVolumeData", "ShadedVolumeStyle", "SilhouetteEnhancementVolumeStyle", "ToneMappedVolumeStyle", + "VolumeData" + }; + + const std::string nn = node.name(); + + if (nn.empty()) { + const std::string nv = node.value(); + if (!nv.empty()) { + LogInfo("Ignoring comment \"" + nv + "\" in " + pParentNodeName + "."); + return; + } + } + + bool found = false; + + for (size_t i = 0; i < Uns_Skip_Len; i++) { + if (nn == Uns_Skip[i]) { + found = true; + } + } + + if (!found) throw DeadlyImportError("Unknown node \"" + nn + "\" in " + pParentNodeName + "."); + + LogInfo("Skipping node \"" + nn + "\" in " + pParentNodeName + "."); +} + +X3DImporter::X3DImporter() : + mNodeElementCur(nullptr), + mScene(nullptr), + mpIOHandler(nullptr) { + // empty +} + +X3DImporter::~X3DImporter() { + // Clear() is accounting if data already is deleted. So, just check again if all data is deleted. + Clear(); +} + +void X3DImporter::Clear() { + mNodeElementCur = nullptr; + // Delete all elements + if (!NodeElement_List.empty()) { + for (std::list<X3DNodeElementBase *>::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); ++it) { + delete *it; + } + NodeElement_List.clear(); + } +} + +void X3DImporter::ParseFile(const std::string &file, IOSystem *pIOHandler) { + ai_assert(nullptr != pIOHandler); + + static const std::string mode = "rb"; + std::unique_ptr<IOStream> fileStream(pIOHandler->Open(file, mode)); + if (!fileStream.get()) { + throw DeadlyImportError("Failed to open file " + file + "."); + } + + XmlParser theParser; + if (!theParser.parse(fileStream.get())) { + return; + } + + XmlNode *node = theParser.findNode("X3D"); + if (nullptr == node) { + return; + } + + for (auto ¤tNode : node->children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "head") { + readHead(currentNode); + } else if (currentName == "Scene") { + readScene(currentNode); + } else { + skipUnsupportedNode("X3D", currentNode); + } + } +} + +bool X3DImporter::CanRead(const std::string &pFile, IOSystem * /*pIOHandler*/, bool checkSig) const { + if (checkSig) { + if (GetExtension(pFile) == "x3d") + return true; + } + + return false; +} + +void X3DImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + mpIOHandler = pIOHandler; + + Clear(); + std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb")); + if (!stream) { + throw DeadlyImportError("Could not open file for reading"); + } + std::string::size_type slashPos = pFile.find_last_of("\\/"); + + mScene = pScene; + pScene->mRootNode = new aiNode(pFile); + pScene->mRootNode->mParent = nullptr; + pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED; + + pIOHandler->PushDirectory(slashPos == std::string::npos ? std::string() : pFile.substr(0, slashPos + 1)); + ParseFile(pFile, pIOHandler); + pIOHandler->PopDirectory(); + + //search for root node element + + mNodeElementCur = NodeElement_List.front(); + if (mNodeElementCur == nullptr) { + return; + } + while (mNodeElementCur->Parent != nullptr) { + mNodeElementCur = mNodeElementCur->Parent; + } + + { // fill aiScene with objects. + std::list<aiMesh *> mesh_list; + std::list<aiMaterial *> mat_list; + std::list<aiLight *> light_list; + + // create nodes tree + Postprocess_BuildNode(*mNodeElementCur, *pScene->mRootNode, mesh_list, mat_list, light_list); + // copy needed data to scene + if (!mesh_list.empty()) { + std::list<aiMesh *>::const_iterator it = mesh_list.begin(); + + pScene->mNumMeshes = static_cast<unsigned int>(mesh_list.size()); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + for (size_t i = 0; i < pScene->mNumMeshes; i++) + pScene->mMeshes[i] = *it++; + } + + if (!mat_list.empty()) { + std::list<aiMaterial *>::const_iterator it = mat_list.begin(); + + pScene->mNumMaterials = static_cast<unsigned int>(mat_list.size()); + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + for (size_t i = 0; i < pScene->mNumMaterials; i++) + pScene->mMaterials[i] = *it++; + } + + if (!light_list.empty()) { + std::list<aiLight *>::const_iterator it = light_list.begin(); + + pScene->mNumLights = static_cast<unsigned int>(light_list.size()); + pScene->mLights = new aiLight *[pScene->mNumLights]; + for (size_t i = 0; i < pScene->mNumLights; i++) + pScene->mLights[i] = *it++; + } + } +} + +const aiImporterDesc *X3DImporter::GetInfo() const { + return &Description; +} + +struct meta_entry { + std::string name; + std::string value; +}; + +void X3DImporter::readHead(XmlNode &node) { + std::vector<meta_entry> metaArray; + for (auto currentNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "meta") { + //checkNodeMustBeEmpty(node); + meta_entry entry; + if (XmlParser::getStdStrAttribute(currentNode, "name", entry.name)) { + XmlParser::getStdStrAttribute(currentNode, "content", entry.value); + metaArray.emplace_back(entry); + } + } + // TODO: check if other node types in head should be supported + } + mScene->mMetaData = aiMetadata::Alloc(static_cast<unsigned int>(metaArray.size())); + unsigned int i = 0; + for (auto currentMeta : metaArray) { + mScene->mMetaData->Set(i, currentMeta.name, aiString(currentMeta.value)); + ++i; + } +} + +void X3DImporter::readChildNodes(XmlNode &node, const std::string &pParentNodeName) { + if (node.empty()) { + return; + } + for (auto currentNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "Shape") + readShape(currentNode); + else if (currentName == "Group") { + startReadGroup(currentNode); + readChildNodes(currentNode, "Group"); + endReadGroup(); + } else if (currentName == "StaticGroup") { + startReadStaticGroup(currentNode); + readChildNodes(currentNode, "StaticGroup"); + endReadStaticGroup(); + } else if (currentName == "Transform") { + startReadTransform(currentNode); + readChildNodes(currentNode, "Transform"); + endReadTransform(); + } else if (currentName == "Switch") { + startReadSwitch(currentNode); + readChildNodes(currentNode, "Switch"); + endReadSwitch(); + } else if (currentName == "DirectionalLight") { + readDirectionalLight(currentNode); + } else if (currentName == "PointLight") { + readPointLight(currentNode); + } else if (currentName == "SpotLight") { + readSpotLight(currentNode); + } else if (currentName == "Inline") { + readInline(currentNode); + } else if (!checkForMetadataNode(currentNode)) { + skipUnsupportedNode(pParentNodeName, currentNode); + } + } +} + +void X3DImporter::readScene(XmlNode &node) { + ParseHelper_Group_Begin(true); + readChildNodes(node, "Scene"); + ParseHelper_Node_Exit(); +} + +/*********************************************************************************************************************************************/ +/************************************************************ Functions: find set ************************************************************/ +/*********************************************************************************************************************************************/ + +bool X3DImporter::FindNodeElement_FromRoot(const std::string &pID, const X3DElemType pType, X3DNodeElementBase **pElement) { + for (std::list<X3DNodeElementBase *>::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); ++it) { + if (((*it)->Type == pType) && ((*it)->ID == pID)) { + if (pElement != nullptr) *pElement = *it; + + return true; + } + } // for(std::list<CX3DImporter_NodeElement*>::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); it++) + + return false; +} + +bool X3DImporter::FindNodeElement_FromNode(X3DNodeElementBase *pStartNode, const std::string &pID, + const X3DElemType pType, X3DNodeElementBase **pElement) { + bool found = false; // flag: true - if requested element is found. + + // Check if pStartNode - this is the element, we are looking for. + if ((pStartNode->Type == pType) && (pStartNode->ID == pID)) { + found = true; + if (pElement != nullptr) { + *pElement = pStartNode; + } + + goto fne_fn_end; + } // if((pStartNode->Type() == pType) && (pStartNode->ID() == pID)) + + // Check childs of pStartNode. + for (std::list<X3DNodeElementBase *>::iterator ch_it = pStartNode->Children.begin(); ch_it != pStartNode->Children.end(); ++ch_it) { + found = FindNodeElement_FromNode(*ch_it, pID, pType, pElement); + if (found) { + break; + } + } // for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = it->Children.begin(); ch_it != it->Children.end(); ch_it++) + +fne_fn_end: + + return found; +} + +bool X3DImporter::FindNodeElement(const std::string &pID, const X3DElemType pType, X3DNodeElementBase **pElement) { + X3DNodeElementBase *tnd = mNodeElementCur; // temporary pointer to node. + bool static_search = false; // flag: true if searching in static node. + + // At first check if we have deal with static node. Go up through parent nodes and check flag. + while (tnd != nullptr) { + if (tnd->Type == X3DElemType::ENET_Group) { + if (((X3DNodeElementGroup *)tnd)->Static) { + static_search = true; // Flag found, stop walking up. Node with static flag will holded in tnd variable. + break; + } + } + + tnd = tnd->Parent; // go up in graph. + } // while (tnd != nullptr) + + // at now call appropriate search function. + if (static_search) { + return FindNodeElement_FromNode(tnd, pID, pType, pElement); + } else { + return FindNodeElement_FromRoot(pID, pType, pElement); + } +} + +/*********************************************************************************************************************************************/ +/************************************************************ Functions: parse set ***********************************************************/ +/*********************************************************************************************************************************************/ + +void X3DImporter::ParseHelper_Group_Begin(const bool pStatic) { + X3DNodeElementGroup *new_group = new X3DNodeElementGroup(mNodeElementCur, pStatic); // create new node with current node as parent. + + // if we are adding not the root element then add new element to current element child list. + if (mNodeElementCur != nullptr) { + mNodeElementCur->Children.push_back(new_group); + } + + NodeElement_List.push_back(new_group); // it's a new element - add it to list. + mNodeElementCur = new_group; // switch current element to new one. +} + +void X3DImporter::ParseHelper_Node_Enter(X3DNodeElementBase *pNode) { + ai_assert(nullptr != pNode); + + mNodeElementCur->Children.push_back(pNode); // add new element to current element child list. + mNodeElementCur = pNode; // switch current element to new one. +} + +void X3DImporter::ParseHelper_Node_Exit() { + // check if we can walk up. + if (mNodeElementCur != nullptr) { + mNodeElementCur = mNodeElementCur->Parent; + } else { + int i = 0; + ++i; + } +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter.hpp b/libs/assimp/code/AssetLib/X3D/X3DImporter.hpp new file mode 100644 index 0000000..d9aed70 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter.hpp @@ -0,0 +1,383 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef INCLUDED_AI_X3D_IMPORTER_H +#define INCLUDED_AI_X3D_IMPORTER_H + +#include "X3DImporter_Node.hpp" + +#include <assimp/BaseImporter.h> +#include <assimp/XmlParser.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/types.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/ProgressHandler.hpp> + +#include <list> +#include <string> + +namespace Assimp { + +inline void Throw_ArgOutOfRange(const std::string &argument) { + throw DeadlyImportError("Argument value is out of range for: \"" + argument + "\"."); +} + +inline void Throw_CloseNotFound(const std::string &node) { + throw DeadlyImportError("Close tag for node <" + node + "> not found. Seems file is corrupt."); +} + +inline void Throw_ConvertFail_Str2ArrF(const std::string &nodeName, const std::string &pAttrValue) { + throw DeadlyImportError("In <" + nodeName + "> failed to convert attribute value \"" + pAttrValue + + "\" from string to array of floats."); +} + +inline void Throw_ConvertFail_Str2ArrD(const std::string &nodeName, const std::string &pAttrValue) { + throw DeadlyImportError("In <" + nodeName + "> failed to convert attribute value \"" + pAttrValue + + "\" from string to array of doubles."); +} + +inline void Throw_ConvertFail_Str2ArrB(const std::string &nodeName, const std::string &pAttrValue) { + throw DeadlyImportError("In <" + nodeName + "> failed to convert attribute value \"" + pAttrValue + + "\" from string to array of booleans."); +} + +inline void Throw_ConvertFail_Str2ArrI(const std::string &nodeName, const std::string &pAttrValue) { + throw DeadlyImportError("In <" + nodeName + "> failed to convert attribute value \"" + pAttrValue + + "\" from string to array of integers."); +} + +inline void Throw_DEF_And_USE(const std::string &nodeName) { + throw DeadlyImportError("\"DEF\" and \"USE\" can not be defined both in <" + nodeName + ">."); +} + +inline void Throw_IncorrectAttr(const std::string &nodeName, const std::string &pAttrName) { + throw DeadlyImportError("Node <" + nodeName + "> has incorrect attribute \"" + pAttrName + "\"."); +} + +inline void Throw_IncorrectAttrValue(const std::string &nodeName, const std::string &pAttrName) { + throw DeadlyImportError("Attribute \"" + pAttrName + "\" in node <" + nodeName + "> has incorrect value."); +} + +inline void Throw_MoreThanOnceDefined(const std::string &nodeName, const std::string &pNodeType, const std::string &pDescription) { + throw DeadlyImportError("\"" + pNodeType + "\" node can be used only once in " + nodeName + ". Description: " + pDescription); +} + +inline void Throw_TagCountIncorrect(const std::string &pNode) { + throw DeadlyImportError("Count of open and close tags for node <" + pNode + "> are not equivalent. Seems file is corrupt."); +} + +inline void Throw_USE_NotFound(const std::string &nodeName, const std::string &pAttrValue) { + throw DeadlyImportError("Not found node with name \"" + pAttrValue + "\" in <" + nodeName + ">."); +} + +inline void LogInfo(const std::string &message) { + DefaultLogger::get()->info(message); +} + +/// \class X3DImporter +/// Class that holding scene graph which include: groups, geometry, metadata etc. +/// +/// Limitations. +/// +/// Pay attention that X3D is format for interactive graphic and simulations for web browsers. +/// So not all features can be imported using Assimp. +/// +/// Unsupported nodes: +/// CAD geometry component: +/// "CADAssembly", "CADFace", "CADLayer", "CADPart", "IndexedQuadSet", "QuadSet" +/// Core component: +/// "ROUTE", "ExternProtoDeclare", "ProtoDeclare", "ProtoInstance", "ProtoInterface", "WorldInfo" +/// Distributed interactive simulation (DIS) component: +/// "DISEntityManager", "DISEntityTypeMapping", "EspduTransform", "ReceiverPdu", "SignalPdu", "TransmitterPdu" +/// Cube map environmental texturing component: +/// "ComposedCubeMapTexture", "GeneratedCubeMapTexture", "ImageCubeMapTexture" +/// Environmental effects component: +/// "Background", "Fog", "FogCoordinate", "LocalFog", "TextureBackground" +/// Environmental sensor component: +/// "ProximitySensor", "TransformSensor", "VisibilitySensor" +/// Followers component: +/// "ColorChaser", "ColorDamper", "CoordinateChaser", "CoordinateDamper", "OrientationChaser", "OrientationDamper", "PositionChaser", +/// "PositionChaser2D", "PositionDamper", "PositionDamper2D", "ScalarChaser", "ScalarDamper", "TexCoordChaser2D", "TexCoordDamper2D" +/// Geospatial component: +/// "GeoCoordinate", "GeoElevationGrid", "GeoLocation", "GeoLOD", "GeoMetadata", "GeoOrigin", "GeoPositionInterpolator", "GeoProximitySensor", +/// "GeoTouchSensor", "GeoTransform", "GeoViewpoint" +/// Humanoid Animation (H-Anim) component: +/// "HAnimDisplacer", "HAnimHumanoid", "HAnimJoint", "HAnimSegment", "HAnimSite" +/// Interpolation component: +/// "ColorInterpolator", "CoordinateInterpolator", "CoordinateInterpolator2D", "EaseInEaseOut", "NormalInterpolator", "OrientationInterpolator", +/// "PositionInterpolator", "PositionInterpolator2D", "ScalarInterpolator", "SplinePositionInterpolator", "SplinePositionInterpolator2D", +/// "SplineScalarInterpolator", "SquadOrientationInterpolator", +/// Key device sensor component: +/// "KeySensor", "StringSensor" +/// Layering component: +/// "Layer", "LayerSet", "Viewport" +/// Layout component: +/// "Layout", "LayoutGroup", "LayoutLayer", "ScreenFontStyle", "ScreenGroup" +/// Navigation component: +/// "Billboard", "Collision", "LOD", "NavigationInfo", "OrthoViewpoint", "Viewpoint", "ViewpointGroup" +/// Networking component: +/// "EXPORT", "IMPORT", "Anchor", "LoadSensor" +/// NURBS component: +/// "Contour2D", "ContourPolyline2D", "CoordinateDouble", "NurbsCurve", "NurbsCurve2D", "NurbsOrientationInterpolator", "NurbsPatchSurface", +/// "NurbsPositionInterpolator", "NurbsSet", "NurbsSurfaceInterpolator", "NurbsSweptSurface", "NurbsSwungSurface", "NurbsTextureCoordinate", +/// "NurbsTrimmedSurface" +/// Particle systems component: +/// "BoundedPhysicsModel", "ConeEmitter", "ExplosionEmitter", "ForcePhysicsModel", "ParticleSystem", "PointEmitter", "PolylineEmitter", +/// "SurfaceEmitter", "VolumeEmitter", "WindPhysicsModel" +/// Picking component: +/// "LinePickSensor", "PickableGroup", "PointPickSensor", "PrimitivePickSensor", "VolumePickSensor" +/// Pointing device sensor component: +/// "CylinderSensor", "PlaneSensor", "SphereSensor", "TouchSensor" +/// Rendering component: +/// "ClipPlane" +/// Rigid body physics: +/// "BallJoint", "CollidableOffset", "CollidableShape", "CollisionCollection", "CollisionSensor", "CollisionSpace", "Contact", "DoubleAxisHingeJoint", +/// "MotorJoint", "RigidBody", "RigidBodyCollection", "SingleAxisHingeJoint", "SliderJoint", "UniversalJoint" +/// Scripting component: +/// "Script" +/// Programmable shaders component: +/// "ComposedShader", "FloatVertexAttribute", "Matrix3VertexAttribute", "Matrix4VertexAttribute", "PackagedShader", "ProgramShader", "ShaderPart", +/// "ShaderProgram", +/// Shape component: +/// "FillProperties", "LineProperties", "TwoSidedMaterial" +/// Sound component: +/// "AudioClip", "Sound" +/// Text component: +/// "FontStyle", "Text" +/// Texturing3D Component: +/// "ComposedTexture3D", "ImageTexture3D", "PixelTexture3D", "TextureCoordinate3D", "TextureCoordinate4D", "TextureTransformMatrix3D", +/// "TextureTransform3D" +/// Texturing component: +/// "MovieTexture", "MultiTexture", "MultiTextureCoordinate", "MultiTextureTransform", "PixelTexture", "TextureCoordinateGenerator", +/// "TextureProperties", +/// Time component: +/// "TimeSensor" +/// Event Utilities component: +/// "BooleanFilter", "BooleanSequencer", "BooleanToggle", "BooleanTrigger", "IntegerSequencer", "IntegerTrigger", "TimeTrigger", +/// Volume rendering component: +/// "BlendedVolumeStyle", "BoundaryEnhancementVolumeStyle", "CartoonVolumeStyle", "ComposedVolumeStyle", "EdgeEnhancementVolumeStyle", +/// "IsoSurfaceVolumeData", "OpacityMapVolumeStyle", "ProjectionVolumeStyle", "SegmentedVolumeData", "ShadedVolumeStyle", +/// "SilhouetteEnhancementVolumeStyle", "ToneMappedVolumeStyle", "VolumeData" +/// +/// Supported nodes: +/// Core component: +/// "MetadataBoolean", "MetadataDouble", "MetadataFloat", "MetadataInteger", "MetadataSet", "MetadataString" +/// Geometry2D component: +/// "Arc2D", "ArcClose2D", "Circle2D", "Disk2D", "Polyline2D", "Polypoint2D", "Rectangle2D", "TriangleSet2D" +/// Geometry3D component: +/// "Box", "Cone", "Cylinder", "ElevationGrid", "Extrusion", "IndexedFaceSet", "Sphere" +/// Grouping component: +/// "Group", "StaticGroup", "Switch", "Transform" +/// Lighting component: +/// "DirectionalLight", "PointLight", "SpotLight" +/// Networking component: +/// "Inline" +/// Rendering component: +/// "Color", "ColorRGBA", "Coordinate", "IndexedLineSet", "IndexedTriangleFanSet", "IndexedTriangleSet", "IndexedTriangleStripSet", "LineSet", +/// "PointSet", "TriangleFanSet", "TriangleSet", "TriangleStripSet", "Normal" +/// Shape component: +/// "Shape", "Appearance", "Material" +/// Texturing component: +/// "ImageTexture", "TextureCoordinate", "TextureTransform" +/// +/// Limitations of attribute "USE". +/// If "USE" is set then node must be empty, like that: +/// <Node USE='name'/> +/// not the +/// <Node USE='name'><!-- something --> </Node> +/// +/// Ignored attributes: "creaseAngle", "convex", "solid". +/// +/// Texture coordinates generating: only for Sphere, Cone, Cylinder. In all other case used PLANE mapping. +/// It's better that Assimp main code has powerful texture coordinates generator. Then is not needed to +/// duplicate this code in every importer. +/// +/// Lighting limitations. +/// If light source placed in some group with "DEF" set. And after that some node is use it group with "USE" attribute then +/// you will get error about duplicate light sources. That's happening because Assimp require names for lights but do not like +/// duplicates of it )). +/// +/// Color for faces. +/// That's happening when attribute "colorPerVertex" is set to "false". But Assimp do not hold how many colors has mesh and require +/// equal length for mVertices and mColors. You will see the colors but vertices will use call which last used in "colorIdx". +/// +/// That's all for now. Enjoy +/// + +using X3DElementList = std::list<X3DNodeElementBase *>; + +class X3DImporter : public BaseImporter { +public: + std::list<X3DNodeElementBase *> NodeElement_List; ///< All elements of scene graph. + +public: + /// Default constructor. + X3DImporter(); + + /// Default destructor. + ~X3DImporter(); + + /***********************************************/ + /******** Functions: parse set, public *********/ + /***********************************************/ + + /// Parse X3D file and fill scene graph. The function has no return value. Result can be found by analyzing the generated graph. + /// Also exception can be thrown if trouble will found. + /// \param [in] pFile - name of file to be parsed. + /// \param [in] pIOHandler - pointer to IO helper object. + void ParseFile(const std::string &pFile, IOSystem *pIOHandler); + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool pCheckSig) const; + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler); + const aiImporterDesc *GetInfo() const; + void Clear(); + +private: + X3DNodeElementBase *MACRO_USE_CHECKANDAPPLY(XmlNode &node, std::string pDEF, std::string pUSE, X3DElemType pType, X3DNodeElementBase *pNE); + bool isNodeEmpty(XmlNode &node); + void checkNodeMustBeEmpty(XmlNode &node); + void skipUnsupportedNode(const std::string &pParentNodeName, XmlNode &node); + void readHead(XmlNode &node); + void readChildNodes(XmlNode &node, const std::string &pParentNodeName); + void readScene(XmlNode &node); + + bool FindNodeElement_FromRoot(const std::string &pID, const X3DElemType pType, X3DNodeElementBase **pElement); + bool FindNodeElement_FromNode(X3DNodeElementBase *pStartNode, const std::string &pID, + const X3DElemType pType, X3DNodeElementBase **pElement); + bool FindNodeElement(const std::string &pID, const X3DElemType pType, X3DNodeElementBase **pElement); + void ParseHelper_Group_Begin(const bool pStatic = false); + void ParseHelper_Node_Enter(X3DNodeElementBase *pNode); + void ParseHelper_Node_Exit(); + + // 2D geometry + void readArc2D(XmlNode &node); + void readArcClose2D(XmlNode &node); + void readCircle2D(XmlNode &node); + void readDisk2D(XmlNode &node); + void readPolyline2D(XmlNode &node); + void readPolypoint2D(XmlNode &node); + void readRectangle2D(XmlNode &node); + void readTriangleSet2D(XmlNode &node); + + // 3D geometry + void readBox(XmlNode &node); + void readCone(XmlNode &node); + void readCylinder(XmlNode &node); + void readElevationGrid(XmlNode &node); + void readExtrusion(XmlNode &node); + void readIndexedFaceSet(XmlNode &node); + void readSphere(XmlNode &node); + + // group + void startReadGroup(XmlNode &node); + void endReadGroup(); + void startReadStaticGroup(XmlNode &node); + void endReadStaticGroup(); + void startReadSwitch(XmlNode &node); + void endReadSwitch(); + void startReadTransform(XmlNode &node); + void endReadTransform(); + + // light + void readDirectionalLight(XmlNode &node); + void readPointLight(XmlNode &node); + void readSpotLight(XmlNode &node); + + // metadata + bool checkForMetadataNode(XmlNode &node); + void childrenReadMetadata(XmlNode &node, X3DNodeElementBase *pParentElement, const std::string &pNodeName); + void readMetadataBoolean(XmlNode &node); + void readMetadataDouble(XmlNode &node); + void readMetadataFloat(XmlNode &node); + void readMetadataInteger(XmlNode &node); + void readMetadataSet(XmlNode &node); + void readMetadataString(XmlNode &node); + + // networking + void readInline(XmlNode &node); + + // postprocessing + aiMatrix4x4 PostprocessHelper_Matrix_GlobalToCurrent() const; + void PostprocessHelper_CollectMetadata(const X3DNodeElementBase &pNodeElement, std::list<X3DNodeElementBase *> &pList) const; + bool PostprocessHelper_ElementIsMetadata(const X3DElemType pType) const; + bool PostprocessHelper_ElementIsMesh(const X3DElemType pType) const; + void Postprocess_BuildLight(const X3DNodeElementBase &pNodeElement, std::list<aiLight *> &pSceneLightList) const; + void Postprocess_BuildMaterial(const X3DNodeElementBase &pNodeElement, aiMaterial **pMaterial) const; + void Postprocess_BuildMesh(const X3DNodeElementBase &pNodeElement, aiMesh **pMesh) const; + void Postprocess_BuildNode(const X3DNodeElementBase &pNodeElement, aiNode &pSceneNode, std::list<aiMesh *> &pSceneMeshList, + std::list<aiMaterial *> &pSceneMaterialList, std::list<aiLight *> &pSceneLightList) const; + void Postprocess_BuildShape(const X3DNodeElementShape &pShapeNodeElement, std::list<unsigned int> &pNodeMeshInd, + std::list<aiMesh *> &pSceneMeshList, std::list<aiMaterial *> &pSceneMaterialList) const; + void Postprocess_CollectMetadata(const X3DNodeElementBase &pNodeElement, aiNode &pSceneNode) const; + + // rendering + void readColor(XmlNode &node); + void readColorRGBA(XmlNode &node); + void readCoordinate(XmlNode &node); + void readIndexedLineSet(XmlNode &node); + void readIndexedTriangleFanSet(XmlNode &node); + void readIndexedTriangleSet(XmlNode &node); + void readIndexedTriangleStripSet(XmlNode &node); + void readLineSet(XmlNode &node); + void readPointSet(XmlNode &node); + void readTriangleFanSet(XmlNode &node); + void readTriangleSet(XmlNode &node); + void readTriangleStripSet(XmlNode &node); + void readNormal(XmlNode &node); + + // shape + void readShape(XmlNode &node); + void readAppearance(XmlNode &node); + void readMaterial(XmlNode &node); + + // texturing + void readImageTexture(XmlNode &node); + void readTextureCoordinate(XmlNode &node); + void readTextureTransform(XmlNode &node); + + static const aiImporterDesc Description; + X3DNodeElementBase *mNodeElementCur; + aiScene *mScene; + IOSystem *mpIOHandler; +}; // class X3DImporter + +} // namespace Assimp + +#endif // INCLUDED_AI_X3D_IMPORTER_H diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp b/libs/assimp/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp new file mode 100644 index 0000000..8d0f5ba --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp @@ -0,0 +1,467 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 X3DImporter_Geometry2D.cpp +/// \brief Parsing data from nodes of "Geometry2D" set of X3D. +/// date 2015-2016 +/// author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" +#include "X3DGeoHelper.h" + +namespace Assimp { + +// <Arc2D +// DEF="" ID +// USE="" IDREF +// endAngle="1.570796" SFFloat [initializeOnly] +// radius="1" SFFloat [initializeOnly] +// startAngle="0" SFFloat [initializeOnly] +// /> +// The Arc2D node specifies a linear circular arc whose center is at (0,0) and whose angles are measured starting at the positive x-axis and sweeping +// towards the positive y-axis. The radius field specifies the radius of the circle of which the arc is a portion. The arc extends from the startAngle +// counterclockwise to the endAngle. The values of startAngle and endAngle shall be in the range [-2pi, 2pi] radians (or the equivalent if a different +// angle base unit has been specified). If startAngle and endAngle have the same value, a circle is specified. +void X3DImporter::readArc2D(XmlNode &node) { + std::string def, use; + float endAngle = AI_MATH_HALF_PI_F; + float radius = 1; + float startAngle = 0; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "endAngle", endAngle); + XmlParser::getFloatAttribute(node, "radius", radius); + XmlParser::getFloatAttribute(node, "startAngle", startAngle); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Arc2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Arc2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // create point list of geometry object and convert it to line set. + std::list<aiVector3D> tlist; + + X3DGeoHelper::make_arc2D(startAngle, endAngle, radius, 10, tlist); ///TODO: IME - AI_CONFIG for NumSeg + X3DGeoHelper::extend_point_to_line(tlist, ((X3DNodeElementGeometry2D *)ne)->Vertices); + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 2; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Arc2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <ArcClose2D +// DEF="" ID +// USE="" IDREF +// closureType="PIE" SFString [initializeOnly], {"PIE", "CHORD"} +// endAngle="1.570796" SFFloat [initializeOnly] +// radius="1" SFFloat [initializeOnly] +// solid="false" SFBool [initializeOnly] +// startAngle="0" SFFloat [initializeOnly] +// /> +// The ArcClose node specifies a portion of a circle whose center is at (0,0) and whose angles are measured starting at the positive x-axis and sweeping +// towards the positive y-axis. The end points of the arc specified are connected as defined by the closureType field. The radius field specifies the radius +// of the circle of which the arc is a portion. The arc extends from the startAngle counterclockwise to the endAngle. The value of radius shall be greater +// than zero. The values of startAngle and endAngle shall be in the range [-2pi, 2pi] radians (or the equivalent if a different default angle base unit has +// been specified). If startAngle and endAngle have the same value, a circle is specified and closureType is ignored. If the absolute difference between +// startAngle and endAngle is greater than or equal to 2pi, a complete circle is produced with no chord or radial line(s) drawn from the center. +// A closureType of "PIE" connects the end point to the start point by defining two straight line segments first from the end point to the center and then +// the center to the start point. A closureType of "CHORD" connects the end point to the start point by defining a straight line segment from the end point +// to the start point. Textures are applied individually to each face of the ArcClose2D. On the front (+Z) and back (-Z) faces of the ArcClose2D, when +// viewed from the +Z-axis, the texture is mapped onto each face with the same orientation as if the image were displayed normally in 2D. +void X3DImporter::readArcClose2D(XmlNode &node) { + std::string def, use; + std::string closureType("PIE"); + float endAngle = AI_MATH_HALF_PI_F; + float radius = 1; + bool solid = false; + float startAngle = 0; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "closureType", closureType); + XmlParser::getFloatAttribute(node, "endAngle", endAngle); + XmlParser::getFloatAttribute(node, "endAngle", endAngle); + XmlParser::getFloatAttribute(node, "radius", radius); + XmlParser::getBoolAttribute(node, "solid", solid); + XmlParser::getFloatAttribute(node, "startAngle", startAngle); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ArcClose2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_ArcClose2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementGeometry2D *)ne)->Solid = solid; + // create point list of geometry object. + X3DGeoHelper::make_arc2D(startAngle, endAngle, radius, 10, ((X3DNodeElementGeometry2D *)ne)->Vertices); ///TODO: IME - AI_CONFIG for NumSeg + // add chord or two radiuses only if not a circle was defined + if (!((std::fabs(endAngle - startAngle) >= AI_MATH_TWO_PI_F) || (endAngle == startAngle))) { + std::list<aiVector3D> &vlist = ((X3DNodeElementGeometry2D *)ne)->Vertices; // just short alias. + + if ((closureType == "PIE") || (closureType == "\"PIE\"")) + vlist.push_back(aiVector3D(0, 0, 0)); // center point - first radial line + else if ((closureType != "CHORD") && (closureType != "\"CHORD\"")) + Throw_IncorrectAttrValue("ArcClose2D", "closureType"); + + vlist.push_back(*vlist.begin()); // arc first point - chord from first to last point of arc(if CHORD) or second radial line(if PIE). + } + + ((X3DNodeElementGeometry2D *)ne)->NumIndices = ((X3DNodeElementGeometry2D *)ne)->Vertices.size(); + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "ArcClose2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <Circle2D +// DEF="" ID +// USE="" IDREF +// radius="1" SFFloat [initializeOnly] +// /> +void X3DImporter::readCircle2D(XmlNode &node) { + std::string def, use; + float radius = 1; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "radius", radius); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Circle2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Circle2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // create point list of geometry object and convert it to line set. + std::list<aiVector3D> tlist; + + X3DGeoHelper::make_arc2D(0, 0, radius, 10, tlist); ///TODO: IME - AI_CONFIG for NumSeg + X3DGeoHelper::extend_point_to_line(tlist, ((X3DNodeElementGeometry2D *)ne)->Vertices); + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 2; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Circle2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <Disk2D +// DEF="" ID +// USE="" IDREF +// innerRadius="0" SFFloat [initializeOnly] +// outerRadius="1" SFFloat [initializeOnly] +// solid="false" SFBool [initializeOnly] +// /> +// The Disk2D node specifies a circular disk which is centred at (0, 0) in the local coordinate system. The outerRadius field specifies the radius of the +// outer dimension of the Disk2D. The innerRadius field specifies the inner dimension of the Disk2D. The value of outerRadius shall be greater than zero. +// The value of innerRadius shall be greater than or equal to zero and less than or equal to outerRadius. If innerRadius is zero, the Disk2D is completely +// filled. Otherwise, the area within the innerRadius forms a hole in the Disk2D. If innerRadius is equal to outerRadius, a solid circular line shall +// be drawn using the current line properties. Textures are applied individually to each face of the Disk2D. On the front (+Z) and back (-Z) faces of +// the Disk2D, when viewed from the +Z-axis, the texture is mapped onto each face with the same orientation as if the image were displayed normally in 2D. +void X3DImporter::readDisk2D(XmlNode &node) { + std::string def, use; + float innerRadius = 0; + float outerRadius = 1; + bool solid = false; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "innerRadius", innerRadius); + XmlParser::getFloatAttribute(node, "outerRadius", outerRadius); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Disk2D, ne); + } else { + std::list<aiVector3D> tlist_o, tlist_i; + + if (innerRadius > outerRadius) Throw_IncorrectAttrValue("Disk2D", "innerRadius"); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Disk2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // create point list of geometry object. + ///TODO: IME - AI_CONFIG for NumSeg + X3DGeoHelper::make_arc2D(0, 0, outerRadius, 10, tlist_o); // outer circle + if (innerRadius == 0.0f) { // make filled disk + // in tlist_o we already have points of circle. just copy it and sign as polygon. + ((X3DNodeElementGeometry2D *)ne)->Vertices = tlist_o; + ((X3DNodeElementGeometry2D *)ne)->NumIndices = tlist_o.size(); + } else if (innerRadius == outerRadius) { // make circle + // in tlist_o we already have points of circle. convert it to line set. + X3DGeoHelper::extend_point_to_line(tlist_o, ((X3DNodeElementGeometry2D *)ne)->Vertices); + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 2; + } else { // make disk + std::list<aiVector3D> &vlist = ((X3DNodeElementGeometry2D *)ne)->Vertices; // just short alias. + + X3DGeoHelper::make_arc2D(0, 0, innerRadius, 10, tlist_i); // inner circle + // + // create quad list from two point lists + // + if (tlist_i.size() < 2) throw DeadlyImportError("Disk2D. Not enough points for creating quad list."); // tlist_i and tlist_o has equal size. + + // add all quads except last + for (std::list<aiVector3D>::iterator it_i = tlist_i.begin(), it_o = tlist_o.begin(); it_i != tlist_i.end();) { + // do not forget - CCW direction + vlist.push_back(*it_i++); // 1st point + vlist.push_back(*it_o++); // 2nd point + vlist.push_back(*it_o); // 3rd point + vlist.push_back(*it_i); // 4th point + } + + // add last quad + vlist.push_back(*tlist_i.end()); // 1st point + vlist.push_back(*tlist_o.end()); // 2nd point + vlist.push_back(*tlist_o.begin()); // 3rd point + vlist.push_back(*tlist_o.begin()); // 4th point + + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 4; + } + + ((X3DNodeElementGeometry2D *)ne)->Solid = solid; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Disk2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <Polyline2D +// DEF="" ID +// USE="" IDREF +// lineSegments="" MFVec2F [intializeOnly] +// /> +void X3DImporter::readPolyline2D(XmlNode &node) { + std::string def, use; + std::list<aiVector2D> lineSegments; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector2DListAttribute(node, "lineSegments", lineSegments); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Polyline2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Polyline2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // + // convert read point list of geometry object to line set. + // + std::list<aiVector3D> tlist; + + // convert vec2 to vec3 + for (std::list<aiVector2D>::iterator it2 = lineSegments.begin(); it2 != lineSegments.end(); ++it2) + tlist.push_back(aiVector3D(it2->x, it2->y, 0)); + + // convert point set to line set + X3DGeoHelper::extend_point_to_line(tlist, ((X3DNodeElementGeometry2D *)ne)->Vertices); + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 2; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Polyline2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <Polypoint2D +// DEF="" ID +// USE="" IDREF +// point="" MFVec2F [inputOutput] +// /> +void X3DImporter::readPolypoint2D(XmlNode &node) { + std::string def, use; + std::list<aiVector2D> point; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector2DListAttribute(node, "point", point); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Polypoint2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Polypoint2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // convert vec2 to vec3 + for (std::list<aiVector2D>::iterator it2 = point.begin(); it2 != point.end(); ++it2) { + ((X3DNodeElementGeometry2D *)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0)); + } + + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 1; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Polypoint2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <Rectangle2D +// DEF="" ID +// USE="" IDREF +// size="2 2" SFVec2f [initializeOnly] +// solid="false" SFBool [initializeOnly] +// /> +void X3DImporter::readRectangle2D(XmlNode &node) { + std::string def, use; + aiVector2D size(2, 2); + bool solid = false; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector2DAttribute(node, "size", size); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Rectangle2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Rectangle2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + float x1 = -size.x / 2.0f; + float x2 = size.x / 2.0f; + float y1 = -size.y / 2.0f; + float y2 = size.y / 2.0f; + std::list<aiVector3D> &vlist = ((X3DNodeElementGeometry2D *)ne)->Vertices; // just short alias. + + vlist.push_back(aiVector3D(x2, y1, 0)); // 1st point + vlist.push_back(aiVector3D(x2, y2, 0)); // 2nd point + vlist.push_back(aiVector3D(x1, y2, 0)); // 3rd point + vlist.push_back(aiVector3D(x1, y1, 0)); // 4th point + ((X3DNodeElementGeometry2D *)ne)->Solid = solid; + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 4; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Rectangle2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <TriangleSet2D +// DEF="" ID +// USE="" IDREF +// solid="false" SFBool [initializeOnly] +// vertices="" MFVec2F [inputOutput] +// /> +void X3DImporter::readTriangleSet2D(XmlNode &node) { + std::string def, use; + bool solid = false; + std::list<aiVector2D> vertices; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector2DListAttribute(node, "vertices", vertices); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleSet2D, ne); + } else { + if (vertices.size() % 3) throw DeadlyImportError("TriangleSet2D. Not enough points for defining triangle."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_TriangleSet2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // convert vec2 to vec3 + for (std::list<aiVector2D>::iterator it2 = vertices.begin(); it2 != vertices.end(); ++it2) { + ((X3DNodeElementGeometry2D *)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0)); + } + + ((X3DNodeElementGeometry2D *)ne)->Solid = solid; + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 3; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "TriangleSet2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp b/libs/assimp/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp new file mode 100644 index 0000000..b9fc2a4 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp @@ -0,0 +1,918 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 X3DImporter_Geometry3D.cpp +/// \brief Parsing data from nodes of "Geometry3D" set of X3D. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DGeoHelper.h" +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" + +// Header files, Assimp. +#include <assimp/StandardShapes.h> + +namespace Assimp { + +// <Box +// DEF="" ID +// USE="" IDREF +// size="2 2 2" SFVec3f [initializeOnly] +// solid="true" SFBool [initializeOnly] +// /> +// The Box node specifies a rectangular parallelepiped box centred at (0, 0, 0) in the local coordinate system and aligned with the local coordinate axes. +// By default, the box measures 2 units in each dimension, from -1 to +1. The size field specifies the extents of the box along the X-, Y-, and Z-axes +// respectively and each component value shall be greater than zero. +void X3DImporter::readBox(XmlNode &node) { + std::string def, use; + bool solid = true; + aiVector3D size(2, 2, 2); + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector3DAttribute(node, "size", size); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Box, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Box, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DGeoHelper::rect_parallel_epiped(size, ((X3DNodeElementGeometry3D *)ne)->Vertices); // get quad list + ((X3DNodeElementGeometry3D *)ne)->Solid = solid; + ((X3DNodeElementGeometry3D *)ne)->NumIndices = 4; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Box"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <Cone +// DEF="" ID +// USE="" IDREF +// bottom="true" SFBool [initializeOnly] +// bottomRadius="1" SFloat [initializeOnly] +// height="2" SFloat [initializeOnly] +// side="true" SFBool [initializeOnly] +// solid="true" SFBool [initializeOnly] +// /> +void X3DImporter::readCone(XmlNode &node) { + std::string use, def; + bool bottom = true; + float bottomRadius = 1; + float height = 2; + bool side = true; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "solid", solid); + XmlParser::getBoolAttribute(node, "side", side); + XmlParser::getBoolAttribute(node, "bottom", bottom); + XmlParser::getFloatAttribute(node, "height", height); + XmlParser::getFloatAttribute(node, "bottomRadius", bottomRadius); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Cone, ne); + } else { + const unsigned int tess = 30; ///TODO: IME tessellation factor through ai_property + + std::vector<aiVector3D> tvec; // temp array for vertices. + + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Cone, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // make cone or parts according to flags. + if (side) { + StandardShapes::MakeCone(height, 0, bottomRadius, tess, tvec, !bottom); + } else if (bottom) { + StandardShapes::MakeCircle(bottomRadius, tess, tvec); + height = -(height / 2); + for (std::vector<aiVector3D>::iterator it = tvec.begin(); it != tvec.end(); ++it) + it->y = height; // y - because circle made in oXZ. + } + + // copy data from temp array + for (std::vector<aiVector3D>::iterator it = tvec.begin(); it != tvec.end(); ++it) + ((X3DNodeElementGeometry3D *)ne)->Vertices.push_back(*it); + + ((X3DNodeElementGeometry3D *)ne)->Solid = solid; + ((X3DNodeElementGeometry3D *)ne)->NumIndices = 3; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Cone"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <Cylinder +// DEF="" ID +// USE="" IDREF +// bottom="true" SFBool [initializeOnly] +// height="2" SFloat [initializeOnly] +// radius="1" SFloat [initializeOnly] +// side="true" SFBool [initializeOnly] +// solid="true" SFBool [initializeOnly] +// top="true" SFBool [initializeOnly] +// /> +void X3DImporter::readCylinder(XmlNode &node) { + std::string use, def; + bool bottom = true; + float height = 2; + float radius = 1; + bool side = true; + bool solid = true; + bool top = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "radius", radius); + XmlParser::getBoolAttribute(node, "solid", solid); + XmlParser::getBoolAttribute(node, "bottom", bottom); + XmlParser::getBoolAttribute(node, "top", top); + XmlParser::getBoolAttribute(node, "side", side); + XmlParser::getFloatAttribute(node, "height", height); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Cylinder, ne); + } else { + const unsigned int tess = 30; ///TODO: IME tessellation factor through ai_property + + std::vector<aiVector3D> tside; // temp array for vertices of side. + std::vector<aiVector3D> tcir; // temp array for vertices of circle. + + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Cylinder, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // make cilynder or parts according to flags. + if (side) StandardShapes::MakeCone(height, radius, radius, tess, tside, true); + + height /= 2; // height defined for whole cylinder, when creating top and bottom circle we are using just half of height. + if (top || bottom) StandardShapes::MakeCircle(radius, tess, tcir); + // copy data from temp arrays + std::list<aiVector3D> &vlist = ((X3DNodeElementGeometry3D *)ne)->Vertices; // just short alias. + + for (std::vector<aiVector3D>::iterator it = tside.begin(); it != tside.end(); ++it) + vlist.push_back(*it); + + if (top) { + for (std::vector<aiVector3D>::iterator it = tcir.begin(); it != tcir.end(); ++it) { + (*it).y = height; // y - because circle made in oXZ. + vlist.push_back(*it); + } + } // if(top) + + if (bottom) { + for (std::vector<aiVector3D>::iterator it = tcir.begin(); it != tcir.end(); ++it) { + (*it).y = -height; // y - because circle made in oXZ. + vlist.push_back(*it); + } + } // if(top) + + ((X3DNodeElementGeometry3D *)ne)->Solid = solid; + ((X3DNodeElementGeometry3D *)ne)->NumIndices = 3; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Cylinder"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <ElevationGrid +// DEF="" ID +// USE="" IDREF +// ccw="true" SFBool [initializeOnly] +// colorPerVertex="true" SFBool [initializeOnly] +// creaseAngle="0" SFloat [initializeOnly] +// height="" MFloat [initializeOnly] +// normalPerVertex="true" SFBool [initializeOnly] +// solid="true" SFBool [initializeOnly] +// xDimension="0" SFInt32 [initializeOnly] +// xSpacing="1.0" SFloat [initializeOnly] +// zDimension="0" SFInt32 [initializeOnly] +// zSpacing="1.0" SFloat [initializeOnly] +// > +// <!-- ColorNormalTexCoordContentModel --> +// ColorNormalTexCoordContentModel can contain Color (or ColorRGBA), Normal and TextureCoordinate, in any order. No more than one instance of any single +// node type is allowed. A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </ElevationGrid> +// The ElevationGrid node specifies a uniform rectangular grid of varying height in the Y=0 plane of the local coordinate system. The geometry is described +// by a scalar array of height values that specify the height of a surface above each point of the grid. The xDimension and zDimension fields indicate +// the number of elements of the grid height array in the X and Z directions. Both xDimension and zDimension shall be greater than or equal to zero. +// If either the xDimension or the zDimension is less than two, the ElevationGrid contains no quadrilaterals. +void X3DImporter::readElevationGrid(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + float creaseAngle = 0; + std::vector<float> height; + bool normalPerVertex = true; + bool solid = true; + int32_t xDimension = 0; + float xSpacing = 1; + int32_t zDimension = 0; + float zSpacing = 1; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "solid", solid); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getFloatAttribute(node, "creaseAngle", creaseAngle); + X3DXmlHelper::getFloatArrayAttribute(node, "height", height); + XmlParser::getIntAttribute(node, "xDimension", xDimension); + XmlParser::getFloatAttribute(node, "xSpacing", xSpacing); + XmlParser::getIntAttribute(node, "zDimension", zDimension); + XmlParser::getFloatAttribute(node, "zSpacing", zSpacing); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ElevationGrid, ne); + } else { + if ((xSpacing == 0.0f) || (zSpacing == 0.0f)) throw DeadlyImportError("Spacing in <ElevationGrid> must be grater than zero."); + if ((xDimension <= 0) || (zDimension <= 0)) throw DeadlyImportError("Dimension in <ElevationGrid> must be grater than zero."); + if ((size_t)(xDimension * zDimension) != height.size()) DeadlyImportError("Heights count must be equal to \"xDimension * zDimension\" in <ElevationGrid>"); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementElevationGrid(X3DElemType::ENET_ElevationGrid, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementElevationGrid &grid_alias = *((X3DNodeElementElevationGrid *)ne); // create alias for conveience + + { // create grid vertices list + std::vector<float>::const_iterator he_it = height.begin(); + + for (int32_t zi = 0; zi < zDimension; zi++) // rows + { + for (int32_t xi = 0; xi < xDimension; xi++) // columns + { + aiVector3D tvec(xSpacing * xi, *he_it, zSpacing * zi); + + grid_alias.Vertices.push_back(tvec); + ++he_it; + } + } + } // END: create grid vertices list + // + // create faces list. In "coordIdx" format + // + // check if we have quads + if ((xDimension < 2) || (zDimension < 2)) // only one element in dimension is set, create line set. + { + ((X3DNodeElementElevationGrid *)ne)->NumIndices = 2; // will be holded as line set. + for (size_t i = 0, i_e = (grid_alias.Vertices.size() - 1); i < i_e; i++) { + grid_alias.CoordIdx.push_back(static_cast<int32_t>(i)); + grid_alias.CoordIdx.push_back(static_cast<int32_t>(i + 1)); + grid_alias.CoordIdx.push_back(-1); + } + } else // two or more elements in every dimension is set. create quad set. + { + ((X3DNodeElementElevationGrid *)ne)->NumIndices = 4; + for (int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++) // rows + { + for (int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++) // columns + { + // points direction in face. + if (ccw) { + // CCW: + // 3 2 + // 0 1 + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi); + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back(fzi * xDimension + fxi); + } else { + // CW: + // 0 1 + // 3 2 + grid_alias.CoordIdx.push_back(fzi * xDimension + fxi); + grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi); + } // if(ccw) else + + grid_alias.CoordIdx.push_back(-1); + } // for(int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++) + } // for(int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++) + } // if((xDimension < 2) || (zDimension < 2)) else + + grid_alias.ColorPerVertex = colorPerVertex; + grid_alias.NormalPerVertex = normalPerVertex; + grid_alias.CreaseAngle = creaseAngle; + grid_alias.Solid = solid; + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("ElevationGrid", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!mReader->isEmptyElement()) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } // if(!mReader->isEmptyElement()) else + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +template <typename TVector> +static void GeometryHelper_Extrusion_CurveIsClosed(std::vector<TVector> &pCurve, const bool pDropTail, const bool pRemoveLastPoint, bool &pCurveIsClosed) { + size_t cur_sz = pCurve.size(); + + pCurveIsClosed = false; + // for curve with less than four points checking is have no sense, + if (cur_sz < 4) return; + + for (size_t s = 3, s_e = cur_sz; s < s_e; s++) { + // search for first point of duplicated part. + if (pCurve[0] == pCurve[s]) { + bool found = true; + + // check if tail(indexed by b2) is duplicate of head(indexed by b1). + for (size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++) { + if (pCurve[b1] != pCurve[b2]) { // points not match: clear flag and break loop. + found = false; + + break; + } + } // for(size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++) + + // if duplicate tail is found then drop or not it depending on flags. + if (found) { + pCurveIsClosed = true; + if (pDropTail) { + if (!pRemoveLastPoint) s++; // prepare value for iterator's arithmetics. + + pCurve.erase(pCurve.begin() + s, pCurve.end()); // remove tail + } + + break; + } // if(found) + } // if(pCurve[0] == pCurve[s]) + } // for(size_t s = 3, s_e = (cur_sz - 1); s < s_e; s++) +} + +static aiVector3D GeometryHelper_Extrusion_GetNextY(const size_t pSpine_PointIdx, const std::vector<aiVector3D> &pSpine, const bool pSpine_Closed) { + const size_t spine_idx_last = pSpine.size() - 1; + aiVector3D tvec; + + if ((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) // at first special cases + { + if (pSpine_Closed) { // If the spine curve is closed: The SCP for the first and last points is the same and is found using (spine[1] - spine[n - 2]) to compute the Y-axis. + // As we even for closed spine curve last and first point in pSpine are not the same: duplicates(spine[n - 1] which are equivalent to spine[0]) + // in tail are removed. + // So, last point in pSpine is a spine[n - 2] + tvec = pSpine[1] - pSpine[spine_idx_last]; + } else if (pSpine_PointIdx == 0) { // The Y-axis used for the first point is the vector from spine[0] to spine[1] + tvec = pSpine[1] - pSpine[0]; + } else { // The Y-axis used for the last point it is the vector from spine[n-2] to spine[n-1]. In our case(see above about dropping tail) spine[n - 1] is + // the spine[0]. + tvec = pSpine[spine_idx_last] - pSpine[spine_idx_last - 1]; + } + } // if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) + else { // For all points other than the first or last: The Y-axis for spine[i] is found by normalizing the vector defined by (spine[i+1] - spine[i-1]). + tvec = pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx - 1]; + } // if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) else + + return tvec.Normalize(); +} + +static aiVector3D GeometryHelper_Extrusion_GetNextZ(const size_t pSpine_PointIdx, const std::vector<aiVector3D> &pSpine, const bool pSpine_Closed, + const aiVector3D pVecZ_Prev) { + const aiVector3D zero_vec(0); + const size_t spine_idx_last = pSpine.size() - 1; + + aiVector3D tvec; + + // at first special cases + if (pSpine.size() < 3) // spine have not enough points for vector calculations. + { + tvec.Set(0, 0, 1); + } else if (pSpine_PointIdx == 0) // special case: first point + { + if (pSpine_Closed) // for calculating use previous point in curve s[n - 2]. In list it's a last point, because point s[n - 1] was removed as duplicate. + { + tvec = (pSpine[1] - pSpine[0]) ^ (pSpine[spine_idx_last] - pSpine[0]); + } else // for not closed curve first and next point(s[0] and s[1]) has the same vector Z. + { + bool found = false; + + // As said: "If the Z-axis of the first point is undefined (because the spine is not closed and the first two spine segments are collinear) + // then the Z-axis for the first spine point with a defined Z-axis is used." + // Walk through spine and find Z. + for (size_t next_point = 2; (next_point <= spine_idx_last) && !found; next_point++) { + // (pSpine[2] - pSpine[1]) ^ (pSpine[0] - pSpine[1]) + tvec = (pSpine[next_point] - pSpine[next_point - 1]) ^ (pSpine[next_point - 2] - pSpine[next_point - 1]); + found = !tvec.Equal(zero_vec); + } + + // if entire spine are collinear then use OZ axis. + if (!found) tvec.Set(0, 0, 1); + } // if(pSpine_Closed) else + } // else if(pSpine_PointIdx == 0) + else if (pSpine_PointIdx == spine_idx_last) // special case: last point + { + if (pSpine_Closed) { // do not forget that real last point s[n - 1] is removed as duplicated. And in this case we are calculating vector Z for point s[n - 2]. + tvec = (pSpine[0] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]); + // if taken spine vectors are collinear then use previous vector Z. + if (tvec.Equal(zero_vec)) tvec = pVecZ_Prev; + } else { // vector Z for last point of not closed curve is previous vector Z. + tvec = pVecZ_Prev; + } + } else // regular point + { + tvec = (pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]); + // if taken spine vectors are collinear then use previous vector Z. + if (tvec.Equal(zero_vec)) tvec = pVecZ_Prev; + } + + // After determining the Z-axis, its dot product with the Z-axis of the previous spine point is computed. If this value is negative, the Z-axis + // is flipped (multiplied by -1). + if ((tvec * pVecZ_Prev) < 0) tvec = -tvec; + + return tvec.Normalize(); +} + +// <Extrusion +// DEF="" ID +// USE="" IDREF +// beginCap="true" SFBool [initializeOnly] +// ccw="true" SFBool [initializeOnly] +// convex="true" SFBool [initializeOnly] +// creaseAngle="0.0" SFloat [initializeOnly] +// crossSection="1 1 1 -1 -1 -1 -1 1 1 1" MFVec2f [initializeOnly] +// endCap="true" SFBool [initializeOnly] +// orientation="0 0 1 0" MFRotation [initializeOnly] +// scale="1 1" MFVec2f [initializeOnly] +// solid="true" SFBool [initializeOnly] +// spine="0 0 0 0 1 0" MFVec3f [initializeOnly] +// /> +void X3DImporter::readExtrusion(XmlNode &node) { + std::string use, def; + bool beginCap = true; + bool ccw = true; + bool convex = true; + float creaseAngle = 0; + std::vector<aiVector2D> crossSection; + bool endCap = true; + std::vector<float> orientation; + std::vector<aiVector2D> scale; + bool solid = true; + std::vector<aiVector3D> spine; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "beginCap", beginCap); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "convex", convex); + XmlParser::getFloatAttribute(node, "creaseAngle", creaseAngle); + X3DXmlHelper::getVector2DArrayAttribute(node, "crossSection", crossSection); + XmlParser::getBoolAttribute(node, "endCap", endCap); + X3DXmlHelper::getFloatArrayAttribute(node, "orientation", orientation); + X3DXmlHelper::getVector2DArrayAttribute(node, "scale", scale); + XmlParser::getBoolAttribute(node, "solid", solid); + X3DXmlHelper::getVector3DArrayAttribute(node, "spine", spine); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Extrusion, ne); + } else { + // + // check if default values must be assigned + // + if (spine.size() == 0) { + spine.resize(2); + spine[0].Set(0, 0, 0), spine[1].Set(0, 1, 0); + } else if (spine.size() == 1) { + throw DeadlyImportError("ParseNode_Geometry3D_Extrusion. Spine must have at least two points."); + } + + if (crossSection.size() == 0) { + crossSection.resize(5); + crossSection[0].Set(1, 1), crossSection[1].Set(1, -1), crossSection[2].Set(-1, -1), crossSection[3].Set(-1, 1), crossSection[4].Set(1, 1); + } + + { // orientation + size_t ori_size = orientation.size() / 4; + + if (ori_size < spine.size()) { + float add_ori[4]; // values that will be added + + if (ori_size == 1) // if "orientation" has one element(means one MFRotation with four components) then use it value for all spine points. + { + add_ori[0] = orientation[0], add_ori[1] = orientation[1], add_ori[2] = orientation[2], add_ori[3] = orientation[3]; + } else // else - use default values + { + add_ori[0] = 0, add_ori[1] = 0, add_ori[2] = 1, add_ori[3] = 0; + } + + orientation.reserve(spine.size() * 4); + for (size_t i = 0, i_e = (spine.size() - ori_size); i < i_e; i++) + orientation.push_back(add_ori[0]), orientation.push_back(add_ori[1]), orientation.push_back(add_ori[2]), orientation.push_back(add_ori[3]); + } + + if (orientation.size() % 4) throw DeadlyImportError("Attribute \"orientation\" in <Extrusion> must has multiple four quantity of numbers."); + } // END: orientation + + { // scale + if (scale.size() < spine.size()) { + aiVector2D add_sc; + + if (scale.size() == 1) // if "scale" has one element then use it value for all spine points. + add_sc = scale[0]; + else // else - use default values + add_sc.Set(1, 1); + + scale.reserve(spine.size()); + for (size_t i = 0, i_e = (spine.size() - scale.size()); i < i_e; i++) + scale.push_back(add_sc); + } + } // END: scale + // + // create and if needed - define new geometry object. + // + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_Extrusion, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementIndexedSet &ext_alias = *((X3DNodeElementIndexedSet *)ne); // create alias for conveience + // assign part of input data + ext_alias.CCW = ccw; + ext_alias.Convex = convex; + ext_alias.CreaseAngle = creaseAngle; + ext_alias.Solid = solid; + + // + // How we done it at all? + // 1. At first we will calculate array of basises for every point in spine(look SCP in ISO-dic). Also "orientation" vector + // are applied vor every basis. + // 2. After that we can create array of point sets: which are scaled, transferred to basis of relative basis and at final translated to real position + // using relative spine point. + // 3. Next step is creating CoordIdx array(do not forget "-1" delimiter). While creating CoordIdx also created faces for begin and end caps, if + // needed. While createing CootdIdx is taking in account CCW flag. + // 4. The last step: create Vertices list. + // + bool spine_closed; // flag: true if spine curve is closed. + bool cross_closed; // flag: true if cross curve is closed. + std::vector<aiMatrix3x3> basis_arr; // array of basises. ROW_a - X, ROW_b - Y, ROW_c - Z. + std::vector<std::vector<aiVector3D>> pointset_arr; // array of point sets: cross curves. + + // detect closed curves + GeometryHelper_Extrusion_CurveIsClosed(crossSection, true, true, cross_closed); // true - drop tail, true - remove duplicate end. + GeometryHelper_Extrusion_CurveIsClosed(spine, true, true, spine_closed); // true - drop tail, true - remove duplicate end. + // If both cap are requested and spine curve is closed then we can make only one cap. Because second cap will be the same surface. + if (spine_closed) { + beginCap |= endCap; + endCap = false; + } + + { // 1. Calculate array of basises. + aiMatrix4x4 rotmat; + aiVector3D vecX(0), vecY(0), vecZ(0); + + basis_arr.resize(spine.size()); + for (size_t i = 0, i_e = spine.size(); i < i_e; i++) { + aiVector3D tvec; + + // get axises of basis. + vecY = GeometryHelper_Extrusion_GetNextY(i, spine, spine_closed); + vecZ = GeometryHelper_Extrusion_GetNextZ(i, spine, spine_closed, vecZ); + vecX = (vecY ^ vecZ).Normalize(); + // get rotation matrix and apply "orientation" to basis + aiMatrix4x4::Rotation(orientation[i * 4 + 3], aiVector3D(orientation[i * 4], orientation[i * 4 + 1], orientation[i * 4 + 2]), rotmat); + tvec = vecX, tvec *= rotmat, basis_arr[i].a1 = tvec.x, basis_arr[i].a2 = tvec.y, basis_arr[i].a3 = tvec.z; + tvec = vecY, tvec *= rotmat, basis_arr[i].b1 = tvec.x, basis_arr[i].b2 = tvec.y, basis_arr[i].b3 = tvec.z; + tvec = vecZ, tvec *= rotmat, basis_arr[i].c1 = tvec.x, basis_arr[i].c2 = tvec.y, basis_arr[i].c3 = tvec.z; + } // for(size_t i = 0, i_e = spine.size(); i < i_e; i++) + } // END: 1. Calculate array of basises + + { // 2. Create array of point sets. + aiMatrix4x4 scmat; + std::vector<aiVector3D> tcross(crossSection.size()); + + pointset_arr.resize(spine.size()); + for (size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++) { + aiVector3D tc23vec; + + tc23vec.Set(scale[spi].x, 0, scale[spi].y); + aiMatrix4x4::Scaling(tc23vec, scmat); + for (size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++) { + aiVector3D tvecX, tvecY, tvecZ; + + tc23vec.Set(crossSection[cri].x, 0, crossSection[cri].y); + // apply scaling to point + tcross[cri] = scmat * tc23vec; + // + // transfer point to new basis + // calculate coordinate in new basis + tvecX.Set(basis_arr[spi].a1, basis_arr[spi].a2, basis_arr[spi].a3), tvecX *= tcross[cri].x; + tvecY.Set(basis_arr[spi].b1, basis_arr[spi].b2, basis_arr[spi].b3), tvecY *= tcross[cri].y; + tvecZ.Set(basis_arr[spi].c1, basis_arr[spi].c2, basis_arr[spi].c3), tvecZ *= tcross[cri].z; + // apply new coordinates and translate it to spine point. + tcross[cri] = tvecX + tvecY + tvecZ + spine[spi]; + } // for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; i++) + + pointset_arr[spi] = tcross; // store transferred point set + } // for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; i++) + } // END: 2. Create array of point sets. + + { // 3. Create CoordIdx. + // add caps if needed + if (beginCap) { + // add cap as polygon. vertices of cap are places at begin, so just add numbers from zero. + for (size_t i = 0, i_e = crossSection.size(); i < i_e; i++) + ext_alias.CoordIndex.push_back(static_cast<int32_t>(i)); + + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } // if(beginCap) + + if (endCap) { + // add cap as polygon. vertices of cap are places at end, as for beginCap use just sequence of numbers but with offset. + size_t beg = (pointset_arr.size() - 1) * crossSection.size(); + + for (size_t i = beg, i_e = (beg + crossSection.size()); i < i_e; i++) + ext_alias.CoordIndex.push_back(static_cast<int32_t>(i)); + + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } // if(beginCap) + + // add quads + for (size_t spi = 0, spi_e = (spine.size() - 1); spi <= spi_e; spi++) { + const size_t cr_sz = crossSection.size(); + const size_t cr_last = crossSection.size() - 1; + + size_t right_col; // hold index basis for points of quad placed in right column; + + if (spi != spi_e) + right_col = spi + 1; + else if (spine_closed) // if spine curve is closed then one more quad is needed: between first and last points of curve. + right_col = 0; + else + break; // if spine curve is not closed then break the loop, because spi is out of range for that type of spine. + + for (size_t cri = 0; cri < cr_sz; cri++) { + if (cri != cr_last) { + MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex, + static_cast<int32_t>(spi * cr_sz + cri), + static_cast<int32_t>(right_col * cr_sz + cri), + static_cast<int32_t>(right_col * cr_sz + cri + 1), + static_cast<int32_t>(spi * cr_sz + cri + 1)); + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } else if (cross_closed) // if cross curve is closed then one more quad is needed: between first and last points of curve. + { + MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex, + static_cast<int32_t>(spi * cr_sz + cri), + static_cast<int32_t>(right_col * cr_sz + cri), + static_cast<int32_t>(right_col * cr_sz + 0), + static_cast<int32_t>(spi * cr_sz + 0)); + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } + } // for(size_t cri = 0; cri < cr_sz; cri++) + } // for(size_t spi = 0, spi_e = (spine.size() - 2); spi < spi_e; spi++) + } // END: 3. Create CoordIdx. + + { // 4. Create vertices list. + // just copy all vertices + for (size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++) { + for (size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++) { + ext_alias.Vertices.emplace_back(pointset_arr[spi][cri]); + } + } + } // END: 4. Create vertices list. + //PrintVectorSet("Ext. CoordIdx", ext_alias.CoordIndex); + //PrintVectorSet("Ext. Vertices", ext_alias.Vertices); + // check for child nodes + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Extrusion"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <IndexedFaceSet +// DEF="" ID +// USE="" IDREF +// ccw="true" SFBool [initializeOnly] +// colorIndex="" MFInt32 [initializeOnly] +// colorPerVertex="true" SFBool [initializeOnly] +// convex="true" SFBool [initializeOnly] +// coordIndex="" MFInt32 [initializeOnly] +// creaseAngle="0" SFFloat [initializeOnly] +// normalIndex="" MFInt32 [initializeOnly] +// normalPerVertex="true" SFBool [initializeOnly] +// solid="true" SFBool [initializeOnly] +// texCoordIndex="" MFInt32 [initializeOnly] +// > +// <!-- ComposedGeometryContentModel --> +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </IndexedFaceSet> +void X3DImporter::readIndexedFaceSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + std::vector<int32_t> colorIndex; + bool colorPerVertex = true; + bool convex = true; + std::vector<int32_t> coordIndex; + float creaseAngle = 0; + std::vector<int32_t> normalIndex; + bool normalPerVertex = true; + bool solid = true; + std::vector<int32_t> texCoordIndex; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + X3DXmlHelper::getInt32ArrayAttribute(node, "colorIndex", colorIndex); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + XmlParser::getBoolAttribute(node, "convex", convex); + X3DXmlHelper::getInt32ArrayAttribute(node, "coordIndex", coordIndex); + XmlParser::getFloatAttribute(node, "creaseAngle", creaseAngle); + X3DXmlHelper::getInt32ArrayAttribute(node, "normalIndex", normalIndex); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + X3DXmlHelper::getInt32ArrayAttribute(node, "texCoordIndex", texCoordIndex); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedFaceSet, ne); + } else { + // check data + if (coordIndex.size() == 0) throw DeadlyImportError("IndexedFaceSet must contain not empty \"coordIndex\" attribute."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_IndexedFaceSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementIndexedSet &ne_alias = *((X3DNodeElementIndexedSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorIndex = colorIndex; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.Convex = convex; + ne_alias.CoordIndex = coordIndex; + ne_alias.CreaseAngle = creaseAngle; + ne_alias.NormalIndex = normalIndex; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + ne_alias.TexCoordIndex = texCoordIndex; + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("IndexedFaceSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <Sphere +// DEF="" ID +// USE="" IDREF +// radius="1" SFloat [initializeOnly] +// solid="true" SFBool [initializeOnly] +// /> +void X3DImporter::readSphere(XmlNode &node) { + std::string use, def; + ai_real radius = 1; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getRealAttribute(node, "radius", radius); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Sphere, ne); + } else { + const unsigned int tess = 3; ///TODO: IME tessellation factor through ai_property + + std::vector<aiVector3D> tlist; + + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Sphere, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + StandardShapes::MakeSphere(tess, tlist); + // copy data from temp array and apply scale + for (std::vector<aiVector3D>::iterator it = tlist.begin(); it != tlist.end(); ++it) { + aiVector3D v = *it; + ((X3DNodeElementGeometry3D *)ne)->Vertices.emplace_back(v * radius); + } + + ((X3DNodeElementGeometry3D *)ne)->Solid = solid; + ((X3DNodeElementGeometry3D *)ne)->NumIndices = 3; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Sphere"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter_Group.cpp b/libs/assimp/code/AssetLib/X3D/X3DImporter_Group.cpp new file mode 100644 index 0000000..e7a2e91 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter_Group.cpp @@ -0,0 +1,277 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 X3DImporter_Group.cpp +/// \brief Parsing data from nodes of "Grouping" set of X3D. +/// date 2015-2016 +/// author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" + +namespace Assimp { + +// <Group +// DEF="" ID +// USE="" IDREF +// bboxCenter="0 0 0" SFVec3f [initializeOnly] +// bboxSize="-1 -1 -1" SFVec3f [initializeOnly] +// > +// <\!-- ChildContentModel --> +// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, +// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the +// precise palette of legal nodes that are available depends on assigned profile and components. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </Group> +// A Group node contains children nodes without introducing a new transformation. It is equivalent to a Transform node containing an identity transform. +void X3DImporter::startReadGroup(XmlNode &node) { + std::string def, use; + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + X3DNodeElementBase *ne(nullptr); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + } else { + ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) mNodeElementCur->ID = def; + // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in <Scene> parser function. + + // for empty element exit from node in that place + if (isNodeEmpty(node)) ParseHelper_Node_Exit(); + } // if(!use.empty()) else +} + +void X3DImporter::endReadGroup() { + ParseHelper_Node_Exit(); // go up in scene graph +} + +// <StaticGroup +// DEF="" ID +// USE="" IDREF +// bboxCenter="0 0 0" SFVec3f [initializeOnly] +// bboxSize="-1 -1 -1" SFVec3f [initializeOnly] +// > +// <\!-- ChildContentModel --> +// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, +// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the +// precise palette of legal nodes that are available depends on assigned profile and components. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </StaticGroup> +// The StaticGroup node contains children nodes which cannot be modified. StaticGroup children are guaranteed to not change, send events, receive events or +// contain any USE references outside the StaticGroup. +void X3DImporter::startReadStaticGroup(XmlNode &node) { + std::string def, use; + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + X3DNodeElementBase *ne(nullptr); + + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + } else { + ParseHelper_Group_Begin(true); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) mNodeElementCur->ID = def; + // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in <Scene> parser function. + + // for empty element exit from node in that place + if (isNodeEmpty(node)) ParseHelper_Node_Exit(); + } // if(!use.empty()) else +} + +void X3DImporter::endReadStaticGroup() { + ParseHelper_Node_Exit(); // go up in scene graph +} + +// <Switch +// DEF="" ID +// USE="" IDREF +// bboxCenter="0 0 0" SFVec3f [initializeOnly] +// bboxSize="-1 -1 -1" SFVec3f [initializeOnly] +// whichChoice="-1" SFInt32 [inputOutput] +// > +// <\!-- ChildContentModel --> +// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, +// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the +// precise palette of legal nodes that are available depends on assigned profile and components. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </Switch> +// The Switch grouping node traverses zero or one of the nodes specified in the children field. The whichChoice field specifies the index of the child +// to traverse, with the first child having index 0. If whichChoice is less than zero or greater than the number of nodes in the children field, nothing +// is chosen. +void X3DImporter::startReadSwitch(XmlNode &node) { + std::string def, use; + int32_t whichChoice = -1; + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getIntAttribute(node, "whichChoice", whichChoice); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + X3DNodeElementBase *ne(nullptr); + + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + } else { + ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) mNodeElementCur->ID = def; + + // also set values specific to this type of group + ((X3DNodeElementGroup *)mNodeElementCur)->UseChoice = true; + ((X3DNodeElementGroup *)mNodeElementCur)->Choice = whichChoice; + // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in <Scene> parser function. + + // for empty element exit from node in that place + if (isNodeEmpty(node)) ParseHelper_Node_Exit(); + } // if(!use.empty()) else +} + +void X3DImporter::endReadSwitch() { + // just exit from node. Defined choice will be accepted at postprocessing stage. + ParseHelper_Node_Exit(); // go up in scene graph +} + +// <Transform +// DEF="" ID +// USE="" IDREF +// bboxCenter="0 0 0" SFVec3f [initializeOnly] +// bboxSize="-1 -1 -1" SFVec3f [initializeOnly] +// center="0 0 0" SFVec3f [inputOutput] +// rotation="0 0 1 0" SFRotation [inputOutput] +// scale="1 1 1" SFVec3f [inputOutput] +// scaleOrientation="0 0 1 0" SFRotation [inputOutput] +// translation="0 0 0" SFVec3f [inputOutput] +// > +// <\!-- ChildContentModel --> +// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, +// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the +// precise palette of legal nodes that are available depends on assigned profile and components. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </Transform> +// The Transform node is a grouping node that defines a coordinate system for its children that is relative to the coordinate systems of its ancestors. +// Given a 3-dimensional point P and Transform node, P is transformed into point P' in its parent's coordinate system by a series of intermediate +// transformations. In matrix transformation notation, where C (center), SR (scaleOrientation), T (translation), R (rotation), and S (scale) are the +// equivalent transformation matrices, +// P' = T * C * R * SR * S * -SR * -C * P +void X3DImporter::startReadTransform(XmlNode &node) { + aiVector3D center(0, 0, 0); + float rotation[4] = { 0, 0, 1, 0 }; + aiVector3D scale(1, 1, 1); // A value of zero indicates that any child geometry shall not be displayed + float scale_orientation[4] = { 0, 0, 1, 0 }; + aiVector3D translation(0, 0, 0); + aiMatrix4x4 matr, tmatr; + std::string use, def; + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector3DAttribute(node, "center", center); + X3DXmlHelper::getVector3DAttribute(node, "scale", scale); + X3DXmlHelper::getVector3DAttribute(node, "translation", translation); + std::vector<float> tvec; + if (X3DXmlHelper::getFloatArrayAttribute(node, "rotation", tvec)) { + if (tvec.size() != 4) throw DeadlyImportError("<Transform>: rotation vector must have 4 elements."); + memcpy(rotation, tvec.data(), sizeof(rotation)); + tvec.clear(); + } + if (X3DXmlHelper::getFloatArrayAttribute(node, "scaleOrientation", tvec)) { + if (tvec.size() != 4) throw DeadlyImportError("<Transform>: scaleOrientation vector must have 4 elements."); + memcpy(scale_orientation, tvec.data(), sizeof(scale_orientation)); + tvec.clear(); + } + + // if "USE" defined then find already defined element. + if (!use.empty()) { + X3DNodeElementBase *ne(nullptr); + bool newgroup = (nullptr == mNodeElementCur); + if(newgroup) + ParseHelper_Group_Begin(); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + if (newgroup && isNodeEmpty(node)) { + ParseHelper_Node_Exit(); + } + } else { + ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) { + mNodeElementCur->ID = def; + } + + // + // also set values specific to this type of group + // + // calculate transformation matrix + aiMatrix4x4::Translation(translation, matr); // T + aiMatrix4x4::Translation(center, tmatr); // C + matr *= tmatr; + aiMatrix4x4::Rotation(rotation[3], aiVector3D(rotation[0], rotation[1], rotation[2]), tmatr); // R + matr *= tmatr; + aiMatrix4x4::Rotation(scale_orientation[3], aiVector3D(scale_orientation[0], scale_orientation[1], scale_orientation[2]), tmatr); // SR + matr *= tmatr; + aiMatrix4x4::Scaling(scale, tmatr); // S + matr *= tmatr; + aiMatrix4x4::Rotation(-scale_orientation[3], aiVector3D(scale_orientation[0], scale_orientation[1], scale_orientation[2]), tmatr); // -SR + matr *= tmatr; + aiMatrix4x4::Translation(-center, tmatr); // -C + matr *= tmatr; + // and assign it + ((X3DNodeElementGroup *)mNodeElementCur)->Transformation = matr; + // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in <Scene> parser function. + + // for empty element exit from node in that place + if (isNodeEmpty(node)) { + ParseHelper_Node_Exit(); + } + } // if(!use.empty()) else +} + +void X3DImporter::endReadTransform() { + ParseHelper_Node_Exit(); // go up in scene graph +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter_Light.cpp b/libs/assimp/code/AssetLib/X3D/X3DImporter_Light.cpp new file mode 100644 index 0000000..f1ed5e4 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter_Light.cpp @@ -0,0 +1,270 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 X3DImporter_Light.cpp +/// \brief Parsing data from nodes of "Lighting" set of X3D. +/// date 2015-2016 +/// author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" +#include <assimp/StringUtils.h> + +namespace Assimp { + +// <DirectionalLight +// DEF="" ID +// USE="" IDREF +// ambientIntensity="0" SFFloat [inputOutput] +// color="1 1 1" SFColor [inputOutput] +// direction="0 0 -1" SFVec3f [inputOutput] +// global="false" SFBool [inputOutput] +// intensity="1" SFFloat [inputOutput] +// on="true" SFBool [inputOutput] +// /> +void X3DImporter::readDirectionalLight(XmlNode &node) { + std::string def, use; + float ambientIntensity = 0; + aiColor3D color(1, 1, 1); + aiVector3D direction(0, 0, -1); + bool global = false; + float intensity = 1; + bool on = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "ambientIntensity", ambientIntensity); + X3DXmlHelper::getColor3DAttribute(node, "color", color); + X3DXmlHelper::getVector3DAttribute(node, "direction", direction); + XmlParser::getBoolAttribute(node, "global", global); + XmlParser::getFloatAttribute(node, "intensity", intensity); + XmlParser::getBoolAttribute(node, "on", on); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_DirectionalLight, ne); + } else { + if (on) { + // create and if needed - define new geometry object. + ne = new X3DNodeElementLight(X3DElemType::ENET_DirectionalLight, mNodeElementCur); + if (!def.empty()) + ne->ID = def; + else + ne->ID = "DirectionalLight_" + ai_to_string((size_t)ne); // make random name + + ((X3DNodeElementLight *)ne)->AmbientIntensity = ambientIntensity; + ((X3DNodeElementLight *)ne)->Color = color; + ((X3DNodeElementLight *)ne)->Direction = direction; + ((X3DNodeElementLight *)ne)->Global = global; + ((X3DNodeElementLight *)ne)->Intensity = intensity; + // Assimp want a node with name similar to a light. "Why? I don't no." ) + ParseHelper_Group_Begin(false); + + mNodeElementCur->ID = ne->ID; // assign name to node and return to light element. + ParseHelper_Node_Exit(); + // check for child nodes + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "DirectionalLight"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(on) + } // if(!use.empty()) else +} + +// <PointLight +// DEF="" ID +// USE="" IDREF +// ambientIntensity="0" SFFloat [inputOutput] +// attenuation="1 0 0" SFVec3f [inputOutput] +// color="1 1 1" SFColor [inputOutput] +// global="true" SFBool [inputOutput] +// intensity="1" SFFloat [inputOutput] +// location="0 0 0" SFVec3f [inputOutput] +// on="true" SFBool [inputOutput] +// radius="100" SFFloat [inputOutput] +// /> +void X3DImporter::readPointLight(XmlNode &node) { + std::string def, use; + float ambientIntensity = 0; + aiVector3D attenuation(1, 0, 0); + aiColor3D color(1, 1, 1); + bool global = true; + float intensity = 1; + aiVector3D location(0, 0, 0); + bool on = true; + float radius = 100; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "ambientIntensity", ambientIntensity); + X3DXmlHelper::getVector3DAttribute(node, "attenuation", attenuation); + X3DXmlHelper::getColor3DAttribute(node, "color", color); + XmlParser::getBoolAttribute(node, "global", global); + XmlParser::getFloatAttribute(node, "intensity", intensity); + X3DXmlHelper::getVector3DAttribute(node, "location", location); + XmlParser::getBoolAttribute(node, "on", on); + XmlParser::getFloatAttribute(node, "radius", radius); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_PointLight, ne); + } else { + if (on) { + // create and if needed - define new geometry object. + ne = new X3DNodeElementLight(X3DElemType::ENET_PointLight, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementLight *)ne)->AmbientIntensity = ambientIntensity; + ((X3DNodeElementLight *)ne)->Attenuation = attenuation; + ((X3DNodeElementLight *)ne)->Color = color; + ((X3DNodeElementLight *)ne)->Global = global; + ((X3DNodeElementLight *)ne)->Intensity = intensity; + ((X3DNodeElementLight *)ne)->Location = location; + ((X3DNodeElementLight *)ne)->Radius = radius; + // Assimp want a node with name similar to a light. "Why? I don't no." ) + ParseHelper_Group_Begin(false); + // make random name + if (ne->ID.empty()) ne->ID = "PointLight_" + ai_to_string((size_t)ne); + + mNodeElementCur->ID = ne->ID; // assign name to node and return to light element. + ParseHelper_Node_Exit(); + // check for child nodes + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "PointLight"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(on) + } // if(!use.empty()) else +} + +// <SpotLight +// DEF="" ID +// USE="" IDREF +// ambientIntensity="0" SFFloat [inputOutput] +// attenuation="1 0 0" SFVec3f [inputOutput] +// beamWidth="0.7854" SFFloat [inputOutput] +// color="1 1 1" SFColor [inputOutput] +// cutOffAngle="1.570796" SFFloat [inputOutput] +// direction="0 0 -1" SFVec3f [inputOutput] +// global="true" SFBool [inputOutput] +// intensity="1" SFFloat [inputOutput] +// location="0 0 0" SFVec3f [inputOutput] +// on="true" SFBool [inputOutput] +// radius="100" SFFloat [inputOutput] +// /> +void X3DImporter::readSpotLight(XmlNode &node) { + std::string def, use; + float ambientIntensity = 0; + aiVector3D attenuation(1, 0, 0); + float beamWidth = 0.7854f; + aiColor3D color(1, 1, 1); + float cutOffAngle = 1.570796f; + aiVector3D direction(0, 0, -1); + bool global = true; + float intensity = 1; + aiVector3D location(0, 0, 0); + bool on = true; + float radius = 100; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "ambientIntensity", ambientIntensity); + X3DXmlHelper::getVector3DAttribute(node, "attenuation", attenuation); + XmlParser::getFloatAttribute(node, "beamWidth", beamWidth); + X3DXmlHelper::getColor3DAttribute(node, "color", color); + XmlParser::getFloatAttribute(node, "cutOffAngle", cutOffAngle); + X3DXmlHelper::getVector3DAttribute(node, "direction", direction); + XmlParser::getBoolAttribute(node, "global", global); + XmlParser::getFloatAttribute(node, "intensity", intensity); + X3DXmlHelper::getVector3DAttribute(node, "location", location); + XmlParser::getBoolAttribute(node, "on", on); + XmlParser::getFloatAttribute(node, "radius", radius); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_SpotLight, ne); + } else { + if (on) { + // create and if needed - define new geometry object. + ne = new X3DNodeElementLight(X3DElemType::ENET_SpotLight, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + if (beamWidth > cutOffAngle) beamWidth = cutOffAngle; + + ((X3DNodeElementLight *)ne)->AmbientIntensity = ambientIntensity; + ((X3DNodeElementLight *)ne)->Attenuation = attenuation; + ((X3DNodeElementLight *)ne)->BeamWidth = beamWidth; + ((X3DNodeElementLight *)ne)->Color = color; + ((X3DNodeElementLight *)ne)->CutOffAngle = cutOffAngle; + ((X3DNodeElementLight *)ne)->Direction = direction; + ((X3DNodeElementLight *)ne)->Global = global; + ((X3DNodeElementLight *)ne)->Intensity = intensity; + ((X3DNodeElementLight *)ne)->Location = location; + ((X3DNodeElementLight *)ne)->Radius = radius; + + // Assimp want a node with name similar to a light. "Why? I don't no." ) + ParseHelper_Group_Begin(false); + // make random name + if (ne->ID.empty()) ne->ID = "SpotLight_" + ai_to_string((size_t)ne); + + mNodeElementCur->ID = ne->ID; // assign name to node and return to light element. + ParseHelper_Node_Exit(); + // check for child nodes + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "SpotLight"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(on) + } // if(!use.empty()) else +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter_Macro.hpp b/libs/assimp/code/AssetLib/X3D/X3DImporter_Macro.hpp new file mode 100644 index 0000000..a84a739 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter_Macro.hpp @@ -0,0 +1,121 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 X3DImporter_Macro.hpp +/// \brief Useful macrodefines. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef X3DIMPORTER_MACRO_HPP_INCLUDED +#define X3DIMPORTER_MACRO_HPP_INCLUDED + +#include <assimp/XmlParser.h> +#include "X3DImporter.hpp" +#include <string> + +namespace Assimp { + +/// Used for regular checking while attribute "USE" is defined. +/// \param [in] pNode - pugi xml node to read. +/// \param [in] pDEF - string holding "DEF" value. +/// \param [in] pUSE - string holding "USE" value. +/// \param [in] pType - type of element to find. +/// \param [out] pNE - pointer to found node element. +inline X3DNodeElementBase *X3DImporter::MACRO_USE_CHECKANDAPPLY(XmlNode &node, std::string pDEF, std::string pUSE, X3DElemType pType, X3DNodeElementBase *pNE) { + checkNodeMustBeEmpty(node); + if (!pDEF.empty()) + Assimp::Throw_DEF_And_USE(node.name()); + if (!FindNodeElement(pUSE, pType, &pNE)) + Assimp::Throw_USE_NotFound(node.name(), pUSE); + ai_assert(nullptr != mNodeElementCur); + mNodeElementCur->Children.push_back(pNE); /* add found object as child to current element */ + + return pNE; +} + +} // namespace Assimp + +/// \def MACRO_ATTRREAD_CHECKUSEDEF_RET +/// Compact variant for checking "USE" and "DEF". +/// \param [in] pNode - pugi xml node to read. +/// \param [out] pDEF_Var - output variable name for "DEF" value. +/// \param [out] pUSE_Var - output variable name for "USE" value. +#define MACRO_ATTRREAD_CHECKUSEDEF_RET(pNode, pDEF_Var, pUSE_Var) \ + do { \ + XmlParser::getStdStrAttribute(pNode, "DEF", pDEF_Var); \ + XmlParser::getStdStrAttribute(pNode, "USE", pUSE_Var); \ + } while (false) + +/// \def MACRO_FACE_ADD_QUAD_FA(pCCW, pOut, pIn, pP1, pP2, pP3, pP4) +/// Add points as quad. Means that pP1..pP4 set in CCW order. +#define MACRO_FACE_ADD_QUAD_FA(pCCW, pOut, pIn, pP1, pP2, pP3, pP4) \ + do { \ + if (pCCW) { \ + pOut.push_back(pIn[pP1]); \ + pOut.push_back(pIn[pP2]); \ + pOut.push_back(pIn[pP3]); \ + pOut.push_back(pIn[pP4]); \ + } else { \ + pOut.push_back(pIn[pP4]); \ + pOut.push_back(pIn[pP3]); \ + pOut.push_back(pIn[pP2]); \ + pOut.push_back(pIn[pP1]); \ + } \ + } while (false) + +/// \def MACRO_FACE_ADD_QUAD(pCCW, pOut, pP1, pP2, pP3, pP4) +/// Add points as quad. Means that pP1..pP4 set in CCW order. +#define MACRO_FACE_ADD_QUAD(pCCW, pOut, pP1, pP2, pP3, pP4) \ + do { \ + if (pCCW) { \ + pOut.push_back(pP1); \ + pOut.push_back(pP2); \ + pOut.push_back(pP3); \ + pOut.push_back(pP4); \ + } else { \ + pOut.push_back(pP4); \ + pOut.push_back(pP3); \ + pOut.push_back(pP2); \ + pOut.push_back(pP1); \ + } \ + } while (false) + +#endif // X3DIMPORTER_MACRO_HPP_INCLUDED diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter_Metadata.cpp b/libs/assimp/code/AssetLib/X3D/X3DImporter_Metadata.cpp new file mode 100644 index 0000000..8e07d8b --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter_Metadata.cpp @@ -0,0 +1,255 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 X3DImporter_Metadata.cpp +/// \brief Parsing data from nodes of "Metadata" set of X3D. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" + +namespace Assimp { + +bool X3DImporter::checkForMetadataNode(XmlNode &node) { + const std::string &name = node.name(); + if (name == "MetadataBoolean") { + readMetadataBoolean(node); + } else if (name == "MetadataDouble") { + readMetadataDouble(node); + } else if (name == "MetadataFloat") { + readMetadataFloat(node); + } else if (name == "MetadataInteger") { + readMetadataInteger(node); + } else if (name == "MetadataSet") { + readMetadataSet(node); + } else if (name == "MetadataString") { + readMetadataString(node); + } else + return false; + return true; +} + +void X3DImporter::childrenReadMetadata(XmlNode &node, X3DNodeElementBase *pParentElement, const std::string &pNodeName) { + ParseHelper_Node_Enter(pParentElement); + for (auto childNode : node.children()) { + if (!checkForMetadataNode(childNode)) skipUnsupportedNode(pNodeName, childNode); + } + ParseHelper_Node_Exit(); +} + +/// \def MACRO_METADATA_FINDCREATE(pDEF_Var, pUSE_Var, pReference, pValue, pNE, pMetaName) +/// Find element by "USE" or create new one. +/// \param [in] pNode - pugi xml node to read. +/// \param [in] pDEF_Var - variable name with "DEF" value. +/// \param [in] pUSE_Var - variable name with "USE" value. +/// \param [in] pReference - variable name with "reference" value. +/// \param [in] pValue - variable name with "value" value. +/// \param [in, out] pNE - pointer to node element. +/// \param [in] pMetaClass - Class of node. +/// \param [in] pMetaName - Name of node. +/// \param [in] pType - type of element to find. +#define MACRO_METADATA_FINDCREATE(pNode, pDEF_Var, pUSE_Var, pReference, pValue, pNE, pMetaClass, pMetaName, pType) \ + /* if "USE" defined then find already defined element. */ \ + if (!pUSE_Var.empty()) { \ + ne = MACRO_USE_CHECKANDAPPLY(pNode, pDEF_Var, pUSE_Var, pType, pNE); \ + } else { \ + pNE = new pMetaClass(mNodeElementCur); \ + if (!pDEF_Var.empty()) pNE->ID = pDEF_Var; \ + \ + ((pMetaClass *)pNE)->Reference = pReference; \ + ((pMetaClass *)pNE)->Value = pValue; \ + /* also metadata node can contain childs */ \ + if (!isNodeEmpty(pNode)) \ + childrenReadMetadata(pNode, pNE, pMetaName); /* in that case node element will be added to child elements list of current node. */ \ + else \ + mNodeElementCur->Children.push_back(pNE); /* else - add element to child list manually */ \ + \ + NodeElement_List.push_back(pNE); /* add new element to elements list. */ \ + } /* if(!pUSE_Var.empty()) else */ \ + \ + do { \ + } while (false) + +// <MetadataBoolean +// DEF="" ID +// USE="" IDREF +// name="" SFString [inputOutput] +// reference="" SFString [inputOutput] +// value="" MFBool [inputOutput] +// /> +void X3DImporter::readMetadataBoolean(XmlNode &node) { + std::string def, use; + std::string name, reference; + std::vector<bool> value; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "name", name); + XmlParser::getStdStrAttribute(node, "reference", reference); + X3DXmlHelper::getBooleanArrayAttribute(node, "value", value); + + MACRO_METADATA_FINDCREATE(node, def, use, reference, value, ne, X3DNodeElementMetaBoolean, "MetadataBoolean", ENET_MetaBoolean); +} + +// <MetadataDouble +// DEF="" ID +// USE="" IDREF +// name="" SFString [inputOutput] +// reference="" SFString [inputOutput] +// value="" MFDouble [inputOutput] +// /> +void X3DImporter::readMetadataDouble(XmlNode &node) { + std::string def, use; + std::string name, reference; + std::vector<double> value; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "name", name); + XmlParser::getStdStrAttribute(node, "reference", reference); + X3DXmlHelper::getDoubleArrayAttribute(node, "value", value); + + MACRO_METADATA_FINDCREATE(node, def, use, reference, value, ne, X3DNodeElementMetaDouble, "MetadataDouble", ENET_MetaDouble); +} + +// <MetadataFloat +// DEF="" ID +// USE="" IDREF +// name="" SFString [inputOutput] +// reference="" SFString [inputOutput] +// value="" MFFloat [inputOutput] +// /> +void X3DImporter::readMetadataFloat(XmlNode &node) { + std::string def, use; + std::string name, reference; + std::vector<float> value; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "name", name); + XmlParser::getStdStrAttribute(node, "reference", reference); + X3DXmlHelper::getFloatArrayAttribute(node, "value", value); + + MACRO_METADATA_FINDCREATE(node, def, use, reference, value, ne, X3DNodeElementMetaFloat, "MetadataFloat", ENET_MetaFloat); +} + +// <MetadataInteger +// DEF="" ID +// USE="" IDREF +// name="" SFString [inputOutput] +// reference="" SFString [inputOutput] +// value="" MFInteger [inputOutput] +// /> +void X3DImporter::readMetadataInteger(XmlNode &node) { + std::string def, use; + std::string name, reference; + std::vector<int32_t> value; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "name", name); + XmlParser::getStdStrAttribute(node, "reference", reference); + X3DXmlHelper::getInt32ArrayAttribute(node, "value", value); + + MACRO_METADATA_FINDCREATE(node, def, use, reference, value, ne, X3DNodeElementMetaInt, "MetadataInteger", ENET_MetaInteger); +} + +// <MetadataSet +// DEF="" ID +// USE="" IDREF +// name="" SFString [inputOutput] +// reference="" SFString [inputOutput] +// /> +void X3DImporter::readMetadataSet(XmlNode &node) { + std::string def, use; + std::string name, reference; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "name", name); + XmlParser::getStdStrAttribute(node, "reference", reference); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_MetaSet, ne); + } else { + ne = new X3DNodeElementMetaSet(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementMetaSet *)ne)->Reference = reference; + // also metadata node can contain children + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "MetadataSet"); + else + mNodeElementCur->Children.push_back(ne); // made object as child to current element + + NodeElement_List.push_back(ne); // add new element to elements list. + } // if(!use.empty()) else +} + +// <MetadataString +// DEF="" ID +// USE="" IDREF +// name="" SFString [inputOutput] +// reference="" SFString [inputOutput] +// value="" MFString [inputOutput] +// /> +void X3DImporter::readMetadataString(XmlNode &node) { + std::string def, use; + std::string name, reference; + std::vector<std::string> value; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "name", name); + XmlParser::getStdStrAttribute(node, "reference", reference); + X3DXmlHelper::getStringArrayAttribute(node, "value", value); + + MACRO_METADATA_FINDCREATE(node, def, use, reference, value, ne, X3DNodeElementMetaString, "MetadataString", ENET_MetaString); +} + +}// namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter_Networking.cpp b/libs/assimp/code/AssetLib/X3D/X3DImporter_Networking.cpp new file mode 100644 index 0000000..f2b4716 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter_Networking.cpp @@ -0,0 +1,124 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 X3DImporter_Networking.cpp +/// \brief Parsing data from nodes of "Networking" set of X3D. +/// date 2015-2016 +/// author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" + +// Header files, Assimp. +#include <assimp/DefaultIOSystem.h> + +//#include <regex> + +namespace Assimp { + +//static std::regex pattern_parentDir(R"((^|/)[^/]+/../)"); +static std::string parentDir("/../"); + +// <Inline +// DEF="" ID +// USE="" IDREF +// bboxCenter="0 0 0" SFVec3f [initializeOnly] +// bboxSize="-1 -1 -1" SFVec3f [initializeOnly] +// load="true" SFBool [inputOutput] +// url="" MFString [inputOutput] +// /> +void X3DImporter::readInline(XmlNode &node) { + std::string def, use; + bool load = true; + std::list<std::string> url; + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "load", load); + X3DXmlHelper::getStringListAttribute(node, "url", url); + + // if "USE" defined then find already defined element. + X3DNodeElementBase *ne = nullptr; + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + } else { + ParseHelper_Group_Begin(true); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) mNodeElementCur->ID = def; + + if (load && !url.empty()) { + std::string full_path = mpIOHandler->CurrentDirectory() + url.front(); + + //full_path = std::regex_replace(full_path, pattern_parentDir, "$1"); + for (std::string::size_type pos = full_path.find(parentDir); pos != std::string::npos; pos = full_path.find(parentDir, pos)) { + if (pos > 0) { + std::string::size_type pos2 = full_path.rfind('/', pos - 1); + if (pos2 != std::string::npos) { + full_path.erase(pos2, pos - pos2 + 3); + pos = pos2; + } else { + full_path.erase(0, pos + 4); + pos = 0; + } + } else { + pos += 3; + } + } + // Attribute "url" can contain list of strings. But we need only one - first. + std::string::size_type slashPos = full_path.find_last_of("\\/"); + mpIOHandler->PushDirectory(slashPos == std::string::npos ? std::string() : full_path.substr(0, slashPos + 1)); + ParseFile(full_path, mpIOHandler); + mpIOHandler->PopDirectory(); + } + + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) childrenReadMetadata(node, mNodeElementCur, "Inline"); + + // exit from node in that place + ParseHelper_Node_Exit(); + } // if(!use.empty()) else +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter_Node.hpp b/libs/assimp/code/AssetLib/X3D/X3DImporter_Node.hpp new file mode 100644 index 0000000..8d33c4b --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter_Node.hpp @@ -0,0 +1,463 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 X3DImporter_Node.hpp +/// \brief Elements of scene graph. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef INCLUDED_AI_X3D_IMPORTER_NODE_H +#define INCLUDED_AI_X3D_IMPORTER_NODE_H + +// Header files, Assimp. +#include <assimp/types.h> + +#include <list> +#include <vector> + +enum X3DElemType { + ENET_Group, ///< Element has type "Group". + ENET_MetaBoolean, ///< Element has type "Metadata boolean". + ENET_MetaDouble, ///< Element has type "Metadata double". + ENET_MetaFloat, ///< Element has type "Metadata float". + ENET_MetaInteger, ///< Element has type "Metadata integer". + ENET_MetaSet, ///< Element has type "Metadata set". + ENET_MetaString, ///< Element has type "Metadata string". + ENET_Arc2D, ///< Element has type "Arc2D". + ENET_ArcClose2D, ///< Element has type "ArcClose2D". + ENET_Circle2D, ///< Element has type "Circle2D". + ENET_Disk2D, ///< Element has type "Disk2D". + ENET_Polyline2D, ///< Element has type "Polyline2D". + ENET_Polypoint2D, ///< Element has type "Polypoint2D". + ENET_Rectangle2D, ///< Element has type "Rectangle2D". + ENET_TriangleSet2D, ///< Element has type "TriangleSet2D". + ENET_Box, ///< Element has type "Box". + ENET_Cone, ///< Element has type "Cone". + ENET_Cylinder, ///< Element has type "Cylinder". + ENET_Sphere, ///< Element has type "Sphere". + ENET_ElevationGrid, ///< Element has type "ElevationGrid". + ENET_Extrusion, ///< Element has type "Extrusion". + ENET_Coordinate, ///< Element has type "Coordinate". + ENET_Normal, ///< Element has type "Normal". + ENET_TextureCoordinate, ///< Element has type "TextureCoordinate". + ENET_IndexedFaceSet, ///< Element has type "IndexedFaceSet". + ENET_IndexedLineSet, ///< Element has type "IndexedLineSet". + ENET_IndexedTriangleSet, ///< Element has type "IndexedTriangleSet". + ENET_IndexedTriangleFanSet, ///< Element has type "IndexedTriangleFanSet". + ENET_IndexedTriangleStripSet, ///< Element has type "IndexedTriangleStripSet". + ENET_LineSet, ///< Element has type "LineSet". + ENET_PointSet, ///< Element has type "PointSet". + ENET_TriangleSet, ///< Element has type "TriangleSet". + ENET_TriangleFanSet, ///< Element has type "TriangleFanSet". + ENET_TriangleStripSet, ///< Element has type "TriangleStripSet". + ENET_Color, ///< Element has type "Color". + ENET_ColorRGBA, ///< Element has type "ColorRGBA". + ENET_Shape, ///< Element has type "Shape". + ENET_Appearance, ///< Element has type "Appearance". + ENET_Material, ///< Element has type "Material". + ENET_ImageTexture, ///< Element has type "ImageTexture". + ENET_TextureTransform, ///< Element has type "TextureTransform". + ENET_DirectionalLight, ///< Element has type "DirectionalLight". + ENET_PointLight, ///< Element has type "PointLight". + ENET_SpotLight, ///< Element has type "SpotLight". + + ENET_Invalid ///< Element has invalid type and possible contain invalid data. +}; + +struct X3DNodeElementBase { + X3DNodeElementBase *Parent; + std::string ID; + std::list<X3DNodeElementBase *> Children; + X3DElemType Type; + + virtual ~X3DNodeElementBase() { + // empty + } + +protected: + X3DNodeElementBase(X3DElemType type, X3DNodeElementBase *pParent) : + Parent(pParent), Type(type) { + // empty + } +}; + +/// This struct hold <Color> value. +struct X3DNodeElementColor : X3DNodeElementBase { + std::list<aiColor3D> Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementColor(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Color, pParent) {} + +}; // struct X3DNodeElementColor + +/// This struct hold <ColorRGBA> value. +struct X3DNodeElementColorRGBA : X3DNodeElementBase { + std::list<aiColor4D> Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementColorRGBA(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_ColorRGBA, pParent) {} + +}; // struct X3DNodeElementColorRGBA + +/// This struct hold <Coordinate> value. +struct X3DNodeElementCoordinate : public X3DNodeElementBase { + std::list<aiVector3D> Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementCoordinate(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Coordinate, pParent) {} + +}; // struct X3DNodeElementCoordinate + +/// This struct hold <Normal> value. +struct X3DNodeElementNormal : X3DNodeElementBase { + std::list<aiVector3D> Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementNormal(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Normal, pParent) {} + +}; // struct X3DNodeElementNormal + +/// This struct hold <TextureCoordinate> value. +struct X3DNodeElementTextureCoordinate : X3DNodeElementBase { + std::list<aiVector2D> Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementTextureCoordinate(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_TextureCoordinate, pParent) {} + +}; // struct X3DNodeElementTextureCoordinate + +/// Two-dimensional figure. +struct X3DNodeElementGeometry2D : X3DNodeElementBase { + std::list<aiVector3D> Vertices; ///< Vertices list. + size_t NumIndices; ///< Number of indices in one face. + bool Solid; ///< Flag: if true then render must use back-face culling, else render must draw both sides of object. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + X3DNodeElementGeometry2D(X3DElemType pType, X3DNodeElementBase *pParent) : + X3DNodeElementBase(pType, pParent), Solid(true) {} + +}; // class X3DNodeElementGeometry2D + +/// Three-dimensional body. +struct X3DNodeElementGeometry3D : X3DNodeElementBase { + std::list<aiVector3D> Vertices; ///< Vertices list. + size_t NumIndices; ///< Number of indices in one face. + bool Solid; ///< Flag: if true then render must use back-face culling, else render must draw both sides of object. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + X3DNodeElementGeometry3D(X3DElemType pType, X3DNodeElementBase *pParent) : + X3DNodeElementBase(pType, pParent), Vertices(), NumIndices(0), Solid(true) { + // empty + } +}; // class X3DNodeElementGeometry3D + +/// Uniform rectangular grid of varying height. +struct X3DNodeElementElevationGrid : X3DNodeElementGeometry3D { + bool NormalPerVertex; ///< If true then normals are defined for every vertex, else for every face(line). + bool ColorPerVertex; ///< If true then colors are defined for every vertex, else for every face(line). + /// If the angle between the geometric normals of two adjacent faces is less than the crease angle, normals shall be calculated so that the faces are + /// shaded smoothly across the edge; otherwise, normals shall be calculated so that a lighting discontinuity across the edge is produced. + float CreaseAngle; + std::vector<int32_t> CoordIdx; ///< Coordinates list by faces. In X3D format: "-1" - delimiter for faces. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + X3DNodeElementElevationGrid(X3DElemType pType, X3DNodeElementBase *pParent) : + X3DNodeElementGeometry3D(pType, pParent) {} +}; // class X3DNodeElementIndexedSet + +/// Shape with indexed vertices. +struct X3DNodeElementIndexedSet : public X3DNodeElementGeometry3D { + /// The ccw field defines the ordering of the vertex coordinates of the geometry with respect to user-given or automatically generated normal vectors + /// used in the lighting model equations. If ccw is TRUE, the normals shall follow the right hand rule; the orientation of each normal with respect to + /// the vertices (taken in order) shall be such that the vertices appear to be oriented in a counterclockwise order when the vertices are viewed (in the + /// local coordinate system of the Shape) from the opposite direction as the normal. If ccw is FALSE, the normals shall be oriented in the opposite + /// direction. If normals are not generated but are supplied using a Normal node, and the orientation of the normals does not match the setting of the + /// ccw field, results are undefined. + bool CCW; + std::vector<int32_t> ColorIndex; ///< Field to specify the polygonal faces by indexing into the <Color> or <ColorRGBA>. + bool ColorPerVertex; ///< If true then colors are defined for every vertex, else for every face(line). + /// The convex field indicates whether all polygons in the shape are convex (TRUE). A polygon is convex if it is planar, does not intersect itself, + /// and all of the interior angles at its vertices are less than 180 degrees. Non planar and self intersecting polygons may produce undefined results + /// even if the convex field is FALSE. + bool Convex; + std::vector<int32_t> CoordIndex; ///< Field to specify the polygonal faces by indexing into the <Coordinate>. + /// If the angle between the geometric normals of two adjacent faces is less than the crease angle, normals shall be calculated so that the faces are + /// shaded smoothly across the edge; otherwise, normals shall be calculated so that a lighting discontinuity across the edge is produced. + float CreaseAngle; + std::vector<int32_t> NormalIndex; ///< Field to specify the polygonal faces by indexing into the <Normal>. + bool NormalPerVertex; ///< If true then normals are defined for every vertex, else for every face(line). + std::vector<int32_t> TexCoordIndex; ///< Field to specify the polygonal faces by indexing into the <TextureCoordinate>. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + X3DNodeElementIndexedSet(X3DElemType pType, X3DNodeElementBase *pParent) : + X3DNodeElementGeometry3D(pType, pParent) {} +}; // class X3DNodeElementIndexedSet + +/// Shape with set of vertices. +struct X3DNodeElementSet : X3DNodeElementGeometry3D { + /// The ccw field defines the ordering of the vertex coordinates of the geometry with respect to user-given or automatically generated normal vectors + /// used in the lighting model equations. If ccw is TRUE, the normals shall follow the right hand rule; the orientation of each normal with respect to + /// the vertices (taken in order) shall be such that the vertices appear to be oriented in a counterclockwise order when the vertices are viewed (in the + /// local coordinate system of the Shape) from the opposite direction as the normal. If ccw is FALSE, the normals shall be oriented in the opposite + /// direction. If normals are not generated but are supplied using a Normal node, and the orientation of the normals does not match the setting of the + /// ccw field, results are undefined. + bool CCW; + bool ColorPerVertex; ///< If true then colors are defined for every vertex, else for every face(line). + bool NormalPerVertex; ///< If true then normals are defined for every vertex, else for every face(line). + std::vector<int32_t> CoordIndex; ///< Field to specify the polygonal faces by indexing into the <Coordinate>. + std::vector<int32_t> NormalIndex; ///< Field to specify the polygonal faces by indexing into the <Normal>. + std::vector<int32_t> TexCoordIndex; ///< Field to specify the polygonal faces by indexing into the <TextureCoordinate>. + std::vector<int32_t> VertexCount; ///< Field describes how many vertices are to be used in each polyline(polygon) from the <Coordinate> field. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + X3DNodeElementSet(X3DElemType type, X3DNodeElementBase *pParent) : + X3DNodeElementGeometry3D(type, pParent) {} + +}; // class X3DNodeElementSet + +/// This struct hold <Shape> value. +struct X3DNodeElementShape : X3DNodeElementBase { + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementShape(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Shape, pParent) {} +}; // struct X3DNodeElementShape + +/// This struct hold <Appearance> value. +struct X3DNodeElementAppearance : public X3DNodeElementBase { + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementAppearance(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Appearance, pParent) {} + +}; // struct X3DNodeElementAppearance + +struct X3DNodeElementMaterial : public X3DNodeElementBase { + float AmbientIntensity; ///< Specifies how much ambient light from light sources this surface shall reflect. + aiColor3D DiffuseColor; ///< Reflects all X3D light sources depending on the angle of the surface with respect to the light source. + aiColor3D EmissiveColor; ///< Models "glowing" objects. This can be useful for displaying pre-lit models. + float Shininess; ///< Lower shininess values produce soft glows, while higher values result in sharper, smaller highlights. + aiColor3D SpecularColor; ///< The specularColor and shininess fields determine the specular highlights. + float Transparency; ///< Specifies how "clear" an object is, with 1.0 being completely transparent, and 0.0 completely opaque. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + X3DNodeElementMaterial(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Material, pParent), + AmbientIntensity(0.0f), + DiffuseColor(), + EmissiveColor(), + Shininess(0.0f), + SpecularColor(), + Transparency(1.0f) { + // empty + } +}; // class X3DNodeElementMaterial + +/// This struct hold <ImageTexture> value. +struct X3DNodeElementImageTexture : X3DNodeElementBase { + /// RepeatS and RepeatT, that specify how the texture wraps in the S and T directions. If repeatS is TRUE (the default), the texture map is repeated + /// outside the [0.0, 1.0] texture coordinate range in the S direction so that it fills the shape. If repeatS is FALSE, the texture coordinates are + /// clamped in the S direction to lie within the [0.0, 1.0] range. The repeatT field is analogous to the repeatS field. + bool RepeatS; + bool RepeatT; ///< See \ref RepeatS. + std::string URL; ///< URL of the texture. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementImageTexture(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_ImageTexture, pParent) {} + +}; // struct X3DNodeElementImageTexture + +/// This struct hold <TextureTransform> value. +struct X3DNodeElementTextureTransform : X3DNodeElementBase { + aiVector2D Center; ///< Specifies a translation offset in texture coordinate space about which the rotation and scale fields are applied. + float Rotation; ///< Specifies a rotation in angle base units of the texture coordinates about the center point after the scale has been applied. + aiVector2D Scale; ///< Specifies a scaling factor in S and T of the texture coordinates about the center point. + aiVector2D Translation; ///< Specifies a translation of the texture coordinates. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementTextureTransform(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_TextureTransform, pParent) {} + +}; // struct X3DNodeElementTextureTransform + +struct X3DNodeElementGroup : X3DNodeElementBase { + aiMatrix4x4 Transformation; ///< Transformation matrix. + + /// As you know node elements can use already defined node elements when attribute "USE" is defined. + /// Standard search when looking for an element in the whole scene graph, existing at this moment. + /// If a node is marked as static, the children(or lower) can not search for elements in the nodes upper then static. + bool Static; + + bool UseChoice; ///< Flag: if true then use number from \ref Choice to choose what the child will be kept. + int32_t Choice; ///< Number of the child which will be kept. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pStatic - static node flag. + X3DNodeElementGroup(X3DNodeElementBase *pParent, const bool pStatic = false) : + X3DNodeElementBase(X3DElemType::ENET_Group, pParent), Static(pStatic), UseChoice(false) {} +}; + +struct X3DNodeElementMeta : X3DNodeElementBase { + std::string Name; ///< Name of metadata object. + std::string Reference; + + virtual ~X3DNodeElementMeta() { + // empty + } + +protected: + X3DNodeElementMeta(X3DElemType type, X3DNodeElementBase *parent) : + X3DNodeElementBase(type, parent) { + // empty + } +}; + +struct X3DNodeElementMetaBoolean : X3DNodeElementMeta { + std::vector<bool> Value; ///< Stored value. + + explicit X3DNodeElementMetaBoolean(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaBoolean, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaDouble : X3DNodeElementMeta { + std::vector<double> Value; ///< Stored value. + + explicit X3DNodeElementMetaDouble(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaDouble, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaFloat : public X3DNodeElementMeta { + std::vector<float> Value; ///< Stored value. + + explicit X3DNodeElementMetaFloat(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaFloat, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaInt : public X3DNodeElementMeta { + std::vector<int32_t> Value; ///< Stored value. + + explicit X3DNodeElementMetaInt(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaInteger, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaSet : public X3DNodeElementMeta { + std::list<X3DNodeElementMeta> Value; ///< Stored value. + + explicit X3DNodeElementMetaSet(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaSet, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaString : X3DNodeElementMeta { + std::vector<std::string> Value; ///< Stored value. + + explicit X3DNodeElementMetaString(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaString, pParent) { + // empty + } +}; + +/// \struct X3DNodeElementLight +/// This struct hold <TextureTransform> value. +struct X3DNodeElementLight : X3DNodeElementBase { + float AmbientIntensity; ///< Specifies the intensity of the ambient emission from the light. + aiColor3D Color; ///< specifies the spectral colour properties of both the direct and ambient light emission as an RGB value. + aiVector3D Direction; ///< Specifies the direction vector of the illumination emanating from the light source in the local coordinate system. + /// \var Global + /// Field that determines whether the light is global or scoped. Global lights illuminate all objects that fall within their volume of lighting influence. + /// Scoped lights only illuminate objects that are in the same transformation hierarchy as the light. + bool Global; + float Intensity; ///< Specifies the brightness of the direct emission from the light. + /// \var Attenuation + /// PointLight node's illumination falls off with distance as specified by three attenuation coefficients. The attenuation factor + /// is: "1 / max(attenuation[0] + attenuation[1] * r + attenuation[2] * r2, 1)", where r is the distance from the light to the surface being illuminated. + aiVector3D Attenuation; + aiVector3D Location; ///< Specifies a translation offset of the centre point of the light source from the light's local coordinate system origin. + float Radius; ///< Specifies the radial extent of the solid angle and the maximum distance from location that may be illuminated by the light source. + float BeamWidth; ///< Specifies an inner solid angle in which the light source emits light at uniform full intensity. + float CutOffAngle; ///< The light source's emission intensity drops off from the inner solid angle (beamWidth) to the outer solid angle (cutOffAngle). + + /// Constructor + /// \param [in] pParent - pointer to parent node. + /// \param [in] pLightType - type of the light source. + X3DNodeElementLight(X3DElemType pLightType, X3DNodeElementBase *pParent) : + X3DNodeElementBase(pLightType, pParent) {} + +}; // struct X3DNodeElementLight + +#endif // INCLUDED_AI_X3D_IMPORTER_NODE_H diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter_Postprocess.cpp b/libs/assimp/code/AssetLib/X3D/X3DImporter_Postprocess.cpp new file mode 100644 index 0000000..87121ef --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter_Postprocess.cpp @@ -0,0 +1,731 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 X3DImporter_Postprocess.cpp +/// \brief Convert built scenegraph and objects to Assimp scenegraph. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DGeoHelper.h" +#include "X3DImporter.hpp" + +// Header files, Assimp. +#include <assimp/StandardShapes.h> +#include <assimp/StringUtils.h> +#include <assimp/ai_assert.h> + +// Header files, stdlib. +#include <algorithm> +#include <iterator> +#include <string> + +namespace Assimp { + +aiMatrix4x4 X3DImporter::PostprocessHelper_Matrix_GlobalToCurrent() const { + X3DNodeElementBase *cur_node; + std::list<aiMatrix4x4> matr; + aiMatrix4x4 out_matr; + + // starting walk from current element to root + cur_node = mNodeElementCur; + if (cur_node != nullptr) { + do { + // if cur_node is group then store group transformation matrix in list. + if (cur_node->Type == X3DElemType::ENET_Group) matr.push_back(((X3DNodeElementGroup *)cur_node)->Transformation); + + cur_node = cur_node->Parent; + } while (cur_node != nullptr); + } + + // multiplicate all matrices in reverse order + for (std::list<aiMatrix4x4>::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); ++rit) + out_matr = out_matr * (*rit); + + return out_matr; +} + +void X3DImporter::PostprocessHelper_CollectMetadata(const X3DNodeElementBase &pNodeElement, std::list<X3DNodeElementBase *> &pList) const { + // walk through childs and find for metadata. + for (std::list<X3DNodeElementBase *>::const_iterator el_it = pNodeElement.Children.begin(); el_it != pNodeElement.Children.end(); ++el_it) { + if (((*el_it)->Type == X3DElemType::ENET_MetaBoolean) || ((*el_it)->Type == X3DElemType::ENET_MetaDouble) || + ((*el_it)->Type == X3DElemType::ENET_MetaFloat) || ((*el_it)->Type == X3DElemType::ENET_MetaInteger) || + ((*el_it)->Type == X3DElemType::ENET_MetaString)) { + pList.push_back(*el_it); + } else if ((*el_it)->Type == X3DElemType::ENET_MetaSet) { + PostprocessHelper_CollectMetadata(**el_it, pList); + } + } // for(std::list<X3DNodeElementBase*>::const_iterator el_it = pNodeElement.Children.begin(); el_it != pNodeElement.Children.end(); el_it++) +} + +bool X3DImporter::PostprocessHelper_ElementIsMetadata(const X3DElemType pType) const { + if ((pType == X3DElemType::ENET_MetaBoolean) || (pType == X3DElemType::ENET_MetaDouble) || + (pType == X3DElemType::ENET_MetaFloat) || (pType == X3DElemType::ENET_MetaInteger) || + (pType == X3DElemType::ENET_MetaString) || (pType == X3DElemType::ENET_MetaSet)) { + return true; + } else { + return false; + } +} + +bool X3DImporter::PostprocessHelper_ElementIsMesh(const X3DElemType pType) const { + if ((pType == X3DElemType::ENET_Arc2D) || (pType == X3DElemType::ENET_ArcClose2D) || + (pType == X3DElemType::ENET_Box) || (pType == X3DElemType::ENET_Circle2D) || + (pType == X3DElemType::ENET_Cone) || (pType == X3DElemType::ENET_Cylinder) || + (pType == X3DElemType::ENET_Disk2D) || (pType == X3DElemType::ENET_ElevationGrid) || + (pType == X3DElemType::ENET_Extrusion) || (pType == X3DElemType::ENET_IndexedFaceSet) || + (pType == X3DElemType::ENET_IndexedLineSet) || (pType == X3DElemType::ENET_IndexedTriangleFanSet) || + (pType == X3DElemType::ENET_IndexedTriangleSet) || (pType == X3DElemType::ENET_IndexedTriangleStripSet) || + (pType == X3DElemType::ENET_PointSet) || (pType == X3DElemType::ENET_LineSet) || + (pType == X3DElemType::ENET_Polyline2D) || (pType == X3DElemType::ENET_Polypoint2D) || + (pType == X3DElemType::ENET_Rectangle2D) || (pType == X3DElemType::ENET_Sphere) || + (pType == X3DElemType::ENET_TriangleFanSet) || (pType == X3DElemType::ENET_TriangleSet) || + (pType == X3DElemType::ENET_TriangleSet2D) || (pType == X3DElemType::ENET_TriangleStripSet)) { + return true; + } else { + return false; + } +} + +void X3DImporter::Postprocess_BuildLight(const X3DNodeElementBase &pNodeElement, std::list<aiLight *> &pSceneLightList) const { + const X3DNodeElementLight &ne = *((X3DNodeElementLight *)&pNodeElement); + aiMatrix4x4 transform_matr = PostprocessHelper_Matrix_GlobalToCurrent(); + aiLight *new_light = new aiLight; + + new_light->mName = ne.ID; + new_light->mColorAmbient = ne.Color * ne.AmbientIntensity; + new_light->mColorDiffuse = ne.Color * ne.Intensity; + new_light->mColorSpecular = ne.Color * ne.Intensity; + switch (pNodeElement.Type) { + case X3DElemType::ENET_DirectionalLight: + new_light->mType = aiLightSource_DIRECTIONAL; + new_light->mDirection = ne.Direction, new_light->mDirection *= transform_matr; + + break; + case X3DElemType::ENET_PointLight: + new_light->mType = aiLightSource_POINT; + new_light->mPosition = ne.Location, new_light->mPosition *= transform_matr; + new_light->mAttenuationConstant = ne.Attenuation.x; + new_light->mAttenuationLinear = ne.Attenuation.y; + new_light->mAttenuationQuadratic = ne.Attenuation.z; + + break; + case X3DElemType::ENET_SpotLight: + new_light->mType = aiLightSource_SPOT; + new_light->mPosition = ne.Location, new_light->mPosition *= transform_matr; + new_light->mDirection = ne.Direction, new_light->mDirection *= transform_matr; + new_light->mAttenuationConstant = ne.Attenuation.x; + new_light->mAttenuationLinear = ne.Attenuation.y; + new_light->mAttenuationQuadratic = ne.Attenuation.z; + new_light->mAngleInnerCone = ne.BeamWidth; + new_light->mAngleOuterCone = ne.CutOffAngle; + + break; + default: + throw DeadlyImportError("Postprocess_BuildLight. Unknown type of light: " + ai_to_string(pNodeElement.Type) + "."); + } + + pSceneLightList.push_back(new_light); +} + +void X3DImporter::Postprocess_BuildMaterial(const X3DNodeElementBase &pNodeElement, aiMaterial **pMaterial) const { + // check argument + if (pMaterial == nullptr) throw DeadlyImportError("Postprocess_BuildMaterial. pMaterial is nullptr."); + if (*pMaterial != nullptr) throw DeadlyImportError("Postprocess_BuildMaterial. *pMaterial must be nullptr."); + + *pMaterial = new aiMaterial; + aiMaterial &taimat = **pMaterial; // creating alias for convenience. + + // at this point pNodeElement point to <Appearance> node. Walk through childs and add all stored data. + for (std::list<X3DNodeElementBase *>::const_iterator el_it = pNodeElement.Children.begin(); el_it != pNodeElement.Children.end(); ++el_it) { + if ((*el_it)->Type == X3DElemType::ENET_Material) { + aiColor3D tcol3; + float tvalf; + X3DNodeElementMaterial &tnemat = *((X3DNodeElementMaterial *)*el_it); + + tcol3.r = tnemat.AmbientIntensity, tcol3.g = tnemat.AmbientIntensity, tcol3.b = tnemat.AmbientIntensity; + taimat.AddProperty(&tcol3, 1, AI_MATKEY_COLOR_AMBIENT); + taimat.AddProperty(&tnemat.DiffuseColor, 1, AI_MATKEY_COLOR_DIFFUSE); + taimat.AddProperty(&tnemat.EmissiveColor, 1, AI_MATKEY_COLOR_EMISSIVE); + taimat.AddProperty(&tnemat.SpecularColor, 1, AI_MATKEY_COLOR_SPECULAR); + tvalf = 1; + taimat.AddProperty(&tvalf, 1, AI_MATKEY_SHININESS_STRENGTH); + taimat.AddProperty(&tnemat.Shininess, 1, AI_MATKEY_SHININESS); + tvalf = 1.0f - tnemat.Transparency; + taimat.AddProperty(&tvalf, 1, AI_MATKEY_OPACITY); + } // if((*el_it)->Type == X3DElemType::ENET_Material) + else if ((*el_it)->Type == X3DElemType::ENET_ImageTexture) { + X3DNodeElementImageTexture &tnetex = *((X3DNodeElementImageTexture *)*el_it); + aiString url_str(tnetex.URL.c_str()); + int mode = aiTextureOp_Multiply; + + taimat.AddProperty(&url_str, AI_MATKEY_TEXTURE_DIFFUSE(0)); + taimat.AddProperty(&tnetex.RepeatS, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0)); + taimat.AddProperty(&tnetex.RepeatT, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0)); + taimat.AddProperty(&mode, 1, AI_MATKEY_TEXOP_DIFFUSE(0)); + } // else if((*el_it)->Type == X3DElemType::ENET_ImageTexture) + else if ((*el_it)->Type == X3DElemType::ENET_TextureTransform) { + aiUVTransform trans; + X3DNodeElementTextureTransform &tnetextr = *((X3DNodeElementTextureTransform *)*el_it); + + trans.mTranslation = tnetextr.Translation - tnetextr.Center; + trans.mScaling = tnetextr.Scale; + trans.mRotation = tnetextr.Rotation; + taimat.AddProperty(&trans, 1, AI_MATKEY_UVTRANSFORM_DIFFUSE(0)); + } // else if((*el_it)->Type == X3DElemType::ENET_TextureTransform) + } // for(std::list<X3DNodeElementBase*>::const_iterator el_it = pNodeElement.Children.begin(); el_it != pNodeElement.Children.end(); el_it++) +} + +void X3DImporter::Postprocess_BuildMesh(const X3DNodeElementBase &pNodeElement, aiMesh **pMesh) const { + // check argument + if (pMesh == nullptr) throw DeadlyImportError("Postprocess_BuildMesh. pMesh is nullptr."); + if (*pMesh != nullptr) throw DeadlyImportError("Postprocess_BuildMesh. *pMesh must be nullptr."); + + /************************************************************************************************************************************/ + /************************************************************ Geometry2D ************************************************************/ + /************************************************************************************************************************************/ + if ((pNodeElement.Type == X3DElemType::ENET_Arc2D) || (pNodeElement.Type == X3DElemType::ENET_ArcClose2D) || + (pNodeElement.Type == X3DElemType::ENET_Circle2D) || (pNodeElement.Type == X3DElemType::ENET_Disk2D) || + (pNodeElement.Type == X3DElemType::ENET_Polyline2D) || (pNodeElement.Type == X3DElemType::ENET_Polypoint2D) || + (pNodeElement.Type == X3DElemType::ENET_Rectangle2D) || (pNodeElement.Type == X3DElemType::ENET_TriangleSet2D)) { + X3DNodeElementGeometry2D &tnemesh = *((X3DNodeElementGeometry2D *)&pNodeElement); // create alias for convenience + std::vector<aiVector3D> tarr; + + tarr.reserve(tnemesh.Vertices.size()); + for (std::list<aiVector3D>::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); ++it) + tarr.push_back(*it); + *pMesh = StandardShapes::MakeMesh(tarr, static_cast<unsigned int>(tnemesh.NumIndices)); // create mesh from vertices using Assimp help. + + return; // mesh is build, nothing to do anymore. + } + /************************************************************************************************************************************/ + /************************************************************ Geometry3D ************************************************************/ + /************************************************************************************************************************************/ + // + // Predefined figures + // + if ((pNodeElement.Type == X3DElemType::ENET_Box) || (pNodeElement.Type == X3DElemType::ENET_Cone) || + (pNodeElement.Type == X3DElemType::ENET_Cylinder) || (pNodeElement.Type == X3DElemType::ENET_Sphere)) { + X3DNodeElementGeometry3D &tnemesh = *((X3DNodeElementGeometry3D *)&pNodeElement); // create alias for convenience + std::vector<aiVector3D> tarr; + + tarr.reserve(tnemesh.Vertices.size()); + for (std::list<aiVector3D>::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); ++it) + tarr.push_back(*it); + + *pMesh = StandardShapes::MakeMesh(tarr, static_cast<unsigned int>(tnemesh.NumIndices)); // create mesh from vertices using Assimp help. + + return; // mesh is build, nothing to do anymore. + } + // + // Parametric figures + // + if (pNodeElement.Type == X3DElemType::ENET_ElevationGrid) { + X3DNodeElementElevationGrid &tnemesh = *((X3DNodeElementElevationGrid *)&pNodeElement); // create alias for convenience + + // at first create mesh from existing vertices. + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIdx, tnemesh.Vertices); + // copy additional information from children + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Normal) + X3DGeoHelper::add_normal(**pMesh, ((X3DNodeElementNormal *)*ch_it)->Value, tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_TextureCoordinate) + X3DGeoHelper::add_tex_coord(**pMesh, ((X3DNodeElementTextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of ElevationGrid: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list<X3DNodeElementBase*>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_ElevationGrid) + // + // Indexed primitives sets + // + if (pNodeElement.Type == X3DElemType::ENET_IndexedFaceSet) { + X3DNodeElementIndexedSet &tnemesh = *((X3DNodeElementIndexedSet *)&pNodeElement); // create alias for convenience + + // at first search for <Coordinate> node and create mesh. + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((X3DNodeElementColorRGBA *)*ch_it)->Value, + tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == X3DElemType::ENET_Normal) + X3DGeoHelper::add_normal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((X3DNodeElementNormal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_TextureCoordinate) + X3DGeoHelper::add_tex_coord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((X3DNodeElementTextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedFaceSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list<X3DNodeElementBase*>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_IndexedFaceSet) + + if (pNodeElement.Type == X3DElemType::ENET_IndexedLineSet) { + X3DNodeElementIndexedSet &tnemesh = *((X3DNodeElementIndexedSet *)&pNodeElement); // create alias for convenience + + // at first search for <Coordinate> node and create mesh. + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((X3DNodeElementColorRGBA *)*ch_it)->Value, + tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedLineSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list<X3DNodeElementBase*>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_IndexedLineSet) + + if ((pNodeElement.Type == X3DElemType::ENET_IndexedTriangleSet) || + (pNodeElement.Type == X3DElemType::ENET_IndexedTriangleFanSet) || + (pNodeElement.Type == X3DElemType::ENET_IndexedTriangleStripSet)) { + X3DNodeElementIndexedSet &tnemesh = *((X3DNodeElementIndexedSet *)&pNodeElement); // create alias for convenience + + // at first search for <Coordinate> node and create mesh. + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((X3DNodeElementColorRGBA *)*ch_it)->Value, + tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == X3DElemType::ENET_Normal) + X3DGeoHelper::add_normal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((X3DNodeElementNormal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_TextureCoordinate) + X3DGeoHelper::add_tex_coord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((X3DNodeElementTextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedTriangleSet or IndexedTriangleFanSet, or \ + IndexedTriangleStripSet: " + + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list<X3DNodeElementBase*>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if((pNodeElement.Type == X3DElemType::ENET_IndexedTriangleFanSet) || (pNodeElement.Type == X3DElemType::ENET_IndexedTriangleStripSet)) + + if (pNodeElement.Type == X3DElemType::ENET_Extrusion) { + X3DNodeElementIndexedSet &tnemesh = *((X3DNodeElementIndexedSet *)&pNodeElement); // create alias for convenience + + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, tnemesh.Vertices); + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_Extrusion) + + // + // Primitives sets + // + if (pNodeElement.Type == X3DElemType::ENET_PointSet) { + X3DNodeElementSet &tnemesh = *((X3DNodeElementSet *)&pNodeElement); // create alias for convenience + + // at first search for <Coordinate> node and create mesh. + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + std::vector<aiVector3D> vec_copy; + + vec_copy.reserve(((X3DNodeElementCoordinate *)*ch_it)->Value.size()); + for (std::list<aiVector3D>::const_iterator it = ((X3DNodeElementCoordinate *)*ch_it)->Value.begin(); + it != ((X3DNodeElementCoordinate *)*ch_it)->Value.end(); ++it) { + vec_copy.push_back(*it); + } + + *pMesh = StandardShapes::MakeMesh(vec_copy, 1); + } + } + + // copy additional information from children + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColor *)*ch_it)->Value, true); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColorRGBA *)*ch_it)->Value, true); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of PointSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list<X3DNodeElementBase*>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_PointSet) + + if (pNodeElement.Type == X3DElemType::ENET_LineSet) { + X3DNodeElementSet &tnemesh = *((X3DNodeElementSet *)&pNodeElement); // create alias for convenience + + // at first search for <Coordinate> node and create mesh. + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColor *)*ch_it)->Value, true); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColorRGBA *)*ch_it)->Value, true); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of LineSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list<X3DNodeElementBase*>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_LineSet) + + if (pNodeElement.Type == X3DElemType::ENET_TriangleFanSet) { + X3DNodeElementSet &tnemesh = *((X3DNodeElementSet *)&pNodeElement); // create alias for convenience + + // at first search for <Coordinate> node and create mesh. + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if (nullptr == *pMesh) { + break; + } + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == X3DElemType::ENET_Normal) + X3DGeoHelper::add_normal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((X3DNodeElementNormal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_TextureCoordinate) + X3DGeoHelper::add_tex_coord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((X3DNodeElementTextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeFanSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list<X3DNodeElementBase*>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_TriangleFanSet) + + if (pNodeElement.Type == X3DElemType::ENET_TriangleSet) { + X3DNodeElementSet &tnemesh = *((X3DNodeElementSet *)&pNodeElement); // create alias for convenience + + // at first search for <Coordinate> node and create mesh. + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + std::vector<aiVector3D> vec_copy; + + vec_copy.reserve(((X3DNodeElementCoordinate *)*ch_it)->Value.size()); + for (std::list<aiVector3D>::const_iterator it = ((X3DNodeElementCoordinate *)*ch_it)->Value.begin(); + it != ((X3DNodeElementCoordinate *)*ch_it)->Value.end(); ++it) { + vec_copy.push_back(*it); + } + + *pMesh = StandardShapes::MakeMesh(vec_copy, 3); + } + } + + // copy additional information from children + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == X3DElemType::ENET_Normal) + X3DGeoHelper::add_normal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((X3DNodeElementNormal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_TextureCoordinate) + X3DGeoHelper::add_tex_coord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((X3DNodeElementTextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list<X3DNodeElementBase*>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_TriangleSet) + + if (pNodeElement.Type == X3DElemType::ENET_TriangleStripSet) { + X3DNodeElementSet &tnemesh = *((X3DNodeElementSet *)&pNodeElement); // create alias for convenience + + // at first search for <Coordinate> node and create mesh. + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list<X3DNodeElementBase *>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == X3DElemType::ENET_Normal) + X3DGeoHelper::add_normal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((X3DNodeElementNormal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_TextureCoordinate) + X3DGeoHelper::add_tex_coord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((X3DNodeElementTextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TriangleStripSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list<X3DNodeElementBase*>::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_TriangleStripSet) + + throw DeadlyImportError("Postprocess_BuildMesh. Unknown mesh type: " + ai_to_string(pNodeElement.Type) + "."); +} + +void X3DImporter::Postprocess_BuildNode(const X3DNodeElementBase &pNodeElement, aiNode &pSceneNode, std::list<aiMesh *> &pSceneMeshList, + std::list<aiMaterial *> &pSceneMaterialList, std::list<aiLight *> &pSceneLightList) const { + std::list<X3DNodeElementBase *>::const_iterator chit_begin = pNodeElement.Children.begin(); + std::list<X3DNodeElementBase *>::const_iterator chit_end = pNodeElement.Children.end(); + std::list<aiNode *> SceneNode_Child; + std::list<unsigned int> SceneNode_Mesh; + + // At first read all metadata + Postprocess_CollectMetadata(pNodeElement, pSceneNode); + // check if we have deal with grouping node. Which can contain transformation or switch + if (pNodeElement.Type == X3DElemType::ENET_Group) { + const X3DNodeElementGroup &tne_group = *((X3DNodeElementGroup *)&pNodeElement); // create alias for convenience + + pSceneNode.mTransformation = tne_group.Transformation; + if (tne_group.UseChoice) { + // If Choice is less than zero or greater than the number of nodes in the children field, nothing is chosen. + if ((tne_group.Choice < 0) || ((size_t)tne_group.Choice >= pNodeElement.Children.size())) { + chit_begin = pNodeElement.Children.end(); + chit_end = pNodeElement.Children.end(); + } else { + for (size_t i = 0; i < (size_t)tne_group.Choice; i++) + ++chit_begin; // forward iterator to chosen node. + + chit_end = chit_begin; + ++chit_end; // point end iterator to next element after chosen node. + } + } // if(tne_group.UseChoice) + } // if(pNodeElement.Type == X3DElemType::ENET_Group) + + // Reserve memory for fast access and check children. + for (std::list<X3DNodeElementBase *>::const_iterator it = chit_begin; it != chit_end; ++it) { // in this loop we do not read metadata because it's already read at begin. + if ((*it)->Type == X3DElemType::ENET_Group) { + // if child is group then create new node and do recursive call. + aiNode *new_node = new aiNode; + + new_node->mName = (*it)->ID; + new_node->mParent = &pSceneNode; + SceneNode_Child.push_back(new_node); + Postprocess_BuildNode(**it, *new_node, pSceneMeshList, pSceneMaterialList, pSceneLightList); + } else if ((*it)->Type == X3DElemType::ENET_Shape) { + // shape can contain only one geometry and one appearance nodes. + Postprocess_BuildShape(*((X3DNodeElementShape *)*it), SceneNode_Mesh, pSceneMeshList, pSceneMaterialList); + } else if (((*it)->Type == X3DElemType::ENET_DirectionalLight) || ((*it)->Type == X3DElemType::ENET_PointLight) || + ((*it)->Type == X3DElemType::ENET_SpotLight)) { + Postprocess_BuildLight(*((X3DNodeElementLight *)*it), pSceneLightList); + } else if (!PostprocessHelper_ElementIsMetadata((*it)->Type)) // skip metadata + { + throw DeadlyImportError("Postprocess_BuildNode. Unknown type: " + ai_to_string((*it)->Type) + "."); + } + } // for(std::list<X3DNodeElementBase*>::const_iterator it = chit_begin; it != chit_end; it++) + + // copy data about children and meshes to aiNode. + if (!SceneNode_Child.empty()) { + std::list<aiNode *>::const_iterator it = SceneNode_Child.begin(); + + pSceneNode.mNumChildren = static_cast<unsigned int>(SceneNode_Child.size()); + pSceneNode.mChildren = new aiNode *[pSceneNode.mNumChildren]; + for (size_t i = 0; i < pSceneNode.mNumChildren; i++) + pSceneNode.mChildren[i] = *it++; + } + + if (!SceneNode_Mesh.empty()) { + std::list<unsigned int>::const_iterator it = SceneNode_Mesh.begin(); + + pSceneNode.mNumMeshes = static_cast<unsigned int>(SceneNode_Mesh.size()); + pSceneNode.mMeshes = new unsigned int[pSceneNode.mNumMeshes]; + for (size_t i = 0; i < pSceneNode.mNumMeshes; i++) + pSceneNode.mMeshes[i] = *it++; + } + + // that's all. return to previous deals +} + +void X3DImporter::Postprocess_BuildShape(const X3DNodeElementShape &pShapeNodeElement, std::list<unsigned int> &pNodeMeshInd, + std::list<aiMesh *> &pSceneMeshList, std::list<aiMaterial *> &pSceneMaterialList) const { + aiMaterial *tmat = nullptr; + aiMesh *tmesh = nullptr; + X3DElemType mesh_type = X3DElemType::ENET_Invalid; + unsigned int mat_ind = 0; + + for (std::list<X3DNodeElementBase *>::const_iterator it = pShapeNodeElement.Children.begin(); it != pShapeNodeElement.Children.end(); ++it) { + if (PostprocessHelper_ElementIsMesh((*it)->Type)) { + Postprocess_BuildMesh(**it, &tmesh); + if (tmesh != nullptr) { + // if mesh successfully built then add data about it to arrays + pNodeMeshInd.push_back(static_cast<unsigned int>(pSceneMeshList.size())); + pSceneMeshList.push_back(tmesh); + // keep mesh type. Need above for texture coordinate generation. + mesh_type = (*it)->Type; + } + } else if ((*it)->Type == X3DElemType::ENET_Appearance) { + Postprocess_BuildMaterial(**it, &tmat); + if (tmat != nullptr) { + // if material successfully built then add data about it to array + mat_ind = static_cast<unsigned int>(pSceneMaterialList.size()); + pSceneMaterialList.push_back(tmat); + } + } + } // for(std::list<X3DNodeElementBase*>::const_iterator it = pShapeNodeElement.Children.begin(); it != pShapeNodeElement.Children.end(); it++) + + // associate read material with read mesh. + if ((tmesh != nullptr) && (tmat != nullptr)) { + tmesh->mMaterialIndex = mat_ind; + // Check texture mapping. If material has texture but mesh has no texture coordinate then try to ask Assimp to generate texture coordinates. + if ((tmat->GetTextureCount(aiTextureType_DIFFUSE) != 0) && !tmesh->HasTextureCoords(0)) { + int32_t tm; + aiVector3D tvec3; + + switch (mesh_type) { + case X3DElemType::ENET_Box: + tm = aiTextureMapping_BOX; + break; + case X3DElemType::ENET_Cone: + case X3DElemType::ENET_Cylinder: + tm = aiTextureMapping_CYLINDER; + break; + case X3DElemType::ENET_Sphere: + tm = aiTextureMapping_SPHERE; + break; + default: + tm = aiTextureMapping_PLANE; + break; + } // switch(mesh_type) + + tmat->AddProperty(&tm, 1, AI_MATKEY_MAPPING_DIFFUSE(0)); + } // if((tmat->GetTextureCount(aiTextureType_DIFFUSE) != 0) && !tmesh->HasTextureCoords(0)) + } // if((tmesh != nullptr) && (tmat != nullptr)) +} + +void X3DImporter::Postprocess_CollectMetadata(const X3DNodeElementBase &pNodeElement, aiNode &pSceneNode) const { + std::list<X3DNodeElementBase *> meta_list; + size_t meta_idx; + + PostprocessHelper_CollectMetadata(pNodeElement, meta_list); // find metadata in current node element. + if (!meta_list.empty()) { + if (pSceneNode.mMetaData != nullptr) { + throw DeadlyImportError("Postprocess. MetaData member in node are not nullptr. Something went wrong."); + } + + // copy collected metadata to output node. + pSceneNode.mMetaData = aiMetadata::Alloc(static_cast<unsigned int>(meta_list.size())); + meta_idx = 0; + for (std::list<X3DNodeElementBase *>::const_iterator it = meta_list.begin(); it != meta_list.end(); ++it, ++meta_idx) { + X3DNodeElementMeta *cur_meta = (X3DNodeElementMeta *)*it; + + // due to limitations we can add only first element of value list. + // Add an element according to its type. + if ((*it)->Type == X3DElemType::ENET_MetaBoolean) { + if (((X3DNodeElementMetaBoolean *)cur_meta)->Value.size() > 0) + pSceneNode.mMetaData->Set(static_cast<unsigned int>(meta_idx), cur_meta->Name, *(((X3DNodeElementMetaBoolean *)cur_meta)->Value.begin()) == true); + } else if ((*it)->Type == X3DElemType::ENET_MetaDouble) { + if (((X3DNodeElementMetaDouble *)cur_meta)->Value.size() > 0) + pSceneNode.mMetaData->Set(static_cast<unsigned int>(meta_idx), cur_meta->Name, (float)*(((X3DNodeElementMetaDouble *)cur_meta)->Value.begin())); + } else if ((*it)->Type == X3DElemType::ENET_MetaFloat) { + if (((X3DNodeElementMetaFloat *)cur_meta)->Value.size() > 0) + pSceneNode.mMetaData->Set(static_cast<unsigned int>(meta_idx), cur_meta->Name, *(((X3DNodeElementMetaFloat *)cur_meta)->Value.begin())); + } else if ((*it)->Type == X3DElemType::ENET_MetaInteger) { + if (((X3DNodeElementMetaInt *)cur_meta)->Value.size() > 0) + pSceneNode.mMetaData->Set(static_cast<unsigned int>(meta_idx), cur_meta->Name, *(((X3DNodeElementMetaInt *)cur_meta)->Value.begin())); + } else if ((*it)->Type == X3DElemType::ENET_MetaString) { + if (((X3DNodeElementMetaString *)cur_meta)->Value.size() > 0) { + aiString tstr(((X3DNodeElementMetaString *)cur_meta)->Value.begin()->data()); + + pSceneNode.mMetaData->Set(static_cast<unsigned int>(meta_idx), cur_meta->Name, tstr); + } + } else { + throw DeadlyImportError("Postprocess. Unknown metadata type."); + } // if((*it)->Type == X3DElemType::ENET_Meta*) else + } // for(std::list<X3DNodeElementBase*>::const_iterator it = meta_list.begin(); it != meta_list.end(); it++) + } // if( !meta_list.empty() ) +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter_Rendering.cpp b/libs/assimp/code/AssetLib/X3D/X3DImporter_Rendering.cpp new file mode 100644 index 0000000..66a30a9 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter_Rendering.cpp @@ -0,0 +1,993 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 X3DImporter_Rendering.cpp +/// \brief Parsing data from nodes of "Rendering" set of X3D. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" + +namespace Assimp { + +// <Color +// DEF="" ID +// USE="" IDREF +// color="" MFColor [inputOutput] +// /> +void X3DImporter::readColor(XmlNode &node) { + std::string use, def; + std::list<aiColor3D> color; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getColor3DListAttribute(node, "color", color); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Color, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementColor(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementColor *)ne)->Value = color; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Color"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <ColorRGBA +// DEF="" ID +// USE="" IDREF +// color="" MFColorRGBA [inputOutput] +// /> +void X3DImporter::readColorRGBA(XmlNode &node) { + std::string use, def; + std::list<aiColor4D> color; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getColor4DListAttribute(node, "color", color); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ColorRGBA, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementColorRGBA(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementColorRGBA *)ne)->Value = color; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "ColorRGBA"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <Coordinate +// DEF="" ID +// USE="" IDREF +// point="" MFVec3f [inputOutput] +// /> +void X3DImporter::readCoordinate(XmlNode &node) { + std::string use, def; + std::list<aiVector3D> point; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector3DListAttribute(node, "point", point); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Coordinate, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementCoordinate(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementCoordinate *)ne)->Value = point; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Coordinate"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <IndexedLineSet +// DEF="" ID +// USE="" IDREF +// colorIndex="" MFInt32 [initializeOnly] +// colorPerVertex="true" SFBool [initializeOnly] +// coordIndex="" MFInt32 [initializeOnly] +// > +// <!-- ColorCoordinateContentModel --> +// ColorCoordinateContentModel is the child-node content model corresponding to IndexedLineSet, LineSet and PointSet. ColorCoordinateContentModel can +// contain any-order Coordinate node with Color (or ColorRGBA) node. No more than one instance of any single node type is allowed. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </IndexedLineSet> +void X3DImporter::readIndexedLineSet(XmlNode &node) { + std::string use, def; + std::vector<int32_t> colorIndex; + bool colorPerVertex = true; + std::vector<int32_t> coordIndex; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getInt32ArrayAttribute(node, "colorIndex", colorIndex); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + X3DXmlHelper::getInt32ArrayAttribute(node, "coordIndex", coordIndex); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedLineSet, ne); + } else { + // check data + if ((coordIndex.size() < 2) || ((coordIndex.back() == (-1)) && (coordIndex.size() < 3))) + throw DeadlyImportError("IndexedLineSet must contain not empty \"coordIndex\" attribute."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_IndexedLineSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementIndexedSet &ne_alias = *((X3DNodeElementIndexedSet *)ne); + + ne_alias.ColorIndex = colorIndex; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.CoordIndex = coordIndex; + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for Color and Coordinate nodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("IndexedLineSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <IndexedTriangleFanSet +// DEF="" ID +// USE="" IDREF +// ccw="true" SFBool [initializeOnly] +// colorPerVertex="true" SFBool [initializeOnly] +// index="" MFInt32 [initializeOnly] +// normalPerVertex="true" SFBool [initializeOnly] +// solid="true" SFBool [initializeOnly] +// > +// <!-- ComposedGeometryContentModel --> +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </IndexedTriangleFanSet> +void X3DImporter::readIndexedTriangleFanSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + std::vector<int32_t> index; + bool normalPerVertex = true; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + X3DXmlHelper::getInt32ArrayAttribute(node, "index", index); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedTriangleFanSet, ne); + } else { + // check data + if (index.size() == 0) throw DeadlyImportError("IndexedTriangleFanSet must contain not empty \"index\" attribute."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_IndexedTriangleFanSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementIndexedSet &ne_alias = *((X3DNodeElementIndexedSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + + ne_alias.CoordIndex.clear(); + int counter = 0; + int32_t idx[3]; + for (std::vector<int32_t>::const_iterator idx_it = index.begin(); idx_it != index.end(); ++idx_it) { + idx[2] = *idx_it; + if (idx[2] < 0) { + counter = 0; + } else { + if (counter >= 2) { + if (ccw) { + ne_alias.CoordIndex.push_back(idx[0]); + ne_alias.CoordIndex.push_back(idx[1]); + ne_alias.CoordIndex.push_back(idx[2]); + } else { + ne_alias.CoordIndex.push_back(idx[0]); + ne_alias.CoordIndex.push_back(idx[2]); + ne_alias.CoordIndex.push_back(idx[1]); + } + ne_alias.CoordIndex.push_back(-1); + idx[1] = idx[2]; + } else { + idx[counter] = idx[2]; + } + ++counter; + } + } // for(std::list<int32_t>::const_iterator idx_it = index.begin(); idx_it != ne_alias.index.end(); idx_it++) + + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("IndexedTriangleFanSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <IndexedTriangleSet +// DEF="" ID +// USE="" IDREF +// ccw="true" SFBool [initializeOnly] +// colorPerVertex="true" SFBool [initializeOnly] +// index="" MFInt32 [initializeOnly] +// normalPerVertex="true" SFBool [initializeOnly] +// solid="true" SFBool [initializeOnly] +// > +// <!-- ComposedGeometryContentModel --> +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </IndexedTriangleSet> +void X3DImporter::readIndexedTriangleSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + std::vector<int32_t> index; + bool normalPerVertex = true; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + X3DXmlHelper::getInt32ArrayAttribute(node, "index", index); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedTriangleSet, ne); + } else { + // check data + if (index.size() == 0) throw DeadlyImportError("IndexedTriangleSet must contain not empty \"index\" attribute."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_IndexedTriangleSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementIndexedSet &ne_alias = *((X3DNodeElementIndexedSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + + ne_alias.CoordIndex.clear(); + int counter = 0; + int32_t idx[3]; + for (std::vector<int32_t>::const_iterator idx_it = index.begin(); idx_it != index.end(); ++idx_it) { + idx[counter++] = *idx_it; + if (counter > 2) { + counter = 0; + if (ccw) { + ne_alias.CoordIndex.push_back(idx[0]); + ne_alias.CoordIndex.push_back(idx[1]); + ne_alias.CoordIndex.push_back(idx[2]); + } else { + ne_alias.CoordIndex.push_back(idx[0]); + ne_alias.CoordIndex.push_back(idx[2]); + ne_alias.CoordIndex.push_back(idx[1]); + } + ne_alias.CoordIndex.push_back(-1); + } + } // for(std::list<int32_t>::const_iterator idx_it = index.begin(); idx_it != ne_alias.index.end(); idx_it++) + + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("IndexedTriangleSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <IndexedTriangleStripSet +// DEF="" ID +// USE="" IDREF +// ccw="true" SFBool [initializeOnly] +// colorPerVertex="true" SFBool [initializeOnly] +// index="" MFInt32 [initializeOnly] +// normalPerVertex="true" SFBool [initializeOnly] +// solid="true" SFBool [initializeOnly] +// > +// <!-- ComposedGeometryContentModel --> +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </IndexedTriangleStripSet> +void X3DImporter::readIndexedTriangleStripSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + std::vector<int32_t> index; + bool normalPerVertex = true; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + X3DXmlHelper::getInt32ArrayAttribute(node, "index", index); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedTriangleStripSet, ne); + } else { + // check data + if (index.empty()) { + throw DeadlyImportError("IndexedTriangleStripSet must contain not empty \"index\" attribute."); + } + + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_IndexedTriangleStripSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementIndexedSet &ne_alias = *((X3DNodeElementIndexedSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + + ne_alias.CoordIndex.clear(); + int counter = 0; + int32_t idx[3]; + for (std::vector<int32_t>::const_iterator idx_it = index.begin(); idx_it != index.end(); ++idx_it) { + idx[2] = *idx_it; + if (idx[2] < 0) { + counter = 0; + } else { + if (counter >= 2) { + if (ccw) { + ne_alias.CoordIndex.push_back(idx[0]); + ne_alias.CoordIndex.push_back(idx[1]); + ne_alias.CoordIndex.push_back(idx[2]); + } else { + ne_alias.CoordIndex.push_back(idx[0]); + ne_alias.CoordIndex.push_back(idx[2]); + ne_alias.CoordIndex.push_back(idx[1]); + } + ne_alias.CoordIndex.push_back(-1); + } + idx[counter & 1] = idx[2]; + ++counter; + } + } // for(std::list<int32_t>::const_iterator idx_it = index.begin(); idx_it != ne_alias.index.end(); idx_it++) + + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("IndexedTriangleStripSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <LineSet +// DEF="" ID +// USE="" IDREF +// vertexCount="" MFInt32 [initializeOnly] +// > +// <!-- ColorCoordinateContentModel --> +// ColorCoordinateContentModel is the child-node content model corresponding to IndexedLineSet, LineSet and PointSet. ColorCoordinateContentModel can +// contain any-order Coordinate node with Color (or ColorRGBA) node. No more than one instance of any single node type is allowed. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </LineSet> +void X3DImporter::readLineSet(XmlNode &node) { + std::string use, def; + std::vector<int32_t> vertexCount; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getInt32ArrayAttribute(node, "vertexCount", vertexCount); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_LineSet, ne); + } else { + // check data + if (vertexCount.empty()) { + throw DeadlyImportError("LineSet must contain not empty \"vertexCount\" attribute."); + } + + // create and if needed - define new geometry object. + ne = new X3DNodeElementSet(X3DElemType::ENET_LineSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementSet &ne_alias = *((X3DNodeElementSet *)ne); + + ne_alias.VertexCount = vertexCount; + // create CoordIdx + size_t coord_num = 0; + + ne_alias.CoordIndex.clear(); + for (std::vector<int32_t>::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); ++vc_it) { + if (*vc_it < 2) throw DeadlyImportError("LineSet. vertexCount shall be greater than or equal to two."); + + for (int32_t i = 0; i < *vc_it; i++) + ne_alias.CoordIndex.push_back(static_cast<int32_t>(coord_num++)); // add vertices indices + + ne_alias.CoordIndex.push_back(-1); // add face delimiter. + } + + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("LineSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <PointSet +// DEF="" ID +// USE="" IDREF +// > +// <!-- ColorCoordinateContentModel --> +// ColorCoordinateContentModel is the child-node content model corresponding to IndexedLineSet, LineSet and PointSet. ColorCoordinateContentModel can +// contain any-order Coordinate node with Color (or ColorRGBA) node. No more than one instance of any single node type is allowed. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </PointSet> +void X3DImporter::readPointSet(XmlNode &node) { + std::string use, def; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_PointSet, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_PointSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("PointSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <TriangleFanSet +// DEF="" ID +// USE="" IDREF +// ccw="true" SFBool [initializeOnly] +// colorPerVertex="true" SFBool [initializeOnly] +// fanCount="" MFInt32 [inputOutput] +// normalPerVertex="true" SFBool [initializeOnly] +// solid="true" SFBool [initializeOnly] +// > +// <!-- ComposedGeometryContentModel --> +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </TriangleFanSet> +void X3DImporter::readTriangleFanSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + std::vector<int32_t> fanCount; + bool normalPerVertex = true; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + X3DXmlHelper::getInt32ArrayAttribute(node, "fanCount", fanCount); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleFanSet, ne); + } else { + // check data + if (fanCount.empty()) { + throw DeadlyImportError("TriangleFanSet must contain not empty \"fanCount\" attribute."); + } + + // create and if needed - define new geometry object. + ne = new X3DNodeElementSet(X3DElemType::ENET_TriangleFanSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementSet &ne_alias = *((X3DNodeElementSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.VertexCount = fanCount; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + // create CoordIdx + size_t coord_num_first, coord_num_prev; + + ne_alias.CoordIndex.clear(); + // assign indices for first triangle + coord_num_first = 0; + coord_num_prev = 1; + for (std::vector<int32_t>::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); ++vc_it) { + if (*vc_it < 3) throw DeadlyImportError("TriangleFanSet. fanCount shall be greater than or equal to three."); + + for (int32_t vc = 2; vc < *vc_it; vc++) { + if (ccw) { + // 2 1 + // 0 + ne_alias.CoordIndex.push_back(static_cast<int32_t>(coord_num_first)); // first vertex is a center and always is [0]. + ne_alias.CoordIndex.push_back(static_cast<int32_t>(coord_num_prev++)); + ne_alias.CoordIndex.push_back(static_cast<int32_t>(coord_num_prev)); + } else { + // 1 2 + // 0 + ne_alias.CoordIndex.push_back(static_cast<int32_t>(coord_num_first)); // first vertex is a center and always is [0]. + ne_alias.CoordIndex.push_back(static_cast<int32_t>(coord_num_prev + 1)); + ne_alias.CoordIndex.push_back(static_cast<int32_t>(coord_num_prev++)); + } // if(ccw) else + + ne_alias.CoordIndex.push_back(-1); // add face delimiter. + } // for(int32_t vc = 2; vc < *vc_it; vc++) + + coord_num_prev++; // that index will be center of next fan + coord_num_first = coord_num_prev++; // forward to next point - second point of fan + } // for(std::list<int32_t>::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); vc_it++) + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("TriangleFanSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <TriangleSet +// DEF="" ID +// USE="" IDREF +// ccw="true" SFBool [initializeOnly] +// colorPerVertex="true" SFBool [initializeOnly] +// normalPerVertex="true" SFBool [initializeOnly] +// solid="true" SFBool [initializeOnly] +// > +// <!-- ComposedGeometryContentModel --> +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </TriangleSet> +void X3DImporter::readTriangleSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + bool normalPerVertex = true; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleSet, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_TriangleSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementSet &ne_alias = *((X3DNodeElementSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("TriangleSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <TriangleStripSet +// DEF="" ID +// USE="" IDREF +// ccw="true" SFBool [initializeOnly] +// colorPerVertex="true" SFBool [initializeOnly] +// normalPerVertex="true" SFBool [initializeOnly] +// solid="true" SFBool [initializeOnly] +// stripCount="" MFInt32 [inputOutput] +// > +// <!-- ComposedGeometryContentModel --> +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// </TriangleStripSet> +void X3DImporter::readTriangleStripSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + std::vector<int32_t> stripCount; + bool normalPerVertex = true; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + X3DXmlHelper::getInt32ArrayAttribute(node, "stripCount", stripCount); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleStripSet, ne); + } else { + // check data + if (stripCount.size() == 0) throw DeadlyImportError("TriangleStripSet must contain not empty \"stripCount\" attribute."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementSet(X3DElemType::ENET_TriangleStripSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementSet &ne_alias = *((X3DNodeElementSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.VertexCount = stripCount; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + // create CoordIdx + size_t coord_num0, coord_num1, coord_num2; // indices of current triangle + bool odd_tri; // sequence of current triangle + size_t coord_num_sb; // index of first point of strip + + ne_alias.CoordIndex.clear(); + coord_num_sb = 0; + for (std::vector<int32_t>::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); ++vc_it) { + if (*vc_it < 3) throw DeadlyImportError("TriangleStripSet. stripCount shall be greater than or equal to three."); + + // set initial values for first triangle + coord_num0 = coord_num_sb; + coord_num1 = coord_num_sb + 1; + coord_num2 = coord_num_sb + 2; + odd_tri = true; + + for (int32_t vc = 2; vc < *vc_it; vc++) { + if (ccw) { + // 0 2 + // 1 + ne_alias.CoordIndex.push_back(static_cast<int32_t>(coord_num0)); + ne_alias.CoordIndex.push_back(static_cast<int32_t>(coord_num1)); + ne_alias.CoordIndex.push_back(static_cast<int32_t>(coord_num2)); + } else { + // 0 1 + // 2 + ne_alias.CoordIndex.push_back(static_cast<int32_t>(coord_num0)); + ne_alias.CoordIndex.push_back(static_cast<int32_t>(coord_num2)); + ne_alias.CoordIndex.push_back(static_cast<int32_t>(coord_num1)); + } // if(ccw) else + + ne_alias.CoordIndex.push_back(-1); // add face delimiter. + // prepare values for next triangle + if (odd_tri) { + coord_num0 = coord_num2; + coord_num2++; + } else { + coord_num1 = coord_num2; + coord_num2 = coord_num1 + 1; + } + + odd_tri = !odd_tri; + coord_num_sb = coord_num2; // that index will be start of next strip + } // for(int32_t vc = 2; vc < *vc_it; vc++) + } // for(std::list<int32_t>::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); vc_it++) + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("TriangleStripSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <Normal +// DEF="" ID +// USE="" IDREF +// vector="" MFVec3f [inputOutput] +// /> +void X3DImporter::readNormal(XmlNode &node) { + std::string use, def; + std::list<aiVector3D> vector; + X3DNodeElementBase *ne=nullptr; + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector3DListAttribute(node, "vector", vector); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Normal, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementNormal(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementNormal *)ne)->Value = vector; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Normal"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter_Shape.cpp b/libs/assimp/code/AssetLib/X3D/X3DImporter_Shape.cpp new file mode 100644 index 0000000..1c472e1 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter_Shape.cpp @@ -0,0 +1,241 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 X3DImporter_Shape.cpp +/// \brief Parsing data from nodes of "Shape" set of X3D. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" + +namespace Assimp { + +void X3DImporter::readShape(XmlNode &node) { + std::string use, def; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Shape, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementShape(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for appearance node + if (currentChildName == "Appearance") readAppearance(currentChildNode); + // check for X3DGeometryNodes + else if (currentChildName == "Arc2D") + readArc2D(currentChildNode); + else if (currentChildName == "ArcClose2D") + readArcClose2D(currentChildNode); + else if (currentChildName == "Circle2D") + readCircle2D(currentChildNode); + else if (currentChildName == "Disk2D") + readDisk2D(currentChildNode); + else if (currentChildName == "Polyline2D") + readPolyline2D(currentChildNode); + else if (currentChildName == "Polypoint2D") + readPolypoint2D(currentChildNode); + else if (currentChildName == "Rectangle2D") + readRectangle2D(currentChildNode); + else if (currentChildName == "TriangleSet2D") + readTriangleSet2D(currentChildNode); + else if (currentChildName == "Box") + readBox(currentChildNode); + else if (currentChildName == "Cone") + readCone(currentChildNode); + else if (currentChildName == "Cylinder") + readCylinder(currentChildNode); + else if (currentChildName == "ElevationGrid") + readElevationGrid(currentChildNode); + else if (currentChildName == "Extrusion") + readExtrusion(currentChildNode); + else if (currentChildName == "IndexedFaceSet") + readIndexedFaceSet(currentChildNode); + else if (currentChildName == "Sphere") + readSphere(currentChildNode); + else if (currentChildName == "IndexedLineSet") + readIndexedLineSet(currentChildNode); + else if (currentChildName == "LineSet") + readLineSet(currentChildNode); + else if (currentChildName == "PointSet") + readPointSet(currentChildNode); + else if (currentChildName == "IndexedTriangleFanSet") + readIndexedTriangleFanSet(currentChildNode); + else if (currentChildName == "IndexedTriangleSet") + readIndexedTriangleSet(currentChildNode); + else if (currentChildName == "IndexedTriangleStripSet") + readIndexedTriangleStripSet(currentChildNode); + else if (currentChildName == "TriangleFanSet") + readTriangleFanSet(currentChildNode); + else if (currentChildName == "TriangleSet") + readTriangleSet(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("Shape", currentChildNode); + } + + ParseHelper_Node_Exit(); + } // if (!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <Appearance +// DEF="" ID +// USE="" IDREF +// > +// <!-- AppearanceChildContentModel --> +// "Child-node content model corresponding to X3DAppearanceChildNode. Appearance can contain FillProperties, LineProperties, Material, any Texture node and +// any TextureTransform node, in any order. No more than one instance of these nodes is allowed. Appearance may also contain multiple shaders (ComposedShader, +// PackagedShader, ProgramShader). +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model." +// </Appearance> +void X3DImporter::readAppearance(XmlNode &node) { + std::string use, def; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Appearance, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementAppearance(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + if (currentChildName == "Material") + readMaterial(currentChildNode); + else if (currentChildName == "ImageTexture") + readImageTexture(currentChildNode); + else if (currentChildName == "TextureTransform") + readTextureTransform(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("Appearance", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <Material +// DEF="" ID +// USE="" IDREF +// ambientIntensity="0.2" SFFloat [inputOutput] +// diffuseColor="0.8 0.8 0.8" SFColor [inputOutput] +// emissiveColor="0 0 0" SFColor [inputOutput] +// shininess="0.2" SFFloat [inputOutput] +// specularColor="0 0 0" SFColor [inputOutput] +// transparency="0" SFFloat [inputOutput] +// /> +void X3DImporter::readMaterial(XmlNode &node) { + std::string use, def; + float ambientIntensity = 0.2f; + float shininess = 0.2f; + float transparency = 0; + aiColor3D diffuseColor(0.8f, 0.8f, 0.8f); + aiColor3D emissiveColor(0, 0, 0); + aiColor3D specularColor(0, 0, 0); + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "ambientIntensity", ambientIntensity); + XmlParser::getFloatAttribute(node, "shininess", shininess); + XmlParser::getFloatAttribute(node, "transparency", transparency); + X3DXmlHelper::getColor3DAttribute(node, "diffuseColor", diffuseColor); + X3DXmlHelper::getColor3DAttribute(node, "emissiveColor", emissiveColor); + X3DXmlHelper::getColor3DAttribute(node, "specularColor", specularColor); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Material, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementMaterial(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementMaterial *)ne)->AmbientIntensity = ambientIntensity; + ((X3DNodeElementMaterial *)ne)->Shininess = shininess; + ((X3DNodeElementMaterial *)ne)->Transparency = transparency; + ((X3DNodeElementMaterial *)ne)->DiffuseColor = diffuseColor; + ((X3DNodeElementMaterial *)ne)->EmissiveColor = emissiveColor; + ((X3DNodeElementMaterial *)ne)->SpecularColor = specularColor; + // check for child nodes + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Material"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/X3D/X3DImporter_Texturing.cpp b/libs/assimp/code/AssetLib/X3D/X3DImporter_Texturing.cpp new file mode 100644 index 0000000..32c1a90 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DImporter_Texturing.cpp @@ -0,0 +1,179 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 X3DImporter_Texturing.cpp +/// \brief Parsing data from nodes of "Texturing" set of X3D. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" + +namespace Assimp { + +// <ImageTexture +// DEF="" ID +// USE="" IDREF +// repeatS="true" SFBool +// repeatT="true" SFBool +// url="" MFString +// /> +// When the url field contains no values ([]), texturing is disabled. +void X3DImporter::readImageTexture(XmlNode &node) { + std::string use, def; + bool repeatS = true; + bool repeatT = true; + std::list<std::string> url; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "repeatS", repeatS); + XmlParser::getBoolAttribute(node, "repeatT", repeatT); + X3DXmlHelper::getStringListAttribute(node, "url", url); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ImageTexture, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementImageTexture(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementImageTexture *)ne)->RepeatS = repeatS; + ((X3DNodeElementImageTexture *)ne)->RepeatT = repeatT; + // Attribute "url" can contain list of strings. But we need only one - first. + if (!url.empty()) + ((X3DNodeElementImageTexture *)ne)->URL = url.front(); + else + ((X3DNodeElementImageTexture *)ne)->URL = ""; + + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "ImageTexture"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <TextureCoordinate +// DEF="" ID +// USE="" IDREF +// point="" MFVec3f [inputOutput] +// /> +void X3DImporter::readTextureCoordinate(XmlNode &node) { + std::string use, def; + std::list<aiVector2D> point; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector2DListAttribute(node, "point", point); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TextureCoordinate, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementTextureCoordinate(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementTextureCoordinate *)ne)->Value = point; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "TextureCoordinate"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// <TextureTransform +// DEF="" ID +// USE="" IDREF +// center="0 0" SFVec2f [inputOutput] +// rotation="0" SFFloat [inputOutput] +// scale="1 1" SFVec2f [inputOutput] +// translation="0 0" SFVec2f [inputOutput] +// /> +void X3DImporter::readTextureTransform(XmlNode &node) { + std::string use, def; + aiVector2D center(0, 0); + float rotation = 0; + aiVector2D scale(1, 1); + aiVector2D translation(0, 0); + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector2DAttribute(node, "center", center); + XmlParser::getFloatAttribute(node, "rotation", rotation); + X3DXmlHelper::getVector2DAttribute(node, "scale", scale); + X3DXmlHelper::getVector2DAttribute(node, "translation", translation); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TextureTransform, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementTextureTransform(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementTextureTransform *)ne)->Center = center; + ((X3DNodeElementTextureTransform *)ne)->Rotation = rotation; + ((X3DNodeElementTextureTransform *)ne)->Scale = scale; + ((X3DNodeElementTextureTransform *)ne)->Translation = translation; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "TextureTransform"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/libs/assimp/code/AssetLib/X3D/X3DXmlHelper.cpp b/libs/assimp/code/AssetLib/X3D/X3DXmlHelper.cpp new file mode 100644 index 0000000..ff24b74 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DXmlHelper.cpp @@ -0,0 +1,294 @@ +#include "X3DXmlHelper.h" +#include "X3DImporter.hpp" + +#include <assimp/ParsingUtils.h> + +namespace Assimp { + +bool X3DXmlHelper::getColor3DAttribute(XmlNode &node, const char *attributeName, aiColor3D &color) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector<std::string> values; + tokenize<std::string>(val, values, " "); + if (values.size() != 3) { + Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + return false; + } + auto it = values.begin(); + color.r = stof(*it++); + color.g = stof(*it++); + color.b = stof(*it); + return true; + } + return false; +} + +bool X3DXmlHelper::getVector2DAttribute(XmlNode &node, const char *attributeName, aiVector2D &color) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector<std::string> values; + tokenize<std::string>(val, values, " "); + if (values.size() != 2) { + Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + return false; + } + auto it = values.begin(); + color.x = stof(*it++); + color.y = stof(*it); + return true; + } + return false; +} + +bool X3DXmlHelper::getVector3DAttribute(XmlNode &node, const char *attributeName, aiVector3D &color) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector<std::string> values; + tokenize<std::string>(val, values, " "); + if (values.size() != 3) { + Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + return false; + } + auto it = values.begin(); + color.x = stof(*it++); + color.y = stof(*it++); + color.z = stof(*it); + return true; + } + return false; +} + +bool X3DXmlHelper::getBooleanArrayAttribute(XmlNode &node, const char *attributeName, std::vector<bool> &boolArray) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector<std::string> values; + tokenize<std::string>(val, values, " "); + auto it = values.begin(); + while (it != values.end()) { + auto s = *it++; + if (!s.empty()) + boolArray.push_back(s[0] == 't' || s[0] == '1'); + else + Throw_ConvertFail_Str2ArrB(node.name(), attributeName); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getDoubleArrayAttribute(XmlNode &node, const char *attributeName, std::vector<double> &doubleArray) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector<std::string> values; + tokenize<std::string>(val, values, " "); + auto it = values.begin(); + while (it != values.end()) { + auto s = *it++; + if (!s.empty()) + doubleArray.push_back(atof(s.c_str())); + else + Throw_ConvertFail_Str2ArrD(node.name(), attributeName); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getFloatArrayAttribute(XmlNode &node, const char *attributeName, std::vector<float> &floatArray) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector<std::string> values; + tokenize<std::string>(val, values, " "); + auto it = values.begin(); + while (it != values.end()) { + auto s = *it++; + if (!s.empty()) + floatArray.push_back((float)atof(s.c_str())); + else + Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getInt32ArrayAttribute(XmlNode &node, const char *attributeName, std::vector<int32_t> &intArray) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector<std::string> values; + tokenize<std::string>(val, values, " "); + auto it = values.begin(); + while (it != values.end()) { + auto s = *it++; + if (!s.empty()) + intArray.push_back((int32_t)atof(s.c_str())); + else + Throw_ConvertFail_Str2ArrI(node.name(), attributeName); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getStringListAttribute(XmlNode &node, const char *attributeName, std::list<std::string> &stringList) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector<std::string> values; + tokenize<std::string>(val, values, " "); + auto it = values.begin(); + std::string currentConcat = ""; + bool inQuotes = false; + while (it != values.end()) { + auto s = *it++; + if (!s.empty()) { + if (inQuotes) { + if (*(s.rbegin()) == '"') { + stringList.push_back(currentConcat + s.substr(0, s.length() - 1)); + currentConcat = ""; + inQuotes = false; + } else { + currentConcat += " " + s; + } + } else { + if (s[0] == '"') { + currentConcat = s.substr(1); + inQuotes = true; + } else { + stringList.push_back(s); + } + } + } else if (!inQuotes) + Throw_ConvertFail_Str2ArrI(node.name(), attributeName); + } + if (inQuotes) Throw_ConvertFail_Str2ArrI(node.name(), attributeName); + return true; + } + return false; +} + +bool X3DXmlHelper::getStringArrayAttribute(XmlNode &node, const char *attributeName, std::vector<std::string> &stringArray) { + std::list<std::string> tlist; + + if (getStringListAttribute(node, attributeName, tlist)) { + if (!tlist.empty()) { + stringArray.reserve(tlist.size()); + for (std::list<std::string>::iterator it = tlist.begin(); it != tlist.end(); ++it) { + stringArray.push_back(*it); + } + return true; + } + } + return false; +} + +bool X3DXmlHelper::getVector2DListAttribute(XmlNode &node, const char *attributeName, std::list<aiVector2D> &vectorList) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector<std::string> values; + tokenize<std::string>(val, values, " "); + if (values.size() % 2) Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + auto it = values.begin(); + while (it != values.end()) { + aiVector2D tvec; + + tvec.x = (float)atof((*it++).c_str()); + tvec.y = (float)atof((*it++).c_str()); + vectorList.push_back(tvec); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getVector2DArrayAttribute(XmlNode &node, const char *attributeName, std::vector<aiVector2D> &vectorArray) { + std::list<aiVector2D> tlist; + + if (getVector2DListAttribute(node, attributeName, tlist)) { + if (!tlist.empty()) { + vectorArray.reserve(tlist.size()); + for (std::list<aiVector2D>::iterator it = tlist.begin(); it != tlist.end(); ++it) { + vectorArray.push_back(*it); + } + return true; + } + } + return false; +} + +bool X3DXmlHelper::getVector3DListAttribute(XmlNode &node, const char *attributeName, std::list<aiVector3D> &vectorList) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector<std::string> values; + tokenize<std::string>(val, values, " "); + if (values.size() % 3 != 0) Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + auto it = values.begin(); + while (it != values.end()) { + aiVector3D tvec; + + tvec.x = (float)atof((*it++).c_str()); + tvec.y = (float)atof((*it++).c_str()); + tvec.z = (float)atof((*it++).c_str()); + vectorList.push_back(tvec); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getVector3DArrayAttribute(XmlNode &node, const char *attributeName, std::vector<aiVector3D> &vectorArray) { + std::list<aiVector3D> tlist; + + if (getVector3DListAttribute(node, attributeName, tlist)) { + if (!tlist.empty()) { + vectorArray.reserve(tlist.size()); + for (std::list<aiVector3D>::iterator it = tlist.begin(); it != tlist.end(); ++it) { + vectorArray.push_back(*it); + } + return true; + } + } + return false; +} + +bool X3DXmlHelper::getColor3DListAttribute(XmlNode &node, const char *attributeName, std::list<aiColor3D> &colorList) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector<std::string> values; + tokenize<std::string>(val, values, " "); + if (values.size() % 3 != 0) Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + auto it = values.begin(); + while (it != values.end()) { + aiColor3D tvec; + + tvec.r = (float)atof((*it++).c_str()); + tvec.g = (float)atof((*it++).c_str()); + tvec.b = (float)atof((*it++).c_str()); + colorList.push_back(tvec); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getColor4DListAttribute(XmlNode &node, const char *attributeName, std::list<aiColor4D> &colorList) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector<std::string> values; + tokenize<std::string>(val, values, " "); + if (values.size() % 4 != 0) Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + auto it = values.begin(); + while (it != values.end()) { + aiColor4D tvec; + + tvec.r = (float)atof((*it++).c_str()); + tvec.g = (float)atof((*it++).c_str()); + tvec.b = (float)atof((*it++).c_str()); + tvec.a = (float)atof((*it++).c_str()); + colorList.push_back(tvec); + } + return true; + } + return false; +} + +} // namespace Assimp diff --git a/libs/assimp/code/AssetLib/X3D/X3DXmlHelper.h b/libs/assimp/code/AssetLib/X3D/X3DXmlHelper.h new file mode 100644 index 0000000..dd305f8 --- /dev/null +++ b/libs/assimp/code/AssetLib/X3D/X3DXmlHelper.h @@ -0,0 +1,30 @@ +#pragma once + +#include <assimp/XmlParser.h> +#include <assimp/types.h> +#include <list> + +namespace Assimp { + +class X3DXmlHelper { +public: + static bool getColor3DAttribute(XmlNode &node, const char *attributeName, aiColor3D &color); + static bool getVector2DAttribute(XmlNode &node, const char *attributeName, aiVector2D &vector); + static bool getVector3DAttribute(XmlNode &node, const char *attributeName, aiVector3D &vector); + + static bool getBooleanArrayAttribute(XmlNode &node, const char *attributeName, std::vector<bool> &boolArray); + static bool getDoubleArrayAttribute(XmlNode &node, const char *attributeName, std::vector<double> &doubleArray); + static bool getFloatArrayAttribute(XmlNode &node, const char *attributeName, std::vector<float> &floatArray); + static bool getInt32ArrayAttribute(XmlNode &node, const char *attributeName, std::vector<int32_t> &intArray); + static bool getStringListAttribute(XmlNode &node, const char *attributeName, std::list<std::string> &stringArray); + static bool getStringArrayAttribute(XmlNode &node, const char *attributeName, std::vector<std::string> &stringArray); + + static bool getVector2DListAttribute(XmlNode &node, const char *attributeName, std::list<aiVector2D> &vectorList); + static bool getVector2DArrayAttribute(XmlNode &node, const char *attributeName, std::vector<aiVector2D> &vectorArray); + static bool getVector3DListAttribute(XmlNode &node, const char *attributeName, std::list<aiVector3D> &vectorList); + static bool getVector3DArrayAttribute(XmlNode &node, const char *attributeName, std::vector<aiVector3D> &vectorArray); + static bool getColor3DListAttribute(XmlNode &node, const char *attributeName, std::list<aiColor3D> &colorList); + static bool getColor4DListAttribute(XmlNode &node, const char *attributeName, std::list<aiColor4D> &colorList); +}; + +} // namespace Assimp diff --git a/libs/assimp/code/AssetLib/XGL/XGLLoader.cpp b/libs/assimp/code/AssetLib/XGL/XGLLoader.cpp new file mode 100644 index 0000000..154af98 --- /dev/null +++ b/libs/assimp/code/AssetLib/XGL/XGLLoader.cpp @@ -0,0 +1,787 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the XGL/ZGL importer class */ + +#ifndef ASSIMP_BUILD_NO_XGL_IMPORTER + +#include "XGLLoader.h" +#include "Common/Compression.h" + +#include <assimp/ParsingUtils.h> +#include <assimp/fast_atof.h> +#include <assimp/MemoryIOWrapper.h> +#include <assimp/StreamReader.h> +#include <assimp/importerdesc.h> +#include <assimp/mesh.h> +#include <assimp/scene.h> +//#include <cctype> +//#include <memory> + +using namespace Assimp; + +namespace Assimp { // this has to be in here because LogFunctions is in ::Assimp + +template <> +const char *LogFunctions<XGLImporter>::Prefix() { + static auto prefix = "XGL: "; + return prefix; +} + +} // namespace Assimp + +static const aiImporterDesc desc = { + "XGL Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportCompressedFlavour, + 0, + 0, + 0, + 0, + "xgl zgl" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +XGLImporter::XGLImporter() : + mXmlParser(nullptr), + m_scene(nullptr) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +XGLImporter::~XGLImporter() { + delete mXmlParser; +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool XGLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "<world>", "<World>", "<WORLD>" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Get a list of all file extensions which are handled by this class +const aiImporterDesc *XGLImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void XGLImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + #ifndef ASSIMP_BUILD_NO_COMPRESSED_XGL + std::vector<char> uncompressed; +#endif + + m_scene = pScene; + std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb")); + + // check whether we can read from the file + if (stream.get() == NULL) { + throw DeadlyImportError("Failed to open XGL/ZGL file " + pFile); + } + + // see if its compressed, if so uncompress it + if (GetExtension(pFile) == "zgl") { +#ifdef ASSIMP_BUILD_NO_COMPRESSED_XGL + ThrowException("Cannot read ZGL file since Assimp was built without compression support"); +#else + std::unique_ptr<StreamReaderLE> raw_reader(new StreamReaderLE(stream)); + + Compression compression; + size_t total = 0l; + if (compression.open(Compression::Format::Binary, Compression::FlushMode::NoFlush, -Compression::MaxWBits)) { + // skip two extra bytes, zgl files do carry a crc16 upfront (I think) + raw_reader->IncPtr(2); + total = compression.decompress((unsigned char *)raw_reader->GetPtr(), raw_reader->GetRemainingSize(), uncompressed); + compression.close(); + } + // replace the input stream with a memory stream + stream.reset(new MemoryIOStream(reinterpret_cast<uint8_t*>(uncompressed.data()), total)); +#endif + } + + // parse the XML file + mXmlParser = new XmlParser; + if (!mXmlParser->parse(stream.get())) { + throw DeadlyImportError("XML parse error while loading XGL file ", pFile); + } + + TempScope scope; + XmlNode *worldNode = mXmlParser->findNode("WORLD"); + if (nullptr != worldNode) { + ReadWorld(*worldNode, scope); + } + + std::vector<aiMesh *> &meshes = scope.meshes_linear; + std::vector<aiMaterial *> &materials = scope.materials_linear; + if (!meshes.size() || !materials.size()) { + ThrowException("failed to extract data from XGL file, no meshes loaded"); + } + + // copy meshes + m_scene->mNumMeshes = static_cast<unsigned int>(meshes.size()); + m_scene->mMeshes = new aiMesh *[m_scene->mNumMeshes](); + std::copy(meshes.begin(), meshes.end(), m_scene->mMeshes); + + // copy materials + m_scene->mNumMaterials = static_cast<unsigned int>(materials.size()); + m_scene->mMaterials = new aiMaterial *[m_scene->mNumMaterials](); + std::copy(materials.begin(), materials.end(), m_scene->mMaterials); + + if (scope.light) { + m_scene->mNumLights = 1; + m_scene->mLights = new aiLight *[1]; + m_scene->mLights[0] = scope.light; + + scope.light->mName = m_scene->mRootNode->mName; + } + + scope.dismiss(); +} + +// ------------------------------------------------------------------------------------------------ +void XGLImporter::ReadWorld(XmlNode &node, TempScope &scope) { + for (XmlNode ¤tNode : node.children()) { + const std::string &s = ai_stdStrToLower(currentNode.name()); + + // XXX right now we'd skip <lighting> if it comes after + // <object> or <mesh> + if (s == "lighting") { + ReadLighting(currentNode, scope); + } else if (s == "object" || s == "mesh" || s == "mat") { + break; + } + } + + aiNode *const nd = ReadObject(node, scope); + if (!nd) { + ThrowException("failure reading <world>"); + } + if (nd->mName.length == 0) { + nd->mName.Set("WORLD"); + } + + m_scene->mRootNode = nd; +} + +// ------------------------------------------------------------------------------------------------ +void XGLImporter::ReadLighting(XmlNode &node, TempScope &scope) { + const std::string &s = ai_stdStrToLower(node.name()); + if (s == "directionallight") { + scope.light = ReadDirectionalLight(node); + } else if (s == "ambient") { + LogWarn("ignoring <ambient> tag"); + } else if (s == "spheremap") { + LogWarn("ignoring <spheremap> tag"); + } +} + +// ------------------------------------------------------------------------------------------------ +aiLight *XGLImporter::ReadDirectionalLight(XmlNode &node) { + std::unique_ptr<aiLight> l(new aiLight()); + l->mType = aiLightSource_DIRECTIONAL; + find_node_by_name_predicate predicate("directionallight"); + XmlNode child = node.find_child(predicate); + if (child.empty()) { + return nullptr; + } + + const std::string &s = ai_stdStrToLower(child.name()); + if (s == "direction") { + l->mDirection = ReadVec3(child); + } else if (s == "diffuse") { + l->mColorDiffuse = ReadCol3(child); + } else if (s == "specular") { + l->mColorSpecular = ReadCol3(child); + } + + return l.release(); +} + +// ------------------------------------------------------------------------------------------------ +aiNode *XGLImporter::ReadObject(XmlNode &node, TempScope &scope) { + aiNode *nd = new aiNode; + std::vector<aiNode *> children; + std::vector<unsigned int> meshes; + + try { + for (XmlNode &child : node.children()) { + const std::string &s = ai_stdStrToLower(child.name()); + if (s == "mesh") { + const size_t prev = scope.meshes_linear.size(); + bool empty; + if (ReadMesh(child, scope, empty)) { + const size_t newc = scope.meshes_linear.size(); + for (size_t i = 0; i < newc - prev; ++i) { + meshes.push_back(static_cast<unsigned int>(i + prev)); + } + } + } else if (s == "mat") { + ReadMaterial(child, scope); + } else if (s == "object") { + children.push_back(ReadObject(child, scope)); + } else if (s == "objectref") { + // XXX + } else if (s == "meshref") { + const unsigned int id = static_cast<unsigned int>(ReadIndexFromText(child)); + + std::multimap<unsigned int, aiMesh *>::iterator it = scope.meshes.find(id), end = scope.meshes.end(); + if (it == end) { + ThrowException("<meshref> index out of range"); + } + + for (; it != end && (*it).first == id; ++it) { + // ok, this is n^2 and should get optimized one day + aiMesh *const m = it->second; + unsigned int i = 0, mcount = static_cast<unsigned int>(scope.meshes_linear.size()); + for (; i < mcount; ++i) { + if (scope.meshes_linear[i] == m) { + meshes.push_back(i); + break; + } + } + + ai_assert(i < mcount); + } + } else if (s == "transform") { + nd->mTransformation = ReadTrafo(child); + } + } + } catch (...) { + for (aiNode *ch : children) { + delete ch; + } + throw; + } + + // FIX: since we used std::multimap<> to keep meshes by id, mesh order now depends on the behaviour + // of the multimap implementation with respect to the ordering of entries with same values. + // C++11 gives the guarantee that it uses insertion order, before it is implementation-specific. + // Sort by material id to always guarantee a deterministic result. + std::sort(meshes.begin(), meshes.end(), SortMeshByMaterialId(scope)); + + // link meshes to node + nd->mNumMeshes = static_cast<unsigned int>(meshes.size()); + if (0 != nd->mNumMeshes) { + nd->mMeshes = new unsigned int[nd->mNumMeshes](); + for (unsigned int i = 0; i < nd->mNumMeshes; ++i) { + nd->mMeshes[i] = meshes[i]; + } + } + + // link children to parent + nd->mNumChildren = static_cast<unsigned int>(children.size()); + if (nd->mNumChildren) { + nd->mChildren = new aiNode *[nd->mNumChildren](); + for (unsigned int i = 0; i < nd->mNumChildren; ++i) { + nd->mChildren[i] = children[i]; + children[i]->mParent = nd; + } + } + + return nd; +} + +// ------------------------------------------------------------------------------------------------ +aiMatrix4x4 XGLImporter::ReadTrafo(XmlNode &node) { + aiVector3D forward, up, right, position; + float scale = 1.0f; + + aiMatrix4x4 m; + XmlNode child = node.child("TRANSFORM"); + if (child.empty()) { + return m; + } + + for (XmlNode &sub_child : child.children()) { + const std::string &s = ai_stdStrToLower(sub_child.name()); + if (s == "forward") { + forward = ReadVec3(sub_child); + } else if (s == "up") { + up = ReadVec3(sub_child); + } else if (s == "position") { + position = ReadVec3(sub_child); + } + if (s == "scale") { + scale = ReadFloat(sub_child); + if (scale < 0.f) { + // this is wrong, but we can leave the value and pass it to the caller + LogError("found negative scaling in <transform>, ignoring"); + } + } + } + + if (forward.SquareLength() < 1e-4 || up.SquareLength() < 1e-4) { + LogError("A direction vector in <transform> is zero, ignoring trafo"); + return m; + } + + forward.Normalize(); + up.Normalize(); + + right = forward ^ up; + if (std::fabs(up * forward) > 1e-4) { + // this is definitely wrong - a degenerate coordinate space ruins everything + // so substitute identity transform. + LogError("<forward> and <up> vectors in <transform> are skewing, ignoring trafo"); + return m; + } + + right *= scale; + up *= scale; + forward *= scale; + + m.a1 = right.x; + m.b1 = right.y; + m.c1 = right.z; + + m.a2 = up.x; + m.b2 = up.y; + m.c2 = up.z; + + m.a3 = forward.x; + m.b3 = forward.y; + m.c3 = forward.z; + + m.a4 = position.x; + m.b4 = position.y; + m.c4 = position.z; + + return m; +} + +// ------------------------------------------------------------------------------------------------ +aiMesh *XGLImporter::ToOutputMesh(const TempMaterialMesh &m) { + std::unique_ptr<aiMesh> mesh(new aiMesh()); + + mesh->mNumVertices = static_cast<unsigned int>(m.positions.size()); + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + std::copy(m.positions.begin(), m.positions.end(), mesh->mVertices); + + if (!m.normals.empty()) { + mesh->mNormals = new aiVector3D[mesh->mNumVertices]; + std::copy(m.normals.begin(), m.normals.end(), mesh->mNormals); + } + + if (!m.uvs.empty()) { + mesh->mNumUVComponents[0] = 2; + mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; + + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + mesh->mTextureCoords[0][i] = aiVector3D(m.uvs[i].x, m.uvs[i].y, 0.f); + } + } + + mesh->mNumFaces = static_cast<unsigned int>(m.vcounts.size()); + mesh->mFaces = new aiFace[m.vcounts.size()]; + + unsigned int idx = 0; + for (unsigned int i = 0; i < mesh->mNumFaces; ++i) { + aiFace &f = mesh->mFaces[i]; + f.mNumIndices = m.vcounts[i]; + f.mIndices = new unsigned int[f.mNumIndices]; + for (unsigned int c = 0; c < f.mNumIndices; ++c) { + f.mIndices[c] = idx++; + } + } + + ai_assert(idx == mesh->mNumVertices); + + mesh->mPrimitiveTypes = m.pflags; + mesh->mMaterialIndex = m.matid; + + return mesh.release(); +} + +// ------------------------------------------------------------------------------------------------ +bool XGLImporter::ReadMesh(XmlNode &node, TempScope &scope, bool &empty) { + TempMesh t; + + std::map<unsigned int, TempMaterialMesh> bymat; + const unsigned int mesh_id = ReadIDAttr(node); + bool empty_mesh = true; + for (XmlNode &child : node.children()) { + const std::string &s = ai_stdStrToLower(child.name()); + + if (s == "mat") { + ReadMaterial(child, scope); + } else if (s == "p") { + pugi::xml_attribute attr = child.attribute("ID"); + if (attr.empty()) { + LogWarn("no ID attribute on <p>, ignoring"); + } else { + int id = attr.as_int(); + t.points[id] = ReadVec3(child); + } + } else if (s == "n") { + pugi::xml_attribute attr = child.attribute("ID"); + if (attr.empty()) { + LogWarn("no ID attribute on <n>, ignoring"); + } else { + int id = attr.as_int(); + t.normals[id] = ReadVec3(child); + } + } else if (s == "tc") { + pugi::xml_attribute attr = child.attribute("ID"); + if (attr.empty()) { + LogWarn("no ID attribute on <tc>, ignoring"); + } else { + int id = attr.as_int(); + t.uvs[id] = ReadVec2(child); + } + } else if (s == "f" || s == "l" || s == "p") { + const unsigned int vcount = s == "f" ? 3 : (s == "l" ? 2 : 1); + + unsigned int mid = ~0u; + TempFace tf[3]; + bool has[3] = { false }; + for (XmlNode &sub_child : child.children()) { + const std::string &scn = ai_stdStrToLower(sub_child.name()); + if (scn == "fv1" || scn == "lv1" || scn == "pv1") { + ReadFaceVertex(sub_child, t, tf[0]); + has[0] = true; + } else if (scn == "fv2" || scn == "lv2") { + ReadFaceVertex(sub_child, t, tf[1]); + has[1] = true; + } else if (scn == "fv3") { + ReadFaceVertex(sub_child, t, tf[2]); + has[2] = true; + } else if (scn == "mat") { + if (mid != ~0u) { + LogWarn("only one material tag allowed per <f>"); + } + mid = ResolveMaterialRef(sub_child, scope); + } else if (scn == "matref") { + if (mid != ~0u) { + LogWarn("only one material tag allowed per <f>"); + } + mid = ResolveMaterialRef(sub_child, scope); + } + } + if (has[0] || has[1] || has[2]) { + empty_mesh = false; + } + + if (mid == ~0u) { + ThrowException("missing material index"); + } + + bool nor = false; + bool uv = false; + for (unsigned int i = 0; i < vcount; ++i) { + if (!has[i]) { + ThrowException("missing face vertex data"); + } + + nor = nor || tf[i].has_normal; + uv = uv || tf[i].has_uv; + } + + if (mid >= (1 << 30)) { + LogWarn("material indices exhausted, this may cause errors in the output"); + } + unsigned int meshId = mid | ((nor ? 1 : 0) << 31) | ((uv ? 1 : 0) << 30); + + TempMaterialMesh &mesh = bymat[meshId]; + mesh.matid = mid; + + for (unsigned int i = 0; i < vcount; ++i) { + mesh.positions.push_back(tf[i].pos); + if (nor) { + mesh.normals.push_back(tf[i].normal); + } + if (uv) { + mesh.uvs.push_back(tf[i].uv); + } + + mesh.pflags |= 1 << (vcount - 1); + } + + mesh.vcounts.push_back(vcount); + } + } + + // finally extract output meshes and add them to the scope + using pairt = std::pair<const unsigned int, TempMaterialMesh>; + for (const pairt &p : bymat) { + aiMesh *const m = ToOutputMesh(p.second); + scope.meshes_linear.push_back(m); + + // if this is a definition, keep it on the stack + if (mesh_id != ~0u) { + scope.meshes.insert(std::pair<unsigned int, aiMesh *>(mesh_id, m)); + } + } + if (empty_mesh) { + LogWarn("Mesh is empty, skipping."); + empty = empty_mesh; + return false; + } + + // no id == not a reference, insert this mesh right *here* + return mesh_id == ~0u; +} + +// ---------------------------------------------------------------------------------------------- +unsigned int XGLImporter::ResolveMaterialRef(XmlNode &node, TempScope &scope) { + const std::string &s = node.name(); + if (s == "mat") { + ReadMaterial(node, scope); + return static_cast<unsigned int>(scope.materials_linear.size() - 1); + } + + const int id = ReadIndexFromText(node); + + auto it = scope.materials.find(id), end = scope.materials.end(); + if (it == end) { + ThrowException("<matref> index out of range"); + } + + // ok, this is n^2 and should get optimized one day + aiMaterial *const m = it->second; + + unsigned int i = 0, mcount = static_cast<unsigned int>(scope.materials_linear.size()); + for (; i < mcount; ++i) { + if (scope.materials_linear[i] == m) { + return i; + } + } + + ai_assert(false); + + return 0; +} + +// ------------------------------------------------------------------------------------------------ +void XGLImporter::ReadMaterial(XmlNode &node, TempScope &scope) { + const unsigned int mat_id = ReadIDAttr(node); + + auto *mat(new aiMaterial); + for (XmlNode &child : node.children()) { + const std::string &s = ai_stdStrToLower(child.name()); + if (s == "amb") { + const aiColor3D c = ReadCol3(child); + mat->AddProperty(&c, 1, AI_MATKEY_COLOR_AMBIENT); + } else if (s == "diff") { + const aiColor3D c = ReadCol3(child); + mat->AddProperty(&c, 1, AI_MATKEY_COLOR_DIFFUSE); + } else if (s == "spec") { + const aiColor3D c = ReadCol3(child); + mat->AddProperty(&c, 1, AI_MATKEY_COLOR_SPECULAR); + } else if (s == "emiss") { + const aiColor3D c = ReadCol3(child); + mat->AddProperty(&c, 1, AI_MATKEY_COLOR_EMISSIVE); + } else if (s == "alpha") { + const float f = ReadFloat(child); + mat->AddProperty(&f, 1, AI_MATKEY_OPACITY); + } else if (s == "shine") { + const float f = ReadFloat(child); + mat->AddProperty(&f, 1, AI_MATKEY_SHININESS); + } + } + + scope.materials[mat_id] = mat; + scope.materials_linear.push_back(mat); +} + +// ---------------------------------------------------------------------------------------------- +void XGLImporter::ReadFaceVertex(XmlNode &node, const TempMesh &t, TempFace &out) { + bool havep = false; + for (XmlNode &child : node.children()) { + const std::string &s = ai_stdStrToLower(child.name()); + if (s == "pref") { + const unsigned int id = ReadIndexFromText(child); + std::map<unsigned int, aiVector3D>::const_iterator it = t.points.find(id); + if (it == t.points.end()) { + ThrowException("point index out of range"); + } + + out.pos = (*it).second; + havep = true; + } else if (s == "nref") { + const unsigned int id = ReadIndexFromText(child); + std::map<unsigned int, aiVector3D>::const_iterator it = t.normals.find(id); + if (it == t.normals.end()) { + ThrowException("normal index out of range"); + } + + out.normal = (*it).second; + out.has_normal = true; + } else if (s == "tcref") { + const unsigned int id = ReadIndexFromText(child); + std::map<unsigned int, aiVector2D>::const_iterator it = t.uvs.find(id); + if (it == t.uvs.end()) { + ThrowException("uv index out of range"); + } + + out.uv = (*it).second; + out.has_uv = true; + } else if (s == "p") { + out.pos = ReadVec3(child); + } else if (s == "n") { + out.normal = ReadVec3(child); + } else if (s == "tc") { + out.uv = ReadVec2(child); + } + } + + if (!havep) { + ThrowException("missing <pref> in <fvN> element"); + } +} + +// ------------------------------------------------------------------------------------------------ +unsigned int XGLImporter::ReadIDAttr(XmlNode &node) { + for (pugi::xml_attribute attr : node.attributes()) { + if (!ASSIMP_stricmp(attr.name(), "id")) { + return attr.as_int(); + } + } + + return ~0u; +} + +// ------------------------------------------------------------------------------------------------ +float XGLImporter::ReadFloat(XmlNode &node) { + std::string v; + XmlParser::getValueAsString(node, v); + const char *s = v.c_str(), *se; + if (!SkipSpaces(&s)) { + LogError("unexpected EOL, failed to parse index element"); + return 0.f; + } + float t; + se = fast_atoreal_move(s, t); + if (se == s) { + LogError("failed to read float text"); + return 0.f; + } + + return t; +} + +// ------------------------------------------------------------------------------------------------ +unsigned int XGLImporter::ReadIndexFromText(XmlNode &node) { + std::string v; + XmlParser::getValueAsString(node, v); + const char *s = v.c_str(); + if (!SkipSpaces(&s)) { + LogError("unexpected EOL, failed to parse index element"); + return ~0u; + } + const char *se; + const unsigned int t = strtoul10(s, &se); + + if (se == s) { + LogError("failed to read index"); + return ~0u; + } + + return t; +} + +// ------------------------------------------------------------------------------------------------ +aiVector2D XGLImporter::ReadVec2(XmlNode &node) { + aiVector2D vec; + std::string val; + XmlParser::getValueAsString(node, val); + const char *s = val.c_str(); + ai_real v[2] = {}; + for (int i = 0; i < 2; ++i) { + if (!SkipSpaces(&s)) { + LogError("unexpected EOL, failed to parse vec2"); + return vec; + } + + v[i] = fast_atof(&s); + + SkipSpaces(&s); + if (i != 1 && *s != ',') { + LogError("expected comma, failed to parse vec2"); + return vec; + } + ++s; + } + vec.x = v[0]; + vec.y = v[1]; + + return vec; +} + +// ------------------------------------------------------------------------------------------------ +aiVector3D XGLImporter::ReadVec3(XmlNode &node) { + aiVector3D vec; + std::string v; + XmlParser::getValueAsString(node, v); + const char *s = v.c_str(); + for (int i = 0; i < 3; ++i) { + if (!SkipSpaces(&s)) { + LogError("unexpected EOL, failed to parse vec3"); + return vec; + } + vec[i] = fast_atof(&s); + + SkipSpaces(&s); + if (i != 2 && *s != ',') { + LogError("expected comma, failed to parse vec3"); + return vec; + } + ++s; + } + + return vec; +} + +// ------------------------------------------------------------------------------------------------ +aiColor3D XGLImporter::ReadCol3(XmlNode &node) { + const aiVector3D &v = ReadVec3(node); + if (v.x < 0.f || v.x > 1.0f || v.y < 0.f || v.y > 1.0f || v.z < 0.f || v.z > 1.0f) { + LogWarn("color values out of range, ignoring"); + } + return aiColor3D(v.x, v.y, v.z); +} + +#endif // ASSIMP_BUILD_NO_XGL_IMPORTER diff --git a/libs/assimp/code/AssetLib/XGL/XGLLoader.h b/libs/assimp/code/AssetLib/XGL/XGLLoader.h new file mode 100644 index 0000000..ae7ccdd --- /dev/null +++ b/libs/assimp/code/AssetLib/XGL/XGLLoader.h @@ -0,0 +1,209 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 XGLLoader.h + * @brief Declaration of the .xgl/.zgl + */ +#ifndef AI_XGLLOADER_H_INCLUDED +#define AI_XGLLOADER_H_INCLUDED + +#include <assimp/BaseImporter.h> +#include <assimp/XmlParser.h> +#include <assimp/LogAux.h> +#include <assimp/light.h> +#include <assimp/material.h> +#include <assimp/mesh.h> +#include <assimp/light.h> +#include <assimp/Importer.hpp> +#include <assimp/XmlParser.h> + +#include <map> +#include <memory> + +struct aiNode; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** XGL/ZGL importer. + * + * Spec: http://vizstream.aveva.com/release/vsplatform/XGLSpec.htm + */ +class XGLImporter : public BaseImporter, public LogFunctions<XGLImporter> { +public: + XGLImporter(); + ~XGLImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details */ + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + +private: + struct TempScope { + TempScope() : + light() { + // empty + } + + ~TempScope() { + for (aiMesh *m : meshes_linear) { + delete m; + } + + for (aiMaterial *m : materials_linear) { + delete m; + } + + delete light; + } + + void dismiss() { + light = nullptr; + meshes_linear.clear(); + materials_linear.clear(); + meshes.clear(); + materials.clear(); + } + + std::multimap<unsigned int, aiMesh *> meshes; + std::map<unsigned int, aiMaterial *> materials; + + std::vector<aiMesh *> meshes_linear; + std::vector<aiMaterial *> materials_linear; + + aiLight *light; + }; + + struct SortMeshByMaterialId { + SortMeshByMaterialId(const TempScope &scope) : + scope(scope) { + // empty + } + bool operator()(unsigned int a, unsigned int b) const { + return scope.meshes_linear[a]->mMaterialIndex < scope.meshes_linear[b]->mMaterialIndex; + }; + + const TempScope &scope; + }; + + struct TempMesh { + std::map<unsigned int, aiVector3D> points; + std::map<unsigned int, aiVector3D> normals; + std::map<unsigned int, aiVector2D> uvs; + }; + + struct TempMaterialMesh { + TempMaterialMesh() : + pflags(), + matid() { + // empty + } + + std::vector<aiVector3D> positions, normals; + std::vector<aiVector2D> uvs; + + std::vector<unsigned int> vcounts; + unsigned int pflags; + unsigned int matid; + }; + + struct TempFace { + TempFace() : + has_uv(), + has_normal() { + // empty + } + + aiVector3D pos; + aiVector3D normal; + aiVector2D uv; + bool has_uv; + bool has_normal; + }; + +private: + void Cleanup(); + + std::string GetElementName(); + bool ReadElement(); + bool ReadElementUpToClosing(const char *closetag); + bool SkipToText(); + unsigned int ReadIDAttr(XmlNode &node); + + void ReadWorld(XmlNode &node, TempScope &scope); + void ReadLighting(XmlNode &node, TempScope &scope); + aiLight *ReadDirectionalLight(XmlNode &node); + aiNode *ReadObject(XmlNode &node, TempScope &scope); + bool ReadMesh(XmlNode &node, TempScope &scope, bool &empty); + void ReadMaterial(XmlNode &node, TempScope &scope); + aiVector2D ReadVec2(XmlNode &node); + aiVector3D ReadVec3(XmlNode &node); + aiColor3D ReadCol3(XmlNode &node); + aiMatrix4x4 ReadTrafo(XmlNode &node); + unsigned int ReadIndexFromText(XmlNode &node); + float ReadFloat(XmlNode &node); + + aiMesh *ToOutputMesh(const TempMaterialMesh &m); + void ReadFaceVertex(XmlNode &node, const TempMesh &t, TempFace &out); + unsigned int ResolveMaterialRef(XmlNode &node, TempScope &scope); + +private: + XmlParser *mXmlParser; + aiScene *m_scene; +}; + +} // end of namespace Assimp + +#endif // AI_IRRMESHIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/glTF/glTFAsset.h b/libs/assimp/code/AssetLib/glTF/glTFAsset.h new file mode 100644 index 0000000..1a42e90 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFAsset.h @@ -0,0 +1,1017 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 glTFAsset.h + * Declares a glTF class to handle gltf/glb files + * + * glTF Extensions Support: + * KHR_binary_glTF: full + * KHR_materials_common: full + */ +#ifndef GLTFASSET_H_INC +#define GLTFASSET_H_INC + +#if !defined(ASSIMP_BUILD_NO_GLTF_IMPORTER) && !defined(ASSIMP_BUILD_NO_GLTF1_IMPORTER) + +#include "glTFCommon.h" +#include <assimp/Exceptional.h> +#include <algorithm> +#include <list> +#include <map> +#include <stdexcept> +#include <string> +#include <vector> + +// clang-format off + +#if (__GNUC__ == 8 && __GNUC_MINOR__ >= 0) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclass-memaccess" +#endif + +#include <rapidjson/rapidjson.h> +#include <rapidjson/document.h> +#include <rapidjson/error/en.h> + +#if (__GNUC__ == 8 && __GNUC_MINOR__ >= 0) +#pragma GCC diagnostic pop +#endif + +#ifdef ASSIMP_API +# include <memory> +# include <assimp/DefaultIOSystem.h> +# include <assimp/ByteSwapper.h> +#else +# include <memory> +# define AI_SWAP4(p) +# define ai_assert +#endif + + +#if _MSC_VER > 1500 || (defined __GNUC___) +# define ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +# else +# define gltf_unordered_map map +#endif + +#ifdef ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +# include <unordered_map> +# if defined(_MSC_VER) && _MSC_VER <= 1600 +# define gltf_unordered_map tr1::unordered_map +# else +# define gltf_unordered_map unordered_map +# endif +#endif + +// clang-format on + +#include "AssetLib/glTF/glTFCommon.h" + +namespace glTF { + +using glTFCommon::IOStream; +using glTFCommon::IOSystem; +using glTFCommon::Nullable; +using glTFCommon::Ref; +using glTFCommon::shared_ptr; + +using rapidjson::Document; +using rapidjson::Value; + +class Asset; +class AssetWriter; + +struct BufferView; // here due to cross-reference +struct Texture; +struct Light; +struct Skin; + +using glTFCommon::mat4; +using glTFCommon::vec3; +using glTFCommon::vec4; + +//! Magic number for GLB files +#define AI_GLB_MAGIC_NUMBER "glTF" + +// clang-format off +#ifdef ASSIMP_API +# include <assimp/Compiler/pushpack1.h> +#endif +// clang-format on + +//! For the KHR_binary_glTF extension (binary .glb file) +//! 20-byte header (+ the JSON + a "body" data section) +struct GLB_Header { + uint8_t magic[4]; //!< Magic number: "glTF" + uint32_t version; //!< Version number (always 1 as of the last update) + uint32_t length; //!< Total length of the Binary glTF, including header, scene, and body, in bytes + uint32_t sceneLength; //!< Length, in bytes, of the glTF scene + uint32_t sceneFormat; //!< Specifies the format of the glTF scene (see the SceneFormat enum) +} PACK_STRUCT; + +// clang-format off +#ifdef ASSIMP_API +# include <assimp/Compiler/poppack1.h> +#endif +// clang-format on + +//! Values for the GLB_Header::sceneFormat field +enum SceneFormat { + SceneFormat_JSON = 0 +}; + +//! Values for the mesh primitive modes +enum PrimitiveMode { + PrimitiveMode_POINTS = 0, + PrimitiveMode_LINES = 1, + PrimitiveMode_LINE_LOOP = 2, + PrimitiveMode_LINE_STRIP = 3, + PrimitiveMode_TRIANGLES = 4, + PrimitiveMode_TRIANGLE_STRIP = 5, + PrimitiveMode_TRIANGLE_FAN = 6 +}; + +//! Values for the Accessor::componentType field +enum ComponentType { + ComponentType_BYTE = 5120, + ComponentType_UNSIGNED_BYTE = 5121, + ComponentType_SHORT = 5122, + ComponentType_UNSIGNED_SHORT = 5123, + ComponentType_UNSIGNED_INT = 5125, + ComponentType_FLOAT = 5126 +}; + +inline unsigned int ComponentTypeSize(ComponentType t) { + switch (t) { + case ComponentType_SHORT: + case ComponentType_UNSIGNED_SHORT: + return 2; + + case ComponentType_UNSIGNED_INT: + case ComponentType_FLOAT: + return 4; + + case ComponentType_BYTE: + case ComponentType_UNSIGNED_BYTE: + return 1; + default: + std::string err = "GLTF: Unsupported Component Type "; + err += std::to_string(t); + throw DeadlyImportError(err); + } +} + +//! Values for the BufferView::target field +enum BufferViewTarget { + BufferViewTarget_NONE = 0, + BufferViewTarget_ARRAY_BUFFER = 34962, + BufferViewTarget_ELEMENT_ARRAY_BUFFER = 34963 +}; + +//! Values for the Sampler::magFilter field +enum SamplerMagFilter { + SamplerMagFilter_Nearest = 9728, + SamplerMagFilter_Linear = 9729 +}; + +//! Values for the Sampler::minFilter field +enum SamplerMinFilter { + SamplerMinFilter_Nearest = 9728, + SamplerMinFilter_Linear = 9729, + SamplerMinFilter_Nearest_Mipmap_Nearest = 9984, + SamplerMinFilter_Linear_Mipmap_Nearest = 9985, + SamplerMinFilter_Nearest_Mipmap_Linear = 9986, + SamplerMinFilter_Linear_Mipmap_Linear = 9987 +}; + +//! Values for the Sampler::wrapS and Sampler::wrapT field +enum SamplerWrap { + SamplerWrap_Clamp_To_Edge = 33071, + SamplerWrap_Mirrored_Repeat = 33648, + SamplerWrap_Repeat = 10497 +}; + +//! Values for the Texture::format and Texture::internalFormat fields +enum TextureFormat { + TextureFormat_ALPHA = 6406, + TextureFormat_RGB = 6407, + TextureFormat_RGBA = 6408, + TextureFormat_LUMINANCE = 6409, + TextureFormat_LUMINANCE_ALPHA = 6410 +}; + +//! Values for the Texture::target field +enum TextureTarget { + TextureTarget_TEXTURE_2D = 3553 +}; + +//! Values for the Texture::type field +enum TextureType { + TextureType_UNSIGNED_BYTE = 5121, + TextureType_UNSIGNED_SHORT_5_6_5 = 33635, + TextureType_UNSIGNED_SHORT_4_4_4_4 = 32819, + TextureType_UNSIGNED_SHORT_5_5_5_1 = 32820 +}; + +//! Values for the Accessor::type field (helper class) +class AttribType { +public: + enum Value { SCALAR, + VEC2, + VEC3, + VEC4, + MAT2, + MAT3, + MAT4 + }; + + inline static Value FromString(const char *str) { + for (size_t i = 0; i < NUM_VALUES; ++i) { + if (strcmp(data<0>::infos[i].name, str) == 0) { + return static_cast<Value>(i); + } + } + return SCALAR; + } + + inline static const char *ToString(Value type) { + return data<0>::infos[static_cast<size_t>(type)].name; + } + + inline static unsigned int GetNumComponents(Value type) { + return data<0>::infos[static_cast<size_t>(type)].numComponents; + } + +private: + static const size_t NUM_VALUES = static_cast<size_t>(MAT4) + 1; + struct Info { + const char *name; + unsigned int numComponents; + }; + + template <int N> + struct data { + static const Info infos[NUM_VALUES]; + }; +}; + +// must match the order of the AttribTypeTraits::Value enum! +template <int N> +const AttribType::Info AttribType::data<N>::infos[AttribType::NUM_VALUES] = { + { "SCALAR", 1 }, + { "VEC2", 2 }, + { "VEC3", 3 }, + { "VEC4", 4 }, + { "MAT2", 4 }, + { "MAT3", 9 }, + { "MAT4", 16 } +}; + +//! Base class for all glTF top-level objects +struct Object { + std::string id; //!< The globally unique ID used to reference this object + std::string name; //!< The user-defined name of this object + + //! Objects marked as special are not exported (used to emulate the binary body buffer) + virtual bool IsSpecial() const { return false; } + + Object() = default; + virtual ~Object() {} + + //! Maps special IDs to another ID, where needed. Subclasses may override it (statically) + static const char *TranslateId(Asset & /*r*/, const char *id) { return id; } +}; + +// +// Classes for each glTF top-level object type +// + +//! A typed view into a BufferView. A BufferView contains raw binary data. +//! An accessor provides a typed view into a BufferView or a subset of a BufferView +//! similar to how WebGL's vertexAttribPointer() defines an attribute in a buffer. +struct Accessor : public Object { + Ref<BufferView> bufferView; //!< The ID of the bufferView. (required) + unsigned int byteOffset; //!< The offset relative to the start of the bufferView in bytes. (required) + unsigned int byteStride; //!< The stride, in bytes, between attributes referenced by this accessor. (default: 0) + ComponentType componentType; //!< The datatype of components in the attribute. (required) + unsigned int count; //!< The number of attributes referenced by this accessor. (required) + AttribType::Value type; //!< Specifies if the attribute is a scalar, vector, or matrix. (required) + std::vector<double> max; //!< Maximum value of each component in this attribute. + std::vector<double> min; //!< Minimum value of each component in this attribute. + + unsigned int GetNumComponents(); + unsigned int GetBytesPerComponent(); + unsigned int GetElementSize(); + + inline uint8_t *GetPointer(); + + template <class T> + bool ExtractData(T *&outData); + + void WriteData(size_t count, const void *src_buffer, size_t src_stride); + + //! Helper class to iterate the data + class Indexer { + friend struct Accessor; + + // This field is reported as not used, making it protectd is the easiest way to work around it without going to the bottom of what the problem is: + // ../code/glTF2/glTF2Asset.h:392:19: error: private field 'accessor' is not used [-Werror,-Wunused-private-field] + protected: + Accessor &accessor; + + private: + uint8_t *data; + size_t elemSize, stride; + + Indexer(Accessor &acc); + + public: + //! Accesses the i-th value as defined by the accessor + template <class T> + T GetValue(int i); + + //! Accesses the i-th value as defined by the accessor + inline unsigned int GetUInt(int i) { + return GetValue<unsigned int>(i); + } + + inline bool IsValid() const { + return data != 0; + } + }; + + inline Indexer GetIndexer() { + return Indexer(*this); + } + + Accessor() = default; + void Read(Value &obj, Asset &r); +}; + +//! A buffer points to binary geometry, animation, or skins. +struct Buffer : public Object { + /********************* Types *********************/ + enum Type { + Type_arraybuffer, + Type_text + }; + + /// @brief Descriptor of encoded region in "bufferView". + struct SEncodedRegion { + const size_t Offset; ///< Offset from begin of "bufferView" to encoded region, in bytes. + const size_t EncodedData_Length; ///< Size of encoded region, in bytes. + uint8_t *const DecodedData; ///< Cached encoded data. + const size_t DecodedData_Length; ///< Size of decoded region, in bytes. + const std::string ID; ///< ID of the region. + + /// @brief Constructor. + /// \param [in] pOffset - offset from begin of "bufferView" to encoded region, in bytes. + /// \param [in] pEncodedData_Length - size of encoded region, in bytes. + /// \param [in] pDecodedData - pointer to decoded data array. + /// \param [in] pDecodedData_Length - size of encoded region, in bytes. + /// \param [in] pID - ID of the region. + SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID) : + Offset(pOffset), EncodedData_Length(pEncodedData_Length), DecodedData(pDecodedData), DecodedData_Length(pDecodedData_Length), ID(pID) {} + + /// Destructor. + ~SEncodedRegion() { delete[] DecodedData; } + }; + + /******************* Variables *******************/ + + size_t byteLength; //!< The length of the buffer in bytes. (default: 0) + + Type type; + + /// \var EncodedRegion_Current + /// Pointer to currently active encoded region. + /// Why not decoding all regions at once and not to set one buffer with decoded data? + /// Yes, why not? Even "accessor" point to decoded data. I mean that fields "byteOffset", "byteStride" and "count" has values which describes decoded + /// data array. But only in range of mesh while is active parameters from "compressedData". For another mesh accessors point to decoded data too. But + /// offset is counted for another regions is encoded. + /// Example. You have two meshes. For every of it you have 4 bytes of data. That data compressed to 2 bytes. So, you have buffer with encoded data: + /// M1_E0, M1_E1, M2_E0, M2_E1. + /// After decoding you'll get: + /// M1_D0, M1_D1, M1_D2, M1_D3, M2_D0, M2_D1, M2_D2, M2_D3. + /// "accessors" must to use values that point to decoded data - obviously. So, you'll expect "accessors" like + /// "accessor_0" : { byteOffset: 0, byteLength: 4}, "accessor_1" : { byteOffset: 4, byteLength: 4} + /// but in real life you'll get: + /// "accessor_0" : { byteOffset: 0, byteLength: 4}, "accessor_1" : { byteOffset: 2, byteLength: 4} + /// Yes, accessor of next mesh has offset and length which mean: current mesh data is decoded, all other data is encoded. + /// And when before you start to read data of current mesh (with encoded data ofcourse) you must decode region of "bufferView", after read finished + /// delete encoding mark. And after that you can repeat process: decode data of mesh, read, delete decoded data. + /// + /// Remark. Encoding all data at once is good in world with computers which do not has RAM limitation. So, you must use step by step encoding in + /// exporter and importer. And, thanks to such way, there is no need to load whole file into memory. + SEncodedRegion *EncodedRegion_Current; + +private: + shared_ptr<uint8_t> mData; //!< Pointer to the data + bool mIsSpecial; //!< Set to true for special cases (e.g. the body buffer) + size_t capacity = 0; //!< The capacity of the buffer in bytes. (default: 0) + /// \var EncodedRegion_List + /// List of encoded regions. + std::list<SEncodedRegion *> EncodedRegion_List; + + /******************* Functions *******************/ + +public: + Buffer(); + ~Buffer(); + + void Read(Value &obj, Asset &r); + + bool LoadFromStream(IOStream &stream, size_t length = 0, size_t baseOffset = 0); + + /// Mark region of "bufferView" as encoded. When data is request from such region then "bufferView" use decoded data. + /// \param [in] pOffset - offset from begin of "bufferView" to encoded region, in bytes. + /// \param [in] pEncodedData_Length - size of encoded region, in bytes. + /// \param [in] pDecodedData - pointer to decoded data array. + /// \param [in] pDecodedData_Length - size of encoded region, in bytes. + /// \param [in] pID - ID of the region. + void EncodedRegion_Mark(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID); + + /// Select current encoded region by ID. \sa EncodedRegion_Current. + /// \param [in] pID - ID of the region. + void EncodedRegion_SetCurrent(const std::string &pID); + + /// Replace part of buffer data. Pay attention that function work with original array of data (\ref mData) not with encoded regions. + /// \param [in] pBufferData_Offset - index of first element in buffer from which new data will be placed. + /// \param [in] pBufferData_Count - count of bytes in buffer which will be replaced. + /// \param [in] pReplace_Data - pointer to array with new data for buffer. + /// \param [in] pReplace_Count - count of bytes in new data. + /// \return true - if successfully replaced, false if input arguments is out of range. + bool ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count); + + size_t AppendData(uint8_t *data, size_t length); + void Grow(size_t amount); + + uint8_t *GetPointer() { return mData.get(); } + + void MarkAsSpecial() { mIsSpecial = true; } + + bool IsSpecial() const { return mIsSpecial; } + + std::string GetURI() { return std::string(this->id) + ".bin"; } + + static const char *TranslateId(Asset &r, const char *id); +}; + +//! A view into a buffer generally representing a subset of the buffer. +struct BufferView : public Object { + Ref<Buffer> buffer; //! The ID of the buffer. (required) + size_t byteOffset; //! The offset into the buffer in bytes. (required) + size_t byteLength; //! The length of the bufferView in bytes. (default: 0) + + BufferViewTarget target; //! The target that the WebGL buffer should be bound to. + + void Read(Value &obj, Asset &r); +}; + +struct Camera : public Object { + enum Type { + Perspective, + Orthographic + }; + + Type type; + + union { + struct { + float aspectRatio; //!<The floating - point aspect ratio of the field of view. (0 = undefined = use the canvas one) + float yfov; //!<The floating - point vertical field of view in radians. (required) + float zfar; //!<The floating - point distance to the far clipping plane. (required) + float znear; //!< The floating - point distance to the near clipping plane. (required) + } perspective; + + struct { + float xmag; //! The floating-point horizontal magnification of the view. (required) + float ymag; //! The floating-point vertical magnification of the view. (required) + float zfar; //! The floating-point distance to the far clipping plane. (required) + float znear; //! The floating-point distance to the near clipping plane. (required) + } ortographic; + }; + + Camera() = default; + void Read(Value &obj, Asset &r); +}; + +//! Image data used to create a texture. +struct Image : public Object { + std::string uri; //! The uri of the image, that can be a file path, a data URI, etc.. (required) + Ref<BufferView> bufferView; + std::string mimeType; + int width, height; + +public: + Image(); + void Read(Value &obj, Asset &r); + inline bool HasData() const { return mDataLength > 0; } + inline size_t GetDataLength() const { return mDataLength; } + inline const uint8_t *GetData() const { return mData.get(); } + inline uint8_t *StealData(); + inline void SetData(uint8_t *data, size_t length, Asset &r); + +private: + std::unique_ptr<uint8_t[]> mData; + size_t mDataLength; +}; + +//! Holds a material property that can be a texture or a color +struct TexProperty { + Ref<Texture> texture; + vec4 color; +}; + +//! The material appearance of a primitive. +struct Material : public Object { + //Ref<Sampler> source; //!< The ID of the technique. + //std::gltf_unordered_map<std::string, std::string> values; //!< A dictionary object of parameter values. + + //! Techniques defined by KHR_materials_common + enum Technique { + Technique_undefined = 0, + Technique_BLINN, + Technique_PHONG, + Technique_LAMBERT, + Technique_CONSTANT + }; + + TexProperty ambient; + TexProperty diffuse; + TexProperty specular; + TexProperty emission; + + bool doubleSided; + bool transparent; + float transparency; + float shininess; + + Technique technique; + + Material() { SetDefaults(); } + void Read(Value &obj, Asset &r); + void SetDefaults(); +}; + +//! A set of primitives to be rendered. A node can contain one or more meshes. A node's transform places the mesh in the scene. +struct Mesh : public Object { + typedef std::vector<Ref<Accessor>> AccessorList; + + struct Primitive { + PrimitiveMode mode; + + struct Attributes { + AccessorList position, normal, texcoord, color, joint, jointmatrix, weight; + } attributes; + + Ref<Accessor> indices; + + Ref<Material> material; + }; + + /// \struct SExtension + /// Extension used for mesh. + struct SExtension { + /// \enum EType + /// Type of extension. + enum EType { +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + Compression_Open3DGC, ///< Compression of mesh data using Open3DGC algorithm. +#endif + + Unknown + }; + + EType Type; ///< Type of extension. + + /// \fn SExtension + /// Constructor. + /// \param [in] pType - type of extension. + SExtension(const EType pType) : + Type(pType) {} + + virtual ~SExtension() { + // empty + } + }; + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + + /// \struct SCompression_Open3DGC + /// Compression of mesh data using Open3DGC algorithm. + struct SCompression_Open3DGC : public SExtension { + using SExtension::Type; + + std::string Buffer; ///< ID of "buffer" used for storing compressed data. + size_t Offset; ///< Offset in "bufferView" where compressed data are stored. + size_t Count; ///< Count of elements in compressed data. Is always equivalent to size in bytes: look comments for "Type" and "Component_Type". + bool Binary; ///< If true then "binary" mode is used for coding, if false - "ascii" mode. + size_t IndicesCount; ///< Count of indices in mesh. + size_t VerticesCount; ///< Count of vertices in mesh. + // AttribType::Value Type;///< Is always "SCALAR". + // ComponentType Component_Type;///< Is always "ComponentType_UNSIGNED_BYTE" (5121). + + /// \fn SCompression_Open3DGC + /// Constructor. + SCompression_Open3DGC() : + SExtension(Compression_Open3DGC) { + // empty + } + + virtual ~SCompression_Open3DGC() { + // empty + } + }; +#endif + + std::vector<Primitive> primitives; + std::list<SExtension *> Extension; ///< List of extensions used in mesh. + + Mesh() {} + + /// Destructor. + ~Mesh() { + for (std::list<SExtension *>::iterator it = Extension.begin(), it_end = Extension.end(); it != it_end; it++) { + delete *it; + }; + } + + /// @brief Get mesh data from JSON-object and place them to root asset. + /// \param [in] pJSON_Object - reference to pJSON-object from which data are read. + /// \param [out] pAsset_Root - reference to root asset where data will be stored. + void Read(Value &pJSON_Object, Asset &pAsset_Root); + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + /// @brief Decode part of "buffer" which encoded with Open3DGC algorithm. + /// \param [in] pCompression_Open3DGC - reference to structure which describe encoded region. + /// \param [out] pAsset_Root - reference to root assed where data will be stored. + void Decode_O3DGC(const SCompression_Open3DGC &pCompression_Open3DGC, Asset &pAsset_Root); +#endif +}; + +struct Node : public Object { + std::vector<Ref<Node>> children; + std::vector<Ref<Mesh>> meshes; + + Nullable<mat4> matrix; + Nullable<vec3> translation; + Nullable<vec4> rotation; + Nullable<vec3> scale; + + Ref<Camera> camera; + Ref<Light> light; + + std::vector<Ref<Node>> skeletons; //!< The ID of skeleton nodes. Each of which is the root of a node hierarchy. + Ref<Skin> skin; //!< The ID of the skin referenced by this node. + std::string jointName; //!< Name used when this node is a joint in a skin. + + Ref<Node> parent; //!< This is not part of the glTF specification. Used as a helper. + + Node() {} + void Read(Value &obj, Asset &r); +}; + +struct Program : public Object { + Program() {} + void Read(Value &obj, Asset &r); +}; + +struct Sampler : public Object { + SamplerMagFilter magFilter; //!< The texture magnification filter. (required) + SamplerMinFilter minFilter; //!< The texture minification filter. (required) + SamplerWrap wrapS; //!< The texture wrapping in the S direction. (required) + SamplerWrap wrapT; //!< The texture wrapping in the T direction. (required) + + Sampler() = default; + void Read(Value &obj, Asset &r); + void SetDefaults(); +}; + +struct Scene : public Object { + std::vector<Ref<Node>> nodes; + + Scene() = default; + void Read(Value &obj, Asset &r); +}; + +struct Shader : public Object { + Shader() = default; + void Read(Value &obj, Asset &r); +}; + +struct Skin : public Object { + Nullable<mat4> bindShapeMatrix; //!< Floating-point 4x4 transformation matrix stored in column-major order. + Ref<Accessor> inverseBindMatrices; //!< The ID of the accessor containing the floating-point 4x4 inverse-bind matrices. + std::vector<Ref<Node>> jointNames; //!< Joint names of the joints (nodes with a jointName property) in this skin. + std::string name; //!< The user-defined name of this object. + + Skin() = default; + void Read(Value &obj, Asset &r); +}; + +struct Technique : public Object { + struct Parameters { + }; + + struct States { + }; + + struct Functions { + }; + + Technique() = default; + void Read(Value &obj, Asset &r); +}; + +//! A texture and its sampler. +struct Texture : public Object { + Ref<Sampler> sampler; //!< The ID of the sampler used by this texture. (required) + Ref<Image> source; //!< The ID of the image used by this texture. (required) + + Texture() = default; + void Read(Value &obj, Asset &r); +}; + +//! A light (from KHR_materials_common extension) +struct Light : public Object { + enum Type { + Type_undefined, + Type_ambient, + Type_directional, + Type_point, + Type_spot + }; + + Type type; + vec4 color; + float distance; + float constantAttenuation; + float linearAttenuation; + float quadraticAttenuation; + float falloffAngle; + float falloffExponent; + + Light() = default; + void Read(Value &obj, Asset &r); + void SetDefaults(); +}; + +struct Animation : public Object { + struct AnimSampler { + std::string id; //!< The ID of this sampler. + std::string input; //!< The ID of a parameter in this animation to use as key-frame input. + std::string interpolation; //!< Type of interpolation algorithm to use between key-frames. + std::string output; //!< The ID of a parameter in this animation to use as key-frame output. + }; + + struct AnimChannel { + std::string sampler; //!< The ID of one sampler present in the containing animation's samplers property. + + struct AnimTarget { + Ref<Node> id; //!< The ID of the node to animate. + std::string path; //!< The name of property of the node to animate ("translation", "rotation", or "scale"). + } target; + }; + + struct AnimParameters { + Ref<Accessor> TIME; //!< Accessor reference to a buffer storing a array of floating point scalar values. + Ref<Accessor> rotation; //!< Accessor reference to a buffer storing a array of four-component floating-point vectors. + Ref<Accessor> scale; //!< Accessor reference to a buffer storing a array of three-component floating-point vectors. + Ref<Accessor> translation; //!< Accessor reference to a buffer storing a array of three-component floating-point vectors. + }; + + std::vector<AnimChannel> Channels; //!< Connect the output values of the key-frame animation to a specific node in the hierarchy. + AnimParameters Parameters; //!< The samplers that interpolate between the key-frames. + std::vector<AnimSampler> Samplers; //!< The parameterized inputs representing the key-frame data. + + Animation() = default; + void Read(Value &obj, Asset &r); +}; + +//! Base class for LazyDict that acts as an interface +class LazyDictBase { +public: + virtual ~LazyDictBase() {} + + virtual void AttachToDocument(Document &doc) = 0; + virtual void DetachFromDocument() = 0; + +#if !defined(ASSIMP_BUILD_NO_EXPORT) + virtual void WriteObjects(AssetWriter &writer) = 0; +#endif +}; + +template <class T> +class LazyDict; + +//! (Implemented in glTFAssetWriter.h) +template <class T> +void WriteLazyDict(LazyDict<T> &d, AssetWriter &w); + +//! Manages lazy loading of the glTF top-level objects, and keeps a reference to them by ID +//! It is the owner the loaded objects, so when it is destroyed it also deletes them +template <class T> +class LazyDict : public LazyDictBase { + friend class Asset; + friend class AssetWriter; + + typedef typename std::gltf_unordered_map<std::string, unsigned int> Dict; + + std::vector<T *> mObjs; //! The read objects + Dict mObjsById; //! The read objects accessible by id + const char *mDictId; //! ID of the dictionary object + const char *mExtId; //! ID of the extension defining the dictionary + Value *mDict; //! JSON dictionary object + Asset &mAsset; //! The asset instance + + void AttachToDocument(Document &doc); + void DetachFromDocument(); + +#if !defined(ASSIMP_BUILD_NO_EXPORT) + void WriteObjects(AssetWriter &writer) { WriteLazyDict<T>(*this, writer); } +#endif + + Ref<T> Add(T *obj); + +public: + LazyDict(Asset &asset, const char *dictId, const char *extId = 0); + ~LazyDict(); + + Ref<T> Get(const char *id); + Ref<T> Get(unsigned int i); + Ref<T> Get(const std::string &pID) { return Get(pID.c_str()); } + + Ref<T> Create(const char *id); + Ref<T> Create(const std::string &id) { return Create(id.c_str()); } + + inline unsigned int Size() const { return unsigned(mObjs.size()); } + + inline T &operator[](size_t i) { return *mObjs[i]; } +}; + +struct AssetMetadata { + std::string copyright; //!< A copyright message suitable for display to credit the content creator. + std::string generator; //!< Tool that generated this glTF model.Useful for debugging. + bool premultipliedAlpha; //!< Specifies if the shaders were generated with premultiplied alpha. (default: false) + + struct { + std::string api; //!< Specifies the target rendering API (default: "WebGL") + std::string version; //!< Specifies the target rendering API (default: "1.0.3") + } profile; //!< Specifies the target rendering API and version, e.g., WebGL 1.0.3. (default: {}) + + std::string version; //!< The glTF format version (should be 1.0) + + void Read(Document &doc); + + AssetMetadata() : + premultipliedAlpha(false), version() { + } +}; + +// +// glTF Asset class +// + +//! Root object for a glTF asset +class Asset { + using IdMap = std::gltf_unordered_map<std::string, int>; + + template <class T> + friend class LazyDict; + friend struct Buffer; // To access OpenFile + friend class AssetWriter; + +private: + IOSystem *mIOSystem; + + std::string mCurrentAssetDir; + + size_t mSceneLength; + size_t mBodyOffset, mBodyLength; + + std::vector<LazyDictBase *> mDicts; + + IdMap mUsedIds; + + Ref<Buffer> mBodyBuffer; + + Asset(Asset &); + Asset &operator=(const Asset &); + +public: + //! Keeps info about the enabled extensions + struct Extensions { + bool KHR_binary_glTF; + bool KHR_materials_common; + + } extensionsUsed; + + AssetMetadata asset; + + // Dictionaries for each type of object + + LazyDict<Accessor> accessors; + LazyDict<Animation> animations; + LazyDict<Buffer> buffers; + LazyDict<BufferView> bufferViews; + LazyDict<Camera> cameras; + LazyDict<Image> images; + LazyDict<Material> materials; + LazyDict<Mesh> meshes; + LazyDict<Node> nodes; + LazyDict<Sampler> samplers; + LazyDict<Scene> scenes; + LazyDict<Skin> skins; + LazyDict<Texture> textures; + + LazyDict<Light> lights; // KHR_materials_common ext + + Ref<Scene> scene; + +public: + Asset(IOSystem *io = 0) : + mIOSystem(io), + asset(), + accessors(*this, "accessors"), + animations(*this, "animations"), + buffers(*this, "buffers"), + bufferViews(*this, "bufferViews"), + cameras(*this, "cameras"), + images(*this, "images"), + materials(*this, "materials"), + meshes(*this, "meshes"), + nodes(*this, "nodes"), + samplers(*this, "samplers"), + scenes(*this, "scenes"), + skins(*this, "skins"), + textures(*this, "textures"), + lights(*this, "lights", "KHR_materials_common") { + memset(&extensionsUsed, 0, sizeof(extensionsUsed)); + } + + //! Main function + void Load(const std::string &file, bool isBinary = false); + + //! Enables the "KHR_binary_glTF" extension on the asset + void SetAsBinary(); + + //! Search for an available name, starting from the given strings + std::string FindUniqueID(const std::string &str, const char *suffix); + + Ref<Buffer> GetBodyBuffer() { return mBodyBuffer; } + +private: + void ReadBinaryHeader(IOStream &stream); + + void ReadExtensionsUsed(Document &doc); + + IOStream *OpenFile(const std::string &path, const char *mode, bool absolute = false); +}; + +} // namespace glTF + +// Include the implementation of the methods +#include "glTFAsset.inl" + +#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER + +#endif // GLTFASSET_H_INC diff --git a/libs/assimp/code/AssetLib/glTF/glTFAsset.inl b/libs/assimp/code/AssetLib/glTF/glTFAsset.inl new file mode 100644 index 0000000..2b76a30 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFAsset.inl @@ -0,0 +1,1316 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#include <assimp/MemoryIOWrapper.h> +#include <assimp/StringUtils.h> +#include <iomanip> + +// Header files, Assimp +#include <assimp/DefaultLogger.hpp> +#include <assimp/Base64.hpp> + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC +// Header files, Open3DGC. +#include <Open3DGC/o3dgcSC3DMCDecoder.h> +#endif + +using namespace Assimp; +using namespace glTFCommon; + +namespace glTF { + +#if _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4706) +#endif // _MSC_VER + +// +// LazyDict methods +// + +template <class T> +inline LazyDict<T>::LazyDict(Asset &asset, const char *dictId, const char *extId) : + mDictId(dictId), mExtId(extId), mDict(0), mAsset(asset) { + asset.mDicts.push_back(this); // register to the list of dictionaries +} + +template <class T> +inline LazyDict<T>::~LazyDict() { + for (size_t i = 0; i < mObjs.size(); ++i) { + delete mObjs[i]; + } +} + +template <class T> +inline void LazyDict<T>::AttachToDocument(Document &doc) { + Value *container = 0; + + if (mExtId) { + if (Value *exts = FindObject(doc, "extensions")) { + container = FindObject(*exts, mExtId); + } + } else { + container = &doc; + } + + if (container) { + mDict = FindObject(*container, mDictId); + } +} + +template <class T> +inline void LazyDict<T>::DetachFromDocument() { + mDict = 0; +} + +template <class T> +Ref<T> LazyDict<T>::Get(unsigned int i) { + return Ref<T>(mObjs, i); +} + +template <class T> +Ref<T> LazyDict<T>::Get(const char *id) { + id = T::TranslateId(mAsset, id); + + typename Dict::iterator it = mObjsById.find(id); + if (it != mObjsById.end()) { // already created? + return Ref<T>(mObjs, it->second); + } + + // read it from the JSON object + if (!mDict) { + throw DeadlyImportError("GLTF: Missing section \"", mDictId, "\""); + } + + Value::MemberIterator obj = mDict->FindMember(id); + if (obj == mDict->MemberEnd()) { + throw DeadlyImportError("GLTF: Missing object with id \"", id, "\" in \"", mDictId, "\""); + } + if (!obj->value.IsObject()) { + throw DeadlyImportError("GLTF: Object with id \"", id, "\" is not a JSON object"); + } + + // create an instance of the given type + T *inst = new T(); + inst->id = id; + ReadMember(obj->value, "name", inst->name); + inst->Read(obj->value, mAsset); + return Add(inst); +} + +template <class T> +Ref<T> LazyDict<T>::Add(T *obj) { + unsigned int idx = unsigned(mObjs.size()); + mObjs.push_back(obj); + mObjsById[obj->id] = idx; + mAsset.mUsedIds[obj->id] = true; + return Ref<T>(mObjs, idx); +} + +template <class T> +Ref<T> LazyDict<T>::Create(const char *id) { + Asset::IdMap::iterator it = mAsset.mUsedIds.find(id); + if (it != mAsset.mUsedIds.end()) { + throw DeadlyImportError("GLTF: two objects with the same ID exist"); + } + T *inst = new T(); + inst->id = id; + return Add(inst); +} + +// +// glTF dictionary objects methods +// + +inline Buffer::Buffer() : + byteLength(0), type(Type_arraybuffer), EncodedRegion_Current(nullptr), mIsSpecial(false) {} + +inline Buffer::~Buffer() { + for (SEncodedRegion *reg : EncodedRegion_List) + delete reg; +} + +inline const char *Buffer::TranslateId(Asset &r, const char *id) { + // Compatibility with old spec + if (r.extensionsUsed.KHR_binary_glTF && strcmp(id, "KHR_binary_glTF") == 0) { + return "binary_glTF"; + } + + return id; +} + +inline void Buffer::Read(Value &obj, Asset &r) { + size_t statedLength = MemberOrDefault<size_t>(obj, "byteLength", 0); + byteLength = statedLength; + + Value *it = FindString(obj, "uri"); + if (!it) { + if (statedLength > 0) { + throw DeadlyImportError("GLTF: buffer with non-zero length missing the \"uri\" attribute"); + } + return; + } + + const char *uri = it->GetString(); + + glTFCommon::Util::DataURI dataURI; + if (ParseDataURI(uri, it->GetStringLength(), dataURI)) { + if (dataURI.base64) { + uint8_t *data = 0; + this->byteLength = Base64::Decode(dataURI.data, dataURI.dataLength, data); + this->mData.reset(data, std::default_delete<uint8_t[]>()); + + if (statedLength > 0 && this->byteLength != statedLength) { + throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(statedLength), + " bytes, but found ", ai_to_string(dataURI.dataLength)); + } + } else { // assume raw data + if (statedLength != dataURI.dataLength) { + throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(statedLength), + " bytes, but found ", ai_to_string(dataURI.dataLength)); + } + + this->mData.reset(new uint8_t[dataURI.dataLength], std::default_delete<uint8_t[]>()); + memcpy(this->mData.get(), dataURI.data, dataURI.dataLength); + } + } else { // Local file + if (byteLength > 0) { + std::string dir = !r.mCurrentAssetDir.empty() ? ( + r.mCurrentAssetDir.back() == '/' ? + r.mCurrentAssetDir : + r.mCurrentAssetDir + '/') : + ""; + + IOStream *file = r.OpenFile(dir + uri, "rb"); + if (file) { + bool ok = LoadFromStream(*file, byteLength); + delete file; + + if (!ok) + throw DeadlyImportError("GLTF: error while reading referenced file \"", uri, "\""); + } else { + throw DeadlyImportError("GLTF: could not open referenced file \"", uri, "\""); + } + } + } +} + +inline bool Buffer::LoadFromStream(IOStream &stream, size_t length, size_t baseOffset) { + byteLength = length ? length : stream.FileSize(); + + if (baseOffset) { + stream.Seek(baseOffset, aiOrigin_SET); + } + + mData.reset(new uint8_t[byteLength], std::default_delete<uint8_t[]>()); + + if (stream.Read(mData.get(), byteLength, 1) != 1) { + return false; + } + return true; +} + +inline void Buffer::EncodedRegion_Mark(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID) { + // Check pointer to data + if (pDecodedData == nullptr) throw DeadlyImportError("GLTF: for marking encoded region pointer to decoded data must be provided."); + + // Check offset + if (pOffset > byteLength) { + const uint8_t val_size = 32; + + char val[val_size]; + + ai_snprintf(val, val_size, AI_SIZEFMT, pOffset); + throw DeadlyImportError("GLTF: incorrect offset value (", val, ") for marking encoded region."); + } + + // Check length + if ((pOffset + pEncodedData_Length) > byteLength) { + const uint8_t val_size = 64; + + char val[val_size]; + + ai_snprintf(val, val_size, AI_SIZEFMT "/" AI_SIZEFMT, pOffset, pEncodedData_Length); + throw DeadlyImportError("GLTF: encoded region with offset/length (", val, ") is out of range."); + } + + // Add new region + EncodedRegion_List.push_back(new SEncodedRegion(pOffset, pEncodedData_Length, pDecodedData, pDecodedData_Length, pID)); + // And set new value for "byteLength" + byteLength += (pDecodedData_Length - pEncodedData_Length); +} + +inline void Buffer::EncodedRegion_SetCurrent(const std::string &pID) { + if ((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) return; + + for (SEncodedRegion *reg : EncodedRegion_List) { + if (reg->ID == pID) { + EncodedRegion_Current = reg; + + return; + } + } + + throw DeadlyImportError("GLTF: EncodedRegion with ID: \"", pID, "\" not found."); +} + +inline bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count) { + const size_t new_data_size = byteLength + pReplace_Count - pBufferData_Count; + + uint8_t *new_data; + + if ((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) return false; + + new_data = new uint8_t[new_data_size]; + // Copy data which place before replacing part. + memcpy(new_data, mData.get(), pBufferData_Offset); + // Copy new data. + memcpy(&new_data[pBufferData_Offset], pReplace_Data, pReplace_Count); + // Copy data which place after replacing part. + memcpy(&new_data[pBufferData_Offset + pReplace_Count], &mData.get()[pBufferData_Offset + pBufferData_Count], pBufferData_Offset); + // Apply new data + mData.reset(new_data, std::default_delete<uint8_t[]>()); + byteLength = new_data_size; + + return true; +} + +inline size_t Buffer::AppendData(uint8_t *data, size_t length) { + size_t offset = this->byteLength; + Grow(length); + memcpy(mData.get() + offset, data, length); + return offset; +} + +inline void Buffer::Grow(size_t amount) { + if (amount <= 0) return; + if (capacity >= byteLength + amount) { + byteLength += amount; + return; + } + + // Shift operation is standard way to divide integer by 2, it doesn't cast it to float back and forth, also works for odd numbers, + // originally it would look like: static_cast<size_t>(capacity * 1.5f) + capacity = std::max(capacity + (capacity >> 1), byteLength + amount); + + uint8_t *b = new uint8_t[capacity]; + if (mData) memcpy(b, mData.get(), byteLength); + mData.reset(b, std::default_delete<uint8_t[]>()); + byteLength += amount; +} + +// +// struct BufferView +// + +inline void BufferView::Read(Value &obj, Asset &r) { + const char *bufferId = MemberOrDefault<const char *>(obj, "buffer", 0); + if (bufferId) { + buffer = r.buffers.Get(bufferId); + } + + byteOffset = MemberOrDefault(obj, "byteOffset", 0u); + byteLength = MemberOrDefault(obj, "byteLength", 0u); +} + +// +// struct Accessor +// + +inline void Accessor::Read(Value &obj, Asset &r) { + const char *bufferViewId = MemberOrDefault<const char *>(obj, "bufferView", 0); + if (bufferViewId) { + bufferView = r.bufferViews.Get(bufferViewId); + } + + byteOffset = MemberOrDefault(obj, "byteOffset", 0u); + byteStride = MemberOrDefault(obj, "byteStride", 0u); + componentType = MemberOrDefault(obj, "componentType", ComponentType_BYTE); + count = MemberOrDefault(obj, "count", 0u); + + const char *typestr; + type = ReadMember(obj, "type", typestr) ? AttribType::FromString(typestr) : AttribType::SCALAR; +} + +inline unsigned int Accessor::GetNumComponents() { + return AttribType::GetNumComponents(type); +} + +inline unsigned int Accessor::GetBytesPerComponent() { + return int(ComponentTypeSize(componentType)); +} + +inline unsigned int Accessor::GetElementSize() { + return GetNumComponents() * GetBytesPerComponent(); +} + +inline uint8_t *Accessor::GetPointer() { + if (!bufferView || !bufferView->buffer) return 0; + uint8_t *basePtr = bufferView->buffer->GetPointer(); + if (!basePtr) return 0; + + size_t offset = byteOffset + bufferView->byteOffset; + + // Check if region is encoded. + if (bufferView->buffer->EncodedRegion_Current != nullptr) { + const size_t begin = bufferView->buffer->EncodedRegion_Current->Offset; + const size_t end = begin + bufferView->buffer->EncodedRegion_Current->DecodedData_Length; + + if ((offset >= begin) && (offset < end)) + return &bufferView->buffer->EncodedRegion_Current->DecodedData[offset - begin]; + } + + return basePtr + offset; +} + +namespace { +inline void CopyData(size_t count, + const uint8_t *src, size_t src_stride, + uint8_t *dst, size_t dst_stride) { + if (src_stride == dst_stride) { + memcpy(dst, src, count * src_stride); + } else { + size_t sz = std::min(src_stride, dst_stride); + for (size_t i = 0; i < count; ++i) { + memcpy(dst, src, sz); + if (sz < dst_stride) { + memset(dst + sz, 0, dst_stride - sz); + } + src += src_stride; + dst += dst_stride; + } + } +} +} // namespace + +template <class T> +bool Accessor::ExtractData(T *&outData) { + uint8_t *data = GetPointer(); + if (!data) return false; + + const size_t elemSize = GetElementSize(); + const size_t totalSize = elemSize * count; + + const size_t stride = byteStride ? byteStride : elemSize; + + const size_t targetElemSize = sizeof(T); + ai_assert(elemSize <= targetElemSize); + + ai_assert(count * stride <= bufferView->byteLength); + + outData = new T[count]; + if (stride == elemSize && targetElemSize == elemSize) { + memcpy(outData, data, totalSize); + } else { + for (size_t i = 0; i < count; ++i) { + memcpy(outData + i, data + i * stride, elemSize); + } + } + + return true; +} + +inline void Accessor::WriteData(size_t cnt, const void *src_buffer, size_t src_stride) { + uint8_t *buffer_ptr = bufferView->buffer->GetPointer(); + size_t offset = byteOffset + bufferView->byteOffset; + + size_t dst_stride = GetNumComponents() * GetBytesPerComponent(); + + const uint8_t *src = reinterpret_cast<const uint8_t *>(src_buffer); + uint8_t *dst = reinterpret_cast<uint8_t *>(buffer_ptr + offset); + + ai_assert(dst + count * dst_stride <= buffer_ptr + bufferView->buffer->byteLength); + CopyData(cnt, src, src_stride, dst, dst_stride); +} + +inline Accessor::Indexer::Indexer(Accessor &acc) : + accessor(acc), data(acc.GetPointer()), elemSize(acc.GetElementSize()), stride(acc.byteStride ? acc.byteStride : elemSize) { +} + +//! Accesses the i-th value as defined by the accessor +template <class T> +T Accessor::Indexer::GetValue(int i) { + ai_assert(data); + ai_assert(i * stride < accessor.bufferView->byteLength); + T value = T(); + memcpy(&value, data + i * stride, elemSize); + //value >>= 8 * (sizeof(T) - elemSize); + return value; +} + +inline Image::Image() : + width(0), height(0), mDataLength(0) { +} + +inline void Image::Read(Value &obj, Asset &r) { + // Check for extensions first (to detect binary embedded data) + if (Value *extensions = FindObject(obj, "extensions")) { + if (r.extensionsUsed.KHR_binary_glTF) { + if (Value *ext = FindObject(*extensions, "KHR_binary_glTF")) { + + width = MemberOrDefault(*ext, "width", 0); + height = MemberOrDefault(*ext, "height", 0); + + ReadMember(*ext, "mimeType", mimeType); + + const char *bufferViewId; + if (ReadMember(*ext, "bufferView", bufferViewId)) { + Ref<BufferView> bv = r.bufferViews.Get(bufferViewId); + if (bv) { + mDataLength = bv->byteLength; + mData.reset(new uint8_t[mDataLength]); + memcpy(mData.get(), bv->buffer->GetPointer() + bv->byteOffset, mDataLength); + } + } + } + } + } + + if (!mDataLength) { + Value *curUri = FindString(obj, "uri"); + if (nullptr != curUri) { + const char *uristr = curUri->GetString(); + + glTFCommon::Util::DataURI dataURI; + if (ParseDataURI(uristr, curUri->GetStringLength(), dataURI)) { + mimeType = dataURI.mediaType; + if (dataURI.base64) { + uint8_t *ptr = nullptr; + mDataLength = Base64::Decode(dataURI.data, dataURI.dataLength, ptr); + mData.reset(ptr); + } + } else { + this->uri = uristr; + } + } + } +} + +inline uint8_t *Image::StealData() { + mDataLength = 0; + return mData.release(); +} + +inline void Image::SetData(uint8_t *data, size_t length, Asset &r) { + Ref<Buffer> b = r.GetBodyBuffer(); + if (b) { // binary file: append to body + std::string bvId = r.FindUniqueID(this->id, "imgdata"); + bufferView = r.bufferViews.Create(bvId); + + bufferView->buffer = b; + bufferView->byteLength = length; + bufferView->byteOffset = b->AppendData(data, length); + } else { // text file: will be stored as a data uri + uint8_t *temp = new uint8_t[length]; + memcpy(temp, data, length); + this->mData.reset(temp); + this->mDataLength = length; + } +} + +inline void Sampler::Read(Value &obj, Asset & /*r*/) { + SetDefaults(); + + ReadMember(obj, "magFilter", magFilter); + ReadMember(obj, "minFilter", minFilter); + ReadMember(obj, "wrapS", wrapS); + ReadMember(obj, "wrapT", wrapT); +} + +inline void Sampler::SetDefaults() { + magFilter = SamplerMagFilter_Linear; + minFilter = SamplerMinFilter_Linear; + wrapS = SamplerWrap_Repeat; + wrapT = SamplerWrap_Repeat; +} + +inline void Texture::Read(Value &obj, Asset &r) { + const char *sourcestr; + if (ReadMember(obj, "source", sourcestr)) { + source = r.images.Get(sourcestr); + } + + const char *samplerstr; + if (ReadMember(obj, "sampler", samplerstr)) { + sampler = r.samplers.Get(samplerstr); + } +} + +namespace { +inline void ReadMaterialProperty(Asset &r, Value &vals, const char *propName, TexProperty &out) { + if (Value *prop = FindMember(vals, propName)) { + if (prop->IsString()) { + out.texture = r.textures.Get(prop->GetString()); + } else { + ReadValue(*prop, out.color); + } + } +} +} // namespace + +inline void Material::Read(Value &material, Asset &r) { + SetDefaults(); + + if (Value *values = FindObject(material, "values")) { + ReadMaterialProperty(r, *values, "ambient", this->ambient); + ReadMaterialProperty(r, *values, "diffuse", this->diffuse); + ReadMaterialProperty(r, *values, "specular", this->specular); + + ReadMember(*values, "transparency", transparency); + ReadMember(*values, "shininess", shininess); + } + + if (Value *extensions = FindObject(material, "extensions")) { + if (r.extensionsUsed.KHR_materials_common) { + if (Value *ext = FindObject(*extensions, "KHR_materials_common")) { + if (Value *tnq = FindString(*ext, "technique")) { + const char *t = tnq->GetString(); + if (strcmp(t, "BLINN") == 0) + technique = Technique_BLINN; + else if (strcmp(t, "PHONG") == 0) + technique = Technique_PHONG; + else if (strcmp(t, "LAMBERT") == 0) + technique = Technique_LAMBERT; + else if (strcmp(t, "CONSTANT") == 0) + technique = Technique_CONSTANT; + } + + if (Value *values = FindObject(*ext, "values")) { + ReadMaterialProperty(r, *values, "ambient", this->ambient); + ReadMaterialProperty(r, *values, "diffuse", this->diffuse); + ReadMaterialProperty(r, *values, "specular", this->specular); + + ReadMember(*values, "doubleSided", doubleSided); + ReadMember(*values, "transparent", transparent); + ReadMember(*values, "transparency", transparency); + ReadMember(*values, "shininess", shininess); + } + } + } + } +} + +namespace { +void SetVector(vec4 &v, float x, float y, float z, float w) { + v[0] = x; + v[1] = y; + v[2] = z; + v[3] = w; +} +} // namespace + +inline void Material::SetDefaults() { + SetVector(ambient.color, 0, 0, 0, 1); + SetVector(diffuse.color, 0, 0, 0, 1); + SetVector(specular.color, 0, 0, 0, 1); + SetVector(emission.color, 0, 0, 0, 1); + + doubleSided = false; + transparent = false; + transparency = 1.0; + shininess = 0.0; + + technique = Technique_undefined; +} + +namespace { + +template <int N> +inline int Compare(const char *attr, const char (&str)[N]) { + return (strncmp(attr, str, N - 1) == 0) ? N - 1 : 0; +} + +inline bool GetAttribVector(Mesh::Primitive &p, const char *attr, Mesh::AccessorList *&v, int &pos) { + if ((pos = Compare(attr, "POSITION"))) { + v = &(p.attributes.position); + } else if ((pos = Compare(attr, "NORMAL"))) { + v = &(p.attributes.normal); + } else if ((pos = Compare(attr, "TEXCOORD"))) { + v = &(p.attributes.texcoord); + } else if ((pos = Compare(attr, "COLOR"))) { + v = &(p.attributes.color); + } else if ((pos = Compare(attr, "JOINT"))) { + v = &(p.attributes.joint); + } else if ((pos = Compare(attr, "JOINTMATRIX"))) { + v = &(p.attributes.jointmatrix); + } else if ((pos = Compare(attr, "WEIGHT"))) { + v = &(p.attributes.weight); + } else + return false; + return true; +} +} // namespace + +inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { + /****************** Mesh primitives ******************/ + Value *curPrimitives = FindArray(pJSON_Object, "primitives"); + if (nullptr != curPrimitives) { + this->primitives.resize(curPrimitives->Size()); + for (unsigned int i = 0; i < curPrimitives->Size(); ++i) { + Value &primitive = (*curPrimitives)[i]; + + Primitive &prim = this->primitives[i]; + prim.mode = MemberOrDefault(primitive, "mode", PrimitiveMode_TRIANGLES); + + if (Value *attrs = FindObject(primitive, "attributes")) { + for (Value::MemberIterator it = attrs->MemberBegin(); it != attrs->MemberEnd(); ++it) { + if (!it->value.IsString()) continue; + const char *attr = it->name.GetString(); + // Valid attribute semantics include POSITION, NORMAL, TEXCOORD, COLOR, JOINT, JOINTMATRIX, + // and WEIGHT.Attribute semantics can be of the form[semantic]_[set_index], e.g., TEXCOORD_0, TEXCOORD_1, etc. + + int undPos = 0; + Mesh::AccessorList *vec = 0; + if (GetAttribVector(prim, attr, vec, undPos)) { + size_t idx = (attr[undPos] == '_') ? atoi(attr + undPos + 1) : 0; + if ((*vec).size() <= idx) (*vec).resize(idx + 1); + (*vec)[idx] = pAsset_Root.accessors.Get(it->value.GetString()); + } + } + } + + if (Value *indices = FindString(primitive, "indices")) { + prim.indices = pAsset_Root.accessors.Get(indices->GetString()); + } + + if (Value *material = FindString(primitive, "material")) { + prim.material = pAsset_Root.materials.Get(material->GetString()); + } + } + } + + /****************** Mesh extensions ******************/ + Value *json_extensions = FindObject(pJSON_Object, "extensions"); + + if (json_extensions == nullptr) goto mr_skip_extensions; + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + for (Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); it_memb++) { + if (it_memb->name.GetString() == std::string("Open3DGC-compression")) { + // Search for compressed data. + // Compressed data contain description of part of "buffer" which is encoded. This part must be decoded and + // new data will replace old encoded part by request. In fact \"compressedData\" is kind of "accessor" structure. + Value *comp_data = FindObject(it_memb->value, "compressedData"); + + if (comp_data == nullptr) throw DeadlyImportError("GLTF: \"Open3DGC-compression\" must has \"compressedData\"."); + + ASSIMP_LOG_INFO("GLTF: Decompressing Open3DGC data."); + +/************** Read data from JSON-document **************/ +#define MESH_READ_COMPRESSEDDATA_MEMBER(pFieldName, pOut) \ + if (!ReadMember(*comp_data, pFieldName, pOut)) { \ + throw DeadlyImportError("GLTF: \"compressedData\" must has \"", pFieldName, "\"."); \ + } + + const char *mode_str; + const char *type_str; + ComponentType component_type; + SCompression_Open3DGC *ext_o3dgc = new SCompression_Open3DGC; + + MESH_READ_COMPRESSEDDATA_MEMBER("buffer", ext_o3dgc->Buffer); + MESH_READ_COMPRESSEDDATA_MEMBER("byteOffset", ext_o3dgc->Offset); + MESH_READ_COMPRESSEDDATA_MEMBER("componentType", component_type); + MESH_READ_COMPRESSEDDATA_MEMBER("type", type_str); + MESH_READ_COMPRESSEDDATA_MEMBER("count", ext_o3dgc->Count); + MESH_READ_COMPRESSEDDATA_MEMBER("mode", mode_str); + MESH_READ_COMPRESSEDDATA_MEMBER("indicesCount", ext_o3dgc->IndicesCount); + MESH_READ_COMPRESSEDDATA_MEMBER("verticesCount", ext_o3dgc->VerticesCount); + +#undef MESH_READ_COMPRESSEDDATA_MEMBER + + // Check some values + if (strcmp(type_str, "SCALAR")) throw DeadlyImportError("GLTF: only \"SCALAR\" type is supported for compressed data."); + if (component_type != ComponentType_UNSIGNED_BYTE) throw DeadlyImportError("GLTF: only \"UNSIGNED_BYTE\" component type is supported for compressed data."); + + // Set read/write data mode. + if (strcmp(mode_str, "binary") == 0) + ext_o3dgc->Binary = true; + else if (strcmp(mode_str, "ascii") == 0) + ext_o3dgc->Binary = false; + else + throw DeadlyImportError("GLTF: for compressed data supported modes is: \"ascii\", \"binary\". Not the: \"", mode_str, "\"."); + + /************************ Decoding ************************/ + Decode_O3DGC(*ext_o3dgc, pAsset_Root); + Extension.push_back(ext_o3dgc); // store info in mesh extensions list. + } // if(it_memb->name.GetString() == "Open3DGC-compression") + else { + throw DeadlyImportError("GLTF: Unknown mesh extension: \"", it_memb->name.GetString(), "\"."); + } + } // for(Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); json_extensions++) +#endif + +mr_skip_extensions: + + return; // After label some operators must be present. +} + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC +inline void Mesh::Decode_O3DGC(const SCompression_Open3DGC &pCompression_Open3DGC, Asset &pAsset_Root) { + typedef unsigned short IndicesType; ///< \sa glTFExporter::ExportMeshes. + + o3dgc::SC3DMCDecoder<IndicesType> decoder; + o3dgc::IndexedFaceSet<IndicesType> ifs; + o3dgc::BinaryStream bstream; + uint8_t *decoded_data; + size_t decoded_data_size = 0; + Ref<Buffer> buf = pAsset_Root.buffers.Get(pCompression_Open3DGC.Buffer); + + // Read data from buffer and place it in BinaryStream for decoder. + // Just "Count" because always is used type equivalent to uint8_t. + bstream.LoadFromBuffer(&buf->GetPointer()[pCompression_Open3DGC.Offset], static_cast<unsigned long>(pCompression_Open3DGC.Count)); + + // After decoding header we can get size of primitives. + if (decoder.DecodeHeader(ifs, bstream) != o3dgc::O3DGC_OK) throw DeadlyImportError("GLTF: can not decode Open3DGC header."); + + /****************** Get sizes of arrays and check sizes ******************/ + // Note. See "Limitations for meshes when using Open3DGC-compression". + + // Indices + size_t size_coordindex = ifs.GetNCoordIndex() * 3; // See float attributes note. + + if (primitives[0].indices->count != size_coordindex) + throw DeadlyImportError("GLTF: Open3DGC. Compressed indices count (", ai_to_string(size_coordindex), + ") not equal to uncompressed (", ai_to_string(primitives[0].indices->count), ")."); + + size_coordindex *= sizeof(IndicesType); + // Coordinates + size_t size_coord = ifs.GetNCoord(); // See float attributes note. + + if (primitives[0].attributes.position[0]->count != size_coord) + throw DeadlyImportError("GLTF: Open3DGC. Compressed positions count (", ai_to_string(size_coord), + ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.position[0]->count), ")."); + + size_coord *= 3 * sizeof(float); + // Normals + size_t size_normal = ifs.GetNNormal(); // See float attributes note. + + if (primitives[0].attributes.normal[0]->count != size_normal) + throw DeadlyImportError("GLTF: Open3DGC. Compressed normals count (", ai_to_string(size_normal), + ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.normal[0]->count), ")."); + + size_normal *= 3 * sizeof(float); + // Additional attributes. + std::vector<size_t> size_floatattr; + std::vector<size_t> size_intattr; + + size_floatattr.resize(ifs.GetNumFloatAttributes()); + size_intattr.resize(ifs.GetNumIntAttributes()); + + decoded_data_size = size_coordindex + size_coord + size_normal; + for (size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) { + // size = number_of_elements * components_per_element * size_of_component. + // Note. But as you can see above, at first we are use this variable in meaning "count". After checking count of objects... + size_t tval = ifs.GetNFloatAttribute(static_cast<unsigned long>(idx)); + + switch (ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))) { + case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: + // Check situation when encoded data contain texture coordinates but primitive not. + if (idx_texcoord < primitives[0].attributes.texcoord.size()) { + if (primitives[0].attributes.texcoord[idx]->count != tval) + throw DeadlyImportError("GLTF: Open3DGC. Compressed texture coordinates count (", ai_to_string(tval), + ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.texcoord[idx]->count), ")."); + + idx_texcoord++; + } else { + ifs.SetNFloatAttribute(static_cast<unsigned long>(idx), 0ul); // Disable decoding this attribute. + } + + break; + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx)))); + } + + tval *= ifs.GetFloatAttributeDim(static_cast<unsigned long>(idx)) * sizeof(o3dgc::Real); // After checking count of objects we can get size of array. + size_floatattr[idx] = tval; + decoded_data_size += tval; + } + + for (size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) { + // size = number_of_elements * components_per_element * size_of_component. See float attributes note. + size_t tval = ifs.GetNIntAttribute(static_cast<unsigned long>(idx)); + switch (ifs.GetIntAttributeType(static_cast<unsigned long>(idx))) { + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: + break; + + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx)))); + } + + tval *= ifs.GetIntAttributeDim(static_cast<unsigned long>(idx)) * sizeof(long); // See float attributes note. + size_intattr[idx] = tval; + decoded_data_size += tval; + } + + // Create array for decoded data. + decoded_data = new uint8_t[decoded_data_size]; + + /****************** Set right array regions for decoder ******************/ + + auto get_buf_offset = [](Ref<Accessor> &pAccessor) -> size_t { return pAccessor->byteOffset + pAccessor->bufferView->byteOffset; }; + + // Indices + ifs.SetCoordIndex((IndicesType *const)(decoded_data + get_buf_offset(primitives[0].indices))); + // Coordinates + ifs.SetCoord((o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.position[0]))); + // Normals + if (size_normal) { + ifs.SetNormal((o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.normal[0]))); + } + + for (size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) { + switch (ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))) { + case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: + if (idx_texcoord < primitives[0].attributes.texcoord.size()) { + // See above about absent attributes. + ifs.SetFloatAttribute(static_cast<unsigned long>(idx), (o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.texcoord[idx]))); + idx_texcoord++; + } + + break; + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx)))); + } + } + + for (size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) { + switch (ifs.GetIntAttributeType(static_cast<unsigned int>(idx))) { + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: + break; + + // ifs.SetIntAttribute(idx, (long* const)(decoded_data + get_buf_offset(primitives[0].attributes.joint))); + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx)))); + } + } + + // + // Decode data + // + if (decoder.DecodePayload(ifs, bstream) != o3dgc::O3DGC_OK) { + throw DeadlyImportError("GLTF: can not decode Open3DGC data."); + } + + // Set encoded region for "buffer". + buf->EncodedRegion_Mark(pCompression_Open3DGC.Offset, pCompression_Open3DGC.Count, decoded_data, decoded_data_size, id); + // No. Do not delete "output_data". After calling "EncodedRegion_Mark" bufferView is owner of "output_data". + // "delete [] output_data;" +} +#endif + +inline void Camera::Read(Value &obj, Asset & /*r*/) { + type = MemberOrDefault(obj, "type", Camera::Perspective); + + const char *subobjId = (type == Camera::Orthographic) ? "orthographic" : "perspective"; + + Value *it = FindObject(obj, subobjId); + if (!it) throw DeadlyImportError("GLTF: Camera missing its parameters"); + + if (type == Camera::Perspective) { + perspective.aspectRatio = MemberOrDefault(*it, "aspectRatio", 0.f); + perspective.yfov = MemberOrDefault(*it, "yfov", 3.1415f / 2.f); + perspective.zfar = MemberOrDefault(*it, "zfar", 100.f); + perspective.znear = MemberOrDefault(*it, "znear", 0.01f); + } else { + ortographic.xmag = MemberOrDefault(*it, "xmag", 1.f); + ortographic.ymag = MemberOrDefault(*it, "ymag", 1.f); + ortographic.zfar = MemberOrDefault(*it, "zfar", 100.f); + ortographic.znear = MemberOrDefault(*it, "znear", 0.01f); + } +} + +inline void Light::Read(Value &obj, Asset & /*r*/) { + SetDefaults(); + + Value *curType = FindString(obj, "type"); + if (nullptr != curType) { + const char *t = curType->GetString(); + if (strcmp(t, "ambient") == 0) + this->type = Type_ambient; + else if (strcmp(t, "directional") == 0) + this->type = Type_directional; + else if (strcmp(t, "point") == 0) + this->type = Type_point; + else if (strcmp(t, "spot") == 0) + this->type = Type_spot; + + if (this->type != Type_undefined) { + if (Value *vals = FindString(obj, t)) { + ReadMember(*vals, "color", color); + + ReadMember(*vals, "constantAttenuation", constantAttenuation); + ReadMember(*vals, "linearAttenuation", linearAttenuation); + ReadMember(*vals, "quadraticAttenuation", quadraticAttenuation); + ReadMember(*vals, "distance", distance); + + ReadMember(*vals, "falloffAngle", falloffAngle); + ReadMember(*vals, "falloffExponent", falloffExponent); + } + } + } +} + +inline void Light::SetDefaults() { +#ifndef M_PI + const float M_PI = 3.14159265358979323846f; +#endif + + type = Type_undefined; + + SetVector(color, 0.f, 0.f, 0.f, 1.f); + + constantAttenuation = 0.f; + linearAttenuation = 1.f; + quadraticAttenuation = 1.f; + distance = 0.f; + + falloffAngle = static_cast<float>(M_PI / 2.f); + falloffExponent = 0.f; +} + +inline void Node::Read(Value &obj, Asset &r) { + if (name.empty()) { + name = id; + } + + Value *curChildren = FindArray(obj, "children"); + if (nullptr != curChildren) { + this->children.reserve(curChildren->Size()); + for (unsigned int i = 0; i < curChildren->Size(); ++i) { + Value &child = (*curChildren)[i]; + if (child.IsString()) { + // get/create the child node + Ref<Node> chn = r.nodes.Get(child.GetString()); + if (chn) this->children.push_back(chn); + } + } + } + + Value *curMatrix = FindArray(obj, "matrix"); + if (nullptr != curMatrix) { + ReadValue(*curMatrix, this->matrix); + } else { + ReadMember(obj, "translation", translation); + ReadMember(obj, "scale", scale); + ReadMember(obj, "rotation", rotation); + } + + Value *curMeshes = FindArray(obj, "meshes"); + if (nullptr != curMeshes) { + unsigned int numMeshes = (unsigned int)curMeshes->Size(); + + std::vector<unsigned int> meshList; + + this->meshes.reserve(numMeshes); + for (unsigned i = 0; i < numMeshes; ++i) { + if ((*curMeshes)[i].IsString()) { + Ref<Mesh> mesh = r.meshes.Get((*curMeshes)[i].GetString()); + if (mesh) { + this->meshes.push_back(mesh); + } + } + } + } + + Value *curCamera = FindString(obj, "camera"); + if (nullptr != curCamera) { + this->camera = r.cameras.Get(curCamera->GetString()); + if (this->camera) { + this->camera->id = this->id; + } + } + + // TODO load "skeletons", "skin", "jointName" + + if (Value *extensions = FindObject(obj, "extensions")) { + if (r.extensionsUsed.KHR_materials_common) { + + if (Value *ext = FindObject(*extensions, "KHR_materials_common")) { + Value *curLight = FindString(*ext, "light"); + if (nullptr != curLight) { + this->light = r.lights.Get(curLight->GetString()); + } + } + } + } +} + +inline void Scene::Read(Value &obj, Asset &r) { + if (Value *array = FindArray(obj, "nodes")) { + for (unsigned int i = 0; i < array->Size(); ++i) { + if (!(*array)[i].IsString()) continue; + Ref<Node> node = r.nodes.Get((*array)[i].GetString()); + if (node) + this->nodes.push_back(node); + } + } +} + +inline void AssetMetadata::Read(Document &doc) { + // read the version, etc. + if (Value *obj = FindObject(doc, "asset")) { + ReadMember(*obj, "copyright", copyright); + ReadMember(*obj, "generator", generator); + + premultipliedAlpha = MemberOrDefault(*obj, "premultipliedAlpha", false); + + if (Value *versionString = FindString(*obj, "version")) { + version = versionString->GetString(); + } else if (Value *versionNumber = FindNumber(*obj, "version")) { + char buf[4]; + + ai_snprintf(buf, 4, "%.1f", versionNumber->GetDouble()); + + version = buf; + } + + Value *curProfile = FindObject(*obj, "profile"); + if (nullptr != curProfile) { + ReadMember(*curProfile, "api", this->profile.api); + ReadMember(*curProfile, "version", this->profile.version); + } + } + + if (version.empty() || version[0] != '1') { + throw DeadlyImportError("GLTF: Unsupported glTF version: ", version); + } +} + +// +// Asset methods implementation +// + +inline void Asset::ReadBinaryHeader(IOStream &stream) { + GLB_Header header; + if (stream.Read(&header, sizeof(header), 1) != 1) { + throw DeadlyImportError("GLTF: Unable to read the file header"); + } + + if (strncmp((char *)header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)) != 0) { + throw DeadlyImportError("GLTF: Invalid binary glTF file"); + } + + AI_SWAP4(header.version); + asset.version = ai_to_string(header.version); + if (header.version != 1) { + throw DeadlyImportError("GLTF: Unsupported binary glTF version"); + } + + AI_SWAP4(header.sceneFormat); + if (header.sceneFormat != SceneFormat_JSON) { + throw DeadlyImportError("GLTF: Unsupported binary glTF scene format"); + } + + AI_SWAP4(header.length); + AI_SWAP4(header.sceneLength); + + static_assert(std::numeric_limits<uint32_t>::max() <= std::numeric_limits<size_t>::max(), "size_t must be at least 32bits"); + mSceneLength = static_cast<size_t>(header.sceneLength); // Can't be larger than 4GB (max. uint32_t) + + mBodyOffset = sizeof(header) + mSceneLength; + mBodyOffset = (mBodyOffset + 3) & ~3; // Round up to next multiple of 4 + + mBodyLength = header.length - mBodyOffset; +} + +inline void Asset::Load(const std::string &pFile, bool isBinary) { + mCurrentAssetDir.clear(); + + /*int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\'))); + if (pos != int(std::string::npos)) mCurrentAssetDir = pFile.substr(0, pos + 1);*/ + if (0 != strncmp(pFile.c_str(), AI_MEMORYIO_MAGIC_FILENAME, AI_MEMORYIO_MAGIC_FILENAME_LENGTH)) { + mCurrentAssetDir = getCurrentAssetDir(pFile); + } + + shared_ptr<IOStream> stream(OpenFile(pFile.c_str(), "rb", true)); + if (!stream) { + throw DeadlyImportError("GLTF: Could not open file for reading"); + } + + // is binary? then read the header + if (isBinary) { + SetAsBinary(); // also creates the body buffer + ReadBinaryHeader(*stream); + } else { + mSceneLength = stream->FileSize(); + mBodyLength = 0; + } + + // Smallest legal JSON file is "{}" Smallest loadable glTF file is larger than that but catch it later + if (mSceneLength < 2) { + throw DeadlyImportError("GLTF: No JSON file contents"); + } + + // Binary format only supports up to 4GB of JSON so limit it there to avoid extreme memory allocation + if (mSceneLength >= std::numeric_limits<uint32_t>::max()) { + throw DeadlyImportError("GLTF: JSON size greater than 4GB"); + } + + // read the scene data, ensure null termination + std::vector<char> sceneData(mSceneLength + 1); + sceneData[mSceneLength] = '\0'; + + if (stream->Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { + throw DeadlyImportError("GLTF: Could not read the file contents"); + } + + // parse the JSON document + + Document doc; + doc.ParseInsitu(&sceneData[0]); + + if (doc.HasParseError()) { + char buffer[32]; + ai_snprintf(buffer, 32, "%d", static_cast<int>(doc.GetErrorOffset())); + throw DeadlyImportError("GLTF: JSON parse error, offset ", buffer, ": ", GetParseError_En(doc.GetParseError())); + } + + if (!doc.IsObject()) { + throw DeadlyImportError("GLTF: JSON document root must be a JSON object"); + } + + // Fill the buffer instance for the current file embedded contents + if (mBodyLength > 0) { + if (!mBodyBuffer->LoadFromStream(*stream, mBodyLength, mBodyOffset)) { + throw DeadlyImportError("GLTF: Unable to read gltf file"); + } + } + + // Load the metadata + asset.Read(doc); + ReadExtensionsUsed(doc); + + // Prepare the dictionaries + for (size_t i = 0; i < mDicts.size(); ++i) { + mDicts[i]->AttachToDocument(doc); + } + + // Read the "scene" property, which specifies which scene to load + // and recursively load everything referenced by it + Value *curScene = FindString(doc, "scene"); + if (nullptr != curScene) { + this->scene = scenes.Get(curScene->GetString()); + } + + // Clean up + for (size_t i = 0; i < mDicts.size(); ++i) { + mDicts[i]->DetachFromDocument(); + } +} + +inline void Asset::SetAsBinary() { + if (!extensionsUsed.KHR_binary_glTF) { + extensionsUsed.KHR_binary_glTF = true; + mBodyBuffer = buffers.Create("binary_glTF"); + mBodyBuffer->MarkAsSpecial(); + } +} + +inline void Asset::ReadExtensionsUsed(Document &doc) { + Value *extsUsed = FindArray(doc, "extensionsUsed"); + if (!extsUsed) return; + + std::gltf_unordered_map<std::string, bool> exts; + + for (unsigned int i = 0; i < extsUsed->Size(); ++i) { + if ((*extsUsed)[i].IsString()) { + exts[(*extsUsed)[i].GetString()] = true; + } + } + + CHECK_EXT(KHR_binary_glTF); + CHECK_EXT(KHR_materials_common); + +#undef CHECK_EXT +} + +inline IOStream *Asset::OpenFile(const std::string &path, const char *mode, bool absolute) { +#ifdef ASSIMP_API + (void)absolute; + return mIOSystem->Open(path, mode); +#else + if (path.size() < 2) return 0; + if (!absolute && path[1] != ':' && path[0] != '/') { // relative? + path = mCurrentAssetDir + path; + } + FILE *f = fopen(path.c_str(), mode); + return f ? new IOStream(f) : 0; +#endif +} + +inline std::string Asset::FindUniqueID(const std::string &str, const char *suffix) { + std::string id = str; + + if (!id.empty()) { + if (mUsedIds.find(id) == mUsedIds.end()) + return id; + + id += "_"; + } + + id += suffix; + + Asset::IdMap::iterator it = mUsedIds.find(id); + if (it == mUsedIds.end()) + return id; + + char buffer[1024]; + int offset = ai_snprintf(buffer, sizeof(buffer), "%s_", id.c_str()); + for (int i = 0; it != mUsedIds.end(); ++i) { + ai_snprintf(buffer + offset, sizeof(buffer) - offset, "%d", i); + id = buffer; + it = mUsedIds.find(id); + } + + return id; +} + +#if _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + +} // namespace glTF diff --git a/libs/assimp/code/AssetLib/glTF/glTFAssetWriter.h b/libs/assimp/code/AssetLib/glTF/glTFAssetWriter.h new file mode 100644 index 0000000..6dbc424 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFAssetWriter.h @@ -0,0 +1,96 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 glTFWriter.h + * Declares a class to write gltf/glb files + * + * glTF Extensions Support: + * KHR_binary_glTF: full + * KHR_materials_common: full + */ +#ifndef GLTFASSETWRITER_H_INC +#define GLTFASSETWRITER_H_INC + +#if !defined(ASSIMP_BUILD_NO_GLTF_IMPORTER) && !defined(ASSIMP_BUILD_NO_GLTF1_IMPORTER) + +#include "glTFAsset.h" + +namespace glTF +{ + +using rapidjson::MemoryPoolAllocator; + +class AssetWriter +{ + template<class T> + friend void WriteLazyDict(LazyDict<T>& d, AssetWriter& w); + +private: + + void WriteBinaryData(IOStream* outfile, size_t sceneLength); + + void WriteMetadata(); + void WriteExtensionsUsed(); + + template<class T> + void WriteObjects(LazyDict<T>& d); + +public: + Document mDoc; + Asset& mAsset; + + MemoryPoolAllocator<>& mAl; + + AssetWriter(Asset& asset); + + void WriteFile(const char* path); + void WriteGLBFile(const char* path); +}; + +} + +// Include the implementation of the methods +#include "glTFAssetWriter.inl" + +#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER + +#endif // GLTFASSETWRITER_H_INC diff --git a/libs/assimp/code/AssetLib/glTF/glTFAssetWriter.inl b/libs/assimp/code/AssetLib/glTF/glTFAssetWriter.inl new file mode 100644 index 0000000..a1265fb --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFAssetWriter.inl @@ -0,0 +1,716 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#include <assimp/Base64.hpp> + +#include <rapidjson/stringbuffer.h> +#include <rapidjson/writer.h> +#include <rapidjson/prettywriter.h> + +#if _MSC_VER +# pragma warning(push) +# pragma warning( disable : 4706) +#endif // _MSC_VER + +namespace glTF { + + using rapidjson::StringBuffer; + using rapidjson::PrettyWriter; + using rapidjson::Writer; + using rapidjson::StringRef; + using rapidjson::StringRef; + + namespace { + + template<typename T, size_t N> + inline + Value& MakeValue(Value& val, T(&r)[N], MemoryPoolAllocator<>& al) { + val.SetArray(); + val.Reserve(N, al); + for (decltype(N) i = 0; i < N; ++i) { + val.PushBack(r[i], al); + } + return val; + } + + template<typename T> + inline + Value& MakeValue(Value& val, const std::vector<T> & r, MemoryPoolAllocator<>& al) { + val.SetArray(); + val.Reserve(static_cast<rapidjson::SizeType>(r.size()), al); + for (unsigned int i = 0; i < r.size(); ++i) { + val.PushBack(r[i], al); + } + return val; + } + + template<typename C, typename T> + inline Value& MakeValueCast(Value& val, const std::vector<T> & r, MemoryPoolAllocator<>& al) { + val.SetArray(); + val.Reserve(static_cast<rapidjson::SizeType>(r.size()), al); + for (unsigned int i = 0; i < r.size(); ++i) { + val.PushBack(static_cast<C>(r[i]), al); + } + return val; + } + + template<class T> + inline void AddRefsVector(Value& obj, const char* fieldId, std::vector< Ref<T> >& v, MemoryPoolAllocator<>& al) { + if (v.empty()) return; + Value lst; + lst.SetArray(); + lst.Reserve(unsigned(v.size()), al); + for (size_t i = 0; i < v.size(); ++i) { + lst.PushBack(StringRef(v[i]->id), al); + } + obj.AddMember(StringRef(fieldId), lst, al); + } + + + } + + inline void Write(Value& obj, Accessor& a, AssetWriter& w) + { + obj.AddMember("bufferView", Value(a.bufferView->id, w.mAl).Move(), w.mAl); + obj.AddMember("byteOffset", a.byteOffset, w.mAl); + obj.AddMember("byteStride", a.byteStride, w.mAl); + obj.AddMember("componentType", int(a.componentType), w.mAl); + obj.AddMember("count", a.count, w.mAl); + obj.AddMember("type", StringRef(AttribType::ToString(a.type)), w.mAl); + + Value vTmpMax, vTmpMin; + if (a.componentType == ComponentType_FLOAT) { + obj.AddMember("max", MakeValue(vTmpMax, a.max, w.mAl), w.mAl); + obj.AddMember("min", MakeValue(vTmpMin, a.min, w.mAl), w.mAl); + } else { + obj.AddMember("max", MakeValueCast<int64_t>(vTmpMax, a.max, w.mAl), w.mAl); + obj.AddMember("min", MakeValueCast<int64_t>(vTmpMin, a.min, w.mAl), w.mAl); + } + } + + inline void Write(Value& obj, Animation& a, AssetWriter& w) + { + /****************** Channels *******************/ + Value channels; + channels.SetArray(); + channels.Reserve(unsigned(a.Channels.size()), w.mAl); + + for (size_t i = 0; i < unsigned(a.Channels.size()); ++i) { + Animation::AnimChannel& c = a.Channels[i]; + Value valChannel; + valChannel.SetObject(); + { + valChannel.AddMember("sampler", c.sampler, w.mAl); + + Value valTarget; + valTarget.SetObject(); + { + valTarget.AddMember("id", StringRef(c.target.id->id), w.mAl); + valTarget.AddMember("path", c.target.path, w.mAl); + } + valChannel.AddMember("target", valTarget, w.mAl); + } + channels.PushBack(valChannel, w.mAl); + } + obj.AddMember("channels", channels, w.mAl); + + /****************** Parameters *******************/ + Value valParameters; + valParameters.SetObject(); + { + if (a.Parameters.TIME) { + valParameters.AddMember("TIME", StringRef(a.Parameters.TIME->id), w.mAl); + } + if (a.Parameters.rotation) { + valParameters.AddMember("rotation", StringRef(a.Parameters.rotation->id), w.mAl); + } + if (a.Parameters.scale) { + valParameters.AddMember("scale", StringRef(a.Parameters.scale->id), w.mAl); + } + if (a.Parameters.translation) { + valParameters.AddMember("translation", StringRef(a.Parameters.translation->id), w.mAl); + } + } + obj.AddMember("parameters", valParameters, w.mAl); + + /****************** Samplers *******************/ + Value valSamplers; + valSamplers.SetObject(); + + for (size_t i = 0; i < unsigned(a.Samplers.size()); ++i) { + Animation::AnimSampler& s = a.Samplers[i]; + Value valSampler; + valSampler.SetObject(); + { + valSampler.AddMember("input", s.input, w.mAl); + valSampler.AddMember("interpolation", s.interpolation, w.mAl); + valSampler.AddMember("output", s.output, w.mAl); + } + valSamplers.AddMember(StringRef(s.id), valSampler, w.mAl); + } + obj.AddMember("samplers", valSamplers, w.mAl); + } + + inline void Write(Value& obj, Buffer& b, AssetWriter& w) + { + const char* type; + switch (b.type) { + case Buffer::Type_text: + type = "text"; break; + default: + type = "arraybuffer"; + } + + obj.AddMember("byteLength", static_cast<uint64_t>(b.byteLength), w.mAl); + obj.AddMember("type", StringRef(type), w.mAl); + obj.AddMember("uri", Value(b.GetURI(), w.mAl).Move(), w.mAl); + } + + inline void Write(Value& obj, BufferView& bv, AssetWriter& w) + { + obj.AddMember("buffer", Value(bv.buffer->id, w.mAl).Move(), w.mAl); + obj.AddMember("byteOffset", static_cast<uint64_t>(bv.byteOffset), w.mAl); + obj.AddMember("byteLength", static_cast<uint64_t>(bv.byteLength), w.mAl); + if (bv.target != BufferViewTarget_NONE) { + obj.AddMember("target", int(bv.target), w.mAl); + } + } + + inline void Write(Value& /*obj*/, Camera& /*c*/, AssetWriter& /*w*/) + { + + } + + inline void Write(Value& obj, Image& img, AssetWriter& w) + { + std::string uri; + if (w.mAsset.extensionsUsed.KHR_binary_glTF && img.bufferView) { + Value exts, ext; + exts.SetObject(); + ext.SetObject(); + + ext.AddMember("bufferView", StringRef(img.bufferView->id), w.mAl); + + if (!img.mimeType.empty()) + ext.AddMember("mimeType", StringRef(img.mimeType), w.mAl); + + exts.AddMember("KHR_binary_glTF", ext, w.mAl); + obj.AddMember("extensions", exts, w.mAl); + return; + } + else if (img.HasData()) { + uri = "data:" + (img.mimeType.empty() ? "application/octet-stream" : img.mimeType); + uri += ";base64,"; + Base64::Encode(img.GetData(), img.GetDataLength(), uri); + } + else { + uri = img.uri; + } + + obj.AddMember("uri", Value(uri, w.mAl).Move(), w.mAl); + } + + namespace { + inline void WriteColorOrTex(Value& obj, TexProperty& prop, const char* propName, MemoryPoolAllocator<>& al) + { + if (prop.texture) + obj.AddMember(StringRef(propName), Value(prop.texture->id, al).Move(), al); + else { + Value col; + obj.AddMember(StringRef(propName), MakeValue(col, prop.color, al), al); + } + } + } + + inline void Write(Value& obj, Material& m, AssetWriter& w) + { + Value v; + v.SetObject(); + { + WriteColorOrTex(v, m.ambient, "ambient", w.mAl); + WriteColorOrTex(v, m.diffuse, "diffuse", w.mAl); + WriteColorOrTex(v, m.specular, "specular", w.mAl); + WriteColorOrTex(v, m.emission, "emission", w.mAl); + + if (m.transparent) + v.AddMember("transparency", m.transparency, w.mAl); + + v.AddMember("shininess", m.shininess, w.mAl); + } + obj.AddMember("values", v, w.mAl); + } + + namespace { + inline void WriteAttrs(AssetWriter& w, Value& attrs, Mesh::AccessorList& lst, + const char* semantic, bool forceNumber = false) + { + if (lst.empty()) return; + if (lst.size() == 1 && !forceNumber) { + attrs.AddMember(StringRef(semantic), Value(lst[0]->id, w.mAl).Move(), w.mAl); + } + else { + for (size_t i = 0; i < lst.size(); ++i) { + char buffer[32]; + ai_snprintf(buffer, 32, "%s_%d", semantic, int(i)); + attrs.AddMember(Value(buffer, w.mAl).Move(), Value(lst[i]->id, w.mAl).Move(), w.mAl); + } + } + } + } + + inline void Write(Value& obj, Mesh& m, AssetWriter& w) + { + /********************* Name **********************/ + obj.AddMember("name", m.name, w.mAl); + + /**************** Mesh extensions ****************/ + if(m.Extension.size() > 0) + { + Value json_extensions; + + json_extensions.SetObject(); +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + for(Mesh::SExtension* ptr_ext : m.Extension) + { + switch(ptr_ext->Type) + { + case Mesh::SExtension::EType::Compression_Open3DGC: + { + Value json_comp_data; + Mesh::SCompression_Open3DGC* ptr_ext_comp = (Mesh::SCompression_Open3DGC*)ptr_ext; + + // filling object "compressedData" + json_comp_data.SetObject(); + json_comp_data.AddMember("buffer", ptr_ext_comp->Buffer, w.mAl); + json_comp_data.AddMember("byteOffset", static_cast<uint64_t>(ptr_ext_comp->Offset), w.mAl); + json_comp_data.AddMember("componentType", 5121, w.mAl); + json_comp_data.AddMember("type", "SCALAR", w.mAl); + json_comp_data.AddMember("count", static_cast<uint64_t>(ptr_ext_comp->Count), w.mAl); + if(ptr_ext_comp->Binary) + json_comp_data.AddMember("mode", "binary", w.mAl); + else + json_comp_data.AddMember("mode", "ascii", w.mAl); + + json_comp_data.AddMember("indicesCount", static_cast<uint64_t>(ptr_ext_comp->IndicesCount), w.mAl); + json_comp_data.AddMember("verticesCount", static_cast<uint64_t>(ptr_ext_comp->VerticesCount), w.mAl); + // filling object "Open3DGC-compression" + Value json_o3dgc; + + json_o3dgc.SetObject(); + json_o3dgc.AddMember("compressedData", json_comp_data, w.mAl); + // add member to object "extensions" + json_extensions.AddMember("Open3DGC-compression", json_o3dgc, w.mAl); + } + + break; + default: + throw DeadlyImportError("GLTF: Can not write mesh: unknown mesh extension, only Open3DGC is supported."); + }// switch(ptr_ext->Type) + }// for(Mesh::SExtension* ptr_ext : m.Extension) +#endif + + // Add extensions to mesh + obj.AddMember("extensions", json_extensions, w.mAl); + }// if(m.Extension.size() > 0) + + /****************** Primitives *******************/ + Value primitives; + primitives.SetArray(); + primitives.Reserve(unsigned(m.primitives.size()), w.mAl); + + for (size_t i = 0; i < m.primitives.size(); ++i) { + Mesh::Primitive& p = m.primitives[i]; + Value prim; + prim.SetObject(); + { + prim.AddMember("mode", Value(int(p.mode)).Move(), w.mAl); + + if (p.material) + prim.AddMember("material", p.material->id, w.mAl); + + if (p.indices) + prim.AddMember("indices", Value(p.indices->id, w.mAl).Move(), w.mAl); + + Value attrs; + attrs.SetObject(); + { + WriteAttrs(w, attrs, p.attributes.position, "POSITION"); + WriteAttrs(w, attrs, p.attributes.normal, "NORMAL"); + WriteAttrs(w, attrs, p.attributes.texcoord, "TEXCOORD", true); + WriteAttrs(w, attrs, p.attributes.color, "COLOR"); + WriteAttrs(w, attrs, p.attributes.joint, "JOINT"); + WriteAttrs(w, attrs, p.attributes.jointmatrix, "JOINTMATRIX"); + WriteAttrs(w, attrs, p.attributes.weight, "WEIGHT"); + } + prim.AddMember("attributes", attrs, w.mAl); + } + primitives.PushBack(prim, w.mAl); + } + + obj.AddMember("primitives", primitives, w.mAl); + } + + inline void Write(Value& obj, Node& n, AssetWriter& w) + { + + if (n.matrix.isPresent) { + Value val; + obj.AddMember("matrix", MakeValue(val, n.matrix.value, w.mAl).Move(), w.mAl); + } + + if (n.translation.isPresent) { + Value val; + obj.AddMember("translation", MakeValue(val, n.translation.value, w.mAl).Move(), w.mAl); + } + + if (n.scale.isPresent) { + Value val; + obj.AddMember("scale", MakeValue(val, n.scale.value, w.mAl).Move(), w.mAl); + } + if (n.rotation.isPresent) { + Value val; + obj.AddMember("rotation", MakeValue(val, n.rotation.value, w.mAl).Move(), w.mAl); + } + + AddRefsVector(obj, "children", n.children, w.mAl); + + AddRefsVector(obj, "meshes", n.meshes, w.mAl); + + AddRefsVector(obj, "skeletons", n.skeletons, w.mAl); + + if (n.skin) { + obj.AddMember("skin", Value(n.skin->id, w.mAl).Move(), w.mAl); + } + + if (!n.jointName.empty()) { + obj.AddMember("jointName", n.jointName, w.mAl); + } + } + + inline void Write(Value& /*obj*/, Program& /*b*/, AssetWriter& /*w*/) + { + + } + + inline void Write(Value& obj, Sampler& b, AssetWriter& w) + { + if (b.wrapS) { + obj.AddMember("wrapS", b.wrapS, w.mAl); + } + if (b.wrapT) { + obj.AddMember("wrapT", b.wrapT, w.mAl); + } + if (b.magFilter) { + obj.AddMember("magFilter", b.magFilter, w.mAl); + } + if (b.minFilter) { + obj.AddMember("minFilter", b.minFilter, w.mAl); + } + } + + inline void Write(Value& scene, Scene& s, AssetWriter& w) + { + AddRefsVector(scene, "nodes", s.nodes, w.mAl); + } + + inline void Write(Value& /*obj*/, Shader& /*b*/, AssetWriter& /*w*/) + { + + } + + inline void Write(Value& obj, Skin& b, AssetWriter& w) + { + /****************** jointNames *******************/ + Value vJointNames; + vJointNames.SetArray(); + vJointNames.Reserve(unsigned(b.jointNames.size()), w.mAl); + + for (size_t i = 0; i < unsigned(b.jointNames.size()); ++i) { + vJointNames.PushBack(StringRef(b.jointNames[i]->jointName), w.mAl); + } + obj.AddMember("jointNames", vJointNames, w.mAl); + + if (b.bindShapeMatrix.isPresent) { + Value val; + obj.AddMember("bindShapeMatrix", MakeValue(val, b.bindShapeMatrix.value, w.mAl).Move(), w.mAl); + } + + if (b.inverseBindMatrices) { + obj.AddMember("inverseBindMatrices", Value(b.inverseBindMatrices->id, w.mAl).Move(), w.mAl); + } + + } + + inline void Write(Value& /*obj*/, Technique& /*b*/, AssetWriter& /*w*/) + { + + } + + inline void Write(Value& obj, Texture& tex, AssetWriter& w) + { + if (tex.source) { + obj.AddMember("source", Value(tex.source->id, w.mAl).Move(), w.mAl); + } + if (tex.sampler) { + obj.AddMember("sampler", Value(tex.sampler->id, w.mAl).Move(), w.mAl); + } + } + + inline void Write(Value& /*obj*/, Light& /*b*/, AssetWriter& /*w*/) + { + + } + + + inline AssetWriter::AssetWriter(Asset& a) + : mDoc() + , mAsset(a) + , mAl(mDoc.GetAllocator()) + { + mDoc.SetObject(); + + WriteMetadata(); + WriteExtensionsUsed(); + + // Dump the contents of the dictionaries + for (size_t i = 0; i < a.mDicts.size(); ++i) { + a.mDicts[i]->WriteObjects(*this); + } + + // Add the target scene field + if (mAsset.scene) { + mDoc.AddMember("scene", StringRef(mAsset.scene->id), mAl); + } + } + + inline void AssetWriter::WriteFile(const char* path) + { + std::unique_ptr<IOStream> jsonOutFile(mAsset.OpenFile(path, "wt", true)); + + if (jsonOutFile == 0) { + throw DeadlyExportError("Could not open output file: " + std::string(path)); + } + + StringBuffer docBuffer; + + PrettyWriter<StringBuffer> writer(docBuffer); + if (!mDoc.Accept(writer)) { + throw DeadlyExportError("Failed to write scene data!"); + } + + if (jsonOutFile->Write(docBuffer.GetString(), docBuffer.GetSize(), 1) != 1) { + throw DeadlyExportError("Failed to write scene data!"); + } + + // Write buffer data to separate .bin files + for (unsigned int i = 0; i < mAsset.buffers.Size(); ++i) { + Ref<Buffer> b = mAsset.buffers.Get(i); + + std::string binPath = b->GetURI(); + + std::unique_ptr<IOStream> binOutFile(mAsset.OpenFile(binPath, "wb", true)); + + if (binOutFile == 0) { + throw DeadlyExportError("Could not open output file: " + binPath); + } + + if (b->byteLength > 0) { + if (binOutFile->Write(b->GetPointer(), b->byteLength, 1) != 1) { + throw DeadlyExportError("Failed to write binary file: " + binPath); + } + } + } + } + + inline void AssetWriter::WriteGLBFile(const char* path) + { + std::unique_ptr<IOStream> outfile(mAsset.OpenFile(path, "wb", true)); + + if (outfile == 0) { + throw DeadlyExportError("Could not open output file: " + std::string(path)); + } + + // we will write the header later, skip its size + outfile->Seek(sizeof(GLB_Header), aiOrigin_SET); + + StringBuffer docBuffer; + Writer<StringBuffer> writer(docBuffer); + if (!mDoc.Accept(writer)) { + throw DeadlyExportError("Failed to write scene data!"); + } + + if (outfile->Write(docBuffer.GetString(), docBuffer.GetSize(), 1) != 1) { + throw DeadlyExportError("Failed to write scene data!"); + } + + WriteBinaryData(outfile.get(), docBuffer.GetSize()); + } + + inline void AssetWriter::WriteBinaryData(IOStream* outfile, size_t sceneLength) + { + // + // write the body data + // + + size_t bodyLength = 0; + if (Ref<Buffer> b = mAsset.GetBodyBuffer()) { + bodyLength = b->byteLength; + + if (bodyLength > 0) { + size_t bodyOffset = sizeof(GLB_Header) + sceneLength; + bodyOffset = (bodyOffset + 3) & ~3; // Round up to next multiple of 4 + + outfile->Seek(bodyOffset, aiOrigin_SET); + + if (outfile->Write(b->GetPointer(), b->byteLength, 1) != 1) { + throw DeadlyExportError("Failed to write body data!"); + } + } + } + + // + // write the header + // + + GLB_Header header; + memcpy(header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)); + + header.version = 1; + AI_SWAP4(header.version); + + header.length = uint32_t(sizeof(header) + sceneLength + bodyLength); + AI_SWAP4(header.length); + + header.sceneLength = uint32_t(sceneLength); + AI_SWAP4(header.sceneLength); + + header.sceneFormat = SceneFormat_JSON; + AI_SWAP4(header.sceneFormat); + + outfile->Seek(0, aiOrigin_SET); + + if (outfile->Write(&header, 1, sizeof(header)) != sizeof(header)) { + throw DeadlyExportError("Failed to write the header!"); + } + } + + + inline void AssetWriter::WriteMetadata() + { + Value asset; + asset.SetObject(); + asset.AddMember("version", Value(mAsset.asset.version, mAl).Move(), mAl); + asset.AddMember("generator", Value(mAsset.asset.generator, mAl).Move(), mAl); + if (!mAsset.asset.copyright.empty()) + asset.AddMember("copyright", Value(mAsset.asset.copyright, mAl).Move(), mAl); + + mDoc.AddMember("asset", asset, mAl); + } + + inline void AssetWriter::WriteExtensionsUsed() + { + Value exts; + exts.SetArray(); + { + if (false) + exts.PushBack(StringRef("KHR_binary_glTF"), mAl); + + if (false) + exts.PushBack(StringRef("KHR_materials_common"), mAl); + } + + if (!exts.Empty()) + mDoc.AddMember("extensionsUsed", exts, mAl); + } + + template<class T> + void AssetWriter::WriteObjects(LazyDict<T>& d) + { + if (d.mObjs.empty()) return; + + Value* container = &mDoc; + + if (d.mExtId) { + Value* exts = FindObject(mDoc, "extensions"); + if (!exts) { + mDoc.AddMember("extensions", Value().SetObject().Move(), mDoc.GetAllocator()); + exts = FindObject(mDoc, "extensions"); + } + + if (!(container = FindObject(*exts, d.mExtId))) { + exts->AddMember(StringRef(d.mExtId), Value().SetObject().Move(), mDoc.GetAllocator()); + container = FindObject(*exts, d.mExtId); + } + } + + Value* dict; + if (!(dict = FindObject(*container, d.mDictId))) { + container->AddMember(StringRef(d.mDictId), Value().SetObject().Move(), mDoc.GetAllocator()); + dict = FindObject(*container, d.mDictId); + } + + for (size_t i = 0; i < d.mObjs.size(); ++i) { + if (d.mObjs[i]->IsSpecial()) continue; + + Value obj; + obj.SetObject(); + + if (!d.mObjs[i]->name.empty()) { + obj.AddMember("name", StringRef(d.mObjs[i]->name.c_str()), mAl); + } + + Write(obj, *d.mObjs[i], *this); + + dict->AddMember(StringRef(d.mObjs[i]->id), obj, mAl); + } + } + + template<class T> + void WriteLazyDict(LazyDict<T>& d, AssetWriter& w) + { + w.WriteObjects(d); + } + +#if _MSC_VER +# pragma warning(pop) +#endif // _WIN32 + +} diff --git a/libs/assimp/code/AssetLib/glTF/glTFCommon.cpp b/libs/assimp/code/AssetLib/glTF/glTFCommon.cpp new file mode 100644 index 0000000..fea680c --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFCommon.cpp @@ -0,0 +1,117 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_GLTF_IMPORTER + +#include "AssetLib/glTF/glTFCommon.h" + +namespace glTFCommon { + +using namespace glTFCommon::Util; + +namespace Util { + +bool ParseDataURI(const char *const_uri, size_t uriLen, DataURI &out) { + if (nullptr == const_uri) { + return false; + } + + if (const_uri[0] != 0x10) { // we already parsed this uri? + if (strncmp(const_uri, "data:", 5) != 0) // not a data uri? + return false; + } + + // set defaults + out.mediaType = "text/plain"; + out.charset = "US-ASCII"; + out.base64 = false; + + char *uri = const_cast<char *>(const_uri); + if (uri[0] != 0x10) { + uri[0] = 0x10; + uri[1] = uri[2] = uri[3] = uri[4] = 0; + + size_t i = 5, j; + if (uri[i] != ';' && uri[i] != ',') { // has media type? + uri[1] = char(i); + for (;i < uriLen && uri[i] != ';' && uri[i] != ','; ++i) { + // nothing to do! + } + } + while (i < uriLen && uri[i] == ';') { + uri[i++] = '\0'; + for (j = i; i < uriLen && uri[i] != ';' && uri[i] != ','; ++i) { + // nothing to do! + } + + if (strncmp(uri + j, "charset=", 8) == 0) { + uri[2] = char(j + 8); + } else if (strncmp(uri + j, "base64", 6) == 0) { + uri[3] = char(j); + } + } + if (i < uriLen) { + uri[i++] = '\0'; + uri[4] = char(i); + } else { + uri[1] = uri[2] = uri[3] = 0; + uri[4] = 5; + } + } + + if (uri[1] != 0) { + out.mediaType = uri + uri[1]; + } + if (uri[2] != 0) { + out.charset = uri + uri[2]; + } + if (uri[3] != 0) { + out.base64 = true; + } + out.data = uri + uri[4]; + out.dataLength = (uri + uriLen) - out.data; + + return true; +} + +} // namespace Util +} // namespace glTFCommon + +#endif diff --git a/libs/assimp/code/AssetLib/glTF/glTFCommon.h b/libs/assimp/code/AssetLib/glTF/glTFCommon.h new file mode 100644 index 0000000..edc3c7e --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFCommon.h @@ -0,0 +1,520 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef AI_GLFTCOMMON_H_INC +#define AI_GLFTCOMMON_H_INC + +#ifndef ASSIMP_BUILD_NO_GLTF_IMPORTER + +#include <assimp/Exceptional.h> + +#include <algorithm> +#include <list> +#include <map> +#include <stdexcept> +#include <string> +#include <vector> + +#include <rapidjson/document.h> +#include <rapidjson/error/en.h> +#include <rapidjson/rapidjson.h> + +// clang-format off + +#ifdef ASSIMP_API +# include <assimp/ByteSwapper.h> +# include <assimp/DefaultIOSystem.h> +# include <memory> +#else +# include <memory> +# define AI_SWAP4(p) +# define ai_assert +#endif + +#if _MSC_VER > 1500 || (defined __GNUC___) +# define ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +#else +# define gltf_unordered_map map +#endif + +#ifdef ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +# include <unordered_map> +# if defined(_MSC_VER) && _MSC_VER <= 1600 +# define gltf_unordered_map tr1::unordered_map +# else +# define gltf_unordered_map unordered_map +# endif +#endif +// clang-format on + + +namespace glTFCommon { + +using rapidjson::Document; +using rapidjson::Value; + +#ifdef ASSIMP_API +using Assimp::IOStream; +using Assimp::IOSystem; +using std::shared_ptr; +#else +using std::shared_ptr; + +typedef std::runtime_error DeadlyImportError; +typedef std::runtime_error DeadlyExportError; + +enum aiOrigin { + aiOrigin_SET = 0, + aiOrigin_CUR = 1, + aiOrigin_END = 2 +}; + +class IOSystem; + +class IOStream { +public: + IOStream(FILE *file) : + f(file) {} + ~IOStream() { + fclose(f); + } + + size_t Read(void *b, size_t sz, size_t n) { return fread(b, sz, n, f); } + size_t Write(const void *b, size_t sz, size_t n) { return fwrite(b, sz, n, f); } + int Seek(size_t off, aiOrigin orig) { return fseek(f, off, int(orig)); } + size_t Tell() const { return ftell(f); } + + size_t FileSize() { + long p = Tell(), len = (Seek(0, aiOrigin_END), Tell()); + return size_t((Seek(p, aiOrigin_SET), len)); + } + +private: + FILE *f; +}; +#endif + +// Vec/matrix types, as raw float arrays +typedef float(vec3)[3]; +typedef float(vec4)[4]; +typedef float(mat4)[16]; + +inline void CopyValue(const glTFCommon::vec3 &v, aiColor4D &out) { + out.r = v[0]; + out.g = v[1]; + out.b = v[2]; + out.a = 1.0; +} + +inline void CopyValue(const glTFCommon::vec4 &v, aiColor4D &out) { + out.r = v[0]; + out.g = v[1]; + out.b = v[2]; + out.a = v[3]; +} + +inline void CopyValue(const glTFCommon::vec4 &v, aiColor3D &out) { + out.r = v[0]; + out.g = v[1]; + out.b = v[2]; +} + +inline void CopyValue(const glTFCommon::vec3 &v, aiColor3D &out) { + out.r = v[0]; + out.g = v[1]; + out.b = v[2]; +} + +inline void CopyValue(const glTFCommon::vec3 &v, aiVector3D &out) { + out.x = v[0]; + out.y = v[1]; + out.z = v[2]; +} + +inline void CopyValue(const glTFCommon::vec4 &v, aiQuaternion &out) { + out.x = v[0]; + out.y = v[1]; + out.z = v[2]; + out.w = v[3]; +} + +inline void CopyValue(const glTFCommon::mat4 &v, aiMatrix4x4 &o) { + o.a1 = v[0]; + o.b1 = v[1]; + o.c1 = v[2]; + o.d1 = v[3]; + o.a2 = v[4]; + o.b2 = v[5]; + o.c2 = v[6]; + o.d2 = v[7]; + o.a3 = v[8]; + o.b3 = v[9]; + o.c3 = v[10]; + o.d3 = v[11]; + o.a4 = v[12]; + o.b4 = v[13]; + o.c4 = v[14]; + o.d4 = v[15]; +} + +#if _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4310) +#endif // _MSC_VER + +inline std::string getCurrentAssetDir(const std::string &pFile) { + int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\'))); + if (pos == int(std::string::npos)) { + return std::string(); + } + + return pFile.substr(0, pos + 1); +} +#if _MSC_VER +# pragma warning(pop) +#endif // _MSC_VER + +namespace Util { + +void EncodeBase64(const uint8_t *in, size_t inLength, std::string &out); + +size_t DecodeBase64(const char *in, size_t inLength, uint8_t *&out); + +inline size_t DecodeBase64(const char *in, uint8_t *&out) { + return DecodeBase64(in, strlen(in), out); +} + +struct DataURI { + const char *mediaType; + const char *charset; + bool base64; + const char *data; + size_t dataLength; +}; + +//! Check if a uri is a data URI +bool ParseDataURI(const char *const_uri, size_t uriLen, DataURI &out); + +} // namespace Util + +#define CHECK_EXT(EXT) \ + if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true; + +//! Helper struct to represent values that might not be present +template <class T> +struct Nullable { + T value; + bool isPresent; + + Nullable() : + isPresent(false) {} + Nullable(T &val) : + value(val), + isPresent(true) {} +}; + +//! A reference to one top-level object, which is valid +//! until the Asset instance is destroyed +template <class T> +class Ref { + std::vector<T *> *vector; + unsigned int index; + +public: + Ref() : + vector(0), + index(0) {} + Ref(std::vector<T *> &vec, unsigned int idx) : + vector(&vec), + index(idx) {} + + inline unsigned int GetIndex() const { return index; } + + operator bool() const { return vector != nullptr && index < vector->size(); } + + T *operator->() { return (*vector)[index]; } + + T &operator*() { return *((*vector)[index]); } +}; + +// +// JSON Value reading helpers +// + +template <class T> +struct ReadHelper { + static bool Read(Value &val, T &out) { + return val.IsInt() ? out = static_cast<T>(val.GetInt()), true : false; + } +}; + +template <> +struct ReadHelper<bool> { + static bool Read(Value &val, bool &out) { + return val.IsBool() ? out = val.GetBool(), true : false; + } +}; + +template <> +struct ReadHelper<float> { + static bool Read(Value &val, float &out) { + return val.IsNumber() ? out = static_cast<float>(val.GetDouble()), true : false; + } +}; + +template <unsigned int N> +struct ReadHelper<float[N]> { + static bool Read(Value &val, float (&out)[N]) { + if (!val.IsArray() || val.Size() != N) return false; + for (unsigned int i = 0; i < N; ++i) { + if (val[i].IsNumber()) + out[i] = static_cast<float>(val[i].GetDouble()); + } + return true; + } +}; + +template <> +struct ReadHelper<const char *> { + static bool Read(Value &val, const char *&out) { + return val.IsString() ? (out = val.GetString(), true) : false; + } +}; + +template <> +struct ReadHelper<std::string> { + static bool Read(Value &val, std::string &out) { + return val.IsString() ? (out = std::string(val.GetString(), val.GetStringLength()), true) : false; + } +}; + +template <class T> +struct ReadHelper<Nullable<T>> { + static bool Read(Value &val, Nullable<T> &out) { + return out.isPresent = ReadHelper<T>::Read(val, out.value); + } +}; + +template <> +struct ReadHelper<uint64_t> { + static bool Read(Value &val, uint64_t &out) { + return val.IsUint64() ? out = val.GetUint64(), true : false; + } +}; + +template <> +struct ReadHelper<int64_t> { + static bool Read(Value &val, int64_t &out) { + return val.IsInt64() ? out = val.GetInt64(), true : false; + } +}; + +template <class T> +inline static bool ReadValue(Value &val, T &out) { + return ReadHelper<T>::Read(val, out); +} + +template <class T> +inline static bool ReadMember(Value &obj, const char *id, T &out) { + if (!obj.IsObject()) { + return false; + } + Value::MemberIterator it = obj.FindMember(id); + if (it != obj.MemberEnd()) { + return ReadHelper<T>::Read(it->value, out); + } + return false; +} + +template <class T> +inline static T MemberOrDefault(Value &obj, const char *id, T defaultValue) { + T out; + return ReadMember(obj, id, out) ? out : defaultValue; +} + +inline Value *FindMember(Value &val, const char *id) { + if (!val.IsObject()) { + return nullptr; + } + Value::MemberIterator it = val.FindMember(id); + return (it != val.MemberEnd()) ? &it->value : nullptr; +} + +template <int N> +inline void throwUnexpectedTypeError(const char (&expectedTypeName)[N], const char *memberId, const char *context, const char *extraContext) { + std::string fullContext = context; + if (extraContext && (strlen(extraContext) > 0)) { + fullContext = fullContext + " (" + extraContext + ")"; + } + throw DeadlyImportError("Member \"", memberId, "\" was not of type \"", expectedTypeName, "\" when reading ", fullContext); +} + +// Look-up functions with type checks. Context and extra context help the user identify the problem if there's an error. + +inline Value *FindStringInContext(Value &val, const char *memberId, const char *context, const char *extraContext = nullptr) { + if (!val.IsObject()) { + return nullptr; + } + Value::MemberIterator it = val.FindMember(memberId); + if (it == val.MemberEnd()) { + return nullptr; + } + if (!it->value.IsString()) { + throwUnexpectedTypeError("string", memberId, context, extraContext); + } + return &it->value; +} + +inline Value *FindNumberInContext(Value &val, const char *memberId, const char *context, const char *extraContext = nullptr) { + if (!val.IsObject()) { + return nullptr; + } + Value::MemberIterator it = val.FindMember(memberId); + if (it == val.MemberEnd()) { + return nullptr; + } + if (!it->value.IsNumber()) { + throwUnexpectedTypeError("number", memberId, context, extraContext); + } + return &it->value; +} + +inline Value *FindUIntInContext(Value &val, const char *memberId, const char *context, const char *extraContext = nullptr) { + if (!val.IsObject()) { + return nullptr; + } + Value::MemberIterator it = val.FindMember(memberId); + if (it == val.MemberEnd()) { + return nullptr; + } + if (!it->value.IsUint()) { + throwUnexpectedTypeError("uint", memberId, context, extraContext); + } + return &it->value; +} + +inline Value *FindArrayInContext(Value &val, const char *memberId, const char *context, const char *extraContext = nullptr) { + if (!val.IsObject()) { + return nullptr; + } + Value::MemberIterator it = val.FindMember(memberId); + if (it == val.MemberEnd()) { + return nullptr; + } + if (!it->value.IsArray()) { + throwUnexpectedTypeError("array", memberId, context, extraContext); + } + return &it->value; +} + +inline Value *FindObjectInContext(Value &val, const char *memberId, const char *context, const char *extraContext = nullptr) { + if (!val.IsObject()) { + return nullptr; + } + Value::MemberIterator it = val.FindMember(memberId); + if (it == val.MemberEnd()) { + return nullptr; + } + if (!it->value.IsObject()) { + throwUnexpectedTypeError("object", memberId, context, extraContext); + } + return &it->value; +} + +inline Value *FindExtensionInContext(Value &val, const char *extensionId, const char *context, const char *extraContext = nullptr) { + if (Value *extensionList = FindObjectInContext(val, "extensions", context, extraContext)) { + if (Value *extension = FindObjectInContext(*extensionList, extensionId, context, extraContext)) { + return extension; + } + } + return nullptr; +} + +// Overloads when the value is the document. + +inline Value *FindString(Document &doc, const char *memberId) { + return FindStringInContext(doc, memberId, "the document"); +} + +inline Value *FindNumber(Document &doc, const char *memberId) { + return FindNumberInContext(doc, memberId, "the document"); +} + +inline Value *FindUInt(Document &doc, const char *memberId) { + return FindUIntInContext(doc, memberId, "the document"); +} + +inline Value *FindArray(Document &val, const char *memberId) { + return FindArrayInContext(val, memberId, "the document"); +} + +inline Value *FindObject(Document &doc, const char *memberId) { + return FindObjectInContext(doc, memberId, "the document"); +} + +inline Value *FindExtension(Value &val, const char *extensionId) { + return FindExtensionInContext(val, extensionId, "the document"); +} + +inline Value *FindString(Value &val, const char *id) { + Value::MemberIterator it = val.FindMember(id); + return (it != val.MemberEnd() && it->value.IsString()) ? &it->value : 0; +} + +inline Value *FindObject(Value &val, const char *id) { + Value::MemberIterator it = val.FindMember(id); + return (it != val.MemberEnd() && it->value.IsObject()) ? &it->value : 0; +} + +inline Value *FindArray(Value &val, const char *id) { + Value::MemberIterator it = val.FindMember(id); + return (it != val.MemberEnd() && it->value.IsArray()) ? &it->value : 0; +} + +inline Value *FindNumber(Value &val, const char *id) { + Value::MemberIterator it = val.FindMember(id); + return (it != val.MemberEnd() && it->value.IsNumber()) ? &it->value : 0; +} + +} // namespace glTFCommon + +#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER + +#endif // AI_GLFTCOMMON_H_INC diff --git a/libs/assimp/code/AssetLib/glTF/glTFExporter.cpp b/libs/assimp/code/AssetLib/glTF/glTFExporter.cpp new file mode 100644 index 0000000..afcfb12 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFExporter.cpp @@ -0,0 +1,1065 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER + +#include "AssetLib/glTF/glTFExporter.h" +#include "AssetLib/glTF/glTFAssetWriter.h" +#include "PostProcessing/SplitLargeMeshes.h" + +#include <assimp/commonMetaData.h> +#include <assimp/Exceptional.h> +#include <assimp/StringComparison.h> +#include <assimp/ByteSwapper.h> +#include <assimp/SceneCombiner.h> +#include <assimp/version.h> +#include <assimp/IOSystem.hpp> +#include <assimp/Exporter.hpp> +#include <assimp/material.h> +#include <assimp/scene.h> + +// Header files, standard library. +#include <memory> +#include <limits> +#include <inttypes.h> + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + // Header files, Open3DGC. +# include <Open3DGC/o3dgcSC3DMCEncoder.h> +#endif + +using namespace rapidjson; + +using namespace Assimp; +using namespace glTF; + +namespace Assimp { + + // ------------------------------------------------------------------------------------------------ + // Worker function for exporting a scene to GLTF. Prototyped and registered in Exporter.cpp + void ExportSceneGLTF(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties) + { + // invoke the exporter + glTFExporter exporter(pFile, pIOSystem, pScene, pProperties, false); + } + + // ------------------------------------------------------------------------------------------------ + // Worker function for exporting a scene to GLB. Prototyped and registered in Exporter.cpp + void ExportSceneGLB(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties) + { + // invoke the exporter + glTFExporter exporter(pFile, pIOSystem, pScene, pProperties, true); + } + +} // end of namespace Assimp + +glTFExporter::glTFExporter(const char* filename, IOSystem* pIOSystem, const aiScene* pScene, + const ExportProperties* pProperties, bool isBinary) + : mFilename(filename) + , mIOSystem(pIOSystem) + , mProperties(pProperties) +{ + aiScene* sceneCopy_tmp; + SceneCombiner::CopyScene(&sceneCopy_tmp, pScene); + + SplitLargeMeshesProcess_Triangle tri_splitter; + tri_splitter.SetLimit(0xffff); + tri_splitter.Execute(sceneCopy_tmp); + + SplitLargeMeshesProcess_Vertex vert_splitter; + vert_splitter.SetLimit(0xffff); + vert_splitter.Execute(sceneCopy_tmp); + + mScene.reset(sceneCopy_tmp); + + mAsset.reset( new glTF::Asset( pIOSystem ) ); + + if (isBinary) { + mAsset->SetAsBinary(); + } + + ExportMetadata(); + + //for (unsigned int i = 0; i < pScene->mNumCameras; ++i) {} + + //for (unsigned int i = 0; i < pScene->mNumLights; ++i) {} + + ExportMaterials(); + + if (mScene->mRootNode) { + ExportNodeHierarchy(mScene->mRootNode); + } + + ExportMeshes(); + + //for (unsigned int i = 0; i < pScene->mNumTextures; ++i) {} + + ExportScene(); + + ExportAnimations(); + + glTF::AssetWriter writer(*mAsset); + + if (isBinary) { + writer.WriteGLBFile(filename); + } else { + writer.WriteFile(filename); + } +} + +/* + * Copy a 4x4 matrix from struct aiMatrix to typedef mat4. + * Also converts from row-major to column-major storage. + */ +static void CopyValue(const aiMatrix4x4& v, glTF::mat4& o) +{ + o[ 0] = v.a1; o[ 1] = v.b1; o[ 2] = v.c1; o[ 3] = v.d1; + o[ 4] = v.a2; o[ 5] = v.b2; o[ 6] = v.c2; o[ 7] = v.d2; + o[ 8] = v.a3; o[ 9] = v.b3; o[10] = v.c3; o[11] = v.d3; + o[12] = v.a4; o[13] = v.b4; o[14] = v.c4; o[15] = v.d4; +} + +static void CopyValue(const aiMatrix4x4& v, aiMatrix4x4& o) +{ + memcpy(&o, &v, sizeof(aiMatrix4x4)); +} + +static void IdentityMatrix4(glTF::mat4& o) +{ + o[ 0] = 1; o[ 1] = 0; o[ 2] = 0; o[ 3] = 0; + o[ 4] = 0; o[ 5] = 1; o[ 6] = 0; o[ 7] = 0; + o[ 8] = 0; o[ 9] = 0; o[10] = 1; o[11] = 0; + o[12] = 0; o[13] = 0; o[14] = 0; o[15] = 1; +} + +template<typename T> +void SetAccessorRange(Ref<Accessor> acc, void* data, unsigned int count, + unsigned int numCompsIn, unsigned int numCompsOut) +{ + ai_assert(numCompsOut <= numCompsIn); + + // Allocate and initialize with large values. + for (unsigned int i = 0 ; i < numCompsOut ; i++) { + acc->min.push_back( std::numeric_limits<double>::max()); + acc->max.push_back(-std::numeric_limits<double>::max()); + } + + size_t totalComps = count * numCompsIn; + T* buffer_ptr = static_cast<T*>(data); + T* buffer_end = buffer_ptr + totalComps; + + // Search and set extreme values. + for (; buffer_ptr < buffer_end ; buffer_ptr += numCompsIn) { + for (unsigned int j = 0 ; j < numCompsOut ; j++) { + double valueTmp = buffer_ptr[j]; + + if (valueTmp < acc->min[j]) { + acc->min[j] = valueTmp; + } + if (valueTmp > acc->max[j]) { + acc->max[j] = valueTmp; + } + } + } +} + +inline void SetAccessorRange(ComponentType compType, Ref<Accessor> acc, void* data, + unsigned int count, unsigned int numCompsIn, unsigned int numCompsOut) +{ + switch (compType) { + case ComponentType_SHORT: + SetAccessorRange<short>(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_UNSIGNED_SHORT: + SetAccessorRange<unsigned short>(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_UNSIGNED_INT: + SetAccessorRange<unsigned int>(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_FLOAT: + SetAccessorRange<float>(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_BYTE: + SetAccessorRange<int8_t>(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_UNSIGNED_BYTE: + SetAccessorRange<uint8_t>(acc, data, count, numCompsIn, numCompsOut); + return; + } +} + +inline Ref<Accessor> ExportData(Asset &a, std::string &meshName, Ref<Buffer> &buffer, + unsigned int count, void *data, AttribType::Value typeIn, AttribType::Value typeOut, ComponentType compType, BufferViewTarget target = BufferViewTarget_NONE) { + if (!count || !data) return Ref<Accessor>(); + + unsigned int numCompsIn = AttribType::GetNumComponents(typeIn); + unsigned int numCompsOut = AttribType::GetNumComponents(typeOut); + unsigned int bytesPerComp = ComponentTypeSize(compType); + + size_t offset = buffer->byteLength; + // make sure offset is correctly byte-aligned, as required by spec + size_t padding = offset % bytesPerComp; + offset += padding; + size_t length = count * numCompsOut * bytesPerComp; + buffer->Grow(length + padding); + + // bufferView + Ref<BufferView> bv = a.bufferViews.Create(a.FindUniqueID(meshName, "view")); + bv->buffer = buffer; + bv->byteOffset = unsigned(offset); + bv->byteLength = length; //! The target that the WebGL buffer should be bound to. + bv->target = target; + + // accessor + Ref<Accessor> acc = a.accessors.Create(a.FindUniqueID(meshName, "accessor")); + acc->bufferView = bv; + acc->byteOffset = 0; + acc->byteStride = 0; + acc->componentType = compType; + acc->count = count; + acc->type = typeOut; + + // calculate min and max values + SetAccessorRange(compType, acc, data, count, numCompsIn, numCompsOut); + + // copy the data + acc->WriteData(count, data, numCompsIn*bytesPerComp); + + return acc; +} + +namespace { + void GetMatScalar(const aiMaterial* mat, float& val, const char* propName, int type, int idx) { + ai_assert( nullptr != mat ); + if ( nullptr != mat ) { + mat->Get(propName, type, idx, val); + } + } +} + +void glTFExporter::GetTexSampler(const aiMaterial* mat, glTF::TexProperty& prop) +{ + std::string samplerId = mAsset->FindUniqueID("", "sampler"); + prop.texture->sampler = mAsset->samplers.Create(samplerId); + + aiTextureMapMode mapU, mapV; + aiGetMaterialInteger(mat,AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0),(int*)&mapU); + aiGetMaterialInteger(mat,AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0),(int*)&mapV); + + switch (mapU) { + case aiTextureMapMode_Wrap: + prop.texture->sampler->wrapS = SamplerWrap_Repeat; + break; + case aiTextureMapMode_Clamp: + prop.texture->sampler->wrapS = SamplerWrap_Clamp_To_Edge; + break; + case aiTextureMapMode_Mirror: + prop.texture->sampler->wrapS = SamplerWrap_Mirrored_Repeat; + break; + case aiTextureMapMode_Decal: + default: + prop.texture->sampler->wrapS = SamplerWrap_Repeat; + break; + }; + + switch (mapV) { + case aiTextureMapMode_Wrap: + prop.texture->sampler->wrapT = SamplerWrap_Repeat; + break; + case aiTextureMapMode_Clamp: + prop.texture->sampler->wrapT = SamplerWrap_Clamp_To_Edge; + break; + case aiTextureMapMode_Mirror: + prop.texture->sampler->wrapT = SamplerWrap_Mirrored_Repeat; + break; + case aiTextureMapMode_Decal: + default: + prop.texture->sampler->wrapT = SamplerWrap_Repeat; + break; + }; + + // Hard coded Texture filtering options because I do not know where to find them in the aiMaterial. + prop.texture->sampler->magFilter = SamplerMagFilter_Linear; + prop.texture->sampler->minFilter = SamplerMinFilter_Linear; +} + +void glTFExporter::GetMatColorOrTex(const aiMaterial* mat, glTF::TexProperty& prop, + const char* propName, int type, int idx, aiTextureType tt) { + aiString tex; + aiColor4D col; + if (mat->GetTextureCount(tt) > 0) { + if (mat->Get(AI_MATKEY_TEXTURE(tt, 0), tex) == AI_SUCCESS) { + std::string path = tex.C_Str(); + + if (path.size() > 0) { + if (path[0] != '*') { + std::map<std::string, unsigned int>::iterator it = mTexturesByPath.find(path); + if (it != mTexturesByPath.end()) { + prop.texture = mAsset->textures.Get(it->second); + } + } + + if (!prop.texture) { + std::string texId = mAsset->FindUniqueID("", "texture"); + prop.texture = mAsset->textures.Create(texId); + mTexturesByPath[path] = prop.texture.GetIndex(); + + std::string imgId = mAsset->FindUniqueID("", "image"); + prop.texture->source = mAsset->images.Create(imgId); + + if (path[0] == '*') { // embedded + aiTexture* curTex = mScene->mTextures[atoi(&path[1])]; + + prop.texture->source->name = curTex->mFilename.C_Str(); + + uint8_t *data = reinterpret_cast<uint8_t *>(curTex->pcData); + prop.texture->source->SetData(data, curTex->mWidth, *mAsset); + + if (curTex->achFormatHint[0]) { + std::string mimeType = "image/"; + mimeType += (memcmp(curTex->achFormatHint, "jpg", 3) == 0) ? "jpeg" : curTex->achFormatHint; + prop.texture->source->mimeType = mimeType; + } + } else { + prop.texture->source->uri = path; + } + + GetTexSampler(mat, prop); + } + } + } + } + + if (mat->Get(propName, type, idx, col) == AI_SUCCESS) { + prop.color[0] = col.r; + prop.color[1] = col.g; + prop.color[2] = col.b; + prop.color[3] = col.a; + } +} + + +void glTFExporter::ExportMaterials() +{ + aiString aiName; + for (unsigned int i = 0; i < mScene->mNumMaterials; ++i) { + const aiMaterial* mat = mScene->mMaterials[i]; + + + std::string name; + if (mat->Get(AI_MATKEY_NAME, aiName) == AI_SUCCESS) { + name = aiName.C_Str(); + } + name = mAsset->FindUniqueID(name, "material"); + + Ref<Material> m = mAsset->materials.Create(name); + + GetMatColorOrTex(mat, m->ambient, AI_MATKEY_COLOR_AMBIENT, aiTextureType_AMBIENT); + GetMatColorOrTex(mat, m->diffuse, AI_MATKEY_COLOR_DIFFUSE, aiTextureType_DIFFUSE); + GetMatColorOrTex(mat, m->specular, AI_MATKEY_COLOR_SPECULAR, aiTextureType_SPECULAR); + GetMatColorOrTex(mat, m->emission, AI_MATKEY_COLOR_EMISSIVE, aiTextureType_EMISSIVE); + + m->transparent = mat->Get(AI_MATKEY_OPACITY, m->transparency) == aiReturn_SUCCESS && m->transparency != 1.0; + + GetMatScalar(mat, m->shininess, AI_MATKEY_SHININESS); + } +} + +/* + * Search through node hierarchy and find the node containing the given meshID. + * Returns true on success, and false otherwise. + */ +bool FindMeshNode(Ref<Node> &nodeIn, Ref<Node> &meshNode, const std::string &meshID) { + for (unsigned int i = 0; i < nodeIn->meshes.size(); ++i) { + if (meshID.compare(nodeIn->meshes[i]->id) == 0) { + meshNode = nodeIn; + return true; + } + } + + for (unsigned int i = 0; i < nodeIn->children.size(); ++i) { + if(FindMeshNode(nodeIn->children[i], meshNode, meshID)) { + return true; + } + } + + return false; +} + +/* + * Find the root joint of the skeleton. + * Starts will any joint node and traces up the tree, + * until a parent is found that does not have a jointName. + * Returns the first parent Ref<Node> found that does not have a jointName. + */ +Ref<Node> FindSkeletonRootJoint(Ref<Skin>& skinRef) +{ + Ref<Node> startNodeRef; + Ref<Node> parentNodeRef; + + // Arbitrarily use the first joint to start the search. + startNodeRef = skinRef->jointNames[0]; + parentNodeRef = skinRef->jointNames[0]; + + do { + startNodeRef = parentNodeRef; + parentNodeRef = startNodeRef->parent; + } while (!parentNodeRef->jointName.empty()); + + return parentNodeRef; +} + +void ExportSkin(Asset& mAsset, const aiMesh* aimesh, Ref<Mesh>& meshRef, Ref<Buffer>& bufferRef, Ref<Skin>& skinRef, std::vector<aiMatrix4x4>& inverseBindMatricesData) +{ + if (aimesh->mNumBones < 1) { + return; + } + + // Store the vertex joint and weight data. + const size_t NumVerts( aimesh->mNumVertices ); + vec4* vertexJointData = new vec4[ NumVerts ]; + vec4* vertexWeightData = new vec4[ NumVerts ]; + int* jointsPerVertex = new int[ NumVerts ]; + for (size_t i = 0; i < NumVerts; ++i) { + jointsPerVertex[i] = 0; + for (size_t j = 0; j < 4; ++j) { + vertexJointData[i][j] = 0; + vertexWeightData[i][j] = 0; + } + } + + for (unsigned int idx_bone = 0; idx_bone < aimesh->mNumBones; ++idx_bone) { + const aiBone* aib = aimesh->mBones[idx_bone]; + + // aib->mName =====> skinRef->jointNames + // Find the node with id = mName. + Ref<Node> nodeRef = mAsset.nodes.Get(aib->mName.C_Str()); + nodeRef->jointName = nodeRef->id; + + unsigned int jointNamesIndex = 0; + bool addJointToJointNames = true; + for ( unsigned int idx_joint = 0; idx_joint < skinRef->jointNames.size(); ++idx_joint) { + if (skinRef->jointNames[idx_joint]->jointName.compare(nodeRef->jointName) == 0) { + addJointToJointNames = false; + jointNamesIndex = idx_joint; + } + } + + if (addJointToJointNames) { + skinRef->jointNames.push_back(nodeRef); + + // aib->mOffsetMatrix =====> skinRef->inverseBindMatrices + aiMatrix4x4 tmpMatrix4; + CopyValue(aib->mOffsetMatrix, tmpMatrix4); + inverseBindMatricesData.push_back(tmpMatrix4); + jointNamesIndex = static_cast<unsigned int>(inverseBindMatricesData.size() - 1); + } + + // aib->mWeights =====> vertexWeightData + for (unsigned int idx_weights = 0; idx_weights < aib->mNumWeights; ++idx_weights) { + unsigned int vertexId = aib->mWeights[idx_weights].mVertexId; + float vertWeight = aib->mWeights[idx_weights].mWeight; + + // A vertex can only have at most four joint weights. Ignore all others. + if (jointsPerVertex[vertexId] > 3) { + continue; + } + + vertexJointData[vertexId][jointsPerVertex[vertexId]] = static_cast<float>(jointNamesIndex); + vertexWeightData[vertexId][jointsPerVertex[vertexId]] = vertWeight; + + jointsPerVertex[vertexId] += 1; + } + + } // End: for-loop mNumMeshes + + Mesh::Primitive& p = meshRef->primitives.back(); + Ref<Accessor> vertexJointAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices, vertexJointData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); + if ( vertexJointAccessor ) { + p.attributes.joint.push_back( vertexJointAccessor ); + } + + Ref<Accessor> vertexWeightAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices, vertexWeightData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); + if ( vertexWeightAccessor ) { + p.attributes.weight.push_back( vertexWeightAccessor ); + } + delete[] jointsPerVertex; + delete[] vertexWeightData; + delete[] vertexJointData; +} + +#if defined(__has_warning) +#if __has_warning("-Wunused-but-set-variable") +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif +#endif + +void glTFExporter::ExportMeshes() +{ + // Not for + // using IndicesType = decltype(aiFace::mNumIndices); + // But yes for + // using IndicesType = unsigned short; + // because "ComponentType_UNSIGNED_SHORT" used for indices. And it's a maximal type according to glTF specification. + typedef unsigned short IndicesType; + + // Variables needed for compression. BEGIN. + // Indices, not pointers - because pointer to buffer is changing while writing to it. +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + size_t idx_srcdata_begin = 0; // Index of buffer before writing mesh data. Also, index of begin of coordinates array in buffer. + size_t idx_srcdata_normal = SIZE_MAX;// Index of begin of normals array in buffer. SIZE_MAX - mean that mesh has no normals. + size_t idx_srcdata_ind;// Index of begin of coordinates indices array in buffer. +#endif + std::vector<size_t> idx_srcdata_tc;// Array of indices. Every index point to begin of texture coordinates array in buffer. + bool comp_allow;// Point that data of current mesh can be compressed. + // Variables needed for compression. END. + + std::string fname = std::string(mFilename); + std::string bufferIdPrefix = fname.substr(0, fname.rfind(".gltf")); + std::string bufferId = mAsset->FindUniqueID("", bufferIdPrefix.c_str()); + + Ref<Buffer> b = mAsset->GetBodyBuffer(); + if (!b) { + b = mAsset->buffers.Create(bufferId); + } + + //---------------------------------------- + // Initialize variables for the skin + bool createSkin = false; + for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) { + const aiMesh* aim = mScene->mMeshes[idx_mesh]; + if(aim->HasBones()) { + createSkin = true; + break; + } + } + + Ref<Skin> skinRef; + std::string skinName = mAsset->FindUniqueID("skin", "skin"); + std::vector<aiMatrix4x4> inverseBindMatricesData; + if(createSkin) { + skinRef = mAsset->skins.Create(skinName); + skinRef->name = skinName; + } + //---------------------------------------- + + for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) { + const aiMesh* aim = mScene->mMeshes[idx_mesh]; + + // Check if compressing requested and mesh can be encoded. +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + comp_allow = mProperties->GetPropertyBool("extensions.Open3DGC.use", false); +#else + comp_allow = false; +#endif + + if(comp_allow && (aim->mPrimitiveTypes == aiPrimitiveType_TRIANGLE) && (aim->mNumVertices > 0) && (aim->mNumFaces > 0)) + { + idx_srcdata_tc.clear(); + idx_srcdata_tc.reserve(AI_MAX_NUMBER_OF_TEXTURECOORDS); + } + else + { + std::string msg; + + if(aim->mPrimitiveTypes != aiPrimitiveType_TRIANGLE) + msg = "all primitives of the mesh must be a triangles."; + else + msg = "mesh must has vertices and faces."; + + ASSIMP_LOG_WARN("GLTF: can not use Open3DGC-compression: ", msg); + comp_allow = false; + } + + std::string meshId = mAsset->FindUniqueID(aim->mName.C_Str(), "mesh"); + Ref<Mesh> m = mAsset->meshes.Create(meshId); + m->primitives.resize(1); + Mesh::Primitive& p = m->primitives.back(); + + p.material = mAsset->materials.Get(aim->mMaterialIndex); + + /******************* Vertices ********************/ + // If compression is used then you need parameters of uncompressed region: begin and size. At this step "begin" is stored. +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + if(comp_allow) idx_srcdata_begin = b->byteLength; +#endif + + Ref<Accessor> v = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mVertices, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + if (v) p.attributes.position.push_back(v); + + /******************** Normals ********************/ +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + if(comp_allow && (aim->mNormals != 0)) idx_srcdata_normal = b->byteLength;// Store index of normals array. +#endif + + Ref<Accessor> n = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mNormals, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + if (n) p.attributes.normal.push_back(n); + + /************** Texture coordinates **************/ + for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + // Flip UV y coords + if (aim -> mNumUVComponents[i] > 1) { + for (unsigned int j = 0; j < aim->mNumVertices; ++j) { + aim->mTextureCoords[i][j].y = 1 - aim->mTextureCoords[i][j].y; + } + } + + if (aim->mNumUVComponents[i] > 0) { + AttribType::Value type = (aim->mNumUVComponents[i] == 2) ? AttribType::VEC2 : AttribType::VEC3; + + if(comp_allow) idx_srcdata_tc.push_back(b->byteLength);// Store index of texture coordinates array. + + Ref<Accessor> tc = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mTextureCoords[i], AttribType::VEC3, type, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + if (tc) p.attributes.texcoord.push_back(tc); + } + } + + /*************** Vertices indices ****************/ +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + idx_srcdata_ind = b->byteLength;// Store index of indices array. +#endif + + if (aim->mNumFaces > 0) { + std::vector<IndicesType> indices; + unsigned int nIndicesPerFace = aim->mFaces[0].mNumIndices; + indices.resize(aim->mNumFaces * nIndicesPerFace); + for (size_t i = 0; i < aim->mNumFaces; ++i) { + for (size_t j = 0; j < nIndicesPerFace; ++j) { + indices[i*nIndicesPerFace + j] = uint16_t(aim->mFaces[i].mIndices[j]); + } + } + + p.indices = ExportData(*mAsset, meshId, b, unsigned(indices.size()), &indices[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_UNSIGNED_SHORT, BufferViewTarget_ELEMENT_ARRAY_BUFFER); + } + + switch (aim->mPrimitiveTypes) { + case aiPrimitiveType_POLYGON: + p.mode = PrimitiveMode_TRIANGLES; break; // TODO implement this + case aiPrimitiveType_LINE: + p.mode = PrimitiveMode_LINES; break; + case aiPrimitiveType_POINT: + p.mode = PrimitiveMode_POINTS; break; + default: // aiPrimitiveType_TRIANGLE + p.mode = PrimitiveMode_TRIANGLES; + } + + /*************** Skins ****************/ + if(aim->HasBones()) { + ExportSkin(*mAsset, aim, m, b, skinRef, inverseBindMatricesData); + } + + /****************** Compression ******************/ + ///TODO: animation: weights, joints. + if(comp_allow) + { +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + // Only one type of compression supported at now - Open3DGC. + // + o3dgc::BinaryStream bs; + o3dgc::SC3DMCEncoder<IndicesType> encoder; + o3dgc::IndexedFaceSet<IndicesType> comp_o3dgc_ifs; + o3dgc::SC3DMCEncodeParams comp_o3dgc_params; + + // + // Fill data for encoder. + // + // Quantization + unsigned quant_coord = mProperties->GetPropertyInteger("extensions.Open3DGC.quantization.POSITION", 12); + unsigned quant_normal = mProperties->GetPropertyInteger("extensions.Open3DGC.quantization.NORMAL", 10); + unsigned quant_texcoord = mProperties->GetPropertyInteger("extensions.Open3DGC.quantization.TEXCOORD", 10); + + // Prediction + o3dgc::O3DGCSC3DMCPredictionMode prediction_position = o3dgc::O3DGC_SC3DMC_PARALLELOGRAM_PREDICTION; + o3dgc::O3DGCSC3DMCPredictionMode prediction_normal = o3dgc::O3DGC_SC3DMC_SURF_NORMALS_PREDICTION; + o3dgc::O3DGCSC3DMCPredictionMode prediction_texcoord = o3dgc::O3DGC_SC3DMC_PARALLELOGRAM_PREDICTION; + + // IndexedFacesSet: "Crease angle", "solid", "convex" are set to default. + comp_o3dgc_ifs.SetCCW(true); + comp_o3dgc_ifs.SetIsTriangularMesh(true); + comp_o3dgc_ifs.SetNumFloatAttributes(0); + // Coordinates + comp_o3dgc_params.SetCoordQuantBits(quant_coord); + comp_o3dgc_params.SetCoordPredMode(prediction_position); + comp_o3dgc_ifs.SetNCoord(aim->mNumVertices); + comp_o3dgc_ifs.SetCoord((o3dgc::Real* const)&b->GetPointer()[idx_srcdata_begin]); + // Normals + if(idx_srcdata_normal != SIZE_MAX) + { + comp_o3dgc_params.SetNormalQuantBits(quant_normal); + comp_o3dgc_params.SetNormalPredMode(prediction_normal); + comp_o3dgc_ifs.SetNNormal(aim->mNumVertices); + comp_o3dgc_ifs.SetNormal((o3dgc::Real* const)&b->GetPointer()[idx_srcdata_normal]); + } + + // Texture coordinates + for(size_t num_tc = 0; num_tc < idx_srcdata_tc.size(); num_tc++) + { + size_t num = comp_o3dgc_ifs.GetNumFloatAttributes(); + + comp_o3dgc_params.SetFloatAttributeQuantBits(static_cast<unsigned long>(num), quant_texcoord); + comp_o3dgc_params.SetFloatAttributePredMode(static_cast<unsigned long>(num), prediction_texcoord); + comp_o3dgc_ifs.SetNFloatAttribute(static_cast<unsigned long>(num), aim->mNumVertices);// number of elements. + comp_o3dgc_ifs.SetFloatAttributeDim(static_cast<unsigned long>(num), aim->mNumUVComponents[num_tc]);// components per element: aiVector3D => x * float + comp_o3dgc_ifs.SetFloatAttributeType(static_cast<unsigned long>(num), o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD); + comp_o3dgc_ifs.SetFloatAttribute(static_cast<unsigned long>(num), (o3dgc::Real* const)&b->GetPointer()[idx_srcdata_tc[num_tc]]); + comp_o3dgc_ifs.SetNumFloatAttributes(static_cast<unsigned long>(num + 1)); + } + + // Coordinates indices + comp_o3dgc_ifs.SetNCoordIndex(aim->mNumFaces); + comp_o3dgc_ifs.SetCoordIndex((IndicesType* const)&b->GetPointer()[idx_srcdata_ind]); + // Prepare to encoding + comp_o3dgc_params.SetNumFloatAttributes(comp_o3dgc_ifs.GetNumFloatAttributes()); + if(mProperties->GetPropertyBool("extensions.Open3DGC.binary", true)) + comp_o3dgc_params.SetStreamType(o3dgc::O3DGC_STREAM_TYPE_BINARY); + else + comp_o3dgc_params.SetStreamType(o3dgc::O3DGC_STREAM_TYPE_ASCII); + + comp_o3dgc_ifs.ComputeMinMax(o3dgc::O3DGC_SC3DMC_MAX_ALL_DIMS); + // + // Encoding + // + encoder.Encode(comp_o3dgc_params, comp_o3dgc_ifs, bs); + // Replace data in buffer. + b->ReplaceData(idx_srcdata_begin, b->byteLength - idx_srcdata_begin, bs.GetBuffer(), bs.GetSize()); + // + // Add information about extension to mesh. + // + // Create extension structure. + Mesh::SCompression_Open3DGC* ext = new Mesh::SCompression_Open3DGC; + + // Fill it. + ext->Buffer = b->id; + ext->Offset = idx_srcdata_begin; + ext->Count = b->byteLength - idx_srcdata_begin; + ext->Binary = mProperties->GetPropertyBool("extensions.Open3DGC.binary"); + ext->IndicesCount = comp_o3dgc_ifs.GetNCoordIndex() * 3; + ext->VerticesCount = comp_o3dgc_ifs.GetNCoord(); + // And assign to mesh. + m->Extension.push_back(ext); +#endif + }// if(comp_allow) + }// for (unsigned int i = 0; i < mScene->mNumMeshes; ++i) + + //---------------------------------------- + // Finish the skin + // Create the Accessor for skinRef->inverseBindMatrices + if (createSkin) { + mat4* invBindMatrixData = new mat4[inverseBindMatricesData.size()]; + for ( unsigned int idx_joint = 0; idx_joint < inverseBindMatricesData.size(); ++idx_joint) { + CopyValue(inverseBindMatricesData[idx_joint], invBindMatrixData[idx_joint]); + } + + Ref<Accessor> invBindMatrixAccessor = ExportData(*mAsset, skinName, b, static_cast<unsigned int>(inverseBindMatricesData.size()), invBindMatrixData, AttribType::MAT4, AttribType::MAT4, ComponentType_FLOAT); + if (invBindMatrixAccessor) skinRef->inverseBindMatrices = invBindMatrixAccessor; + + // Identity Matrix =====> skinRef->bindShapeMatrix + // Temporary. Hard-coded identity matrix here + skinRef->bindShapeMatrix.isPresent = true; + IdentityMatrix4(skinRef->bindShapeMatrix.value); + + // Find node that contains this mesh and add "skeletons" and "skin" attributes to that node. + Ref<Node> rootNode = mAsset->nodes.Get(unsigned(0)); + Ref<Node> meshNode; + std::string meshID = mAsset->meshes.Get(unsigned(0))->id; + FindMeshNode(rootNode, meshNode, meshID); + + Ref<Node> rootJoint = FindSkeletonRootJoint(skinRef); + meshNode->skeletons.push_back(rootJoint); + meshNode->skin = skinRef; + } +} + +#if defined(__has_warning) +#if __has_warning("-Wunused-but-set-variable") +#pragma GCC diagnostic pop +#endif +#endif + +/* + * Export the root node of the node hierarchy. + * Calls ExportNode for all children. + */ +unsigned int glTFExporter::ExportNodeHierarchy(const aiNode* n) +{ + Ref<Node> node = mAsset->nodes.Create(mAsset->FindUniqueID(n->mName.C_Str(), "node")); + + if (!n->mTransformation.IsIdentity()) { + node->matrix.isPresent = true; + CopyValue(n->mTransformation, node->matrix.value); + } + + for (unsigned int i = 0; i < n->mNumMeshes; ++i) { + node->meshes.push_back(mAsset->meshes.Get(n->mMeshes[i])); + } + + for (unsigned int i = 0; i < n->mNumChildren; ++i) { + unsigned int idx = ExportNode(n->mChildren[i], node); + node->children.push_back(mAsset->nodes.Get(idx)); + } + + return node.GetIndex(); +} + +/* + * Export node and recursively calls ExportNode for all children. + * Since these nodes are not the root node, we also export the parent Ref<Node> + */ +unsigned int glTFExporter::ExportNode(const aiNode* n, Ref<Node>& parent) +{ + Ref<Node> node = mAsset->nodes.Create(mAsset->FindUniqueID(n->mName.C_Str(), "node")); + + node->parent = parent; + + if (!n->mTransformation.IsIdentity()) { + node->matrix.isPresent = true; + CopyValue(n->mTransformation, node->matrix.value); + } + + for (unsigned int i = 0; i < n->mNumMeshes; ++i) { + node->meshes.push_back(mAsset->meshes.Get(n->mMeshes[i])); + } + + for (unsigned int i = 0; i < n->mNumChildren; ++i) { + unsigned int idx = ExportNode(n->mChildren[i], node); + node->children.push_back(mAsset->nodes.Get(idx)); + } + + return node.GetIndex(); +} + + +void glTFExporter::ExportScene() +{ + const char* sceneName = "defaultScene"; + Ref<Scene> scene = mAsset->scenes.Create(sceneName); + + // root node will be the first one exported (idx 0) + if (mAsset->nodes.Size() > 0) { + scene->nodes.push_back(mAsset->nodes.Get(0u)); + } + + // set as the default scene + mAsset->scene = scene; +} + +void glTFExporter::ExportMetadata() +{ + glTF::AssetMetadata& asset = mAsset->asset; + asset.version = "1.0"; + + char buffer[256]; + ai_snprintf(buffer, 256, "Open Asset Import Library (assimp v%d.%d.%x)", + aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision()); + + asset.generator = buffer; + + // Copyright + aiString copyright_str; + if (mScene->mMetaData != nullptr && mScene->mMetaData->Get(AI_METADATA_SOURCE_COPYRIGHT, copyright_str)) { + asset.copyright = copyright_str.C_Str(); + } +} + +inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref<Animation>& animRef, Ref<Buffer>& buffer, const aiNodeAnim* nodeChannel, float ticksPerSecond) +{ + // Loop over the data and check to see if it exactly matches an existing buffer. + // If yes, then reference the existing corresponding accessor. + // Otherwise, add to the buffer and create a new accessor. + + size_t counts[3] = { + nodeChannel->mNumPositionKeys, + nodeChannel->mNumScalingKeys, + nodeChannel->mNumRotationKeys, + }; + size_t numKeyframes = 1; + for (int i = 0; i < 3; ++i) { + if (counts[i] > numKeyframes) { + numKeyframes = counts[i]; + } + } + + //------------------------------------------------------- + // Extract TIME parameter data. + // Check if the timeStamps are the same for mPositionKeys, mRotationKeys, and mScalingKeys. + if(nodeChannel->mNumPositionKeys > 0) { + typedef float TimeType; + std::vector<TimeType> timeData; + timeData.resize(numKeyframes); + for (size_t i = 0; i < numKeyframes; ++i) { + size_t frameIndex = i * nodeChannel->mNumPositionKeys / numKeyframes; + // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. + // Check if we have to cast type here. e.g. uint16_t() + timeData[i] = static_cast<float>(nodeChannel->mPositionKeys[frameIndex].mTime / ticksPerSecond); + } + + Ref<Accessor> timeAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), &timeData[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT); + if (timeAccessor) animRef->Parameters.TIME = timeAccessor; + } + + //------------------------------------------------------- + // Extract translation parameter data + if(nodeChannel->mNumPositionKeys > 0) { + C_STRUCT aiVector3D* translationData = new aiVector3D[numKeyframes]; + for (size_t i = 0; i < numKeyframes; ++i) { + size_t frameIndex = i * nodeChannel->mNumPositionKeys / numKeyframes; + translationData[i] = nodeChannel->mPositionKeys[frameIndex].mValue; + } + + Ref<Accessor> tranAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), translationData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); + if ( tranAccessor ) { + animRef->Parameters.translation = tranAccessor; + } + delete[] translationData; + } + + //------------------------------------------------------- + // Extract scale parameter data + if(nodeChannel->mNumScalingKeys > 0) { + C_STRUCT aiVector3D* scaleData = new aiVector3D[numKeyframes]; + for (size_t i = 0; i < numKeyframes; ++i) { + size_t frameIndex = i * nodeChannel->mNumScalingKeys / numKeyframes; + scaleData[i] = nodeChannel->mScalingKeys[frameIndex].mValue; + } + + Ref<Accessor> scaleAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), scaleData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); + if ( scaleAccessor ) { + animRef->Parameters.scale = scaleAccessor; + } + delete[] scaleData; + } + + //------------------------------------------------------- + // Extract rotation parameter data + if(nodeChannel->mNumRotationKeys > 0) { + vec4* rotationData = new vec4[numKeyframes]; + for (size_t i = 0; i < numKeyframes; ++i) { + size_t frameIndex = i * nodeChannel->mNumRotationKeys / numKeyframes; + rotationData[i][0] = nodeChannel->mRotationKeys[frameIndex].mValue.x; + rotationData[i][1] = nodeChannel->mRotationKeys[frameIndex].mValue.y; + rotationData[i][2] = nodeChannel->mRotationKeys[frameIndex].mValue.z; + rotationData[i][3] = nodeChannel->mRotationKeys[frameIndex].mValue.w; + } + + Ref<Accessor> rotAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), rotationData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); + if ( rotAccessor ) { + animRef->Parameters.rotation = rotAccessor; + } + delete[] rotationData; + } +} + +void glTFExporter::ExportAnimations() +{ + Ref<Buffer> bufferRef = mAsset->buffers.Get(unsigned (0)); + + for (unsigned int i = 0; i < mScene->mNumAnimations; ++i) { + const aiAnimation* anim = mScene->mAnimations[i]; + + std::string nameAnim = "anim"; + if (anim->mName.length > 0) { + nameAnim = anim->mName.C_Str(); + } + + for (unsigned int channelIndex = 0; channelIndex < anim->mNumChannels; ++channelIndex) { + const aiNodeAnim* nodeChannel = anim->mChannels[channelIndex]; + + // It appears that assimp stores this type of animation as multiple animations. + // where each aiNodeAnim in mChannels animates a specific node. + std::string name = nameAnim + "_" + ai_to_string(channelIndex); + name = mAsset->FindUniqueID(name, "animation"); + Ref<Animation> animRef = mAsset->animations.Create(name); + + /******************* Parameters ********************/ + ExtractAnimationData(*mAsset, name, animRef, bufferRef, nodeChannel, static_cast<float>(anim->mTicksPerSecond)); + + for (unsigned int j = 0; j < 3; ++j) { + std::string channelType; + int channelSize=0; + switch (j) { + case 0: + channelType = "rotation"; + channelSize = nodeChannel->mNumRotationKeys; + break; + case 1: + channelType = "scale"; + channelSize = nodeChannel->mNumScalingKeys; + break; + case 2: + channelType = "translation"; + channelSize = nodeChannel->mNumPositionKeys; + break; + } + + if (channelSize < 1) { continue; } + + Animation::AnimChannel tmpAnimChannel; + Animation::AnimSampler tmpAnimSampler; + + tmpAnimChannel.sampler = name + "_" + channelType; + tmpAnimChannel.target.path = channelType; + tmpAnimSampler.output = channelType; + tmpAnimSampler.id = name + "_" + channelType; + + tmpAnimChannel.target.id = mAsset->nodes.Get(nodeChannel->mNodeName.C_Str()); + + tmpAnimSampler.input = "TIME"; + tmpAnimSampler.interpolation = "LINEAR"; + + animRef->Channels.push_back(tmpAnimChannel); + animRef->Samplers.push_back(tmpAnimSampler); + } + + } + + // Assimp documentation staes this is not used (not implemented) + // for (unsigned int channelIndex = 0; channelIndex < anim->mNumMeshChannels; ++channelIndex) { + // const aiMeshAnim* meshChannel = anim->mMeshChannels[channelIndex]; + // } + + } // End: for-loop mNumAnimations +} + + +#endif // ASSIMP_BUILD_NO_GLTF_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/glTF/glTFExporter.h b/libs/assimp/code/AssetLib/glTF/glTFExporter.h new file mode 100644 index 0000000..a526954 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFExporter.h @@ -0,0 +1,118 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 GltfExporter.h + * Declares the exporter class to write a scene to a gltf/glb file + */ +#pragma once +#ifndef AI_GLTFEXPORTER_H_INC +#define AI_GLTFEXPORTER_H_INC + +#if !defined(ASSIMP_BUILD_NO_GLTF_EXPORTER) && !defined(ASSIMP_BUILD_NO_GLTF1_EXPORTER) + +#include <assimp/material.h> +#include <assimp/types.h> + +#include <map> +#include <memory> +#include <sstream> +#include <vector> + +struct aiScene; +struct aiNode; + +namespace glTFCommon { +template <class T> +class Ref; + +} + +namespace glTF { +class Asset; +struct TexProperty; +struct Node; + +} // namespace glTF + +namespace Assimp { +class IOSystem; +class IOStream; +class ExportProperties; + +// ------------------------------------------------------------------------------------------------ +/** Helper class to export a given scene to an glTF file. */ +// ------------------------------------------------------------------------------------------------ +class glTFExporter { +public: + /// Constructor for a specific scene to export + glTFExporter(const char *filename, IOSystem *pIOSystem, const aiScene *pScene, + const ExportProperties *pProperties, bool binary); + +private: + const char *mFilename; + IOSystem *mIOSystem; + std::shared_ptr<const aiScene> mScene; + const ExportProperties *mProperties; + + std::map<std::string, unsigned int> mTexturesByPath; + + std::shared_ptr<glTF::Asset> mAsset; + + std::vector<unsigned char> mBodyData; + + void WriteBinaryData(IOStream *outfile, std::size_t sceneLength); + + void GetTexSampler(const aiMaterial *mat, glTF::TexProperty &prop); + void GetMatColorOrTex(const aiMaterial *mat, glTF::TexProperty &prop, const char *propName, int type, int idx, aiTextureType tt); + void ExportMetadata(); + void ExportMaterials(); + void ExportMeshes(); + unsigned int ExportNodeHierarchy(const aiNode *n); + unsigned int ExportNode(const aiNode *node, glTFCommon::Ref<glTF::Node> & parent); + void ExportScene(); + void ExportAnimations(); +}; + +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_GLTF_EXPORTER + +#endif // AI_GLTFEXPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/glTF/glTFImporter.cpp b/libs/assimp/code/AssetLib/glTF/glTFImporter.cpp new file mode 100644 index 0000000..81db12e --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFImporter.cpp @@ -0,0 +1,725 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#if !defined(ASSIMP_BUILD_NO_GLTF_IMPORTER) && !defined(ASSIMP_BUILD_NO_GLTF1_IMPORTER) + +#include "AssetLib/glTF/glTFImporter.h" +#include "AssetLib/glTF/glTFAsset.h" +#if !defined(ASSIMP_BUILD_NO_EXPORT) +#include "AssetLib/glTF/glTFAssetWriter.h" +#endif +#include "PostProcessing/MakeVerboseFormat.h" + +#include <assimp/StringComparison.h> +#include <assimp/StringUtils.h> +#include <assimp/ai_assert.h> +#include <assimp/commonMetaData.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> + +#include <memory> + +using namespace Assimp; +using namespace glTF; + +// +// glTFImporter +// + +static const aiImporterDesc desc = { + "glTF Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, + 0, + 0, + 0, + 0, + "gltf glb" +}; + +glTFImporter::glTFImporter() : + BaseImporter(), meshOffsets(), embeddedTexIdxs(), mScene(nullptr) { + // empty +} + +glTFImporter::~glTFImporter() { + // empty +} + +const aiImporterDesc *glTFImporter::GetInfo() const { + return &desc; +} + +bool glTFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /* checkSig */) const { + glTF::Asset asset(pIOHandler); + try { + asset.Load(pFile, GetExtension(pFile) == "glb"); + std::string version = asset.asset.version; + return !version.empty() && version[0] == '1'; + } catch (...) { + return false; + } +} + +inline void SetMaterialColorProperty(std::vector<int> &embeddedTexIdxs, Asset & /*r*/, glTF::TexProperty prop, aiMaterial *mat, + aiTextureType texType, const char *pKey, unsigned int type, unsigned int idx) { + if (prop.texture) { + if (prop.texture->source) { + aiString uri(prop.texture->source->uri); + + int texIdx = embeddedTexIdxs[prop.texture->source.GetIndex()]; + if (texIdx != -1) { // embedded + // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) + uri.data[0] = '*'; + uri.length = 1 + ASSIMP_itoa10(uri.data + 1, MAXLEN - 1, texIdx); + } + + mat->AddProperty(&uri, _AI_MATKEY_TEXTURE_BASE, texType, 0); + } + } else { + aiColor4D col; + CopyValue(prop.color, col); + mat->AddProperty(&col, 1, pKey, type, idx); + } +} + +void glTFImporter::ImportMaterials(glTF::Asset &r) { + mScene->mNumMaterials = unsigned(r.materials.Size()); + mScene->mMaterials = new aiMaterial *[mScene->mNumMaterials]; + + for (unsigned int i = 0; i < mScene->mNumMaterials; ++i) { + aiMaterial *aimat = mScene->mMaterials[i] = new aiMaterial(); + + Material &mat = r.materials[i]; + + /*if (!mat.name.empty())*/ { + aiString str(mat.id /*mat.name*/); + aimat->AddProperty(&str, AI_MATKEY_NAME); + } + + SetMaterialColorProperty(embeddedTexIdxs, r, mat.ambient, aimat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT); + SetMaterialColorProperty(embeddedTexIdxs, r, mat.diffuse, aimat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE); + SetMaterialColorProperty(embeddedTexIdxs, r, mat.specular, aimat, aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR); + SetMaterialColorProperty(embeddedTexIdxs, r, mat.emission, aimat, aiTextureType_EMISSIVE, AI_MATKEY_COLOR_EMISSIVE); + + aimat->AddProperty(&mat.doubleSided, 1, AI_MATKEY_TWOSIDED); + + if (mat.transparent && (mat.transparency != 1.0f)) { + aimat->AddProperty(&mat.transparency, 1, AI_MATKEY_OPACITY); + } + + if (mat.shininess > 0.f) { + aimat->AddProperty(&mat.shininess, 1, AI_MATKEY_SHININESS); + } + } + + if (mScene->mNumMaterials == 0) { + mScene->mNumMaterials = 1; + // Delete the array of length zero created above. + delete[] mScene->mMaterials; + mScene->mMaterials = new aiMaterial *[1]; + mScene->mMaterials[0] = new aiMaterial(); + } +} + +static inline void SetFace(aiFace &face, int a) { + face.mNumIndices = 1; + face.mIndices = new unsigned int[1]; + face.mIndices[0] = a; +} + +static inline void SetFace(aiFace &face, int a, int b) { + face.mNumIndices = 2; + face.mIndices = new unsigned int[2]; + face.mIndices[0] = a; + face.mIndices[1] = b; +} + +static inline void SetFace(aiFace &face, int a, int b, int c) { + face.mNumIndices = 3; + face.mIndices = new unsigned int[3]; + face.mIndices[0] = a; + face.mIndices[1] = b; + face.mIndices[2] = c; +} + +static inline bool CheckValidFacesIndices(aiFace *faces, unsigned nFaces, unsigned nVerts) { + for (unsigned i = 0; i < nFaces; ++i) { + for (unsigned j = 0; j < faces[i].mNumIndices; ++j) { + unsigned idx = faces[i].mIndices[j]; + if (idx >= nVerts) + return false; + } + } + return true; +} + +void glTFImporter::ImportMeshes(glTF::Asset &r) { + std::vector<aiMesh *> meshes; + + unsigned int k = 0; + meshOffsets.clear(); + + for (unsigned int m = 0; m < r.meshes.Size(); ++m) { + Mesh &mesh = r.meshes[m]; + + // Check if mesh extensions is used + if (mesh.Extension.size() > 0) { +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + for (Mesh::SExtension *cur_ext : mesh.Extension) { + if (cur_ext->Type == Mesh::SExtension::EType::Compression_Open3DGC) { + // Limitations for meshes when using Open3DGC-compression. + // It's a current limitation of sp... Specification have not this part still - about mesh compression. Why only one primitive? + // Because glTF is very flexibly. But in fact it ugly flexible. Every primitive can has own set of accessors and accessors can + // point to a-a-a-a-any part of buffer (through bufferview of course) and even to another buffer. We know that "Open3DGC-compression" + // is applicable only to part of buffer. As we can't guaranty continuity of the data for decoder, we will limit quantity of primitives. + // Yes indices, coordinates etc. still can br stored in different buffers, but with current specification it's a exporter problem. + // Also primitive can has only one of "POSITION", "NORMAL" and less then "AI_MAX_NUMBER_OF_TEXTURECOORDS" of "TEXCOORD". All accessor + // of primitive must point to one continuous region of the buffer. + if (mesh.primitives.size() > 2) throw DeadlyImportError("GLTF: When using Open3DGC compression then only one primitive per mesh are allowed."); + + Mesh::SCompression_Open3DGC *o3dgc_ext = (Mesh::SCompression_Open3DGC *)cur_ext; + Ref<Buffer> buf = r.buffers.Get(o3dgc_ext->Buffer); + + buf->EncodedRegion_SetCurrent(mesh.id); + } else + { + throw DeadlyImportError("GLTF: Can not import mesh: unknown mesh extension (code: \"", ai_to_string(cur_ext->Type), + "\"), only Open3DGC is supported."); + } + } +#endif + } // if(mesh.Extension.size() > 0) + + meshOffsets.push_back(k); + k += unsigned(mesh.primitives.size()); + + for (unsigned int p = 0; p < mesh.primitives.size(); ++p) { + Mesh::Primitive &prim = mesh.primitives[p]; + + aiMesh *aim = new aiMesh(); + meshes.push_back(aim); + + aim->mName = mesh.id; + if (mesh.primitives.size() > 1) { + ai_uint32 &len = aim->mName.length; + aim->mName.data[len] = '-'; + len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p); + } + + switch (prim.mode) { + case PrimitiveMode_POINTS: + aim->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + + case PrimitiveMode_LINES: + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: + aim->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + + case PrimitiveMode_TRIANGLES: + case PrimitiveMode_TRIANGLE_STRIP: + case PrimitiveMode_TRIANGLE_FAN: + aim->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + } + + Mesh::Primitive::Attributes &attr = prim.attributes; + + if (attr.position.size() > 0 && attr.position[0]) { + aim->mNumVertices = attr.position[0]->count; + attr.position[0]->ExtractData(aim->mVertices); + } + + if (attr.normal.size() > 0 && attr.normal[0]) attr.normal[0]->ExtractData(aim->mNormals); + + for (size_t tc = 0; tc < attr.texcoord.size() && tc < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) { + attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]); + aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents(); + + aiVector3D *values = aim->mTextureCoords[tc]; + for (unsigned int i = 0; i < aim->mNumVertices; ++i) { + values[i].y = 1 - values[i].y; // Flip Y coords + } + } + + aiFace *faces = 0; + unsigned int nFaces = 0; + + if (prim.indices) { + unsigned int count = prim.indices->count; + + Accessor::Indexer data = prim.indices->GetIndexer(); + ai_assert(data.IsValid()); + + switch (prim.mode) { + case PrimitiveMode_POINTS: { + nFaces = count; + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; ++i) { + SetFace(faces[i], data.GetUInt(i)); + } + break; + } + + case PrimitiveMode_LINES: { + nFaces = count / 2; + if (nFaces * 2 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); + count = nFaces * 2; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 2) { + SetFace(faces[i / 2], data.GetUInt(i), data.GetUInt(i + 1)); + } + break; + } + + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: { + nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); + faces = new aiFace[nFaces]; + SetFace(faces[0], data.GetUInt(0), data.GetUInt(1)); + for (unsigned int i = 2; i < count; ++i) { + SetFace(faces[i - 1], faces[i - 2].mIndices[1], data.GetUInt(i)); + } + if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop + SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]); + } + break; + } + + case PrimitiveMode_TRIANGLES: { + nFaces = count / 3; + if (nFaces * 3 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); + count = nFaces * 3; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 3) { + SetFace(faces[i / 3], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); + } + break; + } + case PrimitiveMode_TRIANGLE_STRIP: { + nFaces = count - 2; + faces = new aiFace[nFaces]; + SetFace(faces[0], data.GetUInt(0), data.GetUInt(1), data.GetUInt(2)); + for (unsigned int i = 3; i < count; ++i) { + SetFace(faces[i - 2], faces[i - 1].mIndices[1], faces[i - 1].mIndices[2], data.GetUInt(i)); + } + break; + } + case PrimitiveMode_TRIANGLE_FAN: + nFaces = count - 2; + faces = new aiFace[nFaces]; + SetFace(faces[0], data.GetUInt(0), data.GetUInt(1), data.GetUInt(2)); + for (unsigned int i = 3; i < count; ++i) { + SetFace(faces[i - 2], faces[0].mIndices[0], faces[i - 1].mIndices[2], data.GetUInt(i)); + } + break; + } + } else { // no indices provided so directly generate from counts + + // use the already determined count as it includes checks + unsigned int count = aim->mNumVertices; + + switch (prim.mode) { + case PrimitiveMode_POINTS: { + nFaces = count; + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; ++i) { + SetFace(faces[i], i); + } + break; + } + + case PrimitiveMode_LINES: { + nFaces = count / 2; + if (nFaces * 2 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); + count = nFaces * 2; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 2) { + SetFace(faces[i / 2], i, i + 1); + } + break; + } + + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: { + nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); + faces = new aiFace[nFaces]; + SetFace(faces[0], 0, 1); + for (unsigned int i = 2; i < count; ++i) { + SetFace(faces[i - 1], faces[i - 2].mIndices[1], i); + } + if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop + SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]); + } + break; + } + + case PrimitiveMode_TRIANGLES: { + nFaces = count / 3; + if (nFaces * 3 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); + count = nFaces * 3; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 3) { + SetFace(faces[i / 3], i, i + 1, i + 2); + } + break; + } + case PrimitiveMode_TRIANGLE_STRIP: { + nFaces = count - 2; + faces = new aiFace[nFaces]; + SetFace(faces[0], 0, 1, 2); + for (unsigned int i = 3; i < count; ++i) { + SetFace(faces[i - 2], faces[i - 1].mIndices[1], faces[i - 1].mIndices[2], i); + } + break; + } + case PrimitiveMode_TRIANGLE_FAN: + nFaces = count - 2; + faces = new aiFace[nFaces]; + SetFace(faces[0], 0, 1, 2); + for (unsigned int i = 3; i < count; ++i) { + SetFace(faces[i - 2], faces[0].mIndices[0], faces[i - 1].mIndices[2], i); + } + break; + } + } + + if (faces) { + aim->mFaces = faces; + aim->mNumFaces = nFaces; + const bool validRes = CheckValidFacesIndices(faces, nFaces, aim->mNumVertices); + if (!validRes) { + ai_assert(validRes); + ASSIMP_LOG_WARN("Invalid number of faces detected."); + } + } + + if (prim.material) { + aim->mMaterialIndex = prim.material.GetIndex(); + } + } + } + + meshOffsets.push_back(k); + + CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes); +} + +void glTFImporter::ImportCameras(glTF::Asset &r) { + if (!r.cameras.Size()) { + return; + } + + mScene->mNumCameras = r.cameras.Size(); + mScene->mCameras = new aiCamera *[r.cameras.Size()]; + for (size_t i = 0; i < r.cameras.Size(); ++i) { + Camera &cam = r.cameras[i]; + + aiCamera *aicam = mScene->mCameras[i] = new aiCamera(); + + if (cam.type == Camera::Perspective) { + aicam->mAspect = cam.perspective.aspectRatio; + aicam->mHorizontalFOV = cam.perspective.yfov * ((aicam->mAspect == 0.f) ? 1.f : aicam->mAspect); + aicam->mClipPlaneFar = cam.perspective.zfar; + aicam->mClipPlaneNear = cam.perspective.znear; + } else { + aicam->mClipPlaneFar = cam.ortographic.zfar; + aicam->mClipPlaneNear = cam.ortographic.znear; + aicam->mHorizontalFOV = 0.0; + aicam->mAspect = 1.0f; + if (0.f != cam.ortographic.ymag) { + aicam->mAspect = cam.ortographic.xmag / cam.ortographic.ymag; + } + } + } +} + +void glTFImporter::ImportLights(glTF::Asset &r) { + if (!r.lights.Size()) return; + + mScene->mNumLights = r.lights.Size(); + mScene->mLights = new aiLight *[r.lights.Size()]; + + for (size_t i = 0; i < r.lights.Size(); ++i) { + Light &l = r.lights[i]; + + aiLight *ail = mScene->mLights[i] = new aiLight(); + + switch (l.type) { + case Light::Type_directional: + ail->mType = aiLightSource_DIRECTIONAL; + break; + + case Light::Type_spot: + ail->mType = aiLightSource_SPOT; + break; + + case Light::Type_ambient: + ail->mType = aiLightSource_AMBIENT; + break; + + default: // Light::Type_point + ail->mType = aiLightSource_POINT; + break; + } + + CopyValue(l.color, ail->mColorAmbient); + CopyValue(l.color, ail->mColorDiffuse); + CopyValue(l.color, ail->mColorSpecular); + + ail->mAngleOuterCone = l.falloffAngle; + ail->mAngleInnerCone = l.falloffExponent; // TODO fix this, it does not look right at all + + ail->mAttenuationConstant = l.constantAttenuation; + ail->mAttenuationLinear = l.linearAttenuation; + ail->mAttenuationQuadratic = l.quadraticAttenuation; + } +} + +aiNode *ImportNode(aiScene *pScene, glTF::Asset &r, std::vector<unsigned int> &meshOffsets, glTF::Ref<glTF::Node> &ptr) { + Node &node = *ptr; + + aiNode *ainode = new aiNode(node.id); + + if (!node.children.empty()) { + ainode->mNumChildren = unsigned(node.children.size()); + ainode->mChildren = new aiNode *[ainode->mNumChildren]; + + for (unsigned int i = 0; i < ainode->mNumChildren; ++i) { + aiNode *child = ImportNode(pScene, r, meshOffsets, node.children[i]); + child->mParent = ainode; + ainode->mChildren[i] = child; + } + } + + aiMatrix4x4 &matrix = ainode->mTransformation; + if (node.matrix.isPresent) { + CopyValue(node.matrix.value, matrix); + } else { + if (node.translation.isPresent) { + aiVector3D trans; + CopyValue(node.translation.value, trans); + aiMatrix4x4 t; + aiMatrix4x4::Translation(trans, t); + matrix = t * matrix; + } + + if (node.scale.isPresent) { + aiVector3D scal(1.f); + CopyValue(node.scale.value, scal); + aiMatrix4x4 s; + aiMatrix4x4::Scaling(scal, s); + matrix = s * matrix; + } + + if (node.rotation.isPresent) { + aiQuaternion rot; + CopyValue(node.rotation.value, rot); + matrix = aiMatrix4x4(rot.GetMatrix()) * matrix; + } + } + + if (!node.meshes.empty()) { + int count = 0; + for (size_t i = 0; i < node.meshes.size(); ++i) { + int idx = node.meshes[i].GetIndex(); + count += meshOffsets[idx + 1] - meshOffsets[idx]; + } + + ainode->mNumMeshes = count; + ainode->mMeshes = new unsigned int[count]; + + int k = 0; + for (size_t i = 0; i < node.meshes.size(); ++i) { + int idx = node.meshes[i].GetIndex(); + for (unsigned int j = meshOffsets[idx]; j < meshOffsets[idx + 1]; ++j, ++k) { + ainode->mMeshes[k] = j; + } + } + } + + if (node.camera) { + pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName; + } + + if (node.light) { + pScene->mLights[node.light.GetIndex()]->mName = ainode->mName; + } + + return ainode; +} + +void glTFImporter::ImportNodes(glTF::Asset &r) { + if (!r.scene) return; + + std::vector<Ref<Node>> rootNodes = r.scene->nodes; + + // The root nodes + unsigned int numRootNodes = unsigned(rootNodes.size()); + if (numRootNodes == 1) { // a single root node: use it + mScene->mRootNode = ImportNode(mScene, r, meshOffsets, rootNodes[0]); + } else if (numRootNodes > 1) { // more than one root node: create a fake root + aiNode *root = new aiNode("ROOT"); + root->mChildren = new aiNode *[numRootNodes]; + for (unsigned int i = 0; i < numRootNodes; ++i) { + aiNode *node = ImportNode(mScene, r, meshOffsets, rootNodes[i]); + node->mParent = root; + root->mChildren[root->mNumChildren++] = node; + } + mScene->mRootNode = root; + } + + //if (!mScene->mRootNode) { + // mScene->mRootNode = new aiNode("EMPTY"); + //} +} + +void glTFImporter::ImportEmbeddedTextures(glTF::Asset &r) { + embeddedTexIdxs.resize(r.images.Size(), -1); + + int numEmbeddedTexs = 0; + for (size_t i = 0; i < r.images.Size(); ++i) { + if (r.images[i].HasData()) + numEmbeddedTexs += 1; + } + + if (numEmbeddedTexs == 0) + return; + + mScene->mTextures = new aiTexture *[numEmbeddedTexs]; + + // Add the embedded textures + for (size_t i = 0; i < r.images.Size(); ++i) { + Image &img = r.images[i]; + if (!img.HasData()) continue; + + int idx = mScene->mNumTextures++; + embeddedTexIdxs[i] = idx; + + aiTexture *tex = mScene->mTextures[idx] = new aiTexture(); + + size_t length = img.GetDataLength(); + void *data = img.StealData(); + + tex->mFilename = img.name; + tex->mWidth = static_cast<unsigned int>(length); + tex->mHeight = 0; + tex->pcData = reinterpret_cast<aiTexel *>(data); + + if (!img.mimeType.empty()) { + const char *ext = strchr(img.mimeType.c_str(), '/') + 1; + if (ext) { + if (strcmp(ext, "jpeg") == 0) ext = "jpg"; + + size_t len = strlen(ext); + if (len <= 3) { + strcpy(tex->achFormatHint, ext); + } + } + } + } +} + +void glTFImporter::ImportCommonMetadata(glTF::Asset &a) { + ai_assert(mScene->mMetaData == nullptr); + const bool hasVersion = !a.asset.version.empty(); + const bool hasGenerator = !a.asset.generator.empty(); + const bool hasCopyright = !a.asset.copyright.empty(); + if (hasVersion || hasGenerator || hasCopyright) { + mScene->mMetaData = new aiMetadata; + if (hasVersion) { + mScene->mMetaData->Add(AI_METADATA_SOURCE_FORMAT_VERSION, aiString(a.asset.version)); + } + if (hasGenerator) { + mScene->mMetaData->Add(AI_METADATA_SOURCE_GENERATOR, aiString(a.asset.generator)); + } + if (hasCopyright) { + mScene->mMetaData->Add(AI_METADATA_SOURCE_COPYRIGHT, aiString(a.asset.copyright)); + } + } +} + +void glTFImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + // clean all member arrays + meshOffsets.clear(); + embeddedTexIdxs.clear(); + + this->mScene = pScene; + + // read the asset file + glTF::Asset asset(pIOHandler); + asset.Load(pFile, GetExtension(pFile) == "glb"); + + // + // Copy the data out + // + + ImportEmbeddedTextures(asset); + ImportMaterials(asset); + + ImportMeshes(asset); + + ImportCameras(asset); + ImportLights(asset); + + ImportNodes(asset); + ImportCommonMetadata(asset); + + if (pScene->mNumMeshes == 0) { + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } +} + +#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER diff --git a/libs/assimp/code/AssetLib/glTF/glTFImporter.h b/libs/assimp/code/AssetLib/glTF/glTFImporter.h new file mode 100644 index 0000000..529da53 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFImporter.h @@ -0,0 +1,89 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#pragma once +#ifndef AI_GLTFIMPORTER_H_INC +#define AI_GLTFIMPORTER_H_INC + +#include <assimp/BaseImporter.h> +#include <assimp/DefaultIOSystem.h> + +struct aiNode; + +namespace glTF { + class Asset; + +} + +namespace Assimp { + +/** + * Load the glTF format. + * https://github.com/KhronosGroup/glTF/tree/master/specification + */ +class glTFImporter : public BaseImporter { +public: + glTFImporter(); + ~glTFImporter() override; + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + +protected: + const aiImporterDesc *GetInfo() const override; + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + +private: + void ImportEmbeddedTextures(glTF::Asset &a); + void ImportMaterials(glTF::Asset &a); + void ImportMeshes(glTF::Asset &a); + void ImportCameras(glTF::Asset &a); + void ImportLights(glTF::Asset &a); + void ImportNodes(glTF::Asset &a); + void ImportCommonMetadata(glTF::Asset &a); + +private: + std::vector<unsigned int> meshOffsets; + std::vector<int> embeddedTexIdxs; + aiScene *mScene; +}; + +} // namespace Assimp + +#endif // AI_GLTFIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/glTF2/glTF2Asset.h b/libs/assimp/code/AssetLib/glTF2/glTF2Asset.h new file mode 100644 index 0000000..c597fc9 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF2/glTF2Asset.h @@ -0,0 +1,1242 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 glTFAsset.h + * Declares a glTF class to handle gltf/glb files + * + * glTF Extensions Support: + * KHR_materials_pbrSpecularGlossiness full + * KHR_materials_unlit full + * KHR_lights_punctual full + * KHR_materials_sheen full + * KHR_materials_clearcoat full + * KHR_materials_transmission full + * KHR_materials_volume full + * KHR_materials_ior full + */ +#ifndef GLTF2ASSET_H_INC +#define GLTF2ASSET_H_INC + +#if !defined(ASSIMP_BUILD_NO_GLTF_IMPORTER) && !defined(ASSIMP_BUILD_NO_GLTF2_IMPORTER) + +#include <assimp/Exceptional.h> + +#include <algorithm> +#include <list> +#include <map> +#include <set> +#include <stdexcept> +#include <string> +#include <vector> + +// clang-format off +#if (__GNUC__ == 8 && __GNUC_MINOR__ >= 0) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclass-memaccess" +#endif + +#include <rapidjson/document.h> +#include <rapidjson/error/en.h> +#include <rapidjson/rapidjson.h> +#include <rapidjson/schema.h> + +#if (__GNUC__ == 8 && __GNUC_MINOR__ >= 0) +# pragma GCC diagnostic pop +#endif + +#ifdef ASSIMP_API +# include <assimp/ByteSwapper.h> +# include <assimp/DefaultIOSystem.h> +# include <memory> +#else +# include <memory> +# define AI_SWAP4(p) +# define ai_assert +#endif + +#if _MSC_VER > 1500 || (defined __GNUC___) +# define ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +#else +# define gltf_unordered_map map +# define gltf_unordered_set set +#endif + +#ifdef ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +# include <unordered_map> +# include <unordered_set> +# if defined(_MSC_VER) && _MSC_VER <= 1600 +# define gltf_unordered_map tr1::unordered_map +# define gltf_unordered_set tr1::unordered_set +# else +# define gltf_unordered_map unordered_map +# define gltf_unordered_set unordered_set +# endif +#endif +// clang-format on + +#include <assimp/StringUtils.h> +#include <assimp/material.h> +#include <assimp/GltfMaterial.h> + +#include "AssetLib/glTF/glTFCommon.h" + +namespace glTF2 { + +using glTFCommon::Nullable; +using glTFCommon::Ref; +using glTFCommon::IOStream; +using glTFCommon::IOSystem; +using glTFCommon::shared_ptr; + +using rapidjson::Document; +using rapidjson::Value; + +class Asset; +class AssetWriter; + +struct BufferView; // here due to cross-reference +struct Texture; +struct Skin; + +using glTFCommon::mat4; +using glTFCommon::vec3; +using glTFCommon::vec4; + +//! Magic number for GLB files +#define AI_GLB_MAGIC_NUMBER "glTF" + +#ifdef ASSIMP_API +#include <assimp/Compiler/pushpack1.h> +#endif + +//! For binary .glb files +//! 12-byte header (+ the JSON + a "body" data section) +struct GLB_Header { + uint8_t magic[4]; //!< Magic number: "glTF" + uint32_t version; //!< Version number (always 2 as of the last update) + uint32_t length; //!< Total length of the Binary glTF, including header, scene, and body, in bytes +} PACK_STRUCT; + +struct GLB_Chunk { + uint32_t chunkLength; + uint32_t chunkType; +} PACK_STRUCT; + +#ifdef ASSIMP_API +#include <assimp/Compiler/poppack1.h> +#endif + +//! Values for the GLB_Chunk::chunkType field +enum ChunkType { + ChunkType_JSON = 0x4E4F534A, + ChunkType_BIN = 0x004E4942 +}; + +//! Values for the mesh primitive modes +enum PrimitiveMode { + PrimitiveMode_POINTS = 0, + PrimitiveMode_LINES = 1, + PrimitiveMode_LINE_LOOP = 2, + PrimitiveMode_LINE_STRIP = 3, + PrimitiveMode_TRIANGLES = 4, + PrimitiveMode_TRIANGLE_STRIP = 5, + PrimitiveMode_TRIANGLE_FAN = 6 +}; + +//! Values for the Accessor::componentType field +enum ComponentType { + ComponentType_BYTE = 5120, + ComponentType_UNSIGNED_BYTE = 5121, + ComponentType_SHORT = 5122, + ComponentType_UNSIGNED_SHORT = 5123, + ComponentType_UNSIGNED_INT = 5125, + ComponentType_FLOAT = 5126 +}; + +inline unsigned int ComponentTypeSize(ComponentType t) { + switch (t) { + case ComponentType_SHORT: + case ComponentType_UNSIGNED_SHORT: + return 2; + + case ComponentType_UNSIGNED_INT: + case ComponentType_FLOAT: + return 4; + + case ComponentType_BYTE: + case ComponentType_UNSIGNED_BYTE: + return 1; + default: + throw DeadlyImportError("GLTF: Unsupported Component Type ", ai_to_string(t)); + } +} + +//! Values for the BufferView::target field +enum BufferViewTarget { + BufferViewTarget_NONE = 0, + BufferViewTarget_ARRAY_BUFFER = 34962, + BufferViewTarget_ELEMENT_ARRAY_BUFFER = 34963 +}; + +//! Values for the Sampler::magFilter field +enum class SamplerMagFilter : unsigned int { + UNSET = 0, + SamplerMagFilter_Nearest = 9728, + SamplerMagFilter_Linear = 9729 +}; + +//! Values for the Sampler::minFilter field +enum class SamplerMinFilter : unsigned int { + UNSET = 0, + SamplerMinFilter_Nearest = 9728, + SamplerMinFilter_Linear = 9729, + SamplerMinFilter_Nearest_Mipmap_Nearest = 9984, + SamplerMinFilter_Linear_Mipmap_Nearest = 9985, + SamplerMinFilter_Nearest_Mipmap_Linear = 9986, + SamplerMinFilter_Linear_Mipmap_Linear = 9987 +}; + +//! Values for the Sampler::wrapS and Sampler::wrapT field +enum class SamplerWrap : unsigned int { + UNSET = 0, + Clamp_To_Edge = 33071, + Mirrored_Repeat = 33648, + Repeat = 10497 +}; + +//! Values for the Texture::format and Texture::internalFormat fields +enum TextureFormat { + TextureFormat_ALPHA = 6406, + TextureFormat_RGB = 6407, + TextureFormat_RGBA = 6408, + TextureFormat_LUMINANCE = 6409, + TextureFormat_LUMINANCE_ALPHA = 6410 +}; + +//! Values for the Texture::target field +enum TextureTarget { + TextureTarget_TEXTURE_2D = 3553 +}; + +//! Values for the Texture::type field +enum TextureType { + TextureType_UNSIGNED_BYTE = 5121, + TextureType_UNSIGNED_SHORT_5_6_5 = 33635, + TextureType_UNSIGNED_SHORT_4_4_4_4 = 32819, + TextureType_UNSIGNED_SHORT_5_5_5_1 = 32820 +}; + +//! Values for the Animation::Target::path field +enum AnimationPath { + AnimationPath_TRANSLATION, + AnimationPath_ROTATION, + AnimationPath_SCALE, + AnimationPath_WEIGHTS, +}; + +//! Values for the Animation::Sampler::interpolation field +enum Interpolation { + Interpolation_LINEAR, + Interpolation_STEP, + Interpolation_CUBICSPLINE, +}; + +//! Values for the Accessor::type field (helper class) +class AttribType { +public: + enum Value { SCALAR, + VEC2, + VEC3, + VEC4, + MAT2, + MAT3, + MAT4 }; + +private: + static const size_t NUM_VALUES = static_cast<size_t>(MAT4) + 1; + + struct Info { + const char *name; + unsigned int numComponents; + }; + + template <int N> + struct data { static const Info infos[NUM_VALUES]; }; + +public: + inline static Value FromString(const char *str) { + for (size_t i = 0; i < NUM_VALUES; ++i) { + if (strcmp(data<0>::infos[i].name, str) == 0) { + return static_cast<Value>(i); + } + } + return SCALAR; + } + + inline static const char *ToString(Value type) { + return data<0>::infos[static_cast<size_t>(type)].name; + } + + inline static unsigned int GetNumComponents(Value type) { + return data<0>::infos[static_cast<size_t>(type)].numComponents; + } +}; + +// must match the order of the AttribTypeTraits::Value enum! +template <int N> +const AttribType::Info + AttribType::data<N>::infos[AttribType::NUM_VALUES] = { + { "SCALAR", 1 }, { "VEC2", 2 }, { "VEC3", 3 }, { "VEC4", 4 }, { "MAT2", 4 }, { "MAT3", 9 }, { "MAT4", 16 } + }; + + +struct CustomExtension { + + // + // A struct containing custom extension data added to a glTF2 file + // Has to contain Object, Array, String, Double, Uint64, and Int64 at a minimum + // String, Double, Uint64, and Int64 are stored in the Nullables + // Object and Array are stored in the std::vector + // + std::string name; + + Nullable<std::string> mStringValue; + Nullable<double> mDoubleValue; + Nullable<uint64_t> mUint64Value; + Nullable<int64_t> mInt64Value; + Nullable<bool> mBoolValue; + + // std::vector<CustomExtension> handles both Object and Array + Nullable<std::vector<CustomExtension>> mValues; + + operator bool() const { + return Size() != 0; + } + + size_t Size() const { + if (mValues.isPresent) { + return mValues.value.size(); + } else if (mStringValue.isPresent || mDoubleValue.isPresent || mUint64Value.isPresent || mInt64Value.isPresent || mBoolValue.isPresent) { + return 1; + } + return 0; + } + + CustomExtension() = default; + + ~CustomExtension() = default; + + CustomExtension(const CustomExtension &other) : + name(other.name), + mStringValue(other.mStringValue), + mDoubleValue(other.mDoubleValue), + mUint64Value(other.mUint64Value), + mInt64Value(other.mInt64Value), + mBoolValue(other.mBoolValue), + mValues(other.mValues) { + // empty + } +}; + +//! Base class for all glTF top-level objects +struct Object { + int index; //!< The index of this object within its property container + int oIndex; //!< The original index of this object defined in the JSON + std::string id; //!< The globally unique ID used to reference this object + std::string name; //!< The user-defined name of this object + + CustomExtension customExtensions; + CustomExtension extras; + + //! Objects marked as special are not exported (used to emulate the binary body buffer) + virtual bool IsSpecial() const { return false; } + + virtual ~Object() {} + + //! Maps special IDs to another ID, where needed. Subclasses may override it (statically) + static const char *TranslateId(Asset & /*r*/, const char *id) { return id; } + + inline Value *FindString(Value &val, const char *id); + inline Value *FindNumber(Value &val, const char *id); + inline Value *FindUInt(Value &val, const char *id); + inline Value *FindArray(Value &val, const char *id); + inline Value *FindObject(Value &val, const char *id); + inline Value *FindExtension(Value &val, const char *extensionId); + + inline void ReadExtensions(Value &val); + inline void ReadExtras(Value &val); +}; + +// +// Classes for each glTF top-level object type +// + +//! A buffer points to binary geometry, animation, or skins. +struct Buffer : public Object { + /********************* Types *********************/ +public: + enum Type { + Type_arraybuffer, + Type_text + }; + + /// \struct SEncodedRegion + /// Descriptor of encoded region in "bufferView". + struct SEncodedRegion { + const size_t Offset; ///< Offset from begin of "bufferView" to encoded region, in bytes. + const size_t EncodedData_Length; ///< Size of encoded region, in bytes. + uint8_t *const DecodedData; ///< Cached encoded data. + const size_t DecodedData_Length; ///< Size of decoded region, in bytes. + const std::string ID; ///< ID of the region. + + /// \fn SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t* pDecodedData, const size_t pDecodedData_Length, const std::string pID) + /// Constructor. + /// \param [in] pOffset - offset from begin of "bufferView" to encoded region, in bytes. + /// \param [in] pEncodedData_Length - size of encoded region, in bytes. + /// \param [in] pDecodedData - pointer to decoded data array. + /// \param [in] pDecodedData_Length - size of encoded region, in bytes. + /// \param [in] pID - ID of the region. + SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID) : + Offset(pOffset), + EncodedData_Length(pEncodedData_Length), + DecodedData(pDecodedData), + DecodedData_Length(pDecodedData_Length), + ID(pID) {} + + /// \fn ~SEncodedRegion() + /// Destructor. + ~SEncodedRegion() { delete[] DecodedData; } + }; + + /******************* Variables *******************/ + + //std::string uri; //!< The uri of the buffer. Can be a filepath, a data uri, etc. (required) + size_t byteLength; //!< The length of the buffer in bytes. (default: 0) + //std::string type; //!< XMLHttpRequest responseType (default: "arraybuffer") + size_t capacity = 0; //!< The capacity of the buffer in bytes. (default: 0) + + Type type; + + /// \var EncodedRegion_Current + /// Pointer to currently active encoded region. + /// Why not decoding all regions at once and not to set one buffer with decoded data? + /// Yes, why not? Even "accessor" point to decoded data. I mean that fields "byteOffset", "byteStride" and "count" has values which describes decoded + /// data array. But only in range of mesh while is active parameters from "compressedData". For another mesh accessors point to decoded data too. But + /// offset is counted for another regions is encoded. + /// Example. You have two meshes. For every of it you have 4 bytes of data. That data compressed to 2 bytes. So, you have buffer with encoded data: + /// M1_E0, M1_E1, M2_E0, M2_E1. + /// After decoding you'll get: + /// M1_D0, M1_D1, M1_D2, M1_D3, M2_D0, M2_D1, M2_D2, M2_D3. + /// "accessors" must to use values that point to decoded data - obviously. So, you'll expect "accessors" like + /// "accessor_0" : { byteOffset: 0, byteLength: 4}, "accessor_1" : { byteOffset: 4, byteLength: 4} + /// but in real life you'll get: + /// "accessor_0" : { byteOffset: 0, byteLength: 4}, "accessor_1" : { byteOffset: 2, byteLength: 4} + /// Yes, accessor of next mesh has offset and length which mean: current mesh data is decoded, all other data is encoded. + /// And when before you start to read data of current mesh (with encoded data of course) you must decode region of "bufferView", after read finished + /// delete encoding mark. And after that you can repeat process: decode data of mesh, read, delete decoded data. + /// + /// Remark. Encoding all data at once is good in world with computers which do not has RAM limitation. So, you must use step by step encoding in + /// exporter and importer. And, thanks to such way, there is no need to load whole file into memory. + SEncodedRegion *EncodedRegion_Current; + +private: + shared_ptr<uint8_t> mData; //!< Pointer to the data + bool mIsSpecial; //!< Set to true for special cases (e.g. the body buffer) + + /// \var EncodedRegion_List + /// List of encoded regions. + std::list<SEncodedRegion *> EncodedRegion_List; + + /******************* Functions *******************/ + +public: + Buffer(); + ~Buffer(); + + void Read(Value &obj, Asset &r); + + bool LoadFromStream(IOStream &stream, size_t length = 0, size_t baseOffset = 0); + + /// \fn void EncodedRegion_Mark(const size_t pOffset, const size_t pEncodedData_Length, uint8_t* pDecodedData, const size_t pDecodedData_Length, const std::string& pID) + /// Mark region of "bufferView" as encoded. When data is request from such region then "bufferView" use decoded data. + /// \param [in] pOffset - offset from begin of "bufferView" to encoded region, in bytes. + /// \param [in] pEncodedData_Length - size of encoded region, in bytes. + /// \param [in] pDecodedData - pointer to decoded data array. + /// \param [in] pDecodedData_Length - size of encoded region, in bytes. + /// \param [in] pID - ID of the region. + void EncodedRegion_Mark(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID); + + /// \fn void EncodedRegion_SetCurrent(const std::string& pID) + /// Select current encoded region by ID. \sa EncodedRegion_Current. + /// \param [in] pID - ID of the region. + void EncodedRegion_SetCurrent(const std::string &pID); + + /// \fn bool ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t* pReplace_Data, const size_t pReplace_Count) + /// Replace part of buffer data. Pay attention that function work with original array of data (\ref mData) not with encoded regions. + /// \param [in] pBufferData_Offset - index of first element in buffer from which new data will be placed. + /// \param [in] pBufferData_Count - count of bytes in buffer which will be replaced. + /// \param [in] pReplace_Data - pointer to array with new data for buffer. + /// \param [in] pReplace_Count - count of bytes in new data. + /// \return true - if successfully replaced, false if input arguments is out of range. + bool ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count); + bool ReplaceData_joint(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count); + + size_t AppendData(uint8_t *data, size_t length); + void Grow(size_t amount); + + uint8_t *GetPointer() { return mData.get(); } + + void MarkAsSpecial() { mIsSpecial = true; } + + bool IsSpecial() const override { return mIsSpecial; } + + std::string GetURI() { return std::string(this->id) + ".bin"; } + + static const char *TranslateId(Asset &r, const char *id); +}; + +//! A view into a buffer generally representing a subset of the buffer. +struct BufferView : public Object { + Ref<Buffer> buffer; //! The ID of the buffer. (required) + size_t byteOffset; //! The offset into the buffer in bytes. (required) + size_t byteLength; //! The length of the bufferView in bytes. (default: 0) + unsigned int byteStride; //!< The stride, in bytes, between attributes referenced by this accessor. (default: 0) + + BufferViewTarget target; //! The target that the WebGL buffer should be bound to. + + void Read(Value &obj, Asset &r); + uint8_t *GetPointer(size_t accOffset); +}; + +//! A typed view into a BufferView. A BufferView contains raw binary data. +//! An accessor provides a typed view into a BufferView or a subset of a BufferView +//! similar to how WebGL's vertexAttribPointer() defines an attribute in a buffer. +struct Accessor : public Object { + struct Sparse; + + Ref<BufferView> bufferView; //!< The ID of the bufferView. (required) + size_t byteOffset; //!< The offset relative to the start of the bufferView in bytes. (required) + ComponentType componentType; //!< The datatype of components in the attribute. (required) + size_t count; //!< The number of attributes referenced by this accessor. (required) + AttribType::Value type; //!< Specifies if the attribute is a scalar, vector, or matrix. (required) + std::vector<double> max; //!< Maximum value of each component in this attribute. + std::vector<double> min; //!< Minimum value of each component in this attribute. + std::unique_ptr<Sparse> sparse; + std::unique_ptr<Buffer> decodedBuffer; // Packed decoded data, returned instead of original bufferView if present + + unsigned int GetNumComponents(); + unsigned int GetBytesPerComponent(); + unsigned int GetElementSize(); + + inline uint8_t *GetPointer(); + inline size_t GetStride(); + inline size_t GetMaxByteSize(); + + template <class T> + void ExtractData(T *&outData); + + void WriteData(size_t count, const void *src_buffer, size_t src_stride); + void WriteSparseValues(size_t count, const void *src_data, size_t src_dataStride); + void WriteSparseIndices(size_t count, const void *src_idx, size_t src_idxStride); + + //! Helper class to iterate the data + class Indexer { + friend struct Accessor; + + // This field is reported as not used, making it protectd is the easiest way to work around it without going to the bottom of what the problem is: + // ../code/glTF2/glTF2Asset.h:392:19: error: private field 'accessor' is not used [-Werror,-Wunused-private-field] + protected: + Accessor &accessor; + + private: + uint8_t *data; + size_t elemSize, stride; + + Indexer(Accessor &acc); + + public: + //! Accesses the i-th value as defined by the accessor + template <class T> + T GetValue(int i); + + //! Accesses the i-th value as defined by the accessor + inline unsigned int GetUInt(int i) { + return GetValue<unsigned int>(i); + } + + inline bool IsValid() const { + return data != nullptr; + } + }; + + inline Indexer GetIndexer() { + return Indexer(*this); + } + + Accessor() {} + void Read(Value &obj, Asset &r); + + //sparse + struct Sparse { + size_t count; + ComponentType indicesType; + Ref<BufferView> indices; + size_t indicesByteOffset; + Ref<BufferView> values; + size_t valuesByteOffset; + + std::vector<uint8_t> data; //!< Actual data, which may be defaulted to an array of zeros or the original data, with the sparse buffer view applied on top of it. + + void PopulateData(size_t numBytes, uint8_t *bytes); + void PatchData(unsigned int elementSize); + }; +}; + +struct Camera : public Object { + enum Type { + Perspective, + Orthographic + }; + + Type type; + + union { + struct { + float aspectRatio; //!<The floating - point aspect ratio of the field of view. (0 = undefined = use the canvas one) + float yfov; //!<The floating - point vertical field of view in radians. (required) + float zfar; //!<The floating - point distance to the far clipping plane. (required) + float znear; //!< The floating - point distance to the near clipping plane. (required) + } perspective; + + struct { + float xmag; //! The floating-point horizontal magnification of the view. (required) + float ymag; //! The floating-point vertical magnification of the view. (required) + float zfar; //! The floating-point distance to the far clipping plane. (required) + float znear; //! The floating-point distance to the near clipping plane. (required) + } ortographic; + } cameraProperties; + + Camera() : + type(Perspective), + cameraProperties() { + // empty + } + void Read(Value &obj, Asset &r); +}; + +//! A light (from KHR_lights_punctual extension) +struct Light : public Object { + enum Type { + Directional, + Point, + Spot + }; + + Type type; + + vec3 color; + float intensity; + Nullable<float> range; + + float innerConeAngle; + float outerConeAngle; + + Light() {} + void Read(Value &obj, Asset &r); +}; + +//! Image data used to create a texture. +struct Image : public Object { + std::string uri; //! The uri of the image, that can be a file path, a data URI, etc.. (required) + + Ref<BufferView> bufferView; + + std::string mimeType; + + int width, height; + +private: + std::unique_ptr<uint8_t[]> mData; + size_t mDataLength; + +public: + Image(); + void Read(Value &obj, Asset &r); + + inline bool HasData() const { return mDataLength > 0; } + + inline size_t GetDataLength() const { return mDataLength; } + + inline const uint8_t *GetData() const { return mData.get(); } + + inline uint8_t *StealData(); + + inline void SetData(uint8_t *data, size_t length, Asset &r); +}; + +const vec4 defaultBaseColor = { 1, 1, 1, 1 }; +const vec3 defaultEmissiveFactor = { 0, 0, 0 }; +const vec4 defaultDiffuseFactor = { 1, 1, 1, 1 }; +const vec3 defaultSpecularFactor = { 1, 1, 1 }; +const vec3 defaultSheenFactor = { 0, 0, 0 }; +const vec3 defaultAttenuationColor = { 1, 1, 1 }; + +struct TextureInfo { + Ref<Texture> texture; + unsigned int index; + unsigned int texCoord = 0; + + bool textureTransformSupported = false; + struct TextureTransformExt { + float offset[2]; + float rotation; + float scale[2]; + } TextureTransformExt_t; +}; + +struct NormalTextureInfo : TextureInfo { + float scale = 1; +}; + +struct OcclusionTextureInfo : TextureInfo { + float strength = 1; +}; + +struct PbrMetallicRoughness { + vec4 baseColorFactor; + TextureInfo baseColorTexture; + TextureInfo metallicRoughnessTexture; + float metallicFactor; + float roughnessFactor; +}; + +struct PbrSpecularGlossiness { + vec4 diffuseFactor; + vec3 specularFactor; + float glossinessFactor; + TextureInfo diffuseTexture; + TextureInfo specularGlossinessTexture; + + PbrSpecularGlossiness() { SetDefaults(); } + void SetDefaults(); +}; + +struct MaterialSheen { + vec3 sheenColorFactor; + float sheenRoughnessFactor; + TextureInfo sheenColorTexture; + TextureInfo sheenRoughnessTexture; + + MaterialSheen() { SetDefaults(); } + void SetDefaults(); +}; + +struct MaterialClearcoat { + float clearcoatFactor = 0.f; + float clearcoatRoughnessFactor = 0.f; + TextureInfo clearcoatTexture; + TextureInfo clearcoatRoughnessTexture; + NormalTextureInfo clearcoatNormalTexture; +}; + +struct MaterialTransmission { + TextureInfo transmissionTexture; + float transmissionFactor = 0.f; +}; + +struct MaterialVolume { + float thicknessFactor = 0.f; + TextureInfo thicknessTexture; + float attenuationDistance = 0.f; + vec3 attenuationColor; + + MaterialVolume() { SetDefaults(); } + void SetDefaults(); +}; + +struct MaterialIOR { + float ior = 0.f; + + MaterialIOR() { SetDefaults(); } + void SetDefaults(); +}; + +//! The material appearance of a primitive. +struct Material : public Object { + //PBR metallic roughness properties + PbrMetallicRoughness pbrMetallicRoughness; + + //other basic material properties + NormalTextureInfo normalTexture; + OcclusionTextureInfo occlusionTexture; + TextureInfo emissiveTexture; + vec3 emissiveFactor; + std::string alphaMode; + float alphaCutoff; + bool doubleSided; + + //extension: KHR_materials_pbrSpecularGlossiness + Nullable<PbrSpecularGlossiness> pbrSpecularGlossiness; + + //extension: KHR_materials_sheen + Nullable<MaterialSheen> materialSheen; + + //extension: KHR_materials_clearcoat + Nullable<MaterialClearcoat> materialClearcoat; + + //extension: KHR_materials_transmission + Nullable<MaterialTransmission> materialTransmission; + + //extension: KHR_materials_volume + Nullable<MaterialVolume> materialVolume; + + //extension: KHR_materials_ior + Nullable<MaterialIOR> materialIOR; + + //extension: KHR_materials_unlit + bool unlit; + + Material() { SetDefaults(); } + void Read(Value &obj, Asset &r); + void SetDefaults(); + + inline void SetTextureProperties(Asset &r, Value *prop, TextureInfo &out); + inline void ReadTextureProperty(Asset &r, Value &vals, const char *propName, TextureInfo &out); + inline void ReadTextureProperty(Asset &r, Value &vals, const char *propName, NormalTextureInfo &out); + inline void ReadTextureProperty(Asset &r, Value &vals, const char *propName, OcclusionTextureInfo &out); +}; + +//! A set of primitives to be rendered. A node can contain one or more meshes. A node's transform places the mesh in the scene. +struct Mesh : public Object { + using AccessorList = std::vector<Ref<Accessor>>; + + struct Primitive { + PrimitiveMode mode; + + struct Attributes { + AccessorList position, normal, tangent, texcoord, color, joint, jointmatrix, weight; + } attributes; + + Ref<Accessor> indices; + + Ref<Material> material; + + struct Target { + AccessorList position, normal, tangent; + }; + std::vector<Target> targets; + + // extension: FB_ngon_encoding + bool ngonEncoded; + + Primitive(): ngonEncoded(false) {} + }; + + std::vector<Primitive> primitives; + + std::vector<float> weights; + std::vector<std::string> targetNames; + + Mesh() {} + + /// Get mesh data from JSON-object and place them to root asset. + /// \param [in] pJSON_Object - reference to pJSON-object from which data are read. + /// \param [out] pAsset_Root - reference to root asset where data will be stored. + void Read(Value &pJSON_Object, Asset &pAsset_Root); +}; + +struct Node : public Object { + std::vector<Ref<Node>> children; + std::vector<Ref<Mesh>> meshes; + + Nullable<mat4> matrix; + Nullable<vec3> translation; + Nullable<vec4> rotation; + Nullable<vec3> scale; + + Ref<Camera> camera; + Ref<Light> light; + + std::vector<Ref<Node>> skeletons; //!< The ID of skeleton nodes. Each of which is the root of a node hierarchy. + Ref<Skin> skin; //!< The ID of the skin referenced by this node. + std::string jointName; //!< Name used when this node is a joint in a skin. + + Ref<Node> parent; //!< This is not part of the glTF specification. Used as a helper. + + Node() {} + void Read(Value &obj, Asset &r); +}; + +struct Program : public Object { + Program() {} + void Read(Value &obj, Asset &r); +}; + +struct Sampler : public Object { + SamplerMagFilter magFilter; //!< The texture magnification filter. + SamplerMinFilter minFilter; //!< The texture minification filter. + SamplerWrap wrapS; //!< The texture wrapping in the S direction. + SamplerWrap wrapT; //!< The texture wrapping in the T direction. + + Sampler() { SetDefaults(); } + void Read(Value &obj, Asset &r); + void SetDefaults(); +}; + +struct Scene : public Object { + std::string name; + std::vector<Ref<Node>> nodes; + + Scene() {} + void Read(Value &obj, Asset &r); +}; + +struct Shader : public Object { + Shader() {} + void Read(Value &obj, Asset &r); +}; + +struct Skin : public Object { + Nullable<mat4> bindShapeMatrix; //!< Floating-point 4x4 transformation matrix stored in column-major order. + Ref<Accessor> inverseBindMatrices; //!< The ID of the accessor containing the floating-point 4x4 inverse-bind matrices. + std::vector<Ref<Node>> jointNames; //!< Joint names of the joints (nodes with a jointName property) in this skin. + std::string name; //!< The user-defined name of this object. + + Skin() {} + void Read(Value &obj, Asset &r); +}; + +//! A texture and its sampler. +struct Texture : public Object { + Ref<Sampler> sampler; //!< The ID of the sampler used by this texture. (required) + Ref<Image> source; //!< The ID of the image used by this texture. (required) + + //TextureFormat format; //!< The texture's format. (default: TextureFormat_RGBA) + //TextureFormat internalFormat; //!< The texture's internal format. (default: TextureFormat_RGBA) + + //TextureTarget target; //!< The target that the WebGL texture should be bound to. (default: TextureTarget_TEXTURE_2D) + //TextureType type; //!< Texel datatype. (default: TextureType_UNSIGNED_BYTE) + + Texture() {} + void Read(Value &obj, Asset &r); +}; + +struct Animation : public Object { + struct Sampler { + Sampler() : + interpolation(Interpolation_LINEAR) {} + + Ref<Accessor> input; //!< Accessor reference to the buffer storing the key-frame times. + Ref<Accessor> output; //!< Accessor reference to the buffer storing the key-frame values. + Interpolation interpolation; //!< Type of interpolation algorithm to use between key-frames. + }; + + struct Target { + Target() : + path(AnimationPath_TRANSLATION) {} + + Ref<Node> node; //!< The node to animate. + AnimationPath path; //!< The property of the node to animate. + }; + + struct Channel { + Channel() : + sampler(-1) {} + + int sampler; //!< The sampler index containing the animation data. + Target target; //!< The node and property to animate. + }; + + std::vector<Sampler> samplers; //!< All the key-frame data for this animation. + std::vector<Channel> channels; //!< Data to connect nodes to key-frames. + + Animation() {} + void Read(Value &obj, Asset &r); +}; + +//! Base class for LazyDict that acts as an interface +class LazyDictBase { +public: + virtual ~LazyDictBase() {} + + virtual void AttachToDocument(Document &doc) = 0; + virtual void DetachFromDocument() = 0; + +#if !defined(ASSIMP_BUILD_NO_EXPORT) + virtual void WriteObjects(AssetWriter &writer) = 0; +#endif +}; + +template <class T> +class LazyDict; + +//! (Implemented in glTFAssetWriter.h) +template <class T> +void WriteLazyDict(LazyDict<T> &d, AssetWriter &w); + +//! Manages lazy loading of the glTF top-level objects, and keeps a reference to them by ID +//! It is the owner the loaded objects, so when it is destroyed it also deletes them +template <class T> +class LazyDict : public LazyDictBase { + friend class Asset; + friend class AssetWriter; + + using Dict = typename std::gltf_unordered_map<unsigned int, unsigned int>; + using IdDict = typename std::gltf_unordered_map<std::string, unsigned int>; + + std::vector<T *> mObjs; //! The read objects + Dict mObjsByOIndex; //! The read objects accessible by original index + IdDict mObjsById; //! The read objects accessible by id + const char *mDictId; //! ID of the dictionary object + const char *mExtId; //! ID of the extension defining the dictionary + Value *mDict; //! JSON dictionary object + Asset &mAsset; //! The asset instance + + std::gltf_unordered_set<unsigned int> mRecursiveReferenceCheck; //! Used by Retrieve to prevent recursive lookups + + void AttachToDocument(Document &doc); + void DetachFromDocument(); + +#if !defined(ASSIMP_BUILD_NO_EXPORT) + void WriteObjects(AssetWriter &writer) { WriteLazyDict<T>(*this, writer); } +#endif + + Ref<T> Add(T *obj); + +public: + LazyDict(Asset &asset, const char *dictId, const char *extId = 0); + ~LazyDict(); + + Ref<T> Retrieve(unsigned int i); + + Ref<T> Get(unsigned int i); + Ref<T> Get(const char *id); + + Ref<T> Create(const char *id); + Ref<T> Create(const std::string &id) { return Create(id.c_str()); } + + unsigned int Remove(const char *id); + + inline unsigned int Size() const { return unsigned(mObjs.size()); } + + inline T &operator[](size_t i) { return *mObjs[i]; } +}; + +struct AssetMetadata { + std::string copyright; //!< A copyright message suitable for display to credit the content creator. + std::string generator; //!< Tool that generated this glTF model.Useful for debugging. + + struct { + std::string api; //!< Specifies the target rendering API (default: "WebGL") + std::string version; //!< Specifies the target rendering API (default: "1.0.3") + } profile; //!< Specifies the target rendering API and version, e.g., WebGL 1.0.3. (default: {}) + + std::string version; //!< The glTF format version + + void Read(Document &doc); + + AssetMetadata() : + version() {} +}; + +// +// glTF Asset class +// + +//! Root object for a glTF asset +class Asset { + using IdMap = std::gltf_unordered_map<std::string, int>; + + template <class T> + friend class LazyDict; + friend struct Buffer; // To access OpenFile + friend class AssetWriter; + + std::vector<LazyDictBase *> mDicts; + +public: + //! Keeps info about the enabled extensions + struct Extensions { + bool KHR_materials_pbrSpecularGlossiness; + bool KHR_materials_unlit; + bool KHR_lights_punctual; + bool KHR_texture_transform; + bool KHR_materials_sheen; + bool KHR_materials_clearcoat; + bool KHR_materials_transmission; + bool KHR_materials_volume; + bool KHR_materials_ior; + bool KHR_draco_mesh_compression; + bool FB_ngon_encoding; + bool KHR_texture_basisu; + + Extensions() : + KHR_materials_pbrSpecularGlossiness(false), + KHR_materials_unlit(false), + KHR_lights_punctual(false), + KHR_texture_transform(false), + KHR_materials_sheen(false), + KHR_materials_clearcoat(false), + KHR_materials_transmission(false), + KHR_materials_volume(false), + KHR_materials_ior(false), + KHR_draco_mesh_compression(false), + FB_ngon_encoding(false), + KHR_texture_basisu(false) { + // empty + } + } extensionsUsed; + + //! Keeps info about the required extensions + struct RequiredExtensions { + bool KHR_draco_mesh_compression; + bool KHR_texture_basisu; + + RequiredExtensions() : KHR_draco_mesh_compression(false), KHR_texture_basisu(false) { + // empty + } + } extensionsRequired; + + AssetMetadata asset; + Value *extras; + + // Dictionaries for each type of object + + LazyDict<Accessor> accessors; + LazyDict<Animation> animations; + LazyDict<Buffer> buffers; + LazyDict<BufferView> bufferViews; + LazyDict<Camera> cameras; + LazyDict<Light> lights; + LazyDict<Image> images; + LazyDict<Material> materials; + LazyDict<Mesh> meshes; + LazyDict<Node> nodes; + LazyDict<Sampler> samplers; + LazyDict<Scene> scenes; + LazyDict<Skin> skins; + LazyDict<Texture> textures; + + Ref<Scene> scene; + +public: + Asset(IOSystem *io = nullptr, rapidjson::IRemoteSchemaDocumentProvider *schemaDocumentProvider = nullptr) : + mDicts(), + extensionsUsed(), + extensionsRequired(), + asset(), + extras(nullptr), + accessors(*this, "accessors"), + animations(*this, "animations"), + buffers(*this, "buffers"), + bufferViews(*this, "bufferViews"), + cameras(*this, "cameras"), + lights(*this, "lights", "KHR_lights_punctual"), + images(*this, "images"), + materials(*this, "materials"), + meshes(*this, "meshes"), + nodes(*this, "nodes"), + samplers(*this, "samplers"), + scenes(*this, "scenes"), + skins(*this, "skins"), + textures(*this, "textures") , + mIOSystem(io), + mSchemaDocumentProvider(schemaDocumentProvider) { + // empty + } + + //! Main function + void Load(const std::string &file, bool isBinary = false); + + //! Parse the AssetMetadata and check that the version is 2. + bool CanRead(const std::string &pFile, bool isBinary = false); + + //! Enables binary encoding on the asset + void SetAsBinary(); + + //! Search for an available name, starting from the given strings + std::string FindUniqueID(const std::string &str, const char *suffix); + + Ref<Buffer> GetBodyBuffer() { return mBodyBuffer; } + + Asset(Asset &) = delete; + Asset &operator=(const Asset &) = delete; + +private: + void ReadBinaryHeader(IOStream &stream, std::vector<char> &sceneData); + + /// Obtain a JSON document from the stream. + /// \param second argument is a buffer used by the document. It must be kept + /// alive while the document is in use. + Document ReadDocument(IOStream& stream, bool isBinary, std::vector<char>& sceneData); + + void ReadExtensionsUsed(Document &doc); + void ReadExtensionsRequired(Document &doc); + + IOStream *OpenFile(const std::string &path, const char *mode, bool absolute = false); + +private: + IOSystem *mIOSystem; + rapidjson::IRemoteSchemaDocumentProvider *mSchemaDocumentProvider; + std::string mCurrentAssetDir; + size_t mSceneLength; + size_t mBodyOffset; + size_t mBodyLength; + IdMap mUsedIds; + Ref<Buffer> mBodyBuffer; +}; + +inline std::string getContextForErrorMessages(const std::string &id, const std::string &name) { + std::string context = id; + if (!name.empty()) { + context += " (\"" + name + "\")"; + } + return context; +} + +} // namespace glTF2 + +// Include the implementation of the methods +#include "glTF2Asset.inl" + +#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER + +#endif // GLTF2ASSET_H_INC diff --git a/libs/assimp/code/AssetLib/glTF2/glTF2Asset.inl b/libs/assimp/code/AssetLib/glTF2/glTF2Asset.inl new file mode 100644 index 0000000..ec481a7 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF2/glTF2Asset.inl @@ -0,0 +1,2069 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#include "AssetLib/glTF/glTFCommon.h" + +#include <assimp/MemoryIOWrapper.h> +#include <assimp/StringUtils.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Base64.hpp> + +// clang-format off +#ifdef ASSIMP_ENABLE_DRACO + +// Google draco library headers spew many warnings. Bad Google, no cookie +# if _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4018) // Signed/unsigned mismatch +# pragma warning(disable : 4804) // Unsafe use of type 'bool' +# elif defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wsign-compare" +# elif defined(__GNUC__) +# pragma GCC diagnostic push +# if (__GNUC__ > 4) +# pragma GCC diagnostic ignored "-Wbool-compare" +# endif +# pragma GCC diagnostic ignored "-Wsign-compare" +#endif + +#include "draco/compression/decode.h" +#include "draco/core/decoder_buffer.h" + +#if _MSC_VER +# pragma warning(pop) +#elif defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif +#ifndef DRACO_MESH_COMPRESSION_SUPPORTED +# error glTF: KHR_draco_mesh_compression: draco library must have DRACO_MESH_COMPRESSION_SUPPORTED +#endif +#endif +// clang-format on + +using namespace Assimp; +using namespace glTFCommon; + +namespace glTF2 { + +namespace { + +// +// JSON Value reading helpers +// +inline CustomExtension ReadExtensions(const char *name, Value &obj) { + CustomExtension ret; + ret.name = name; + if (obj.IsObject()) { + ret.mValues.isPresent = true; + for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) { + auto &val = it->value; + ret.mValues.value.push_back(ReadExtensions(it->name.GetString(), val)); + } + } else if (obj.IsArray()) { + ret.mValues.value.reserve(obj.Size()); + ret.mValues.isPresent = true; + for (unsigned int i = 0; i < obj.Size(); ++i) { + ret.mValues.value.push_back(ReadExtensions(name, obj[i])); + } + } else if (obj.IsNumber()) { + if (obj.IsUint64()) { + ret.mUint64Value.value = obj.GetUint64(); + ret.mUint64Value.isPresent = true; + } else if (obj.IsInt64()) { + ret.mInt64Value.value = obj.GetInt64(); + ret.mInt64Value.isPresent = true; + } else if (obj.IsDouble()) { + ret.mDoubleValue.value = obj.GetDouble(); + ret.mDoubleValue.isPresent = true; + } + } else if (obj.IsString()) { + ReadValue(obj, ret.mStringValue); + ret.mStringValue.isPresent = true; + } else if (obj.IsBool()) { + ret.mBoolValue.value = obj.GetBool(); + ret.mBoolValue.isPresent = true; + } + return ret; +} + +inline void CopyData(size_t count, const uint8_t *src, size_t src_stride, + uint8_t *dst, size_t dst_stride) { + if (src_stride == dst_stride) { + memcpy(dst, src, count * src_stride); + return; + } + + size_t sz = std::min(src_stride, dst_stride); + for (size_t i = 0; i < count; ++i) { + memcpy(dst, src, sz); + if (sz < dst_stride) { + memset(dst + sz, 0, dst_stride - sz); + } + src += src_stride; + dst += dst_stride; + } +} + +void SetVector(vec4 &v, const float (&in)[4]) { + v[0] = in[0]; + v[1] = in[1]; + v[2] = in[2]; + v[3] = in[3]; +} + +void SetVector(vec3 &v, const float (&in)[3]) { + v[0] = in[0]; + v[1] = in[1]; + v[2] = in[2]; +} + +template <int N> +inline int Compare(const char *attr, const char (&str)[N]) { + return (strncmp(attr, str, N - 1) == 0) ? N - 1 : 0; +} + +#if _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4706) +#endif // _MSC_VER + +inline bool GetAttribVector(Mesh::Primitive &p, const char *attr, Mesh::AccessorList *&v, int &pos) { + if ((pos = Compare(attr, "POSITION"))) { + v = &(p.attributes.position); + } else if ((pos = Compare(attr, "NORMAL"))) { + v = &(p.attributes.normal); + } else if ((pos = Compare(attr, "TANGENT"))) { + v = &(p.attributes.tangent); + } else if ((pos = Compare(attr, "TEXCOORD"))) { + v = &(p.attributes.texcoord); + } else if ((pos = Compare(attr, "COLOR"))) { + v = &(p.attributes.color); + } else if ((pos = Compare(attr, "JOINT"))) { + v = &(p.attributes.joint); + } else if ((pos = Compare(attr, "JOINTMATRIX"))) { + v = &(p.attributes.jointmatrix); + } else if ((pos = Compare(attr, "WEIGHT"))) { + v = &(p.attributes.weight); + } else + return false; + return true; +} + +inline bool GetAttribTargetVector(Mesh::Primitive &p, const int targetIndex, const char *attr, Mesh::AccessorList *&v, int &pos) { + if ((pos = Compare(attr, "POSITION"))) { + v = &(p.targets[targetIndex].position); + } else if ((pos = Compare(attr, "NORMAL"))) { + v = &(p.targets[targetIndex].normal); + } else if ((pos = Compare(attr, "TANGENT"))) { + v = &(p.targets[targetIndex].tangent); + } else + return false; + return true; +} + +} // namespace + +inline Value *Object::FindString(Value &val, const char *memberId) { + return FindStringInContext(val, memberId, id.c_str(), name.c_str()); +} + +inline Value *Object::FindNumber(Value &val, const char *memberId) { + return FindNumberInContext(val, memberId, id.c_str(), name.c_str()); +} + +inline Value *Object::FindUInt(Value &val, const char *memberId) { + return FindUIntInContext(val, memberId, id.c_str(), name.c_str()); +} + +inline Value *Object::FindArray(Value &val, const char *memberId) { + return FindArrayInContext(val, memberId, id.c_str(), name.c_str()); +} + +inline Value *Object::FindObject(Value &val, const char *memberId) { + return FindObjectInContext(val, memberId, id.c_str(), name.c_str()); +} + +inline Value *Object::FindExtension(Value &val, const char *extensionId) { + return FindExtensionInContext(val, extensionId, id.c_str(), name.c_str()); +} + +inline void Object::ReadExtensions(Value &val) { + if (Value *curExtensions = FindObject(val, "extensions")) { + this->customExtensions = glTF2::ReadExtensions("extensions", *curExtensions); + } +} + +inline void Object::ReadExtras(Value &val) { + if (Value *curExtras = FindObject(val, "extras")) { + this->extras = glTF2::ReadExtensions("extras", *curExtras); + } +} + +#ifdef ASSIMP_ENABLE_DRACO + +template <typename T> +inline void CopyFaceIndex_Draco(Buffer &decodedIndexBuffer, const draco::Mesh &draco_mesh) { + const size_t faceStride = sizeof(T) * 3; + for (draco::FaceIndex f(0); f < draco_mesh.num_faces(); ++f) { + const draco::Mesh::Face &face = draco_mesh.face(f); + T indices[3] = { static_cast<T>(face[0].value()), static_cast<T>(face[1].value()), static_cast<T>(face[2].value()) }; + memcpy(decodedIndexBuffer.GetPointer() + (f.value() * faceStride), &indices[0], faceStride); + } +} + +inline void SetDecodedIndexBuffer_Draco(const draco::Mesh &dracoMesh, Mesh::Primitive &prim) { + if (!prim.indices || dracoMesh.num_faces() == 0) + return; + + // Create a decoded Index buffer (if there is one) + size_t componentBytes = prim.indices->GetBytesPerComponent(); + + std::unique_ptr<Buffer> decodedIndexBuffer(new Buffer()); + decodedIndexBuffer->Grow(dracoMesh.num_faces() * 3 * componentBytes); + + // If accessor uses the same size as draco implementation, copy the draco buffer directly + + // Usually uint32_t but shouldn't assume + if (sizeof(dracoMesh.face(draco::FaceIndex(0))[0]) == componentBytes) { + memcpy(decodedIndexBuffer->GetPointer(), &dracoMesh.face(draco::FaceIndex(0))[0], decodedIndexBuffer->byteLength); + return; + } + + // Not same size, convert + switch (componentBytes) { + case sizeof(uint32_t): + CopyFaceIndex_Draco<uint32_t>(*decodedIndexBuffer, dracoMesh); + break; + case sizeof(uint16_t): + CopyFaceIndex_Draco<uint16_t>(*decodedIndexBuffer, dracoMesh); + break; + case sizeof(uint8_t): + CopyFaceIndex_Draco<uint8_t>(*decodedIndexBuffer, dracoMesh); + break; + default: + ai_assert(false); + break; + } + + // Assign this alternate data buffer to the accessor + prim.indices->decodedBuffer.swap(decodedIndexBuffer); +} + +template <typename T> +static bool GetAttributeForAllPoints_Draco(const draco::Mesh &dracoMesh, + const draco::PointAttribute &dracoAttribute, + Buffer &outBuffer) { + size_t byteOffset = 0; + T values[4] = { 0, 0, 0, 0 }; + for (draco::PointIndex i(0); i < dracoMesh.num_points(); ++i) { + const draco::AttributeValueIndex val_index = dracoAttribute.mapped_index(i); + if (!dracoAttribute.ConvertValue<T>(val_index, dracoAttribute.num_components(), values)) { + return false; + } + + memcpy(outBuffer.GetPointer() + byteOffset, &values[0], sizeof(T) * dracoAttribute.num_components()); + byteOffset += sizeof(T) * dracoAttribute.num_components(); + } + + return true; +} + +inline void SetDecodedAttributeBuffer_Draco(const draco::Mesh &dracoMesh, uint32_t dracoAttribId, Accessor &accessor) { + // Create decoded buffer + const draco::PointAttribute *pDracoAttribute = dracoMesh.GetAttributeByUniqueId(dracoAttribId); + if (pDracoAttribute == nullptr) { + throw DeadlyImportError("GLTF: Invalid draco attribute id: ", dracoAttribId); + } + + size_t componentBytes = accessor.GetBytesPerComponent(); + + std::unique_ptr<Buffer> decodedAttribBuffer(new Buffer()); + decodedAttribBuffer->Grow(dracoMesh.num_points() * pDracoAttribute->num_components() * componentBytes); + + switch (accessor.componentType) { + case ComponentType_BYTE: + GetAttributeForAllPoints_Draco<int8_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + break; + case ComponentType_UNSIGNED_BYTE: + GetAttributeForAllPoints_Draco<uint8_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + break; + case ComponentType_SHORT: + GetAttributeForAllPoints_Draco<int16_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + break; + case ComponentType_UNSIGNED_SHORT: + GetAttributeForAllPoints_Draco<uint16_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + break; + case ComponentType_UNSIGNED_INT: + GetAttributeForAllPoints_Draco<uint32_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + break; + case ComponentType_FLOAT: + GetAttributeForAllPoints_Draco<float>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + break; + default: + ai_assert(false); + break; + } + + // Assign this alternate data buffer to the accessor + accessor.decodedBuffer.swap(decodedAttribBuffer); +} + +#endif // ASSIMP_ENABLE_DRACO + +// +// LazyDict methods +// + +template <class T> +inline LazyDict<T>::LazyDict(Asset &asset, const char *dictId, const char *extId) : + mDictId(dictId), + mExtId(extId), + mDict(0), + mAsset(asset) { + asset.mDicts.push_back(this); // register to the list of dictionaries +} + +template <class T> +inline LazyDict<T>::~LazyDict() { + for (size_t i = 0; i < mObjs.size(); ++i) { + delete mObjs[i]; + } +} + +template <class T> +inline void LazyDict<T>::AttachToDocument(Document &doc) { + Value *container = nullptr; + const char *context = nullptr; + + if (mExtId) { + if (Value *exts = FindObject(doc, "extensions")) { + container = FindObjectInContext(*exts, mExtId, "extensions"); + context = mExtId; + } + } else { + container = &doc; + context = "the document"; + } + + if (container) { + mDict = FindArrayInContext(*container, mDictId, context); + } +} + +template <class T> +inline void LazyDict<T>::DetachFromDocument() { + mDict = nullptr; +} + +template <class T> +unsigned int LazyDict<T>::Remove(const char *id) { + id = T::TranslateId(mAsset, id); + + typename IdDict::iterator objIt = mObjsById.find(id); + + if (objIt == mObjsById.end()) { + throw DeadlyExportError("GLTF: Object with id \"" + std::string(id) + "\" is not found"); + } + + const unsigned int index = objIt->second; + + mAsset.mUsedIds[id] = false; + mObjsById.erase(id); + mObjsByOIndex.erase(index); + delete mObjs[index]; + mObjs.erase(mObjs.begin() + index); + + //update index of object in mObjs; + for (unsigned int i = index; i < mObjs.size(); ++i) { + T *obj = mObjs[i]; + + obj->index = i; + } + + for (IdDict::iterator it = mObjsById.begin(); it != mObjsById.end(); ++it) { + if (it->second <= index) { + continue; + } + + mObjsById[it->first] = it->second - 1; + } + + for (Dict::iterator it = mObjsByOIndex.begin(); it != mObjsByOIndex.end(); ++it) { + if (it->second <= index) { + continue; + } + + mObjsByOIndex[it->first] = it->second - 1; + } + + return index; +} + +template <class T> +Ref<T> LazyDict<T>::Retrieve(unsigned int i) { + + typename Dict::iterator it = mObjsByOIndex.find(i); + if (it != mObjsByOIndex.end()) { // already created? + return Ref<T>(mObjs, it->second); + } + + // read it from the JSON object + if (!mDict) { + throw DeadlyImportError("GLTF: Missing section \"", mDictId, "\""); + } + + if (!mDict->IsArray()) { + throw DeadlyImportError("GLTF: Field \"", mDictId, "\" is not an array"); + } + + if (i >= mDict->Size()) { + throw DeadlyImportError("GLTF: Array index ", i, " is out of bounds (", mDict->Size(), ") for \"", mDictId, "\""); + } + + Value &obj = (*mDict)[i]; + + if (!obj.IsObject()) { + throw DeadlyImportError("GLTF: Object at index ", i, " in array \"", mDictId, "\" is not a JSON object"); + } + + if (mRecursiveReferenceCheck.find(i) != mRecursiveReferenceCheck.end()) { + throw DeadlyImportError("GLTF: Object at index ", i, " in array \"", mDictId, "\" has recursive reference to itself"); + } + mRecursiveReferenceCheck.insert(i); + + // Unique ptr prevents memory leak in case of Read throws an exception + auto inst = std::unique_ptr<T>(new T()); + // Try to make this human readable so it can be used in error messages. + inst->id = std::string(mDictId) + "[" + ai_to_string(i) + "]"; + inst->oIndex = i; + ReadMember(obj, "name", inst->name); + inst->Read(obj, mAsset); + inst->ReadExtensions(obj); + inst->ReadExtras(obj); + + Ref<T> result = Add(inst.release()); + mRecursiveReferenceCheck.erase(i); + return result; +} + +template <class T> +Ref<T> LazyDict<T>::Get(unsigned int i) { + return Ref<T>(mObjs, i); +} + +template <class T> +Ref<T> LazyDict<T>::Get(const char *id) { + id = T::TranslateId(mAsset, id); + + typename IdDict::iterator it = mObjsById.find(id); + if (it != mObjsById.end()) { // already created? + return Ref<T>(mObjs, it->second); + } + + return Ref<T>(); +} + +template <class T> +Ref<T> LazyDict<T>::Add(T *obj) { + unsigned int idx = unsigned(mObjs.size()); + mObjs.push_back(obj); + mObjsByOIndex[obj->oIndex] = idx; + mObjsById[obj->id] = idx; + mAsset.mUsedIds[obj->id] = true; + return Ref<T>(mObjs, idx); +} + +template <class T> +Ref<T> LazyDict<T>::Create(const char *id) { + Asset::IdMap::iterator it = mAsset.mUsedIds.find(id); + if (it != mAsset.mUsedIds.end()) { + throw DeadlyImportError("GLTF: two objects with the same ID exist"); + } + T *inst = new T(); + unsigned int idx = unsigned(mObjs.size()); + inst->id = id; + inst->index = idx; + inst->oIndex = idx; + return Add(inst); +} + +// +// glTF dictionary objects methods +// +inline Buffer::Buffer() : + byteLength(0), + type(Type_arraybuffer), + EncodedRegion_Current(nullptr), + mIsSpecial(false) {} + +inline Buffer::~Buffer() { + for (SEncodedRegion *reg : EncodedRegion_List) + delete reg; +} + +inline const char *Buffer::TranslateId(Asset & /*r*/, const char *id) { + return id; +} + +inline void Buffer::Read(Value &obj, Asset &r) { + size_t statedLength = MemberOrDefault<size_t>(obj, "byteLength", 0); + byteLength = statedLength; + + Value *it = FindString(obj, "uri"); + if (!it) { + if (statedLength > 0) { + throw DeadlyImportError("GLTF: buffer with non-zero length missing the \"uri\" attribute"); + } + return; + } + + const char *uri = it->GetString(); + + glTFCommon::Util::DataURI dataURI; + if (ParseDataURI(uri, it->GetStringLength(), dataURI)) { + if (dataURI.base64) { + uint8_t *data = nullptr; + this->byteLength = Base64::Decode(dataURI.data, dataURI.dataLength, data); + this->mData.reset(data, std::default_delete<uint8_t[]>()); + + if (statedLength > 0 && this->byteLength != statedLength) { + throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(statedLength), + " bytes, but found ", ai_to_string(dataURI.dataLength)); + } + } else { // assume raw data + if (statedLength != dataURI.dataLength) { + throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(statedLength), + " bytes, but found ", ai_to_string(dataURI.dataLength)); + } + + this->mData.reset(new uint8_t[dataURI.dataLength], std::default_delete<uint8_t[]>()); + memcpy(this->mData.get(), dataURI.data, dataURI.dataLength); + } + } else { // Local file + if (byteLength > 0) { + std::string dir = !r.mCurrentAssetDir.empty() ? (r.mCurrentAssetDir.back() == '/' ? r.mCurrentAssetDir : r.mCurrentAssetDir + '/') : ""; + + IOStream *file = r.OpenFile(dir + uri, "rb"); + if (file) { + bool ok = LoadFromStream(*file, byteLength); + delete file; + + if (!ok) + throw DeadlyImportError("GLTF: error while reading referenced file \"", uri, "\""); + } else { + throw DeadlyImportError("GLTF: could not open referenced file \"", uri, "\""); + } + } + } +} + +inline bool Buffer::LoadFromStream(IOStream &stream, size_t length, size_t baseOffset) { + byteLength = length ? length : stream.FileSize(); + + if (byteLength > stream.FileSize()) { + throw DeadlyImportError("GLTF: Invalid byteLength exceeds size of actual data."); + } + + if (baseOffset) { + stream.Seek(baseOffset, aiOrigin_SET); + } + + mData.reset(new uint8_t[byteLength], std::default_delete<uint8_t[]>()); + + if (stream.Read(mData.get(), byteLength, 1) != 1) { + return false; + } + return true; +} + +inline void Buffer::EncodedRegion_Mark(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID) { + // Check pointer to data + if (pDecodedData == nullptr) throw DeadlyImportError("GLTF: for marking encoded region pointer to decoded data must be provided."); + + // Check offset + if (pOffset > byteLength) { + const uint8_t val_size = 32; + + char val[val_size]; + + ai_snprintf(val, val_size, AI_SIZEFMT, pOffset); + throw DeadlyImportError("GLTF: incorrect offset value (", val, ") for marking encoded region."); + } + + // Check length + if ((pOffset + pEncodedData_Length) > byteLength) { + const uint8_t val_size = 64; + + char val[val_size]; + + ai_snprintf(val, val_size, AI_SIZEFMT "/" AI_SIZEFMT, pOffset, pEncodedData_Length); + throw DeadlyImportError("GLTF: encoded region with offset/length (", val, ") is out of range."); + } + + // Add new region + EncodedRegion_List.push_back(new SEncodedRegion(pOffset, pEncodedData_Length, pDecodedData, pDecodedData_Length, pID)); + // And set new value for "byteLength" + byteLength += (pDecodedData_Length - pEncodedData_Length); +} + +inline void Buffer::EncodedRegion_SetCurrent(const std::string &pID) { + if ((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) { + return; + } + + for (SEncodedRegion *reg : EncodedRegion_List) { + if (reg->ID == pID) { + EncodedRegion_Current = reg; + return; + } + } + + throw DeadlyImportError("GLTF: EncodedRegion with ID: \"", pID, "\" not found."); +} + +inline bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count) { + + if ((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) { + return false; + } + + const size_t new_data_size = byteLength + pReplace_Count - pBufferData_Count; + uint8_t *new_data = new uint8_t[new_data_size]; + // Copy data which place before replacing part. + ::memcpy(new_data, mData.get(), pBufferData_Offset); + // Copy new data. + ::memcpy(&new_data[pBufferData_Offset], pReplace_Data, pReplace_Count); + // Copy data which place after replacing part. + ::memcpy(&new_data[pBufferData_Offset + pReplace_Count], &mData.get()[pBufferData_Offset + pBufferData_Count], pBufferData_Offset); + // Apply new data + mData.reset(new_data, std::default_delete<uint8_t[]>()); + byteLength = new_data_size; + + return true; +} + +inline bool Buffer::ReplaceData_joint(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count) { + if ((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) { + return false; + } + + const size_t new_data_size = byteLength + pReplace_Count - pBufferData_Count; + uint8_t *new_data = new uint8_t[new_data_size]; + // Copy data which place before replacing part. + memcpy(new_data, mData.get(), pBufferData_Offset); + // Copy new data. + memcpy(&new_data[pBufferData_Offset], pReplace_Data, pReplace_Count); + // Copy data which place after replacing part. + memcpy(&new_data[pBufferData_Offset + pReplace_Count], &mData.get()[pBufferData_Offset + pBufferData_Count], new_data_size - (pBufferData_Offset + pReplace_Count)); + // Apply new data + mData.reset(new_data, std::default_delete<uint8_t[]>()); + byteLength = new_data_size; + + return true; +} + +inline size_t Buffer::AppendData(uint8_t *data, size_t length) { + const size_t offset = this->byteLength; + + // Force alignment to 4 bits + const size_t paddedLength = (length + 3) & ~3; + Grow(paddedLength); + memcpy(mData.get() + offset, data, length); + memset(mData.get() + offset + length, 0, paddedLength - length); + return offset; +} + +inline void Buffer::Grow(size_t amount) { + if (amount <= 0) { + return; + } + + // Capacity is big enough + if (capacity >= byteLength + amount) { + byteLength += amount; + return; + } + + // Just allocate data which we need + capacity = byteLength + amount; + + uint8_t *b = new uint8_t[capacity]; + if (nullptr != mData) { + memcpy(b, mData.get(), byteLength); + } + mData.reset(b, std::default_delete<uint8_t[]>()); + byteLength += amount; +} + +// +// struct BufferView +// +inline void BufferView::Read(Value &obj, Asset &r) { + if (Value *bufferVal = FindUInt(obj, "buffer")) { + buffer = r.buffers.Retrieve(bufferVal->GetUint()); + } + + if (!buffer) { + throw DeadlyImportError("GLTF: Buffer view without valid buffer."); + } + + byteOffset = MemberOrDefault(obj, "byteOffset", size_t(0)); + byteLength = MemberOrDefault(obj, "byteLength", size_t(0)); + byteStride = MemberOrDefault(obj, "byteStride", 0u); + + // Check length + if ((byteOffset + byteLength) > buffer->byteLength) { + throw DeadlyImportError("GLTF: Buffer view with offset/length (", byteOffset, "/", byteLength, ") is out of range."); + } +} + +inline uint8_t *BufferView::GetPointer(size_t accOffset) { + if (!buffer) { + return nullptr; + } + uint8_t *basePtr = buffer->GetPointer(); + if (!basePtr) { + return nullptr; + } + + size_t offset = accOffset + byteOffset; + if (buffer->EncodedRegion_Current != nullptr) { + const size_t begin = buffer->EncodedRegion_Current->Offset; + const size_t end = begin + buffer->EncodedRegion_Current->DecodedData_Length; + if ((offset >= begin) && (offset < end)) { + return &buffer->EncodedRegion_Current->DecodedData[offset - begin]; + } + } + + return basePtr + offset; +} + +// +// struct Accessor +// +inline void Accessor::Sparse::PopulateData(size_t numBytes, uint8_t *bytes) { + if (bytes) { + data.assign(bytes, bytes + numBytes); + } else { + data.resize(numBytes, 0x00); + } +} + +inline void Accessor::Sparse::PatchData(unsigned int elementSize) { + uint8_t *pIndices = indices->GetPointer(indicesByteOffset); + const unsigned int indexSize = int(ComponentTypeSize(indicesType)); + uint8_t *indicesEnd = pIndices + count * indexSize; + + uint8_t *pValues = values->GetPointer(valuesByteOffset); + while (pIndices != indicesEnd) { + size_t offset; + switch (indicesType) { + case ComponentType_UNSIGNED_BYTE: + offset = *pIndices; + break; + case ComponentType_UNSIGNED_SHORT: + offset = *reinterpret_cast<uint16_t *>(pIndices); + break; + case ComponentType_UNSIGNED_INT: + offset = *reinterpret_cast<uint32_t *>(pIndices); + break; + default: + // have fun with float and negative values from signed types as indices. + throw DeadlyImportError("Unsupported component type in index."); + } + + offset *= elementSize; + + if (offset + elementSize > data.size()) { + throw DeadlyImportError("Invalid sparse accessor. Byte offset for patching points outside allocated memory."); + } + + std::memcpy(data.data() + offset, pValues, elementSize); + + pValues += elementSize; + pIndices += indexSize; + } +} + +inline void Accessor::Read(Value &obj, Asset &r) { + if (Value *bufferViewVal = FindUInt(obj, "bufferView")) { + bufferView = r.bufferViews.Retrieve(bufferViewVal->GetUint()); + } + + byteOffset = MemberOrDefault(obj, "byteOffset", size_t(0)); + componentType = MemberOrDefault(obj, "componentType", ComponentType_BYTE); + { + const Value *countValue = FindUInt(obj, "count"); + if (!countValue) { + throw DeadlyImportError("A count value is required, when reading ", id.c_str(), name.empty() ? "" : " (" + name + ")"); + } + count = countValue->GetUint(); + } + + const char *typestr; + type = ReadMember(obj, "type", typestr) ? AttribType::FromString(typestr) : AttribType::SCALAR; + + if (bufferView) { + // Check length + unsigned long long byteLength = (unsigned long long)GetBytesPerComponent() * (unsigned long long)count; + + // handle integer overflow + if (byteLength < count) { + throw DeadlyImportError("GLTF: Accessor with offset/count (", byteOffset, "/", count, ") is out of range."); + } + + if ((byteOffset + byteLength) > bufferView->byteLength || (bufferView->byteOffset + byteOffset + byteLength) > bufferView->buffer->byteLength) { + throw DeadlyImportError("GLTF: Accessor with offset/length (", byteOffset, "/", byteLength, ") is out of range."); + } + } + + if (Value *sparseValue = FindObject(obj, "sparse")) { + sparse.reset(new Sparse); + // count + ReadMember(*sparseValue, "count", sparse->count); + + // indices + if (Value *indicesValue = FindObject(*sparseValue, "indices")) { + //indices bufferView + Value *indiceViewID = FindUInt(*indicesValue, "bufferView"); + sparse->indices = r.bufferViews.Retrieve(indiceViewID->GetUint()); + //indices byteOffset + sparse->indicesByteOffset = MemberOrDefault(*indicesValue, "byteOffset", size_t(0)); + //indices componentType + sparse->indicesType = MemberOrDefault(*indicesValue, "componentType", ComponentType_BYTE); + //sparse->indices->Read(*indicesValue, r); + } else { + // indicesType + sparse->indicesType = MemberOrDefault(*sparseValue, "componentType", ComponentType_UNSIGNED_SHORT); + } + + // value + if (Value *valuesValue = FindObject(*sparseValue, "values")) { + //value bufferView + Value *valueViewID = FindUInt(*valuesValue, "bufferView"); + sparse->values = r.bufferViews.Retrieve(valueViewID->GetUint()); + //value byteOffset + sparse->valuesByteOffset = MemberOrDefault(*valuesValue, "byteOffset", size_t(0)); + //sparse->values->Read(*valuesValue, r); + } + + + const unsigned int elementSize = GetElementSize(); + const size_t dataSize = count * elementSize; + sparse->PopulateData(dataSize, bufferView ? bufferView->GetPointer(byteOffset) : 0); + sparse->PatchData(elementSize); + } +} + +inline unsigned int Accessor::GetNumComponents() { + return AttribType::GetNumComponents(type); +} + +inline unsigned int Accessor::GetBytesPerComponent() { + return int(ComponentTypeSize(componentType)); +} + +inline unsigned int Accessor::GetElementSize() { + return GetNumComponents() * GetBytesPerComponent(); +} + +inline uint8_t *Accessor::GetPointer() { + if (decodedBuffer) + return decodedBuffer->GetPointer(); + + if (sparse) + return sparse->data.data(); + + if (!bufferView || !bufferView->buffer) return nullptr; + uint8_t *basePtr = bufferView->buffer->GetPointer(); + if (!basePtr) return nullptr; + + size_t offset = byteOffset + bufferView->byteOffset; + + // Check if region is encoded. + if (bufferView->buffer->EncodedRegion_Current != nullptr) { + const size_t begin = bufferView->buffer->EncodedRegion_Current->Offset; + const size_t end = begin + bufferView->buffer->EncodedRegion_Current->DecodedData_Length; + + if ((offset >= begin) && (offset < end)) + return &bufferView->buffer->EncodedRegion_Current->DecodedData[offset - begin]; + } + + return basePtr + offset; +} + +inline size_t Accessor::GetStride() { + // Decoded buffer is always packed + if (decodedBuffer) + return GetElementSize(); + + // Sparse and normal bufferView + return (bufferView && bufferView->byteStride ? bufferView->byteStride : GetElementSize()); +} + +inline size_t Accessor::GetMaxByteSize() { + if (decodedBuffer) + return decodedBuffer->byteLength; + + return (bufferView ? bufferView->byteLength : sparse->data.size()); +} + +template <class T> +void Accessor::ExtractData(T *&outData) { + uint8_t *data = GetPointer(); + if (!data) { + throw DeadlyImportError("GLTF2: data is null when extracting data from ", getContextForErrorMessages(id, name)); + } + + const size_t elemSize = GetElementSize(); + const size_t totalSize = elemSize * count; + + const size_t stride = GetStride(); + + const size_t targetElemSize = sizeof(T); + + if (elemSize > targetElemSize) { + throw DeadlyImportError("GLTF: elemSize ", elemSize, " > targetElemSize ", targetElemSize, " in ", getContextForErrorMessages(id, name)); + } + + const size_t maxSize = GetMaxByteSize(); + if (count * stride > maxSize) { + throw DeadlyImportError("GLTF: count*stride ", (count * stride), " > maxSize ", maxSize, " in ", getContextForErrorMessages(id, name)); + } + + outData = new T[count]; + if (stride == elemSize && targetElemSize == elemSize) { + memcpy(outData, data, totalSize); + } else { + for (size_t i = 0; i < count; ++i) { + memcpy(outData + i, data + i * stride, elemSize); + } + } +} + +inline void Accessor::WriteData(size_t _count, const void *src_buffer, size_t src_stride) { + uint8_t *buffer_ptr = bufferView->buffer->GetPointer(); + size_t offset = byteOffset + bufferView->byteOffset; + + size_t dst_stride = GetNumComponents() * GetBytesPerComponent(); + + const uint8_t *src = reinterpret_cast<const uint8_t *>(src_buffer); + uint8_t *dst = reinterpret_cast<uint8_t *>(buffer_ptr + offset); + + ai_assert(dst + _count * dst_stride <= buffer_ptr + bufferView->buffer->byteLength); + CopyData(_count, src, src_stride, dst, dst_stride); +} + +inline void Accessor::WriteSparseValues(size_t _count, const void *src_data, size_t src_dataStride) { + if (!sparse) + return; + + // values + uint8_t *value_buffer_ptr = sparse->values->buffer->GetPointer(); + size_t value_offset = sparse->valuesByteOffset + sparse->values->byteOffset; + size_t value_dst_stride = GetNumComponents() * GetBytesPerComponent(); + const uint8_t *value_src = reinterpret_cast<const uint8_t *>(src_data); + uint8_t *value_dst = reinterpret_cast<uint8_t *>(value_buffer_ptr + value_offset); + ai_assert(value_dst + _count * value_dst_stride <= value_buffer_ptr + sparse->values->buffer->byteLength); + CopyData(_count, value_src, src_dataStride, value_dst, value_dst_stride); +} + +inline void Accessor::WriteSparseIndices(size_t _count, const void *src_idx, size_t src_idxStride) { + if (!sparse) + return; + + // indices + uint8_t *indices_buffer_ptr = sparse->indices->buffer->GetPointer(); + size_t indices_offset = sparse->indicesByteOffset + sparse->indices->byteOffset; + size_t indices_dst_stride = 1 * sizeof(unsigned short); + const uint8_t *indices_src = reinterpret_cast<const uint8_t *>(src_idx); + uint8_t *indices_dst = reinterpret_cast<uint8_t *>(indices_buffer_ptr + indices_offset); + ai_assert(indices_dst + _count * indices_dst_stride <= indices_buffer_ptr + sparse->indices->buffer->byteLength); + CopyData(_count, indices_src, src_idxStride, indices_dst, indices_dst_stride); +} + +inline Accessor::Indexer::Indexer(Accessor &acc) : + accessor(acc), + data(acc.GetPointer()), + elemSize(acc.GetElementSize()), + stride(acc.GetStride()) { +} + +//! Accesses the i-th value as defined by the accessor +template <class T> +T Accessor::Indexer::GetValue(int i) { + ai_assert(data); + if (i * stride >= accessor.GetMaxByteSize()) { + throw DeadlyImportError("GLTF: Invalid index ", i, ", count out of range for buffer with stride ", stride, " and size ", accessor.GetMaxByteSize(), "."); + } + // Ensure that the memcpy doesn't overwrite the local. + const size_t sizeToCopy = std::min(elemSize, sizeof(T)); + T value = T(); + // Assume platform endianness matches GLTF binary data (which is little-endian). + memcpy(&value, data + i * stride, sizeToCopy); + return value; +} + +inline Image::Image() : + width(0), + height(0), + mDataLength(0) { +} + +inline void Image::Read(Value &obj, Asset &r) { + //basisu: no need to handle .ktx2, .basis, load as is + if (!mDataLength) { + Value *curUri = FindString(obj, "uri"); + if (nullptr != curUri) { + const char *uristr = curUri->GetString(); + + glTFCommon::Util::DataURI dataURI; + if (ParseDataURI(uristr, curUri->GetStringLength(), dataURI)) { + mimeType = dataURI.mediaType; + if (dataURI.base64) { + uint8_t *ptr = nullptr; + mDataLength = Base64::Decode(dataURI.data, dataURI.dataLength, ptr); + mData.reset(ptr); + } + } else { + this->uri = uristr; + } + } else if (Value *bufferViewVal = FindUInt(obj, "bufferView")) { + this->bufferView = r.bufferViews.Retrieve(bufferViewVal->GetUint()); + if (Value *mtype = FindString(obj, "mimeType")) { + this->mimeType = mtype->GetString(); + } + if (!this->bufferView || this->mimeType.empty()) { + throw DeadlyImportError("GLTF2: ", getContextForErrorMessages(id, name), " does not have a URI, so it must have a valid bufferView and mimetype"); + } + + Ref<Buffer> buffer = this->bufferView->buffer; + + this->mDataLength = this->bufferView->byteLength; + // maybe this memcpy could be avoided if aiTexture does not delete[] pcData at destruction. + + this->mData.reset(new uint8_t[this->mDataLength]); + memcpy(this->mData.get(), buffer->GetPointer() + this->bufferView->byteOffset, this->mDataLength); + } else { + throw DeadlyImportError("GLTF2: ", getContextForErrorMessages(id, name), " should have either a URI of a bufferView and mimetype"); + } + } +} + +inline uint8_t *Image::StealData() { + mDataLength = 0; + return mData.release(); +} + +// Never take over the ownership of data whenever binary or not +inline void Image::SetData(uint8_t *data, size_t length, Asset &r) { + Ref<Buffer> b = r.GetBodyBuffer(); + if (b) { // binary file: append to body + std::string bvId = r.FindUniqueID(this->id, "imgdata"); + bufferView = r.bufferViews.Create(bvId); + + bufferView->buffer = b; + bufferView->byteLength = length; + bufferView->byteOffset = b->AppendData(data, length); + } else { // text file: will be stored as a data uri + uint8_t *temp = new uint8_t[length]; + memcpy(temp, data, length); + this->mData.reset(temp); + this->mDataLength = length; + } +} + +inline void Sampler::Read(Value &obj, Asset & /*r*/) { + SetDefaults(); + + ReadMember(obj, "name", name); + ReadMember(obj, "magFilter", magFilter); + ReadMember(obj, "minFilter", minFilter); + ReadMember(obj, "wrapS", wrapS); + ReadMember(obj, "wrapT", wrapT); +} + +inline void Sampler::SetDefaults() { + //only wrapping modes have defaults + wrapS = SamplerWrap::Repeat; + wrapT = SamplerWrap::Repeat; + magFilter = SamplerMagFilter::UNSET; + minFilter = SamplerMinFilter::UNSET; +} + +inline void Texture::Read(Value &obj, Asset &r) { + if (Value *sourceVal = FindUInt(obj, "source")) { + source = r.images.Retrieve(sourceVal->GetUint()); + } + + if (Value *samplerVal = FindUInt(obj, "sampler")) { + sampler = r.samplers.Retrieve(samplerVal->GetUint()); + } +} + +void Material::SetTextureProperties(Asset &r, Value *prop, TextureInfo &out) { + if (r.extensionsUsed.KHR_texture_transform) { + if (Value *pKHR_texture_transform = FindExtension(*prop, "KHR_texture_transform")) { + out.textureTransformSupported = true; + if (Value *array = FindArray(*pKHR_texture_transform, "offset")) { + out.TextureTransformExt_t.offset[0] = (*array)[0].GetFloat(); + out.TextureTransformExt_t.offset[1] = (*array)[1].GetFloat(); + } else { + out.TextureTransformExt_t.offset[0] = 0; + out.TextureTransformExt_t.offset[1] = 0; + } + + if (!ReadMember(*pKHR_texture_transform, "rotation", out.TextureTransformExt_t.rotation)) { + out.TextureTransformExt_t.rotation = 0; + } + + if (Value *array = FindArray(*pKHR_texture_transform, "scale")) { + out.TextureTransformExt_t.scale[0] = (*array)[0].GetFloat(); + out.TextureTransformExt_t.scale[1] = (*array)[1].GetFloat(); + } else { + out.TextureTransformExt_t.scale[0] = 1; + out.TextureTransformExt_t.scale[1] = 1; + } + } + } + + if (Value *indexProp = FindUInt(*prop, "index")) { + out.texture = r.textures.Retrieve(indexProp->GetUint()); + } + + if (Value *texcoord = FindUInt(*prop, "texCoord")) { + out.texCoord = texcoord->GetUint(); + } +} + +inline void Material::ReadTextureProperty(Asset &r, Value &vals, const char *propName, TextureInfo &out) { + if (Value *prop = FindMember(vals, propName)) { + SetTextureProperties(r, prop, out); + } +} + +inline void Material::ReadTextureProperty(Asset &r, Value &vals, const char *propName, NormalTextureInfo &out) { + if (Value *prop = FindMember(vals, propName)) { + SetTextureProperties(r, prop, out); + + if (Value *scale = FindNumber(*prop, "scale")) { + out.scale = static_cast<float>(scale->GetDouble()); + } + } +} + +inline void Material::ReadTextureProperty(Asset &r, Value &vals, const char *propName, OcclusionTextureInfo &out) { + if (Value *prop = FindMember(vals, propName)) { + SetTextureProperties(r, prop, out); + + if (Value *strength = FindNumber(*prop, "strength")) { + out.strength = static_cast<float>(strength->GetDouble()); + } + } +} + +inline void Material::Read(Value &material, Asset &r) { + SetDefaults(); + + if (Value *curPbrMetallicRoughness = FindObject(material, "pbrMetallicRoughness")) { + ReadMember(*curPbrMetallicRoughness, "baseColorFactor", this->pbrMetallicRoughness.baseColorFactor); + ReadTextureProperty(r, *curPbrMetallicRoughness, "baseColorTexture", this->pbrMetallicRoughness.baseColorTexture); + ReadTextureProperty(r, *curPbrMetallicRoughness, "metallicRoughnessTexture", this->pbrMetallicRoughness.metallicRoughnessTexture); + ReadMember(*curPbrMetallicRoughness, "metallicFactor", this->pbrMetallicRoughness.metallicFactor); + ReadMember(*curPbrMetallicRoughness, "roughnessFactor", this->pbrMetallicRoughness.roughnessFactor); + } + + ReadTextureProperty(r, material, "normalTexture", this->normalTexture); + ReadTextureProperty(r, material, "occlusionTexture", this->occlusionTexture); + ReadTextureProperty(r, material, "emissiveTexture", this->emissiveTexture); + ReadMember(material, "emissiveFactor", this->emissiveFactor); + + ReadMember(material, "doubleSided", this->doubleSided); + ReadMember(material, "alphaMode", this->alphaMode); + ReadMember(material, "alphaCutoff", this->alphaCutoff); + + if (Value *extensions = FindObject(material, "extensions")) { + if (r.extensionsUsed.KHR_materials_pbrSpecularGlossiness) { + if (Value *curPbrSpecularGlossiness = FindObject(*extensions, "KHR_materials_pbrSpecularGlossiness")) { + PbrSpecularGlossiness pbrSG; + + ReadMember(*curPbrSpecularGlossiness, "diffuseFactor", pbrSG.diffuseFactor); + ReadTextureProperty(r, *curPbrSpecularGlossiness, "diffuseTexture", pbrSG.diffuseTexture); + ReadTextureProperty(r, *curPbrSpecularGlossiness, "specularGlossinessTexture", pbrSG.specularGlossinessTexture); + ReadMember(*curPbrSpecularGlossiness, "specularFactor", pbrSG.specularFactor); + ReadMember(*curPbrSpecularGlossiness, "glossinessFactor", pbrSG.glossinessFactor); + + this->pbrSpecularGlossiness = Nullable<PbrSpecularGlossiness>(pbrSG); + } + } + + // Extension KHR_texture_transform is handled in ReadTextureProperty + + if (r.extensionsUsed.KHR_materials_sheen) { + if (Value *curMaterialSheen = FindObject(*extensions, "KHR_materials_sheen")) { + MaterialSheen sheen; + + ReadMember(*curMaterialSheen, "sheenColorFactor", sheen.sheenColorFactor); + ReadTextureProperty(r, *curMaterialSheen, "sheenColorTexture", sheen.sheenColorTexture); + ReadMember(*curMaterialSheen, "sheenRoughnessFactor", sheen.sheenRoughnessFactor); + ReadTextureProperty(r, *curMaterialSheen, "sheenRoughnessTexture", sheen.sheenRoughnessTexture); + + this->materialSheen = Nullable<MaterialSheen>(sheen); + } + } + + if (r.extensionsUsed.KHR_materials_clearcoat) { + if (Value *curMaterialClearcoat = FindObject(*extensions, "KHR_materials_clearcoat")) { + MaterialClearcoat clearcoat; + + ReadMember(*curMaterialClearcoat, "clearcoatFactor", clearcoat.clearcoatFactor); + ReadTextureProperty(r, *curMaterialClearcoat, "clearcoatTexture", clearcoat.clearcoatTexture); + ReadMember(*curMaterialClearcoat, "clearcoatRoughnessFactor", clearcoat.clearcoatRoughnessFactor); + ReadTextureProperty(r, *curMaterialClearcoat, "clearcoatRoughnessTexture", clearcoat.clearcoatRoughnessTexture); + ReadTextureProperty(r, *curMaterialClearcoat, "clearcoatNormalTexture", clearcoat.clearcoatNormalTexture); + + this->materialClearcoat = Nullable<MaterialClearcoat>(clearcoat); + } + } + + if (r.extensionsUsed.KHR_materials_transmission) { + if (Value *curMaterialTransmission = FindObject(*extensions, "KHR_materials_transmission")) { + MaterialTransmission transmission; + + ReadMember(*curMaterialTransmission, "transmissionFactor", transmission.transmissionFactor); + ReadTextureProperty(r, *curMaterialTransmission, "transmissionTexture", transmission.transmissionTexture); + + this->materialTransmission = Nullable<MaterialTransmission>(transmission); + } + } + + if (r.extensionsUsed.KHR_materials_volume) { + if (Value *curMaterialVolume = FindObject(*extensions, "KHR_materials_volume")) { + MaterialVolume volume; + + ReadMember(*curMaterialVolume, "thicknessFactor", volume.thicknessFactor); + ReadTextureProperty(r, *curMaterialVolume, "thicknessTexture", volume.thicknessTexture); + ReadMember(*curMaterialVolume, "attenuationDistance", volume.attenuationDistance); + ReadMember(*curMaterialVolume, "attenuationColor", volume.attenuationColor); + + this->materialVolume = Nullable<MaterialVolume>(volume); + } + } + + if (r.extensionsUsed.KHR_materials_ior) { + if (Value *curMaterialIOR = FindObject(*extensions, "KHR_materials_ior")) { + MaterialIOR ior; + + ReadMember(*curMaterialIOR, "ior", ior.ior); + + this->materialIOR = Nullable<MaterialIOR>(ior); + } + } + + unlit = nullptr != FindObject(*extensions, "KHR_materials_unlit"); + } +} + +inline void Material::SetDefaults() { + //pbr materials + SetVector(pbrMetallicRoughness.baseColorFactor, defaultBaseColor); + pbrMetallicRoughness.metallicFactor = 1.0f; + pbrMetallicRoughness.roughnessFactor = 1.0f; + + SetVector(emissiveFactor, defaultEmissiveFactor); + alphaMode = "OPAQUE"; + alphaCutoff = 0.5f; + doubleSided = false; + unlit = false; +} + +inline void PbrSpecularGlossiness::SetDefaults() { + //pbrSpecularGlossiness properties + SetVector(diffuseFactor, defaultDiffuseFactor); + SetVector(specularFactor, defaultSpecularFactor); + glossinessFactor = 1.0f; +} + +inline void MaterialSheen::SetDefaults() { + //KHR_materials_sheen properties + SetVector(sheenColorFactor, defaultSheenFactor); + sheenRoughnessFactor = 0.f; +} + +inline void MaterialVolume::SetDefaults() { + //KHR_materials_volume properties + thicknessFactor = 0.f; + attenuationDistance = INFINITY; + SetVector(attenuationColor, defaultAttenuationColor); +} + +inline void MaterialIOR::SetDefaults() { + //KHR_materials_ior properties + ior = 1.5f; +} + +inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { + Value *curName = FindMember(pJSON_Object, "name"); + if (nullptr != curName && curName->IsString()) { + name = curName->GetString(); + } + + /****************** Mesh primitives ******************/ + Value *curPrimitives = FindArray(pJSON_Object, "primitives"); + if (nullptr != curPrimitives) { + this->primitives.resize(curPrimitives->Size()); + for (unsigned int i = 0; i < curPrimitives->Size(); ++i) { + Value &primitive = (*curPrimitives)[i]; + + Primitive &prim = this->primitives[i]; + prim.mode = MemberOrDefault(primitive, "mode", PrimitiveMode_TRIANGLES); + + if (Value *indices = FindUInt(primitive, "indices")) { + prim.indices = pAsset_Root.accessors.Retrieve(indices->GetUint()); + } + + if (Value *material = FindUInt(primitive, "material")) { + prim.material = pAsset_Root.materials.Retrieve(material->GetUint()); + } + + if (Value *attrs = FindObject(primitive, "attributes")) { + for (Value::MemberIterator it = attrs->MemberBegin(); it != attrs->MemberEnd(); ++it) { + if (!it->value.IsUint()) continue; + const char *attr = it->name.GetString(); + // Valid attribute semantics include POSITION, NORMAL, TANGENT, TEXCOORD, COLOR, JOINT, JOINTMATRIX, + // and WEIGHT.Attribute semantics can be of the form[semantic]_[set_index], e.g., TEXCOORD_0, TEXCOORD_1, etc. + + int undPos = 0; + Mesh::AccessorList *vec = nullptr; + if (GetAttribVector(prim, attr, vec, undPos)) { + size_t idx = (attr[undPos] == '_') ? atoi(attr + undPos + 1) : 0; + if ((*vec).size() != idx) { + throw DeadlyImportError("GLTF: Invalid attribute in mesh: ", name, " primitive: ", i, "attrib: ", attr, + ". All indices for indexed attribute semantics must start with 0 and be continuous positive integers: TEXCOORD_0, TEXCOORD_1, etc."); + } + (*vec).resize(idx + 1); + (*vec)[idx] = pAsset_Root.accessors.Retrieve(it->value.GetUint()); + } + } + } + +#ifdef ASSIMP_ENABLE_DRACO + // KHR_draco_mesh_compression spec: Draco can only be used for glTF Triangles or Triangle Strips + if (pAsset_Root.extensionsUsed.KHR_draco_mesh_compression && (prim.mode == PrimitiveMode_TRIANGLES || prim.mode == PrimitiveMode_TRIANGLE_STRIP)) { + // Look for draco mesh compression extension and bufferView + // Skip if any missing + if (Value *dracoExt = FindExtension(primitive, "KHR_draco_mesh_compression")) { + if (Value *bufView = FindUInt(*dracoExt, "bufferView")) { + // Attempt to load indices and attributes using draco compression + auto bufferView = pAsset_Root.bufferViews.Retrieve(bufView->GetUint()); + // Attempt to perform the draco decode on the buffer data + const char *bufferViewData = reinterpret_cast<const char *>(bufferView->buffer->GetPointer() + bufferView->byteOffset); + draco::DecoderBuffer decoderBuffer; + decoderBuffer.Init(bufferViewData, bufferView->byteLength); + draco::Decoder decoder; + auto decodeResult = decoder.DecodeMeshFromBuffer(&decoderBuffer); + if (!decodeResult.ok()) { + // A corrupt Draco isn't actually fatal if the primitive data is also provided in a standard buffer, but does anyone do that? + throw DeadlyImportError("GLTF: Invalid Draco mesh compression in mesh: ", name, " primitive: ", i, ": ", decodeResult.status().error_msg_string()); + } + + // Now we have a draco mesh + const std::unique_ptr<draco::Mesh> &pDracoMesh = decodeResult.value(); + + // Redirect the accessors to the decoded data + + // Indices + SetDecodedIndexBuffer_Draco(*pDracoMesh, prim); + + // Vertex attributes + if (Value *attrs = FindObject(*dracoExt, "attributes")) { + for (Value::MemberIterator it = attrs->MemberBegin(); it != attrs->MemberEnd(); ++it) { + if (!it->value.IsUint()) continue; + const char *attr = it->name.GetString(); + + int undPos = 0; + Mesh::AccessorList *vec = nullptr; + if (GetAttribVector(prim, attr, vec, undPos)) { + size_t idx = (attr[undPos] == '_') ? atoi(attr + undPos + 1) : 0; + if (idx >= (*vec).size()) { + throw DeadlyImportError("GLTF: Invalid draco attribute in mesh: ", name, " primitive: ", i, " attrib: ", attr, + ". All indices for indexed attribute semantics must start with 0 and be continuous positive integers: TEXCOORD_0, TEXCOORD_1, etc."); + } + + if (!(*vec)[idx]) { + throw DeadlyImportError("GLTF: Invalid draco attribute in mesh: ", name, " primitive: ", i, " attrib: ", attr, + ". All draco-encoded attributes must also define an accessor."); + } + + Accessor &attribAccessor = *(*vec)[idx]; + if (attribAccessor.count == 0) + throw DeadlyImportError("GLTF: Invalid draco attribute in mesh: ", name, " primitive: ", i, " attrib: ", attr); + + // Redirect this accessor to the appropriate Draco vertex attribute data + const uint32_t dracoAttribId = it->value.GetUint(); + SetDecodedAttributeBuffer_Draco(*pDracoMesh, dracoAttribId, attribAccessor); + } + } + } + } + } + } +#endif + + Value *targetsArray = FindArray(primitive, "targets"); + if (nullptr != targetsArray) { + prim.targets.resize(targetsArray->Size()); + for (unsigned int j = 0; j < targetsArray->Size(); ++j) { + Value &target = (*targetsArray)[j]; + if (!target.IsObject()) { + continue; + } + for (Value::MemberIterator it = target.MemberBegin(); it != target.MemberEnd(); ++it) { + if (!it->value.IsUint()) { + continue; + } + const char *attr = it->name.GetString(); + // Valid attribute semantics include POSITION, NORMAL, TANGENT + int undPos = 0; + Mesh::AccessorList *vec = nullptr; + if (GetAttribTargetVector(prim, j, attr, vec, undPos)) { + size_t idx = (attr[undPos] == '_') ? atoi(attr + undPos + 1) : 0; + if ((*vec).size() <= idx) { + (*vec).resize(idx + 1); + } + (*vec)[idx] = pAsset_Root.accessors.Retrieve(it->value.GetUint()); + } + } + } + } + } + } + + Value *curWeights = FindArray(pJSON_Object, "weights"); + if (nullptr != curWeights) { + this->weights.resize(curWeights->Size()); + for (unsigned int i = 0; i < curWeights->Size(); ++i) { + Value &weightValue = (*curWeights)[i]; + if (weightValue.IsNumber()) { + this->weights[i] = weightValue.GetFloat(); + } + } + } + + Value *curExtras = FindObject(pJSON_Object, "extras"); + if (nullptr != curExtras) { + if (Value *curTargetNames = FindArray(*curExtras, "targetNames")) { + this->targetNames.resize(curTargetNames->Size()); + for (unsigned int i = 0; i < curTargetNames->Size(); ++i) { + Value &targetNameValue = (*curTargetNames)[i]; + if (targetNameValue.IsString()) { + this->targetNames[i] = targetNameValue.GetString(); + } + } + } + } +} + +inline void Camera::Read(Value &obj, Asset & /*r*/) { + std::string type_string = std::string(MemberOrDefault(obj, "type", "perspective")); + if (type_string == "orthographic") { + type = Camera::Orthographic; + } else { + type = Camera::Perspective; + } + + const char *subobjId = (type == Camera::Orthographic) ? "orthographic" : "perspective"; + + Value *it = FindObject(obj, subobjId); + if (!it) throw DeadlyImportError("GLTF: Camera missing its parameters"); + + if (type == Camera::Perspective) { + cameraProperties.perspective.aspectRatio = MemberOrDefault(*it, "aspectRatio", 0.f); + cameraProperties.perspective.yfov = MemberOrDefault(*it, "yfov", 3.1415f / 2.f); + cameraProperties.perspective.zfar = MemberOrDefault(*it, "zfar", 100.f); + cameraProperties.perspective.znear = MemberOrDefault(*it, "znear", 0.01f); + } else { + cameraProperties.ortographic.xmag = MemberOrDefault(*it, "xmag", 1.f); + cameraProperties.ortographic.ymag = MemberOrDefault(*it, "ymag", 1.f); + cameraProperties.ortographic.zfar = MemberOrDefault(*it, "zfar", 100.f); + cameraProperties.ortographic.znear = MemberOrDefault(*it, "znear", 0.01f); + } +} + +inline void Light::Read(Value &obj, Asset & /*r*/) { +#ifndef M_PI + const float M_PI = 3.14159265358979323846f; +#endif + + std::string type_string; + ReadMember(obj, "type", type_string); + if (type_string == "directional") + type = Light::Directional; + else if (type_string == "point") + type = Light::Point; + else + type = Light::Spot; + + name = MemberOrDefault(obj, "name", ""); + + SetVector(color, vec3{ 1.0f, 1.0f, 1.0f }); + ReadMember(obj, "color", color); + + intensity = MemberOrDefault(obj, "intensity", 1.0f); + + ReadMember(obj, "range", range); + + if (type == Light::Spot) { + Value *spot = FindObject(obj, "spot"); + if (!spot) throw DeadlyImportError("GLTF: Light missing its spot parameters"); + innerConeAngle = MemberOrDefault(*spot, "innerConeAngle", 0.0f); + outerConeAngle = MemberOrDefault(*spot, "outerConeAngle", static_cast<float>(M_PI / 4.0f)); + } +} + +inline void Node::Read(Value &obj, Asset &r) { + if (name.empty()) { + name = id; + } + + Value *curChildren = FindArray(obj, "children"); + if (nullptr != curChildren) { + this->children.reserve(curChildren->Size()); + for (unsigned int i = 0; i < curChildren->Size(); ++i) { + Value &child = (*curChildren)[i]; + if (child.IsUint()) { + // get/create the child node + Ref<Node> chn = r.nodes.Retrieve(child.GetUint()); + if (chn) { + this->children.push_back(chn); + } + } + } + } + + Value *curMatrix = FindArray(obj, "matrix"); + if (nullptr != curMatrix) { + ReadValue(*curMatrix, this->matrix); + } else { + ReadMember(obj, "translation", translation); + ReadMember(obj, "scale", scale); + ReadMember(obj, "rotation", rotation); + } + + Value *curMesh = FindUInt(obj, "mesh"); + if (nullptr != curMesh) { + unsigned int numMeshes = 1; + this->meshes.reserve(numMeshes); + Ref<Mesh> meshRef = r.meshes.Retrieve((*curMesh).GetUint()); + if (meshRef) { + this->meshes.push_back(meshRef); + } + } + + // Do not retrieve a skin here, just take a reference, to avoid infinite recursion + // Skins will be properly loaded later + Value *curSkin = FindUInt(obj, "skin"); + if (nullptr != curSkin) { + this->skin = r.skins.Get(curSkin->GetUint()); + } + + Value *curCamera = FindUInt(obj, "camera"); + if (nullptr != curCamera) { + this->camera = r.cameras.Retrieve(curCamera->GetUint()); + if (this->camera) { + this->camera->id = this->id; + } + } + + Value *curExtensions = FindObject(obj, "extensions"); + if (nullptr != curExtensions) { + if (r.extensionsUsed.KHR_lights_punctual) { + if (Value *ext = FindObject(*curExtensions, "KHR_lights_punctual")) { + Value *curLight = FindUInt(*ext, "light"); + if (nullptr != curLight) { + this->light = r.lights.Retrieve(curLight->GetUint()); + if (this->light) { + this->light->id = this->id; + } + } + } + } + } +} + +inline void Scene::Read(Value &obj, Asset &r) { + if (Value *scene_name = FindString(obj, "name")) { + if (scene_name->IsString()) { + this->name = scene_name->GetString(); + } + } + if (Value *array = FindArray(obj, "nodes")) { + for (unsigned int i = 0; i < array->Size(); ++i) { + if (!(*array)[i].IsUint()) continue; + Ref<Node> node = r.nodes.Retrieve((*array)[i].GetUint()); + if (node) + this->nodes.push_back(node); + } + } +} + +inline void Skin::Read(Value &obj, Asset &r) { + if (Value *matrices = FindUInt(obj, "inverseBindMatrices")) { + inverseBindMatrices = r.accessors.Retrieve(matrices->GetUint()); + } + + if (Value *joints = FindArray(obj, "joints")) { + for (unsigned i = 0; i < joints->Size(); ++i) { + if (!(*joints)[i].IsUint()) continue; + Ref<Node> node = r.nodes.Retrieve((*joints)[i].GetUint()); + if (node) { + this->jointNames.push_back(node); + } + } + } +} + +inline void Animation::Read(Value &obj, Asset &r) { + Value *curSamplers = FindArray(obj, "samplers"); + if (nullptr != curSamplers) { + for (unsigned i = 0; i < curSamplers->Size(); ++i) { + Value &sampler = (*curSamplers)[i]; + + Sampler s; + if (Value *input = FindUInt(sampler, "input")) { + s.input = r.accessors.Retrieve(input->GetUint()); + } + if (Value *output = FindUInt(sampler, "output")) { + s.output = r.accessors.Retrieve(output->GetUint()); + } + s.interpolation = Interpolation_LINEAR; + if (Value *interpolation = FindString(sampler, "interpolation")) { + const std::string interp = interpolation->GetString(); + if (interp == "LINEAR") { + s.interpolation = Interpolation_LINEAR; + } else if (interp == "STEP") { + s.interpolation = Interpolation_STEP; + } else if (interp == "CUBICSPLINE") { + s.interpolation = Interpolation_CUBICSPLINE; + } + } + this->samplers.push_back(s); + } + } + + Value *curChannels = FindArray(obj, "channels"); + if (nullptr != curChannels) { + for (unsigned i = 0; i < curChannels->Size(); ++i) { + Value &channel = (*curChannels)[i]; + + Channel c; + Value *curSampler = FindUInt(channel, "sampler"); + if (nullptr != curSampler) { + c.sampler = curSampler->GetUint(); + } + + if (Value *target = FindObject(channel, "target")) { + if (Value *node = FindUInt(*target, "node")) { + c.target.node = r.nodes.Retrieve(node->GetUint()); + } + if (Value *path = FindString(*target, "path")) { + const std::string p = path->GetString(); + if (p == "translation") { + c.target.path = AnimationPath_TRANSLATION; + } else if (p == "rotation") { + c.target.path = AnimationPath_ROTATION; + } else if (p == "scale") { + c.target.path = AnimationPath_SCALE; + } else if (p == "weights") { + c.target.path = AnimationPath_WEIGHTS; + } + } + } + this->channels.push_back(c); + } + } +} + +inline void AssetMetadata::Read(Document &doc) { + if (Value *obj = FindObject(doc, "asset")) { + ReadMember(*obj, "copyright", copyright); + ReadMember(*obj, "generator", generator); + + if (Value *versionString = FindStringInContext(*obj, "version", "\"asset\"")) { + version = versionString->GetString(); + } + Value *curProfile = FindObjectInContext(*obj, "profile", "\"asset\""); + if (nullptr != curProfile) { + ReadMember(*curProfile, "api", this->profile.api); + ReadMember(*curProfile, "version", this->profile.version); + } + } + + if (version.empty() || version[0] != '2') { + throw DeadlyImportError("GLTF: Unsupported glTF version: ", version); + } +} + +// +// Asset methods implementation +// + +inline void Asset::ReadBinaryHeader(IOStream &stream, std::vector<char> &sceneData) { + ASSIMP_LOG_DEBUG("Reading GLTF2 binary"); + GLB_Header header; + if (stream.Read(&header, sizeof(header), 1) != 1) { + throw DeadlyImportError("GLTF: Unable to read the file header"); + } + + if (strncmp((char *)header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)) != 0) { + throw DeadlyImportError("GLTF: Invalid binary glTF file"); + } + + AI_SWAP4(header.version); + asset.version = ai_to_string(header.version); + if (header.version != 2) { + throw DeadlyImportError("GLTF: Unsupported binary glTF version"); + } + + GLB_Chunk chunk; + if (stream.Read(&chunk, sizeof(chunk), 1) != 1) { + throw DeadlyImportError("GLTF: Unable to read JSON chunk"); + } + + AI_SWAP4(chunk.chunkLength); + AI_SWAP4(chunk.chunkType); + + if (chunk.chunkType != ChunkType_JSON) { + throw DeadlyImportError("GLTF: JSON chunk missing"); + } + + // read the scene data, ensure null termination + static_assert(std::numeric_limits<uint32_t>::max() <= std::numeric_limits<size_t>::max(), "size_t must be at least 32bits"); + mSceneLength = chunk.chunkLength; // Can't be larger than 4GB (max. uint32_t) + sceneData.resize(mSceneLength + 1); + sceneData[mSceneLength] = '\0'; + + if (stream.Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { + throw DeadlyImportError("GLTF: Could not read the file contents"); + } + + uint32_t padding = ((chunk.chunkLength + 3) & ~3) - chunk.chunkLength; + if (padding > 0) { + stream.Seek(padding, aiOrigin_CUR); + } + + AI_SWAP4(header.length); + mBodyOffset = 12 + 8 + chunk.chunkLength + padding + 8; + if (header.length >= mBodyOffset) { + if (stream.Read(&chunk, sizeof(chunk), 1) != 1) { + throw DeadlyImportError("GLTF: Unable to read BIN chunk"); + } + + AI_SWAP4(chunk.chunkLength); + AI_SWAP4(chunk.chunkType); + + if (chunk.chunkType != ChunkType_BIN) { + throw DeadlyImportError("GLTF: BIN chunk missing"); + } + + mBodyLength = chunk.chunkLength; + } else { + mBodyOffset = mBodyLength = 0; + } +} + +inline rapidjson::Document Asset::ReadDocument(IOStream &stream, bool isBinary, std::vector<char> &sceneData) { + ASSIMP_LOG_DEBUG("Loading GLTF2 asset"); + + // is binary? then read the header + if (isBinary) { + SetAsBinary(); // also creates the body buffer + ReadBinaryHeader(stream, sceneData); + } else { + mSceneLength = stream.FileSize(); + mBodyLength = 0; + + // Binary format only supports up to 4GB of JSON, use that as a maximum + if (mSceneLength >= std::numeric_limits<uint32_t>::max()) { + throw DeadlyImportError("GLTF: JSON size greater than 4GB"); + } + + // read the scene data, ensure null termination + sceneData.resize(mSceneLength + 1); + sceneData[mSceneLength] = '\0'; + + if (stream.Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { + throw DeadlyImportError("GLTF: Could not read the file contents"); + } + } + + // Smallest legal JSON file is "{}" Smallest loadable glTF file is larger than that but catch it later + if (mSceneLength < 2) { + throw DeadlyImportError("GLTF: No JSON file contents"); + } + + // parse the JSON document + ASSIMP_LOG_DEBUG("Parsing GLTF2 JSON"); + Document doc; + doc.ParseInsitu(&sceneData[0]); + + if (doc.HasParseError()) { + char buffer[32]; + ai_snprintf(buffer, 32, "%d", static_cast<int>(doc.GetErrorOffset())); + throw DeadlyImportError("GLTF: JSON parse error, offset ", buffer, ": ", GetParseError_En(doc.GetParseError())); + } + + if (!doc.IsObject()) { + throw DeadlyImportError("GLTF: JSON document root must be a JSON object"); + } + + return doc; +} + +inline void Asset::Load(const std::string &pFile, bool isBinary) +{ + mCurrentAssetDir.clear(); + if (0 != strncmp(pFile.c_str(), AI_MEMORYIO_MAGIC_FILENAME, AI_MEMORYIO_MAGIC_FILENAME_LENGTH)) { + mCurrentAssetDir = glTFCommon::getCurrentAssetDir(pFile); + } + + shared_ptr<IOStream> stream(OpenFile(pFile.c_str(), "rb", true)); + if (!stream) { + throw DeadlyImportError("GLTF: Could not open file for reading"); + } + + std::vector<char> sceneData; + rapidjson::Document doc = ReadDocument(*stream, isBinary, sceneData); + + // If a schemaDocumentProvider is available, see if the glTF schema is present. + // If so, use it to validate the document. + if (mSchemaDocumentProvider) { + if (const rapidjson::SchemaDocument *gltfSchema = mSchemaDocumentProvider->GetRemoteDocument("glTF.schema.json", 16)) { + // The schemas are found here: https://github.com/KhronosGroup/glTF/tree/main/specification/2.0/schema + rapidjson::SchemaValidator validator(*gltfSchema); + if (!doc.Accept(validator)) { + rapidjson::StringBuffer pathBuffer; + validator.GetInvalidSchemaPointer().StringifyUriFragment(pathBuffer); + rapidjson::StringBuffer argumentBuffer; + validator.GetInvalidDocumentPointer().StringifyUriFragment(argumentBuffer); + throw DeadlyImportError("GLTF: The JSON document did not satisfy the glTF2 schema. Schema keyword: ", validator.GetInvalidSchemaKeyword(), ", document path: ", pathBuffer.GetString(), ", argument: ", argumentBuffer.GetString()); + } + } + } + + // Fill the buffer instance for the current file embedded contents + if (mBodyLength > 0) { + if (!mBodyBuffer->LoadFromStream(*stream, mBodyLength, mBodyOffset)) { + throw DeadlyImportError("GLTF: Unable to read gltf file"); + } + } + + // Load the metadata + asset.Read(doc); + ReadExtensionsUsed(doc); + ReadExtensionsRequired(doc); + +#ifndef ASSIMP_ENABLE_DRACO + // Is Draco required? + if (extensionsRequired.KHR_draco_mesh_compression) { + throw DeadlyImportError("GLTF: Draco mesh compression not supported."); + } +#endif + + // Prepare the dictionaries + for (size_t i = 0; i < mDicts.size(); ++i) { + mDicts[i]->AttachToDocument(doc); + } + + // Read the "scene" property, which specifies which scene to load + // and recursively load everything referenced by it + unsigned int sceneIndex = 0; + Value *curScene = FindUInt(doc, "scene"); + if (nullptr != curScene) { + sceneIndex = curScene->GetUint(); + } + + if (Value *scenesArray = FindArray(doc, "scenes")) { + if (sceneIndex < scenesArray->Size()) { + this->scene = scenes.Retrieve(sceneIndex); + } + } + + if (Value *skinsArray = FindArray(doc, "skins")) { + for (unsigned int i = 0; i < skinsArray->Size(); ++i) { + skins.Retrieve(i); + } + } + + if (Value *animsArray = FindArray(doc, "animations")) { + for (unsigned int i = 0; i < animsArray->Size(); ++i) { + animations.Retrieve(i); + } + } + + // Clean up + for (size_t i = 0; i < mDicts.size(); ++i) { + mDicts[i]->DetachFromDocument(); + } +} + +inline bool Asset::CanRead(const std::string &pFile, bool isBinary) { + try { + shared_ptr<IOStream> stream(OpenFile(pFile.c_str(), "rb", true)); + if (!stream) { + return false; + } + std::vector<char> sceneData; + rapidjson::Document doc = ReadDocument(*stream, isBinary, sceneData); + asset.Read(doc); + } catch (...) { + return false; + } + return true; +} + +inline void Asset::SetAsBinary() { + if (!mBodyBuffer) { + mBodyBuffer = buffers.Create("binary_glTF"); + mBodyBuffer->MarkAsSpecial(); + } +} + +// As required extensions are only a concept in glTF 2.0, this is here +// instead of glTFCommon.h +#define CHECK_REQUIRED_EXT(EXT) \ + if (exts.find(#EXT) != exts.end()) extensionsRequired.EXT = true; + +inline void Asset::ReadExtensionsRequired(Document &doc) { + Value *extsRequired = FindArray(doc, "extensionsRequired"); + if (nullptr == extsRequired) { + return; + } + + std::gltf_unordered_map<std::string, bool> exts; + for (unsigned int i = 0; i < extsRequired->Size(); ++i) { + if ((*extsRequired)[i].IsString()) { + exts[(*extsRequired)[i].GetString()] = true; + } + } + + CHECK_REQUIRED_EXT(KHR_draco_mesh_compression); + +#undef CHECK_REQUIRED_EXT +} + +inline void Asset::ReadExtensionsUsed(Document &doc) { + Value *extsUsed = FindArray(doc, "extensionsUsed"); + if (!extsUsed) return; + + std::gltf_unordered_map<std::string, bool> exts; + + for (unsigned int i = 0; i < extsUsed->Size(); ++i) { + if ((*extsUsed)[i].IsString()) { + exts[(*extsUsed)[i].GetString()] = true; + } + } + + CHECK_EXT(KHR_materials_pbrSpecularGlossiness); + CHECK_EXT(KHR_materials_unlit); + CHECK_EXT(KHR_lights_punctual); + CHECK_EXT(KHR_texture_transform); + CHECK_EXT(KHR_materials_sheen); + CHECK_EXT(KHR_materials_clearcoat); + CHECK_EXT(KHR_materials_transmission); + CHECK_EXT(KHR_materials_volume); + CHECK_EXT(KHR_materials_ior); + CHECK_EXT(KHR_draco_mesh_compression); + CHECK_EXT(KHR_texture_basisu); + +#undef CHECK_EXT +} + +inline IOStream *Asset::OpenFile(const std::string &path, const char *mode, bool /*absolute*/) { +#ifdef ASSIMP_API + return mIOSystem->Open(path, mode); +#else + if (path.size() < 2) return nullptr; + if (!absolute && path[1] != ':' && path[0] != '/') { // relative? + path = mCurrentAssetDir + path; + } + FILE *f = fopen(path.c_str(), mode); + return f ? new IOStream(f) : nullptr; +#endif +} + +inline std::string Asset::FindUniqueID(const std::string &str, const char *suffix) { + std::string id = str; + + if (!id.empty()) { + if (mUsedIds.find(id) == mUsedIds.end()) + return id; + + id += "_"; + } + + id += suffix; + + Asset::IdMap::iterator it = mUsedIds.find(id); + if (it == mUsedIds.end()) { + return id; + } + + std::vector<char> buffer; + buffer.resize(id.size() + 16); + int offset = ai_snprintf(buffer.data(), buffer.size(), "%s_", id.c_str()); + for (int i = 0; it != mUsedIds.end(); ++i) { + ai_snprintf(buffer.data() + offset, buffer.size() - offset, "%d", i); + id = buffer.data(); + it = mUsedIds.find(id); + } + + return id; +} + +#if _MSC_VER +# pragma warning(pop) +#endif // _MSC_VER + +} // namespace glTF2 diff --git a/libs/assimp/code/AssetLib/glTF2/glTF2AssetWriter.h b/libs/assimp/code/AssetLib/glTF2/glTF2AssetWriter.h new file mode 100644 index 0000000..089a158 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF2/glTF2AssetWriter.h @@ -0,0 +1,101 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 glTFWriter.h + * Declares a class to write gltf/glb files + * + * glTF Extensions Support: + * KHR_materials_pbrSpecularGlossiness: full + * KHR_materials_unlit: full + * KHR_materials_sheen: full + * KHR_materials_clearcoat: full + * KHR_materials_transmission: full + * KHR_materials_volume: full + * KHR_materials_ior: full + */ +#ifndef GLTF2ASSETWRITER_H_INC +#define GLTF2ASSETWRITER_H_INC + +#if !defined(ASSIMP_BUILD_NO_GLTF_IMPORTER) && !defined(ASSIMP_BUILD_NO_GLTF2_IMPORTER) + +#include "glTF2Asset.h" + +namespace glTF2 +{ + +using rapidjson::MemoryPoolAllocator; + +class AssetWriter +{ + template<class T> + friend void WriteLazyDict(LazyDict<T>& d, AssetWriter& w); + +private: + + void WriteBinaryData(IOStream* outfile, size_t sceneLength); + + void WriteMetadata(); + void WriteExtensionsUsed(); + + template<class T> + void WriteObjects(LazyDict<T>& d); + +public: + Document mDoc; + Asset& mAsset; + + MemoryPoolAllocator<>& mAl; + + AssetWriter(Asset& asset); + + void WriteFile(const char* path); + void WriteGLBFile(const char* path); +}; + +} + +// Include the implementation of the methods +#include "glTF2AssetWriter.inl" + +#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER + +#endif // GLTF2ASSETWRITER_H_INC diff --git a/libs/assimp/code/AssetLib/glTF2/glTF2AssetWriter.inl b/libs/assimp/code/AssetLib/glTF2/glTF2AssetWriter.inl new file mode 100644 index 0000000..0be1395 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF2/glTF2AssetWriter.inl @@ -0,0 +1,1015 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#include <assimp/Base64.hpp> +#include <rapidjson/stringbuffer.h> +#include <rapidjson/writer.h> +#include <rapidjson/prettywriter.h> + +namespace glTF2 { + + using rapidjson::StringBuffer; + using rapidjson::PrettyWriter; + using rapidjson::Writer; + using rapidjson::StringRef; + using rapidjson::StringRef; + + namespace { + + template<typename T, size_t N> + inline Value& MakeValue(Value& val, T(&r)[N], MemoryPoolAllocator<>& al) { + val.SetArray(); + val.Reserve(N, al); + for (decltype(N) i = 0; i < N; ++i) { + val.PushBack(r[i], al); + } + return val; + } + + template<typename T> + inline Value& MakeValue(Value& val, const std::vector<T> & r, MemoryPoolAllocator<>& al) { + val.SetArray(); + val.Reserve(static_cast<rapidjson::SizeType>(r.size()), al); + for (unsigned int i = 0; i < r.size(); ++i) { + val.PushBack(r[i], al); + } + return val; + } + + template<typename C, typename T> + inline Value& MakeValueCast(Value& val, const std::vector<T> & r, MemoryPoolAllocator<>& al) { + val.SetArray(); + val.Reserve(static_cast<rapidjson::SizeType>(r.size()), al); + for (unsigned int i = 0; i < r.size(); ++i) { + val.PushBack(static_cast<C>(r[i]), al); + } + return val; + } + + template<typename T> + inline Value& MakeValue(Value& val, T r, MemoryPoolAllocator<>& /*al*/) { + val.Set(r); + + return val; + } + + template<class T> + inline void AddRefsVector(Value& obj, const char* fieldId, std::vector< Ref<T> >& v, MemoryPoolAllocator<>& al) { + if (v.empty()) return; + Value lst; + lst.SetArray(); + lst.Reserve(unsigned(v.size()), al); + for (size_t i = 0; i < v.size(); ++i) { + lst.PushBack(v[i]->index, al); + } + obj.AddMember(StringRef(fieldId), lst, al); + } + + + } + + inline void Write(Value& obj, Accessor& a, AssetWriter& w) + { + if (a.bufferView) { + obj.AddMember("bufferView", a.bufferView->index, w.mAl); + obj.AddMember("byteOffset", (unsigned int)a.byteOffset, w.mAl); + } + obj.AddMember("componentType", int(a.componentType), w.mAl); + obj.AddMember("count", (unsigned int)a.count, w.mAl); + obj.AddMember("type", StringRef(AttribType::ToString(a.type)), w.mAl); + Value vTmpMax, vTmpMin; + if (a.componentType == ComponentType_FLOAT) { + obj.AddMember("max", MakeValue(vTmpMax, a.max, w.mAl), w.mAl); + obj.AddMember("min", MakeValue(vTmpMin, a.min, w.mAl), w.mAl); + } else { + obj.AddMember("max", MakeValueCast<int64_t>(vTmpMax, a.max, w.mAl), w.mAl); + obj.AddMember("min", MakeValueCast<int64_t>(vTmpMin, a.min, w.mAl), w.mAl); + } + + if (a.sparse) { + Value sparseValue; + sparseValue.SetObject(); + + //count + sparseValue.AddMember("count", (unsigned int)a.sparse->count, w.mAl); + + //indices + Value indices; + indices.SetObject(); + indices.AddMember("bufferView", a.sparse->indices->index, w.mAl); + indices.AddMember("byteOffset", (unsigned int)a.sparse->indicesByteOffset, w.mAl); + indices.AddMember("componentType", int(a.sparse->indicesType), w.mAl); + sparseValue.AddMember("indices", indices, w.mAl); + + //values + Value values; + values.SetObject(); + values.AddMember("bufferView", a.sparse->values->index, w.mAl); + values.AddMember("byteOffset", (unsigned int)a.sparse->valuesByteOffset, w.mAl); + sparseValue.AddMember("values", values, w.mAl); + + obj.AddMember("sparse", sparseValue, w.mAl); + } + } + + inline void Write(Value& obj, Animation& a, AssetWriter& w) + { + /****************** Channels *******************/ + Value channels; + channels.SetArray(); + channels.Reserve(unsigned(a.channels.size()), w.mAl); + + for (size_t i = 0; i < unsigned(a.channels.size()); ++i) { + Animation::Channel& c = a.channels[i]; + Value valChannel; + valChannel.SetObject(); + { + valChannel.AddMember("sampler", c.sampler, w.mAl); + + Value valTarget; + valTarget.SetObject(); + { + valTarget.AddMember("node", c.target.node->index, w.mAl); + switch (c.target.path) { + case AnimationPath_TRANSLATION: + valTarget.AddMember("path", "translation", w.mAl); + break; + case AnimationPath_ROTATION: + valTarget.AddMember("path", "rotation", w.mAl); + break; + case AnimationPath_SCALE: + valTarget.AddMember("path", "scale", w.mAl); + break; + case AnimationPath_WEIGHTS: + valTarget.AddMember("path", "weights", w.mAl); + break; + } + } + valChannel.AddMember("target", valTarget, w.mAl); + } + channels.PushBack(valChannel, w.mAl); + } + obj.AddMember("channels", channels, w.mAl); + + /****************** Samplers *******************/ + Value valSamplers; + valSamplers.SetArray(); + + for (size_t i = 0; i < unsigned(a.samplers.size()); ++i) { + Animation::Sampler& s = a.samplers[i]; + Value valSampler; + valSampler.SetObject(); + { + valSampler.AddMember("input", s.input->index, w.mAl); + switch (s.interpolation) { + case Interpolation_LINEAR: + valSampler.AddMember("interpolation", "LINEAR", w.mAl); + break; + case Interpolation_STEP: + valSampler.AddMember("interpolation", "STEP", w.mAl); + break; + case Interpolation_CUBICSPLINE: + valSampler.AddMember("interpolation", "CUBICSPLINE", w.mAl); + break; + } + valSampler.AddMember("output", s.output->index, w.mAl); + } + valSamplers.PushBack(valSampler, w.mAl); + } + obj.AddMember("samplers", valSamplers, w.mAl); + } + + inline void Write(Value& obj, Buffer& b, AssetWriter& w) + { + obj.AddMember("byteLength", static_cast<uint64_t>(b.byteLength), w.mAl); + + const auto uri = b.GetURI(); + const auto relativeUri = uri.substr(uri.find_last_of("/\\") + 1u); + obj.AddMember("uri", Value(relativeUri, w.mAl).Move(), w.mAl); + } + + inline void Write(Value& obj, BufferView& bv, AssetWriter& w) + { + obj.AddMember("buffer", bv.buffer->index, w.mAl); + obj.AddMember("byteOffset", static_cast<uint64_t>(bv.byteOffset), w.mAl); + obj.AddMember("byteLength", static_cast<uint64_t>(bv.byteLength), w.mAl); + if (bv.byteStride != 0) { + obj.AddMember("byteStride", bv.byteStride, w.mAl); + } + if (bv.target != BufferViewTarget_NONE) { + obj.AddMember("target", int(bv.target), w.mAl); + } + } + + inline void Write(Value& /*obj*/, Camera& /*c*/, AssetWriter& /*w*/) + { + + } + + inline void Write(Value& /*obj*/, Light& /*c*/, AssetWriter& /*w*/) + { + + } + + inline void Write(Value& obj, Image& img, AssetWriter& w) + { + //basisu: no need to handle .ktx2, .basis, write as is + if (img.bufferView) { + obj.AddMember("bufferView", img.bufferView->index, w.mAl); + obj.AddMember("mimeType", Value(img.mimeType, w.mAl).Move(), w.mAl); + } + else { + std::string uri; + if (img.HasData()) { + uri = "data:" + (img.mimeType.empty() ? "application/octet-stream" : img.mimeType); + uri += ";base64,"; + Base64::Encode(img.GetData(), img.GetDataLength(), uri); + } + else { + uri = img.uri; + } + + obj.AddMember("uri", Value(uri, w.mAl).Move(), w.mAl); + } + } + + namespace { + inline void SetTexBasic(TextureInfo t, Value& tex, MemoryPoolAllocator<>& al) + { + tex.SetObject(); + tex.AddMember("index", t.texture->index, al); + + if (t.texCoord != 0) { + tex.AddMember("texCoord", t.texCoord, al); + } + } + + inline void WriteTex(Value& obj, TextureInfo t, const char* propName, MemoryPoolAllocator<>& al) + { + + if (t.texture) { + Value tex; + + SetTexBasic(t, tex, al); + + obj.AddMember(StringRef(propName), tex, al); + } + } + + inline void WriteTex(Value& obj, NormalTextureInfo t, const char* propName, MemoryPoolAllocator<>& al) + { + + if (t.texture) { + Value tex; + + SetTexBasic(t, tex, al); + + if (t.scale != 1) { + tex.AddMember("scale", t.scale, al); + } + + obj.AddMember(StringRef(propName), tex, al); + } + } + + inline void WriteTex(Value& obj, OcclusionTextureInfo t, const char* propName, MemoryPoolAllocator<>& al) + { + + if (t.texture) { + Value tex; + + SetTexBasic(t, tex, al); + + if (t.strength != 1) { + tex.AddMember("strength", t.strength, al); + } + + obj.AddMember(StringRef(propName), tex, al); + } + } + + template<size_t N> + inline void WriteVec(Value& obj, float(&prop)[N], const char* propName, MemoryPoolAllocator<>& al) + { + Value arr; + obj.AddMember(StringRef(propName), MakeValue(arr, prop, al), al); + } + + template<size_t N> + inline void WriteVec(Value& obj, float(&prop)[N], const char* propName, const float(&defaultVal)[N], MemoryPoolAllocator<>& al) + { + if (!std::equal(std::begin(prop), std::end(prop), std::begin(defaultVal))) { + WriteVec(obj, prop, propName, al); + } + } + + inline void WriteFloat(Value& obj, float prop, const char* propName, MemoryPoolAllocator<>& al) + { + Value num; + obj.AddMember(StringRef(propName), MakeValue(num, prop, al), al); + } + } + + inline void Write(Value& obj, Material& m, AssetWriter& w) + { + Value pbrMetallicRoughness; + pbrMetallicRoughness.SetObject(); + { + WriteTex(pbrMetallicRoughness, m.pbrMetallicRoughness.baseColorTexture, "baseColorTexture", w.mAl); + WriteTex(pbrMetallicRoughness, m.pbrMetallicRoughness.metallicRoughnessTexture, "metallicRoughnessTexture", w.mAl); + WriteVec(pbrMetallicRoughness, m.pbrMetallicRoughness.baseColorFactor, "baseColorFactor", defaultBaseColor, w.mAl); + + if (m.pbrMetallicRoughness.metallicFactor != 1) { + WriteFloat(pbrMetallicRoughness, m.pbrMetallicRoughness.metallicFactor, "metallicFactor", w.mAl); + } + + if (m.pbrMetallicRoughness.roughnessFactor != 1) { + WriteFloat(pbrMetallicRoughness, m.pbrMetallicRoughness.roughnessFactor, "roughnessFactor", w.mAl); + } + } + + if (!pbrMetallicRoughness.ObjectEmpty()) { + obj.AddMember("pbrMetallicRoughness", pbrMetallicRoughness, w.mAl); + } + + WriteTex(obj, m.normalTexture, "normalTexture", w.mAl); + WriteTex(obj, m.emissiveTexture, "emissiveTexture", w.mAl); + WriteTex(obj, m.occlusionTexture, "occlusionTexture", w.mAl); + WriteVec(obj, m.emissiveFactor, "emissiveFactor", defaultEmissiveFactor, w.mAl); + + if (m.alphaCutoff != 0.5) { + WriteFloat(obj, m.alphaCutoff, "alphaCutoff", w.mAl); + } + + if (m.alphaMode != "OPAQUE") { + obj.AddMember("alphaMode", Value(m.alphaMode, w.mAl).Move(), w.mAl); + } + + if (m.doubleSided) { + obj.AddMember("doubleSided", m.doubleSided, w.mAl); + } + + Value exts; + exts.SetObject(); + + if (m.pbrSpecularGlossiness.isPresent) { + Value pbrSpecularGlossiness; + pbrSpecularGlossiness.SetObject(); + + PbrSpecularGlossiness &pbrSG = m.pbrSpecularGlossiness.value; + + //pbrSpecularGlossiness + WriteVec(pbrSpecularGlossiness, pbrSG.diffuseFactor, "diffuseFactor", defaultDiffuseFactor, w.mAl); + WriteVec(pbrSpecularGlossiness, pbrSG.specularFactor, "specularFactor", defaultSpecularFactor, w.mAl); + + if (pbrSG.glossinessFactor != 1) { + WriteFloat(pbrSpecularGlossiness, pbrSG.glossinessFactor, "glossinessFactor", w.mAl); + } + + WriteTex(pbrSpecularGlossiness, pbrSG.diffuseTexture, "diffuseTexture", w.mAl); + WriteTex(pbrSpecularGlossiness, pbrSG.specularGlossinessTexture, "specularGlossinessTexture", w.mAl); + + if (!pbrSpecularGlossiness.ObjectEmpty()) { + exts.AddMember("KHR_materials_pbrSpecularGlossiness", pbrSpecularGlossiness, w.mAl); + } + } + + if (m.unlit) { + Value unlit; + unlit.SetObject(); + exts.AddMember("KHR_materials_unlit", unlit, w.mAl); + } + + if (m.materialSheen.isPresent) { + Value materialSheen(rapidjson::Type::kObjectType); + + MaterialSheen &sheen = m.materialSheen.value; + + WriteVec(materialSheen, sheen.sheenColorFactor, "sheenColorFactor", defaultSheenFactor, w.mAl); + + if (sheen.sheenRoughnessFactor != 0.f) { + WriteFloat(materialSheen, sheen.sheenRoughnessFactor, "sheenRoughnessFactor", w.mAl); + } + + WriteTex(materialSheen, sheen.sheenColorTexture, "sheenColorTexture", w.mAl); + WriteTex(materialSheen, sheen.sheenRoughnessTexture, "sheenRoughnessTexture", w.mAl); + + if (!materialSheen.ObjectEmpty()) { + exts.AddMember("KHR_materials_sheen", materialSheen, w.mAl); + } + } + + if (m.materialClearcoat.isPresent) { + Value materialClearcoat(rapidjson::Type::kObjectType); + + MaterialClearcoat &clearcoat = m.materialClearcoat.value; + + if (clearcoat.clearcoatFactor != 0.f) { + WriteFloat(materialClearcoat, clearcoat.clearcoatFactor, "clearcoatFactor", w.mAl); + } + + if (clearcoat.clearcoatRoughnessFactor != 0.f) { + WriteFloat(materialClearcoat, clearcoat.clearcoatRoughnessFactor, "clearcoatRoughnessFactor", w.mAl); + } + + WriteTex(materialClearcoat, clearcoat.clearcoatTexture, "clearcoatTexture", w.mAl); + WriteTex(materialClearcoat, clearcoat.clearcoatRoughnessTexture, "clearcoatRoughnessTexture", w.mAl); + WriteTex(materialClearcoat, clearcoat.clearcoatNormalTexture, "clearcoatNormalTexture", w.mAl); + + if (!materialClearcoat.ObjectEmpty()) { + exts.AddMember("KHR_materials_clearcoat", materialClearcoat, w.mAl); + } + } + + if (m.materialTransmission.isPresent) { + Value materialTransmission(rapidjson::Type::kObjectType); + + MaterialTransmission &transmission = m.materialTransmission.value; + + if (transmission.transmissionFactor != 0.f) { + WriteFloat(materialTransmission, transmission.transmissionFactor, "transmissionFactor", w.mAl); + } + + WriteTex(materialTransmission, transmission.transmissionTexture, "transmissionTexture", w.mAl); + + if (!materialTransmission.ObjectEmpty()) { + exts.AddMember("KHR_materials_transmission", materialTransmission, w.mAl); + } + } + + if (m.materialVolume.isPresent) { + Value materialVolume(rapidjson::Type::kObjectType); + + MaterialVolume &volume = m.materialVolume.value; + + if (volume.thicknessFactor != 0.f) { + WriteFloat(materialVolume, volume.thicknessFactor, "thicknessFactor", w.mAl); + } + + WriteTex(materialVolume, volume.thicknessTexture, "thicknessTexture", w.mAl); + + if (volume.attenuationDistance != INFINITY) { + WriteFloat(materialVolume, volume.attenuationDistance, "attenuationDistance", w.mAl); + } + + WriteVec(materialVolume, volume.attenuationColor, "attenuationColor", defaultAttenuationColor, w.mAl); + + if (!materialVolume.ObjectEmpty()) { + exts.AddMember("KHR_materials_volume", materialVolume, w.mAl); + } + } + + if (m.materialIOR.isPresent) { + Value materialIOR(rapidjson::Type::kObjectType); + + MaterialIOR &ior = m.materialIOR.value; + + if (ior.ior != 1.5f) { + WriteFloat(materialIOR, ior.ior, "ior", w.mAl); + } + + if (!materialIOR.ObjectEmpty()) { + exts.AddMember("KHR_materials_ior", materialIOR, w.mAl); + } + } + + if (!exts.ObjectEmpty()) { + obj.AddMember("extensions", exts, w.mAl); + } + } + + namespace { + inline void WriteAttrs(AssetWriter& w, Value& attrs, Mesh::AccessorList& lst, + const char* semantic, bool forceNumber = false) + { + if (lst.empty()) return; + if (lst.size() == 1 && !forceNumber) { + attrs.AddMember(StringRef(semantic), lst[0]->index, w.mAl); + } + else { + for (size_t i = 0; i < lst.size(); ++i) { + char buffer[32]; + ai_snprintf(buffer, 32, "%s_%d", semantic, int(i)); + attrs.AddMember(Value(buffer, w.mAl).Move(), lst[i]->index, w.mAl); + } + } + } + } + + inline void Write(Value& obj, Mesh& m, AssetWriter& w) + { + /****************** Primitives *******************/ + Value primitives; + primitives.SetArray(); + primitives.Reserve(unsigned(m.primitives.size()), w.mAl); + + for (size_t i = 0; i < m.primitives.size(); ++i) { + Mesh::Primitive& p = m.primitives[i]; + Value prim; + prim.SetObject(); + + // Extensions + if (p.ngonEncoded) + { + Value exts; + exts.SetObject(); + + Value FB_ngon_encoding; + FB_ngon_encoding.SetObject(); + + exts.AddMember(StringRef("FB_ngon_encoding"), FB_ngon_encoding, w.mAl); + prim.AddMember("extensions", exts, w.mAl); + } + + { + prim.AddMember("mode", Value(int(p.mode)).Move(), w.mAl); + + if (p.material) + prim.AddMember("material", p.material->index, w.mAl); + + if (p.indices) + prim.AddMember("indices", p.indices->index, w.mAl); + + Value attrs; + attrs.SetObject(); + { + WriteAttrs(w, attrs, p.attributes.position, "POSITION"); + WriteAttrs(w, attrs, p.attributes.normal, "NORMAL"); + WriteAttrs(w, attrs, p.attributes.texcoord, "TEXCOORD", true); + WriteAttrs(w, attrs, p.attributes.color, "COLOR", true); + WriteAttrs(w, attrs, p.attributes.joint, "JOINTS", true); + WriteAttrs(w, attrs, p.attributes.weight, "WEIGHTS", true); + } + prim.AddMember("attributes", attrs, w.mAl); + + // targets for blendshapes + if (p.targets.size() > 0) { + Value tjs; + tjs.SetArray(); + tjs.Reserve(unsigned(p.targets.size()), w.mAl); + for (unsigned int t = 0; t < p.targets.size(); ++t) { + Value tj; + tj.SetObject(); + { + WriteAttrs(w, tj, p.targets[t].position, "POSITION"); + WriteAttrs(w, tj, p.targets[t].normal, "NORMAL"); + WriteAttrs(w, tj, p.targets[t].tangent, "TANGENT"); + } + tjs.PushBack(tj, w.mAl); + } + prim.AddMember("targets", tjs, w.mAl); + } + } + primitives.PushBack(prim, w.mAl); + } + + obj.AddMember("primitives", primitives, w.mAl); + // targetNames + if (m.targetNames.size() > 0) { + Value extras; + extras.SetObject(); + Value targetNames; + targetNames.SetArray(); + targetNames.Reserve(unsigned(m.targetNames.size()), w.mAl); + for (unsigned int n = 0; n < m.targetNames.size(); ++n) { + std::string name = m.targetNames[n]; + Value tname; + tname.SetString(name.c_str(), w.mAl); + targetNames.PushBack(tname, w.mAl); + } + extras.AddMember("targetNames", targetNames, w.mAl); + obj.AddMember("extras", extras, w.mAl); + } + } + + inline void Write(Value& obj, Node& n, AssetWriter& w) + { + if (n.matrix.isPresent) { + Value val; + obj.AddMember("matrix", MakeValue(val, n.matrix.value, w.mAl).Move(), w.mAl); + } + + if (n.translation.isPresent) { + Value val; + obj.AddMember("translation", MakeValue(val, n.translation.value, w.mAl).Move(), w.mAl); + } + + if (n.scale.isPresent) { + Value val; + obj.AddMember("scale", MakeValue(val, n.scale.value, w.mAl).Move(), w.mAl); + } + if (n.rotation.isPresent) { + Value val; + obj.AddMember("rotation", MakeValue(val, n.rotation.value, w.mAl).Move(), w.mAl); + } + + AddRefsVector(obj, "children", n.children, w.mAl); + + if (!n.meshes.empty()) { + obj.AddMember("mesh", n.meshes[0]->index, w.mAl); + } + + if (n.skin) { + obj.AddMember("skin", n.skin->index, w.mAl); + } + + //gltf2 spec does not support "skeletons" under node + if(n.skeletons.size()) { + AddRefsVector(obj, "skeletons", n.skeletons, w.mAl); + } + } + + inline void Write(Value& /*obj*/, Program& /*b*/, AssetWriter& /*w*/) + { + + } + + inline void Write(Value& obj, Sampler& b, AssetWriter& w) + { + if (!b.name.empty()) { + obj.AddMember("name", b.name, w.mAl); + } + + if (b.wrapS != SamplerWrap::UNSET && b.wrapS != SamplerWrap::Repeat) { + obj.AddMember("wrapS", static_cast<unsigned int>(b.wrapS), w.mAl); + } + + if (b.wrapT != SamplerWrap::UNSET && b.wrapT != SamplerWrap::Repeat) { + obj.AddMember("wrapT", static_cast<unsigned int>(b.wrapT), w.mAl); + } + + if (b.magFilter != SamplerMagFilter::UNSET) { + obj.AddMember("magFilter", static_cast<unsigned int>(b.magFilter), w.mAl); + } + + if (b.minFilter != SamplerMinFilter::UNSET) { + obj.AddMember("minFilter", static_cast<unsigned int>(b.minFilter), w.mAl); + } + } + + inline void Write(Value& scene, Scene& s, AssetWriter& w) + { + AddRefsVector(scene, "nodes", s.nodes, w.mAl); + } + + inline void Write(Value& /*obj*/, Shader& /*b*/, AssetWriter& /*w*/) + { + + } + + inline void Write(Value& obj, Skin& b, AssetWriter& w) + { + /****************** jointNames *******************/ + Value vJointNames; + vJointNames.SetArray(); + vJointNames.Reserve(unsigned(b.jointNames.size()), w.mAl); + + for (size_t i = 0; i < unsigned(b.jointNames.size()); ++i) { + vJointNames.PushBack(b.jointNames[i]->index, w.mAl); + } + obj.AddMember("joints", vJointNames, w.mAl); + + if (b.bindShapeMatrix.isPresent) { + Value val; + obj.AddMember("bindShapeMatrix", MakeValue(val, b.bindShapeMatrix.value, w.mAl).Move(), w.mAl); + } + + if (b.inverseBindMatrices) { + obj.AddMember("inverseBindMatrices", b.inverseBindMatrices->index, w.mAl); + } + + } + + inline void Write(Value& obj, Texture& tex, AssetWriter& w) + { + if (tex.source) { + obj.AddMember("source", tex.source->index, w.mAl); + } + if (tex.sampler) { + obj.AddMember("sampler", tex.sampler->index, w.mAl); + } + } + + + inline AssetWriter::AssetWriter(Asset& a) + : mDoc() + , mAsset(a) + , mAl(mDoc.GetAllocator()) + { + mDoc.SetObject(); + + WriteMetadata(); + WriteExtensionsUsed(); + + // Dump the contents of the dictionaries + for (size_t i = 0; i < a.mDicts.size(); ++i) { + a.mDicts[i]->WriteObjects(*this); + } + + // Add the target scene field + if (mAsset.scene) { + mDoc.AddMember("scene", mAsset.scene->index, mAl); + } + + if(mAsset.extras) { + mDoc.AddMember("extras", *mAsset.extras, mAl); + } + } + + inline void AssetWriter::WriteFile(const char* path) + { + std::unique_ptr<IOStream> jsonOutFile(mAsset.OpenFile(path, "wt", true)); + + if (jsonOutFile == 0) { + throw DeadlyExportError("Could not open output file: " + std::string(path)); + } + + StringBuffer docBuffer; + + PrettyWriter<StringBuffer> writer(docBuffer); + if (!mDoc.Accept(writer)) { + throw DeadlyExportError("Failed to write scene data!"); + } + + if (jsonOutFile->Write(docBuffer.GetString(), docBuffer.GetSize(), 1) != 1) { + throw DeadlyExportError("Failed to write scene data!"); + } + + // Write buffer data to separate .bin files + for (unsigned int i = 0; i < mAsset.buffers.Size(); ++i) { + Ref<Buffer> b = mAsset.buffers.Get(i); + + std::string binPath = b->GetURI(); + + std::unique_ptr<IOStream> binOutFile(mAsset.OpenFile(binPath, "wb", true)); + + if (binOutFile == 0) { + throw DeadlyExportError("Could not open output file: " + binPath); + } + + if (b->byteLength > 0) { + if (binOutFile->Write(b->GetPointer(), b->byteLength, 1) != 1) { + throw DeadlyExportError("Failed to write binary file: " + binPath); + } + } + } + } + + inline void AssetWriter::WriteGLBFile(const char* path) + { + std::unique_ptr<IOStream> outfile(mAsset.OpenFile(path, "wb", true)); + + if (outfile == 0) { + throw DeadlyExportError("Could not open output file: " + std::string(path)); + } + + Ref<Buffer> bodyBuffer = mAsset.GetBodyBuffer(); + if (bodyBuffer->byteLength > 0) { + rapidjson::Value glbBodyBuffer; + glbBodyBuffer.SetObject(); + glbBodyBuffer.AddMember("byteLength", static_cast<uint64_t>(bodyBuffer->byteLength), mAl); + mDoc["buffers"].PushBack(glbBodyBuffer, mAl); + } + + // Padding with spaces as required by the spec + uint32_t padding = 0x20202020; + + // + // JSON chunk + // + + StringBuffer docBuffer; + Writer<StringBuffer> writer(docBuffer); + if (!mDoc.Accept(writer)) { + throw DeadlyExportError("Failed to write scene data!"); + } + + uint32_t jsonChunkLength = (docBuffer.GetSize() + 3) & ~3; // Round up to next multiple of 4 + auto paddingLength = jsonChunkLength - docBuffer.GetSize(); + + GLB_Chunk jsonChunk; + jsonChunk.chunkLength = jsonChunkLength; + jsonChunk.chunkType = ChunkType_JSON; + AI_SWAP4(jsonChunk.chunkLength); + + outfile->Seek(sizeof(GLB_Header), aiOrigin_SET); + if (outfile->Write(&jsonChunk, 1, sizeof(GLB_Chunk)) != sizeof(GLB_Chunk)) { + throw DeadlyExportError("Failed to write scene data header!"); + } + if (outfile->Write(docBuffer.GetString(), 1, docBuffer.GetSize()) != docBuffer.GetSize()) { + throw DeadlyExportError("Failed to write scene data!"); + } + if (paddingLength && outfile->Write(&padding, 1, paddingLength) != paddingLength) { + throw DeadlyExportError("Failed to write scene data padding!"); + } + + // + // Binary chunk + // + + int GLB_Chunk_count = 1; + uint32_t binaryChunkLength = 0; + if (bodyBuffer->byteLength > 0) { + binaryChunkLength = (bodyBuffer->byteLength + 3) & ~3; // Round up to next multiple of 4 + + auto curPaddingLength = binaryChunkLength - bodyBuffer->byteLength; + ++GLB_Chunk_count; + + GLB_Chunk binaryChunk; + binaryChunk.chunkLength = binaryChunkLength; + binaryChunk.chunkType = ChunkType_BIN; + AI_SWAP4(binaryChunk.chunkLength); + + size_t bodyOffset = sizeof(GLB_Header) + sizeof(GLB_Chunk) + jsonChunk.chunkLength; + outfile->Seek(bodyOffset, aiOrigin_SET); + if (outfile->Write(&binaryChunk, 1, sizeof(GLB_Chunk)) != sizeof(GLB_Chunk)) { + throw DeadlyExportError("Failed to write body data header!"); + } + if (outfile->Write(bodyBuffer->GetPointer(), 1, bodyBuffer->byteLength) != bodyBuffer->byteLength) { + throw DeadlyExportError("Failed to write body data!"); + } + if (curPaddingLength && outfile->Write(&padding, 1, paddingLength) != paddingLength) { + throw DeadlyExportError("Failed to write body data padding!"); + } + } + + // + // Header + // + + GLB_Header header; + memcpy(header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)); + + header.version = 2; + AI_SWAP4(header.version); + + header.length = uint32_t(sizeof(GLB_Header) + GLB_Chunk_count * sizeof(GLB_Chunk) + jsonChunkLength + binaryChunkLength); + AI_SWAP4(header.length); + + outfile->Seek(0, aiOrigin_SET); + if (outfile->Write(&header, 1, sizeof(GLB_Header)) != sizeof(GLB_Header)) { + throw DeadlyExportError("Failed to write the header!"); + } + } + + inline void AssetWriter::WriteMetadata() + { + Value asset; + asset.SetObject(); + asset.AddMember("version", Value(mAsset.asset.version, mAl).Move(), mAl); + asset.AddMember("generator", Value(mAsset.asset.generator, mAl).Move(), mAl); + if (!mAsset.asset.copyright.empty()) + asset.AddMember("copyright", Value(mAsset.asset.copyright, mAl).Move(), mAl); + mDoc.AddMember("asset", asset, mAl); + } + + inline void AssetWriter::WriteExtensionsUsed() + { + Value exts; + exts.SetArray(); + { + // This is used to export pbrSpecularGlossiness materials with GLTF 2. + if (this->mAsset.extensionsUsed.KHR_materials_pbrSpecularGlossiness) { + exts.PushBack(StringRef("KHR_materials_pbrSpecularGlossiness"), mAl); + } + + if (this->mAsset.extensionsUsed.KHR_materials_unlit) { + exts.PushBack(StringRef("KHR_materials_unlit"), mAl); + } + + if (this->mAsset.extensionsUsed.KHR_materials_sheen) { + exts.PushBack(StringRef("KHR_materials_sheen"), mAl); + } + + if (this->mAsset.extensionsUsed.KHR_materials_clearcoat) { + exts.PushBack(StringRef("KHR_materials_clearcoat"), mAl); + } + + if (this->mAsset.extensionsUsed.KHR_materials_transmission) { + exts.PushBack(StringRef("KHR_materials_transmission"), mAl); + } + + if (this->mAsset.extensionsUsed.KHR_materials_volume) { + exts.PushBack(StringRef("KHR_materials_volume"), mAl); + } + + if (this->mAsset.extensionsUsed.KHR_materials_ior) { + exts.PushBack(StringRef("KHR_materials_ior"), mAl); + } + + if (this->mAsset.extensionsUsed.FB_ngon_encoding) { + exts.PushBack(StringRef("FB_ngon_encoding"), mAl); + } + + if (this->mAsset.extensionsUsed.KHR_texture_basisu) { + exts.PushBack(StringRef("KHR_texture_basisu"), mAl); + } + } + + if (!exts.Empty()) + mDoc.AddMember("extensionsUsed", exts, mAl); + + //basisu extensionRequired + Value extsReq; + extsReq.SetArray(); + if (this->mAsset.extensionsUsed.KHR_texture_basisu) { + extsReq.PushBack(StringRef("KHR_texture_basisu"), mAl); + mDoc.AddMember("extensionsRequired", extsReq, mAl); + } + } + + template<class T> + void AssetWriter::WriteObjects(LazyDict<T>& d) + { + if (d.mObjs.empty()) return; + + Value* container = &mDoc; + const char* context = "Document"; + + if (d.mExtId) { + Value* exts = FindObject(mDoc, "extensions"); + if (nullptr != exts) { + mDoc.AddMember("extensions", Value().SetObject().Move(), mDoc.GetAllocator()); + exts = FindObject(mDoc, "extensions"); + } + + container = FindObjectInContext(*exts, d.mExtId, "extensions"); + if (nullptr != container) { + exts->AddMember(StringRef(d.mExtId), Value().SetObject().Move(), mDoc.GetAllocator()); + container = FindObjectInContext(*exts, d.mExtId, "extensions"); + context = d.mExtId; + } + } + + Value *dict = FindArrayInContext(*container, d.mDictId, context); + if (nullptr == dict) { + container->AddMember(StringRef(d.mDictId), Value().SetArray().Move(), mDoc.GetAllocator()); + dict = FindArrayInContext(*container, d.mDictId, context); + if (nullptr == dict) { + return; + } + } + + for (size_t i = 0; i < d.mObjs.size(); ++i) { + if (d.mObjs[i]->IsSpecial()) { + continue; + } + + Value obj; + obj.SetObject(); + + if (!d.mObjs[i]->name.empty()) { + obj.AddMember("name", StringRef(d.mObjs[i]->name.c_str()), mAl); + } + + Write(obj, *d.mObjs[i], *this); + + dict->PushBack(obj, mAl); + } + } + + template<class T> + void WriteLazyDict(LazyDict<T>& d, AssetWriter& w) + { + w.WriteObjects(d); + } + +} diff --git a/libs/assimp/code/AssetLib/glTF2/glTF2Exporter.cpp b/libs/assimp/code/AssetLib/glTF2/glTF2Exporter.cpp new file mode 100644 index 0000000..ffd8d22 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -0,0 +1,1542 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER + +#include "AssetLib/glTF2/glTF2Exporter.h" +#include "AssetLib/glTF2/glTF2AssetWriter.h" +#include "PostProcessing/SplitLargeMeshes.h" + +#include <assimp/ByteSwapper.h> +#include <assimp/Exceptional.h> +#include <assimp/SceneCombiner.h> +#include <assimp/StringComparison.h> +#include <assimp/commonMetaData.h> +#include <assimp/material.h> +#include <assimp/scene.h> +#include <assimp/version.h> +#include <assimp/Exporter.hpp> +#include <assimp/IOSystem.hpp> + +// Header files, standard library. +#include <cinttypes> +#include <limits> +#include <memory> + +using namespace rapidjson; + +using namespace Assimp; +using namespace glTF2; + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +// Worker function for exporting a scene to GLTF. Prototyped and registered in Exporter.cpp +void ExportSceneGLTF2(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties *pProperties) { + // invoke the exporter + glTF2Exporter exporter(pFile, pIOSystem, pScene, pProperties, false); +} + +// ------------------------------------------------------------------------------------------------ +// Worker function for exporting a scene to GLB. Prototyped and registered in Exporter.cpp +void ExportSceneGLB2(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties *pProperties) { + // invoke the exporter + glTF2Exporter exporter(pFile, pIOSystem, pScene, pProperties, true); +} + +} // end of namespace Assimp + +glTF2Exporter::glTF2Exporter(const char *filename, IOSystem *pIOSystem, const aiScene *pScene, + const ExportProperties *pProperties, bool isBinary) : + mFilename(filename), mIOSystem(pIOSystem), mScene(pScene), mProperties(pProperties), mAsset(new Asset(pIOSystem)) { + // Always on as our triangulation process is aware of this type of encoding + mAsset->extensionsUsed.FB_ngon_encoding = true; + + if (isBinary) { + mAsset->SetAsBinary(); + } + + ExportMetadata(); + + ExportMaterials(); + + if (mScene->mRootNode) { + ExportNodeHierarchy(mScene->mRootNode); + } + + ExportMeshes(); + MergeMeshes(); + + ExportScene(); + + ExportAnimations(); + + // export extras + if (mProperties->HasPropertyCallback("extras")) { + std::function<void *(void *)> ExportExtras = mProperties->GetPropertyCallback("extras"); + mAsset->extras = (rapidjson::Value *)ExportExtras(0); + } + + AssetWriter writer(*mAsset); + + if (isBinary) { + writer.WriteGLBFile(filename); + } else { + writer.WriteFile(filename); + } +} + +glTF2Exporter::~glTF2Exporter() { + // empty +} + +/* + * Copy a 4x4 matrix from struct aiMatrix to typedef mat4. + * Also converts from row-major to column-major storage. + */ +static void CopyValue(const aiMatrix4x4 &v, mat4 &o) { + o[0] = v.a1; + o[1] = v.b1; + o[2] = v.c1; + o[3] = v.d1; + o[4] = v.a2; + o[5] = v.b2; + o[6] = v.c2; + o[7] = v.d2; + o[8] = v.a3; + o[9] = v.b3; + o[10] = v.c3; + o[11] = v.d3; + o[12] = v.a4; + o[13] = v.b4; + o[14] = v.c4; + o[15] = v.d4; +} + +static void CopyValue(const aiMatrix4x4 &v, aiMatrix4x4 &o) { + memcpy(&o, &v, sizeof(aiMatrix4x4)); +} + +static void IdentityMatrix4(mat4 &o) { + o[0] = 1; + o[1] = 0; + o[2] = 0; + o[3] = 0; + o[4] = 0; + o[5] = 1; + o[6] = 0; + o[7] = 0; + o[8] = 0; + o[9] = 0; + o[10] = 1; + o[11] = 0; + o[12] = 0; + o[13] = 0; + o[14] = 0; + o[15] = 1; +} + +static bool IsBoneWeightFitted(vec4 &weight) { + return weight[0] + weight[1] + weight[2] + weight[3] >= 1.f; +} + +static int FitBoneWeight(vec4 &weight, float value) { + int i = 0; + for (; i < 4; ++i) { + if (weight[i] < value) { + weight[i] = value; + return i; + } + } + + return -1; +} + +template <typename T> +void SetAccessorRange(Ref<Accessor> acc, void *data, size_t count, + unsigned int numCompsIn, unsigned int numCompsOut) { + ai_assert(numCompsOut <= numCompsIn); + + // Allocate and initialize with large values. + for (unsigned int i = 0; i < numCompsOut; i++) { + acc->min.push_back(std::numeric_limits<double>::max()); + acc->max.push_back(-std::numeric_limits<double>::max()); + } + + size_t totalComps = count * numCompsIn; + T *buffer_ptr = static_cast<T *>(data); + T *buffer_end = buffer_ptr + totalComps; + + // Search and set extreme values. + for (; buffer_ptr < buffer_end; buffer_ptr += numCompsIn) { + for (unsigned int j = 0; j < numCompsOut; j++) { + double valueTmp = buffer_ptr[j]; + + // Gracefully tolerate rogue NaN's in buffer data + // Any NaNs/Infs introduced in accessor bounds will end up in + // document and prevent rapidjson from writing out valid JSON + if (!std::isfinite(valueTmp)) { + continue; + } + + if (valueTmp < acc->min[j]) { + acc->min[j] = valueTmp; + } + if (valueTmp > acc->max[j]) { + acc->max[j] = valueTmp; + } + } + } +} + +inline void SetAccessorRange(ComponentType compType, Ref<Accessor> acc, void *data, + size_t count, unsigned int numCompsIn, unsigned int numCompsOut) { + switch (compType) { + case ComponentType_SHORT: + SetAccessorRange<short>(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_UNSIGNED_SHORT: + SetAccessorRange<unsigned short>(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_UNSIGNED_INT: + SetAccessorRange<unsigned int>(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_FLOAT: + SetAccessorRange<float>(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_BYTE: + SetAccessorRange<int8_t>(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_UNSIGNED_BYTE: + SetAccessorRange<uint8_t>(acc, data, count, numCompsIn, numCompsOut); + return; + } +} + +// compute the (data-dataBase), store the non-zero data items +template <typename T> +size_t NZDiff(void *data, void *dataBase, size_t count, unsigned int numCompsIn, unsigned int numCompsOut, void *&outputNZDiff, void *&outputNZIdx) { + std::vector<T> vNZDiff; + std::vector<unsigned short> vNZIdx; + size_t totalComps = count * numCompsIn; + T *bufferData_ptr = static_cast<T *>(data); + T *bufferData_end = bufferData_ptr + totalComps; + T *bufferBase_ptr = static_cast<T *>(dataBase); + + // Search and set extreme values. + for (short idx = 0; bufferData_ptr < bufferData_end; idx += 1, bufferData_ptr += numCompsIn) { + bool bNonZero = false; + + //for the data, check any component Non Zero + for (unsigned int j = 0; j < numCompsOut; j++) { + double valueData = bufferData_ptr[j]; + double valueBase = bufferBase_ptr ? bufferBase_ptr[j] : 0; + if ((valueData - valueBase) != 0) { + bNonZero = true; + break; + } + } + + //all zeros, continue + if (!bNonZero) + continue; + + //non zero, store the data + for (unsigned int j = 0; j < numCompsOut; j++) { + T valueData = bufferData_ptr[j]; + T valueBase = bufferBase_ptr ? bufferBase_ptr[j] : 0; + vNZDiff.push_back(valueData - valueBase); + } + vNZIdx.push_back(idx); + } + + //avoid all-0, put 1 item + if (vNZDiff.size() == 0) { + for (unsigned int j = 0; j < numCompsOut; j++) + vNZDiff.push_back(0); + vNZIdx.push_back(0); + } + + //process data + outputNZDiff = new T[vNZDiff.size()]; + memcpy(outputNZDiff, vNZDiff.data(), vNZDiff.size() * sizeof(T)); + + outputNZIdx = new unsigned short[vNZIdx.size()]; + memcpy(outputNZIdx, vNZIdx.data(), vNZIdx.size() * sizeof(unsigned short)); + return vNZIdx.size(); +} + +inline size_t NZDiff(ComponentType compType, void *data, void *dataBase, size_t count, unsigned int numCompsIn, unsigned int numCompsOut, void *&nzDiff, void *&nzIdx) { + switch (compType) { + case ComponentType_SHORT: + return NZDiff<short>(data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx); + case ComponentType_UNSIGNED_SHORT: + return NZDiff<unsigned short>(data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx); + case ComponentType_UNSIGNED_INT: + return NZDiff<unsigned int>(data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx); + case ComponentType_FLOAT: + return NZDiff<float>(data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx); + case ComponentType_BYTE: + return NZDiff<int8_t>(data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx); + case ComponentType_UNSIGNED_BYTE: + return NZDiff<uint8_t>(data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx); + } + return 0; +} + +inline Ref<Accessor> ExportDataSparse(Asset &a, std::string &meshName, Ref<Buffer> &buffer, + size_t count, void *data, AttribType::Value typeIn, AttribType::Value typeOut, ComponentType compType, BufferViewTarget target = BufferViewTarget_NONE, void *dataBase = 0) { + if (!count || !data) { + return Ref<Accessor>(); + } + + unsigned int numCompsIn = AttribType::GetNumComponents(typeIn); + unsigned int numCompsOut = AttribType::GetNumComponents(typeOut); + unsigned int bytesPerComp = ComponentTypeSize(compType); + + // accessor + Ref<Accessor> acc = a.accessors.Create(a.FindUniqueID(meshName, "accessor")); + + // if there is a basic data vector + if (dataBase) { + size_t base_offset = buffer->byteLength; + size_t base_padding = base_offset % bytesPerComp; + base_offset += base_padding; + size_t base_length = count * numCompsOut * bytesPerComp; + buffer->Grow(base_length + base_padding); + + Ref<BufferView> bv = a.bufferViews.Create(a.FindUniqueID(meshName, "view")); + bv->buffer = buffer; + bv->byteOffset = base_offset; + bv->byteLength = base_length; //! The target that the WebGL buffer should be bound to. + bv->byteStride = 0; + bv->target = target; + acc->bufferView = bv; + acc->WriteData(count, dataBase, numCompsIn * bytesPerComp); + } + acc->byteOffset = 0; + acc->componentType = compType; + acc->count = count; + acc->type = typeOut; + + if (data) { + void *nzDiff = 0, *nzIdx = 0; + size_t nzCount = NZDiff(compType, data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx); + acc->sparse.reset(new Accessor::Sparse); + acc->sparse->count = nzCount; + + //indices + unsigned int bytesPerIdx = sizeof(unsigned short); + size_t indices_offset = buffer->byteLength; + size_t indices_padding = indices_offset % bytesPerIdx; + indices_offset += indices_padding; + size_t indices_length = nzCount * 1 * bytesPerIdx; + buffer->Grow(indices_length + indices_padding); + + Ref<BufferView> indicesBV = a.bufferViews.Create(a.FindUniqueID(meshName, "view")); + indicesBV->buffer = buffer; + indicesBV->byteOffset = indices_offset; + indicesBV->byteLength = indices_length; + indicesBV->byteStride = 0; + acc->sparse->indices = indicesBV; + acc->sparse->indicesType = ComponentType_UNSIGNED_SHORT; + acc->sparse->indicesByteOffset = 0; + acc->WriteSparseIndices(nzCount, nzIdx, 1 * bytesPerIdx); + + //values + size_t values_offset = buffer->byteLength; + size_t values_padding = values_offset % bytesPerComp; + values_offset += values_padding; + size_t values_length = nzCount * numCompsOut * bytesPerComp; + buffer->Grow(values_length + values_padding); + + Ref<BufferView> valuesBV = a.bufferViews.Create(a.FindUniqueID(meshName, "view")); + valuesBV->buffer = buffer; + valuesBV->byteOffset = values_offset; + valuesBV->byteLength = values_length; + valuesBV->byteStride = 0; + acc->sparse->values = valuesBV; + acc->sparse->valuesByteOffset = 0; + acc->WriteSparseValues(nzCount, nzDiff, numCompsIn * bytesPerComp); + + //clear + delete[](char *) nzDiff; + delete[](char *) nzIdx; + } + return acc; +} +inline Ref<Accessor> ExportData(Asset &a, std::string &meshName, Ref<Buffer> &buffer, + size_t count, void *data, AttribType::Value typeIn, AttribType::Value typeOut, ComponentType compType, BufferViewTarget target = BufferViewTarget_NONE) { + if (!count || !data) { + return Ref<Accessor>(); + } + + unsigned int numCompsIn = AttribType::GetNumComponents(typeIn); + unsigned int numCompsOut = AttribType::GetNumComponents(typeOut); + unsigned int bytesPerComp = ComponentTypeSize(compType); + + size_t offset = buffer->byteLength; + // make sure offset is correctly byte-aligned, as required by spec + size_t padding = offset % bytesPerComp; + offset += padding; + size_t length = count * numCompsOut * bytesPerComp; + buffer->Grow(length + padding); + + // bufferView + Ref<BufferView> bv = a.bufferViews.Create(a.FindUniqueID(meshName, "view")); + bv->buffer = buffer; + bv->byteOffset = offset; + bv->byteLength = length; //! The target that the WebGL buffer should be bound to. + bv->byteStride = 0; + bv->target = target; + + // accessor + Ref<Accessor> acc = a.accessors.Create(a.FindUniqueID(meshName, "accessor")); + acc->bufferView = bv; + acc->byteOffset = 0; + acc->componentType = compType; + acc->count = count; + acc->type = typeOut; + + // calculate min and max values + SetAccessorRange(compType, acc, data, count, numCompsIn, numCompsOut); + + // copy the data + acc->WriteData(count, data, numCompsIn * bytesPerComp); + + return acc; +} + +inline void SetSamplerWrap(SamplerWrap &wrap, aiTextureMapMode map) { + switch (map) { + case aiTextureMapMode_Clamp: + wrap = SamplerWrap::Clamp_To_Edge; + break; + case aiTextureMapMode_Mirror: + wrap = SamplerWrap::Mirrored_Repeat; + break; + case aiTextureMapMode_Wrap: + case aiTextureMapMode_Decal: + default: + wrap = SamplerWrap::Repeat; + break; + }; +} + +void glTF2Exporter::GetTexSampler(const aiMaterial &mat, Ref<Texture> texture, aiTextureType tt, unsigned int slot) { + aiString aId; + std::string id; + if (aiGetMaterialString(&mat, AI_MATKEY_GLTF_MAPPINGID(tt, slot), &aId) == AI_SUCCESS) { + id = aId.C_Str(); + } + + if (Ref<Sampler> ref = mAsset->samplers.Get(id.c_str())) { + texture->sampler = ref; + } else { + id = mAsset->FindUniqueID(id, "sampler"); + + texture->sampler = mAsset->samplers.Create(id.c_str()); + + aiTextureMapMode mapU, mapV; + SamplerMagFilter filterMag; + SamplerMinFilter filterMin; + + if (aiGetMaterialInteger(&mat, AI_MATKEY_MAPPINGMODE_U(tt, slot), (int *)&mapU) == AI_SUCCESS) { + SetSamplerWrap(texture->sampler->wrapS, mapU); + } + + if (aiGetMaterialInteger(&mat, AI_MATKEY_MAPPINGMODE_V(tt, slot), (int *)&mapV) == AI_SUCCESS) { + SetSamplerWrap(texture->sampler->wrapT, mapV); + } + + if (aiGetMaterialInteger(&mat, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(tt, slot), (int *)&filterMag) == AI_SUCCESS) { + texture->sampler->magFilter = filterMag; + } + + if (aiGetMaterialInteger(&mat, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(tt, slot), (int *)&filterMin) == AI_SUCCESS) { + texture->sampler->minFilter = filterMin; + } + + aiString name; + if (aiGetMaterialString(&mat, AI_MATKEY_GLTF_MAPPINGNAME(tt, slot), &name) == AI_SUCCESS) { + texture->sampler->name = name.C_Str(); + } + } +} + +void glTF2Exporter::GetMatTexProp(const aiMaterial &mat, unsigned int &prop, const char *propName, aiTextureType tt, unsigned int slot) { + std::string textureKey = std::string(_AI_MATKEY_TEXTURE_BASE) + "." + propName; + + mat.Get(textureKey.c_str(), tt, slot, prop); +} + +void glTF2Exporter::GetMatTexProp(const aiMaterial &mat, float &prop, const char *propName, aiTextureType tt, unsigned int slot) { + std::string textureKey = std::string(_AI_MATKEY_TEXTURE_BASE) + "." + propName; + + mat.Get(textureKey.c_str(), tt, slot, prop); +} + +void glTF2Exporter::GetMatTex(const aiMaterial &mat, Ref<Texture> &texture, unsigned int &texCoord, aiTextureType tt, unsigned int slot = 0) { + if (mat.GetTextureCount(tt) == 0) { + return; + } + + aiString tex; + + // Read texcoord (UV map index) + mat.Get(AI_MATKEY_UVWSRC(tt, slot), texCoord); + + if (mat.Get(AI_MATKEY_TEXTURE(tt, slot), tex) == AI_SUCCESS) { + std::string path = tex.C_Str(); + + if (path.size() > 0) { + std::map<std::string, unsigned int>::iterator it = mTexturesByPath.find(path); + if (it != mTexturesByPath.end()) { + texture = mAsset->textures.Get(it->second); + } + + bool useBasisUniversal = false; + if (!texture) { + std::string texId = mAsset->FindUniqueID("", "texture"); + texture = mAsset->textures.Create(texId); + mTexturesByPath[path] = texture.GetIndex(); + + std::string imgId = mAsset->FindUniqueID("", "image"); + texture->source = mAsset->images.Create(imgId); + + const aiTexture *curTex = mScene->GetEmbeddedTexture(path.c_str()); + if (curTex != nullptr) { // embedded + texture->source->name = curTex->mFilename.C_Str(); + + //basisu: embedded ktx2, bu + if (curTex->achFormatHint[0]) { + std::string mimeType = "image/"; + if (memcmp(curTex->achFormatHint, "jpg", 3) == 0) + mimeType += "jpeg"; + else if (memcmp(curTex->achFormatHint, "ktx", 3) == 0) { + useBasisUniversal = true; + mimeType += "ktx"; + } else if (memcmp(curTex->achFormatHint, "kx2", 3) == 0) { + useBasisUniversal = true; + mimeType += "ktx2"; + } else if (memcmp(curTex->achFormatHint, "bu", 2) == 0) { + useBasisUniversal = true; + mimeType += "basis"; + } else + mimeType += curTex->achFormatHint; + texture->source->mimeType = mimeType; + } + + // The asset has its own buffer, see Image::SetData + //basisu: "image/ktx2", "image/basis" as is + texture->source->SetData(reinterpret_cast<uint8_t *>(curTex->pcData), curTex->mWidth, *mAsset); + } else { + texture->source->uri = path; + if (texture->source->uri.find(".ktx") != std::string::npos || + texture->source->uri.find(".basis") != std::string::npos) { + useBasisUniversal = true; + } + } + + //basisu + if (useBasisUniversal) { + mAsset->extensionsUsed.KHR_texture_basisu = true; + mAsset->extensionsRequired.KHR_texture_basisu = true; + } + + GetTexSampler(mat, texture, tt, slot); + } + } + } +} + +void glTF2Exporter::GetMatTex(const aiMaterial &mat, TextureInfo &prop, aiTextureType tt, unsigned int slot = 0) { + Ref<Texture> &texture = prop.texture; + GetMatTex(mat, texture, prop.texCoord, tt, slot); +} + +void glTF2Exporter::GetMatTex(const aiMaterial &mat, NormalTextureInfo &prop, aiTextureType tt, unsigned int slot = 0) { + Ref<Texture> &texture = prop.texture; + + GetMatTex(mat, texture, prop.texCoord, tt, slot); + + if (texture) { + //GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); + GetMatTexProp(mat, prop.scale, "scale", tt, slot); + } +} + +void glTF2Exporter::GetMatTex(const aiMaterial &mat, OcclusionTextureInfo &prop, aiTextureType tt, unsigned int slot = 0) { + Ref<Texture> &texture = prop.texture; + + GetMatTex(mat, texture, prop.texCoord, tt, slot); + + if (texture) { + //GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); + GetMatTexProp(mat, prop.strength, "strength", tt, slot); + } +} + +aiReturn glTF2Exporter::GetMatColor(const aiMaterial &mat, vec4 &prop, const char *propName, int type, int idx) const { + aiColor4D col; + aiReturn result = mat.Get(propName, type, idx, col); + + if (result == AI_SUCCESS) { + prop[0] = col.r; + prop[1] = col.g; + prop[2] = col.b; + prop[3] = col.a; + } + + return result; +} + +aiReturn glTF2Exporter::GetMatColor(const aiMaterial &mat, vec3 &prop, const char *propName, int type, int idx) const { + aiColor3D col; + aiReturn result = mat.Get(propName, type, idx, col); + + if (result == AI_SUCCESS) { + prop[0] = col.r; + prop[1] = col.g; + prop[2] = col.b; + } + + return result; +} + +bool glTF2Exporter::GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlossiness &pbrSG) { + bool result = false; + // If has Glossiness, a Specular Color or Specular Texture, use the KHR_materials_pbrSpecularGlossiness extension + // NOTE: This extension is being considered for deprecation (Dec 2020), may be replaced by KHR_material_specular + + if (mat.Get(AI_MATKEY_GLOSSINESS_FACTOR, pbrSG.glossinessFactor) == AI_SUCCESS) { + result = true; + } else { + // Don't have explicit glossiness, convert from pbr roughness or legacy shininess + float shininess; + if (mat.Get(AI_MATKEY_ROUGHNESS_FACTOR, shininess) == AI_SUCCESS) { + pbrSG.glossinessFactor = 1.0f - shininess; // Extension defines this way + } else if (mat.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) { + pbrSG.glossinessFactor = shininess / 1000; + } + } + + if (GetMatColor(mat, pbrSG.specularFactor, AI_MATKEY_COLOR_SPECULAR) == AI_SUCCESS) { + result = true; + } + // Add any appropriate textures + GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR); + + result = result || pbrSG.specularGlossinessTexture.texture; + + if (result) { + // Likely to always have diffuse + GetMatTex(mat, pbrSG.diffuseTexture, aiTextureType_DIFFUSE); + GetMatColor(mat, pbrSG.diffuseFactor, AI_MATKEY_COLOR_DIFFUSE); + } + + return result; +} + +bool glTF2Exporter::GetMatSheen(const aiMaterial &mat, glTF2::MaterialSheen &sheen) { + // Return true if got any valid Sheen properties or textures + if (GetMatColor(mat, sheen.sheenColorFactor, AI_MATKEY_SHEEN_COLOR_FACTOR) != aiReturn_SUCCESS) { + return false; + } + + // Default Sheen color factor {0,0,0} disables Sheen, so do not export + if (sheen.sheenColorFactor == defaultSheenFactor) { + return false; + } + + mat.Get(AI_MATKEY_SHEEN_ROUGHNESS_FACTOR, sheen.sheenRoughnessFactor); + + GetMatTex(mat, sheen.sheenColorTexture, AI_MATKEY_SHEEN_COLOR_TEXTURE); + GetMatTex(mat, sheen.sheenRoughnessTexture, AI_MATKEY_SHEEN_ROUGHNESS_TEXTURE); + + return true; +} + +bool glTF2Exporter::GetMatClearcoat(const aiMaterial &mat, glTF2::MaterialClearcoat &clearcoat) { + if (mat.Get(AI_MATKEY_CLEARCOAT_FACTOR, clearcoat.clearcoatFactor) != aiReturn_SUCCESS) { + return false; + } + + // Clearcoat factor of zero disables Clearcoat, so do not export + if (clearcoat.clearcoatFactor == 0.0f) + return false; + + mat.Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, clearcoat.clearcoatRoughnessFactor); + + GetMatTex(mat, clearcoat.clearcoatTexture, AI_MATKEY_CLEARCOAT_TEXTURE); + GetMatTex(mat, clearcoat.clearcoatRoughnessTexture, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE); + GetMatTex(mat, clearcoat.clearcoatNormalTexture, AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE); + + return true; +} + +bool glTF2Exporter::GetMatTransmission(const aiMaterial &mat, glTF2::MaterialTransmission &transmission) { + bool result = mat.Get(AI_MATKEY_TRANSMISSION_FACTOR, transmission.transmissionFactor) == aiReturn_SUCCESS; + GetMatTex(mat, transmission.transmissionTexture, AI_MATKEY_TRANSMISSION_TEXTURE); + return result || transmission.transmissionTexture.texture; +} + +bool glTF2Exporter::GetMatVolume(const aiMaterial &mat, glTF2::MaterialVolume &volume) { + bool result = mat.Get(AI_MATKEY_VOLUME_THICKNESS_FACTOR, volume.thicknessFactor) != aiReturn_SUCCESS; + + GetMatTex(mat, volume.thicknessTexture, AI_MATKEY_VOLUME_THICKNESS_TEXTURE); + + result = result || mat.Get(AI_MATKEY_VOLUME_ATTENUATION_DISTANCE, volume.attenuationDistance); + result = result || GetMatColor(mat, volume.attenuationColor, AI_MATKEY_VOLUME_ATTENUATION_COLOR) != aiReturn_SUCCESS; + + // Valid if any of these properties are available + return result || volume.thicknessTexture.texture; +} + +bool glTF2Exporter::GetMatIOR(const aiMaterial &mat, glTF2::MaterialIOR &ior) { + return mat.Get(AI_MATKEY_REFRACTI, ior.ior) == aiReturn_SUCCESS; +} + +void glTF2Exporter::ExportMaterials() { + aiString aiName; + for (unsigned int i = 0; i < mScene->mNumMaterials; ++i) { + ai_assert(mScene->mMaterials[i] != nullptr); + + const aiMaterial &mat = *(mScene->mMaterials[i]); + + std::string id = "material_" + ai_to_string(i); + + Ref<Material> m = mAsset->materials.Create(id); + + std::string name; + if (mat.Get(AI_MATKEY_NAME, aiName) == AI_SUCCESS) { + name = aiName.C_Str(); + } + name = mAsset->FindUniqueID(name, "material"); + + m->name = name; + + GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, aiTextureType_BASE_COLOR); + + if (!m->pbrMetallicRoughness.baseColorTexture.texture) { + //if there wasn't a baseColorTexture defined in the source, fallback to any diffuse texture + GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, aiTextureType_DIFFUSE); + } + + GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); + + if (GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_BASE_COLOR) != AI_SUCCESS) { + // if baseColorFactor wasn't defined, then the source is likely not a metallic roughness material. + //a fallback to any diffuse color should be used instead + GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_COLOR_DIFFUSE); + } + + if (mat.Get(AI_MATKEY_METALLIC_FACTOR, m->pbrMetallicRoughness.metallicFactor) != AI_SUCCESS) { + //if metallicFactor wasn't defined, then the source is likely not a PBR file, and the metallicFactor should be 0 + m->pbrMetallicRoughness.metallicFactor = 0; + } + + // get roughness if source is gltf2 file + if (mat.Get(AI_MATKEY_ROUGHNESS_FACTOR, m->pbrMetallicRoughness.roughnessFactor) != AI_SUCCESS) { + // otherwise, try to derive and convert from specular + shininess values + aiColor4D specularColor; + ai_real shininess; + + if (mat.Get(AI_MATKEY_COLOR_SPECULAR, specularColor) == AI_SUCCESS && mat.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) { + // convert specular color to luminance + float specularIntensity = specularColor[0] * 0.2125f + specularColor[1] * 0.7154f + specularColor[2] * 0.0721f; + //normalize shininess (assuming max is 1000) with an inverse exponentional curve + float normalizedShininess = std::sqrt(shininess / 1000); + + //clamp the shininess value between 0 and 1 + normalizedShininess = std::min(std::max(normalizedShininess, 0.0f), 1.0f); + // low specular intensity values should produce a rough material even if shininess is high. + normalizedShininess = normalizedShininess * specularIntensity; + + m->pbrMetallicRoughness.roughnessFactor = 1 - normalizedShininess; + } + } + + GetMatTex(mat, m->normalTexture, aiTextureType_NORMALS); + GetMatTex(mat, m->occlusionTexture, aiTextureType_LIGHTMAP); + GetMatTex(mat, m->emissiveTexture, aiTextureType_EMISSIVE); + GetMatColor(mat, m->emissiveFactor, AI_MATKEY_COLOR_EMISSIVE); + + mat.Get(AI_MATKEY_TWOSIDED, m->doubleSided); + mat.Get(AI_MATKEY_GLTF_ALPHACUTOFF, m->alphaCutoff); + + float opacity; + aiString alphaMode; + + if (mat.Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) { + if (opacity < 1) { + m->alphaMode = "BLEND"; + m->pbrMetallicRoughness.baseColorFactor[3] *= opacity; + } + } + if (mat.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode) == AI_SUCCESS) { + m->alphaMode = alphaMode.C_Str(); + } + + { + // KHR_materials_pbrSpecularGlossiness extension + // NOTE: This extension is being considered for deprecation (Dec 2020) + PbrSpecularGlossiness pbrSG; + if (GetMatSpecGloss(mat, pbrSG)) { + mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true; + m->pbrSpecularGlossiness = Nullable<PbrSpecularGlossiness>(pbrSG); + } + } + + // glTFv2 is either PBR or Unlit + aiShadingMode shadingMode = aiShadingMode_PBR_BRDF; + mat.Get(AI_MATKEY_SHADING_MODEL, shadingMode); + if (shadingMode == aiShadingMode_Unlit) { + mAsset->extensionsUsed.KHR_materials_unlit = true; + m->unlit = true; + } else { + // These extensions are not compatible with KHR_materials_unlit or KHR_materials_pbrSpecularGlossiness + if (!m->pbrSpecularGlossiness.isPresent) { + // Sheen + MaterialSheen sheen; + if (GetMatSheen(mat, sheen)) { + mAsset->extensionsUsed.KHR_materials_sheen = true; + m->materialSheen = Nullable<MaterialSheen>(sheen); + } + + MaterialClearcoat clearcoat; + if (GetMatClearcoat(mat, clearcoat)) { + mAsset->extensionsUsed.KHR_materials_clearcoat = true; + m->materialClearcoat = Nullable<MaterialClearcoat>(clearcoat); + } + + MaterialTransmission transmission; + if (GetMatTransmission(mat, transmission)) { + mAsset->extensionsUsed.KHR_materials_transmission = true; + m->materialTransmission = Nullable<MaterialTransmission>(transmission); + } + + MaterialVolume volume; + if (GetMatVolume(mat, volume)) { + mAsset->extensionsUsed.KHR_materials_volume = true; + m->materialVolume = Nullable<MaterialVolume>(volume); + } + + MaterialIOR ior; + if (GetMatIOR(mat, ior)) { + mAsset->extensionsUsed.KHR_materials_ior = true; + m->materialIOR = Nullable<MaterialIOR>(ior); + } + } + } + } +} + +/* + * Search through node hierarchy and find the node containing the given meshID. + * Returns true on success, and false otherwise. + */ +bool FindMeshNode(Ref<Node> &nodeIn, Ref<Node> &meshNode, const std::string &meshID) { + for (unsigned int i = 0; i < nodeIn->meshes.size(); ++i) { + if (meshID.compare(nodeIn->meshes[i]->id) == 0) { + meshNode = nodeIn; + return true; + } + } + + for (unsigned int i = 0; i < nodeIn->children.size(); ++i) { + if (FindMeshNode(nodeIn->children[i], meshNode, meshID)) { + return true; + } + } + + return false; +} + +/* + * Find the root joint of the skeleton. + * Starts will any joint node and traces up the tree, + * until a parent is found that does not have a jointName. + * Returns the first parent Ref<Node> found that does not have a jointName. + */ +Ref<Node> FindSkeletonRootJoint(Ref<Skin> &skinRef) { + Ref<Node> startNodeRef; + Ref<Node> parentNodeRef; + + // Arbitrarily use the first joint to start the search. + startNodeRef = skinRef->jointNames[0]; + parentNodeRef = skinRef->jointNames[0]; + + do { + startNodeRef = parentNodeRef; + parentNodeRef = startNodeRef->parent; + } while (!parentNodeRef->jointName.empty()); + + return parentNodeRef; +} + +void ExportSkin(Asset &mAsset, const aiMesh *aimesh, Ref<Mesh> &meshRef, Ref<Buffer> &bufferRef, Ref<Skin> &skinRef, + std::vector<aiMatrix4x4> &inverseBindMatricesData) { + if (aimesh->mNumBones < 1) { + return; + } + + // Store the vertex joint and weight data. + const size_t NumVerts(aimesh->mNumVertices); + vec4 *vertexJointData = new vec4[NumVerts]; + vec4 *vertexWeightData = new vec4[NumVerts]; + int *jointsPerVertex = new int[NumVerts]; + for (size_t i = 0; i < NumVerts; ++i) { + jointsPerVertex[i] = 0; + for (size_t j = 0; j < 4; ++j) { + vertexJointData[i][j] = 0; + vertexWeightData[i][j] = 0; + } + } + + for (unsigned int idx_bone = 0; idx_bone < aimesh->mNumBones; ++idx_bone) { + const aiBone *aib = aimesh->mBones[idx_bone]; + + // aib->mName =====> skinRef->jointNames + // Find the node with id = mName. + Ref<Node> nodeRef = mAsset.nodes.Get(aib->mName.C_Str()); + nodeRef->jointName = nodeRef->name; + + unsigned int jointNamesIndex = 0; + bool addJointToJointNames = true; + for (unsigned int idx_joint = 0; idx_joint < skinRef->jointNames.size(); ++idx_joint) { + if (skinRef->jointNames[idx_joint]->jointName.compare(nodeRef->jointName) == 0) { + addJointToJointNames = false; + jointNamesIndex = idx_joint; + } + } + + if (addJointToJointNames) { + skinRef->jointNames.push_back(nodeRef); + + // aib->mOffsetMatrix =====> skinRef->inverseBindMatrices + aiMatrix4x4 tmpMatrix4; + CopyValue(aib->mOffsetMatrix, tmpMatrix4); + inverseBindMatricesData.push_back(tmpMatrix4); + jointNamesIndex = static_cast<unsigned int>(inverseBindMatricesData.size() - 1); + } + + // aib->mWeights =====> vertexWeightData + for (unsigned int idx_weights = 0; idx_weights < aib->mNumWeights; ++idx_weights) { + unsigned int vertexId = aib->mWeights[idx_weights].mVertexId; + float vertWeight = aib->mWeights[idx_weights].mWeight; + + // A vertex can only have at most four joint weights, which ideally sum up to 1 + if (IsBoneWeightFitted(vertexWeightData[vertexId])) { + continue; + } + if (jointsPerVertex[vertexId] > 3) { + int boneIndexFitted = FitBoneWeight(vertexWeightData[vertexId], vertWeight); + if (boneIndexFitted != -1) { + vertexJointData[vertexId][boneIndexFitted] = static_cast<float>(jointNamesIndex); + } + }else { + vertexJointData[vertexId][jointsPerVertex[vertexId]] = static_cast<float>(jointNamesIndex); + vertexWeightData[vertexId][jointsPerVertex[vertexId]] = vertWeight; + + jointsPerVertex[vertexId] += 1; + } + } + + } // End: for-loop mNumMeshes + + Mesh::Primitive &p = meshRef->primitives.back(); + Ref<Accessor> vertexJointAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices, + vertexJointData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); + if (vertexJointAccessor) { + size_t offset = vertexJointAccessor->bufferView->byteOffset; + size_t bytesLen = vertexJointAccessor->bufferView->byteLength; + unsigned int s_bytesPerComp = ComponentTypeSize(ComponentType_UNSIGNED_SHORT); + unsigned int bytesPerComp = ComponentTypeSize(vertexJointAccessor->componentType); + size_t s_bytesLen = bytesLen * s_bytesPerComp / bytesPerComp; + Ref<Buffer> buf = vertexJointAccessor->bufferView->buffer; + uint8_t *arrys = new uint8_t[bytesLen]; + unsigned int i = 0; + for (unsigned int j = 0; j < bytesLen; j += bytesPerComp) { + size_t len_p = offset + j; + float f_value = *(float *)&buf->GetPointer()[len_p]; + unsigned short c = static_cast<unsigned short>(f_value); + memcpy(&arrys[i * s_bytesPerComp], &c, s_bytesPerComp); + ++i; + } + buf->ReplaceData_joint(offset, bytesLen, arrys, bytesLen); + vertexJointAccessor->componentType = ComponentType_UNSIGNED_SHORT; + vertexJointAccessor->bufferView->byteLength = s_bytesLen; + + p.attributes.joint.push_back(vertexJointAccessor); + delete[] arrys; + } + + Ref<Accessor> vertexWeightAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices, + vertexWeightData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); + if (vertexWeightAccessor) { + p.attributes.weight.push_back(vertexWeightAccessor); + } + delete[] jointsPerVertex; + delete[] vertexWeightData; + delete[] vertexJointData; +} + +void glTF2Exporter::ExportMeshes() { + typedef decltype(aiFace::mNumIndices) IndicesType; + + std::string fname = std::string(mFilename); + std::string bufferIdPrefix = fname.substr(0, fname.rfind(".gltf")); + std::string bufferId = mAsset->FindUniqueID("", bufferIdPrefix.c_str()); + + Ref<Buffer> b = mAsset->GetBodyBuffer(); + if (!b) { + b = mAsset->buffers.Create(bufferId); + } + + //---------------------------------------- + // Initialize variables for the skin + bool createSkin = false; + for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) { + const aiMesh *aim = mScene->mMeshes[idx_mesh]; + if (aim->HasBones()) { + createSkin = true; + break; + } + } + + Ref<Skin> skinRef; + std::string skinName = mAsset->FindUniqueID("skin", "skin"); + std::vector<aiMatrix4x4> inverseBindMatricesData; + if (createSkin) { + skinRef = mAsset->skins.Create(skinName); + skinRef->name = skinName; + } + //---------------------------------------- + + for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) { + const aiMesh *aim = mScene->mMeshes[idx_mesh]; + + std::string name = aim->mName.C_Str(); + + std::string meshId = mAsset->FindUniqueID(name, "mesh"); + Ref<Mesh> m = mAsset->meshes.Create(meshId); + m->primitives.resize(1); + Mesh::Primitive &p = m->primitives.back(); + + m->name = name; + + p.material = mAsset->materials.Get(aim->mMaterialIndex); + p.ngonEncoded = (aim->mPrimitiveTypes & aiPrimitiveType_NGONEncodingFlag) != 0; + + /******************* Vertices ********************/ + Ref<Accessor> v = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mVertices, AttribType::VEC3, + AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + if (v) { + p.attributes.position.push_back(v); + } + + /******************** Normals ********************/ + // Normalize all normals as the validator can emit a warning otherwise + if (nullptr != aim->mNormals) { + for (auto i = 0u; i < aim->mNumVertices; ++i) { + aim->mNormals[i].NormalizeSafe(); + } + } + + Ref<Accessor> n = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mNormals, AttribType::VEC3, + AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + if (n) { + p.attributes.normal.push_back(n); + } + + /************** Texture coordinates **************/ + for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (!aim->HasTextureCoords(i)) { + continue; + } + + // Flip UV y coords + if (aim->mNumUVComponents[i] > 1) { + for (unsigned int j = 0; j < aim->mNumVertices; ++j) { + aim->mTextureCoords[i][j].y = 1 - aim->mTextureCoords[i][j].y; + } + } + + if (aim->mNumUVComponents[i] > 0) { + AttribType::Value type = (aim->mNumUVComponents[i] == 2) ? AttribType::VEC2 : AttribType::VEC3; + + Ref<Accessor> tc = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mTextureCoords[i], + AttribType::VEC3, type, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + if (tc) { + p.attributes.texcoord.push_back(tc); + } + } + } + + /*************** Vertex colors ****************/ + for (unsigned int indexColorChannel = 0; indexColorChannel < aim->GetNumColorChannels(); ++indexColorChannel) { + Ref<Accessor> c = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mColors[indexColorChannel], + AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + if (c) { + p.attributes.color.push_back(c); + } + } + + /*************** Vertices indices ****************/ + if (aim->mNumFaces > 0) { + std::vector<IndicesType> indices; + unsigned int nIndicesPerFace = aim->mFaces[0].mNumIndices; + indices.resize(aim->mNumFaces * nIndicesPerFace); + for (size_t i = 0; i < aim->mNumFaces; ++i) { + for (size_t j = 0; j < nIndicesPerFace; ++j) { + indices[i * nIndicesPerFace + j] = IndicesType(aim->mFaces[i].mIndices[j]); + } + } + + p.indices = ExportData(*mAsset, meshId, b, indices.size(), &indices[0], AttribType::SCALAR, AttribType::SCALAR, + ComponentType_UNSIGNED_INT, BufferViewTarget_ELEMENT_ARRAY_BUFFER); + } + + switch (aim->mPrimitiveTypes) { + case aiPrimitiveType_POLYGON: + p.mode = PrimitiveMode_TRIANGLES; + break; // TODO implement this + case aiPrimitiveType_LINE: + p.mode = PrimitiveMode_LINES; + break; + case aiPrimitiveType_POINT: + p.mode = PrimitiveMode_POINTS; + break; + default: // aiPrimitiveType_TRIANGLE + p.mode = PrimitiveMode_TRIANGLES; + break; + } + + /*************** Skins ****************/ + if (aim->HasBones()) { + ExportSkin(*mAsset, aim, m, b, skinRef, inverseBindMatricesData); + } + + /*************** Targets for blendshapes ****************/ + if (aim->mNumAnimMeshes > 0) { + bool bUseSparse = this->mProperties->HasPropertyBool("GLTF2_SPARSE_ACCESSOR_EXP") && + this->mProperties->GetPropertyBool("GLTF2_SPARSE_ACCESSOR_EXP"); + bool bIncludeNormal = this->mProperties->HasPropertyBool("GLTF2_TARGET_NORMAL_EXP") && + this->mProperties->GetPropertyBool("GLTF2_TARGET_NORMAL_EXP"); + bool bExportTargetNames = this->mProperties->HasPropertyBool("GLTF2_TARGETNAMES_EXP") && + this->mProperties->GetPropertyBool("GLTF2_TARGETNAMES_EXP"); + + p.targets.resize(aim->mNumAnimMeshes); + for (unsigned int am = 0; am < aim->mNumAnimMeshes; ++am) { + aiAnimMesh *pAnimMesh = aim->mAnimMeshes[am]; + if (bExportTargetNames) { + m->targetNames.emplace_back(pAnimMesh->mName.data); + } + // position + if (pAnimMesh->HasPositions()) { + // NOTE: in gltf it is the diff stored + aiVector3D *pPositionDiff = new aiVector3D[pAnimMesh->mNumVertices]; + for (unsigned int vt = 0; vt < pAnimMesh->mNumVertices; ++vt) { + pPositionDiff[vt] = pAnimMesh->mVertices[vt] - aim->mVertices[vt]; + } + Ref<Accessor> vec; + if (bUseSparse) { + vec = ExportDataSparse(*mAsset, meshId, b, + pAnimMesh->mNumVertices, pPositionDiff, + AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); + } else { + vec = ExportData(*mAsset, meshId, b, + pAnimMesh->mNumVertices, pPositionDiff, + AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); + } + if (vec) { + p.targets[am].position.push_back(vec); + } + delete[] pPositionDiff; + } + + // normal + if (pAnimMesh->HasNormals() && bIncludeNormal) { + aiVector3D *pNormalDiff = new aiVector3D[pAnimMesh->mNumVertices]; + for (unsigned int vt = 0; vt < pAnimMesh->mNumVertices; ++vt) { + pNormalDiff[vt] = pAnimMesh->mNormals[vt] - aim->mNormals[vt]; + } + Ref<Accessor> vec; + if (bUseSparse) { + vec = ExportDataSparse(*mAsset, meshId, b, + pAnimMesh->mNumVertices, pNormalDiff, + AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); + } else { + vec = ExportData(*mAsset, meshId, b, + pAnimMesh->mNumVertices, pNormalDiff, + AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); + } + if (vec) { + p.targets[am].normal.push_back(vec); + } + delete[] pNormalDiff; + } + + // tangent? + } + } + } + + //---------------------------------------- + // Finish the skin + // Create the Accessor for skinRef->inverseBindMatrices + bool bAddCustomizedProperty = this->mProperties->HasPropertyBool("GLTF2_CUSTOMIZE_PROPERTY"); + if (createSkin) { + mat4 *invBindMatrixData = new mat4[inverseBindMatricesData.size()]; + for (unsigned int idx_joint = 0; idx_joint < inverseBindMatricesData.size(); ++idx_joint) { + CopyValue(inverseBindMatricesData[idx_joint], invBindMatrixData[idx_joint]); + } + + Ref<Accessor> invBindMatrixAccessor = ExportData(*mAsset, skinName, b, + static_cast<unsigned int>(inverseBindMatricesData.size()), + invBindMatrixData, AttribType::MAT4, AttribType::MAT4, ComponentType_FLOAT); + if (invBindMatrixAccessor) { + skinRef->inverseBindMatrices = invBindMatrixAccessor; + } + + // Identity Matrix =====> skinRef->bindShapeMatrix + // Temporary. Hard-coded identity matrix here + skinRef->bindShapeMatrix.isPresent = bAddCustomizedProperty; + IdentityMatrix4(skinRef->bindShapeMatrix.value); + + // Find nodes that contain a mesh with bones and add "skeletons" and "skin" attributes to those nodes. + Ref<Node> rootNode = mAsset->nodes.Get(unsigned(0)); + Ref<Node> meshNode; + for (unsigned int meshIndex = 0; meshIndex < mAsset->meshes.Size(); ++meshIndex) { + Ref<Mesh> mesh = mAsset->meshes.Get(meshIndex); + bool hasBones = false; + for (unsigned int i = 0; i < mesh->primitives.size(); ++i) { + if (!mesh->primitives[i].attributes.weight.empty()) { + hasBones = true; + break; + } + } + if (!hasBones) { + continue; + } + std::string meshID = mesh->id; + FindMeshNode(rootNode, meshNode, meshID); + Ref<Node> rootJoint = FindSkeletonRootJoint(skinRef); + if (bAddCustomizedProperty) + meshNode->skeletons.push_back(rootJoint); + meshNode->skin = skinRef; + } + delete[] invBindMatrixData; + } +} + +// Merges a node's multiple meshes (with one primitive each) into one mesh with multiple primitives +void glTF2Exporter::MergeMeshes() { + for (unsigned int n = 0; n < mAsset->nodes.Size(); ++n) { + Ref<Node> node = mAsset->nodes.Get(n); + + unsigned int nMeshes = static_cast<unsigned int>(node->meshes.size()); + + //skip if it's 1 or less meshes per node + if (nMeshes > 1) { + Ref<Mesh> firstMesh = node->meshes.at(0); + + //loop backwards to allow easy removal of a mesh from a node once it's merged + for (unsigned int m = nMeshes - 1; m >= 1; --m) { + Ref<Mesh> mesh = node->meshes.at(m); + + //append this mesh's primitives to the first mesh's primitives + firstMesh->primitives.insert( + firstMesh->primitives.end(), + mesh->primitives.begin(), + mesh->primitives.end()); + + //remove the mesh from the list of meshes + unsigned int removedIndex = mAsset->meshes.Remove(mesh->id.c_str()); + + //find the presence of the removed mesh in other nodes + for (unsigned int nn = 0; nn < mAsset->nodes.Size(); ++nn) { + Ref<Node> curNode = mAsset->nodes.Get(nn); + + for (unsigned int mm = 0; mm < curNode->meshes.size(); ++mm) { + Ref<Mesh> &meshRef = curNode->meshes.at(mm); + unsigned int meshIndex = meshRef.GetIndex(); + + if (meshIndex == removedIndex) { + curNode->meshes.erase(curNode->meshes.begin() + mm); + } else if (meshIndex > removedIndex) { + Ref<Mesh> newMeshRef = mAsset->meshes.Get(meshIndex - 1); + + meshRef = newMeshRef; + } + } + } + } + + //since we were looping backwards, reverse the order of merged primitives to their original order + std::reverse(firstMesh->primitives.begin() + 1, firstMesh->primitives.end()); + } + } +} + +/* + * Export the root node of the node hierarchy. + * Calls ExportNode for all children. + */ +unsigned int glTF2Exporter::ExportNodeHierarchy(const aiNode *n) { + Ref<Node> node = mAsset->nodes.Create(mAsset->FindUniqueID(n->mName.C_Str(), "node")); + + node->name = n->mName.C_Str(); + + if (!n->mTransformation.IsIdentity()) { + node->matrix.isPresent = true; + CopyValue(n->mTransformation, node->matrix.value); + } + + for (unsigned int i = 0; i < n->mNumMeshes; ++i) { + node->meshes.emplace_back(mAsset->meshes.Get(n->mMeshes[i])); + } + + for (unsigned int i = 0; i < n->mNumChildren; ++i) { + unsigned int idx = ExportNode(n->mChildren[i], node); + node->children.emplace_back(mAsset->nodes.Get(idx)); + } + + return node.GetIndex(); +} + +/* + * Export node and recursively calls ExportNode for all children. + * Since these nodes are not the root node, we also export the parent Ref<Node> + */ +unsigned int glTF2Exporter::ExportNode(const aiNode *n, Ref<Node> &parent) { + std::string name = mAsset->FindUniqueID(n->mName.C_Str(), "node"); + Ref<Node> node = mAsset->nodes.Create(name); + + node->parent = parent; + node->name = name; + + if (!n->mTransformation.IsIdentity()) { + if (mScene->mNumAnimations > 0 || (mProperties && mProperties->HasPropertyBool("GLTF2_NODE_IN_TRS"))) { + aiQuaternion quaternion; + n->mTransformation.Decompose(*reinterpret_cast<aiVector3D *>(&node->scale.value), quaternion, *reinterpret_cast<aiVector3D *>(&node->translation.value)); + + aiVector3D vector(static_cast<ai_real>(1.0f), static_cast<ai_real>(1.0f), static_cast<ai_real>(1.0f)); + if (!reinterpret_cast<aiVector3D *>(&node->scale.value)->Equal(vector)) { + node->scale.isPresent = true; + } + if (!reinterpret_cast<aiVector3D *>(&node->translation.value)->Equal(vector)) { + node->translation.isPresent = true; + } + node->rotation.isPresent = true; + node->rotation.value[0] = quaternion.x; + node->rotation.value[1] = quaternion.y; + node->rotation.value[2] = quaternion.z; + node->rotation.value[3] = quaternion.w; + node->matrix.isPresent = false; + } else { + node->matrix.isPresent = true; + CopyValue(n->mTransformation, node->matrix.value); + } + } + + for (unsigned int i = 0; i < n->mNumMeshes; ++i) { + node->meshes.emplace_back(mAsset->meshes.Get(n->mMeshes[i])); + } + + for (unsigned int i = 0; i < n->mNumChildren; ++i) { + unsigned int idx = ExportNode(n->mChildren[i], node); + node->children.emplace_back(mAsset->nodes.Get(idx)); + } + + return node.GetIndex(); +} + +void glTF2Exporter::ExportScene() { + // Use the name of the scene if specified + const std::string sceneName = (mScene->mName.length > 0) ? mScene->mName.C_Str() : "defaultScene"; + + // Ensure unique + Ref<Scene> scene = mAsset->scenes.Create(mAsset->FindUniqueID(sceneName, "")); + + // root node will be the first one exported (idx 0) + if (mAsset->nodes.Size() > 0) { + scene->nodes.emplace_back(mAsset->nodes.Get(0u)); + } + + // set as the default scene + mAsset->scene = scene; +} + +void glTF2Exporter::ExportMetadata() { + AssetMetadata &asset = mAsset->asset; + asset.version = "2.0"; + + char buffer[256]; + ai_snprintf(buffer, 256, "Open Asset Import Library (assimp v%d.%d.%x)", + aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision()); + + asset.generator = buffer; + + // Copyright + aiString copyright_str; + if (mScene->mMetaData != nullptr && mScene->mMetaData->Get(AI_METADATA_SOURCE_COPYRIGHT, copyright_str)) { + asset.copyright = copyright_str.C_Str(); + } +} + +inline Ref<Accessor> GetSamplerInputRef(Asset &asset, std::string &animId, Ref<Buffer> &buffer, std::vector<ai_real> ×) { + return ExportData(asset, animId, buffer, (unsigned int)times.size(), ×[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT); +} + +inline void ExtractTranslationSampler(Asset &asset, std::string &animId, Ref<Buffer> &buffer, const aiNodeAnim *nodeChannel, float ticksPerSecond, Animation::Sampler &sampler) { + const unsigned int numKeyframes = nodeChannel->mNumPositionKeys; + + std::vector<ai_real> times(numKeyframes); + std::vector<ai_real> values(numKeyframes * 3); + for (unsigned int i = 0; i < numKeyframes; ++i) { + const aiVectorKey &key = nodeChannel->mPositionKeys[i]; + // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. + times[i] = static_cast<float>(key.mTime / ticksPerSecond); + values[(i * 3) + 0] = (ai_real) key.mValue.x; + values[(i * 3) + 1] = (ai_real) key.mValue.y; + values[(i * 3) + 2] = (ai_real) key.mValue.z; + } + + sampler.input = GetSamplerInputRef(asset, animId, buffer, times); + sampler.output = ExportData(asset, animId, buffer, numKeyframes, &values[0], AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); + sampler.interpolation = Interpolation_LINEAR; +} + +inline void ExtractScaleSampler(Asset &asset, std::string &animId, Ref<Buffer> &buffer, const aiNodeAnim *nodeChannel, float ticksPerSecond, Animation::Sampler &sampler) { + const unsigned int numKeyframes = nodeChannel->mNumScalingKeys; + + std::vector<ai_real> times(numKeyframes); + std::vector<ai_real> values(numKeyframes * 3); + for (unsigned int i = 0; i < numKeyframes; ++i) { + const aiVectorKey &key = nodeChannel->mScalingKeys[i]; + // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. + times[i] = static_cast<float>(key.mTime / ticksPerSecond); + values[(i * 3) + 0] = (ai_real) key.mValue.x; + values[(i * 3) + 1] = (ai_real) key.mValue.y; + values[(i * 3) + 2] = (ai_real) key.mValue.z; + } + + sampler.input = GetSamplerInputRef(asset, animId, buffer, times); + sampler.output = ExportData(asset, animId, buffer, numKeyframes, &values[0], AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); + sampler.interpolation = Interpolation_LINEAR; +} + +inline void ExtractRotationSampler(Asset &asset, std::string &animId, Ref<Buffer> &buffer, const aiNodeAnim *nodeChannel, float ticksPerSecond, Animation::Sampler &sampler) { + const unsigned int numKeyframes = nodeChannel->mNumRotationKeys; + + std::vector<ai_real> times(numKeyframes); + std::vector<ai_real> values(numKeyframes * 4); + for (unsigned int i = 0; i < numKeyframes; ++i) { + const aiQuatKey &key = nodeChannel->mRotationKeys[i]; + // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. + times[i] = static_cast<float>(key.mTime / ticksPerSecond); + values[(i * 4) + 0] = (ai_real) key.mValue.x; + values[(i * 4) + 1] = (ai_real) key.mValue.y; + values[(i * 4) + 2] = (ai_real) key.mValue.z; + values[(i * 4) + 3] = (ai_real) key.mValue.w; + } + + sampler.input = GetSamplerInputRef(asset, animId, buffer, times); + sampler.output = ExportData(asset, animId, buffer, numKeyframes, &values[0], AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); + sampler.interpolation = Interpolation_LINEAR; +} + +static void AddSampler(Ref<Animation> &animRef, Ref<Node> &nodeRef, Animation::Sampler &sampler, AnimationPath path) { + Animation::Channel channel; + channel.sampler = static_cast<int>(animRef->samplers.size()); + channel.target.path = path; + channel.target.node = nodeRef; + animRef->channels.push_back(channel); + animRef->samplers.push_back(sampler); +} + +void glTF2Exporter::ExportAnimations() { + Ref<Buffer> bufferRef = mAsset->buffers.Get(unsigned(0)); + + for (unsigned int i = 0; i < mScene->mNumAnimations; ++i) { + const aiAnimation *anim = mScene->mAnimations[i]; + const float ticksPerSecond = static_cast<float>(anim->mTicksPerSecond); + + std::string nameAnim = "anim"; + if (anim->mName.length > 0) { + nameAnim = anim->mName.C_Str(); + } + Ref<Animation> animRef = mAsset->animations.Create(nameAnim); + animRef->name = nameAnim; + + for (unsigned int channelIndex = 0; channelIndex < anim->mNumChannels; ++channelIndex) { + const aiNodeAnim *nodeChannel = anim->mChannels[channelIndex]; + + std::string name = nameAnim + "_" + ai_to_string(channelIndex); + name = mAsset->FindUniqueID(name, "animation"); + + Ref<Node> animNode = mAsset->nodes.Get(nodeChannel->mNodeName.C_Str()); + + if (nodeChannel->mNumPositionKeys > 0) { + Animation::Sampler translationSampler; + ExtractTranslationSampler(*mAsset, name, bufferRef, nodeChannel, ticksPerSecond, translationSampler); + AddSampler(animRef, animNode, translationSampler, AnimationPath_TRANSLATION); + } + + if (nodeChannel->mNumRotationKeys > 0) { + Animation::Sampler rotationSampler; + ExtractRotationSampler(*mAsset, name, bufferRef, nodeChannel, ticksPerSecond, rotationSampler); + AddSampler(animRef, animNode, rotationSampler, AnimationPath_ROTATION); + } + + if (nodeChannel->mNumScalingKeys > 0) { + Animation::Sampler scaleSampler; + ExtractScaleSampler(*mAsset, name, bufferRef, nodeChannel, ticksPerSecond, scaleSampler); + AddSampler(animRef, animNode, scaleSampler, AnimationPath_SCALE); + } + } + } // End: for-loop mNumAnimations +} + +#endif // ASSIMP_BUILD_NO_GLTF_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/glTF2/glTF2Exporter.h b/libs/assimp/code/AssetLib/glTF2/glTF2Exporter.h new file mode 100644 index 0000000..9942522 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF2/glTF2Exporter.h @@ -0,0 +1,147 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 GltfExporter.h +* Declares the exporter class to write a scene to a gltf/glb file +*/ +#ifndef AI_GLTF2EXPORTER_H_INC +#define AI_GLTF2EXPORTER_H_INC + +#if !defined(ASSIMP_BUILD_NO_GLTF_IMPORTER) && !defined(ASSIMP_BUILD_NO_GLTF2_IMPORTER) + +#include <assimp/material.h> +#include <assimp/types.h> + +#include <map> +#include <memory> +#include <sstream> +#include <vector> + +struct aiScene; +struct aiNode; +struct aiMaterial; + +namespace glTFCommon { +template <class T> +class Ref; + +} + +namespace glTF2 { + +class Asset; + +struct TexProperty; +struct TextureInfo; +struct NormalTextureInfo; +struct OcclusionTextureInfo; +struct Node; +struct Texture; +struct PbrSpecularGlossiness; +struct MaterialSheen; +struct MaterialClearcoat; +struct MaterialTransmission; +struct MaterialVolume; +struct MaterialIOR; + +// Vec/matrix types, as raw float arrays +typedef float(vec2)[2]; +typedef float(vec3)[3]; +typedef float(vec4)[4]; + +} // namespace glTF2 + +namespace Assimp { +class IOSystem; +class IOStream; +class ExportProperties; + +// ------------------------------------------------------------------------------------------------ +/** Helper class to export a given scene to an glTF file. */ +// ------------------------------------------------------------------------------------------------ +class glTF2Exporter { +public: + /// Constructor for a specific scene to export + glTF2Exporter(const char *filename, IOSystem *pIOSystem, const aiScene *pScene, + const ExportProperties *pProperties, bool binary); + ~glTF2Exporter(); + +protected: + void WriteBinaryData(IOStream *outfile, std::size_t sceneLength); + void GetTexSampler(const aiMaterial &mat, glTFCommon::Ref<glTF2::Texture> texture, aiTextureType tt, unsigned int slot); + void GetMatTexProp(const aiMaterial &mat, unsigned int &prop, const char *propName, aiTextureType tt, unsigned int idx); + void GetMatTexProp(const aiMaterial &mat, float &prop, const char *propName, aiTextureType tt, unsigned int idx); + void GetMatTex(const aiMaterial &mat, glTFCommon::Ref<glTF2::Texture> &texture, unsigned int &texCoord, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial &mat, glTF2::TextureInfo &prop, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial &mat, glTF2::NormalTextureInfo &prop, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial &mat, glTF2::OcclusionTextureInfo &prop, aiTextureType tt, unsigned int slot); + aiReturn GetMatColor(const aiMaterial &mat, glTF2::vec4 &prop, const char *propName, int type, int idx) const; + aiReturn GetMatColor(const aiMaterial &mat, glTF2::vec3 &prop, const char *propName, int type, int idx) const; + bool GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlossiness &pbrSG); + bool GetMatSheen(const aiMaterial &mat, glTF2::MaterialSheen &sheen); + bool GetMatClearcoat(const aiMaterial &mat, glTF2::MaterialClearcoat &clearcoat); + bool GetMatTransmission(const aiMaterial &mat, glTF2::MaterialTransmission &transmission); + bool GetMatVolume(const aiMaterial &mat, glTF2::MaterialVolume &volume); + bool GetMatIOR(const aiMaterial &mat, glTF2::MaterialIOR &ior); + void ExportMetadata(); + void ExportMaterials(); + void ExportMeshes(); + void MergeMeshes(); + unsigned int ExportNodeHierarchy(const aiNode *n); + unsigned int ExportNode(const aiNode *node, glTFCommon::Ref<glTF2::Node> &parent); + void ExportScene(); + void ExportAnimations(); + +private: + const char *mFilename; + IOSystem *mIOSystem; + const aiScene *mScene; + const ExportProperties *mProperties; + std::map<std::string, unsigned int> mTexturesByPath; + std::shared_ptr<glTF2::Asset> mAsset; + std::vector<unsigned char> mBodyData; +}; + +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER + +#endif // AI_GLTF2EXPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/glTF2/glTF2Importer.cpp b/libs/assimp/code/AssetLib/glTF2/glTF2Importer.cpp new file mode 100644 index 0000000..1f4cafd --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF2/glTF2Importer.cpp @@ -0,0 +1,1637 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#if !defined(ASSIMP_BUILD_NO_GLTF_IMPORTER) && !defined(ASSIMP_BUILD_NO_GLTF2_IMPORTER) + +#include "AssetLib/glTF2/glTF2Importer.h" +#include "PostProcessing/MakeVerboseFormat.h" +#include "AssetLib/glTF2/glTF2Asset.h" + +#if !defined(ASSIMP_BUILD_NO_EXPORT) +#include "AssetLib/glTF2/glTF2AssetWriter.h" +#endif + +#include <assimp/CreateAnimMesh.h> +#include <assimp/StringComparison.h> +#include <assimp/StringUtils.h> +#include <assimp/ai_assert.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> +#include <assimp/commonMetaData.h> +#include <assimp/DefaultIOSystem.h> + +#include <memory> +#include <unordered_map> + +#include <rapidjson/document.h> +#include <rapidjson/rapidjson.h> + +using namespace Assimp; +using namespace glTF2; +using namespace glTFCommon; + +namespace { +// generate bi-tangents from normals and tangents according to spec +struct Tangent { + aiVector3D xyz; + ai_real w; +}; +} // namespace + +// +// glTF2Importer +// + +static const aiImporterDesc desc = { + "glTF2 Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, + 0, + 0, + 0, + 0, + "gltf glb" +}; + +glTF2Importer::glTF2Importer() : + BaseImporter(), + meshOffsets(), + mEmbeddedTexIdxs(), + mScene(nullptr) { + // empty +} + +glTF2Importer::~glTF2Importer() { + // empty +} + +const aiImporterDesc *glTF2Importer::GetInfo() const { + return &desc; +} + +bool glTF2Importer::CanRead(const std::string &filename, IOSystem *pIOHandler, bool checkSig ) const { + const std::string extension = GetExtension(filename); + if (!checkSig && (extension != "gltf") && (extension != "glb")) { + return false; + } + + if (pIOHandler) { + glTF2::Asset asset(pIOHandler); + return asset.CanRead(filename, extension == "glb"); + } + + return false; +} + +static inline aiTextureMapMode ConvertWrappingMode(SamplerWrap gltfWrapMode) { + switch (gltfWrapMode) { + case SamplerWrap::Mirrored_Repeat: + return aiTextureMapMode_Mirror; + + case SamplerWrap::Clamp_To_Edge: + return aiTextureMapMode_Clamp; + + case SamplerWrap::UNSET: + case SamplerWrap::Repeat: + default: + return aiTextureMapMode_Wrap; + } +} + +static inline void SetMaterialColorProperty(Asset & /*r*/, vec4 &prop, aiMaterial *mat, + const char *pKey, unsigned int type, unsigned int idx) { + aiColor4D col; + CopyValue(prop, col); + mat->AddProperty(&col, 1, pKey, type, idx); +} + +static inline void SetMaterialColorProperty(Asset & /*r*/, vec3 &prop, aiMaterial *mat, + const char *pKey, unsigned int type, unsigned int idx) { + aiColor4D col; + glTFCommon::CopyValue(prop, col); + mat->AddProperty(&col, 1, pKey, type, idx); +} + +static void SetMaterialTextureProperty(std::vector<int> &embeddedTexIdxs, Asset & /*r*/, + glTF2::TextureInfo prop, aiMaterial *mat, aiTextureType texType, + unsigned int texSlot = 0) { + if (prop.texture && prop.texture->source) { + aiString uri(prop.texture->source->uri); + + int texIdx = embeddedTexIdxs[prop.texture->source.GetIndex()]; + if (texIdx != -1) { // embedded + // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) + uri.data[0] = '*'; + uri.length = 1 + ASSIMP_itoa10(uri.data + 1, MAXLEN - 1, texIdx); + } + + mat->AddProperty(&uri, AI_MATKEY_TEXTURE(texType, texSlot)); + const int uvIndex = static_cast<int>(prop.texCoord); + mat->AddProperty(&uvIndex, 1, AI_MATKEY_UVWSRC(texType, texSlot)); + + if (prop.textureTransformSupported) { + aiUVTransform transform; + transform.mScaling.x = prop.TextureTransformExt_t.scale[0]; + transform.mScaling.y = prop.TextureTransformExt_t.scale[1]; + transform.mRotation = -prop.TextureTransformExt_t.rotation; // must be negated + + // A change of coordinates is required to map glTF UV transformations into the space used by + // Assimp. In glTF all UV origins are at 0,1 (top left of texture) in Assimp space. In Assimp + // rotation occurs around the image center (0.5,0.5) where as in glTF rotation is around the + // texture origin. All three can be corrected for solely by a change of the translation since + // the transformations available are shape preserving. Note the importer already flips the V + // coordinate of the actual meshes during import. + const ai_real rcos(cos(-transform.mRotation)); + const ai_real rsin(sin(-transform.mRotation)); + transform.mTranslation.x = (static_cast<ai_real>( 0.5 ) * transform.mScaling.x) * (-rcos + rsin + 1) + prop.TextureTransformExt_t.offset[0]; + transform.mTranslation.y = ((static_cast<ai_real>( 0.5 ) * transform.mScaling.y) * (rsin + rcos - 1)) + 1 - transform.mScaling.y - prop.TextureTransformExt_t.offset[1];; + + mat->AddProperty(&transform, 1, _AI_MATKEY_UVTRANSFORM_BASE, texType, texSlot); + } + + if (prop.texture->sampler) { + Ref<Sampler> sampler = prop.texture->sampler; + + aiString name(sampler->name); + aiString id(sampler->id); + + mat->AddProperty(&name, AI_MATKEY_GLTF_MAPPINGNAME(texType, texSlot)); + mat->AddProperty(&id, AI_MATKEY_GLTF_MAPPINGID(texType, texSlot)); + + aiTextureMapMode wrapS = ConvertWrappingMode(sampler->wrapS); + aiTextureMapMode wrapT = ConvertWrappingMode(sampler->wrapT); + mat->AddProperty(&wrapS, 1, AI_MATKEY_MAPPINGMODE_U(texType, texSlot)); + mat->AddProperty(&wrapT, 1, AI_MATKEY_MAPPINGMODE_V(texType, texSlot)); + + if (sampler->magFilter != SamplerMagFilter::UNSET) { + mat->AddProperty(&sampler->magFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(texType, texSlot)); + } + + if (sampler->minFilter != SamplerMinFilter::UNSET) { + mat->AddProperty(&sampler->minFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(texType, texSlot)); + } + } else { + // Use glTFv2 default sampler + const aiTextureMapMode default_wrap = aiTextureMapMode_Wrap; + mat->AddProperty(&default_wrap, 1, AI_MATKEY_MAPPINGMODE_U(texType, texSlot)); + mat->AddProperty(&default_wrap, 1, AI_MATKEY_MAPPINGMODE_V(texType, texSlot)); + } + } +} + +inline void SetMaterialTextureProperty(std::vector<int> &embeddedTexIdxs, Asset &r, + NormalTextureInfo &prop, aiMaterial *mat, aiTextureType texType, + unsigned int texSlot = 0) { + SetMaterialTextureProperty(embeddedTexIdxs, r, (glTF2::TextureInfo)prop, mat, texType, texSlot); + + if (prop.texture && prop.texture->source) { + mat->AddProperty(&prop.scale, 1, AI_MATKEY_GLTF_TEXTURE_SCALE(texType, texSlot)); + } +} + +inline void SetMaterialTextureProperty(std::vector<int> &embeddedTexIdxs, Asset &r, + OcclusionTextureInfo &prop, aiMaterial *mat, aiTextureType texType, + unsigned int texSlot = 0) { + SetMaterialTextureProperty(embeddedTexIdxs, r, (glTF2::TextureInfo)prop, mat, texType, texSlot); + + if (prop.texture && prop.texture->source) { + mat->AddProperty(&prop.strength, 1, AI_MATKEY_GLTF_TEXTURE_STRENGTH(texType, texSlot)); + } +} + +static aiMaterial *ImportMaterial(std::vector<int> &embeddedTexIdxs, Asset &r, Material &mat) { + aiMaterial *aimat = new aiMaterial(); + + try { + if (!mat.name.empty()) { + aiString str(mat.name); + + aimat->AddProperty(&str, AI_MATKEY_NAME); + } + + // Set Assimp DIFFUSE and BASE COLOR to the pbrMetallicRoughness base color and texture for backwards compatibility + // Technically should not load any pbrMetallicRoughness if extensionsRequired contains KHR_materials_pbrSpecularGlossiness + SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_COLOR_DIFFUSE); + SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_BASE_COLOR); + + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, aiTextureType_DIFFUSE); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, aiTextureType_BASE_COLOR); + + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.metallicRoughnessTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); + + aimat->AddProperty(&mat.pbrMetallicRoughness.metallicFactor, 1, AI_MATKEY_METALLIC_FACTOR); + aimat->AddProperty(&mat.pbrMetallicRoughness.roughnessFactor, 1, AI_MATKEY_ROUGHNESS_FACTOR); + + float roughnessAsShininess = 1 - mat.pbrMetallicRoughness.roughnessFactor; + roughnessAsShininess *= roughnessAsShininess * 1000; + aimat->AddProperty(&roughnessAsShininess, 1, AI_MATKEY_SHININESS); + + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.normalTexture, aimat, aiTextureType_NORMALS); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.occlusionTexture, aimat, aiTextureType_LIGHTMAP); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.emissiveTexture, aimat, aiTextureType_EMISSIVE); + SetMaterialColorProperty(r, mat.emissiveFactor, aimat, AI_MATKEY_COLOR_EMISSIVE); + + aimat->AddProperty(&mat.doubleSided, 1, AI_MATKEY_TWOSIDED); + aimat->AddProperty(&mat.pbrMetallicRoughness.baseColorFactor[3], 1, AI_MATKEY_OPACITY); + + aiString alphaMode(mat.alphaMode); + aimat->AddProperty(&alphaMode, AI_MATKEY_GLTF_ALPHAMODE); + aimat->AddProperty(&mat.alphaCutoff, 1, AI_MATKEY_GLTF_ALPHACUTOFF); + + //pbrSpecularGlossiness + if (mat.pbrSpecularGlossiness.isPresent) { + PbrSpecularGlossiness &pbrSG = mat.pbrSpecularGlossiness.value; + + SetMaterialColorProperty(r, pbrSG.diffuseFactor, aimat, AI_MATKEY_COLOR_DIFFUSE); + SetMaterialColorProperty(r, pbrSG.specularFactor, aimat, AI_MATKEY_COLOR_SPECULAR); + + float glossinessAsShininess = pbrSG.glossinessFactor * 1000.0f; + aimat->AddProperty(&glossinessAsShininess, 1, AI_MATKEY_SHININESS); + aimat->AddProperty(&pbrSG.glossinessFactor, 1, AI_MATKEY_GLOSSINESS_FACTOR); + + SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.diffuseTexture, aimat, aiTextureType_DIFFUSE); + + SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.specularGlossinessTexture, aimat, aiTextureType_SPECULAR); + } + + // glTFv2 is either PBR or Unlit + aiShadingMode shadingMode = aiShadingMode_PBR_BRDF; + if (mat.unlit) { + aimat->AddProperty(&mat.unlit, 1, "$mat.gltf.unlit", 0, 0); // TODO: Remove this property, it is kept for backwards compatibility with assimp 5.0.1 + shadingMode = aiShadingMode_Unlit; + } + + aimat->AddProperty(&shadingMode, 1, AI_MATKEY_SHADING_MODEL); + + + // KHR_materials_sheen + if (mat.materialSheen.isPresent) { + MaterialSheen &sheen = mat.materialSheen.value; + // Default value {0,0,0} disables Sheen + if (std::memcmp(sheen.sheenColorFactor, defaultSheenFactor, sizeof(glTFCommon::vec3)) != 0) { + SetMaterialColorProperty(r, sheen.sheenColorFactor, aimat, AI_MATKEY_SHEEN_COLOR_FACTOR); + aimat->AddProperty(&sheen.sheenRoughnessFactor, 1, AI_MATKEY_SHEEN_ROUGHNESS_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenColorTexture, aimat, AI_MATKEY_SHEEN_COLOR_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenRoughnessTexture, aimat, AI_MATKEY_SHEEN_ROUGHNESS_TEXTURE); + } + } + + // KHR_materials_clearcoat + if (mat.materialClearcoat.isPresent) { + MaterialClearcoat &clearcoat = mat.materialClearcoat.value; + // Default value 0.0 disables clearcoat + if (clearcoat.clearcoatFactor != 0.0f) { + aimat->AddProperty(&clearcoat.clearcoatFactor, 1, AI_MATKEY_CLEARCOAT_FACTOR); + aimat->AddProperty(&clearcoat.clearcoatRoughnessFactor, 1, AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatTexture, aimat, AI_MATKEY_CLEARCOAT_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatRoughnessTexture, aimat, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatNormalTexture, aimat, AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE); + } + } + + // KHR_materials_transmission + if (mat.materialTransmission.isPresent) { + MaterialTransmission &transmission = mat.materialTransmission.value; + + aimat->AddProperty(&transmission.transmissionFactor, 1, AI_MATKEY_TRANSMISSION_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, transmission.transmissionTexture, aimat, AI_MATKEY_TRANSMISSION_TEXTURE); + } + + // KHR_materials_volume + if (mat.materialVolume.isPresent) { + MaterialVolume &volume = mat.materialVolume.value; + + aimat->AddProperty(&volume.thicknessFactor, 1, AI_MATKEY_VOLUME_THICKNESS_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, volume.thicknessTexture, aimat, AI_MATKEY_VOLUME_THICKNESS_TEXTURE); + aimat->AddProperty(&volume.attenuationDistance, 1, AI_MATKEY_VOLUME_ATTENUATION_DISTANCE); + SetMaterialColorProperty(r, volume.attenuationColor, aimat, AI_MATKEY_VOLUME_ATTENUATION_COLOR); + } + + // KHR_materials_ior + if (mat.materialIOR.isPresent) { + MaterialIOR &ior = mat.materialIOR.value; + + aimat->AddProperty(&ior.ior, 1, AI_MATKEY_REFRACTI); + } + + return aimat; + } catch (...) { + delete aimat; + throw; + } +} + +void glTF2Importer::ImportMaterials(Asset &r) { + const unsigned int numImportedMaterials = unsigned(r.materials.Size()); + ASSIMP_LOG_DEBUG("Importing ", numImportedMaterials, " materials"); + Material defaultMaterial; + + mScene->mNumMaterials = numImportedMaterials + 1; + mScene->mMaterials = new aiMaterial *[mScene->mNumMaterials]; + std::fill(mScene->mMaterials, mScene->mMaterials + mScene->mNumMaterials, nullptr); + mScene->mMaterials[numImportedMaterials] = ImportMaterial(mEmbeddedTexIdxs, r, defaultMaterial); + + for (unsigned int i = 0; i < numImportedMaterials; ++i) { + mScene->mMaterials[i] = ImportMaterial(mEmbeddedTexIdxs, r, r.materials[i]); + } +} + +static inline void SetFaceAndAdvance1(aiFace*& face, unsigned int numVertices, unsigned int a) { + if (a >= numVertices) { + return; + } + face->mNumIndices = 1; + face->mIndices = new unsigned int[1]; + face->mIndices[0] = a; + ++face; +} + +static inline void SetFaceAndAdvance2(aiFace*& face, unsigned int numVertices, + unsigned int a, unsigned int b) { + if ((a >= numVertices) || (b >= numVertices)) { + return; + } + face->mNumIndices = 2; + face->mIndices = new unsigned int[2]; + face->mIndices[0] = a; + face->mIndices[1] = b; + ++face; +} + +static inline void SetFaceAndAdvance3(aiFace*& face, unsigned int numVertices, unsigned int a, + unsigned int b, unsigned int c) { + if ((a >= numVertices) || (b >= numVertices) || (c >= numVertices)) { + return; + } + face->mNumIndices = 3; + face->mIndices = new unsigned int[3]; + face->mIndices[0] = a; + face->mIndices[1] = b; + face->mIndices[2] = c; + ++face; +} + +#ifdef ASSIMP_BUILD_DEBUG +static inline bool CheckValidFacesIndices(aiFace *faces, unsigned nFaces, unsigned nVerts) { + for (unsigned i = 0; i < nFaces; ++i) { + for (unsigned j = 0; j < faces[i].mNumIndices; ++j) { + unsigned idx = faces[i].mIndices[j]; + if (idx >= nVerts) { + return false; + } + } + } + return true; +} +#endif // ASSIMP_BUILD_DEBUG + +template<typename T> +aiColor4D* GetVertexColorsForType(Ref<Accessor> input) { + constexpr float max = std::numeric_limits<T>::max(); + aiColor4t<T>* colors; + input->ExtractData(colors); + auto output = new aiColor4D[input->count]; + for (size_t i = 0; i < input->count; i++) { + output[i] = aiColor4D( + colors[i].r / max, colors[i].g / max, + colors[i].b / max, colors[i].a / max + ); + } + delete[] colors; + return output; +} + +void glTF2Importer::ImportMeshes(glTF2::Asset &r) { + ASSIMP_LOG_DEBUG("Importing ", r.meshes.Size(), " meshes"); + std::vector<std::unique_ptr<aiMesh>> meshes; + + unsigned int k = 0; + meshOffsets.clear(); + + for (unsigned int m = 0; m < r.meshes.Size(); ++m) { + Mesh &mesh = r.meshes[m]; + + meshOffsets.push_back(k); + k += unsigned(mesh.primitives.size()); + + for (unsigned int p = 0; p < mesh.primitives.size(); ++p) { + Mesh::Primitive &prim = mesh.primitives[p]; + + aiMesh *aim = new aiMesh(); + meshes.push_back(std::unique_ptr<aiMesh>(aim)); + + aim->mName = mesh.name.empty() ? mesh.id : mesh.name; + + if (mesh.primitives.size() > 1) { + ai_uint32 &len = aim->mName.length; + aim->mName.data[len] = '-'; + len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p); + } + + switch (prim.mode) { + case PrimitiveMode_POINTS: + aim->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + + case PrimitiveMode_LINES: + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: + aim->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + + case PrimitiveMode_TRIANGLES: + case PrimitiveMode_TRIANGLE_STRIP: + case PrimitiveMode_TRIANGLE_FAN: + aim->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + } + + Mesh::Primitive::Attributes &attr = prim.attributes; + + if (!attr.position.empty() && attr.position[0]) { + aim->mNumVertices = static_cast<unsigned int>(attr.position[0]->count); + attr.position[0]->ExtractData(aim->mVertices); + } + + if (!attr.normal.empty() && attr.normal[0]) { + if (attr.normal[0]->count != aim->mNumVertices) { + DefaultLogger::get()->warn("Normal count in mesh \"", mesh.name, "\" does not match the vertex count, normals ignored."); + } else { + attr.normal[0]->ExtractData(aim->mNormals); + + // only extract tangents if normals are present + if (!attr.tangent.empty() && attr.tangent[0]) { + if (attr.tangent[0]->count != aim->mNumVertices) { + DefaultLogger::get()->warn("Tangent count in mesh \"", mesh.name, "\" does not match the vertex count, tangents ignored."); + } else { + // generate bitangents from normals and tangents according to spec + Tangent *tangents = nullptr; + + attr.tangent[0]->ExtractData(tangents); + + aim->mTangents = new aiVector3D[aim->mNumVertices]; + aim->mBitangents = new aiVector3D[aim->mNumVertices]; + + for (unsigned int i = 0; i < aim->mNumVertices; ++i) { + aim->mTangents[i] = tangents[i].xyz; + aim->mBitangents[i] = (aim->mNormals[i] ^ tangents[i].xyz) * tangents[i].w; + } + + delete[] tangents; + } + } + } + } + + for (size_t c = 0; c < attr.color.size() && c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) { + if (attr.color[c]->count != aim->mNumVertices) { + DefaultLogger::get()->warn("Color stream size in mesh \"", mesh.name, + "\" does not match the vertex count"); + continue; + } + + auto componentType = attr.color[c]->componentType; + if (componentType == glTF2::ComponentType_FLOAT) { + attr.color[c]->ExtractData(aim->mColors[c]); + } else { + if (componentType == glTF2::ComponentType_UNSIGNED_BYTE) { + aim->mColors[c] = GetVertexColorsForType<unsigned char>(attr.color[c]); + } else if (componentType == glTF2::ComponentType_UNSIGNED_SHORT) { + aim->mColors[c] = GetVertexColorsForType<unsigned short>(attr.color[c]); + } + } + } + for (size_t tc = 0; tc < attr.texcoord.size() && tc < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) { + if (!attr.texcoord[tc]) { + DefaultLogger::get()->warn("Texture coordinate accessor not found or non-contiguous texture coordinate sets."); + continue; + } + + if (attr.texcoord[tc]->count != aim->mNumVertices) { + DefaultLogger::get()->warn("Texcoord stream size in mesh \"", mesh.name, + "\" does not match the vertex count"); + continue; + } + + attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]); + aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents(); + + aiVector3D *values = aim->mTextureCoords[tc]; + for (unsigned int i = 0; i < aim->mNumVertices; ++i) { + values[i].y = 1 - values[i].y; // Flip Y coords + } + } + + std::vector<Mesh::Primitive::Target> &targets = prim.targets; + if (!targets.empty()) { + aim->mNumAnimMeshes = (unsigned int)targets.size(); + aim->mAnimMeshes = new aiAnimMesh *[aim->mNumAnimMeshes]; + std::fill(aim->mAnimMeshes, aim->mAnimMeshes + aim->mNumAnimMeshes, nullptr); + for (size_t i = 0; i < targets.size(); i++) { + bool needPositions = targets[i].position.size() > 0; + bool needNormals = (targets[i].normal.size() > 0) && aim->HasNormals(); + bool needTangents = (targets[i].tangent.size() > 0) && aim->HasTangentsAndBitangents(); + // GLTF morph does not support colors and texCoords + aim->mAnimMeshes[i] = aiCreateAnimMesh(aim, + needPositions, needNormals, needTangents, false, false); + aiAnimMesh &aiAnimMesh = *(aim->mAnimMeshes[i]); + Mesh::Primitive::Target &target = targets[i]; + + if (needPositions) { + if (target.position[0]->count != aim->mNumVertices) { + ASSIMP_LOG_WARN("Positions of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count"); + } else { + aiVector3D *positionDiff = nullptr; + target.position[0]->ExtractData(positionDiff); + for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { + aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId]; + } + delete[] positionDiff; + } + } + if (needNormals) { + if (target.normal[0]->count != aim->mNumVertices) { + ASSIMP_LOG_WARN("Normals of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count"); + } else { + aiVector3D *normalDiff = nullptr; + target.normal[0]->ExtractData(normalDiff); + for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { + aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId]; + } + delete[] normalDiff; + } + } + if (needTangents) { + if (target.tangent[0]->count != aim->mNumVertices) { + ASSIMP_LOG_WARN("Tangents of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count"); + } else { + Tangent *tangent = nullptr; + attr.tangent[0]->ExtractData(tangent); + + aiVector3D *tangentDiff = nullptr; + target.tangent[0]->ExtractData(tangentDiff); + + for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) { + tangent[vertexId].xyz += tangentDiff[vertexId]; + aiAnimMesh.mTangents[vertexId] = tangent[vertexId].xyz; + aiAnimMesh.mBitangents[vertexId] = (aiAnimMesh.mNormals[vertexId] ^ tangent[vertexId].xyz) * tangent[vertexId].w; + } + delete[] tangent; + delete[] tangentDiff; + } + } + if (mesh.weights.size() > i) { + aiAnimMesh.mWeight = mesh.weights[i]; + } + if (mesh.targetNames.size() > i) { + aiAnimMesh.mName = mesh.targetNames[i]; + } + } + } + + aiFace *faces = nullptr; + aiFace *facePtr = nullptr; + size_t nFaces = 0; + + if (prim.indices) { + size_t count = prim.indices->count; + + Accessor::Indexer data = prim.indices->GetIndexer(); + if (!data.IsValid()) { + throw DeadlyImportError("GLTF: Invalid accessor without data in mesh ", getContextForErrorMessages(mesh.id, mesh.name)); + } + + switch (prim.mode) { + case PrimitiveMode_POINTS: { + nFaces = count; + facePtr = faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; ++i) { + SetFaceAndAdvance1(facePtr, aim->mNumVertices, data.GetUInt(i)); + } + break; + } + + case PrimitiveMode_LINES: { + nFaces = count / 2; + if (nFaces * 2 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); + count = nFaces * 2; + } + facePtr = faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 2) { + SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(i), data.GetUInt(i + 1)); + } + break; + } + + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: { + nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); + facePtr = faces = new aiFace[nFaces]; + SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(0), data.GetUInt(1)); + for (unsigned int i = 2; i < count; ++i) { + SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(i - 1), data.GetUInt(i)); + } + if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop + SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(static_cast<int>(count) - 1), faces[0].mIndices[0]); + } + break; + } + + case PrimitiveMode_TRIANGLES: { + nFaces = count / 3; + if (nFaces * 3 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); + count = nFaces * 3; + } + facePtr = faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 3) { + SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); + } + break; + } + case PrimitiveMode_TRIANGLE_STRIP: { + nFaces = count - 2; + facePtr = faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < nFaces; ++i) { + //The ordering is to ensure that the triangles are all drawn with the same orientation + if ((i + 1) % 2 == 0) { + //For even n, vertices n + 1, n, and n + 2 define triangle n + SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(i + 1), data.GetUInt(i), data.GetUInt(i + 2)); + } else { + //For odd n, vertices n, n+1, and n+2 define triangle n + SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); + } + } + break; + } + case PrimitiveMode_TRIANGLE_FAN: + nFaces = count - 2; + facePtr = faces = new aiFace[nFaces]; + SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(0), data.GetUInt(1), data.GetUInt(2)); + for (unsigned int i = 1; i < nFaces; ++i) { + SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(0), data.GetUInt(i + 1), data.GetUInt(i + 2)); + } + break; + } + } else { // no indices provided so directly generate from counts + + // use the already determined count as it includes checks + unsigned int count = aim->mNumVertices; + + switch (prim.mode) { + case PrimitiveMode_POINTS: { + nFaces = count; + facePtr = faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; ++i) { + SetFaceAndAdvance1(facePtr, aim->mNumVertices, i); + } + break; + } + + case PrimitiveMode_LINES: { + nFaces = count / 2; + if (nFaces * 2 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); + count = (unsigned int)nFaces * 2; + } + facePtr = faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 2) { + SetFaceAndAdvance2(facePtr, aim->mNumVertices, i, i + 1); + } + break; + } + + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: { + nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); + facePtr = faces = new aiFace[nFaces]; + SetFaceAndAdvance2(facePtr, aim->mNumVertices, 0, 1); + for (unsigned int i = 2; i < count; ++i) { + SetFaceAndAdvance2(facePtr, aim->mNumVertices, i - 1, i); + } + if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop + SetFaceAndAdvance2(facePtr, aim->mNumVertices, count - 1, 0); + } + break; + } + + case PrimitiveMode_TRIANGLES: { + nFaces = count / 3; + if (nFaces * 3 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); + count = (unsigned int)nFaces * 3; + } + facePtr = faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 3) { + SetFaceAndAdvance3(facePtr, aim->mNumVertices, i, i + 1, i + 2); + } + break; + } + case PrimitiveMode_TRIANGLE_STRIP: { + nFaces = count - 2; + facePtr = faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < nFaces; ++i) { + //The ordering is to ensure that the triangles are all drawn with the same orientation + if ((i + 1) % 2 == 0) { + //For even n, vertices n + 1, n, and n + 2 define triangle n + SetFaceAndAdvance3(facePtr, aim->mNumVertices, i + 1, i, i + 2); + } else { + //For odd n, vertices n, n+1, and n+2 define triangle n + SetFaceAndAdvance3(facePtr, aim->mNumVertices, i, i + 1, i + 2); + } + } + break; + } + case PrimitiveMode_TRIANGLE_FAN: + nFaces = count - 2; + facePtr = faces = new aiFace[nFaces]; + SetFaceAndAdvance3(facePtr, aim->mNumVertices, 0, 1, 2); + for (unsigned int i = 1; i < nFaces; ++i) { + SetFaceAndAdvance3(facePtr, aim->mNumVertices, 0, i + 1, i + 2); + } + break; + } + } + + if (faces) { + aim->mFaces = faces; + const unsigned int actualNumFaces = static_cast<unsigned int>(facePtr - faces); + if (actualNumFaces < nFaces) { + ASSIMP_LOG_WARN("Some faces had out-of-range indices. Those faces were dropped."); + } + if (actualNumFaces == 0) { + throw DeadlyImportError("Mesh \"", aim->mName.C_Str(), "\" has no faces"); + } + aim->mNumFaces = actualNumFaces; + ai_assert(CheckValidFacesIndices(faces, actualNumFaces, aim->mNumVertices)); + } + + if (prim.material) { + aim->mMaterialIndex = prim.material.GetIndex(); + } else { + aim->mMaterialIndex = mScene->mNumMaterials - 1; + } + } + } + + meshOffsets.push_back(k); + + CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes); +} + +void glTF2Importer::ImportCameras(glTF2::Asset &r) { + if (!r.cameras.Size()) { + return; + } + + const unsigned int numCameras = r.cameras.Size(); + ASSIMP_LOG_DEBUG("Importing ", numCameras, " cameras"); + mScene->mNumCameras = numCameras; + mScene->mCameras = new aiCamera *[numCameras]; + std::fill(mScene->mCameras, mScene->mCameras + numCameras, nullptr); + + for (size_t i = 0; i < numCameras; ++i) { + Camera &cam = r.cameras[i]; + + aiCamera *aicam = mScene->mCameras[i] = new aiCamera(); + + // cameras point in -Z by default, rest is specified in node transform + aicam->mLookAt = aiVector3D(0.f, 0.f, -1.f); + + if (cam.type == Camera::Perspective) { + aicam->mAspect = cam.cameraProperties.perspective.aspectRatio; + aicam->mHorizontalFOV = cam.cameraProperties.perspective.yfov * ((aicam->mAspect == 0.f) ? 1.f : aicam->mAspect); + aicam->mClipPlaneFar = cam.cameraProperties.perspective.zfar; + aicam->mClipPlaneNear = cam.cameraProperties.perspective.znear; + } else { + aicam->mClipPlaneFar = cam.cameraProperties.ortographic.zfar; + aicam->mClipPlaneNear = cam.cameraProperties.ortographic.znear; + aicam->mHorizontalFOV = 0.0; + aicam->mOrthographicWidth = cam.cameraProperties.ortographic.xmag; + aicam->mAspect = 1.0f; + if (0.f != cam.cameraProperties.ortographic.ymag) { + aicam->mAspect = cam.cameraProperties.ortographic.xmag / cam.cameraProperties.ortographic.ymag; + } + } + } +} + +void glTF2Importer::ImportLights(glTF2::Asset &r) { + if (!r.lights.Size()) { + return; + } + + const unsigned int numLights = r.lights.Size(); + ASSIMP_LOG_DEBUG("Importing ", numLights, " lights"); + mScene->mNumLights = numLights; + mScene->mLights = new aiLight *[numLights]; + std::fill(mScene->mLights, mScene->mLights + numLights, nullptr); + + for (size_t i = 0; i < numLights; ++i) { + Light &light = r.lights[i]; + + aiLight *ail = mScene->mLights[i] = new aiLight(); + + switch (light.type) { + case Light::Directional: + ail->mType = aiLightSource_DIRECTIONAL; + break; + case Light::Point: + ail->mType = aiLightSource_POINT; + break; + case Light::Spot: + ail->mType = aiLightSource_SPOT; + break; + } + + if (ail->mType != aiLightSource_POINT) { + ail->mDirection = aiVector3D(0.0f, 0.0f, -1.0f); + ail->mUp = aiVector3D(0.0f, 1.0f, 0.0f); + } + + vec3 colorWithIntensity = { light.color[0] * light.intensity, light.color[1] * light.intensity, light.color[2] * light.intensity }; + CopyValue(colorWithIntensity, ail->mColorAmbient); + CopyValue(colorWithIntensity, ail->mColorDiffuse); + CopyValue(colorWithIntensity, ail->mColorSpecular); + + if (ail->mType == aiLightSource_DIRECTIONAL) { + ail->mAttenuationConstant = 1.0; + ail->mAttenuationLinear = 0.0; + ail->mAttenuationQuadratic = 0.0; + } else { + //in PBR attenuation is calculated using inverse square law which can be expressed + //using assimps equation: 1/(att0 + att1 * d + att2 * d*d) with the following parameters + //this is correct equation for the case when range (see + //https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual) + //is not present. When range is not present it is assumed that it is infinite and so numerator is 1. + //When range is present then numerator might be any value in range [0,1] and then assimps equation + //will not suffice. In this case range is added into metadata in ImportNode function + //and its up to implementation to read it when it wants to + ail->mAttenuationConstant = 0.0; + ail->mAttenuationLinear = 0.0; + ail->mAttenuationQuadratic = 1.0; + } + + if (ail->mType == aiLightSource_SPOT) { + ail->mAngleInnerCone = light.innerConeAngle; + ail->mAngleOuterCone = light.outerConeAngle; + } + } +} + +static void GetNodeTransform(aiMatrix4x4 &matrix, const glTF2::Node &node) { + if (node.matrix.isPresent) { + CopyValue(node.matrix.value, matrix); + return; + } + + if (node.translation.isPresent) { + aiVector3D trans; + CopyValue(node.translation.value, trans); + aiMatrix4x4 t; + aiMatrix4x4::Translation(trans, t); + matrix = matrix * t; + } + + if (node.rotation.isPresent) { + aiQuaternion rot; + CopyValue(node.rotation.value, rot); + matrix = matrix * aiMatrix4x4(rot.GetMatrix()); + } + + if (node.scale.isPresent) { + aiVector3D scal(1.f); + CopyValue(node.scale.value, scal); + aiMatrix4x4 s; + aiMatrix4x4::Scaling(scal, s); + matrix = matrix * s; + } +} + +static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vector<std::vector<aiVertexWeight>> &map) { + Mesh::Primitive::Attributes &attr = primitive.attributes; + if (attr.weight.empty() || attr.joint.empty()) { + return; + } + if (attr.weight[0]->count != attr.joint[0]->count) { + return; + } + + size_t num_vertices = attr.weight[0]->count; + + struct Weights { + float values[4]; + }; + Weights *weights = nullptr; + attr.weight[0]->ExtractData(weights); + + struct Indices8 { + uint8_t values[4]; + }; + struct Indices16 { + uint16_t values[4]; + }; + Indices8 *indices8 = nullptr; + Indices16 *indices16 = nullptr; + if (attr.joint[0]->GetElementSize() == 4) { + attr.joint[0]->ExtractData(indices8); + } else { + attr.joint[0]->ExtractData(indices16); + } + // + if (nullptr == indices8 && nullptr == indices16) { + // Something went completely wrong! + ai_assert(false); + return; + } + + for (size_t i = 0; i < num_vertices; ++i) { + for (int j = 0; j < 4; ++j) { + const unsigned int bone = (indices8 != nullptr) ? indices8[i].values[j] : indices16[i].values[j]; + const float weight = weights[i].values[j]; + if (weight > 0 && bone < map.size()) { + map[bone].reserve(8); + map[bone].emplace_back(static_cast<unsigned int>(i), weight); + } + } + } + + delete[] weights; + delete[] indices8; + delete[] indices16; +} + +static std::string GetNodeName(const Node &node) { + return node.name.empty() ? node.id : node.name; +} + +void ParseExtensions(aiMetadata *metadata, const CustomExtension &extension) { + if (extension.mStringValue.isPresent) { + metadata->Add(extension.name, aiString(extension.mStringValue.value)); + } else if (extension.mDoubleValue.isPresent) { + metadata->Add(extension.name, extension.mDoubleValue.value); + } else if (extension.mUint64Value.isPresent) { + metadata->Add(extension.name, extension.mUint64Value.value); + } else if (extension.mInt64Value.isPresent) { + metadata->Add(extension.name, static_cast<int32_t>(extension.mInt64Value.value)); + } else if (extension.mBoolValue.isPresent) { + metadata->Add(extension.name, extension.mBoolValue.value); + } else if (extension.mValues.isPresent) { + aiMetadata val; + for (auto const & subExtension : extension.mValues.value) { + ParseExtensions(&val, subExtension); + } + metadata->Add(extension.name, val); + } +} + +void ParseExtras(aiMetadata *metadata, const CustomExtension &extension) { + if (extension.mValues.isPresent) { + for (auto const & subExtension : extension.mValues.value) { + ParseExtensions(metadata, subExtension); + } + } +} + +aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector<unsigned int> &meshOffsets, glTF2::Ref<glTF2::Node> &ptr) { + Node &node = *ptr; + + aiNode *ainode = new aiNode(GetNodeName(node)); + + try { + if (!node.children.empty()) { + ainode->mNumChildren = unsigned(node.children.size()); + ainode->mChildren = new aiNode *[ainode->mNumChildren]; + std::fill(ainode->mChildren, ainode->mChildren + ainode->mNumChildren, nullptr); + + for (unsigned int i = 0; i < ainode->mNumChildren; ++i) { + aiNode *child = ImportNode(pScene, r, meshOffsets, node.children[i]); + child->mParent = ainode; + ainode->mChildren[i] = child; + } + } + + if (node.customExtensions || node.extras) { + ainode->mMetaData = new aiMetadata; + if (node.customExtensions) { + ParseExtensions(ainode->mMetaData, node.customExtensions); + } + if (node.extras) { + ParseExtras(ainode->mMetaData, node.extras); + } + } + + GetNodeTransform(ainode->mTransformation, node); + + if (!node.meshes.empty()) { + // GLTF files contain at most 1 mesh per node. + if (node.meshes.size() > 1) + { + throw DeadlyImportError("GLTF: Invalid input, found ", node.meshes.size(), + " meshes in ", getContextForErrorMessages(node.id, node.name), + ", but only 1 mesh per node allowed."); + } + int mesh_idx = node.meshes[0].GetIndex(); + int count = meshOffsets[mesh_idx + 1] - meshOffsets[mesh_idx]; + + ainode->mNumMeshes = count; + ainode->mMeshes = new unsigned int[count]; + + if (node.skin) { + for (int primitiveNo = 0; primitiveNo < count; ++primitiveNo) { + aiMesh *mesh = pScene->mMeshes[meshOffsets[mesh_idx] + primitiveNo]; + unsigned int numBones =static_cast<unsigned int>(node.skin->jointNames.size()); + + std::vector<std::vector<aiVertexWeight>> weighting(numBones); + BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting); + + mesh->mNumBones = static_cast<unsigned int>(numBones); + mesh->mBones = new aiBone *[mesh->mNumBones]; + std::fill(mesh->mBones, mesh->mBones + mesh->mNumBones, nullptr); + + // GLTF and Assimp choose to store bone weights differently. + // GLTF has each vertex specify which bones influence the vertex. + // Assimp has each bone specify which vertices it has influence over. + // To convert this data, we first read over the vertex data and pull + // out the bone-to-vertex mapping. Then, when creating the aiBones, + // we copy the bone-to-vertex mapping into the bone. This is unfortunate + // both because it's somewhat slow and because, for many applications, + // we then need to reconvert the data back into the vertex-to-bone + // mapping which makes things doubly-slow. + + mat4 *pbindMatrices = nullptr; + node.skin->inverseBindMatrices->ExtractData(pbindMatrices); + + for (uint32_t i = 0; i < numBones; ++i) { + const std::vector<aiVertexWeight> &weights = weighting[i]; + aiBone *bone = new aiBone(); + + Ref<Node> joint = node.skin->jointNames[i]; + if (!joint->name.empty()) { + bone->mName = joint->name; + } else { + // Assimp expects each bone to have a unique name. + static const std::string kDefaultName = "bone_"; + char postfix[10] = { 0 }; + ASSIMP_itoa10(postfix, i); + bone->mName = (kDefaultName + postfix); + } + GetNodeTransform(bone->mOffsetMatrix, *joint); + CopyValue(pbindMatrices[i], bone->mOffsetMatrix); + bone->mNumWeights = static_cast<uint32_t>(weights.size()); + + if (bone->mNumWeights > 0) { + bone->mWeights = new aiVertexWeight[bone->mNumWeights]; + memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight)); + } else { + // Assimp expects all bones to have at least 1 weight. + bone->mWeights = new aiVertexWeight[1]; + bone->mNumWeights = 1; + bone->mWeights->mVertexId = 0; + bone->mWeights->mWeight = 0.f; + } + mesh->mBones[i] = bone; + } + + if (pbindMatrices) { + delete[] pbindMatrices; + } + } + } + + int k = 0; + for (unsigned int j = meshOffsets[mesh_idx]; j < meshOffsets[mesh_idx + 1]; ++j, ++k) { + ainode->mMeshes[k] = j; + } + } + + if (node.camera) { + pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName; + if (node.translation.isPresent) { + aiVector3D trans; + CopyValue(node.translation.value, trans); + pScene->mCameras[node.camera.GetIndex()]->mPosition = trans; + } + } + + if (node.light) { + pScene->mLights[node.light.GetIndex()]->mName = ainode->mName; + + //range is optional - see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + //it is added to meta data of parent node, because there is no other place to put it + if (node.light->range.isPresent) { + if (!ainode->mMetaData) { + ainode->mMetaData = aiMetadata::Alloc(1); + ainode->mMetaData->Set(0, "PBR_LightRange", node.light->range.value); + } else { + ainode->mMetaData->Add("PBR_LightRange", node.light->range.value); + } + } + } + + return ainode; + } catch (...) { + delete ainode; + throw; + } +} + +void glTF2Importer::ImportNodes(glTF2::Asset &r) { + if (!r.scene) { + throw DeadlyImportError("GLTF: No scene"); + } + ASSIMP_LOG_DEBUG("Importing nodes"); + + std::vector<Ref<Node>> rootNodes = r.scene->nodes; + + // The root nodes + unsigned int numRootNodes = unsigned(rootNodes.size()); + if (numRootNodes == 1) { // a single root node: use it + mScene->mRootNode = ImportNode(mScene, r, meshOffsets, rootNodes[0]); + } else if (numRootNodes > 1) { // more than one root node: create a fake root + aiNode *root = mScene->mRootNode = new aiNode("ROOT"); + + root->mChildren = new aiNode *[numRootNodes]; + std::fill(root->mChildren, root->mChildren + numRootNodes, nullptr); + + for (unsigned int i = 0; i < numRootNodes; ++i) { + aiNode *node = ImportNode(mScene, r, meshOffsets, rootNodes[i]); + node->mParent = root; + root->mChildren[root->mNumChildren++] = node; + } + } else { + mScene->mRootNode = new aiNode("ROOT"); + } +} + +struct AnimationSamplers { + AnimationSamplers() : + translation(nullptr), + rotation(nullptr), + scale(nullptr), + weight(nullptr) { + // empty + } + + Animation::Sampler *translation; + Animation::Sampler *rotation; + Animation::Sampler *scale; + Animation::Sampler *weight; +}; + +aiNodeAnim *CreateNodeAnim(glTF2::Asset&, Node &node, AnimationSamplers &samplers) { + aiNodeAnim *anim = new aiNodeAnim(); + + try { + anim->mNodeName = GetNodeName(node); + + static const float kMillisecondsFromSeconds = 1000.f; + + if (samplers.translation && samplers.translation->input && samplers.translation->output) { + float *times = nullptr; + samplers.translation->input->ExtractData(times); + aiVector3D *values = nullptr; + samplers.translation->output->ExtractData(values); + anim->mNumPositionKeys = static_cast<uint32_t>(samplers.translation->input->count); + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + unsigned int ii = (samplers.translation->interpolation == Interpolation_CUBICSPLINE) ? 1 : 0; + for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { + anim->mPositionKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mPositionKeys[i].mValue = values[ii]; + ii += (samplers.translation->interpolation == Interpolation_CUBICSPLINE) ? 3 : 1; + } + delete[] times; + delete[] values; + } else if (node.translation.isPresent) { + anim->mNumPositionKeys = 1; + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + anim->mPositionKeys->mTime = 0.f; + anim->mPositionKeys->mValue.x = node.translation.value[0]; + anim->mPositionKeys->mValue.y = node.translation.value[1]; + anim->mPositionKeys->mValue.z = node.translation.value[2]; + } + + if (samplers.rotation && samplers.rotation->input && samplers.rotation->output) { + float *times = nullptr; + samplers.rotation->input->ExtractData(times); + aiQuaternion *values = nullptr; + samplers.rotation->output->ExtractData(values); + anim->mNumRotationKeys = static_cast<uint32_t>(samplers.rotation->input->count); + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; + unsigned int ii = (samplers.rotation->interpolation == Interpolation_CUBICSPLINE) ? 1 : 0; + for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { + anim->mRotationKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mRotationKeys[i].mValue.x = values[ii].w; + anim->mRotationKeys[i].mValue.y = values[ii].x; + anim->mRotationKeys[i].mValue.z = values[ii].y; + anim->mRotationKeys[i].mValue.w = values[ii].z; + ii += (samplers.rotation->interpolation == Interpolation_CUBICSPLINE) ? 3 : 1; + } + delete[] times; + delete[] values; + } else if (node.rotation.isPresent) { + anim->mNumRotationKeys = 1; + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; + anim->mRotationKeys->mTime = 0.f; + anim->mRotationKeys->mValue.x = node.rotation.value[0]; + anim->mRotationKeys->mValue.y = node.rotation.value[1]; + anim->mRotationKeys->mValue.z = node.rotation.value[2]; + anim->mRotationKeys->mValue.w = node.rotation.value[3]; + } + + if (samplers.scale && samplers.scale->input && samplers.scale->output) { + float *times = nullptr; + samplers.scale->input->ExtractData(times); + aiVector3D *values = nullptr; + samplers.scale->output->ExtractData(values); + anim->mNumScalingKeys = static_cast<uint32_t>(samplers.scale->input->count); + anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; + unsigned int ii = (samplers.scale->interpolation == Interpolation_CUBICSPLINE) ? 1 : 0; + for (unsigned int i = 0; i < anim->mNumScalingKeys; ++i) { + anim->mScalingKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mScalingKeys[i].mValue = values[ii]; + ii += (samplers.scale->interpolation == Interpolation_CUBICSPLINE) ? 3 : 1; + } + delete[] times; + delete[] values; + } else if (node.scale.isPresent) { + anim->mNumScalingKeys = 1; + anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; + anim->mScalingKeys->mTime = 0.f; + anim->mScalingKeys->mValue.x = node.scale.value[0]; + anim->mScalingKeys->mValue.y = node.scale.value[1]; + anim->mScalingKeys->mValue.z = node.scale.value[2]; + } + + return anim; + } catch (...) { + delete anim; + throw; + } +} + +aiMeshMorphAnim *CreateMeshMorphAnim(glTF2::Asset&, Node &node, AnimationSamplers &samplers) { + auto *anim = new aiMeshMorphAnim(); + + try { + anim->mName = GetNodeName(node); + + static const float kMillisecondsFromSeconds = 1000.f; + + if (samplers.weight && samplers.weight->input && samplers.weight->output) { + float *times = nullptr; + samplers.weight->input->ExtractData(times); + float *values = nullptr; + samplers.weight->output->ExtractData(values); + anim->mNumKeys = static_cast<uint32_t>(samplers.weight->input->count); + + // for Interpolation_CUBICSPLINE can have more outputs + const unsigned int weightStride = (unsigned int)samplers.weight->output->count / anim->mNumKeys; + const unsigned int numMorphs = (samplers.weight->interpolation == Interpolation_CUBICSPLINE) ? weightStride - 2 : weightStride; + + anim->mKeys = new aiMeshMorphKey[anim->mNumKeys]; + unsigned int ii = (samplers.weight->interpolation == Interpolation_CUBICSPLINE) ? 1 : 0; + for (unsigned int i = 0u; i < anim->mNumKeys; ++i) { + unsigned int k = weightStride * i + ii; + anim->mKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mKeys[i].mNumValuesAndWeights = numMorphs; + anim->mKeys[i].mValues = new unsigned int[numMorphs]; + anim->mKeys[i].mWeights = new double[numMorphs]; + + for (unsigned int j = 0u; j < numMorphs; ++j, ++k) { + anim->mKeys[i].mValues[j] = j; + anim->mKeys[i].mWeights[j] = (0.f > values[k]) ? 0.f : values[k]; + } + } + + delete[] times; + delete[] values; + } + + return anim; + } catch (...) { + delete anim; + throw; + } +} + +std::unordered_map<unsigned int, AnimationSamplers> GatherSamplers(Animation &anim) { + std::unordered_map<unsigned int, AnimationSamplers> samplers; + for (unsigned int c = 0; c < anim.channels.size(); ++c) { + Animation::Channel &channel = anim.channels[c]; + if (channel.sampler < 0 || channel.sampler >= static_cast<int>(anim.samplers.size())) { + continue; + } + + auto& animsampler = anim.samplers[channel.sampler]; + + if (!animsampler.input) { + ASSIMP_LOG_WARN("Animation ", anim.name, ": Missing sampler input. Skipping."); + continue; + } + + if (!animsampler.output) { + ASSIMP_LOG_WARN("Animation ", anim.name, ": Missing sampler output. Skipping."); + continue; + } + + if (animsampler.input->count > animsampler.output->count) { + ASSIMP_LOG_WARN("Animation ", anim.name, ": Number of keyframes in sampler input ", animsampler.input->count, " exceeds number of keyframes in sampler output ", animsampler.output->count); + continue; + } + + const unsigned int node_index = channel.target.node.GetIndex(); + + AnimationSamplers &sampler = samplers[node_index]; + if (channel.target.path == AnimationPath_TRANSLATION) { + sampler.translation = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_ROTATION) { + sampler.rotation = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_SCALE) { + sampler.scale = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_WEIGHTS) { + sampler.weight = &anim.samplers[channel.sampler]; + } + } + + return samplers; +} + +void glTF2Importer::ImportAnimations(glTF2::Asset &r) { + if (!r.scene) return; + + const unsigned numAnimations = r.animations.Size(); + ASSIMP_LOG_DEBUG("Importing ", numAnimations, " animations"); + mScene->mNumAnimations = numAnimations; + if (mScene->mNumAnimations == 0) { + return; + } + + mScene->mAnimations = new aiAnimation *[numAnimations]; + std::fill(mScene->mAnimations, mScene->mAnimations + numAnimations, nullptr); + + for (unsigned int i = 0; i < numAnimations; ++i) { + aiAnimation *ai_anim = mScene->mAnimations[i] = new aiAnimation(); + + Animation &anim = r.animations[i]; + + ai_anim->mName = anim.name; + ai_anim->mDuration = 0; + ai_anim->mTicksPerSecond = 0; + + std::unordered_map<unsigned int, AnimationSamplers> samplers = GatherSamplers(anim); + + uint32_t numChannels = 0u; + uint32_t numMorphMeshChannels = 0u; + + for (auto &iter : samplers) { + if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) { + ++numChannels; + } + if (nullptr != iter.second.weight) { + ++numMorphMeshChannels; + } + } + + ai_anim->mNumChannels = numChannels; + if (ai_anim->mNumChannels > 0) { + ai_anim->mChannels = new aiNodeAnim *[ai_anim->mNumChannels]; + std::fill(ai_anim->mChannels, ai_anim->mChannels + ai_anim->mNumChannels, nullptr); + int j = 0; + for (auto &iter : samplers) { + if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) { + ai_anim->mChannels[j] = CreateNodeAnim(r, r.nodes[iter.first], iter.second); + ++j; + } + } + } + + ai_anim->mNumMorphMeshChannels = numMorphMeshChannels; + if (ai_anim->mNumMorphMeshChannels > 0) { + ai_anim->mMorphMeshChannels = new aiMeshMorphAnim *[ai_anim->mNumMorphMeshChannels]; + std::fill(ai_anim->mMorphMeshChannels, ai_anim->mMorphMeshChannels + ai_anim->mNumMorphMeshChannels, nullptr); + int j = 0; + for (auto &iter : samplers) { + if (nullptr != iter.second.weight) { + ai_anim->mMorphMeshChannels[j] = CreateMeshMorphAnim(r, r.nodes[iter.first], iter.second); + ++j; + } + } + } + + // Use the latest key-frame for the duration of the animation + double maxDuration = 0; + unsigned int maxNumberOfKeys = 0; + for (unsigned int j = 0; j < ai_anim->mNumChannels; ++j) { + auto chan = ai_anim->mChannels[j]; + if (chan->mNumPositionKeys) { + auto lastPosKey = chan->mPositionKeys[chan->mNumPositionKeys - 1]; + if (lastPosKey.mTime > maxDuration) { + maxDuration = lastPosKey.mTime; + } + maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumPositionKeys); + } + if (chan->mNumRotationKeys) { + auto lastRotKey = chan->mRotationKeys[chan->mNumRotationKeys - 1]; + if (lastRotKey.mTime > maxDuration) { + maxDuration = lastRotKey.mTime; + } + maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumRotationKeys); + } + if (chan->mNumScalingKeys) { + auto lastScaleKey = chan->mScalingKeys[chan->mNumScalingKeys - 1]; + if (lastScaleKey.mTime > maxDuration) { + maxDuration = lastScaleKey.mTime; + } + maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumScalingKeys); + } + } + + for (unsigned int j = 0; j < ai_anim->mNumMorphMeshChannels; ++j) { + const auto *const chan = ai_anim->mMorphMeshChannels[j]; + + if (0u != chan->mNumKeys) { + const auto &lastKey = chan->mKeys[chan->mNumKeys - 1u]; + if (lastKey.mTime > maxDuration) { + maxDuration = lastKey.mTime; + } + maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumKeys); + } + } + + ai_anim->mDuration = maxDuration; + ai_anim->mTicksPerSecond = 1000.0; + } +} + +static unsigned int countEmbeddedTextures(glTF2::Asset &r) { + unsigned int numEmbeddedTexs = 0; + for (size_t i = 0; i < r.images.Size(); ++i) { + if (r.images[i].HasData()) { + numEmbeddedTexs += 1; + } + } + + return numEmbeddedTexs; +} + +void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset &r) { + mEmbeddedTexIdxs.resize(r.images.Size(), -1); + const unsigned int numEmbeddedTexs = countEmbeddedTextures(r); + if (numEmbeddedTexs == 0) { + return; + } + + ASSIMP_LOG_DEBUG("Importing ", numEmbeddedTexs, " embedded textures"); + + mScene->mTextures = new aiTexture *[numEmbeddedTexs]; + std::fill(mScene->mTextures, mScene->mTextures + numEmbeddedTexs, nullptr); + + // Add the embedded textures + for (size_t i = 0; i < r.images.Size(); ++i) { + Image &img = r.images[i]; + if (!img.HasData()) { + continue; + } + + int idx = mScene->mNumTextures++; + mEmbeddedTexIdxs[i] = idx; + + aiTexture *tex = mScene->mTextures[idx] = new aiTexture(); + + size_t length = img.GetDataLength(); + void *data = img.StealData(); + + tex->mFilename = img.name; + tex->mWidth = static_cast<unsigned int>(length); + tex->mHeight = 0; + tex->pcData = reinterpret_cast<aiTexel *>(data); + + if (!img.mimeType.empty()) { + const char *ext = strchr(img.mimeType.c_str(), '/') + 1; + if (ext) { + if (strcmp(ext, "jpeg") == 0) { + ext = "jpg"; + } else if(strcmp(ext, "ktx2") == 0) { //basisu: ktx remains + ext = "kx2"; + } else if(strcmp(ext, "basis") == 0) { //basisu + ext = "bu"; + } + + size_t len = strlen(ext); + if (len <= 3) { + strcpy(tex->achFormatHint, ext); + } + } + } + } +} + +void glTF2Importer::ImportCommonMetadata(glTF2::Asset& a) { + ASSIMP_LOG_DEBUG("Importing metadata"); + ai_assert(mScene->mMetaData == nullptr); + const bool hasVersion = !a.asset.version.empty(); + const bool hasGenerator = !a.asset.generator.empty(); + const bool hasCopyright = !a.asset.copyright.empty(); + const bool hasSceneMetadata = a.scene->customExtensions; + if (hasVersion || hasGenerator || hasCopyright || hasSceneMetadata) { + mScene->mMetaData = new aiMetadata; + if (hasVersion) { + mScene->mMetaData->Add(AI_METADATA_SOURCE_FORMAT_VERSION, aiString(a.asset.version)); + } + if (hasGenerator) { + mScene->mMetaData->Add(AI_METADATA_SOURCE_GENERATOR, aiString(a.asset.generator)); + } + if (hasCopyright) { + mScene->mMetaData->Add(AI_METADATA_SOURCE_COPYRIGHT, aiString(a.asset.copyright)); + } + if (hasSceneMetadata) { + ParseExtensions(mScene->mMetaData, a.scene->customExtensions); + } + } +} + +void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + ASSIMP_LOG_DEBUG("Reading GLTF2 file"); + + // clean all member arrays + meshOffsets.clear(); + mEmbeddedTexIdxs.clear(); + + this->mScene = pScene; + + // read the asset file + glTF2::Asset asset(pIOHandler, static_cast<rapidjson::IRemoteSchemaDocumentProvider*>(mSchemaDocumentProvider)); + asset.Load(pFile, GetExtension(pFile) == "glb"); + if (asset.scene) { + pScene->mName = asset.scene->name; + } + + // Copy the data out + ImportEmbeddedTextures(asset); + ImportMaterials(asset); + + ImportMeshes(asset); + + ImportCameras(asset); + ImportLights(asset); + + ImportNodes(asset); + + ImportAnimations(asset); + + ImportCommonMetadata(asset); + + if (pScene->mNumMeshes == 0) { + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } +} + +void glTF2Importer::SetupProperties(const Importer *pImp) { + mSchemaDocumentProvider = static_cast<rapidjson::IRemoteSchemaDocumentProvider*>(pImp->GetPropertyPointer(AI_CONFIG_IMPORT_SCHEMA_DOCUMENT_PROVIDER)); +} + +#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER diff --git a/libs/assimp/code/AssetLib/glTF2/glTF2Importer.h b/libs/assimp/code/AssetLib/glTF2/glTF2Importer.h new file mode 100644 index 0000000..831bcd7 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF2/glTF2Importer.h @@ -0,0 +1,91 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef AI_GLTF2IMPORTER_H_INC +#define AI_GLTF2IMPORTER_H_INC + +#include <assimp/BaseImporter.h> + +struct aiNode; + +namespace glTF2 { + class Asset; +} + +namespace Assimp { + +/** + * Load the glTF2 format. + * https://github.com/KhronosGroup/glTF/tree/master/specification + */ +class glTF2Importer : public BaseImporter { +public: + glTF2Importer(); + ~glTF2Importer() override; + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + +protected: + const aiImporterDesc *GetInfo() const override; + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + virtual void SetupProperties(const Importer *pImp) override; + +private: + void ImportEmbeddedTextures(glTF2::Asset &a); + void ImportMaterials(glTF2::Asset &a); + void ImportMeshes(glTF2::Asset &a); + void ImportCameras(glTF2::Asset &a); + void ImportLights(glTF2::Asset &a); + void ImportNodes(glTF2::Asset &a); + void ImportAnimations(glTF2::Asset &a); + void ImportCommonMetadata(glTF2::Asset &a); + +private: + std::vector<unsigned int> meshOffsets; + std::vector<int> mEmbeddedTexIdxs; + aiScene *mScene; + + /// An instance of rapidjson::IRemoteSchemaDocumentProvider + void *mSchemaDocumentProvider = nullptr; +}; + +} // namespace Assimp + +#endif // AI_GLTF2IMPORTER_H_INC diff --git a/libs/assimp/code/CApi/AssimpCExport.cpp b/libs/assimp/code/CApi/AssimpCExport.cpp new file mode 100644 index 0000000..5e43958 --- /dev/null +++ b/libs/assimp/code/CApi/AssimpCExport.cpp @@ -0,0 +1,144 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AssimpCExport.cpp +Assimp C export interface. See Exporter.cpp for some notes. +*/ + +#ifndef ASSIMP_BUILD_NO_EXPORT + +#include "CInterfaceIOWrapper.h" +#include "Common/ScenePrivate.h" +#include <assimp/SceneCombiner.h> +#include <assimp/Exporter.hpp> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API size_t aiGetExportFormatCount(void) { + return Exporter().GetExportFormatCount(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API const aiExportFormatDesc *aiGetExportFormatDescription(size_t index) { + // Note: this is valid as the index always pertains to a built-in exporter, + // for which the returned structure is guaranteed to be of static storage duration. + Exporter exporter; + const aiExportFormatDesc *orig(exporter.GetExportFormatDescription(index)); + if (nullptr == orig) { + return nullptr; + } + + aiExportFormatDesc *desc = new aiExportFormatDesc; + desc->description = new char[strlen(orig->description) + 1](); + ::memcpy((char *)desc->description, orig->description, strlen(orig->description)); + desc->fileExtension = new char[strlen(orig->fileExtension) + 1](); + ::memcpy((char *)desc->fileExtension, orig->fileExtension, strlen(orig->fileExtension)); + desc->id = new char[strlen(orig->id) + 1](); + ::memcpy((char *)desc->id, orig->id, strlen(orig->id)); + + return desc; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiReleaseExportFormatDescription(const aiExportFormatDesc *desc) { + if (nullptr == desc) { + return; + } + + delete[] desc->description; + delete[] desc->fileExtension; + delete[] desc->id; + delete desc; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiCopyScene(const aiScene *pIn, aiScene **pOut) { + if (!pOut || !pIn) { + return; + } + + SceneCombiner::CopyScene(pOut, pIn, true); + ScenePriv(*pOut)->mIsCopy = true; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiFreeScene(const C_STRUCT aiScene *pIn) { + // note: aiReleaseImport() is also able to delete scene copies, but in addition + // it also handles scenes with import metadata. + delete pIn; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API aiReturn aiExportScene(const aiScene *pScene, const char *pFormatId, const char *pFileName, unsigned int pPreprocessing) { + return ::aiExportSceneEx(pScene, pFormatId, pFileName, nullptr, pPreprocessing); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API aiReturn aiExportSceneEx(const aiScene *pScene, const char *pFormatId, const char *pFileName, aiFileIO *pIO, unsigned int pPreprocessing) { + Exporter exp; + + if (pIO) { + exp.SetIOHandler(new CIOSystemWrapper(pIO)); + } + return exp.Export(pScene, pFormatId, pFileName, pPreprocessing); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API const C_STRUCT aiExportDataBlob *aiExportSceneToBlob(const aiScene *pScene, const char *pFormatId, unsigned int pPreprocessing) { + Exporter exp; + if (!exp.ExportToBlob(pScene, pFormatId, pPreprocessing)) { + return nullptr; + } + const aiExportDataBlob *blob = exp.GetOrphanedBlob(); + ai_assert(blob); + + return blob; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API C_STRUCT void aiReleaseExportBlob(const aiExportDataBlob *pData) { + delete pData; +} + +#endif // !ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/CApi/CInterfaceIOWrapper.cpp b/libs/assimp/code/CApi/CInterfaceIOWrapper.cpp new file mode 100644 index 0000000..579545e --- /dev/null +++ b/libs/assimp/code/CApi/CInterfaceIOWrapper.cpp @@ -0,0 +1,131 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 aiFileIO -> IOSystem wrapper*/ + +#include "CInterfaceIOWrapper.h" + +namespace Assimp { + +CIOStreamWrapper::~CIOStreamWrapper() { + /* Various places depend on this destructor to close the file */ + if (mFile) { + mIO->mFileSystem->CloseProc(mIO->mFileSystem, mFile); + } +} + +// ................................................................... +size_t CIOStreamWrapper::Read(void *pvBuffer, + size_t pSize, + size_t pCount) { + // need to typecast here as C has no void* + return mFile->ReadProc(mFile, (char *)pvBuffer, pSize, pCount); +} + +// ................................................................... +size_t CIOStreamWrapper::Write(const void *pvBuffer, + size_t pSize, + size_t pCount) { + // need to typecast here as C has no void* + return mFile->WriteProc(mFile, (const char *)pvBuffer, pSize, pCount); +} + +// ................................................................... +aiReturn CIOStreamWrapper::Seek(size_t pOffset, + aiOrigin pOrigin) { + return mFile->SeekProc(mFile, pOffset, pOrigin); +} + +// ................................................................... +size_t CIOStreamWrapper::Tell() const { + return mFile->TellProc(mFile); +} + +// ................................................................... +size_t CIOStreamWrapper::FileSize() const { + return mFile->FileSizeProc(mFile); +} + +// ................................................................... +void CIOStreamWrapper::Flush() { + return mFile->FlushProc(mFile); +} + +// ------------------------------------------------------------------------------------------------ +// Custom IOStream implementation for the C-API +bool CIOSystemWrapper::Exists(const char *pFile) const { + aiFile *p = mFileSystem->OpenProc(mFileSystem, pFile, "rb"); + if (p) { + mFileSystem->CloseProc(mFileSystem, p); + return true; + } + return false; +} + +// ................................................................... +char CIOSystemWrapper::getOsSeparator() const { +#ifndef _WIN32 + return '/'; +#else + return '\\'; +#endif +} + +// ................................................................... +IOStream *CIOSystemWrapper::Open(const char *pFile, const char *pMode) { + aiFile *p = mFileSystem->OpenProc(mFileSystem, pFile, pMode); + if (!p) { + return nullptr; + } + return new CIOStreamWrapper(p, this); +} + +// ................................................................... +void CIOSystemWrapper::Close(IOStream *pFile) { + if (!pFile) { + return; + } + delete pFile; +} + +} // namespace Assimp diff --git a/libs/assimp/code/CApi/CInterfaceIOWrapper.h b/libs/assimp/code/CApi/CInterfaceIOWrapper.h new file mode 100644 index 0000000..768be37 --- /dev/null +++ b/libs/assimp/code/CApi/CInterfaceIOWrapper.h @@ -0,0 +1,94 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 aiFileIO -> IOSystem wrapper*/ + +#ifndef AI_CIOSYSTEM_H_INCLUDED +#define AI_CIOSYSTEM_H_INCLUDED + +#include <assimp/cfileio.h> +#include <assimp/IOStream.hpp> +#include <assimp/IOSystem.hpp> + +namespace Assimp { + +class CIOSystemWrapper; + +// ------------------------------------------------------------------------------------------------ +// Custom IOStream implementation for the C-API +class CIOStreamWrapper : public IOStream { +public: + explicit CIOStreamWrapper(aiFile *pFile, CIOSystemWrapper *io) : + mFile(pFile), + mIO(io) {} + ~CIOStreamWrapper(void); + + size_t Read(void *pvBuffer, size_t pSize, size_t pCount); + size_t Write(const void *pvBuffer, size_t pSize, size_t pCount); + aiReturn Seek(size_t pOffset, aiOrigin pOrigin); + size_t Tell(void) const; + size_t FileSize() const; + void Flush(); + +private: + aiFile *mFile; + CIOSystemWrapper *mIO; +}; + +class CIOSystemWrapper : public IOSystem { + friend class CIOStreamWrapper; + +public: + explicit CIOSystemWrapper(aiFileIO *pFile) : + mFileSystem(pFile) {} + + bool Exists(const char *pFile) const; + char getOsSeparator() const; + IOStream *Open(const char *pFile, const char *pMode = "rb"); + void Close(IOStream *pFile); + +private: + aiFileIO *mFileSystem; +}; + +} // namespace Assimp + +#endif diff --git a/libs/assimp/code/CMakeLists.txt b/libs/assimp/code/CMakeLists.txt new file mode 100644 index 0000000..7a2f8a1 --- /dev/null +++ b/libs/assimp/code/CMakeLists.txt @@ -0,0 +1,1377 @@ +# Open Asset Import Library (assimp) +# ---------------------------------------------------------------------- +# +# Copyright (c) 2006-2022, 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. +# +#---------------------------------------------------------------------- + +# Listing and grouping of all the source files. +# 1) Set the file lists for each component +# 2) Create a Source Group for each component, for IDE project orginization +# 3) Add libassimp using the file lists (eliminates duplication of file names between +# source groups and library command) +# +cmake_minimum_required( VERSION 3.10 ) +SET( HEADER_PATH ../include/assimp ) + +if(NOT ANDROID AND ASSIMP_ANDROID_JNIIOSYSTEM) + message(WARNING "Requesting Android JNI I/O-System in non-Android toolchain. Resetting ASSIMP_ANDROID_JNIIOSYSTEM to OFF.") + set(ASSIMP_ANDROID_JNIIOSYSTEM OFF) +endif() + +SET( COMPILER_HEADERS + ${HEADER_PATH}/Compiler/pushpack1.h + ${HEADER_PATH}/Compiler/poppack1.h + ${HEADER_PATH}/Compiler/pstdint.h +) +SOURCE_GROUP( Compiler FILES ${COMPILER_HEADERS}) + +SET( PUBLIC_HEADERS + ${HEADER_PATH}/anim.h + ${HEADER_PATH}/aabb.h + ${HEADER_PATH}/ai_assert.h + ${HEADER_PATH}/camera.h + ${HEADER_PATH}/color4.h + ${HEADER_PATH}/color4.inl + ${CMAKE_CURRENT_BINARY_DIR}/../include/assimp/config.h + ${HEADER_PATH}/ColladaMetaData.h + ${HEADER_PATH}/commonMetaData.h + ${HEADER_PATH}/defs.h + ${HEADER_PATH}/cfileio.h + ${HEADER_PATH}/light.h + ${HEADER_PATH}/material.h + ${HEADER_PATH}/material.inl + ${HEADER_PATH}/matrix3x3.h + ${HEADER_PATH}/matrix3x3.inl + ${HEADER_PATH}/matrix4x4.h + ${HEADER_PATH}/matrix4x4.inl + ${HEADER_PATH}/mesh.h + ${HEADER_PATH}/pbrmaterial.h + ${HEADER_PATH}/GltfMaterial.h + ${HEADER_PATH}/postprocess.h + ${HEADER_PATH}/quaternion.h + ${HEADER_PATH}/quaternion.inl + ${HEADER_PATH}/scene.h + ${HEADER_PATH}/metadata.h + ${HEADER_PATH}/texture.h + ${HEADER_PATH}/types.h + ${HEADER_PATH}/vector2.h + ${HEADER_PATH}/vector2.inl + ${HEADER_PATH}/vector3.h + ${HEADER_PATH}/vector3.inl + ${HEADER_PATH}/version.h + ${HEADER_PATH}/cimport.h + ${HEADER_PATH}/importerdesc.h + ${HEADER_PATH}/Importer.hpp + ${HEADER_PATH}/DefaultLogger.hpp + ${HEADER_PATH}/ProgressHandler.hpp + ${HEADER_PATH}/IOStream.hpp + ${HEADER_PATH}/IOSystem.hpp + ${HEADER_PATH}/Logger.hpp + ${HEADER_PATH}/LogStream.hpp + ${HEADER_PATH}/NullLogger.hpp + ${HEADER_PATH}/cexport.h + ${HEADER_PATH}/Exporter.hpp + ${HEADER_PATH}/DefaultIOStream.h + ${HEADER_PATH}/DefaultIOSystem.h + ${HEADER_PATH}/ZipArchiveIOSystem.h + ${HEADER_PATH}/SceneCombiner.h + ${HEADER_PATH}/fast_atof.h + ${HEADER_PATH}/qnan.h + ${HEADER_PATH}/BaseImporter.h + ${HEADER_PATH}/Hash.h + ${HEADER_PATH}/MemoryIOWrapper.h + ${HEADER_PATH}/ParsingUtils.h + ${HEADER_PATH}/StreamReader.h + ${HEADER_PATH}/StreamWriter.h + ${HEADER_PATH}/StringComparison.h + ${HEADER_PATH}/StringUtils.h + ${HEADER_PATH}/SGSpatialSort.h + ${HEADER_PATH}/GenericProperty.h + ${HEADER_PATH}/SpatialSort.h + ${HEADER_PATH}/SkeletonMeshBuilder.h + ${HEADER_PATH}/SmallVector.h + ${HEADER_PATH}/SmoothingGroups.h + ${HEADER_PATH}/SmoothingGroups.inl + ${HEADER_PATH}/StandardShapes.h + ${HEADER_PATH}/RemoveComments.h + ${HEADER_PATH}/Subdivision.h + ${HEADER_PATH}/Vertex.h + ${HEADER_PATH}/LineSplitter.h + ${HEADER_PATH}/TinyFormatter.h + ${HEADER_PATH}/Profiler.h + ${HEADER_PATH}/LogAux.h + ${HEADER_PATH}/Bitmap.h + ${HEADER_PATH}/XMLTools.h + ${HEADER_PATH}/IOStreamBuffer.h + ${HEADER_PATH}/CreateAnimMesh.h + ${HEADER_PATH}/XmlParser.h + ${HEADER_PATH}/BlobIOSystem.h + ${HEADER_PATH}/MathFunctions.h + ${HEADER_PATH}/Exceptional.h + ${HEADER_PATH}/ByteSwapper.h + ${HEADER_PATH}/Base64.hpp +) + +SET( Core_SRCS + Common/Assimp.cpp +) + +IF(MSVC) + list(APPEND Core_SRCS "res/assimp.rc") +ENDIF() + +SET( Logging_SRCS + ${HEADER_PATH}/DefaultLogger.hpp + ${HEADER_PATH}/LogStream.hpp + ${HEADER_PATH}/Logger.hpp + ${HEADER_PATH}/NullLogger.hpp + Common/Win32DebugLogStream.h + Common/DefaultLogger.cpp + Common/FileLogStream.h + Common/StdOStreamLogStream.h +) +SOURCE_GROUP(Logging FILES ${Logging_SRCS}) + +SET( Common_SRCS + Common/Compression.cpp + Common/Compression.h + Common/BaseImporter.cpp + Common/BaseProcess.cpp + Common/BaseProcess.h + Common/Importer.h + Common/ScenePrivate.h + Common/PostStepRegistry.cpp + Common/ImporterRegistry.cpp + Common/DefaultProgressHandler.h + Common/DefaultIOStream.cpp + Common/IOSystem.cpp + Common/DefaultIOSystem.cpp + Common/ZipArchiveIOSystem.cpp + Common/PolyTools.h + Common/Importer.cpp + Common/IFF.h + Common/SGSpatialSort.cpp + Common/VertexTriangleAdjacency.cpp + Common/VertexTriangleAdjacency.h + Common/SpatialSort.cpp + Common/SceneCombiner.cpp + Common/ScenePreprocessor.cpp + Common/ScenePreprocessor.h + Common/SkeletonMeshBuilder.cpp + Common/StandardShapes.cpp + Common/TargetAnimation.cpp + Common/TargetAnimation.h + Common/RemoveComments.cpp + Common/Subdivision.cpp + Common/scene.cpp + Common/Bitmap.cpp + Common/Version.cpp + Common/CreateAnimMesh.cpp + Common/simd.h + Common/simd.cpp + Common/material.cpp + Common/AssertHandler.cpp + Common/Exceptional.cpp + Common/Base64.cpp +) +SOURCE_GROUP(Common FILES ${Common_SRCS}) + +SET( CApi_SRCS + CApi/CInterfaceIOWrapper.cpp + CApi/CInterfaceIOWrapper.h +) +SOURCE_GROUP(CApi FILES ${CApi_SRCS}) + +SET( STEPParser_SRCS + AssetLib/STEPParser/STEPFileReader.h + AssetLib/STEPParser/STEPFileReader.cpp + AssetLib/STEPParser/STEPFileEncoding.cpp + AssetLib/STEPParser/STEPFileEncoding.h +) +SOURCE_GROUP(STEPParser FILES ${STEPParser_SRCS}) + +IF ( ASSIMP_BUILD_NONFREE_C4D_IMPORTER ) + SET( C4D_SRCS + AssetLib/C4D/C4DImporter.cpp + AssetLib/C4D/C4DImporter.h + ) + SOURCE_GROUP( C4D FILES ${C4D_SRCS}) +ENDIF () + +# if this variable is set to TRUE, the user can manually disable importers by setting +# ASSIMP_BUILD_XXX_IMPORTER to FALSE for each importer +# if this variable is set to FALSE, the user can manually enable importers by setting +# ASSIMP_BUILD_XXX_IMPORTER to TRUE for each importer +OPTION(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_IMPORTER values" TRUE) + +# macro to add the CMake Option ADD_ASSIMP_IMPORTER_<name> which enables compile of loader +# this way selective loaders can be compiled (reduces filesize + compile time) +MACRO(ADD_ASSIMP_IMPORTER name) + IF (ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT) + set(ASSIMP_IMPORTER_ENABLED TRUE) + IF (DEFINED ASSIMP_BUILD_${name}_IMPORTER AND NOT ASSIMP_BUILD_${name}_IMPORTER) + set(ASSIMP_IMPORTER_ENABLED FALSE) + ENDIF () + ELSE () + set(ASSIMP_IMPORTER_ENABLED ${ASSIMP_BUILD_${name}_IMPORTER}) + ENDIF () + IF (ASSIMP_IMPORTER_ENABLED) + LIST(APPEND ASSIMP_LOADER_SRCS ${ARGN}) + SET(ASSIMP_IMPORTERS_ENABLED "${ASSIMP_IMPORTERS_ENABLED} ${name}") + SOURCE_GROUP(AssetLib\\${name} FILES ${ARGN}) + ELSE() + SET(${name}_SRC "") + SET(ASSIMP_IMPORTERS_DISABLED "${ASSIMP_IMPORTERS_DISABLED} ${name}") + add_definitions(-DASSIMP_BUILD_NO_${name}_IMPORTER) + ENDIF() +ENDMACRO() + +if (NOT ASSIMP_NO_EXPORT) + + # if this variable is set to TRUE, the user can manually disable exporters by setting + # ASSIMP_BUILD_XXX_EXPORTER to FALSE for each exporter + # if this variable is set to FALSE, the user can manually enable exporters by setting + # ASSIMP_BUILD_XXX_EXPORTER to TRUE for each exporter + OPTION(ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_EXPORTER values" TRUE) + + # macro to add the CMake Option ADD_ASSIMP_IMPORTER_<name> which enables compile of loader + # this way selective loaders can be compiled (reduces filesize + compile time) + MACRO(ADD_ASSIMP_EXPORTER name) + IF (ASSIMP_NO_EXPORT) + set(ASSIMP_EXPORTER_ENABLED FALSE) + ELSEIF (ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT) + set(ASSIMP_EXPORTER_ENABLED TRUE) + IF (DEFINED ASSIMP_BUILD_${name}_EXPORTER AND NOT ASSIMP_BUILD_${name}_EXPORTER) + set(ASSIMP_EXPORTER_ENABLED FALSE) + ENDIF () + ELSE () + set(ASSIMP_EXPORTER_ENABLED ${ASSIMP_BUILD_${name}_EXPORTER}) + ENDIF () + + IF (ASSIMP_EXPORTER_ENABLED) + SET(ASSIMP_EXPORTERS_ENABLED "${ASSIMP_EXPORTERS_ENABLED} ${name}") + LIST(APPEND ASSIMP_EXPORTER_SRCS ${ARGN}) + SOURCE_GROUP(AssetLib\\${name} FILES ${ARGN}) + ELSE() + SET(ASSIMP_EXPORTERS_DISABLED "${ASSIMP_EXPORTERS_DISABLED} ${name}") + add_definitions(-DASSIMP_BUILD_NO_${name}_EXPORTER) + ENDIF() + ENDMACRO() + +endif() + +SET(ASSIMP_LOADER_SRCS "") +SET(ASSIMP_IMPORTERS_ENABLED "") # list of enabled importers +SET(ASSIMP_IMPORTERS_DISABLED "") # disabled importers list (used to print) + +SET(ASSIMP_EXPORTER_SRCS "") +SET(ASSIMP_EXPORTERS_ENABLED "") # list of enabled exporters +SET(ASSIMP_EXPORTERS_DISABLED "") # disabled exporters list (used to print) + +ADD_ASSIMP_IMPORTER( AMF + AssetLib/AMF/AMFImporter.hpp + AssetLib/AMF/AMFImporter_Node.hpp + AssetLib/AMF/AMFImporter.cpp + AssetLib/AMF/AMFImporter_Geometry.cpp + AssetLib/AMF/AMFImporter_Material.cpp + AssetLib/AMF/AMFImporter_Postprocess.cpp +) + +ADD_ASSIMP_IMPORTER( 3DS + AssetLib/3DS/3DSConverter.cpp + AssetLib/3DS/3DSHelper.h + AssetLib/3DS/3DSLoader.cpp + AssetLib/3DS/3DSLoader.h +) + +ADD_ASSIMP_IMPORTER( AC + AssetLib/AC/ACLoader.cpp + AssetLib/AC/ACLoader.h +) + +ADD_ASSIMP_IMPORTER( ASE + AssetLib/ASE/ASELoader.cpp + AssetLib/ASE/ASELoader.h + AssetLib/ASE/ASEParser.cpp + AssetLib/ASE/ASEParser.h +) + +ADD_ASSIMP_IMPORTER( ASSBIN + AssetLib/Assbin/AssbinLoader.h + AssetLib/Assbin/AssbinLoader.cpp +) + +ADD_ASSIMP_IMPORTER( B3D + AssetLib/B3D/B3DImporter.cpp + AssetLib/B3D/B3DImporter.h +) + +ADD_ASSIMP_IMPORTER( BVH + AssetLib/BVH/BVHLoader.cpp + AssetLib/BVH/BVHLoader.h +) + +ADD_ASSIMP_IMPORTER( COLLADA + AssetLib/Collada/ColladaHelper.cpp + AssetLib/Collada/ColladaHelper.h + AssetLib/Collada/ColladaLoader.cpp + AssetLib/Collada/ColladaLoader.h + AssetLib/Collada/ColladaParser.cpp + AssetLib/Collada/ColladaParser.h +) + +ADD_ASSIMP_IMPORTER( DXF + AssetLib/DXF/DXFLoader.cpp + AssetLib/DXF/DXFLoader.h + AssetLib/DXF/DXFHelper.h +) + +ADD_ASSIMP_IMPORTER( CSM + AssetLib/CSM/CSMLoader.cpp + AssetLib/CSM/CSMLoader.h +) + +ADD_ASSIMP_IMPORTER( HMP + AssetLib/HMP/HMPFileData.h + AssetLib/HMP/HMPLoader.cpp + AssetLib/HMP/HMPLoader.h + AssetLib/HMP/HalfLifeFileData.h +) + +ADD_ASSIMP_IMPORTER( IRRMESH + AssetLib/Irr/IRRMeshLoader.cpp + AssetLib/Irr/IRRMeshLoader.h + AssetLib/Irr/IRRShared.cpp + AssetLib/Irr/IRRShared.h +) + +ADD_ASSIMP_IMPORTER( IQM + AssetLib/IQM/IQMImporter.cpp + AssetLib/IQM/iqm.h + AssetLib/IQM/IQMImporter.h +) + +ADD_ASSIMP_IMPORTER( IRR + AssetLib/Irr/IRRLoader.cpp + AssetLib/Irr/IRRLoader.h + AssetLib/Irr/IRRShared.cpp + AssetLib/Irr/IRRShared.h +) + +ADD_ASSIMP_IMPORTER( LWO + AssetLib/LWO/LWOAnimation.cpp + AssetLib/LWO/LWOAnimation.h + AssetLib/LWO/LWOBLoader.cpp + AssetLib/LWO/LWOFileData.h + AssetLib/LWO/LWOLoader.cpp + AssetLib/LWO/LWOLoader.h + AssetLib/LWO/LWOMaterial.cpp +) + +ADD_ASSIMP_IMPORTER( LWS + AssetLib/LWS/LWSLoader.cpp + AssetLib/LWS/LWSLoader.h +) + +ADD_ASSIMP_IMPORTER( M3D + AssetLib/M3D/M3DMaterials.h + AssetLib/M3D/M3DImporter.h + AssetLib/M3D/M3DImporter.cpp + AssetLib/M3D/M3DWrapper.h + AssetLib/M3D/M3DWrapper.cpp + AssetLib/M3D/m3d.h +) + +ADD_ASSIMP_IMPORTER( MD2 + AssetLib/MD2/MD2FileData.h + AssetLib/MD2/MD2Loader.cpp + AssetLib/MD2/MD2Loader.h + AssetLib/MD2/MD2NormalTable.h +) + +ADD_ASSIMP_IMPORTER( MD3 + AssetLib/MD3/MD3FileData.h + AssetLib/MD3/MD3Loader.cpp + AssetLib/MD3/MD3Loader.h +) + +ADD_ASSIMP_IMPORTER( MD5 + AssetLib/MD5/MD5Loader.cpp + AssetLib/MD5/MD5Loader.h + AssetLib/MD5/MD5Parser.cpp + AssetLib/MD5/MD5Parser.h +) + +ADD_ASSIMP_IMPORTER( MDC + AssetLib/MDC/MDCFileData.h + AssetLib/MDC/MDCLoader.cpp + AssetLib/MDC/MDCLoader.h + AssetLib/MDC/MDCNormalTable.h +) + +ADD_ASSIMP_IMPORTER( MDL + AssetLib/MDL/MDLDefaultColorMap.h + AssetLib/MDL/MDLFileData.h + AssetLib/MDL/MDLLoader.cpp + AssetLib/MDL/MDLLoader.h + AssetLib/MDL/MDLMaterialLoader.cpp + AssetLib/MDL/HalfLife/HalfLifeMDLBaseHeader.h + AssetLib/MDL/HalfLife/HL1FileData.h + AssetLib/MDL/HalfLife/HL1MDLLoader.cpp + AssetLib/MDL/HalfLife/HL1MDLLoader.h + AssetLib/MDL/HalfLife/HL1ImportDefinitions.h + AssetLib/MDL/HalfLife/HL1ImportSettings.h + AssetLib/MDL/HalfLife/HL1MeshTrivert.h + AssetLib/MDL/HalfLife/LogFunctions.h + AssetLib/MDL/HalfLife/UniqueNameGenerator.cpp + AssetLib/MDL/HalfLife/UniqueNameGenerator.h +) + +SET( MaterialSystem_SRCS + Material/MaterialSystem.cpp + Material/MaterialSystem.h +) +SOURCE_GROUP( MaterialSystem FILES ${MaterialSystem_SRCS}) + +ADD_ASSIMP_IMPORTER( NFF + AssetLib/NFF/NFFLoader.cpp + AssetLib/NFF/NFFLoader.h +) + +ADD_ASSIMP_IMPORTER( NDO + AssetLib/NDO/NDOLoader.cpp + AssetLib/NDO/NDOLoader.h +) + +ADD_ASSIMP_IMPORTER( OFF + AssetLib/OFF/OFFLoader.cpp + AssetLib/OFF/OFFLoader.h +) + +ADD_ASSIMP_IMPORTER( OBJ + AssetLib/Obj/ObjFileData.h + AssetLib/Obj/ObjFileImporter.cpp + AssetLib/Obj/ObjFileImporter.h + AssetLib/Obj/ObjFileMtlImporter.cpp + AssetLib/Obj/ObjFileMtlImporter.h + AssetLib/Obj/ObjFileParser.cpp + AssetLib/Obj/ObjFileParser.h + AssetLib/Obj/ObjTools.h +) + +ADD_ASSIMP_IMPORTER( OGRE + AssetLib/Ogre/OgreImporter.h + AssetLib/Ogre/OgreStructs.h + AssetLib/Ogre/OgreParsingUtils.h + AssetLib/Ogre/OgreBinarySerializer.h + AssetLib/Ogre/OgreXmlSerializer.h + AssetLib/Ogre/OgreImporter.cpp + AssetLib/Ogre/OgreStructs.cpp + AssetLib/Ogre/OgreBinarySerializer.cpp + AssetLib/Ogre/OgreXmlSerializer.cpp + AssetLib/Ogre/OgreMaterial.cpp +) + +ADD_ASSIMP_IMPORTER( OPENGEX + AssetLib/OpenGEX/OpenGEXImporter.cpp + AssetLib/OpenGEX/OpenGEXImporter.h + AssetLib/OpenGEX/OpenGEXStructs.h +) + +ADD_ASSIMP_IMPORTER( PLY + AssetLib/Ply/PlyLoader.cpp + AssetLib/Ply/PlyLoader.h + AssetLib/Ply/PlyParser.cpp + AssetLib/Ply/PlyParser.h +) + +ADD_ASSIMP_IMPORTER( MS3D + AssetLib/MS3D/MS3DLoader.cpp + AssetLib/MS3D/MS3DLoader.h +) + +ADD_ASSIMP_IMPORTER( COB + AssetLib/COB/COBLoader.cpp + AssetLib/COB/COBLoader.h + AssetLib/COB/COBScene.h +) + +ADD_ASSIMP_IMPORTER( BLEND + AssetLib/Blender/BlenderLoader.cpp + AssetLib/Blender/BlenderLoader.h + AssetLib/Blender/BlenderDNA.cpp + AssetLib/Blender/BlenderDNA.h + AssetLib/Blender/BlenderDNA.inl + AssetLib/Blender/BlenderScene.cpp + AssetLib/Blender/BlenderScene.h + AssetLib/Blender/BlenderSceneGen.h + AssetLib/Blender/BlenderIntermediate.h + AssetLib/Blender/BlenderModifier.h + AssetLib/Blender/BlenderModifier.cpp + AssetLib/Blender/BlenderBMesh.h + AssetLib/Blender/BlenderBMesh.cpp + AssetLib/Blender/BlenderTessellator.h + AssetLib/Blender/BlenderTessellator.cpp + AssetLib/Blender/BlenderCustomData.h + AssetLib/Blender/BlenderCustomData.cpp +) + +ADD_ASSIMP_IMPORTER( IFC + AssetLib/IFC/IFCLoader.cpp + AssetLib/IFC/IFCLoader.h + AssetLib/IFC/IFCReaderGen1_2x3.cpp + AssetLib/IFC/IFCReaderGen2_2x3.cpp + AssetLib/IFC/IFCReaderGen_2x3.h + AssetLib/IFC/IFCUtil.h + AssetLib/IFC/IFCUtil.cpp + AssetLib/IFC/IFCGeometry.cpp + AssetLib/IFC/IFCMaterial.cpp + AssetLib/IFC/IFCProfile.cpp + AssetLib/IFC/IFCCurve.cpp + AssetLib/IFC/IFCBoolean.cpp + AssetLib/IFC/IFCOpenings.cpp +) + +if (ASSIMP_BUILD_IFC_IMPORTER) + if (MSVC) + set_source_files_properties(Importer/IFC/IFCReaderGen1_2x3.cpp Importer/IFC/IFCReaderGen2_2x3.cpp PROPERTIES COMPILE_FLAGS "/bigobj") + elseif(MINGW) + set_source_files_properties(Importer/IFC/IFCReaderGen1_2x3.cpp Importer/IFC/IFCReaderGen2_2x3.cpp PROPERTIES COMPILE_FLAGS "-O2 -Wa,-mbig-obj") + endif() +endif () + +ADD_ASSIMP_IMPORTER( XGL + AssetLib/XGL/XGLLoader.cpp + AssetLib/XGL/XGLLoader.h +) + +ADD_ASSIMP_IMPORTER( FBX + AssetLib/FBX/FBXImporter.cpp + AssetLib/FBX/FBXCompileConfig.h + AssetLib/FBX/FBXImporter.h + AssetLib/FBX/FBXParser.cpp + AssetLib/FBX/FBXParser.h + AssetLib/FBX/FBXTokenizer.cpp + AssetLib/FBX/FBXTokenizer.h + AssetLib/FBX/FBXImportSettings.h + AssetLib/FBX/FBXConverter.h + AssetLib/FBX/FBXConverter.cpp + AssetLib/FBX/FBXUtil.h + AssetLib/FBX/FBXUtil.cpp + AssetLib/FBX/FBXDocument.h + AssetLib/FBX/FBXDocument.cpp + AssetLib/FBX/FBXProperties.h + AssetLib/FBX/FBXProperties.cpp + AssetLib/FBX/FBXMeshGeometry.h + AssetLib/FBX/FBXMeshGeometry.cpp + AssetLib/FBX/FBXMaterial.cpp + AssetLib/FBX/FBXModel.cpp + AssetLib/FBX/FBXAnimation.cpp + AssetLib/FBX/FBXNodeAttribute.cpp + AssetLib/FBX/FBXDeformer.cpp + AssetLib/FBX/FBXBinaryTokenizer.cpp + AssetLib/FBX/FBXDocumentUtil.cpp + AssetLib/FBX/FBXCommon.h +) + +if (NOT ASSIMP_NO_EXPORT) + + ADD_ASSIMP_EXPORTER( OBJ + AssetLib/Obj/ObjExporter.h + AssetLib/Obj/ObjExporter.cpp) + + ADD_ASSIMP_EXPORTER( OPENGEX + AssetLib/OpenGEX/OpenGEXExporter.cpp + AssetLib/OpenGEX/OpenGEXExporter.h) + + ADD_ASSIMP_EXPORTER( PLY + AssetLib/Ply/PlyExporter.cpp + AssetLib/Ply/PlyExporter.h) + + ADD_ASSIMP_EXPORTER( 3DS + AssetLib/3DS/3DSExporter.h + AssetLib/3DS/3DSExporter.cpp) + + ADD_ASSIMP_EXPORTER( ASSBIN + AssetLib/Assbin/AssbinExporter.h + AssetLib/Assbin/AssbinExporter.cpp + AssetLib/Assbin/AssbinFileWriter.h + AssetLib/Assbin/AssbinFileWriter.cpp) + + ADD_ASSIMP_EXPORTER( ASSXML + AssetLib/Assxml/AssxmlExporter.h + AssetLib/Assxml/AssxmlExporter.cpp + AssetLib/Assxml/AssxmlFileWriter.h + AssetLib/Assxml/AssxmlFileWriter.cpp) + + ADD_ASSIMP_EXPORTER(M3D + AssetLib/M3D/M3DExporter.h + AssetLib/M3D/M3DExporter.cpp) + + ADD_ASSIMP_EXPORTER(COLLADA + AssetLib/Collada/ColladaExporter.h + AssetLib/Collada/ColladaExporter.cpp) + + ADD_ASSIMP_EXPORTER( FBX + AssetLib/FBX/FBXExporter.h + AssetLib/FBX/FBXExporter.cpp + AssetLib/FBX/FBXExportNode.h + AssetLib/FBX/FBXExportNode.cpp + AssetLib/FBX/FBXExportProperty.h + AssetLib/FBX/FBXExportProperty.cpp) + + ADD_ASSIMP_EXPORTER( STL + AssetLib/STL/STLExporter.h + AssetLib/STL/STLExporter.cpp) + + ADD_ASSIMP_EXPORTER( X + AssetLib/X/XFileExporter.h + AssetLib/X/XFileExporter.cpp) + + ADD_ASSIMP_EXPORTER( X3D + AssetLib/X3D/X3DExporter.cpp + AssetLib/X3D/X3DExporter.hpp) + + ADD_ASSIMP_EXPORTER( GLTF + AssetLib/glTF/glTFExporter.h + AssetLib/glTF/glTFExporter.cpp + AssetLib/glTF2/glTF2Exporter.h + AssetLib/glTF2/glTF2Exporter.cpp) + + ADD_ASSIMP_EXPORTER( 3MF + AssetLib/3MF/D3MFExporter.h + AssetLib/3MF/D3MFExporter.cpp) + + ADD_ASSIMP_EXPORTER( PBRT + Pbrt/PbrtExporter.h + Pbrt/PbrtExporter.cpp) + + ADD_ASSIMP_EXPORTER( ASSJSON + AssetLib/Assjson/cencode.c + AssetLib/Assjson/cencode.h + AssetLib/Assjson/json_exporter.cpp + AssetLib/Assjson/mesh_splitter.cpp + AssetLib/Assjson/mesh_splitter.h) + + ADD_ASSIMP_EXPORTER( STEP + AssetLib/Step/StepExporter.h + AssetLib/Step/StepExporter.cpp) + +endif() + +SET( PostProcessing_SRCS + PostProcessing/CalcTangentsProcess.cpp + PostProcessing/CalcTangentsProcess.h + PostProcessing/ComputeUVMappingProcess.cpp + PostProcessing/ComputeUVMappingProcess.h + PostProcessing/ConvertToLHProcess.cpp + PostProcessing/ConvertToLHProcess.h + PostProcessing/EmbedTexturesProcess.cpp + PostProcessing/EmbedTexturesProcess.h + PostProcessing/FindDegenerates.cpp + PostProcessing/FindDegenerates.h + PostProcessing/FindInstancesProcess.cpp + PostProcessing/FindInstancesProcess.h + PostProcessing/FindInvalidDataProcess.cpp + PostProcessing/FindInvalidDataProcess.h + PostProcessing/FixNormalsStep.cpp + PostProcessing/FixNormalsStep.h + PostProcessing/DropFaceNormalsProcess.cpp + PostProcessing/DropFaceNormalsProcess.h + PostProcessing/GenFaceNormalsProcess.cpp + PostProcessing/GenFaceNormalsProcess.h + PostProcessing/GenVertexNormalsProcess.cpp + PostProcessing/GenVertexNormalsProcess.h + PostProcessing/PretransformVertices.cpp + PostProcessing/PretransformVertices.h + PostProcessing/ImproveCacheLocality.cpp + PostProcessing/ImproveCacheLocality.h + PostProcessing/JoinVerticesProcess.cpp + PostProcessing/JoinVerticesProcess.h + PostProcessing/LimitBoneWeightsProcess.cpp + PostProcessing/LimitBoneWeightsProcess.h + PostProcessing/RemoveRedundantMaterials.cpp + PostProcessing/RemoveRedundantMaterials.h + PostProcessing/RemoveVCProcess.cpp + PostProcessing/RemoveVCProcess.h + PostProcessing/SortByPTypeProcess.cpp + PostProcessing/SortByPTypeProcess.h + PostProcessing/SplitLargeMeshes.cpp + PostProcessing/SplitLargeMeshes.h + PostProcessing/TextureTransform.cpp + PostProcessing/TextureTransform.h + PostProcessing/TriangulateProcess.cpp + PostProcessing/TriangulateProcess.h + PostProcessing/ValidateDataStructure.cpp + PostProcessing/ValidateDataStructure.h + PostProcessing/OptimizeGraph.cpp + PostProcessing/OptimizeGraph.h + PostProcessing/OptimizeMeshes.cpp + PostProcessing/OptimizeMeshes.h + PostProcessing/DeboneProcess.cpp + PostProcessing/DeboneProcess.h + PostProcessing/ProcessHelper.h + PostProcessing/ProcessHelper.cpp + PostProcessing/MakeVerboseFormat.cpp + PostProcessing/MakeVerboseFormat.h + PostProcessing/ScaleProcess.cpp + PostProcessing/ScaleProcess.h + PostProcessing/ArmaturePopulate.cpp + PostProcessing/ArmaturePopulate.h + PostProcessing/GenBoundingBoxesProcess.cpp + PostProcessing/GenBoundingBoxesProcess.h + PostProcessing/SplitByBoneCountProcess.cpp + PostProcessing/SplitByBoneCountProcess.h +) +SOURCE_GROUP( PostProcessing FILES ${PostProcessing_SRCS}) + +ADD_ASSIMP_IMPORTER( Q3D + AssetLib/Q3D/Q3DLoader.cpp + AssetLib/Q3D/Q3DLoader.h +) + +ADD_ASSIMP_IMPORTER( Q3BSP + AssetLib/Q3BSP/Q3BSPFileData.h + AssetLib/Q3BSP/Q3BSPFileParser.h + AssetLib/Q3BSP/Q3BSPFileParser.cpp + AssetLib/Q3BSP/Q3BSPFileImporter.h + AssetLib/Q3BSP/Q3BSPFileImporter.cpp +) + +ADD_ASSIMP_IMPORTER( RAW + AssetLib/Raw/RawLoader.cpp + AssetLib/Raw/RawLoader.h +) + +ADD_ASSIMP_IMPORTER( SIB + AssetLib/SIB/SIBImporter.cpp + AssetLib/SIB/SIBImporter.h +) + +ADD_ASSIMP_IMPORTER( SMD + AssetLib/SMD/SMDLoader.cpp + AssetLib/SMD/SMDLoader.h +) + +ADD_ASSIMP_IMPORTER( STL + AssetLib/STL/STLLoader.cpp + AssetLib/STL/STLLoader.h +) + +ADD_ASSIMP_IMPORTER( TERRAGEN + AssetLib/Terragen/TerragenLoader.cpp + AssetLib/Terragen/TerragenLoader.h +) + +ADD_ASSIMP_IMPORTER( 3D + AssetLib/Unreal/UnrealLoader.cpp + AssetLib/Unreal/UnrealLoader.h +) + +ADD_ASSIMP_IMPORTER( X + AssetLib/X/XFileHelper.h + AssetLib/X/XFileImporter.cpp + AssetLib/X/XFileImporter.h + AssetLib/X/XFileParser.cpp + AssetLib/X/XFileParser.h +) + +ADD_ASSIMP_IMPORTER( X3D + AssetLib/X3D/X3DImporter.cpp + AssetLib/X3D/X3DImporter_Geometry2D.cpp + AssetLib/X3D/X3DImporter_Geometry3D.cpp + AssetLib/X3D/X3DImporter_Group.cpp + AssetLib/X3D/X3DImporter_Light.cpp + AssetLib/X3D/X3DImporter_Metadata.cpp + AssetLib/X3D/X3DImporter_Networking.cpp + AssetLib/X3D/X3DImporter_Postprocess.cpp + AssetLib/X3D/X3DImporter_Rendering.cpp + AssetLib/X3D/X3DImporter_Shape.cpp + AssetLib/X3D/X3DImporter_Texturing.cpp + AssetLib/X3D/X3DImporter.hpp + AssetLib/X3D/X3DImporter_Macro.hpp + AssetLib/X3D/X3DImporter_Node.hpp + AssetLib/X3D/X3DGeoHelper.cpp + AssetLib/X3D/X3DGeoHelper.h + AssetLib/X3D/X3DXmlHelper.cpp + AssetLib/X3D/X3DXmlHelper.h +) + +ADD_ASSIMP_IMPORTER( GLTF + AssetLib/glTF/glTFCommon.h + AssetLib/glTF/glTFCommon.cpp + AssetLib/glTF/glTFAsset.h + AssetLib/glTF/glTFAsset.inl + AssetLib/glTF/glTFAssetWriter.h + AssetLib/glTF/glTFAssetWriter.inl + AssetLib/glTF/glTFImporter.cpp + AssetLib/glTF/glTFImporter.h + AssetLib/glTF2/glTF2Asset.h + AssetLib/glTF2/glTF2Asset.inl + AssetLib/glTF2/glTF2AssetWriter.h + AssetLib/glTF2/glTF2AssetWriter.inl + AssetLib/glTF2/glTF2Importer.cpp + AssetLib/glTF2/glTF2Importer.h +) + +ADD_ASSIMP_IMPORTER(3MF + AssetLib/3MF/3MFTypes.h + AssetLib/3MF/XmlSerializer.h + AssetLib/3MF/XmlSerializer.cpp + AssetLib/3MF/D3MFImporter.h + AssetLib/3MF/D3MFImporter.cpp + AssetLib/3MF/D3MFOpcPackage.h + AssetLib/3MF/D3MFOpcPackage.cpp + AssetLib/3MF/3MFXmlTags.h +) + +ADD_ASSIMP_IMPORTER( MMD + AssetLib/MMD/MMDCpp14.h + AssetLib/MMD/MMDImporter.cpp + AssetLib/MMD/MMDImporter.h + AssetLib/MMD/MMDPmdParser.h + AssetLib/MMD/MMDPmxParser.h + AssetLib/MMD/MMDPmxParser.cpp + AssetLib/MMD/MMDVmdParser.h +) + +# Workaround for issue #2406 - force problematic large file to be optimized to prevent string table overflow error +# Used -Os instead of -O2 as previous issues had mentioned, since -Os is roughly speaking -O2, excluding any +# optimizations that take up extra space. Given that the issue is a string table overflowing, -Os seemed appropriate +# Also, I'm not positive if both link & compile flags are needed, but this hopefully ensures that the issue should not +# recur for edge cases such as static builds. +if ((MINGW) AND (CMAKE_BUILD_TYPE MATCHES Debug)) + message("-- Applying MinGW StepFileGen1.cpp Debug Workaround") + SET_SOURCE_FILES_PROPERTIES(Importer/StepFile/StepFileGen1.cpp PROPERTIES COMPILE_FLAGS -Os ) + SET_SOURCE_FILES_PROPERTIES(Importer/StepFile/StepFileGen1.cpp PROPERTIES LINK_FLAGS -Os ) + SET_SOURCE_FILES_PROPERTIES(Importer/StepFile/StepFileGen1.cpp PROPERTIES STATIC_LIBRARY_FLAGS -Os ) +endif() + +if ((NOT ASSIMP_NO_EXPORT) OR (NOT ASSIMP_EXPORTERS_ENABLED STREQUAL "")) + SET( Exporter_SRCS + Common/Exporter.cpp + CApi/AssimpCExport.cpp + ${HEADER_PATH}/BlobIOSystem.h + ) + SOURCE_GROUP( Exporter FILES ${Exporter_SRCS}) +endif() + +SET( Extra_SRCS + MD4FileData.h +) +SOURCE_GROUP( Extra FILES ${Extra_SRCS}) + +# pugixml +IF(ASSIMP_HUNTER_ENABLED) + hunter_add_package(pugixml) + find_package(pugixml CONFIG REQUIRED) +ELSE() + SET( Pugixml_SRCS + ../contrib/pugixml/src/pugiconfig.hpp + ../contrib/pugixml/src/pugixml.hpp + ) + INCLUDE_DIRECTORIES("../contrib/pugixml/src") + SOURCE_GROUP( Contrib\\Pugixml FILES ${Pugixml_SRCS}) +ENDIF() + +# utf8 +IF(ASSIMP_HUNTER_ENABLED) + hunter_add_package(utf8) + find_package(utf8cpp CONFIG REQUIRED) +ELSE() + # utf8 is header-only, so Assimp doesn't need to do anything. +ENDIF() + +# polyclipping +IF(ASSIMP_HUNTER_ENABLED) + hunter_add_package(polyclipping) + find_package(polyclipping CONFIG REQUIRED) +ELSE() + SET( Clipper_SRCS + ../contrib/clipper/clipper.hpp + ../contrib/clipper/clipper.cpp + ) + SOURCE_GROUP( Contrib\\Clipper FILES ${Clipper_SRCS}) +ENDIF() + +# poly2tri +IF(ASSIMP_HUNTER_ENABLED) + hunter_add_package(poly2tri) + find_package(poly2tri CONFIG REQUIRED) +ELSE() + SET( Poly2Tri_SRCS + ../contrib/poly2tri/poly2tri/common/shapes.cc + ../contrib/poly2tri/poly2tri/common/shapes.h + ../contrib/poly2tri/poly2tri/common/utils.h + ../contrib/poly2tri/poly2tri/sweep/advancing_front.h + ../contrib/poly2tri/poly2tri/sweep/advancing_front.cc + ../contrib/poly2tri/poly2tri/sweep/cdt.cc + ../contrib/poly2tri/poly2tri/sweep/cdt.h + ../contrib/poly2tri/poly2tri/sweep/sweep.cc + ../contrib/poly2tri/poly2tri/sweep/sweep.h + ../contrib/poly2tri/poly2tri/sweep/sweep_context.cc + ../contrib/poly2tri/poly2tri/sweep/sweep_context.h + ) + SOURCE_GROUP( Contrib\\Poly2Tri FILES ${Poly2Tri_SRCS}) +ENDIF() + +# minizip/unzip +IF(ASSIMP_HUNTER_ENABLED) + hunter_add_package(minizip) + find_package(minizip CONFIG REQUIRED) +ELSE() + SET( unzip_SRCS + ../contrib/unzip/crypt.c + ../contrib/unzip/crypt.h + ../contrib/unzip/ioapi.c + ../contrib/unzip/ioapi.h + ../contrib/unzip/unzip.c + ../contrib/unzip/unzip.h + ) + SOURCE_GROUP(Contrib\\unzip FILES ${unzip_SRCS}) +ENDIF() + +# zip (https://github.com/kuba--/zip) +separate_arguments(ASSIMP_EXPORTERS_LIST UNIX_COMMAND ${ASSIMP_EXPORTERS_ENABLED}) +IF(3MF IN_LIST ASSIMP_EXPORTERS_LIST) + IF(ASSIMP_HUNTER_ENABLED) + hunter_add_package(zip) + find_package(zip CONFIG REQUIRED) + ELSE() + SET( ziplib_SRCS + ../contrib/zip/src/miniz.h + ../contrib/zip/src/zip.c + ../contrib/zip/src/zip.h + ) + + # TODO if cmake required version has been updated to >3.12.0, collapse this to the second case only + if(${CMAKE_VERSION} VERSION_LESS "3.12.0") + add_definitions(-DMINIZ_USE_UNALIGNED_LOADS_AND_STORES=0) + else() + add_compile_definitions(MINIZ_USE_UNALIGNED_LOADS_AND_STORES=0) + endif() + + SOURCE_GROUP( ziplib FILES ${ziplib_SRCS} ) + ENDIF() +ENDIF() + +# openddlparser +IF(ASSIMP_HUNTER_ENABLED) + hunter_add_package(openddlparser) + find_package(openddlparser CONFIG REQUIRED) +ELSE() + SET ( openddl_parser_SRCS + ../contrib/openddlparser/code/OpenDDLParser.cpp + ../contrib/openddlparser/code/DDLNode.cpp + ../contrib/openddlparser/code/OpenDDLCommon.cpp + ../contrib/openddlparser/code/OpenDDLExport.cpp + ../contrib/openddlparser/code/Value.cpp + ../contrib/openddlparser/code/OpenDDLStream.cpp + ../contrib/openddlparser/include/openddlparser/OpenDDLParser.h + ../contrib/openddlparser/include/openddlparser/OpenDDLParserUtils.h + ../contrib/openddlparser/include/openddlparser/OpenDDLCommon.h + ../contrib/openddlparser/include/openddlparser/OpenDDLExport.h + ../contrib/openddlparser/include/openddlparser/OpenDDLStream.h + ../contrib/openddlparser/include/openddlparser/DDLNode.h + ../contrib/openddlparser/include/openddlparser/Value.h + ) + SOURCE_GROUP( Contrib\\openddl_parser FILES ${openddl_parser_SRCS}) +ENDIF() + +# Open3DGC +IF(ASSIMP_HUNTER_ENABLED) + # Nothing to do, not available in Hunter yet. +ELSE() + SET ( open3dgc_SRCS + ../contrib/Open3DGC/o3dgcAdjacencyInfo.h + ../contrib/Open3DGC/o3dgcArithmeticCodec.cpp + ../contrib/Open3DGC/o3dgcArithmeticCodec.h + ../contrib/Open3DGC/o3dgcBinaryStream.h + ../contrib/Open3DGC/o3dgcCommon.h + ../contrib/Open3DGC/o3dgcDVEncodeParams.h + ../contrib/Open3DGC/o3dgcDynamicVectorDecoder.cpp + ../contrib/Open3DGC/o3dgcDynamicVectorDecoder.h + ../contrib/Open3DGC/o3dgcDynamicVectorEncoder.cpp + ../contrib/Open3DGC/o3dgcDynamicVectorEncoder.h + ../contrib/Open3DGC/o3dgcDynamicVector.h + ../contrib/Open3DGC/o3dgcFIFO.h + ../contrib/Open3DGC/o3dgcIndexedFaceSet.h + ../contrib/Open3DGC/o3dgcIndexedFaceSet.inl + ../contrib/Open3DGC/o3dgcSC3DMCDecoder.h + ../contrib/Open3DGC/o3dgcSC3DMCDecoder.inl + ../contrib/Open3DGC/o3dgcSC3DMCEncodeParams.h + ../contrib/Open3DGC/o3dgcSC3DMCEncoder.h + ../contrib/Open3DGC/o3dgcSC3DMCEncoder.inl + ../contrib/Open3DGC/o3dgcTimer.h + ../contrib/Open3DGC/o3dgcTools.cpp + ../contrib/Open3DGC/o3dgcTriangleFans.cpp + ../contrib/Open3DGC/o3dgcTriangleFans.h + ../contrib/Open3DGC/o3dgcTriangleListDecoder.h + ../contrib/Open3DGC/o3dgcTriangleListDecoder.inl + ../contrib/Open3DGC/o3dgcTriangleListEncoder.h + ../contrib/Open3DGC/o3dgcTriangleListEncoder.inl + ../contrib/Open3DGC/o3dgcVector.h + ../contrib/Open3DGC/o3dgcVector.inl + ) + SOURCE_GROUP( Contrib\\open3dgc FILES ${open3dgc_SRCS}) +ENDIF() + +# Check dependencies for glTF importer with Open3DGC-compression. +# RT-extensions is used in "contrib/Open3DGC/o3dgcTimer.h" for collecting statistics. Pointed file +# has implementation for different platforms: WIN32, __MACH__ and other ("else" block). +FIND_PACKAGE(RT QUIET) +IF (NOT ASSIMP_HUNTER_ENABLED AND (RT_FOUND OR WIN32)) + SET( ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC 1 ) + ADD_DEFINITIONS( -DASSIMP_IMPORTER_GLTF_USE_OPEN3DGC=1 ) +ELSE () + SET (open3dgc_SRCS "") + MESSAGE (INFO " Hunter enabled or RT-extension not found. glTF import/export will be built without Open3DGC-compression.") + #!TODO: off course is better to remove statistics timers from o3dgc codec. Or propose to choose what to use. +ENDIF () + +# RapidJSON +IF(ASSIMP_HUNTER_ENABLED) + hunter_add_package(RapidJSON) + find_package(RapidJSON CONFIG REQUIRED) +ELSE() + INCLUDE_DIRECTORIES("../contrib/rapidjson/include") + ADD_DEFINITIONS( -DRAPIDJSON_HAS_STDSTRING=1) + option( ASSIMP_RAPIDJSON_NO_MEMBER_ITERATOR "Suppress rapidjson warning on MSVC (NOTE: breaks android build)" ON ) + if(ASSIMP_RAPIDJSON_NO_MEMBER_ITERATOR) + ADD_DEFINITIONS( -DRAPIDJSON_NOMEMBERITERATORCLASS ) + endif() +ENDIF() + +# stb +IF(ASSIMP_HUNTER_ENABLED) + hunter_add_package(stb) + find_package(stb CONFIG REQUIRED) +ELSE() + SET( stb_SRCS + ../contrib/stb/stb_image.h + ) + INCLUDE_DIRECTORIES("../contrib") + SOURCE_GROUP( Contrib\\stb FILES ${stb_SRCS}) +ENDIF() + +# VC2010 fixes +if(MSVC10) + option( VC10_STDINT_FIX "Fix for VC10 Compiler regarding pstdint.h redefinition errors" OFF ) + if( VC10_STDINT_FIX ) + ADD_DEFINITIONS( -D_STDINT ) + endif() +endif() + +ADD_DEFINITIONS( -DASSIMP_BUILD_DLL_EXPORT ) + +IF( MSVC OR "${CMAKE_CXX_SIMULATE_ID}" MATCHES "MSVC") # clang with MSVC ABI + ADD_DEFINITIONS( -D_SCL_SECURE_NO_WARNINGS ) + ADD_DEFINITIONS( -D_CRT_SECURE_NO_WARNINGS ) +endif () + +IF(NOT ASSIMP_HUNTER_ENABLED) + if (UNZIP_FOUND) + SET (unzip_compile_SRCS "") + else () + SET (unzip_compile_SRCS ${unzip_SRCS}) + INCLUDE_DIRECTORIES( "../contrib/unzip/" ) + endif () +ENDIF() + +MESSAGE(STATUS "Enabled importer formats:${ASSIMP_IMPORTERS_ENABLED}") +MESSAGE(STATUS "Disabled importer formats:${ASSIMP_IMPORTERS_DISABLED}") + +if (NOT ASSIMP_NO_EXPORT) + MESSAGE(STATUS "Enabled exporter formats:${ASSIMP_EXPORTERS_ENABLED}") + MESSAGE(STATUS "Disabled exporter formats:${ASSIMP_EXPORTERS_DISABLED}") +endif() + +SOURCE_GROUP( include\\assimp FILES ${PUBLIC_HEADERS} ) + +SET( assimp_src + # Assimp Files + ${Core_SRCS} + ${CApi_SRCS} + ${Common_SRCS} + ${Logging_SRCS} + ${Exporter_SRCS} + ${PostProcessing_SRCS} + ${MaterialSystem_SRCS} + ${STEPParser_SRCS} +# ${Step_SRCS} check if we need a different approach + + # Model Support + ${ASSIMP_LOADER_SRCS} + ${ASSIMP_EXPORTER_SRCS} + + # Third-party libraries + ${unzip_compile_SRCS} + ${Poly2Tri_SRCS} + ${Clipper_SRCS} + ${openddl_parser_SRCS} + ${open3dgc_SRCS} + ${ziplib_SRCS} + ${Pugixml_SRCS} + ${stb_SRCS} + # Necessary to show the headers in the project when using the VC++ generator: + + ${PUBLIC_HEADERS} + ${COMPILER_HEADERS} +) +ADD_DEFINITIONS( -DOPENDDLPARSER_BUILD ) + +IF(NOT ASSIMP_HUNTER_ENABLED) + INCLUDE_DIRECTORIES( + ${IRRXML_INCLUDE_DIR} + ../contrib/openddlparser/include + ) +ENDIF() + +IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) + SET( assimp_src ${assimp_src} ${C4D_SRCS}) + INCLUDE_DIRECTORIES(${C4D_INCLUDES}) +ENDIF () + +IF (ASSIMP_BUILD_DRACO) + INCLUDE_DIRECTORIES(${draco_INCLUDE_DIRS}) + ADD_DEFINITIONS( -DASSIMP_ENABLE_DRACO ) +ENDIF() + +ADD_LIBRARY( assimp ${assimp_src} ) +ADD_LIBRARY(assimp::assimp ALIAS assimp) + +TARGET_USE_COMMON_OUTPUT_DIRECTORY(assimp) + +# enable warnings as errors ######################################## +IF (MSVC) + TARGET_COMPILE_OPTIONS(assimp PRIVATE /WX) +ELSE() + TARGET_COMPILE_OPTIONS(assimp PRIVATE -Werror) +ENDIF() + +# adds C_FLAGS required to compile zip.c on old GCC 4.x compiler +TARGET_COMPILE_FEATURES(assimp PRIVATE c_std_99) + +TARGET_INCLUDE_DIRECTORIES ( assimp PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include> + $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/../include> + $<INSTALL_INTERFACE:${ASSIMP_INCLUDE_INSTALL_DIR}> +) + +IF(ASSIMP_HUNTER_ENABLED) + TARGET_LINK_LIBRARIES(assimp + PUBLIC + polyclipping::polyclipping + openddlparser::openddl_parser + poly2tri::poly2tri + minizip::minizip + ZLIB::zlib + RapidJSON::rapidjson + utf8cpp + pugixml + stb::stb + ) + if(TARGET zip::zip) + target_link_libraries(assimp PUBLIC zip::zip) + endif() + + if (ASSIMP_BUILD_DRACO) + target_link_libraries(assimp PUBLIC ${draco_LIBRARIES}) + endif() +ELSE() + TARGET_LINK_LIBRARIES(assimp ${ZLIB_LIBRARIES} ${OPENDDL_PARSER_LIBRARIES}) + if (ASSIMP_BUILD_DRACO) + target_link_libraries(assimp ${draco_LIBRARIES}) + endif() +ENDIF() + +if(ASSIMP_ANDROID_JNIIOSYSTEM) + set(ASSIMP_ANDROID_JNIIOSYSTEM_PATH port/AndroidJNI) + add_subdirectory(../${ASSIMP_ANDROID_JNIIOSYSTEM_PATH}/ ../${ASSIMP_ANDROID_JNIIOSYSTEM_PATH}/) + target_link_libraries(assimp android_jniiosystem) +endif() + +IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) + TARGET_LINK_LIBRARIES(assimp optimized ${C4D_RELEASE_LIBRARIES}) + TARGET_LINK_LIBRARIES(assimp debug ${C4D_DEBUG_LIBRARIES}) + TARGET_LINK_LIBRARIES(assimp ${C4D_EXTRA_LIBRARIES}) +ENDIF () + +if( MSVC ) + # in order to prevent DLL hell, each of the DLLs have to be suffixed with the major version and msvc prefix + # CMake 3.12 added a variable for this + if(MSVC_TOOLSET_VERSION) + set(MSVC_PREFIX "vc${MSVC_TOOLSET_VERSION}") + else() + if( MSVC70 OR MSVC71 ) + set(MSVC_PREFIX "vc70") + elseif( MSVC80 ) + set(MSVC_PREFIX "vc80") + elseif( MSVC90 ) + set(MSVC_PREFIX "vc90") + elseif( MSVC10 ) + set(MSVC_PREFIX "vc100") + elseif( MSVC11 ) + set(MSVC_PREFIX "vc110") + elseif( MSVC12 ) + set(MSVC_PREFIX "vc120") + elseif( MSVC_VERSION LESS 1910) + set(MSVC_PREFIX "vc140") + elseif( MSVC_VERSION LESS 1920) + set(MSVC_PREFIX "vc141") + elseif( MSVC_VERSION LESS 1930) + set(MSVC_PREFIX "vc142") + else() + MESSAGE(WARNING "unknown msvc version ${MSVC_VERSION}") + set(MSVC_PREFIX "vc150") + endif() + endif() + set(LIBRARY_SUFFIX "${ASSIMP_LIBRARY_SUFFIX}-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library") +endif() + +if (${CMAKE_SYSTEM_NAME} MATCHES "WindowsStore") + target_compile_definitions(assimp PUBLIC WindowsStore) + TARGET_LINK_LIBRARIES(assimp advapi32) +endif() + +SET_TARGET_PROPERTIES( assimp PROPERTIES + VERSION ${ASSIMP_VERSION} + SOVERSION ${ASSIMP_SOVERSION} # use full version + OUTPUT_NAME assimp${LIBRARY_SUFFIX} +) + +if (WIN32 AND CMAKE_COMPILER_IS_GNUCXX AND BUILD_SHARED_LIBS) + set_target_properties(assimp PROPERTIES + SUFFIX "-${ASSIMP_SOVERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" + ) +endif() + +if (APPLE) + if (ASSIMP_BUILD_FRAMEWORK) + SET_TARGET_PROPERTIES( assimp PROPERTIES + FRAMEWORK TRUE + FRAMEWORK_VERSION C + MACOSX_FRAMEWORK_IDENTIFIER net.sf.assimp + PUBLIC_HEADER "${PUBLIC_HEADERS}" + ) + + # PUBLIC_HEADER option does not support directory structure creation + # add ./Compiler/*.h to assimp.framework via copy command + ADD_CUSTOM_COMMAND(TARGET assimp POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${HEADER_PATH}/Compiler" + assimp.framework/Headers/Compiler + COMMENT "Copying public ./Compiler/ header files to framework bundle's Headers/Compiler/") + ENDIF() +ENDIF() + +# Build against external unzip, or add ../contrib/unzip so +# assimp can #include "unzip.h" +IF(NOT ASSIMP_HUNTER_ENABLED) + if (UNZIP_FOUND) + INCLUDE_DIRECTORIES(${UNZIP_INCLUDE_DIRS}) + TARGET_LINK_LIBRARIES(assimp ${UNZIP_LIBRARIES}) + else () + INCLUDE_DIRECTORIES("../") + endif () +ENDIF() + +# Add RT-extension library for glTF importer with Open3DGC-compression. +IF (RT_FOUND AND ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC) + TARGET_LINK_LIBRARIES(assimp ${RT_LIBRARY}) +ENDIF () + + +INSTALL( TARGETS assimp + EXPORT "${TARGETS_EXPORT_NAME}" + LIBRARY DESTINATION ${ASSIMP_LIB_INSTALL_DIR} COMPONENT ${LIBASSIMP_COMPONENT} + ARCHIVE DESTINATION ${ASSIMP_LIB_INSTALL_DIR} COMPONENT ${LIBASSIMP-DEV_COMPONENT} + RUNTIME DESTINATION ${ASSIMP_BIN_INSTALL_DIR} COMPONENT ${LIBASSIMP_COMPONENT} + FRAMEWORK DESTINATION ${ASSIMP_LIB_INSTALL_DIR} COMPONENT ${LIBASSIMP_COMPONENT} + INCLUDES DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR} +) +INSTALL( FILES ${PUBLIC_HEADERS} DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR}/assimp COMPONENT assimp-dev) +INSTALL( FILES ${COMPILER_HEADERS} DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR}/assimp/Compiler COMPONENT assimp-dev) + +if (ASSIMP_ANDROID_JNIIOSYSTEM) + INSTALL(FILES ${HEADER_PATH}/${ASSIMP_ANDROID_JNIIOSYSTEM_PATH}/AndroidJNIIOSystem.h + DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR} + COMPONENT assimp-dev) +ENDIF() + +if(MSVC AND ASSIMP_INSTALL_PDB) + # When only the static library is built, these properties must + # be set to ensure the static lib .pdb is staged for installation. + IF(NOT BUILD_SHARED_LIBS) + SET_TARGET_PROPERTIES( assimp PROPERTIES + COMPILE_PDB_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMPILE_PDB_NAME assimp${LIBRARY_SUFFIX} + COMPILE_PDB_NAME_DEBUG assimp${LIBRARY_SUFFIX}${CMAKE_DEBUG_POSTFIX} + ) + ENDIF() + + IF(CMAKE_GENERATOR MATCHES "^Visual Studio") + install(FILES ${Assimp_BINARY_DIR}/code/Debug/assimp${LIBRARY_SUFFIX}${CMAKE_DEBUG_POSTFIX}.pdb + DESTINATION ${ASSIMP_LIB_INSTALL_DIR} + CONFIGURATIONS Debug + ) + install(FILES ${Assimp_BINARY_DIR}/code/RelWithDebInfo/assimp${LIBRARY_SUFFIX}.pdb + DESTINATION ${ASSIMP_LIB_INSTALL_DIR} + CONFIGURATIONS RelWithDebInfo + ) + ELSE() + install(FILES ${Assimp_BINARY_DIR}/code/assimp${LIBRARY_SUFFIX}${CMAKE_DEBUG_POSTFIX}.pdb + DESTINATION ${ASSIMP_LIB_INSTALL_DIR} + CONFIGURATIONS Debug + ) + install(FILES ${Assimp_BINARY_DIR}/code/assimp${LIBRARY_SUFFIX}.pdb + DESTINATION ${ASSIMP_LIB_INSTALL_DIR} + CONFIGURATIONS RelWithDebInfo + ) + ENDIF() +ENDIF () + +if (ASSIMP_COVERALLS) + include(Coveralls) + + set(COVERAGE_SRCS ${assimp_src} ${TEST_SRCS} ) + + # Create the coveralls target. + coveralls_setup( + "${COVERAGE_SRCS}" # The source files. + ON # If we should upload. + "${PROJECT_SOURCE_DIR}/cmake-modules/") # (Optional) Alternate project cmake module path. +ENDIF() diff --git a/libs/assimp/code/Common/AssertHandler.cpp b/libs/assimp/code/Common/AssertHandler.cpp new file mode 100644 index 0000000..d0bd6cc --- /dev/null +++ b/libs/assimp/code/Common/AssertHandler.cpp @@ -0,0 +1,72 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 AssertHandler.cpp + * @brief Implementation of assert handling logic. + */ + +#include "AssertHandler.h" + +#include <iostream> +#include <cstdlib> + +void Assimp::defaultAiAssertHandler(const char* failedExpression, const char* file, int line) +{ + std::cerr << "ai_assert failure in " << file << "(" << line << "): " << failedExpression << std::endl; + std::abort(); +} + +namespace +{ + Assimp::AiAssertHandler s_handler = Assimp::defaultAiAssertHandler; +} + +void Assimp::setAiAssertHandler(AiAssertHandler handler) +{ + s_handler = handler; +} + +void Assimp::aiAssertViolation(const char* failedExpression, const char* file, int line) +{ + s_handler(failedExpression, file, line); +} diff --git a/libs/assimp/code/Common/AssertHandler.h b/libs/assimp/code/Common/AssertHandler.h new file mode 100644 index 0000000..2515f0b --- /dev/null +++ b/libs/assimp/code/Common/AssertHandler.h @@ -0,0 +1,75 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, 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 Provides facilities to replace the default assert handler. */ + +#ifndef INCLUDED_AI_ASSERTHANDLER_H +#define INCLUDED_AI_ASSERTHANDLER_H + +#include <assimp/ai_assert.h> +#include <assimp/defs.h> + +namespace Assimp +{ + // --------------------------------------------------------------------------- + /** Signature of functions which handle assert violations. + */ + using AiAssertHandler = void (*)(const char* failedExpression, const char* file, int line); + + // --------------------------------------------------------------------------- + /** Set the assert handler. + */ + ASSIMP_API void setAiAssertHandler(AiAssertHandler handler); + + // --------------------------------------------------------------------------- + /** The assert handler which is set by default. + * + * This issues a message to stderr and calls abort. + */ + ASSIMP_API void defaultAiAssertHandler(const char* failedExpression, const char* file, int line); + + // --------------------------------------------------------------------------- + /** Dispatches an assert violation to the assert handler. + */ + ASSIMP_API void aiAssertViolation(const char* failedExpression, const char* file, int line); +} // end of namespace Assimp + +#endif // INCLUDED_AI_ASSERTHANDLER_H
\ No newline at end of file diff --git a/libs/assimp/code/Common/Assimp.cpp b/libs/assimp/code/Common/Assimp.cpp new file mode 100644 index 0000000..71e312c --- /dev/null +++ b/libs/assimp/code/Common/Assimp.cpp @@ -0,0 +1,1299 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Assimp.cpp + * @brief Implementation of the Plain-C API + */ + +#include <assimp/BaseImporter.h> +#include <assimp/Exceptional.h> +#include <assimp/GenericProperty.h> +#include <assimp/cimport.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Importer.hpp> +#include <assimp/LogStream.hpp> + +#include "CApi/CInterfaceIOWrapper.h" +#include "Importer.h" +#include "ScenePrivate.h" + +#include <list> + +// ------------------------------------------------------------------------------------------------ +#ifndef ASSIMP_BUILD_SINGLETHREADED +#include <mutex> +#include <thread> +#endif +// ------------------------------------------------------------------------------------------------ +using namespace Assimp; + +namespace Assimp { +// underlying structure for aiPropertyStore +typedef BatchLoader::PropertyMap PropertyMap; + +#if defined(__has_warning) +#if __has_warning("-Wordered-compare-function-pointers") +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wordered-compare-function-pointers" +#endif +#endif + +/** Stores the LogStream objects for all active C log streams */ +struct mpred { + bool operator()(const aiLogStream &s0, const aiLogStream &s1) const { + return s0.callback < s1.callback && s0.user < s1.user; + } +}; + +#if defined(__has_warning) +#if __has_warning("-Wordered-compare-function-pointers") +#pragma GCC diagnostic pop +#endif +#endif +typedef std::map<aiLogStream, Assimp::LogStream *, mpred> LogStreamMap; + +/** Stores the LogStream objects allocated by #aiGetPredefinedLogStream */ +typedef std::list<Assimp::LogStream *> PredefLogStreamMap; + +/** Local storage of all active log streams */ +static LogStreamMap gActiveLogStreams; + +/** Local storage of LogStreams allocated by #aiGetPredefinedLogStream */ +static PredefLogStreamMap gPredefinedStreams; + +/** Error message of the last failed import process */ +static std::string gLastErrorString; + +/** Verbose logging active or not? */ +static aiBool gVerboseLogging = false; + +/** will return all registered importers. */ +void GetImporterInstanceList(std::vector<BaseImporter *> &out); + +/** will delete all registered importers. */ +void DeleteImporterInstanceList(std::vector<BaseImporter *> &out); +} // namespace Assimp + +#ifndef ASSIMP_BUILD_SINGLETHREADED +/** Global mutex to manage the access to the log-stream map */ +static std::mutex gLogStreamMutex; +#endif + +// ------------------------------------------------------------------------------------------------ +// Custom LogStream implementation for the C-API +class LogToCallbackRedirector : public LogStream { +public: + explicit LogToCallbackRedirector(const aiLogStream &s) : + stream(s) { + ai_assert(nullptr != s.callback); + } + + ~LogToCallbackRedirector() { +#ifndef ASSIMP_BUILD_SINGLETHREADED + std::lock_guard<std::mutex> lock(gLogStreamMutex); +#endif + // (HACK) Check whether the 'stream.user' pointer points to a + // custom LogStream allocated by #aiGetPredefinedLogStream. + // In this case, we need to delete it, too. Of course, this + // might cause strange problems, but the chance is quite low. + + PredefLogStreamMap::iterator it = std::find(gPredefinedStreams.begin(), + gPredefinedStreams.end(), (Assimp::LogStream *)stream.user); + + if (it != gPredefinedStreams.end()) { + delete *it; + gPredefinedStreams.erase(it); + } + } + + /** @copydoc LogStream::write */ + void write(const char *message) { + stream.callback(message, stream.user); + } + +private: + aiLogStream stream; +}; + +// ------------------------------------------------------------------------------------------------ +void ReportSceneNotFoundError() { + ASSIMP_LOG_ERROR("Unable to find the Assimp::Importer for this aiScene. " + "The C-API does not accept scenes produced by the C++ API and vice versa"); + + ai_assert(false); +} + +// ------------------------------------------------------------------------------------------------ +// Reads the given file and returns its content. +const aiScene *aiImportFile(const char *pFile, unsigned int pFlags) { + return aiImportFileEx(pFile, pFlags, nullptr); +} + +// ------------------------------------------------------------------------------------------------ +const aiScene *aiImportFileEx(const char *pFile, unsigned int pFlags, aiFileIO *pFS) { + return aiImportFileExWithProperties(pFile, pFlags, pFS, nullptr); +} + +// ------------------------------------------------------------------------------------------------ +const aiScene *aiImportFileExWithProperties(const char *pFile, unsigned int pFlags, + aiFileIO *pFS, const aiPropertyStore *props) { + ai_assert(nullptr != pFile); + + const aiScene *scene = nullptr; + ASSIMP_BEGIN_EXCEPTION_REGION(); + + // create an Importer for this file + Assimp::Importer *imp = new Assimp::Importer(); + + // copy properties + if (props) { + const PropertyMap *pp = reinterpret_cast<const PropertyMap *>(props); + ImporterPimpl *pimpl = imp->Pimpl(); + pimpl->mIntProperties = pp->ints; + pimpl->mFloatProperties = pp->floats; + pimpl->mStringProperties = pp->strings; + pimpl->mMatrixProperties = pp->matrices; + } + // setup a custom IO system if necessary + if (pFS) { + imp->SetIOHandler(new CIOSystemWrapper(pFS)); + } + + // and have it read the file + scene = imp->ReadFile(pFile, pFlags); + + // if succeeded, store the importer in the scene and keep it alive + if (scene) { + ScenePrivateData *priv = const_cast<ScenePrivateData *>(ScenePriv(scene)); + priv->mOrigImporter = imp; + } else { + // if failed, extract error code and destroy the import + gLastErrorString = imp->GetErrorString(); + delete imp; + } + + // return imported data. If the import failed the pointer is nullptr anyways + ASSIMP_END_EXCEPTION_REGION(const aiScene *); + + return scene; +} + +// ------------------------------------------------------------------------------------------------ +const aiScene *aiImportFileFromMemory( + const char *pBuffer, + unsigned int pLength, + unsigned int pFlags, + const char *pHint) { + return aiImportFileFromMemoryWithProperties(pBuffer, pLength, pFlags, pHint, nullptr); +} + +// ------------------------------------------------------------------------------------------------ +const aiScene *aiImportFileFromMemoryWithProperties( + const char *pBuffer, + unsigned int pLength, + unsigned int pFlags, + const char *pHint, + const aiPropertyStore *props) { + ai_assert(nullptr != pBuffer); + ai_assert(0 != pLength); + + const aiScene *scene = nullptr; + ASSIMP_BEGIN_EXCEPTION_REGION(); + + // create an Importer for this file + Assimp::Importer *imp = new Assimp::Importer(); + + // copy properties + if (props) { + const PropertyMap *pp = reinterpret_cast<const PropertyMap *>(props); + ImporterPimpl *pimpl = imp->Pimpl(); + pimpl->mIntProperties = pp->ints; + pimpl->mFloatProperties = pp->floats; + pimpl->mStringProperties = pp->strings; + pimpl->mMatrixProperties = pp->matrices; + } + + // and have it read the file from the memory buffer + scene = imp->ReadFileFromMemory(pBuffer, pLength, pFlags, pHint); + + // if succeeded, store the importer in the scene and keep it alive + if (scene) { + ScenePrivateData *priv = const_cast<ScenePrivateData *>(ScenePriv(scene)); + priv->mOrigImporter = imp; + } else { + // if failed, extract error code and destroy the import + gLastErrorString = imp->GetErrorString(); + delete imp; + } + // return imported data. If the import failed the pointer is nullptr anyways + ASSIMP_END_EXCEPTION_REGION(const aiScene *); + return scene; +} + +// ------------------------------------------------------------------------------------------------ +// Releases all resources associated with the given import process. +void aiReleaseImport(const aiScene *pScene) { + if (!pScene) { + return; + } + + ASSIMP_BEGIN_EXCEPTION_REGION(); + + // find the importer associated with this data + const ScenePrivateData *priv = ScenePriv(pScene); + if (!priv || !priv->mOrigImporter) { + delete pScene; + } else { + // deleting the Importer also deletes the scene + // Note: the reason that this is not written as 'delete priv->mOrigImporter' + // is a suspected bug in gcc 4.4+ (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52339) + Importer *importer = priv->mOrigImporter; + delete importer; + } + + ASSIMP_END_EXCEPTION_REGION(void); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API const aiScene *aiApplyPostProcessing(const aiScene *pScene, + unsigned int pFlags) { + const aiScene *sc = nullptr; + + ASSIMP_BEGIN_EXCEPTION_REGION(); + + // find the importer associated with this data + const ScenePrivateData *priv = ScenePriv(pScene); + if (!priv || !priv->mOrigImporter) { + ReportSceneNotFoundError(); + return nullptr; + } + + sc = priv->mOrigImporter->ApplyPostProcessing(pFlags); + + if (!sc) { + aiReleaseImport(pScene); + return nullptr; + } + + ASSIMP_END_EXCEPTION_REGION(const aiScene *); + return sc; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API const aiScene *aiApplyCustomizedPostProcessing(const aiScene *scene, + BaseProcess *process, + bool requestValidation) { + const aiScene *sc(nullptr); + + ASSIMP_BEGIN_EXCEPTION_REGION(); + + // find the importer associated with this data + const ScenePrivateData *priv = ScenePriv(scene); + if (nullptr == priv || nullptr == priv->mOrigImporter) { + ReportSceneNotFoundError(); + return nullptr; + } + + sc = priv->mOrigImporter->ApplyCustomizedPostProcessing(process, requestValidation); + + if (!sc) { + aiReleaseImport(scene); + return nullptr; + } + + ASSIMP_END_EXCEPTION_REGION(const aiScene *); + + return sc; +} + +// ------------------------------------------------------------------------------------------------ +void CallbackToLogRedirector(const char *msg, char *dt) { + ai_assert(nullptr != msg); + ai_assert(nullptr != dt); + LogStream *s = (LogStream *)dt; + + s->write(msg); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API aiLogStream aiGetPredefinedLogStream(aiDefaultLogStream pStream, const char *file) { + aiLogStream sout; + + ASSIMP_BEGIN_EXCEPTION_REGION(); + LogStream *stream = LogStream::createDefaultStream(pStream, file); + if (!stream) { + sout.callback = nullptr; + sout.user = nullptr; + } else { + sout.callback = &CallbackToLogRedirector; + sout.user = (char *)stream; + } + gPredefinedStreams.push_back(stream); + ASSIMP_END_EXCEPTION_REGION(aiLogStream); + return sout; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiAttachLogStream(const aiLogStream *stream) { + ASSIMP_BEGIN_EXCEPTION_REGION(); + +#ifndef ASSIMP_BUILD_SINGLETHREADED + std::lock_guard<std::mutex> lock(gLogStreamMutex); +#endif + + LogStream *lg = new LogToCallbackRedirector(*stream); + gActiveLogStreams[*stream] = lg; + + if (DefaultLogger::isNullLogger()) { + DefaultLogger::create(nullptr, (gVerboseLogging == AI_TRUE ? Logger::VERBOSE : Logger::NORMAL)); + } + DefaultLogger::get()->attachStream(lg); + ASSIMP_END_EXCEPTION_REGION(void); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API aiReturn aiDetachLogStream(const aiLogStream *stream) { + ASSIMP_BEGIN_EXCEPTION_REGION(); + +#ifndef ASSIMP_BUILD_SINGLETHREADED + std::lock_guard<std::mutex> lock(gLogStreamMutex); +#endif + // find the log-stream associated with this data + LogStreamMap::iterator it = gActiveLogStreams.find(*stream); + // it should be there... else the user is playing fools with us + if (it == gActiveLogStreams.end()) { + return AI_FAILURE; + } + DefaultLogger::get()->detachStream(it->second); + delete it->second; + + gActiveLogStreams.erase(it); + + if (gActiveLogStreams.empty()) { + DefaultLogger::kill(); + } + ASSIMP_END_EXCEPTION_REGION(aiReturn); + return AI_SUCCESS; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiDetachAllLogStreams(void) { + ASSIMP_BEGIN_EXCEPTION_REGION(); +#ifndef ASSIMP_BUILD_SINGLETHREADED + std::lock_guard<std::mutex> lock(gLogStreamMutex); +#endif + Logger *logger(DefaultLogger::get()); + if (nullptr == logger) { + return; + } + + for (LogStreamMap::iterator it = gActiveLogStreams.begin(); it != gActiveLogStreams.end(); ++it) { + logger->detachStream(it->second); + delete it->second; + } + gActiveLogStreams.clear(); + DefaultLogger::kill(); + + ASSIMP_END_EXCEPTION_REGION(void); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiEnableVerboseLogging(aiBool d) { + if (!DefaultLogger::isNullLogger()) { + DefaultLogger::get()->setLogSeverity((d == AI_TRUE ? Logger::VERBOSE : Logger::NORMAL)); + } + gVerboseLogging = d; +} + +// ------------------------------------------------------------------------------------------------ +// Returns the error text of the last failed import process. +const char *aiGetErrorString() { + return gLastErrorString.c_str(); +} + +// ----------------------------------------------------------------------------------------------- +// Return the description of a importer given its index +const aiImporterDesc *aiGetImportFormatDescription(size_t pIndex) { + return Importer().GetImporterInfo(pIndex); +} + +// ----------------------------------------------------------------------------------------------- +// Return the number of importers +size_t aiGetImportFormatCount(void) { + return Importer().GetImporterCount(); +} + +// ------------------------------------------------------------------------------------------------ +// Returns the error text of the last failed import process. +aiBool aiIsExtensionSupported(const char *szExtension) { + ai_assert(nullptr != szExtension); + aiBool candoit = AI_FALSE; + ASSIMP_BEGIN_EXCEPTION_REGION(); + + // FIXME: no need to create a temporary Importer instance just for that .. + Assimp::Importer tmp; + candoit = tmp.IsExtensionSupported(std::string(szExtension)) ? AI_TRUE : AI_FALSE; + + ASSIMP_END_EXCEPTION_REGION(aiBool); + return candoit; +} + +// ------------------------------------------------------------------------------------------------ +// Get a list of all file extensions supported by ASSIMP +void aiGetExtensionList(aiString *szOut) { + ai_assert(nullptr != szOut); + ASSIMP_BEGIN_EXCEPTION_REGION(); + + // FIXME: no need to create a temporary Importer instance just for that .. + Assimp::Importer tmp; + tmp.GetExtensionList(*szOut); + + ASSIMP_END_EXCEPTION_REGION(void); +} + +// ------------------------------------------------------------------------------------------------ +// Get the memory requirements for a particular import. +void aiGetMemoryRequirements(const C_STRUCT aiScene *pIn, + C_STRUCT aiMemoryInfo *in) { + ASSIMP_BEGIN_EXCEPTION_REGION(); + + // find the importer associated with this data + const ScenePrivateData *priv = ScenePriv(pIn); + if (!priv || !priv->mOrigImporter) { + ReportSceneNotFoundError(); + return; + } + + return priv->mOrigImporter->GetMemoryRequirements(*in); + ASSIMP_END_EXCEPTION_REGION(void); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API aiPropertyStore *aiCreatePropertyStore(void) { + return reinterpret_cast<aiPropertyStore *>(new PropertyMap()); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiReleasePropertyStore(aiPropertyStore *p) { + delete reinterpret_cast<PropertyMap *>(p); +} + +// ------------------------------------------------------------------------------------------------ +// Importer::SetPropertyInteger +ASSIMP_API void aiSetImportPropertyInteger(aiPropertyStore *p, const char *szName, int value) { + ASSIMP_BEGIN_EXCEPTION_REGION(); + PropertyMap *pp = reinterpret_cast<PropertyMap *>(p); + SetGenericProperty<int>(pp->ints, szName, value); + ASSIMP_END_EXCEPTION_REGION(void); +} + +// ------------------------------------------------------------------------------------------------ +// Importer::SetPropertyFloat +ASSIMP_API void aiSetImportPropertyFloat(aiPropertyStore *p, const char *szName, ai_real value) { + ASSIMP_BEGIN_EXCEPTION_REGION(); + PropertyMap *pp = reinterpret_cast<PropertyMap *>(p); + SetGenericProperty<ai_real>(pp->floats, szName, value); + ASSIMP_END_EXCEPTION_REGION(void); +} + +// ------------------------------------------------------------------------------------------------ +// Importer::SetPropertyString +ASSIMP_API void aiSetImportPropertyString(aiPropertyStore *p, const char *szName, + const C_STRUCT aiString *st) { + if (!st) { + return; + } + ASSIMP_BEGIN_EXCEPTION_REGION(); + PropertyMap *pp = reinterpret_cast<PropertyMap *>(p); + SetGenericProperty<std::string>(pp->strings, szName, std::string(st->C_Str())); + ASSIMP_END_EXCEPTION_REGION(void); +} + +// ------------------------------------------------------------------------------------------------ +// Importer::SetPropertyMatrix +ASSIMP_API void aiSetImportPropertyMatrix(aiPropertyStore *p, const char *szName, + const C_STRUCT aiMatrix4x4 *mat) { + if (!mat) { + return; + } + ASSIMP_BEGIN_EXCEPTION_REGION(); + PropertyMap *pp = reinterpret_cast<PropertyMap *>(p); + SetGenericProperty<aiMatrix4x4>(pp->matrices, szName, *mat); + ASSIMP_END_EXCEPTION_REGION(void); +} + +// ------------------------------------------------------------------------------------------------ +// Rotation matrix to quaternion +ASSIMP_API void aiCreateQuaternionFromMatrix(aiQuaternion *quat, const aiMatrix3x3 *mat) { + ai_assert(nullptr != quat); + ai_assert(nullptr != mat); + *quat = aiQuaternion(*mat); +} + +// ------------------------------------------------------------------------------------------------ +// Matrix decomposition +ASSIMP_API void aiDecomposeMatrix(const aiMatrix4x4 *mat, aiVector3D *scaling, + aiQuaternion *rotation, + aiVector3D *position) { + ai_assert(nullptr != rotation); + ai_assert(nullptr != position); + ai_assert(nullptr != scaling); + ai_assert(nullptr != mat); + mat->Decompose(*scaling, *rotation, *position); +} + +// ------------------------------------------------------------------------------------------------ +// Matrix transpose +ASSIMP_API void aiTransposeMatrix3(aiMatrix3x3 *mat) { + ai_assert(nullptr != mat); + mat->Transpose(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiTransposeMatrix4(aiMatrix4x4 *mat) { + ai_assert(nullptr != mat); + mat->Transpose(); +} + +// ------------------------------------------------------------------------------------------------ +// Vector transformation +ASSIMP_API void aiTransformVecByMatrix3(aiVector3D *vec, + const aiMatrix3x3 *mat) { + ai_assert(nullptr != mat); + ai_assert(nullptr != vec); + *vec *= (*mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiTransformVecByMatrix4(aiVector3D *vec, + const aiMatrix4x4 *mat) { + ai_assert(nullptr != mat); + ai_assert(nullptr != vec); + + *vec *= (*mat); +} + +// ------------------------------------------------------------------------------------------------ +// Matrix multiplication +ASSIMP_API void aiMultiplyMatrix4( + aiMatrix4x4 *dst, + const aiMatrix4x4 *src) { + ai_assert(nullptr != dst); + ai_assert(nullptr != src); + *dst = (*dst) * (*src); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMultiplyMatrix3( + aiMatrix3x3 *dst, + const aiMatrix3x3 *src) { + ai_assert(nullptr != dst); + ai_assert(nullptr != src); + *dst = (*dst) * (*src); +} + +// ------------------------------------------------------------------------------------------------ +// Matrix identity +ASSIMP_API void aiIdentityMatrix3( + aiMatrix3x3 *mat) { + ai_assert(nullptr != mat); + *mat = aiMatrix3x3(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiIdentityMatrix4( + aiMatrix4x4 *mat) { + ai_assert(nullptr != mat); + *mat = aiMatrix4x4(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API C_STRUCT const aiImporterDesc *aiGetImporterDesc(const char *extension) { + if (nullptr == extension) { + return nullptr; + } + const aiImporterDesc *desc(nullptr); + std::vector<BaseImporter *> out; + GetImporterInstanceList(out); + for (size_t i = 0; i < out.size(); ++i) { + if (0 == strncmp(out[i]->GetInfo()->mFileExtensions, extension, strlen(extension))) { + desc = out[i]->GetInfo(); + break; + } + } + + DeleteImporterInstanceList(out); + + return desc; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API int aiVector2AreEqual( + const C_STRUCT aiVector2D *a, + const C_STRUCT aiVector2D *b) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + return *a == *b; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API int aiVector2AreEqualEpsilon( + const C_STRUCT aiVector2D *a, + const C_STRUCT aiVector2D *b, + const float epsilon) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + return a->Equal(*b, epsilon); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector2Add( + C_STRUCT aiVector2D *dst, + const C_STRUCT aiVector2D *src) { + ai_assert(nullptr != dst); + ai_assert(nullptr != src); + *dst = *dst + *src; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector2Subtract( + C_STRUCT aiVector2D *dst, + const C_STRUCT aiVector2D *src) { + ai_assert(nullptr != dst); + ai_assert(nullptr != src); + *dst = *dst - *src; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector2Scale( + C_STRUCT aiVector2D *dst, + const float s) { + ai_assert(nullptr != dst); + *dst *= s; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector2SymMul( + C_STRUCT aiVector2D *dst, + const C_STRUCT aiVector2D *other) { + ai_assert(nullptr != dst); + ai_assert(nullptr != other); + *dst = dst->SymMul(*other); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector2DivideByScalar( + C_STRUCT aiVector2D *dst, + const float s) { + ai_assert(nullptr != dst); + *dst /= s; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector2DivideByVector( + C_STRUCT aiVector2D *dst, + C_STRUCT aiVector2D *v) { + ai_assert(nullptr != dst); + ai_assert(nullptr != v); + *dst = *dst / *v; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API float aiVector2Length( + const C_STRUCT aiVector2D *v) { + ai_assert(nullptr != v); + return v->Length(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API float aiVector2SquareLength( + const C_STRUCT aiVector2D *v) { + ai_assert(nullptr != v); + return v->SquareLength(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector2Negate( + C_STRUCT aiVector2D *dst) { + ai_assert(nullptr != dst); + *dst = -(*dst); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API float aiVector2DotProduct( + const C_STRUCT aiVector2D *a, + const C_STRUCT aiVector2D *b) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + return (*a) * (*b); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector2Normalize( + C_STRUCT aiVector2D *v) { + ai_assert(nullptr != v); + v->Normalize(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API int aiVector3AreEqual( + const C_STRUCT aiVector3D *a, + const C_STRUCT aiVector3D *b) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + return *a == *b; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API int aiVector3AreEqualEpsilon( + const C_STRUCT aiVector3D *a, + const C_STRUCT aiVector3D *b, + const float epsilon) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + return a->Equal(*b, epsilon); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API int aiVector3LessThan( + const C_STRUCT aiVector3D *a, + const C_STRUCT aiVector3D *b) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + return *a < *b; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector3Add( + C_STRUCT aiVector3D *dst, + const C_STRUCT aiVector3D *src) { + ai_assert(nullptr != dst); + ai_assert(nullptr != src); + *dst = *dst + *src; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector3Subtract( + C_STRUCT aiVector3D *dst, + const C_STRUCT aiVector3D *src) { + ai_assert(nullptr != dst); + ai_assert(nullptr != src); + *dst = *dst - *src; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector3Scale( + C_STRUCT aiVector3D *dst, + const float s) { + ai_assert(nullptr != dst); + *dst *= s; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector3SymMul( + C_STRUCT aiVector3D *dst, + const C_STRUCT aiVector3D *other) { + ai_assert(nullptr != dst); + ai_assert(nullptr != other); + *dst = dst->SymMul(*other); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector3DivideByScalar( + C_STRUCT aiVector3D *dst, const float s) { + ai_assert(nullptr != dst); + *dst /= s; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector3DivideByVector( + C_STRUCT aiVector3D *dst, + C_STRUCT aiVector3D *v) { + ai_assert(nullptr != dst); + ai_assert(nullptr != v); + *dst = *dst / *v; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API float aiVector3Length( + const C_STRUCT aiVector3D *v) { + ai_assert(nullptr != v); + return v->Length(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API float aiVector3SquareLength( + const C_STRUCT aiVector3D *v) { + ai_assert(nullptr != v); + return v->SquareLength(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector3Negate( + C_STRUCT aiVector3D *dst) { + ai_assert(nullptr != dst); + *dst = -(*dst); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API float aiVector3DotProduct( + const C_STRUCT aiVector3D *a, + const C_STRUCT aiVector3D *b) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + return (*a) * (*b); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector3CrossProduct( + C_STRUCT aiVector3D *dst, + const C_STRUCT aiVector3D *a, + const C_STRUCT aiVector3D *b) { + ai_assert(nullptr != dst); + ai_assert(nullptr != a); + ai_assert(nullptr != b); + *dst = *a ^ *b; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector3Normalize( + C_STRUCT aiVector3D *v) { + ai_assert(nullptr != v); + v->Normalize(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector3NormalizeSafe( + C_STRUCT aiVector3D *v) { + ai_assert(nullptr != v); + v->NormalizeSafe(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiVector3RotateByQuaternion( + C_STRUCT aiVector3D *v, + const C_STRUCT aiQuaternion *q) { + ai_assert(nullptr != v); + ai_assert(nullptr != q); + *v = q->Rotate(*v); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix3FromMatrix4( + C_STRUCT aiMatrix3x3 *dst, + const C_STRUCT aiMatrix4x4 *mat) { + ai_assert(nullptr != dst); + ai_assert(nullptr != mat); + *dst = aiMatrix3x3(*mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix3FromQuaternion( + C_STRUCT aiMatrix3x3 *mat, + const C_STRUCT aiQuaternion *q) { + ai_assert(nullptr != mat); + ai_assert(nullptr != q); + *mat = q->GetMatrix(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API int aiMatrix3AreEqual( + const C_STRUCT aiMatrix3x3 *a, + const C_STRUCT aiMatrix3x3 *b) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + return *a == *b; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API int aiMatrix3AreEqualEpsilon( + const C_STRUCT aiMatrix3x3 *a, + const C_STRUCT aiMatrix3x3 *b, + const float epsilon) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + return a->Equal(*b, epsilon); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix3Inverse(C_STRUCT aiMatrix3x3 *mat) { + ai_assert(nullptr != mat); + mat->Inverse(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API float aiMatrix3Determinant(const C_STRUCT aiMatrix3x3 *mat) { + ai_assert(nullptr != mat); + return mat->Determinant(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix3RotationZ( + C_STRUCT aiMatrix3x3 *mat, + const float angle) { + ai_assert(nullptr != mat); + aiMatrix3x3::RotationZ(angle, *mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix3FromRotationAroundAxis( + C_STRUCT aiMatrix3x3 *mat, + const C_STRUCT aiVector3D *axis, + const float angle) { + ai_assert(nullptr != mat); + ai_assert(nullptr != axis); + aiMatrix3x3::Rotation(angle, *axis, *mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix3Translation( + C_STRUCT aiMatrix3x3 *mat, + const C_STRUCT aiVector2D *translation) { + ai_assert(nullptr != mat); + ai_assert(nullptr != translation); + aiMatrix3x3::Translation(*translation, *mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix3FromTo( + C_STRUCT aiMatrix3x3 *mat, + const C_STRUCT aiVector3D *from, + const C_STRUCT aiVector3D *to) { + ai_assert(nullptr != mat); + ai_assert(nullptr != from); + ai_assert(nullptr != to); + aiMatrix3x3::FromToMatrix(*from, *to, *mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4FromMatrix3( + C_STRUCT aiMatrix4x4 *dst, + const C_STRUCT aiMatrix3x3 *mat) { + ai_assert(nullptr != dst); + ai_assert(nullptr != mat); + *dst = aiMatrix4x4(*mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4FromScalingQuaternionPosition( + C_STRUCT aiMatrix4x4 *mat, + const C_STRUCT aiVector3D *scaling, + const C_STRUCT aiQuaternion *rotation, + const C_STRUCT aiVector3D *position) { + ai_assert(nullptr != mat); + ai_assert(nullptr != scaling); + ai_assert(nullptr != rotation); + ai_assert(nullptr != position); + *mat = aiMatrix4x4(*scaling, *rotation, *position); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4Add( + C_STRUCT aiMatrix4x4 *dst, + const C_STRUCT aiMatrix4x4 *src) { + ai_assert(nullptr != dst); + ai_assert(nullptr != src); + *dst = *dst + *src; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API int aiMatrix4AreEqual( + const C_STRUCT aiMatrix4x4 *a, + const C_STRUCT aiMatrix4x4 *b) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + return *a == *b; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API int aiMatrix4AreEqualEpsilon( + const C_STRUCT aiMatrix4x4 *a, + const C_STRUCT aiMatrix4x4 *b, + const float epsilon) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + return a->Equal(*b, epsilon); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4Inverse(C_STRUCT aiMatrix4x4 *mat) { + ai_assert(nullptr != mat); + mat->Inverse(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API float aiMatrix4Determinant(const C_STRUCT aiMatrix4x4 *mat) { + ai_assert(nullptr != mat); + return mat->Determinant(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API int aiMatrix4IsIdentity(const C_STRUCT aiMatrix4x4 *mat) { + ai_assert(nullptr != mat); + return mat->IsIdentity(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4DecomposeIntoScalingEulerAnglesPosition( + const C_STRUCT aiMatrix4x4 *mat, + C_STRUCT aiVector3D *scaling, + C_STRUCT aiVector3D *rotation, + C_STRUCT aiVector3D *position) { + ai_assert(nullptr != mat); + ai_assert(nullptr != scaling); + ai_assert(nullptr != rotation); + ai_assert(nullptr != position); + mat->Decompose(*scaling, *rotation, *position); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4DecomposeIntoScalingAxisAnglePosition( + const C_STRUCT aiMatrix4x4 *mat, + C_STRUCT aiVector3D *scaling, + C_STRUCT aiVector3D *axis, + ai_real *angle, + C_STRUCT aiVector3D *position) { + ai_assert(nullptr != mat); + ai_assert(nullptr != scaling); + ai_assert(nullptr != axis); + ai_assert(nullptr != angle); + ai_assert(nullptr != position); + mat->Decompose(*scaling, *axis, *angle, *position); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4DecomposeNoScaling( + const C_STRUCT aiMatrix4x4 *mat, + C_STRUCT aiQuaternion *rotation, + C_STRUCT aiVector3D *position) { + ai_assert(nullptr != mat); + ai_assert(nullptr != rotation); + ai_assert(nullptr != position); + mat->DecomposeNoScaling(*rotation, *position); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4FromEulerAngles( + C_STRUCT aiMatrix4x4 *mat, + float x, float y, float z) { + ai_assert(nullptr != mat); + mat->FromEulerAnglesXYZ(x, y, z); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4RotationX( + C_STRUCT aiMatrix4x4 *mat, + const float angle) { + ai_assert(nullptr != mat); + aiMatrix4x4::RotationX(angle, *mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4RotationY( + C_STRUCT aiMatrix4x4 *mat, + const float angle) { + ai_assert(NULL != mat); + aiMatrix4x4::RotationY(angle, *mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4RotationZ( + C_STRUCT aiMatrix4x4 *mat, + const float angle) { + ai_assert(nullptr != mat); + aiMatrix4x4::RotationZ(angle, *mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4FromRotationAroundAxis( + C_STRUCT aiMatrix4x4 *mat, + const C_STRUCT aiVector3D *axis, + const float angle) { + ai_assert(nullptr != mat); + ai_assert(nullptr != axis); + aiMatrix4x4::Rotation(angle, *axis, *mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4Translation( + C_STRUCT aiMatrix4x4 *mat, + const C_STRUCT aiVector3D *translation) { + ai_assert(nullptr != mat); + ai_assert(nullptr != translation); + aiMatrix4x4::Translation(*translation, *mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4Scaling( + C_STRUCT aiMatrix4x4 *mat, + const C_STRUCT aiVector3D *scaling) { + ai_assert(nullptr != mat); + ai_assert(nullptr != scaling); + aiMatrix4x4::Scaling(*scaling, *mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiMatrix4FromTo( + C_STRUCT aiMatrix4x4 *mat, + const C_STRUCT aiVector3D *from, + const C_STRUCT aiVector3D *to) { + ai_assert(nullptr != mat); + ai_assert(nullptr != from); + ai_assert(nullptr != to); + aiMatrix4x4::FromToMatrix(*from, *to, *mat); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiQuaternionFromEulerAngles( + C_STRUCT aiQuaternion *q, + float x, float y, float z) { + ai_assert(nullptr != q); + *q = aiQuaternion(x, y, z); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiQuaternionFromAxisAngle( + C_STRUCT aiQuaternion *q, + const C_STRUCT aiVector3D *axis, + const float angle) { + ai_assert(nullptr != q); + ai_assert(nullptr != axis); + *q = aiQuaternion(*axis, angle); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiQuaternionFromNormalizedQuaternion( + C_STRUCT aiQuaternion *q, + const C_STRUCT aiVector3D *normalized) { + ai_assert(nullptr != q); + ai_assert(nullptr != normalized); + *q = aiQuaternion(*normalized); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API int aiQuaternionAreEqual( + const C_STRUCT aiQuaternion *a, + const C_STRUCT aiQuaternion *b) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + return *a == *b; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API int aiQuaternionAreEqualEpsilon( + const C_STRUCT aiQuaternion *a, + const C_STRUCT aiQuaternion *b, + const float epsilon) { + ai_assert(nullptr != a); + ai_assert(nullptr != b); + return a->Equal(*b, epsilon); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiQuaternionNormalize( + C_STRUCT aiQuaternion *q) { + ai_assert(nullptr != q); + q->Normalize(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiQuaternionConjugate( + C_STRUCT aiQuaternion *q) { + ai_assert(nullptr != q); + q->Conjugate(); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiQuaternionMultiply( + C_STRUCT aiQuaternion *dst, + const C_STRUCT aiQuaternion *q) { + ai_assert(nullptr != dst); + ai_assert(nullptr != q); + *dst = (*dst) * (*q); +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API void aiQuaternionInterpolate( + C_STRUCT aiQuaternion *dst, + const C_STRUCT aiQuaternion *start, + const C_STRUCT aiQuaternion *end, + const float factor) { + ai_assert(nullptr != dst); + ai_assert(nullptr != start); + ai_assert(nullptr != end); + aiQuaternion::Interpolate(*dst, *start, *end, factor); +} + + +// stb_image is a lightweight image loader. It is shared by: +// - M3D import +// - PBRT export +// Since it's a header-only library, its implementation must be instantiated in some cpp file. +// Don't scatter this task over multiple importers/exporters. Maintain it in a central place (here!). + +#define ASSIMP_HAS_PBRT_EXPORT (!ASSIMP_BUILD_NO_EXPORT && !ASSIMP_BUILD_NO_PBRT_EXPORTER) +#define ASSIMP_HAS_M3D ((!ASSIMP_BUILD_NO_EXPORT && !ASSIMP_BUILD_NO_M3D_EXPORTER) || !ASSIMP_BUILD_NO_M3D_IMPORTER) + +#if ASSIMP_HAS_PBRT_EXPORT +# define ASSIMP_NEEDS_STB_IMAGE 1 +#elif ASSIMP_HAS_M3D +# define ASSIMP_NEEDS_STB_IMAGE 1 +# define STBI_ONLY_PNG +#endif + +#if ASSIMP_NEEDS_STB_IMAGE + +# if _MSC_VER // "unreferenced function has been removed" (SSE2 detection routine in x64 builds) +# pragma warning(push) +# pragma warning(disable: 4505) +# endif + +# define STB_IMAGE_IMPLEMENTATION +# include "stb/stb_image.h" + +# if _MSC_VER +# pragma warning(pop) +# endif + +#endif diff --git a/libs/assimp/code/Common/Base64.cpp b/libs/assimp/code/Common/Base64.cpp new file mode 100644 index 0000000..3e9c474 --- /dev/null +++ b/libs/assimp/code/Common/Base64.cpp @@ -0,0 +1,179 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. +--------------------------------------------------------------------------- +*/ + +#include <assimp/Base64.hpp> +#include <assimp/Exceptional.h> + +namespace Assimp { + +namespace Base64 { + +static const uint8_t tableDecodeBase64[128] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 64, 0, 0, + 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, + 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0 +}; + +static const char *tableEncodeBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + +static inline char EncodeChar(uint8_t b) { + return tableEncodeBase64[size_t(b)]; +} + +inline uint8_t DecodeChar(char c) { + if (c & 0x80) { + throw DeadlyImportError("Invalid base64 char value: ", size_t(c)); + } + return tableDecodeBase64[size_t(c & 0x7F)]; // TODO faster with lookup table or ifs? +} + +void Encode(const uint8_t *in, size_t inLength, std::string &out) { + size_t outLength = ((inLength + 2) / 3) * 4; + + size_t j = out.size(); + out.resize(j + outLength); + + for (size_t i = 0; i < inLength; i += 3) { + uint8_t b = (in[i] & 0xFC) >> 2; + out[j++] = EncodeChar(b); + + b = (in[i] & 0x03) << 4; + if (i + 1 < inLength) { + b |= (in[i + 1] & 0xF0) >> 4; + out[j++] = EncodeChar(b); + + b = (in[i + 1] & 0x0F) << 2; + if (i + 2 < inLength) { + b |= (in[i + 2] & 0xC0) >> 6; + out[j++] = EncodeChar(b); + + b = in[i + 2] & 0x3F; + out[j++] = EncodeChar(b); + } else { + out[j++] = EncodeChar(b); + out[j++] = '='; + } + } else { + out[j++] = EncodeChar(b); + out[j++] = '='; + out[j++] = '='; + } + } +} + +void Encode(const std::vector<uint8_t> &in, std::string &out) { + Encode(in.data(), in.size(), out); +} + +std::string Encode(const std::vector<uint8_t> &in) { + std::string encoded; + Encode(in, encoded); + return encoded; +} + +size_t Decode(const char *in, size_t inLength, uint8_t *&out) { + if (inLength % 4 != 0) { + throw DeadlyImportError("Invalid base64 encoded data: \"", std::string(in, std::min(size_t(32), inLength)), "\", length:", inLength); + } + + if (inLength < 4) { + out = nullptr; + return 0; + } + + int nEquals = int(in[inLength - 1] == '=') + + int(in[inLength - 2] == '='); + + size_t outLength = (inLength * 3) / 4 - nEquals; + out = new uint8_t[outLength]; + memset(out, 0, outLength); + + size_t i, j = 0; + + for (i = 0; i + 4 < inLength; i += 4) { + uint8_t b0 = DecodeChar(in[i]); + uint8_t b1 = DecodeChar(in[i + 1]); + uint8_t b2 = DecodeChar(in[i + 2]); + uint8_t b3 = DecodeChar(in[i + 3]); + + out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); + out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); + out[j++] = (uint8_t)((b2 << 6) | b3); + } + + { + uint8_t b0 = DecodeChar(in[i]); + uint8_t b1 = DecodeChar(in[i + 1]); + uint8_t b2 = DecodeChar(in[i + 2]); + uint8_t b3 = DecodeChar(in[i + 3]); + + out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); + if (b2 < 64) out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); + if (b3 < 64) out[j++] = (uint8_t)((b2 << 6) | b3); + } + + return outLength; +} + +size_t Decode(const std::string &in, std::vector<uint8_t> &out) { + uint8_t *outPtr = nullptr; + size_t decodedSize = Decode(in.data(), in.size(), outPtr); + if (outPtr == nullptr) { + return 0; + } + out.assign(outPtr, outPtr + decodedSize); + delete[] outPtr; + return decodedSize; +} + +std::vector<uint8_t> Decode(const std::string &in) { + std::vector<uint8_t> result; + Decode(in, result); + return result; +} + +} // namespace Base64 +} // namespace Assimp diff --git a/libs/assimp/code/Common/BaseImporter.cpp b/libs/assimp/code/Common/BaseImporter.cpp new file mode 100644 index 0000000..383300e --- /dev/null +++ b/libs/assimp/code/Common/BaseImporter.cpp @@ -0,0 +1,620 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 BaseImporter.cpp + * @brief Implementation of BaseImporter + */ + +#include "FileSystemFilter.h" +#include "Importer.h" +#include <assimp/BaseImporter.h> +#include <assimp/ByteSwapper.h> +#include <assimp/ParsingUtils.h> +#include <assimp/importerdesc.h> +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <assimp/Importer.hpp> + +#include <cctype> +#include <ios> +#include <list> +#include <memory> +#include <sstream> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +BaseImporter::BaseImporter() AI_NO_EXCEPT + : m_progress() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +BaseImporter::~BaseImporter() { + // nothing to do here +} + +void BaseImporter::UpdateImporterScale(Importer *pImp) { + ai_assert(pImp != nullptr); + ai_assert(importerScale != 0.0); + ai_assert(fileScale != 0.0); + + double activeScale = importerScale * fileScale; + + // Set active scaling + pImp->SetPropertyFloat(AI_CONFIG_APP_SCALE_KEY, static_cast<float>(activeScale)); + + ASSIMP_LOG_DEBUG("UpdateImporterScale scale set: ", activeScale); +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file and returns the imported data. +aiScene *BaseImporter::ReadFile(Importer *pImp, const std::string &pFile, IOSystem *pIOHandler) { + + m_progress = pImp->GetProgressHandler(); + if (nullptr == m_progress) { + return nullptr; + } + + ai_assert(m_progress); + + // Gather configuration properties for this run + SetupProperties(pImp); + + // Construct a file system filter to improve our success ratio at reading external files + FileSystemFilter filter(pFile, pIOHandler); + + // create a scene object to hold the data + std::unique_ptr<aiScene> sc(new aiScene()); + + // dispatch importing + try { + InternReadFile(pFile, sc.get(), &filter); + + // Calculate import scale hook - required because pImp not available anywhere else + // passes scale into ScaleProcess + UpdateImporterScale(pImp); + + } catch( const std::exception &err ) { + // extract error description + m_ErrorText = err.what(); + ASSIMP_LOG_ERROR(err.what()); + m_Exception = std::current_exception(); + return nullptr; + } + + // return what we gathered from the import. + return sc.release(); +} + +// ------------------------------------------------------------------------------------------------ +void BaseImporter::SetupProperties(const Importer *) { + // the default implementation does nothing +} + +// ------------------------------------------------------------------------------------------------ +void BaseImporter::GetExtensionList(std::set<std::string> &extensions) { + const aiImporterDesc *desc = GetInfo(); + ai_assert(desc != nullptr); + + const char *ext = desc->mFileExtensions; + ai_assert(ext != nullptr); + + const char *last = ext; + do { + if (!*ext || *ext == ' ') { + extensions.insert(std::string(last, ext - last)); + ai_assert(ext - last > 0); + last = ext; + while (*last == ' ') { + ++last; + } + } + } while (*ext++); +} + +// ------------------------------------------------------------------------------------------------ +/*static*/ bool BaseImporter::SearchFileHeaderForToken(IOSystem *pIOHandler, + const std::string &pFile, + const char **tokens, + std::size_t numTokens, + unsigned int searchBytes /* = 200 */, + bool tokensSol /* false */, + bool noAlphaBeforeTokens /* false */) { + ai_assert(nullptr != tokens); + ai_assert(0 != numTokens); + ai_assert(0 != searchBytes); + + if (nullptr == pIOHandler) { + return false; + } + + std::unique_ptr<IOStream> pStream(pIOHandler->Open(pFile)); + if (pStream) { + // read 200 characters from the file + std::unique_ptr<char[]> _buffer(new char[searchBytes + 1 /* for the '\0' */]); + char *buffer(_buffer.get()); + const size_t read(pStream->Read(buffer, 1, searchBytes)); + if (0 == read) { + return false; + } + + for (size_t i = 0; i < read; ++i) { + buffer[i] = static_cast<char>(::tolower((unsigned char)buffer[i])); + } + + // It is not a proper handling of unicode files here ... + // ehm ... but it works in most cases. + char *cur = buffer, *cur2 = buffer, *end = &buffer[read]; + while (cur != end) { + if (*cur) { + *cur2++ = *cur; + } + ++cur; + } + *cur2 = '\0'; + + std::string token; + for (unsigned int i = 0; i < numTokens; ++i) { + ai_assert(nullptr != tokens[i]); + const size_t len(strlen(tokens[i])); + token.clear(); + const char *ptr(tokens[i]); + for (size_t tokIdx = 0; tokIdx < len; ++tokIdx) { + token.push_back(static_cast<char>(tolower(static_cast<unsigned char>(*ptr)))); + ++ptr; + } + const char *r = strstr(buffer, token.c_str()); + if (!r) { + continue; + } + // We need to make sure that we didn't accidentally identify the end of another token as our token, + // e.g. in a previous version the "gltf " present in some gltf files was detected as "f " + if (noAlphaBeforeTokens && (r != buffer && isalpha(static_cast<unsigned char>(r[-1])))) { + continue; + } + // We got a match, either we don't care where it is, or it happens to + // be in the beginning of the file / line + if (!tokensSol || r == buffer || r[-1] == '\r' || r[-1] == '\n') { + ASSIMP_LOG_DEBUG("Found positive match for header keyword: ", tokens[i]); + return true; + } + } + } + + return false; +} + +// ------------------------------------------------------------------------------------------------ +// Simple check for file extension +/*static*/ bool BaseImporter::SimpleExtensionCheck(const std::string &pFile, + const char *ext0, + const char *ext1, + const char *ext2) { + std::string::size_type pos = pFile.find_last_of('.'); + + // no file extension - can't read + if (pos == std::string::npos) + return false; + + const char *ext_real = &pFile[pos + 1]; + if (!ASSIMP_stricmp(ext_real, ext0)) + return true; + + // check for other, optional, file extensions + if (ext1 && !ASSIMP_stricmp(ext_real, ext1)) + return true; + + if (ext2 && !ASSIMP_stricmp(ext_real, ext2)) + return true; + + return false; +} + +// ------------------------------------------------------------------------------------------------ +// Get file extension from path +std::string BaseImporter::GetExtension(const std::string &file) { + std::string::size_type pos = file.find_last_of('.'); + + // no file extension at all + if (pos == std::string::npos) { + return std::string(); + } + + // thanks to Andy Maloney for the hint + std::string ret = file.substr(pos + 1); + ret = ai_tolower(ret); + + return ret; +} + + +// ------------------------------------------------------------------------------------------------ +// Check for magic bytes at the beginning of the file. +/* static */ bool BaseImporter::CheckMagicToken(IOSystem *pIOHandler, const std::string &pFile, + const void *_magic, std::size_t num, unsigned int offset, unsigned int size) { + ai_assert(size <= 16); + ai_assert(_magic); + + if (!pIOHandler) { + return false; + } + union { + const char *magic; + const uint16_t *magic_u16; + const uint32_t *magic_u32; + }; + magic = reinterpret_cast<const char *>(_magic); + std::unique_ptr<IOStream> pStream(pIOHandler->Open(pFile)); + if (pStream) { + + // skip to offset + pStream->Seek(offset, aiOrigin_SET); + + // read 'size' characters from the file + union { + char data[16]; + uint16_t data_u16[8]; + uint32_t data_u32[4]; + }; + if (size != pStream->Read(data, 1, size)) { + return false; + } + + for (unsigned int i = 0; i < num; ++i) { + // also check against big endian versions of tokens with size 2,4 + // that's just for convenience, the chance that we cause conflicts + // is quite low and it can save some lines and prevent nasty bugs + if (2 == size) { + uint16_t rev = *magic_u16; + ByteSwap::Swap(&rev); + if (data_u16[0] == *magic_u16 || data_u16[0] == rev) { + return true; + } + } else if (4 == size) { + uint32_t rev = *magic_u32; + ByteSwap::Swap(&rev); + if (data_u32[0] == *magic_u32 || data_u32[0] == rev) { + return true; + } + } else { + // any length ... just compare + if (!memcmp(magic, data, size)) { + return true; + } + } + magic += size; + } + } + return false; +} + +#ifdef ASSIMP_USE_HUNTER +#include <utf8.h> +#else +#include "../contrib/utf8cpp/source/utf8.h" +#endif + +// ------------------------------------------------------------------------------------------------ +// Convert to UTF8 data +void BaseImporter::ConvertToUTF8(std::vector<char> &data) { + //ConversionResult result; + if (data.size() < 8) { + throw DeadlyImportError("File is too small"); + } + + // UTF 8 with BOM + if ((uint8_t)data[0] == 0xEF && (uint8_t)data[1] == 0xBB && (uint8_t)data[2] == 0xBF) { + ASSIMP_LOG_DEBUG("Found UTF-8 BOM ..."); + + std::copy(data.begin() + 3, data.end(), data.begin()); + data.resize(data.size() - 3); + return; + } + + // UTF 32 BE with BOM + if (*((uint32_t *)&data.front()) == 0xFFFE0000) { + + // swap the endianness .. + for (uint32_t *p = (uint32_t *)&data.front(), *end = (uint32_t *)&data.back(); p <= end; ++p) { + AI_SWAP4P(p); + } + } + + // UTF 32 LE with BOM + if (*((uint32_t *)&data.front()) == 0x0000FFFE) { + ASSIMP_LOG_DEBUG("Found UTF-32 BOM ..."); + + std::vector<char> output; + int *ptr = (int *)&data[0]; + int *end = ptr + (data.size() / sizeof(int)) + 1; + utf8::utf32to8(ptr, end, back_inserter(output)); + return; + } + + // UTF 16 BE with BOM + if (*((uint16_t *)&data.front()) == 0xFFFE) { + // Check to ensure no overflow can happen + if(data.size() % 2 != 0) { + return; + } + // swap the endianness .. + for (uint16_t *p = (uint16_t *)&data.front(), *end = (uint16_t *)&data.back(); p <= end; ++p) { + ByteSwap::Swap2(p); + } + } + + // UTF 16 LE with BOM + if (*((uint16_t *)&data.front()) == 0xFEFF) { + ASSIMP_LOG_DEBUG("Found UTF-16 BOM ..."); + + std::vector<unsigned char> output; + utf8::utf16to8(data.begin(), data.end(), back_inserter(output)); + return; + } +} + +// ------------------------------------------------------------------------------------------------ +// Convert to UTF8 data to ISO-8859-1 +void BaseImporter::ConvertUTF8toISO8859_1(std::string &data) { + size_t size = data.size(); + size_t i = 0, j = 0; + + while (i < size) { + if ((unsigned char)data[i] < (size_t)0x80) { + data[j] = data[i]; + } else if (i < size - 1) { + if ((unsigned char)data[i] == 0xC2) { + data[j] = data[++i]; + } else if ((unsigned char)data[i] == 0xC3) { + data[j] = ((unsigned char)data[++i] + 0x40); + } else { + std::stringstream stream; + stream << "UTF8 code " << std::hex << data[i] << data[i + 1] << " can not be converted into ISA-8859-1."; + ASSIMP_LOG_ERROR(stream.str()); + + data[j++] = data[i++]; + data[j] = data[i]; + } + } else { + ASSIMP_LOG_ERROR("UTF8 code but only one character remaining"); + + data[j] = data[i]; + } + + i++; + j++; + } + + data.resize(j); +} + +// ------------------------------------------------------------------------------------------------ +void BaseImporter::TextFileToBuffer(IOStream *stream, + std::vector<char> &data, + TextFileMode mode) { + ai_assert(nullptr != stream); + + const size_t fileSize = stream->FileSize(); + if (mode == FORBID_EMPTY) { + if (!fileSize) { + throw DeadlyImportError("File is empty"); + } + } + + data.reserve(fileSize + 1); + data.resize(fileSize); + if (fileSize > 0) { + if (fileSize != stream->Read(&data[0], 1, fileSize)) { + throw DeadlyImportError("File read error"); + } + + ConvertToUTF8(data); + } + + // append a binary zero to simplify string parsing + data.push_back(0); +} + +// ------------------------------------------------------------------------------------------------ +namespace Assimp { +// Represents an import request +struct LoadRequest { + LoadRequest(const std::string &_file, unsigned int _flags, const BatchLoader::PropertyMap *_map, unsigned int _id) : + file(_file), + flags(_flags), + refCnt(1), + scene(nullptr), + loaded(false), + id(_id) { + if (_map) { + map = *_map; + } + } + + bool operator==(const std::string &f) const { + return file == f; + } + + const std::string file; + unsigned int flags; + unsigned int refCnt; + aiScene *scene; + bool loaded; + BatchLoader::PropertyMap map; + unsigned int id; +}; +} // namespace Assimp + +// ------------------------------------------------------------------------------------------------ +// BatchLoader::pimpl data structure +struct Assimp::BatchData { + BatchData(IOSystem *pIO, bool validate) : + pIOSystem(pIO), pImporter(nullptr), next_id(0xffff), validate(validate) { + ai_assert(nullptr != pIO); + + pImporter = new Importer(); + pImporter->SetIOHandler(pIO); + } + + ~BatchData() { + pImporter->SetIOHandler(nullptr); /* get pointer back into our possession */ + delete pImporter; + } + + // IO system to be used for all imports + IOSystem *pIOSystem; + + // Importer used to load all meshes + Importer *pImporter; + + // List of all imports + std::list<LoadRequest> requests; + + // Base path + std::string pathBase; + + // Id for next item + unsigned int next_id; + + // Validation enabled state + bool validate; +}; + +typedef std::list<LoadRequest>::iterator LoadReqIt; + +// ------------------------------------------------------------------------------------------------ +BatchLoader::BatchLoader(IOSystem *pIO, bool validate) { + ai_assert(nullptr != pIO); + + m_data = new BatchData(pIO, validate); +} + +// ------------------------------------------------------------------------------------------------ +BatchLoader::~BatchLoader() { + // delete all scenes what have not been polled by the user + for (LoadReqIt it = m_data->requests.begin(); it != m_data->requests.end(); ++it) { + delete (*it).scene; + } + delete m_data; +} + +// ------------------------------------------------------------------------------------------------ +void BatchLoader::setValidation(bool enabled) { + m_data->validate = enabled; +} + +// ------------------------------------------------------------------------------------------------ +bool BatchLoader::getValidation() const { + return m_data->validate; +} + +// ------------------------------------------------------------------------------------------------ +unsigned int BatchLoader::AddLoadRequest(const std::string &file, + unsigned int steps /*= 0*/, const PropertyMap *map /*= nullptr*/) { + ai_assert(!file.empty()); + + // check whether we have this loading request already + for (LoadReqIt it = m_data->requests.begin(); it != m_data->requests.end(); ++it) { + // Call IOSystem's path comparison function here + if (m_data->pIOSystem->ComparePaths((*it).file, file)) { + if (map) { + if (!((*it).map == *map)) { + continue; + } + } else if (!(*it).map.empty()) { + continue; + } + + (*it).refCnt++; + return (*it).id; + } + } + + // no, we don't have it. So add it to the queue ... + m_data->requests.emplace_back(file, steps, map, m_data->next_id); + return m_data->next_id++; +} + +// ------------------------------------------------------------------------------------------------ +aiScene *BatchLoader::GetImport(unsigned int which) { + for (LoadReqIt it = m_data->requests.begin(); it != m_data->requests.end(); ++it) { + if ((*it).id == which && (*it).loaded) { + aiScene *sc = (*it).scene; + if (!(--(*it).refCnt)) { + m_data->requests.erase(it); + } + return sc; + } + } + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +void BatchLoader::LoadAll() { + // no threaded implementation for the moment + for (LoadReqIt it = m_data->requests.begin(); it != m_data->requests.end(); ++it) { + // force validation in debug builds + unsigned int pp = (*it).flags; + if (m_data->validate) { + pp |= aiProcess_ValidateDataStructure; + } + + // setup config properties if necessary + ImporterPimpl *pimpl = m_data->pImporter->Pimpl(); + pimpl->mFloatProperties = (*it).map.floats; + pimpl->mIntProperties = (*it).map.ints; + pimpl->mStringProperties = (*it).map.strings; + pimpl->mMatrixProperties = (*it).map.matrices; + + if (!DefaultLogger::isNullLogger()) { + ASSIMP_LOG_INFO("%%% BEGIN EXTERNAL FILE %%%"); + ASSIMP_LOG_INFO("File: ", (*it).file); + } + m_data->pImporter->ReadFile((*it).file, pp); + (*it).scene = m_data->pImporter->GetOrphanedScene(); + (*it).loaded = true; + + ASSIMP_LOG_INFO("%%% END EXTERNAL FILE %%%"); + } +} diff --git a/libs/assimp/code/Common/BaseProcess.cpp b/libs/assimp/code/Common/BaseProcess.cpp new file mode 100644 index 0000000..48895b9 --- /dev/null +++ b/libs/assimp/code/Common/BaseProcess.cpp @@ -0,0 +1,100 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of BaseProcess */ + +#include "BaseProcess.h" +#include "Importer.h" +#include <assimp/BaseImporter.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +BaseProcess::BaseProcess() AI_NO_EXCEPT + : shared(), + progress() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +BaseProcess::~BaseProcess() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +void BaseProcess::ExecuteOnScene(Importer *pImp) { + ai_assert( nullptr != pImp ); + ai_assert( nullptr != pImp->Pimpl()->mScene); + + progress = pImp->GetProgressHandler(); + ai_assert(nullptr != progress); + + SetupProperties(pImp); + + // catch exceptions thrown inside the PostProcess-Step + try { + Execute(pImp->Pimpl()->mScene); + + } catch (const std::exception &err) { + + // extract error description + pImp->Pimpl()->mErrorString = err.what(); + ASSIMP_LOG_ERROR(pImp->Pimpl()->mErrorString); + + // and kill the partially imported data + delete pImp->Pimpl()->mScene; + pImp->Pimpl()->mScene = nullptr; + } +} + +// ------------------------------------------------------------------------------------------------ +void BaseProcess::SetupProperties(const Importer * /*pImp*/) { + // the default implementation does nothing +} + +// ------------------------------------------------------------------------------------------------ +bool BaseProcess::RequireVerboseFormat() const { + return true; +} diff --git a/libs/assimp/code/Common/BaseProcess.h b/libs/assimp/code/Common/BaseProcess.h new file mode 100644 index 0000000..6440356 --- /dev/null +++ b/libs/assimp/code/Common/BaseProcess.h @@ -0,0 +1,251 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Base class of all import post processing steps */ +#ifndef INCLUDED_AI_BASEPROCESS_H +#define INCLUDED_AI_BASEPROCESS_H + +#include <assimp/GenericProperty.h> + +#include <map> + +struct aiScene; + +namespace Assimp { + +class Importer; + +// --------------------------------------------------------------------------- +/** Helper class to allow post-processing steps to interact with each other. + * + * The class maintains a simple property list that can be used by pp-steps + * to provide additional information to other steps. This is primarily + * intended for cross-step optimizations. + */ +class SharedPostProcessInfo { +public: + struct Base { + virtual ~Base() {} + }; + + //! Represents data that is allocated on the heap, thus needs to be deleted + template <typename T> + struct THeapData : public Base { + explicit THeapData(T *in) : + data(in) {} + + ~THeapData() { + delete data; + } + T *data; + }; + + //! Represents static, by-value data not allocated on the heap + template <typename T> + struct TStaticData : public Base { + explicit TStaticData(T in) : + data(in) {} + + ~TStaticData() {} + + T data; + }; + + // some typedefs for cleaner code + typedef unsigned int KeyType; + typedef std::map<KeyType, Base *> PropertyMap; + +public: + //! Destructor + ~SharedPostProcessInfo() { + Clean(); + } + + //! Remove all stored properties from the table + void Clean() { + // invoke the virtual destructor for all stored properties + for (PropertyMap::iterator it = pmap.begin(), end = pmap.end(); + it != end; ++it) { + delete (*it).second; + } + pmap.clear(); + } + + //! Add a heap property to the list + template <typename T> + void AddProperty(const char *name, T *in) { + AddProperty(name, (Base *)new THeapData<T>(in)); + } + + //! Add a static by-value property to the list + template <typename T> + void AddProperty(const char *name, T in) { + AddProperty(name, (Base *)new TStaticData<T>(in)); + } + + //! Get a heap property + template <typename T> + bool GetProperty(const char *name, T *&out) const { + THeapData<T> *t = (THeapData<T> *)GetPropertyInternal(name); + if (!t) { + out = nullptr; + return false; + } + out = t->data; + return true; + } + + //! Get a static, by-value property + template <typename T> + bool GetProperty(const char *name, T &out) const { + TStaticData<T> *t = (TStaticData<T> *)GetPropertyInternal(name); + if ( nullptr == t) { + return false; + } + out = t->data; + return true; + } + + //! Remove a property of a specific type + void RemoveProperty(const char *name) { + SetGenericPropertyPtr<Base>(pmap, name, nullptr ); + } + +private: + void AddProperty(const char *name, Base *data) { + SetGenericPropertyPtr<Base>(pmap, name, data); + } + + Base *GetPropertyInternal(const char *name) const { + return GetGenericProperty<Base *>(pmap, name, nullptr ); + } + +private: + //! Map of all stored properties + PropertyMap pmap; +}; + +#define AI_SPP_SPATIAL_SORT "$Spat" + +// --------------------------------------------------------------------------- +/** The BaseProcess defines a common interface for all post processing steps. + * A post processing step is run after a successful import if the caller + * specified the corresponding flag when calling ReadFile(). + * Enum #aiPostProcessSteps defines which flags are available. + * After a successful import the Importer iterates over its internal array + * of processes and calls IsActive() on each process to evaluate if the step + * should be executed. If the function returns true, the class' Execute() + * function is called subsequently. + */ +class ASSIMP_API_WINONLY BaseProcess { + friend class Importer; + +public: + /** Constructor to be privately used by Importer */ + BaseProcess() AI_NO_EXCEPT; + + /** Destructor, private as well */ + virtual ~BaseProcess(); + + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag. + * @param pFlags The processing flags the importer was called with. A + * bitwise combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, + * false if not. + */ + virtual bool IsActive(unsigned int pFlags) const = 0; + + // ------------------------------------------------------------------- + /** Check whether this step expects its input vertex data to be + * in verbose format. */ + virtual bool RequireVerboseFormat() const; + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * The function deletes the scene if the postprocess step fails ( + * the object pointer will be set to nullptr). + * @param pImp Importer instance (pImp->mScene must be valid) + */ + void ExecuteOnScene(Importer *pImp); + + // ------------------------------------------------------------------- + /** Called prior to ExecuteOnScene(). + * The function is a request to the process to update its configuration + * basing on the Importer's configuration property list. + */ + virtual void SetupProperties(const Importer *pImp); + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * A process should throw an ImportErrorException* if it fails. + * This method must be implemented by deriving classes. + * @param pScene The imported data to work at. + */ + virtual void Execute(aiScene *pScene) = 0; + + // ------------------------------------------------------------------- + /** Assign a new SharedPostProcessInfo to the step. This object + * allows multiple postprocess steps to share data. + * @param sh May be nullptr + */ + inline void SetSharedData(SharedPostProcessInfo *sh) { + shared = sh; + } + + // ------------------------------------------------------------------- + /** Get the shared data that is assigned to the step. + */ + inline SharedPostProcessInfo *GetSharedData() { + return shared; + } + +protected: + /** See the doc of #SharedPostProcessInfo for more details */ + SharedPostProcessInfo *shared; + + /** Currently active progress handler */ + ProgressHandler *progress; +}; + +} // end of namespace Assimp + +#endif // AI_BASEPROCESS_H_INC diff --git a/libs/assimp/code/Common/Bitmap.cpp b/libs/assimp/code/Common/Bitmap.cpp new file mode 100644 index 0000000..65dfd17 --- /dev/null +++ b/libs/assimp/code/Common/Bitmap.cpp @@ -0,0 +1,156 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Bitmap.cpp + * @brief Defines bitmap format helper for textures + * + * Used for file formats which embed their textures into the model file. + */ + +#include <assimp/Bitmap.h> +#include <assimp/ByteSwapper.h> +#include <assimp/texture.h> +#include <assimp/IOStream.hpp> + +namespace Assimp { + +bool Bitmap::Save(aiTexture *texture, IOStream *file) { + if (file == nullptr) { + return false; + } + + Header header; + DIB dib; + dib.size = DIB::dib_size; + dib.width = texture->mWidth; + dib.height = texture->mHeight; + dib.planes = 1; + dib.bits_per_pixel = 8 * mBytesPerPixel; + dib.compression = 0; + dib.image_size = (((dib.width * mBytesPerPixel) + 3) & 0x0000FFFC) * dib.height; + dib.x_resolution = 0; + dib.y_resolution = 0; + dib.nb_colors = 0; + dib.nb_important_colors = 0; + + header.type = 0x4D42; // 'BM' + header.offset = Header::header_size + DIB::dib_size; + header.size = header.offset + dib.image_size; + header.reserved1 = 0; + header.reserved2 = 0; + + WriteHeader(header, file); + WriteDIB(dib, file); + WriteData(texture, file); + + return true; +} + +template <typename T> +inline std::size_t Copy(uint8_t *data, const T &field) { +#ifdef AI_BUILD_BIG_ENDIAN + T field_swapped = AI_BE(field); + std::memcpy(data, &field_swapped, sizeof(field)); + return sizeof(field); +#else + std::memcpy(data, &AI_BE(field), sizeof(field)); + return sizeof(field); +#endif +} + +void Bitmap::WriteHeader(Header &header, IOStream *file) { + uint8_t data[Header::header_size]; + + std::size_t offset = 0; + + offset += Copy(&data[offset], header.type); + offset += Copy(&data[offset], header.size); + offset += Copy(&data[offset], header.reserved1); + offset += Copy(&data[offset], header.reserved2); + Copy(&data[offset], header.offset); + + file->Write(data, Header::header_size, 1); +} + +void Bitmap::WriteDIB(DIB &dib, IOStream *file) { + uint8_t data[DIB::dib_size]; + + std::size_t offset = 0; + + offset += Copy(&data[offset], dib.size); + offset += Copy(&data[offset], dib.width); + offset += Copy(&data[offset], dib.height); + offset += Copy(&data[offset], dib.planes); + offset += Copy(&data[offset], dib.bits_per_pixel); + offset += Copy(&data[offset], dib.compression); + offset += Copy(&data[offset], dib.image_size); + offset += Copy(&data[offset], dib.x_resolution); + offset += Copy(&data[offset], dib.y_resolution); + offset += Copy(&data[offset], dib.nb_colors); + Copy(&data[offset], dib.nb_important_colors); + + file->Write(data, DIB::dib_size, 1); +} + +void Bitmap::WriteData(aiTexture *texture, IOStream *file) { + static const std::size_t padding_offset = 4; + static const uint8_t padding_data[padding_offset] = { 0x0, 0x0, 0x0, 0x0 }; + + unsigned int padding = (padding_offset - ((mBytesPerPixel * texture->mWidth) % padding_offset)) % padding_offset; + uint8_t pixel[mBytesPerPixel]; + + for (std::size_t i = 0; i < texture->mHeight; ++i) { + for (std::size_t j = 0; j < texture->mWidth; ++j) { + const aiTexel &texel = texture->pcData[(texture->mHeight - i - 1) * texture->mWidth + j]; // Bitmap files are stored in bottom-up format + + pixel[0] = texel.r; + pixel[1] = texel.g; + pixel[2] = texel.b; + pixel[3] = texel.a; + + file->Write(pixel, mBytesPerPixel, 1); + } + + file->Write(padding_data, padding, 1); + } +} + +} // namespace Assimp diff --git a/libs/assimp/code/Common/Compression.cpp b/libs/assimp/code/Common/Compression.cpp new file mode 100644 index 0000000..3bf306d --- /dev/null +++ b/libs/assimp/code/Common/Compression.cpp @@ -0,0 +1,214 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#include "Compression.h" +#include <assimp/ai_assert.h> +#include <assimp/Exceptional.h> + +namespace Assimp { + +struct Compression::impl { + bool mOpen; + z_stream mZSstream; + FlushMode mFlushMode; + + impl() : + mOpen(false), + mZSstream(), + mFlushMode(Compression::FlushMode::NoFlush) { + // empty + } +}; + +Compression::Compression() : + mImpl(new impl) { + // empty +} + +Compression::~Compression() { + ai_assert(mImpl != nullptr); + + delete mImpl; +} + +bool Compression::open(Format format, FlushMode flush, int windowBits) { + ai_assert(mImpl != nullptr); + + if (mImpl->mOpen) { + return false; + } + + // build a zlib stream + mImpl->mZSstream.opaque = Z_NULL; + mImpl->mZSstream.zalloc = Z_NULL; + mImpl->mZSstream.zfree = Z_NULL; + mImpl->mFlushMode = flush; + if (format == Format::Binary) { + mImpl->mZSstream.data_type = Z_BINARY; + } else { + mImpl->mZSstream.data_type = Z_ASCII; + } + + // raw decompression without a zlib or gzip header + if (windowBits == 0) { + inflateInit(&mImpl->mZSstream); + } else { + inflateInit2(&mImpl->mZSstream, windowBits); + } + mImpl->mOpen = true; + + return mImpl->mOpen; +} + +static int getFlushMode(Compression::FlushMode flush) { + int z_flush = 0; + switch (flush) { + case Compression::FlushMode::NoFlush: + z_flush = Z_NO_FLUSH; + break; + case Compression::FlushMode::Block: + z_flush = Z_BLOCK; + break; + case Compression::FlushMode::Tree: + z_flush = Z_TREES; + break; + case Compression::FlushMode::SyncFlush: + z_flush = Z_SYNC_FLUSH; + break; + case Compression::FlushMode::Finish: + z_flush = Z_FINISH; + break; + default: + ai_assert(false); + break; + } + + return z_flush; +} + +constexpr size_t MYBLOCK = 32786; + +size_t Compression::decompress(const void *data, size_t in, std::vector<char> &uncompressed) { + ai_assert(mImpl != nullptr); + if (data == nullptr || in == 0) { + return 0l; + } + + mImpl->mZSstream.next_in = (Bytef*)(data); + mImpl->mZSstream.avail_in = (uInt)in; + + int ret = 0; + size_t total = 0l; + const int flushMode = getFlushMode(mImpl->mFlushMode); + if (flushMode == Z_FINISH) { + mImpl->mZSstream.avail_out = static_cast<uInt>(uncompressed.size()); + mImpl->mZSstream.next_out = reinterpret_cast<Bytef *>(&*uncompressed.begin()); + ret = inflate(&mImpl->mZSstream, Z_FINISH); + + if (ret != Z_STREAM_END && ret != Z_OK) { + throw DeadlyImportError("Compression", "Failure decompressing this file using gzip."); + } + total = mImpl->mZSstream.avail_out; + } else { + do { + Bytef block[MYBLOCK] = {}; + mImpl->mZSstream.avail_out = MYBLOCK; + mImpl->mZSstream.next_out = block; + + ret = inflate(&mImpl->mZSstream, flushMode); + + if (ret != Z_STREAM_END && ret != Z_OK) { + throw DeadlyImportError("Compression", "Failure decompressing this file using gzip."); + } + const size_t have = MYBLOCK - mImpl->mZSstream.avail_out; + total += have; + uncompressed.resize(total); + ::memcpy(uncompressed.data() + total - have, block, have); + } while (ret != Z_STREAM_END); + } + + return total; +} + +size_t Compression::decompressBlock(const void *data, size_t in, char *out, size_t availableOut) { + ai_assert(mImpl != nullptr); + if (data == nullptr || in == 0 || out == nullptr || availableOut == 0) { + return 0l; + } + + // push data to the stream + mImpl->mZSstream.next_in = (Bytef *)data; + mImpl->mZSstream.avail_in = (uInt)in; + mImpl->mZSstream.next_out = (Bytef *)out; + mImpl->mZSstream.avail_out = (uInt)availableOut; + + // and decompress the data .... + int ret = ::inflate(&mImpl->mZSstream, Z_SYNC_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END) { + throw DeadlyImportError("X: Failed to decompress MSZIP-compressed data"); + } + + ::inflateReset(&mImpl->mZSstream); + ::inflateSetDictionary(&mImpl->mZSstream, (const Bytef *)out, (uInt)availableOut - mImpl->mZSstream.avail_out); + + return availableOut - (size_t)mImpl->mZSstream.avail_out; +} + +bool Compression::isOpen() const { + ai_assert(mImpl != nullptr); + + return mImpl->mOpen; +} + +bool Compression::close() { + ai_assert(mImpl != nullptr); + + if (!mImpl->mOpen) { + return false; + } + + inflateEnd(&mImpl->mZSstream); + mImpl->mOpen = false; + + return true; +} + +} // namespace Assimp diff --git a/libs/assimp/code/Common/Compression.h b/libs/assimp/code/Common/Compression.h new file mode 100644 index 0000000..edf1d23 --- /dev/null +++ b/libs/assimp/code/Common/Compression.h @@ -0,0 +1,121 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#pragma once + +#ifdef ASSIMP_BUILD_NO_OWN_ZLIB +#include <zlib.h> +#else +#include "../contrib/zlib/zlib.h" +#endif + +#include <vector> +#include <cstddef> // size_t + +namespace Assimp { + +/// @brief This class provides the decompression of zlib-compressed data. +class Compression { +public: + static const int MaxWBits = MAX_WBITS; + + /// @brief Describes the format data type + enum class Format { + InvalidFormat = -1, ///< Invalid enum type. + Binary = 0, ///< Binary format. + ASCII, ///< ASCII format. + + NumFormats ///< The number of supported formats. + }; + + /// @brief The supported flush mode, used for blocked access. + enum class FlushMode { + InvalidFormat = -1, ///< Invalid enum type. + NoFlush = 0, ///< No flush, will be done on inflate end. + Block, ///< Assists in combination of compress. + Tree, ///< Assists in combination of compress and returns if stream is finish. + SyncFlush, ///< Synced flush mode. + Finish, ///< Finish mode, all in once, no block access. + + NumModes ///< The number of supported modes. + }; + + /// @brief The class constructor. + Compression(); + + /// @brief The class destructor. + ~Compression(); + + /// @brief Will open the access to the compression. + /// @param[in] format The format type + /// @param[in] flush The flush mode. + /// @param[in] windowBits The windows history working size, shall be between 8 and 15. + /// @return true if close was successful, false if not. + bool open(Format format, FlushMode flush, int windowBits); + + /// @brief Will return the open state. + /// @return true if the access is opened, false if not. + bool isOpen() const; + + /// @brief Will close the decompress access. + /// @return true if close was successful, false if not. + bool close(); + + /// @brief Will decompress the data buffer in one step. + /// @param[in] data The data to decompress + /// @param[in] in The size of the data. + /// @param[out uncompressed A std::vector containing the decompressed data. + size_t decompress(const void *data, size_t in, std::vector<char> &uncompressed); + + /// @brief Will decompress the data buffer block-wise. + /// @param[in] data The compressed data + /// @param[in] in The size of the data buffer + /// @param[out] out The output buffer + /// @param[out] availableOut The upper limit of the output buffer. + /// @return The size of the decompressed data buffer. + size_t decompressBlock(const void *data, size_t in, char *out, size_t availableOut); + +private: + struct impl; + impl *mImpl; +}; + +} // namespace Assimp diff --git a/libs/assimp/code/Common/CreateAnimMesh.cpp b/libs/assimp/code/Common/CreateAnimMesh.cpp new file mode 100644 index 0000000..58f3d90 --- /dev/null +++ b/libs/assimp/code/Common/CreateAnimMesh.cpp @@ -0,0 +1,92 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (C) 2016 The Qt Company Ltd. +Copyright (c) 2006-2022, 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. +--------------------------------------------------------------------------- +*/ + +#include <assimp/CreateAnimMesh.h> + +namespace Assimp { + +aiAnimMesh *aiCreateAnimMesh(const aiMesh *mesh, bool needPositions, bool needNormals, bool needTangents, bool needColors, bool needTexCoords) +{ + aiAnimMesh *animesh = new aiAnimMesh; + animesh->mNumVertices = mesh->mNumVertices; + if (needPositions && mesh->mVertices) { + animesh->mVertices = new aiVector3D[animesh->mNumVertices]; + std::memcpy(animesh->mVertices, mesh->mVertices, mesh->mNumVertices * sizeof(aiVector3D)); + } + if (needNormals && mesh->mNormals) { + animesh->mNormals = new aiVector3D[animesh->mNumVertices]; + std::memcpy(animesh->mNormals, mesh->mNormals, mesh->mNumVertices * sizeof(aiVector3D)); + } + if (needTangents && mesh->mTangents) { + animesh->mTangents = new aiVector3D[animesh->mNumVertices]; + std::memcpy(animesh->mTangents, mesh->mTangents, mesh->mNumVertices * sizeof(aiVector3D)); + } + if (needTangents && mesh->mBitangents) { + animesh->mBitangents = new aiVector3D[animesh->mNumVertices]; + std::memcpy(animesh->mBitangents, mesh->mBitangents, mesh->mNumVertices * sizeof(aiVector3D)); + } + + if (needColors) { + for (int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) { + if (mesh->mColors[i]) { + animesh->mColors[i] = new aiColor4D[animesh->mNumVertices]; + std::memcpy(animesh->mColors[i], mesh->mColors[i], mesh->mNumVertices * sizeof(aiColor4D)); + } else { + animesh->mColors[i] = nullptr; + } + } + } + + if (needTexCoords) { + for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (mesh->mTextureCoords[i]) { + animesh->mTextureCoords[i] = new aiVector3D[animesh->mNumVertices]; + std::memcpy(animesh->mTextureCoords[i], mesh->mTextureCoords[i], mesh->mNumVertices * sizeof(aiVector3D)); + } else { + animesh->mTextureCoords[i] = nullptr; + } + } + } + return animesh; +} + +} // end of namespace Assimp diff --git a/libs/assimp/code/Common/DefaultIOStream.cpp b/libs/assimp/code/Common/DefaultIOStream.cpp new file mode 100644 index 0000000..e30f26a --- /dev/null +++ b/libs/assimp/code/Common/DefaultIOStream.cpp @@ -0,0 +1,180 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 DefaultIOStream.cpp + * @brief Default File I/O implementation for #Importer + */ + +#include <assimp/DefaultIOStream.h> +#include <assimp/ai_assert.h> +#include <sys/stat.h> +#include <sys/types.h> + +using namespace Assimp; + +namespace { + +template <size_t sizeOfPointer> +inline size_t select_ftell(FILE *file) { + return ::ftell(file); +} + +template <size_t sizeOfPointer> +inline int select_fseek(FILE *file, int64_t offset, int origin) { + return ::fseek(file, static_cast<long>(offset), origin); +} + + + +#if defined _WIN32 && (!defined __GNUC__ || __MSVCRT_VERSION__ >= 0x0601) +template <> +inline size_t select_ftell<8>(FILE *file) { + return (size_t)::_ftelli64(file); +} + +template <> +inline int select_fseek<8>(FILE *file, int64_t offset, int origin) { + return ::_fseeki64(file, offset, origin); +} + +#endif // #if defined _WIN32 && (!defined __GNUC__ || __MSVCRT_VERSION__ >= 0x0601) + +} // namespace + +// ---------------------------------------------------------------------------------- +DefaultIOStream::~DefaultIOStream() { + if (mFile) { + ::fclose(mFile); + } +} + +// ---------------------------------------------------------------------------------- +size_t DefaultIOStream::Read(void *pvBuffer, + size_t pSize, + size_t pCount) { + if (0 == pCount) { + return 0; + } + ai_assert(nullptr != pvBuffer); + ai_assert(0 != pSize); + + return (mFile ? ::fread(pvBuffer, pSize, pCount, mFile) : 0); +} + +// ---------------------------------------------------------------------------------- +size_t DefaultIOStream::Write(const void *pvBuffer, + size_t pSize, + size_t pCount) { + ai_assert(nullptr != pvBuffer); + ai_assert(0 != pSize); + + return (mFile ? ::fwrite(pvBuffer, pSize, pCount, mFile) : 0); +} + +// ---------------------------------------------------------------------------------- +aiReturn DefaultIOStream::Seek(size_t pOffset, + aiOrigin pOrigin) { + if (!mFile) { + return AI_FAILURE; + } + + // Just to check whether our enum maps one to one with the CRT constants + static_assert(aiOrigin_CUR == SEEK_CUR && + aiOrigin_END == SEEK_END && aiOrigin_SET == SEEK_SET, + "aiOrigin_CUR == SEEK_CUR && \ + aiOrigin_END == SEEK_END && aiOrigin_SET == SEEK_SET"); + + // do the seek + return (0 == select_fseek<sizeof(void *)>(mFile, (int64_t)pOffset, (int)pOrigin) ? AI_SUCCESS : AI_FAILURE); +} + +// ---------------------------------------------------------------------------------- +size_t DefaultIOStream::Tell() const { + if (!mFile) { + return 0; + } + return select_ftell<sizeof(void *)>(mFile); +} + +// ---------------------------------------------------------------------------------- +size_t DefaultIOStream::FileSize() const { + if (!mFile || mFilename.empty()) { + return 0; + } + + if (SIZE_MAX == mCachedSize) { + + // Although fseek/ftell would allow us to reuse the existing file handle here, + // it is generally unsafe because: + // - For binary streams, it is not technically well-defined + // - For text files the results are meaningless + // That's why we use the safer variant fstat here. + // + // See here for details: + // https://www.securecoding.cert.org/confluence/display/seccode/FIO19-C.+Do+not+use+fseek()+and+ftell()+to+compute+the+size+of+a+regular+file +#if defined _WIN32 && (!defined __GNUC__ || __MSVCRT_VERSION__ >= 0x0601) + struct __stat64 fileStat; + //using fileno + fstat avoids having to handle the filename + int err = _fstat64(_fileno(mFile), &fileStat); + if (0 != err) + return 0; + mCachedSize = (size_t)(fileStat.st_size); +#elif defined __GNUC__ || defined __APPLE__ || defined __MACH__ || defined __FreeBSD__ + struct stat fileStat; + int err = stat(mFilename.c_str(), &fileStat); + if (0 != err) + return 0; + const unsigned long long cachedSize = fileStat.st_size; + mCachedSize = static_cast<size_t>(cachedSize); +#else +#error "Unknown platform" +#endif + } + return mCachedSize; +} + +// ---------------------------------------------------------------------------------- +void DefaultIOStream::Flush() { + if (mFile) { + ::fflush(mFile); + } +} + +// ---------------------------------------------------------------------------------- diff --git a/libs/assimp/code/Common/DefaultIOSystem.cpp b/libs/assimp/code/Common/DefaultIOSystem.cpp new file mode 100644 index 0000000..b28910c --- /dev/null +++ b/libs/assimp/code/Common/DefaultIOSystem.cpp @@ -0,0 +1,223 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Default implementation of IOSystem using the standard C file functions */ + +#include <assimp/StringComparison.h> + +#include <assimp/DefaultIOStream.h> +#include <assimp/DefaultIOSystem.h> +#include <assimp/ai_assert.h> +#include <stdlib.h> +#include <assimp/DefaultLogger.hpp> + +#ifdef __unix__ +# include <stdlib.h> +# include <sys/param.h> +#endif + +#ifdef _WIN32 +# include <windows.h> +#endif + +using namespace Assimp; + +#ifdef _WIN32 + +const std::wstring wdummy; + +static std::wstring Utf8ToWide(const char *in) { + if (nullptr == in) { + return wdummy; + } + int size = MultiByteToWideChar(CP_UTF8, 0, in, -1, nullptr, 0); + // size includes terminating null; std::wstring adds null automatically + std::wstring out(static_cast<size_t>(size) - 1, L'\0'); + MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], size); + + return out; +} + +const std::string dummy; + +static std::string WideToUtf8(const wchar_t *in) { + if (nullptr == in) { + return dummy; + } + int size = WideCharToMultiByte(CP_UTF8, 0, in, -1, nullptr, 0, nullptr, nullptr); + // size includes terminating null; std::string adds null automatically + std::string out(static_cast<size_t>(size) - 1, '\0'); + WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], size, nullptr, nullptr); + + return out; +} +#endif + +// ------------------------------------------------------------------------------------------------ +// Tests for the existence of a file at the given path. +bool DefaultIOSystem::Exists(const char *pFile) const { +#ifdef _WIN32 + struct __stat64 filestat; + if (_wstat64(Utf8ToWide(pFile).c_str(), &filestat) != 0) { + return false; + } +#else + FILE *file = ::fopen(pFile, "rb"); + if (!file) { + return false; + } + + ::fclose(file); +#endif + + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Open a new file with a given path. +IOStream *DefaultIOSystem::Open(const char *strFile, const char *strMode) { + ai_assert(strFile != nullptr); + ai_assert(strMode != nullptr); + FILE *file; +#ifdef _WIN32 + std::wstring name = Utf8ToWide(strFile); + if (name.empty()) { + return nullptr; + } + + file = ::_wfopen(name.c_str(), Utf8ToWide(strMode).c_str()); +#else + file = ::fopen(strFile, strMode); +#endif + if (!file) { + return nullptr; + } + + return new DefaultIOStream(file, strFile); +} + +// ------------------------------------------------------------------------------------------------ +// Closes the given file and releases all resources associated with it. +void DefaultIOSystem::Close(IOStream *pFile) { + delete pFile; +} + +// ------------------------------------------------------------------------------------------------ +// Returns the operation specific directory separator +char DefaultIOSystem::getOsSeparator() const { +#ifndef _WIN32 + return '/'; +#else + return '\\'; +#endif +} + +// ------------------------------------------------------------------------------------------------ +// IOSystem default implementation (ComparePaths isn't a pure virtual function) +bool IOSystem::ComparePaths(const char *one, const char *second) const { + return !ASSIMP_stricmp(one, second); +} + +// ------------------------------------------------------------------------------------------------ +// Convert a relative path into an absolute path +inline static std::string MakeAbsolutePath(const char *in) { + ai_assert(in); + std::string out; +#ifdef _WIN32 + wchar_t *ret = ::_wfullpath(nullptr, Utf8ToWide(in).c_str(), 0); + if (ret) { + out = WideToUtf8(ret); + free(ret); + } +#else + char *ret = realpath(in, nullptr); + if (ret) { + out = ret; + free(ret); + } +#endif + else { + // preserve the input path, maybe someone else is able to fix + // the path before it is accessed (e.g. our file system filter) + ASSIMP_LOG_WARN("Invalid path: ", std::string(in)); + out = in; + } + return out; +} + +// ------------------------------------------------------------------------------------------------ +// DefaultIOSystem's more specialized implementation +bool DefaultIOSystem::ComparePaths(const char *one, const char *second) const { + // chances are quite good both paths are formatted identically, + // so we can hopefully return here already + if (!ASSIMP_stricmp(one, second)) + return true; + + std::string temp1 = MakeAbsolutePath(one); + std::string temp2 = MakeAbsolutePath(second); + + return !ASSIMP_stricmp(temp1, temp2); +} + +// ------------------------------------------------------------------------------------------------ +std::string DefaultIOSystem::fileName(const std::string &path) { + std::string ret = path; + std::size_t last = ret.find_last_of("\\/"); + if (last != std::string::npos) ret = ret.substr(last + 1); + return ret; +} + +// ------------------------------------------------------------------------------------------------ +std::string DefaultIOSystem::completeBaseName(const std::string &path) { + std::string ret = fileName(path); + std::size_t pos = ret.find_last_of('.'); + if (pos != std::string::npos) ret = ret.substr(0, pos); + return ret; +} + +// ------------------------------------------------------------------------------------------------ +std::string DefaultIOSystem::absolutePath(const std::string &path) { + std::string ret = path; + std::size_t last = ret.find_last_of("\\/"); + if (last != std::string::npos) ret = ret.substr(0, last); + return ret; +} + +// ------------------------------------------------------------------------------------------------ diff --git a/libs/assimp/code/Common/DefaultLogger.cpp b/libs/assimp/code/Common/DefaultLogger.cpp new file mode 100644 index 0000000..5cb32d3 --- /dev/null +++ b/libs/assimp/code/Common/DefaultLogger.cpp @@ -0,0 +1,430 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 DefaultLogger.cpp + * @brief Implementation of DefaultLogger (and Logger) + */ + +// Default log streams +#include "FileLogStream.h" +#include "StdOStreamLogStream.h" +#include "Win32DebugLogStream.h" +#include <assimp/StringUtils.h> + +#include <assimp/DefaultIOSystem.h> +#include <assimp/ai_assert.h> +#include <stdio.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/NullLogger.hpp> +#include <iostream> + +#ifndef ASSIMP_BUILD_SINGLETHREADED +#include <mutex> +#include <thread> +std::mutex loggerMutex; +#endif + +namespace Assimp { + +// ---------------------------------------------------------------------------------- +NullLogger DefaultLogger::s_pNullLogger; +Logger *DefaultLogger::m_pLogger = &DefaultLogger::s_pNullLogger; + +static const unsigned int SeverityAll = Logger::Info | Logger::Err | Logger::Warn | Logger::Debugging; + +// ---------------------------------------------------------------------------------- +// Represents a log-stream + its error severity +struct LogStreamInfo { + unsigned int m_uiErrorSeverity; + LogStream *m_pStream; + + // Constructor + LogStreamInfo(unsigned int uiErrorSev, LogStream *pStream) : + m_uiErrorSeverity(uiErrorSev), + m_pStream(pStream) { + // empty + } + + // Destructor + ~LogStreamInfo() { + delete m_pStream; + } +}; + +// ---------------------------------------------------------------------------------- +// Construct a default log stream +LogStream *LogStream::createDefaultStream(aiDefaultLogStream streams, + const char *name /*= "AssimpLog.txt"*/, + IOSystem *io /*= nullptr*/) { + switch (streams) { + // This is a platform-specific feature + case aiDefaultLogStream_DEBUGGER: +#ifdef WIN32 + return new Win32DebugLogStream(); +#else + return nullptr; +#endif + + // Platform-independent default streams + case aiDefaultLogStream_STDERR: + return new StdOStreamLogStream(std::cerr); + case aiDefaultLogStream_STDOUT: + return new StdOStreamLogStream(std::cout); + case aiDefaultLogStream_FILE: + return (name && *name ? new FileLogStream(name, io) : nullptr); + default: + // We don't know this default log stream, so raise an assertion + ai_assert(false); + }; + + // For compilers without dead code path detection + return nullptr; +} + +// ---------------------------------------------------------------------------------- +// Creates the only singleton instance +Logger *DefaultLogger::create(const char *name /*= "AssimpLog.txt"*/, + LogSeverity severity /*= NORMAL*/, + unsigned int defStreams /*= aiDefaultLogStream_DEBUGGER | aiDefaultLogStream_FILE*/, + IOSystem *io /*= nullptr*/) { + // enter the mutex here to avoid concurrency problems +#ifndef ASSIMP_BUILD_SINGLETHREADED + std::lock_guard<std::mutex> lock(loggerMutex); +#endif + + if (m_pLogger && !isNullLogger()) { + delete m_pLogger; + } + + m_pLogger = new DefaultLogger(severity); + + // Attach default log streams + // Stream the log to the MSVC debugger? + if (defStreams & aiDefaultLogStream_DEBUGGER) { + m_pLogger->attachStream(LogStream::createDefaultStream(aiDefaultLogStream_DEBUGGER)); + } + + // Stream the log to COUT? + if (defStreams & aiDefaultLogStream_STDOUT) { + m_pLogger->attachStream(LogStream::createDefaultStream(aiDefaultLogStream_STDOUT)); + } + + // Stream the log to CERR? + if (defStreams & aiDefaultLogStream_STDERR) { + m_pLogger->attachStream(LogStream::createDefaultStream(aiDefaultLogStream_STDERR)); + } + + // Stream the log to a file + if (defStreams & aiDefaultLogStream_FILE && name && *name) { + m_pLogger->attachStream(LogStream::createDefaultStream(aiDefaultLogStream_FILE, name, io)); + } + + return m_pLogger; +} + +// ---------------------------------------------------------------------------------- +void Logger::debug(const char *message) { + + // SECURITY FIX: otherwise it's easy to produce overruns since + // sometimes importers will include data from the input file + // (i.e. node names) in their messages. + if (strlen(message) > MAX_LOG_MESSAGE_LENGTH) { + return OnDebug("<fixme: long message discarded>"); + } + return OnDebug(message); +} + +// ---------------------------------------------------------------------------------- +void Logger::verboseDebug(const char *message) { + + // SECURITY FIX: see above + if (strlen(message) > MAX_LOG_MESSAGE_LENGTH) { + return OnVerboseDebug("<fixme: long message discarded>"); + } + return OnVerboseDebug(message); +} + +// ---------------------------------------------------------------------------------- +void Logger::info(const char *message) { + + // SECURITY FIX: see above + if (strlen(message) > MAX_LOG_MESSAGE_LENGTH) { + return OnInfo("<fixme: long message discarded>"); + } + return OnInfo(message); +} + +// ---------------------------------------------------------------------------------- +void Logger::warn(const char *message) { + + // SECURITY FIX: see above + if (strlen(message) > MAX_LOG_MESSAGE_LENGTH) { + return OnWarn("<fixme: long message discarded>"); + } + return OnWarn(message); +} + +// ---------------------------------------------------------------------------------- +void Logger::error(const char *message) { + // SECURITY FIX: see above + if (strlen(message) > MAX_LOG_MESSAGE_LENGTH) { + return OnError("<fixme: long message discarded>"); + } + return OnError(message); +} + +// ---------------------------------------------------------------------------------- +void DefaultLogger::set(Logger *logger) { + // enter the mutex here to avoid concurrency problems +#ifndef ASSIMP_BUILD_SINGLETHREADED + std::lock_guard<std::mutex> lock(loggerMutex); +#endif + + if (nullptr == logger) { + logger = &s_pNullLogger; + } + if (nullptr != m_pLogger && !isNullLogger()) { + delete m_pLogger; + } + + DefaultLogger::m_pLogger = logger; +} + +// ---------------------------------------------------------------------------------- +bool DefaultLogger::isNullLogger() { + return m_pLogger == &s_pNullLogger; +} + +// ---------------------------------------------------------------------------------- +Logger *DefaultLogger::get() { + return m_pLogger; +} + +// ---------------------------------------------------------------------------------- +// Kills the only instance +void DefaultLogger::kill() { + // enter the mutex here to avoid concurrency problems +#ifndef ASSIMP_BUILD_SINGLETHREADED + std::lock_guard<std::mutex> lock(loggerMutex); +#endif + + if (m_pLogger == &s_pNullLogger) { + return; + } + delete m_pLogger; + m_pLogger = &s_pNullLogger; +} + +// ---------------------------------------------------------------------------------- +// Debug message +void DefaultLogger::OnDebug(const char *message) { + if (m_Severity < Logger::DEBUGGING) { + return; + } + + static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; + char msg[Size]; + ai_snprintf(msg, Size, "Debug, T%u: %s", GetThreadID(), message); + + WriteToStreams(msg, Logger::Debugging); +} + +// Verbose debug message +void DefaultLogger::OnVerboseDebug(const char *message) { + if (m_Severity < Logger::VERBOSE) { + return; + } + + static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; + char msg[Size]; + ai_snprintf(msg, Size, "Debug, T%u: %s", GetThreadID(), message); + + WriteToStreams(msg, Logger::Debugging); +} + +// ---------------------------------------------------------------------------------- +// Logs an info +void DefaultLogger::OnInfo(const char *message) { + static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; + char msg[Size]; + ai_snprintf(msg, Size, "Info, T%u: %s", GetThreadID(), message); + + WriteToStreams(msg, Logger::Info); +} + +// ---------------------------------------------------------------------------------- +// Logs a warning +void DefaultLogger::OnWarn(const char *message) { + static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; + char msg[Size]; + ai_snprintf(msg, Size, "Warn, T%u: %s", GetThreadID(), message); + + WriteToStreams(msg, Logger::Warn); +} + +// ---------------------------------------------------------------------------------- +// Logs an error +void DefaultLogger::OnError(const char *message) { + static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; + char msg[Size]; + ai_snprintf(msg, Size, "Error, T%u: %s", GetThreadID(), message); + + WriteToStreams(msg, Logger::Err); +} + +// ---------------------------------------------------------------------------------- +// Will attach a new stream +bool DefaultLogger::attachStream(LogStream *pStream, unsigned int severity) { + if (nullptr == pStream) { + return false; + } + + if (0 == severity) { + severity = Logger::Info | Logger::Err | Logger::Warn | Logger::Debugging; + } + + for (StreamIt it = m_StreamArray.begin(); + it != m_StreamArray.end(); + ++it) { + if ((*it)->m_pStream == pStream) { + (*it)->m_uiErrorSeverity |= severity; + return true; + } + } + + LogStreamInfo *pInfo = new LogStreamInfo(severity, pStream); + m_StreamArray.push_back(pInfo); + return true; +} + +// ---------------------------------------------------------------------------------- +// Detach a stream +bool DefaultLogger::detachStream(LogStream *pStream, unsigned int severity) { + if (nullptr == pStream) { + return false; + } + + if (0 == severity) { + severity = SeverityAll; + } + + bool res(false); + for (StreamIt it = m_StreamArray.begin(); it != m_StreamArray.end(); ++it) { + if ((*it)->m_pStream == pStream) { + (*it)->m_uiErrorSeverity &= ~severity; + if ((*it)->m_uiErrorSeverity == 0) { + // don't delete the underlying stream 'cause the caller gains ownership again + (**it).m_pStream = nullptr; + delete *it; + m_StreamArray.erase(it); + res = true; + break; + } + return true; + } + } + return res; +} + +// ---------------------------------------------------------------------------------- +// Constructor +DefaultLogger::DefaultLogger(LogSeverity severity) : + Logger(severity), noRepeatMsg(false), lastLen(0) { + lastMsg[0] = '\0'; +} + +// ---------------------------------------------------------------------------------- +// Destructor +DefaultLogger::~DefaultLogger() { + for (StreamIt it = m_StreamArray.begin(); it != m_StreamArray.end(); ++it) { + // also frees the underlying stream, we are its owner. + delete *it; + } +} + +// ---------------------------------------------------------------------------------- +// Writes message to stream +void DefaultLogger::WriteToStreams(const char *message, ErrorSeverity ErrorSev) { + ai_assert(nullptr != message); + + // Check whether this is a repeated message + auto thisLen = ::strlen(message); + if (thisLen == lastLen - 1 && !::strncmp(message, lastMsg, lastLen - 1)) { + if (!noRepeatMsg) { + noRepeatMsg = true; + message = "Skipping one or more lines with the same contents\n"; + } + return; + } else { + // append a new-line character to the message to be printed + lastLen = thisLen; + ::memcpy(lastMsg, message, lastLen + 1); + ::strcat(lastMsg + lastLen, "\n"); + + message = lastMsg; + noRepeatMsg = false; + ++lastLen; + } + for (ConstStreamIt it = m_StreamArray.begin(); + it != m_StreamArray.end(); + ++it) { + if (ErrorSev & (*it)->m_uiErrorSeverity) + (*it)->m_pStream->write(message); + } +} + +// ---------------------------------------------------------------------------------- +// Returns thread id, if not supported only a zero will be returned. +unsigned int DefaultLogger::GetThreadID() { + // fixme: we can get this value via std::threads + // std::this_thread::get_id().hash() returns a (big) size_t, not sure if this is useful in this case. +#ifdef WIN32 + return (unsigned int)::GetCurrentThreadId(); +#else + return 0; // not supported +#endif +} + +// ---------------------------------------------------------------------------------- + +} // namespace Assimp diff --git a/libs/assimp/code/Common/DefaultProgressHandler.h b/libs/assimp/code/Common/DefaultProgressHandler.h new file mode 100644 index 0000000..ac1bb68 --- /dev/null +++ b/libs/assimp/code/Common/DefaultProgressHandler.h @@ -0,0 +1,66 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 ProgressHandler.hpp + * @brief Abstract base class 'ProgressHandler'. + */ +#ifndef INCLUDED_AI_DEFAULTPROGRESSHANDLER_H +#define INCLUDED_AI_DEFAULTPROGRESSHANDLER_H + +#include <assimp/ProgressHandler.hpp> + +namespace Assimp { + +// ------------------------------------------------------------------------------------ +/** + * @brief Internal default implementation of the #ProgressHandler interface. + */ +class DefaultProgressHandler : public ProgressHandler { +public: + /// @brief Ignores the update callback. + bool Update(float) override { + return false; + } +}; + +} // Namespace Assimp + +#endif // INCLUDED_AI_DEFAULTPROGRESSHANDLER_H diff --git a/libs/assimp/code/Common/Exceptional.cpp b/libs/assimp/code/Common/Exceptional.cpp new file mode 100644 index 0000000..b25281a --- /dev/null +++ b/libs/assimp/code/Common/Exceptional.cpp @@ -0,0 +1,52 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Exceptional.cpp + +Implementations of the exception classes. + +*/ + +#include <assimp/Exceptional.h> +#include <assimp/TinyFormatter.h> + +DeadlyErrorBase::DeadlyErrorBase(Assimp::Formatter::format f) : + runtime_error(std::string(f)){} diff --git a/libs/assimp/code/Common/Exporter.cpp b/libs/assimp/code/Common/Exporter.cpp new file mode 100644 index 0000000..9a4e2cf --- /dev/null +++ b/libs/assimp/code/Common/Exporter.cpp @@ -0,0 +1,695 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Exporter.cpp + +Assimp export interface. While it's public interface bears many similarities +to the import interface (in fact, it is largely symmetric), the internal +implementations differs a lot. Exporters are considered stateless and are +simple callbacks which we maintain in a global list along with their +description strings. + +Here we implement only the C++ interface (Assimp::Exporter). +*/ + +#ifndef ASSIMP_BUILD_NO_EXPORT + +#include <assimp/BlobIOSystem.h> +#include <assimp/SceneCombiner.h> +#include <assimp/DefaultIOSystem.h> +#include <assimp/Exporter.hpp> +#include <assimp/mesh.h> +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <assimp/Exceptional.h> + +#include "Common/DefaultProgressHandler.h" +#include "Common/BaseProcess.h" +#include "Common/ScenePrivate.h" +#include "PostProcessing/CalcTangentsProcess.h" +#include "PostProcessing/MakeVerboseFormat.h" +#include "PostProcessing/JoinVerticesProcess.h" +#include "PostProcessing/ConvertToLHProcess.h" +#include "PostProcessing/PretransformVertices.h" + +#include <memory> + +namespace Assimp { + +#ifdef _MSC_VER +# pragma warning( disable : 4800 ) +#endif // _MSC_VER + + +// PostStepRegistry.cpp +void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out); + +// ------------------------------------------------------------------------------------------------ +// Exporter worker function prototypes. Do not use const, because some exporter need to convert +// the scene temporary +#ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER +void ExportSceneCollada(const char*,IOSystem*, const aiScene*, const ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_X_EXPORTER +void ExportSceneXFile(const char*,IOSystem*, const aiScene*, const ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_STEP_EXPORTER +void ExportSceneStep(const char*,IOSystem*, const aiScene*, const ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER +void ExportSceneObj(const char*,IOSystem*, const aiScene*, const ExportProperties*); +void ExportSceneObjNoMtl(const char*,IOSystem*, const aiScene*, const ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_STL_EXPORTER +void ExportSceneSTL(const char*,IOSystem*, const aiScene*, const ExportProperties*); +void ExportSceneSTLBinary(const char*,IOSystem*, const aiScene*, const ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_PLY_EXPORTER +void ExportScenePly(const char*,IOSystem*, const aiScene*, const ExportProperties*); +void ExportScenePlyBinary(const char*, IOSystem*, const aiScene*, const ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_3DS_EXPORTER +void ExportScene3DS(const char*, IOSystem*, const aiScene*, const ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER +void ExportSceneGLTF(const char*, IOSystem*, const aiScene*, const ExportProperties*); +void ExportSceneGLB(const char*, IOSystem*, const aiScene*, const ExportProperties*); +void ExportSceneGLTF2(const char*, IOSystem*, const aiScene*, const ExportProperties*); +void ExportSceneGLB2(const char*, IOSystem*, const aiScene*, const ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER +void ExportSceneAssbin(const char*, IOSystem*, const aiScene*, const ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER +void ExportSceneAssxml(const char*, IOSystem*, const aiScene*, const ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_X3D_EXPORTER +void ExportSceneX3D(const char*, IOSystem*, const aiScene*, const ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER +void ExportSceneFBX(const char*, IOSystem*, const aiScene*, const ExportProperties*); +void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_3MF_EXPORTER +void ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* ); +#endif +#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER +void ExportSceneM3D(const char*, IOSystem*, const aiScene*, const ExportProperties*); +void ExportSceneM3DA(const char*, IOSystem*, const aiScene*, const ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER +void ExportAssimp2Json(const char* , IOSystem*, const aiScene* , const Assimp::ExportProperties*); +#endif +#ifndef ASSIMP_BUILD_NO_PBRT_EXPORTER +void ExportScenePbrt(const char*, IOSystem*, const aiScene*, const ExportProperties*); +#endif + +static void setupExporterArray(std::vector<Exporter::ExportFormatEntry> &exporters) { + (void)exporters; + +#ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("collada", "COLLADA - Digital Asset Exchange Schema", "dae", &ExportSceneCollada)); +#endif + +#ifndef ASSIMP_BUILD_NO_X_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("x", "X Files", "x", &ExportSceneXFile, + aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs)); +#endif + +#ifndef ASSIMP_BUILD_NO_STEP_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("stp", "Step Files", "stp", &ExportSceneStep, 0)); +#endif + +#ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("obj", "Wavefront OBJ format", "obj", &ExportSceneObj, + aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */)); + exporters.push_back(Exporter::ExportFormatEntry("objnomtl", "Wavefront OBJ format without material file", "obj", &ExportSceneObjNoMtl, + aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */)); +#endif + +#ifndef ASSIMP_BUILD_NO_STL_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("stl", "Stereolithography", "stl", &ExportSceneSTL, + aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices)); + exporters.push_back(Exporter::ExportFormatEntry("stlb", "Stereolithography (binary)", "stl", &ExportSceneSTLBinary, + aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices)); +#endif + +#ifndef ASSIMP_BUILD_NO_PLY_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("ply", "Stanford Polygon Library", "ply", &ExportScenePly, + aiProcess_PreTransformVertices)); + exporters.push_back(Exporter::ExportFormatEntry("plyb", "Stanford Polygon Library (binary)", "ply", &ExportScenePlyBinary, + aiProcess_PreTransformVertices)); +#endif + +#ifndef ASSIMP_BUILD_NO_3DS_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("3ds", "Autodesk 3DS (legacy)", "3ds", &ExportScene3DS, + aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_JoinIdenticalVertices)); +#endif + +#if !defined(ASSIMP_BUILD_NO_GLTF_EXPORTER) && !defined(ASSIMP_BUILD_NO_GLTF2_EXPORTER) + exporters.push_back(Exporter::ExportFormatEntry("gltf2", "GL Transmission Format v. 2", "gltf", &ExportSceneGLTF2, + aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType)); + exporters.push_back(Exporter::ExportFormatEntry("glb2", "GL Transmission Format v. 2 (binary)", "glb", &ExportSceneGLB2, + aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType)); +#endif + +#if !defined(ASSIMP_BUILD_NO_GLTF_EXPORTER) && !defined(ASSIMP_BUILD_NO_GLTF1_EXPORTER) + exporters.push_back(Exporter::ExportFormatEntry("gltf", "GL Transmission Format", "gltf", &ExportSceneGLTF, + aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType)); + exporters.push_back(Exporter::ExportFormatEntry("glb", "GL Transmission Format (binary)", "glb", &ExportSceneGLB, + aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType)); +#endif + +#ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("assbin", "Assimp Binary File", "assbin", &ExportSceneAssbin, 0)); +#endif + +#ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("assxml", "Assimp XML Document", "assxml", &ExportSceneAssxml, 0)); +#endif + +#ifndef ASSIMP_BUILD_NO_X3D_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("x3d", "Extensible 3D", "x3d", &ExportSceneX3D, 0)); +#endif + +#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("fbx", "Autodesk FBX (binary)", "fbx", &ExportSceneFBX, 0)); + exporters.push_back(Exporter::ExportFormatEntry("fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0)); +#endif + +#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("m3d", "Model 3D (binary)", "m3d", &ExportSceneM3D, 0)); + exporters.push_back(Exporter::ExportFormatEntry("m3da", "Model 3D (ascii)", "a3d", &ExportSceneM3DA, 0)); +#endif + +#ifndef ASSIMP_BUILD_NO_3MF_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0)); +#endif + +#ifndef ASSIMP_BUILD_NO_PBRT_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("pbrt", "pbrt-v4 scene description file", "pbrt", &ExportScenePbrt, aiProcess_Triangulate | aiProcess_SortByPType)); +#endif + +#ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("assjson", "Assimp JSON Document", "json", &ExportAssimp2Json, 0)); +#endif +} + +class ExporterPimpl { +public: + ExporterPimpl() + : blob() + , mIOSystem(new Assimp::DefaultIOSystem()) + , mIsDefaultIOHandler(true) + , mProgressHandler( nullptr ) + , mIsDefaultProgressHandler( true ) + , mPostProcessingSteps() + , mError() + , mExporters() { + GetPostProcessingStepInstanceList(mPostProcessingSteps); + + // grab all built-in exporters + setupExporterArray(mExporters); + } + + ~ExporterPimpl() { + delete blob; + + // Delete all post-processing plug-ins + for( unsigned int a = 0; a < mPostProcessingSteps.size(); a++) { + delete mPostProcessingSteps[a]; + } + delete mProgressHandler; + } + +public: + aiExportDataBlob* blob; + std::shared_ptr< Assimp::IOSystem > mIOSystem; + bool mIsDefaultIOHandler; + + /** The progress handler */ + ProgressHandler *mProgressHandler; + bool mIsDefaultProgressHandler; + + /** Post processing steps we can apply at the imported data. */ + std::vector< BaseProcess* > mPostProcessingSteps; + + /** Last fatal export error */ + std::string mError; + + /** Exporters, this includes those registered using #Assimp::Exporter::RegisterExporter */ + std::vector<Exporter::ExportFormatEntry> mExporters; +}; + +} // end of namespace Assimp + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +Exporter :: Exporter() +: pimpl(new ExporterPimpl()) { + pimpl->mProgressHandler = new DefaultProgressHandler(); +} + +// ------------------------------------------------------------------------------------------------ +Exporter::~Exporter() { + ai_assert(nullptr != pimpl); + FreeBlob(); + delete pimpl; +} + +// ------------------------------------------------------------------------------------------------ +void Exporter::SetIOHandler( IOSystem* pIOHandler) { + ai_assert(nullptr != pimpl); + pimpl->mIsDefaultIOHandler = !pIOHandler; + pimpl->mIOSystem.reset(pIOHandler); +} + +// ------------------------------------------------------------------------------------------------ +IOSystem* Exporter::GetIOHandler() const { + ai_assert(nullptr != pimpl); + return pimpl->mIOSystem.get(); +} + +// ------------------------------------------------------------------------------------------------ +bool Exporter::IsDefaultIOHandler() const { + ai_assert(nullptr != pimpl); + return pimpl->mIsDefaultIOHandler; +} + +// ------------------------------------------------------------------------------------------------ +void Exporter::SetProgressHandler(ProgressHandler* pHandler) { + ai_assert(nullptr != pimpl); + + if ( nullptr == pHandler) { + // Release pointer in the possession of the caller + pimpl->mProgressHandler = new DefaultProgressHandler(); + pimpl->mIsDefaultProgressHandler = true; + return; + } + + if (pimpl->mProgressHandler == pHandler) { + return; + } + + delete pimpl->mProgressHandler; + pimpl->mProgressHandler = pHandler; + pimpl->mIsDefaultProgressHandler = false; +} + +// ------------------------------------------------------------------------------------------------ +const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const char* pFormatId, + unsigned int pPreprocessing, const ExportProperties* pProperties) { + ai_assert(nullptr != pimpl); + if (pimpl->blob) { + delete pimpl->blob; + pimpl->blob = nullptr; + } + + auto baseName = pProperties ? pProperties->GetPropertyString(AI_CONFIG_EXPORT_BLOB_NAME, AI_BLOBIO_MAGIC) : AI_BLOBIO_MAGIC; + + std::shared_ptr<IOSystem> old = pimpl->mIOSystem; + BlobIOSystem *blobio = new BlobIOSystem(baseName); + pimpl->mIOSystem = std::shared_ptr<IOSystem>( blobio ); + + if (AI_SUCCESS != Export(pScene,pFormatId,blobio->GetMagicFileName(), pPreprocessing, pProperties)) { + pimpl->mIOSystem = old; + return nullptr; + } + + pimpl->blob = blobio->GetBlobChain(); + pimpl->mIOSystem = old; + + return pimpl->blob; +} + +// ------------------------------------------------------------------------------------------------ +aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const char* pPath, + unsigned int pPreprocessing, const ExportProperties* pProperties) { + ASSIMP_BEGIN_EXCEPTION_REGION(); + ai_assert(nullptr != pimpl); + // when they create scenes from scratch, users will likely create them not in verbose + // format. They will likely not be aware that there is a flag in the scene to indicate + // this, however. To avoid surprises and bug reports, we check for duplicates in + // meshes upfront. + const bool is_verbose_format = !(pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) || MakeVerboseFormatProcess::IsVerboseFormat(pScene); + + pimpl->mProgressHandler->UpdateFileWrite(0, 4); + + pimpl->mError = ""; + for (size_t i = 0; i < pimpl->mExporters.size(); ++i) { + const Exporter::ExportFormatEntry& exp = pimpl->mExporters[i]; + if (!strcmp(exp.mDescription.id,pFormatId)) { + try { + // Always create a full copy of the scene. We might optimize this one day, + // but for now it is the most pragmatic way. + aiScene* scenecopy_tmp = nullptr; + SceneCombiner::CopyScene(&scenecopy_tmp,pScene); + + pimpl->mProgressHandler->UpdateFileWrite(1, 4); + + std::unique_ptr<aiScene> scenecopy(scenecopy_tmp); + const ScenePrivateData* const priv = ScenePriv(pScene); + + // steps that are not idempotent, i.e. we might need to run them again, usually to get back to the + // original state before the step was applied first. When checking which steps we don't need + // to run, those are excluded. + const unsigned int nonIdempotentSteps = aiProcess_FlipWindingOrder | aiProcess_FlipUVs | aiProcess_MakeLeftHanded; + + // Erase all pp steps that were already applied to this scene + const unsigned int pp = (exp.mEnforcePP | pPreprocessing) & ~(priv && !priv->mIsCopy + ? (priv->mPPStepsApplied & ~nonIdempotentSteps) + : 0u); + + // If no extra post-processing was specified, and we obtained this scene from an + // Assimp importer, apply the reverse steps automatically. + // TODO: either drop this, or document it. Otherwise it is just a bad surprise. + //if (!pPreprocessing && priv) { + // pp |= (nonIdempotentSteps & priv->mPPStepsApplied); + //} + + // If the input scene is not in verbose format, but there is at least post-processing step that relies on it, + // we need to run the MakeVerboseFormat step first. + bool must_join_again = false; + if (!is_verbose_format) { + bool verbosify = false; + for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++) { + BaseProcess* const p = pimpl->mPostProcessingSteps[a]; + + if (p->IsActive(pp) && p->RequireVerboseFormat()) { + verbosify = true; + break; + } + } + + if (verbosify || (exp.mEnforcePP & aiProcess_JoinIdenticalVertices)) { + ASSIMP_LOG_DEBUG("export: Scene data not in verbose format, applying MakeVerboseFormat step first"); + + MakeVerboseFormatProcess proc; + proc.Execute(scenecopy.get()); + + if(!(exp.mEnforcePP & aiProcess_JoinIdenticalVertices)) { + must_join_again = true; + } + } + } + + pimpl->mProgressHandler->UpdateFileWrite(2, 4); + + if (pp) { + // the three 'conversion' steps need to be executed first because all other steps rely on the standard data layout + { + FlipWindingOrderProcess step; + if (step.IsActive(pp)) { + step.Execute(scenecopy.get()); + } + } + + { + FlipUVsProcess step; + if (step.IsActive(pp)) { + step.Execute(scenecopy.get()); + } + } + + { + MakeLeftHandedProcess step; + if (step.IsActive(pp)) { + step.Execute(scenecopy.get()); + } + } + + bool exportPointCloud(false); + if (nullptr != pProperties) { + exportPointCloud = pProperties->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS); + } + + // dispatch other processes + for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++) { + BaseProcess* const p = pimpl->mPostProcessingSteps[a]; + + if (p->IsActive(pp) + && !dynamic_cast<FlipUVsProcess*>(p) + && !dynamic_cast<FlipWindingOrderProcess*>(p) + && !dynamic_cast<MakeLeftHandedProcess*>(p)) { + if (dynamic_cast<PretransformVertices*>(p) && exportPointCloud) { + continue; + } + p->Execute(scenecopy.get()); + } + } + ScenePrivateData* const privOut = ScenePriv(scenecopy.get()); + ai_assert(nullptr != privOut); + + privOut->mPPStepsApplied |= pp; + } + + pimpl->mProgressHandler->UpdateFileWrite(3, 4); + + if(must_join_again) { + JoinVerticesProcess proc; + proc.Execute(scenecopy.get()); + } + + ExportProperties emptyProperties; // Never pass nullptr ExportProperties so Exporters don't have to worry. + ExportProperties* pProp = pProperties ? (ExportProperties*)pProperties : &emptyProperties; + pProp->SetPropertyBool("bJoinIdenticalVertices", pp & aiProcess_JoinIdenticalVertices); + exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProp); + + pimpl->mProgressHandler->UpdateFileWrite(4, 4); + } catch (DeadlyExportError& err) { + pimpl->mError = err.what(); + return AI_FAILURE; + } + return AI_SUCCESS; + } + } + + pimpl->mError = std::string("Found no exporter to handle this file format: ") + pFormatId; + ASSIMP_END_EXCEPTION_REGION(aiReturn); + + return AI_FAILURE; +} + +// ------------------------------------------------------------------------------------------------ +const char* Exporter::GetErrorString() const { + ai_assert(nullptr != pimpl); + return pimpl->mError.c_str(); +} + +// ------------------------------------------------------------------------------------------------ +void Exporter::FreeBlob() { + ai_assert(nullptr != pimpl); + delete pimpl->blob; + pimpl->blob = nullptr; + + pimpl->mError = ""; +} + +// ------------------------------------------------------------------------------------------------ +const aiExportDataBlob* Exporter::GetBlob() const { + ai_assert(nullptr != pimpl); + return pimpl->blob; +} + +// ------------------------------------------------------------------------------------------------ +const aiExportDataBlob* Exporter::GetOrphanedBlob() const { + ai_assert(nullptr != pimpl); + const aiExportDataBlob *tmp = pimpl->blob; + pimpl->blob = nullptr; + return tmp; +} + +// ------------------------------------------------------------------------------------------------ +size_t Exporter::GetExportFormatCount() const { + ai_assert(nullptr != pimpl); + return pimpl->mExporters.size(); +} + +// ------------------------------------------------------------------------------------------------ +const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) const { + ai_assert(nullptr != pimpl); + if (index >= GetExportFormatCount()) { + return nullptr; + } + + // Return from static storage if the requested index is built-in. + if (index < pimpl->mExporters.size()) { + return &pimpl->mExporters[index].mDescription; + } + + return &pimpl->mExporters[index].mDescription; +} + +// ------------------------------------------------------------------------------------------------ +aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) { + ai_assert(nullptr != pimpl); + for (const ExportFormatEntry &e : pimpl->mExporters) { + if (!strcmp(e.mDescription.id,desc.mDescription.id)) { + return aiReturn_FAILURE; + } + } + + pimpl->mExporters.push_back(desc); + return aiReturn_SUCCESS; +} + +// ------------------------------------------------------------------------------------------------ +void Exporter::UnregisterExporter(const char* id) { + ai_assert(nullptr != pimpl); + for (std::vector<ExportFormatEntry>::iterator it = pimpl->mExporters.begin(); + it != pimpl->mExporters.end(); ++it) { + if (!strcmp((*it).mDescription.id,id)) { + pimpl->mExporters.erase(it); + break; + } + } +} + +// ------------------------------------------------------------------------------------------------ +ExportProperties::ExportProperties() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +ExportProperties::ExportProperties(const ExportProperties &other) +: mIntProperties(other.mIntProperties) +, mFloatProperties(other.mFloatProperties) +, mStringProperties(other.mStringProperties) +, mMatrixProperties(other.mMatrixProperties) +, mCallbackProperties(other.mCallbackProperties){ + // empty +} + +bool ExportProperties::SetPropertyCallback(const char *szName, const std::function<void *(void *)> &f) { + return SetGenericProperty<std::function<void *(void *)>>(mCallbackProperties, szName, f); +} + +std::function<void *(void *)> ExportProperties::GetPropertyCallback(const char *szName) const { + return GetGenericProperty<std::function<void *(void *)>>(mCallbackProperties, szName, 0); +} + +bool ExportProperties::HasPropertyCallback(const char *szName) const { + return HasGenericProperty<std::function<void *(void *)>>(mCallbackProperties, szName); +} + +// ------------------------------------------------------------------------------------------------ +// Set a configuration property +bool ExportProperties::SetPropertyInteger(const char* szName, int iValue) { + return SetGenericProperty<int>(mIntProperties, szName,iValue); +} + +// ------------------------------------------------------------------------------------------------ +// Set a configuration property +bool ExportProperties::SetPropertyFloat(const char* szName, ai_real iValue) { + return SetGenericProperty<ai_real>(mFloatProperties, szName,iValue); +} + +// ------------------------------------------------------------------------------------------------ +// Set a configuration property +bool ExportProperties::SetPropertyString(const char* szName, const std::string& value) { + return SetGenericProperty<std::string>(mStringProperties, szName,value); +} + +// ------------------------------------------------------------------------------------------------ +// Set a configuration property +bool ExportProperties::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value) { + return SetGenericProperty<aiMatrix4x4>(mMatrixProperties, szName,value); +} + +// ------------------------------------------------------------------------------------------------ +// Get a configuration property +int ExportProperties::GetPropertyInteger(const char* szName, int iErrorReturn /*= 0xffffffff*/) const { + return GetGenericProperty<int>(mIntProperties,szName,iErrorReturn); +} + +// ------------------------------------------------------------------------------------------------ +// Get a configuration property +ai_real ExportProperties::GetPropertyFloat(const char* szName, ai_real iErrorReturn /*= 10e10*/) const { + return GetGenericProperty<ai_real>(mFloatProperties,szName,iErrorReturn); +} + +// ------------------------------------------------------------------------------------------------ +// Get a configuration property +const std::string ExportProperties::GetPropertyString(const char* szName, + const std::string& iErrorReturn /*= ""*/) const { + return GetGenericProperty<std::string>(mStringProperties,szName,iErrorReturn); +} + +// ------------------------------------------------------------------------------------------------ +// Has a configuration property +const aiMatrix4x4 ExportProperties::GetPropertyMatrix(const char* szName, + const aiMatrix4x4& iErrorReturn /*= aiMatrix4x4()*/) const { + return GetGenericProperty<aiMatrix4x4>(mMatrixProperties,szName,iErrorReturn); +} + +// ------------------------------------------------------------------------------------------------ +// Has a configuration property +bool ExportProperties::HasPropertyInteger(const char* szName) const { + return HasGenericProperty<int>(mIntProperties, szName); +} + +// ------------------------------------------------------------------------------------------------ +// Has a configuration property +bool ExportProperties::HasPropertyBool(const char* szName) const { + return HasGenericProperty<int>(mIntProperties, szName); +} + +// ------------------------------------------------------------------------------------------------ +// Has a configuration property +bool ExportProperties::HasPropertyFloat(const char* szName) const { + return HasGenericProperty<ai_real>(mFloatProperties, szName); +} + +// ------------------------------------------------------------------------------------------------ +// Has a configuration property +bool ExportProperties::HasPropertyString(const char* szName) const { + return HasGenericProperty<std::string>(mStringProperties, szName); +} + +// ------------------------------------------------------------------------------------------------ +// Has a configuration property +bool ExportProperties::HasPropertyMatrix(const char* szName) const { + return HasGenericProperty<aiMatrix4x4>(mMatrixProperties, szName); +} + + +#endif // !ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/Common/FileLogStream.h b/libs/assimp/code/Common/FileLogStream.h new file mode 100644 index 0000000..3345414 --- /dev/null +++ b/libs/assimp/code/Common/FileLogStream.h @@ -0,0 +1,100 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FileLofStream.h +*/ +#ifndef ASSIMP_FILELOGSTREAM_H_INC +#define ASSIMP_FILELOGSTREAM_H_INC + +#include <assimp/DefaultIOSystem.h> +#include <assimp/IOStream.hpp> +#include <assimp/LogStream.hpp> + +namespace Assimp { + +// ---------------------------------------------------------------------------------- +/** @class FileLogStream + * @brief Logstream to write into a file. + */ +class FileLogStream : public LogStream { +public: + FileLogStream(const char *file, IOSystem *io = nullptr); + ~FileLogStream(); + void write(const char *message); + +private: + IOStream *m_pStream; +}; + +// ---------------------------------------------------------------------------------- +// Constructor +inline FileLogStream::FileLogStream(const char *file, IOSystem *io) : + m_pStream(nullptr) { + if (!file || 0 == *file) + return; + + // If no IOSystem is specified: take a default one + if (!io) { + DefaultIOSystem FileSystem; + m_pStream = FileSystem.Open(file, "wt"); + } else + m_pStream = io->Open(file, "wt"); +} + +// ---------------------------------------------------------------------------------- +// Destructor +inline FileLogStream::~FileLogStream() { + // The virtual d'tor should destroy the underlying file + delete m_pStream; +} + +// ---------------------------------------------------------------------------------- +// Write method +inline void FileLogStream::write(const char *message) { + if (m_pStream != nullptr) { + m_pStream->Write(message, sizeof(char), ::strlen(message)); + m_pStream->Flush(); + } +} + +// ---------------------------------------------------------------------------------- +} // namespace Assimp + +#endif // !! ASSIMP_FILELOGSTREAM_H_INC diff --git a/libs/assimp/code/Common/FileSystemFilter.h b/libs/assimp/code/Common/FileSystemFilter.h new file mode 100644 index 0000000..81576aa --- /dev/null +++ b/libs/assimp/code/Common/FileSystemFilter.h @@ -0,0 +1,346 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, 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 FileSystemFilter.h + * Implements a filter system to filter calls to Exists() and Open() + * in order to improve the success rate of file opening ... + */ +#pragma once +#ifndef AI_FILESYSTEMFILTER_H_INC +#define AI_FILESYSTEMFILTER_H_INC + +#include <assimp/IOSystem.hpp> +#include <assimp/DefaultLogger.hpp> +#include <assimp/fast_atof.h> +#include <assimp/ParsingUtils.h> + +namespace Assimp { + +inline bool IsHex(char s) { + return (s>='0' && s<='9') || (s>='a' && s<='f') || (s>='A' && s<='F'); +} + +// --------------------------------------------------------------------------- +/** File system filter + */ +class FileSystemFilter : public IOSystem +{ +public: + /** Constructor. */ + FileSystemFilter(const std::string& file, IOSystem* old) + : mWrapped (old) + , mSrc_file(file) + , mSep(mWrapped->getOsSeparator()) { + ai_assert(nullptr != mWrapped); + + // Determine base directory + mBase = mSrc_file; + std::string::size_type ss2; + if (std::string::npos != (ss2 = mBase.find_last_of("\\/"))) { + mBase.erase(ss2,mBase.length()-ss2); + } else { + mBase = std::string(); + } + + // make sure the directory is terminated properly + char s; + + if ( mBase.empty() ) { + mBase = "."; + mBase += getOsSeparator(); + } else if ((s = *(mBase.end()-1)) != '\\' && s != '/') { + mBase += getOsSeparator(); + } + + DefaultLogger::get()->info("Import root directory is \'", mBase, "\'"); + } + + /** Destructor. */ + ~FileSystemFilter() { + // empty + } + + // ------------------------------------------------------------------- + /** Tests for the existence of a file at the given path. */ + bool Exists( const char* pFile) const { + ai_assert( nullptr != mWrapped ); + + std::string tmp = pFile; + + // Currently this IOSystem is also used to open THE ONE FILE. + if (tmp != mSrc_file) { + BuildPath(tmp); + Cleanup(tmp); + } + + return mWrapped->Exists(tmp); + } + + // ------------------------------------------------------------------- + /** Returns the directory separator. */ + char getOsSeparator() const { + return mSep; + } + + // ------------------------------------------------------------------- + /** Open a new file with a given path. */ + IOStream* Open( const char* pFile, const char* pMode = "rb") { + ai_assert( nullptr != mWrapped ); + if ( nullptr == pFile || nullptr == pMode ) { + return nullptr; + } + + ai_assert( nullptr != pFile ); + ai_assert( nullptr != pMode ); + + // First try the unchanged path + IOStream* s = mWrapped->Open(pFile,pMode); + + if (nullptr == s) { + std::string tmp = pFile; + + // Try to convert between absolute and relative paths + BuildPath(tmp); + s = mWrapped->Open(tmp,pMode); + + if (nullptr == s) { + // Finally, look for typical issues with paths + // and try to correct them. This is our last + // resort. + tmp = pFile; + Cleanup(tmp); + BuildPath(tmp); + s = mWrapped->Open(tmp,pMode); + } + } + + return s; + } + + // ------------------------------------------------------------------- + /** Closes the given file and releases all resources associated with it. */ + void Close( IOStream* pFile) { + ai_assert( nullptr != mWrapped ); + return mWrapped->Close(pFile); + } + + // ------------------------------------------------------------------- + /** Compare two paths */ + bool ComparePaths (const char* one, const char* second) const { + ai_assert( nullptr != mWrapped ); + return mWrapped->ComparePaths (one,second); + } + + // ------------------------------------------------------------------- + /** Pushes a new directory onto the directory stack. */ + bool PushDirectory(const std::string &path ) { + ai_assert( nullptr != mWrapped ); + return mWrapped->PushDirectory(path); + } + + // ------------------------------------------------------------------- + /** Returns the top directory from the stack. */ + const std::string &CurrentDirectory() const { + ai_assert( nullptr != mWrapped ); + return mWrapped->CurrentDirectory(); + } + + // ------------------------------------------------------------------- + /** Returns the number of directories stored on the stack. */ + size_t StackSize() const { + ai_assert( nullptr != mWrapped ); + return mWrapped->StackSize(); + } + + // ------------------------------------------------------------------- + /** Pops the top directory from the stack. */ + bool PopDirectory() { + ai_assert( nullptr != mWrapped ); + return mWrapped->PopDirectory(); + } + + // ------------------------------------------------------------------- + /** Creates an new directory at the given path. */ + bool CreateDirectory(const std::string &path) { + ai_assert( nullptr != mWrapped ); + return mWrapped->CreateDirectory(path); + } + + // ------------------------------------------------------------------- + /** Will change the current directory to the given path. */ + bool ChangeDirectory(const std::string &path) { + ai_assert( nullptr != mWrapped ); + return mWrapped->ChangeDirectory(path); + } + + // ------------------------------------------------------------------- + /** Delete file. */ + bool DeleteFile(const std::string &file) { + ai_assert( nullptr != mWrapped ); + return mWrapped->DeleteFile(file); + } + +private: + // ------------------------------------------------------------------- + /** Build a valid path from a given relative or absolute path. + */ + void BuildPath (std::string& in) const { + ai_assert( nullptr != mWrapped ); + // if we can already access the file, great. + if (in.length() < 3 || mWrapped->Exists(in)) { + return; + } + + // Determine whether this is a relative path (Windows-specific - most assets are packaged on Windows). + if (in[1] != ':') { + + // append base path and try + const std::string tmp = mBase + in; + if (mWrapped->Exists(tmp)) { + in = tmp; + return; + } + } + + // Chop of the file name and look in the model directory, if + // this fails try all sub paths of the given path, i.e. + // if the given path is foo/bar/something.lwo, try + // <base>/something.lwo + // <base>/bar/something.lwo + // <base>/foo/bar/something.lwo + std::string::size_type pos = in.rfind('/'); + if (std::string::npos == pos) { + pos = in.rfind('\\'); + } + + if (std::string::npos != pos) { + std::string tmp; + std::string::size_type last_dirsep = std::string::npos; + + while(true) { + tmp = mBase; + tmp += mSep; + + std::string::size_type dirsep = in.rfind('/', last_dirsep); + if (std::string::npos == dirsep) { + dirsep = in.rfind('\\', last_dirsep); + } + + if (std::string::npos == dirsep || dirsep == 0) { + // we did try this already. + break; + } + + last_dirsep = dirsep-1; + + tmp += in.substr(dirsep+1, in.length()-pos); + if (mWrapped->Exists(tmp)) { + in = tmp; + return; + } + } + } + + // hopefully the underlying file system has another few tricks to access this file ... + } + + // ------------------------------------------------------------------- + /** Cleanup the given path + */ + void Cleanup (std::string& in) const { + if(in.empty()) { + return; + } + + // Remove a very common issue when we're parsing file names: spaces at the + // beginning of the path. + char last = 0; + std::string::iterator it = in.begin(); + while (IsSpaceOrNewLine( *it ))++it; + if (it != in.begin()) { + in.erase(in.begin(),it+1); + } + + const char separator = getOsSeparator(); + for (it = in.begin(); it != in.end(); ++it) { + int remaining = std::distance(in.end(), it); + // Exclude :// and \\, which remain untouched. + // https://sourceforge.net/tracker/?func=detail&aid=3031725&group_id=226462&atid=1067632 + if (remaining >= 3 && !strncmp(&*it, "://", 3 )) { + it += 3; + continue; + } + if (it == in.begin() && remaining >= 2 && !strncmp(&*it, "\\\\", 2)) { + it += 2; + continue; + } + + // Cleanup path delimiters + if (*it == '/' || (*it) == '\\') { + *it = separator; + + // And we're removing double delimiters, frequent issue with + // incorrectly composited paths ... + if (last == *it) { + it = in.erase(it); + --it; + } + } else if (*it == '%' && in.end() - it > 2) { + // Hex sequence in URIs + if( IsHex((&*it)[0]) && IsHex((&*it)[1]) ) { + *it = HexOctetToDecimal(&*it); + it = in.erase(it+1,it+2); + --it; + } + } + + last = *it; + } + } + +private: + IOSystem *mWrapped; + std::string mSrc_file, mBase; + char mSep; +}; + +} //!ns Assimp + +#endif //AI_DEFAULTIOSYSTEM_H_INC diff --git a/libs/assimp/code/Common/IFF.h b/libs/assimp/code/Common/IFF.h new file mode 100644 index 0000000..91d7d48 --- /dev/null +++ b/libs/assimp/code/Common/IFF.h @@ -0,0 +1,102 @@ +// Definitions for the Interchange File Format (IFF) +// Alexander Gessler, 2006 +// Adapted to Assimp August 2008 + +#ifndef AI_IFF_H_INCLUDED +#define AI_IFF_H_INCLUDED + +#include <assimp/ByteSwapper.h> + +namespace Assimp { +namespace IFF { + +///////////////////////////////////////////////////////////////////////////////// +//! Describes an IFF chunk header +///////////////////////////////////////////////////////////////////////////////// +struct ChunkHeader +{ + //! Type of the chunk header - FourCC + uint32_t type; + + //! Length of the chunk data, in bytes + uint32_t length; +}; + + +///////////////////////////////////////////////////////////////////////////////// +//! Describes an IFF sub chunk header +///////////////////////////////////////////////////////////////////////////////// +struct SubChunkHeader +{ + //! Type of the chunk header - FourCC + uint32_t type; + + //! Length of the chunk data, in bytes + uint16_t length; +}; + + +#define AI_IFF_FOURCC(a,b,c,d) ((uint32_t) (((uint8_t)a << 24u) | \ + ((uint8_t)b << 16u) | ((uint8_t)c << 8u) | ((uint8_t)d))) + + +#define AI_IFF_FOURCC_FORM AI_IFF_FOURCC('F','O','R','M') + + +///////////////////////////////////////////////////////////////////////////////// +//! Load a chunk header +//! @param outFile Pointer to the file data - points to the chunk data afterwards +//! @return Copy of the chunk header +///////////////////////////////////////////////////////////////////////////////// +inline ChunkHeader LoadChunk(uint8_t*& outFile) +{ + ChunkHeader head; + ::memcpy(&head.type, outFile, 4); + outFile += 4; + ::memcpy(&head.length, outFile, 4); + outFile += 4; + AI_LSWAP4(head.length); + AI_LSWAP4(head.type); + return head; +} + +///////////////////////////////////////////////////////////////////////////////// +//! Load a sub chunk header +//! @param outFile Pointer to the file data - points to the chunk data afterwards +//! @return Copy of the sub chunk header +///////////////////////////////////////////////////////////////////////////////// +inline SubChunkHeader LoadSubChunk(uint8_t*& outFile) +{ + SubChunkHeader head; + ::memcpy(&head.type, outFile, 4); + outFile += 4; + ::memcpy(&head.length, outFile, 2); + outFile += 2; + AI_LSWAP2(head.length); + AI_LSWAP4(head.type); + return head; +} + +///////////////////////////////////////////////////////////////////////////////// +//! Read the file header and return the type of the file and its size +//! @param outFile Pointer to the file data. The buffer must at +//! least be 12 bytes large. +//! @param fileType Receives the type of the file +//! @return 0 if everything was OK, otherwise an error message +///////////////////////////////////////////////////////////////////////////////// +inline const char* ReadHeader(uint8_t* outFile, uint32_t& fileType) +{ + ChunkHeader head = LoadChunk(outFile); + if(AI_IFF_FOURCC_FORM != head.type) + { + return "The file is not an IFF file: FORM chunk is missing"; + } + ::memcpy(&fileType, outFile, 4); + AI_LSWAP4(fileType); + return 0; +} + + +}} + +#endif // !! AI_IFF_H_INCLUDED diff --git a/libs/assimp/code/Common/IOSystem.cpp b/libs/assimp/code/Common/IOSystem.cpp new file mode 100644 index 0000000..1e63827 --- /dev/null +++ b/libs/assimp/code/Common/IOSystem.cpp @@ -0,0 +1,53 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Default implementation of IOSystem using the standard C file functions */ + +#include <assimp/IOSystem.hpp> + +using namespace Assimp; + +const std::string &IOSystem::CurrentDirectory() const { + if ( m_pathStack.empty() ) { + static const std::string Dummy = std::string(); + return Dummy; + } + return m_pathStack[ m_pathStack.size()-1 ]; +} diff --git a/libs/assimp/code/Common/Importer.cpp b/libs/assimp/code/Common/Importer.cpp new file mode 100644 index 0000000..52b6097 --- /dev/null +++ b/libs/assimp/code/Common/Importer.cpp @@ -0,0 +1,1287 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Importer.cpp + * @brief Implementation of the CPP-API class #Importer + */ + +#include <assimp/version.h> +#include <assimp/config.h> +#include <assimp/importerdesc.h> + +// ------------------------------------------------------------------------------------------------ +/* Uncomment this line to prevent Assimp from catching unknown exceptions. + * + * Note that any Exception except DeadlyImportError may lead to + * undefined behaviour -> loaders could remain in an unusable state and + * further imports with the same Importer instance could fail/crash/burn ... + */ +// ------------------------------------------------------------------------------------------------ +#ifndef ASSIMP_BUILD_DEBUG +# define ASSIMP_CATCH_GLOBAL_EXCEPTIONS +#endif + +// ------------------------------------------------------------------------------------------------ +// Internal headers +// ------------------------------------------------------------------------------------------------ +#include "Common/Importer.h" +#include "Common/BaseProcess.h" +#include "Common/DefaultProgressHandler.h" +#include "PostProcessing/ProcessHelper.h" +#include "Common/ScenePreprocessor.h" +#include "Common/ScenePrivate.h" + +#include <assimp/BaseImporter.h> +#include <assimp/GenericProperty.h> +#include <assimp/MemoryIOWrapper.h> +#include <assimp/Profiler.h> +#include <assimp/TinyFormatter.h> +#include <assimp/Exceptional.h> +#include <assimp/Profiler.h> +#include <assimp/commonMetaData.h> + +#include <exception> +#include <set> +#include <memory> +#include <cctype> + +#include <assimp/DefaultIOStream.h> +#include <assimp/DefaultIOSystem.h> + +#ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS +# include "PostProcessing/ValidateDataStructure.h" +#endif + +using namespace Assimp::Profiling; +using namespace Assimp::Formatter; + +namespace Assimp { + // ImporterRegistry.cpp + void GetImporterInstanceList(std::vector< BaseImporter* >& out); + void DeleteImporterInstanceList(std::vector< BaseImporter* >& out); + + // PostStepRegistry.cpp + void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out); +} + +using namespace Assimp; +using namespace Assimp::Intern; + +// ------------------------------------------------------------------------------------------------ +// Intern::AllocateFromAssimpHeap serves as abstract base class. It overrides +// new and delete (and their array counterparts) of public API classes (e.g. Logger) to +// utilize our DLL heap. +// See http://www.gotw.ca/publications/mill15.htm +// ------------------------------------------------------------------------------------------------ +void* AllocateFromAssimpHeap::operator new ( size_t num_bytes) { + return ::operator new(num_bytes); +} + +void* AllocateFromAssimpHeap::operator new ( size_t num_bytes, const std::nothrow_t& ) throw() { + try { + return AllocateFromAssimpHeap::operator new( num_bytes ); + } + catch( ... ) { + return nullptr; + } +} + +void AllocateFromAssimpHeap::operator delete ( void* data) { + return ::operator delete(data); +} + +void* AllocateFromAssimpHeap::operator new[] ( size_t num_bytes) { + return ::operator new[](num_bytes); +} + +void* AllocateFromAssimpHeap::operator new[] ( size_t num_bytes, const std::nothrow_t& ) throw() { + try { + return AllocateFromAssimpHeap::operator new[]( num_bytes ); + } catch( ... ) { + return nullptr; + } +} + +void AllocateFromAssimpHeap::operator delete[] ( void* data) { + return ::operator delete[](data); +} + +// ------------------------------------------------------------------------------------------------ +// Importer constructor. +Importer::Importer() + : pimpl( new ImporterPimpl ) { + pimpl->mScene = nullptr; + pimpl->mErrorString = std::string(); + + // Allocate a default IO handler + pimpl->mIOHandler = new DefaultIOSystem; + pimpl->mIsDefaultHandler = true; + pimpl->bExtraVerbose = false; // disable extra verbose mode by default + + pimpl->mProgressHandler = new DefaultProgressHandler(); + pimpl->mIsDefaultProgressHandler = true; + + GetImporterInstanceList(pimpl->mImporter); + GetPostProcessingStepInstanceList(pimpl->mPostProcessingSteps); + + // Allocate a SharedPostProcessInfo object and store pointers to it in all post-process steps in the list. + pimpl->mPPShared = new SharedPostProcessInfo(); + for (std::vector<BaseProcess*>::iterator it = pimpl->mPostProcessingSteps.begin(); + it != pimpl->mPostProcessingSteps.end(); + ++it) { + + (*it)->SetSharedData(pimpl->mPPShared); + } +} + +// ------------------------------------------------------------------------------------------------ +// Destructor of Importer +Importer::~Importer() { + // Delete all import plugins + DeleteImporterInstanceList(pimpl->mImporter); + + // Delete all post-processing plug-ins + for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); ++a ) { + delete pimpl->mPostProcessingSteps[a]; + } + + // Delete the assigned IO and progress handler + delete pimpl->mIOHandler; + delete pimpl->mProgressHandler; + + // Kill imported scene. Destructor's should do that recursively + delete pimpl->mScene; + + // Delete shared post-processing data + delete pimpl->mPPShared; + + // and finally the pimpl itself + delete pimpl; +} + +// ------------------------------------------------------------------------------------------------ +// Register a custom post-processing step +aiReturn Importer::RegisterPPStep(BaseProcess* pImp) { + ai_assert( nullptr != pImp ); + + ASSIMP_BEGIN_EXCEPTION_REGION(); + + pimpl->mPostProcessingSteps.push_back(pImp); + ASSIMP_LOG_INFO("Registering custom post-processing step"); + + ASSIMP_END_EXCEPTION_REGION(aiReturn); + return AI_SUCCESS; +} + +// ------------------------------------------------------------------------------------------------ +// Register a custom loader plugin +aiReturn Importer::RegisterLoader(BaseImporter* pImp) { + ai_assert(nullptr != pImp); + + ASSIMP_BEGIN_EXCEPTION_REGION(); + + // -------------------------------------------------------------------- + // Check whether we would have two loaders for the same file extension + // This is absolutely OK, but we should warn the developer of the new + // loader that his code will probably never be called if the first + // loader is a bit too lazy in his file checking. + // -------------------------------------------------------------------- + std::set<std::string> st; + std::string baked; + pImp->GetExtensionList(st); + + for(std::set<std::string>::const_iterator it = st.begin(); it != st.end(); ++it) { + +#ifdef ASSIMP_BUILD_DEBUG + if (IsExtensionSupported(*it)) { + ASSIMP_LOG_WARN("The file extension ", *it, " is already in use"); + } +#endif + baked += *it; + } + + // add the loader + pimpl->mImporter.push_back(pImp); + ASSIMP_LOG_INFO("Registering custom importer for these file extensions: ", baked); + ASSIMP_END_EXCEPTION_REGION(aiReturn); + + return AI_SUCCESS; +} + +// ------------------------------------------------------------------------------------------------ +// Unregister a custom loader plugin +aiReturn Importer::UnregisterLoader(BaseImporter* pImp) { + if(!pImp) { + // unregistering a nullptr importer is no problem for us ... really! + return AI_SUCCESS; + } + + ASSIMP_BEGIN_EXCEPTION_REGION(); + std::vector<BaseImporter*>::iterator it = std::find(pimpl->mImporter.begin(), + pimpl->mImporter.end(),pImp); + + if (it != pimpl->mImporter.end()) { + pimpl->mImporter.erase(it); + ASSIMP_LOG_INFO("Unregistering custom importer: "); + return AI_SUCCESS; + } + ASSIMP_LOG_WARN("Unable to remove custom importer: I can't find you ..."); + ASSIMP_END_EXCEPTION_REGION(aiReturn); + + return AI_FAILURE; +} + +// ------------------------------------------------------------------------------------------------ +// Unregister a custom loader plugin +aiReturn Importer::UnregisterPPStep(BaseProcess* pImp) { + if(!pImp) { + // unregistering a nullptr ppstep is no problem for us ... really! + return AI_SUCCESS; + } + + ASSIMP_BEGIN_EXCEPTION_REGION(); + std::vector<BaseProcess*>::iterator it = std::find(pimpl->mPostProcessingSteps.begin(), + pimpl->mPostProcessingSteps.end(),pImp); + + if (it != pimpl->mPostProcessingSteps.end()) { + pimpl->mPostProcessingSteps.erase(it); + ASSIMP_LOG_INFO("Unregistering custom post-processing step"); + return AI_SUCCESS; + } + ASSIMP_LOG_WARN("Unable to remove custom post-processing step: I can't find you .."); + ASSIMP_END_EXCEPTION_REGION(aiReturn); + + return AI_FAILURE; +} + +// ------------------------------------------------------------------------------------------------ +// Supplies a custom IO handler to the importer to open and access files. +void Importer::SetIOHandler( IOSystem* pIOHandler) { + ai_assert(nullptr != pimpl); + + ASSIMP_BEGIN_EXCEPTION_REGION(); + // If the new handler is zero, allocate a default IO implementation. + if (!pIOHandler) { + // Release pointer in the possession of the caller + pimpl->mIOHandler = new DefaultIOSystem(); + pimpl->mIsDefaultHandler = true; + } else if (pimpl->mIOHandler != pIOHandler) { // Otherwise register the custom handler + delete pimpl->mIOHandler; + pimpl->mIOHandler = pIOHandler; + pimpl->mIsDefaultHandler = false; + } + ASSIMP_END_EXCEPTION_REGION(void); +} + +// ------------------------------------------------------------------------------------------------ +// Get the currently set IO handler +IOSystem* Importer::GetIOHandler() const { + ai_assert(nullptr != pimpl); + + return pimpl->mIOHandler; +} + +// ------------------------------------------------------------------------------------------------ +// Check whether a custom IO handler is currently set +bool Importer::IsDefaultIOHandler() const { + ai_assert(nullptr != pimpl); + + return pimpl->mIsDefaultHandler; +} + +// ------------------------------------------------------------------------------------------------ +// Supplies a custom progress handler to get regular callbacks during importing +void Importer::SetProgressHandler(ProgressHandler* pHandler) { + ai_assert(nullptr != pimpl); + + ASSIMP_BEGIN_EXCEPTION_REGION(); + + // If the new handler is zero, allocate a default implementation. + if (!pHandler) { + // Release pointer in the possession of the caller + pimpl->mProgressHandler = new DefaultProgressHandler(); + pimpl->mIsDefaultProgressHandler = true; + } else if (pimpl->mProgressHandler != pHandler) { // Otherwise register the custom handler + delete pimpl->mProgressHandler; + pimpl->mProgressHandler = pHandler; + pimpl->mIsDefaultProgressHandler = false; + } + ASSIMP_END_EXCEPTION_REGION(void); +} + +// ------------------------------------------------------------------------------------------------ +// Get the currently set progress handler +ProgressHandler* Importer::GetProgressHandler() const { + ai_assert(nullptr != pimpl); + + return pimpl->mProgressHandler; +} + +// ------------------------------------------------------------------------------------------------ +// Check whether a custom progress handler is currently set +bool Importer::IsDefaultProgressHandler() const { + ai_assert(nullptr != pimpl); + + return pimpl->mIsDefaultProgressHandler; +} + +// ------------------------------------------------------------------------------------------------ +// Validate post process step flags +bool _ValidateFlags(unsigned int pFlags) { + if (pFlags & aiProcess_GenSmoothNormals && pFlags & aiProcess_GenNormals) { + ASSIMP_LOG_ERROR("#aiProcess_GenSmoothNormals and #aiProcess_GenNormals are incompatible"); + return false; + } + if (pFlags & aiProcess_OptimizeGraph && pFlags & aiProcess_PreTransformVertices) { + ASSIMP_LOG_ERROR("#aiProcess_OptimizeGraph and #aiProcess_PreTransformVertices are incompatible"); + return false; + } + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Free the current scene +void Importer::FreeScene( ) { + ai_assert(nullptr != pimpl); + + ASSIMP_BEGIN_EXCEPTION_REGION(); + + delete pimpl->mScene; + pimpl->mScene = nullptr; + + pimpl->mErrorString = std::string(); + pimpl->mException = std::exception_ptr(); + ASSIMP_END_EXCEPTION_REGION(void); +} + +// ------------------------------------------------------------------------------------------------ +// Get the current error string, if any +const char* Importer::GetErrorString() const { + ai_assert(nullptr != pimpl); + + // Must remain valid as long as ReadFile() or FreeFile() are not called + return pimpl->mErrorString.c_str(); +} + +const std::exception_ptr& Importer::GetException() const { + ai_assert(nullptr != pimpl); + + // Must remain valid as long as ReadFile() or FreeFile() are not called + return pimpl->mException; +} + +// ------------------------------------------------------------------------------------------------ +// Enable extra-verbose mode +void Importer::SetExtraVerbose(bool bDo) { + ai_assert(nullptr != pimpl); + + pimpl->bExtraVerbose = bDo; +} + +// ------------------------------------------------------------------------------------------------ +// Get the current scene +const aiScene* Importer::GetScene() const { + ai_assert(nullptr != pimpl); + + return pimpl->mScene; +} + +// ------------------------------------------------------------------------------------------------ +// Orphan the current scene and return it. +aiScene* Importer::GetOrphanedScene() { + ai_assert(nullptr != pimpl); + + aiScene* s = pimpl->mScene; + + ASSIMP_BEGIN_EXCEPTION_REGION(); + pimpl->mScene = nullptr; + + pimpl->mErrorString = std::string(); + pimpl->mException = std::exception_ptr(); + ASSIMP_END_EXCEPTION_REGION(aiScene*); + + return s; +} + +// ------------------------------------------------------------------------------------------------ +// Validate post-processing flags +bool Importer::ValidateFlags(unsigned int pFlags) const { + ASSIMP_BEGIN_EXCEPTION_REGION(); + // run basic checks for mutually exclusive flags + if(!_ValidateFlags(pFlags)) { + return false; + } + + // ValidateDS does not anymore occur in the pp list, it plays an awesome extra role ... +#ifdef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS + if (pFlags & aiProcess_ValidateDataStructure) { + return false; + } +#endif + pFlags &= ~aiProcess_ValidateDataStructure; + + // Now iterate through all bits which are set in the flags and check whether we find at least + // one pp plugin which handles it. + for (unsigned int mask = 1; mask < (1u << (sizeof(unsigned int)*8-1));mask <<= 1) { + + if (pFlags & mask) { + + bool have = false; + for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++) { + if (pimpl->mPostProcessingSteps[a]-> IsActive(mask) ) { + + have = true; + break; + } + } + if (!have) { + return false; + } + } + } + ASSIMP_END_EXCEPTION_REGION(bool); + return true; +} + +// ------------------------------------------------------------------------------------------------ +const aiScene* Importer::ReadFileFromMemory( const void* pBuffer, + size_t pLength, + unsigned int pFlags, + const char* pHint /*= ""*/) { + ai_assert(nullptr != pimpl); + + ASSIMP_BEGIN_EXCEPTION_REGION(); + if (!pHint) { + pHint = ""; + } + + if (!pBuffer || !pLength || strlen(pHint) > MaxLenHint ) { + pimpl->mErrorString = "Invalid parameters passed to ReadFileFromMemory()"; + return nullptr; + } + + // prevent deletion of the previous IOHandler + IOSystem* io = pimpl->mIOHandler; + pimpl->mIOHandler = nullptr; + + SetIOHandler(new MemoryIOSystem((const uint8_t*)pBuffer,pLength,io)); + + // read the file and recover the previous IOSystem + static const size_t BufSize(Importer::MaxLenHint + 28); + char fbuff[BufSize]; + ai_snprintf(fbuff, BufSize, "%s.%s",AI_MEMORYIO_MAGIC_FILENAME,pHint); + + ReadFile(fbuff,pFlags); + SetIOHandler(io); + + ASSIMP_END_EXCEPTION_REGION_WITH_ERROR_STRING(const aiScene*, pimpl->mErrorString, pimpl->mException); + return pimpl->mScene; +} + +// ------------------------------------------------------------------------------------------------ +void WriteLogOpening(const std::string& file) { + + ASSIMP_LOG_INFO("Load ", file); + + // print a full version dump. This is nice because we don't + // need to ask the authors of incoming bug reports for + // the library version they're using - a log dump is + // sufficient. + const unsigned int flags = aiGetCompileFlags(); + std::stringstream stream; + stream << "Assimp " << aiGetVersionMajor() << "." << aiGetVersionMinor() << "." << aiGetVersionRevision() << " " +#if defined(ASSIMP_BUILD_ARCHITECTURE) + << ASSIMP_BUILD_ARCHITECTURE +#elif defined(_M_IX86) || defined(__x86_32__) || defined(__i386__) + << "x86" +#elif defined(_M_X64) || defined(__x86_64__) + << "amd64" +#elif defined(_M_IA64) || defined(__ia64__) + << "itanium" +#elif defined(__ppc__) || defined(__powerpc__) + << "ppc32" +#elif defined(__powerpc64__) + << "ppc64" +#elif defined(__arm__) + << "arm" +#else + << "<unknown architecture>" +#endif + << " " +#if defined(ASSIMP_BUILD_COMPILER) + << (ASSIMP_BUILD_COMPILER) +#elif defined(_MSC_VER) + << "msvc" +#elif defined(__GNUC__) + << "gcc" +#elif defined(__clang__) + << "clang" +#elif defined(__EMSCRIPTEN__) + << "emscripten" +#elif defined(__MINGW32__) + << "MinGW-w64 32bit" +#elif defined(__MINGW64__) + << "MinGW-w64 64bit" +#else + << "<unknown compiler>" +#endif + +#ifdef ASSIMP_BUILD_DEBUG + << " debug" +#endif + + << (flags & ASSIMP_CFLAGS_NOBOOST ? " noboost" : "") + << (flags & ASSIMP_CFLAGS_SHARED ? " shared" : "") + << (flags & ASSIMP_CFLAGS_SINGLETHREADED ? " singlethreaded" : "") + << (flags & ASSIMP_CFLAGS_DOUBLE_SUPPORT ? " double : " : "single : "); + + ASSIMP_LOG_DEBUG(stream.str()); +} + +// ------------------------------------------------------------------------------------------------ +// Reads the given file and returns its contents if successful. +const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags) { + ai_assert(nullptr != pimpl); + + ASSIMP_BEGIN_EXCEPTION_REGION(); + const std::string pFile(_pFile); + + // ---------------------------------------------------------------------- + // Put a large try block around everything to catch all std::exception's + // that might be thrown by STL containers or by new(). + // ImportErrorException's are throw by ourselves and caught elsewhere. + //----------------------------------------------------------------------- + + WriteLogOpening(pFile); + +#ifdef ASSIMP_CATCH_GLOBAL_EXCEPTIONS + try +#endif // ! ASSIMP_CATCH_GLOBAL_EXCEPTIONS + { + // Check whether this Importer instance has already loaded + // a scene. In this case we need to delete the old one + if (pimpl->mScene) { + + ASSIMP_LOG_DEBUG("(Deleting previous scene)"); + FreeScene(); + } + + // First check if the file is accessible at all + if( !pimpl->mIOHandler->Exists( pFile)) { + + pimpl->mErrorString = "Unable to open file \"" + pFile + "\"."; + ASSIMP_LOG_ERROR(pimpl->mErrorString); + return nullptr; + } + + std::unique_ptr<Profiler> profiler(GetPropertyInteger(AI_CONFIG_GLOB_MEASURE_TIME, 0) ? new Profiler() : nullptr); + if (profiler) { + profiler->BeginRegion("total"); + } + + // Find an worker class which can handle the file extension. + // Multiple importers may be able to handle the same extension (.xml!); gather them all. + SetPropertyInteger("importerIndex", -1); + struct ImporterAndIndex { + BaseImporter * importer; + unsigned int index; + }; + std::vector<ImporterAndIndex> possibleImporters; + for (unsigned int a = 0; a < pimpl->mImporter.size(); a++) { + + // Every importer has a list of supported extensions. + std::set<std::string> extensions; + pimpl->mImporter[a]->GetExtensionList(extensions); + + // CAUTION: Do not just search for the extension! + // GetExtension() returns the part after the *last* dot, but some extensions have dots + // inside them, e.g. ogre.mesh.xml. Compare the entire end of the string. + for (std::set<std::string>::const_iterator it = extensions.cbegin(); it != extensions.cend(); ++it) { + + // Yay for C++<20 not having std::string::ends_with() + std::string extension = "." + *it; + if (extension.length() <= pFile.length()) { + // Possible optimization: Fetch the lowercase filename! + if (0 == ASSIMP_stricmp(pFile.c_str() + pFile.length() - extension.length(), extension.c_str())) { + ImporterAndIndex candidate = { pimpl->mImporter[a], a }; + possibleImporters.push_back(candidate); + break; + } + } + + } + + } + + // If just one importer supports this extension, pick it and close the case. + BaseImporter* imp = nullptr; + if (1 == possibleImporters.size()) { + imp = possibleImporters[0].importer; + SetPropertyInteger("importerIndex", possibleImporters[0].index); + } + // If multiple importers claim this file extension, ask them to look at the actual file data to decide. + // This can happen e.g. with XML (COLLADA vs. Irrlicht). + else { + for (std::vector<ImporterAndIndex>::const_iterator it = possibleImporters.begin(); it < possibleImporters.end(); ++it) { + BaseImporter & importer = *it->importer; + + ASSIMP_LOG_INFO("Found a possible importer: " + std::string(importer.GetInfo()->mName) + "; trying signature-based detection"); + if (importer.CanRead( pFile, pimpl->mIOHandler, true)) { + imp = &importer; + SetPropertyInteger("importerIndex", it->index); + break; + } + + } + + } + + if (!imp) { + // not so bad yet ... try format auto detection. + ASSIMP_LOG_INFO("File extension not known, trying signature-based detection"); + for( unsigned int a = 0; a < pimpl->mImporter.size(); a++) { + if( pimpl->mImporter[a]->CanRead( pFile, pimpl->mIOHandler, true)) { + imp = pimpl->mImporter[a]; + SetPropertyInteger("importerIndex", a); + break; + } + } + // Put a proper error message if no suitable importer was found + if( !imp) { + pimpl->mErrorString = "No suitable reader found for the file format of file \"" + pFile + "\"."; + ASSIMP_LOG_ERROR(pimpl->mErrorString); + return nullptr; + } + } + + // Get file size for progress handler + IOStream * fileIO = pimpl->mIOHandler->Open( pFile ); + uint32_t fileSize = 0; + if (fileIO) + { + fileSize = static_cast<uint32_t>(fileIO->FileSize()); + pimpl->mIOHandler->Close( fileIO ); + } + + // Dispatch the reading to the worker class for this format + const aiImporterDesc *desc( imp->GetInfo() ); + std::string ext( "unknown" ); + if ( nullptr != desc ) { + ext = desc->mName; + } + ASSIMP_LOG_INFO("Found a matching importer for this file format: ", ext, "." ); + pimpl->mProgressHandler->UpdateFileRead( 0, fileSize ); + + if (profiler) { + profiler->BeginRegion("import"); + } + + pimpl->mScene = imp->ReadFile( this, pFile, pimpl->mIOHandler); + pimpl->mProgressHandler->UpdateFileRead( fileSize, fileSize ); + + if (profiler) { + profiler->EndRegion("import"); + } + + SetPropertyString("sourceFilePath", pFile); + + // If successful, apply all active post processing steps to the imported data + if( pimpl->mScene) { + if (!pimpl->mScene->mMetaData || !pimpl->mScene->mMetaData->HasKey(AI_METADATA_SOURCE_FORMAT)) { + if (!pimpl->mScene->mMetaData) { + pimpl->mScene->mMetaData = new aiMetadata; + } + pimpl->mScene->mMetaData->Add(AI_METADATA_SOURCE_FORMAT, aiString(ext)); + } + +#ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS + // The ValidateDS process is an exception. It is executed first, even before ScenePreprocessor is called. + if (pFlags & aiProcess_ValidateDataStructure) { + ValidateDSProcess ds; + ds.ExecuteOnScene (this); + if (!pimpl->mScene) { + return nullptr; + } + } +#endif // no validation + + // Preprocess the scene and prepare it for post-processing + if (profiler) { + profiler->BeginRegion("preprocess"); + } + + ScenePreprocessor pre(pimpl->mScene); + pre.ProcessScene(); + + if (profiler) { + profiler->EndRegion("preprocess"); + } + + // Ensure that the validation process won't be called twice + ApplyPostProcessing(pFlags & (~aiProcess_ValidateDataStructure)); + } + // if failed, extract the error string + else if( !pimpl->mScene) { + pimpl->mErrorString = imp->GetErrorText(); + pimpl->mException = imp->GetException(); + } + + // clear any data allocated by post-process steps + pimpl->mPPShared->Clean(); + + if (profiler) { + profiler->EndRegion("total"); + } + } +#ifdef ASSIMP_CATCH_GLOBAL_EXCEPTIONS + catch (std::exception &e) { +#if (defined _MSC_VER) && (defined _CPPRTTI) + // if we have RTTI get the full name of the exception that occurred + pimpl->mErrorString = std::string(typeid( e ).name()) + ": " + e.what(); +#else + pimpl->mErrorString = std::string("std::exception: ") + e.what(); +#endif + + ASSIMP_LOG_ERROR(pimpl->mErrorString); + delete pimpl->mScene; pimpl->mScene = nullptr; + } +#endif // ! ASSIMP_CATCH_GLOBAL_EXCEPTIONS + + // either successful or failure - the pointer expresses it anyways + ASSIMP_END_EXCEPTION_REGION_WITH_ERROR_STRING(const aiScene*, pimpl->mErrorString, pimpl->mException); + + return pimpl->mScene; +} + + +// ------------------------------------------------------------------------------------------------ +// Apply post-processing to the currently bound scene +const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags) { + ai_assert(nullptr != pimpl); + + ASSIMP_BEGIN_EXCEPTION_REGION(); + // Return immediately if no scene is active + if (!pimpl->mScene) { + return nullptr; + } + + // If no flags are given, return the current scene with no further action + if (!pFlags) { + return pimpl->mScene; + } + + // In debug builds: run basic flag validation + ai_assert(_ValidateFlags(pFlags)); + ASSIMP_LOG_INFO("Entering post processing pipeline"); + +#ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS + // The ValidateDS process plays an exceptional role. It isn't contained in the global + // list of post-processing steps, so we need to call it manually. + if (pFlags & aiProcess_ValidateDataStructure) { + ValidateDSProcess ds; + ds.ExecuteOnScene (this); + if (!pimpl->mScene) { + return nullptr; + } + } +#endif // no validation +#ifdef ASSIMP_BUILD_DEBUG + if (pimpl->bExtraVerbose) + { +#ifdef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS + ASSIMP_LOG_ERROR("Verbose Import is not available due to build settings"); +#endif // no validation + pFlags |= aiProcess_ValidateDataStructure; + } +#else + if (pimpl->bExtraVerbose) { + ASSIMP_LOG_WARN("Not a debug build, ignoring extra verbose setting"); + } +#endif // ! DEBUG + + std::unique_ptr<Profiler> profiler(GetPropertyInteger(AI_CONFIG_GLOB_MEASURE_TIME, 0) ? new Profiler() : nullptr); + for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++) { + BaseProcess* process = pimpl->mPostProcessingSteps[a]; + pimpl->mProgressHandler->UpdatePostProcess(static_cast<int>(a), static_cast<int>(pimpl->mPostProcessingSteps.size()) ); + if( process->IsActive( pFlags)) { + if (profiler) { + profiler->BeginRegion("postprocess"); + } + + process->ExecuteOnScene ( this ); + + if (profiler) { + profiler->EndRegion("postprocess"); + } + } + if( !pimpl->mScene) { + break; + } +#ifdef ASSIMP_BUILD_DEBUG + +#ifdef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS + continue; +#endif // no validation + + // If the extra verbose mode is active, execute the ValidateDataStructureStep again - after each step + if (pimpl->bExtraVerbose) { + ASSIMP_LOG_DEBUG("Verbose Import: re-validating data structures"); + + ValidateDSProcess ds; + ds.ExecuteOnScene (this); + if( !pimpl->mScene) { + ASSIMP_LOG_ERROR("Verbose Import: failed to re-validate data structures"); + break; + } + } +#endif // ! DEBUG + } + pimpl->mProgressHandler->UpdatePostProcess( static_cast<int>(pimpl->mPostProcessingSteps.size()), + static_cast<int>(pimpl->mPostProcessingSteps.size()) ); + + // update private scene flags + if( pimpl->mScene ) { + ScenePriv(pimpl->mScene)->mPPStepsApplied |= pFlags; + } + + // clear any data allocated by post-process steps + pimpl->mPPShared->Clean(); + ASSIMP_LOG_INFO("Leaving post processing pipeline"); + + ASSIMP_END_EXCEPTION_REGION(const aiScene*); + + return pimpl->mScene; +} + +// ------------------------------------------------------------------------------------------------ +const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess, bool requestValidation ) { + ai_assert(nullptr != pimpl); + + ASSIMP_BEGIN_EXCEPTION_REGION(); + + // Return immediately if no scene is active + if ( nullptr == pimpl->mScene ) { + return nullptr; + } + + // If no flags are given, return the current scene with no further action + if (nullptr == rootProcess) { + return pimpl->mScene; + } + + // In debug builds: run basic flag validation + ASSIMP_LOG_INFO( "Entering customized post processing pipeline" ); + +#ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS + // The ValidateDS process plays an exceptional role. It isn't contained in the global + // list of post-processing steps, so we need to call it manually. + if ( requestValidation ) + { + ValidateDSProcess ds; + ds.ExecuteOnScene( this ); + if ( !pimpl->mScene ) { + return nullptr; + } + } +#endif // no validation +#ifdef ASSIMP_BUILD_DEBUG + if ( pimpl->bExtraVerbose ) + { +#ifdef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS + ASSIMP_LOG_ERROR( "Verbose Import is not available due to build settings" ); +#endif // no validation + } +#else + if ( pimpl->bExtraVerbose ) { + ASSIMP_LOG_WARN( "Not a debug build, ignoring extra verbose setting" ); + } +#endif // ! DEBUG + + std::unique_ptr<Profiler> profiler(GetPropertyInteger(AI_CONFIG_GLOB_MEASURE_TIME, 0) ? new Profiler() : nullptr); + + if ( profiler ) { + profiler->BeginRegion( "postprocess" ); + } + + rootProcess->ExecuteOnScene( this ); + + if ( profiler ) { + profiler->EndRegion( "postprocess" ); + } + + // If the extra verbose mode is active, execute the ValidateDataStructureStep again - after each step + if ( pimpl->bExtraVerbose || requestValidation ) { + ASSIMP_LOG_DEBUG( "Verbose Import: revalidating data structures" ); + + ValidateDSProcess ds; + ds.ExecuteOnScene( this ); + if ( !pimpl->mScene ) { + ASSIMP_LOG_ERROR( "Verbose Import: failed to revalidate data structures" ); + } + } + + // clear any data allocated by post-process steps + pimpl->mPPShared->Clean(); + ASSIMP_LOG_INFO( "Leaving customized post processing pipeline" ); + + ASSIMP_END_EXCEPTION_REGION( const aiScene* ); + + return pimpl->mScene; +} + +// ------------------------------------------------------------------------------------------------ +// Helper function to check whether an extension is supported by ASSIMP +bool Importer::IsExtensionSupported(const char* szExtension) const { + return nullptr != GetImporter(szExtension); +} + +// ------------------------------------------------------------------------------------------------ +size_t Importer::GetImporterCount() const { + ai_assert(nullptr != pimpl); + + return pimpl->mImporter.size(); +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc* Importer::GetImporterInfo(size_t index) const { + ai_assert(nullptr != pimpl); + + if (index >= pimpl->mImporter.size()) { + return nullptr; + } + return pimpl->mImporter[index]->GetInfo(); +} + + +// ------------------------------------------------------------------------------------------------ +BaseImporter* Importer::GetImporter (size_t index) const { + ai_assert(nullptr != pimpl); + + if (index >= pimpl->mImporter.size()) { + return nullptr; + } + return pimpl->mImporter[index]; +} + +// ------------------------------------------------------------------------------------------------ +// Find a loader plugin for a given file extension +BaseImporter* Importer::GetImporter (const char* szExtension) const { + ai_assert(nullptr != pimpl); + + return GetImporter(GetImporterIndex(szExtension)); +} + +// ------------------------------------------------------------------------------------------------ +// Find a loader plugin for a given file extension +size_t Importer::GetImporterIndex (const char* szExtension) const { + ai_assert(nullptr != pimpl); + ai_assert(nullptr != szExtension); + + ASSIMP_BEGIN_EXCEPTION_REGION(); + + // skip over wild-card and dot characters at string head -- + for ( ; *szExtension == '*' || *szExtension == '.'; ++szExtension ); + + std::string ext(szExtension); + if (ext.empty()) { + return static_cast<size_t>(-1); + } + ext = ai_tolower(ext); + std::set<std::string> str; + for (std::vector<BaseImporter*>::const_iterator i = pimpl->mImporter.begin();i != pimpl->mImporter.end();++i) { + str.clear(); + + (*i)->GetExtensionList(str); + for (std::set<std::string>::const_iterator it = str.begin(); it != str.end(); ++it) { + if (ext == *it) { + return std::distance(static_cast< std::vector<BaseImporter*>::const_iterator >(pimpl->mImporter.begin()), i); + } + } + } + ASSIMP_END_EXCEPTION_REGION(size_t); + return static_cast<size_t>(-1); +} + +// ------------------------------------------------------------------------------------------------ +// Helper function to build a list of all file extensions supported by ASSIMP +void Importer::GetExtensionList(aiString& szOut) const { + ai_assert(nullptr != pimpl); + + ASSIMP_BEGIN_EXCEPTION_REGION(); + std::set<std::string> str; + for (std::vector<BaseImporter*>::const_iterator i = pimpl->mImporter.begin();i != pimpl->mImporter.end();++i) { + (*i)->GetExtensionList(str); + } + + // List can be empty + if( !str.empty() ) { + for (std::set<std::string>::const_iterator it = str.begin();; ) { + szOut.Append("*."); + szOut.Append((*it).c_str()); + + if (++it == str.end()) { + break; + } + szOut.Append(";"); + } + } + ASSIMP_END_EXCEPTION_REGION(void); +} + +// ------------------------------------------------------------------------------------------------ +// Set a configuration property +bool Importer::SetPropertyInteger(const char* szName, int iValue) { + ai_assert(nullptr != pimpl); + + bool existing; + ASSIMP_BEGIN_EXCEPTION_REGION(); + existing = SetGenericProperty<int>(pimpl->mIntProperties, szName,iValue); + ASSIMP_END_EXCEPTION_REGION(bool); + return existing; +} + +// ------------------------------------------------------------------------------------------------ +// Set a configuration property +bool Importer::SetPropertyFloat(const char* szName, ai_real iValue) { + ai_assert(nullptr != pimpl); + + bool existing; + ASSIMP_BEGIN_EXCEPTION_REGION(); + existing = SetGenericProperty<ai_real>(pimpl->mFloatProperties, szName,iValue); + ASSIMP_END_EXCEPTION_REGION(bool); + return existing; +} + +// ------------------------------------------------------------------------------------------------ +// Set a configuration property +bool Importer::SetPropertyString(const char* szName, const std::string& value) { + ai_assert(nullptr != pimpl); + + bool existing; + ASSIMP_BEGIN_EXCEPTION_REGION(); + existing = SetGenericProperty<std::string>(pimpl->mStringProperties, szName,value); + ASSIMP_END_EXCEPTION_REGION(bool); + return existing; +} + +// ------------------------------------------------------------------------------------------------ +// Set a configuration property +bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value) { + ai_assert(nullptr != pimpl); + + bool existing; + ASSIMP_BEGIN_EXCEPTION_REGION(); + existing = SetGenericProperty<aiMatrix4x4>(pimpl->mMatrixProperties, szName,value); + ASSIMP_END_EXCEPTION_REGION(bool); + return existing; +} + +// ------------------------------------------------------------------------------------------------ +// Set a configuration property +bool Importer::SetPropertyPointer(const char* szName, void* value) { + ai_assert(nullptr != pimpl); + + bool existing; + ASSIMP_BEGIN_EXCEPTION_REGION(); + existing = SetGenericProperty<void*>(pimpl->mPointerProperties, szName,value); + ASSIMP_END_EXCEPTION_REGION(bool); + return existing; +} + +// ------------------------------------------------------------------------------------------------ +// Get a configuration property +int Importer::GetPropertyInteger(const char* szName, int iErrorReturn /*= 0xffffffff*/) const { + ai_assert(nullptr != pimpl); + + return GetGenericProperty<int>(pimpl->mIntProperties,szName,iErrorReturn); +} + +// ------------------------------------------------------------------------------------------------ +// Get a configuration property +ai_real Importer::GetPropertyFloat(const char* szName, ai_real iErrorReturn /*= 10e10*/) const { + ai_assert(nullptr != pimpl); + + return GetGenericProperty<ai_real>(pimpl->mFloatProperties,szName,iErrorReturn); +} + +// ------------------------------------------------------------------------------------------------ +// Get a configuration property +std::string Importer::GetPropertyString(const char* szName, const std::string& iErrorReturn /*= ""*/) const { + ai_assert(nullptr != pimpl); + + return GetGenericProperty<std::string>(pimpl->mStringProperties,szName,iErrorReturn); +} + +// ------------------------------------------------------------------------------------------------ +// Get a configuration property +aiMatrix4x4 Importer::GetPropertyMatrix(const char* szName, const aiMatrix4x4& iErrorReturn /*= aiMatrix4x4()*/) const { + ai_assert(nullptr != pimpl); + + return GetGenericProperty<aiMatrix4x4>(pimpl->mMatrixProperties,szName,iErrorReturn); +} + +// ------------------------------------------------------------------------------------------------ +// Get a configuration property +void* Importer::GetPropertyPointer(const char* szName, void* iErrorReturn /*= nullptr*/) const { + ai_assert(nullptr != pimpl); + + return GetGenericProperty<void*>(pimpl->mPointerProperties,szName,iErrorReturn); +} + +// ------------------------------------------------------------------------------------------------ +// Get the memory requirements of a single node +inline +void AddNodeWeight(unsigned int& iScene,const aiNode* pcNode) { + if ( nullptr == pcNode ) { + return; + } + iScene += sizeof(aiNode); + iScene += sizeof(unsigned int) * pcNode->mNumMeshes; + iScene += sizeof(void*) * pcNode->mNumChildren; + + for (unsigned int i = 0; i < pcNode->mNumChildren;++i) { + AddNodeWeight(iScene,pcNode->mChildren[i]); + } +} + +// ------------------------------------------------------------------------------------------------ +// Get the memory requirements of the scene +void Importer::GetMemoryRequirements(aiMemoryInfo& in) const { + ai_assert(nullptr != pimpl); + + in = aiMemoryInfo(); + aiScene* mScene = pimpl->mScene; + + // return if we have no scene loaded + if (!mScene) + return; + + in.total = sizeof(aiScene); + + // add all meshes + for (unsigned int i = 0; i < mScene->mNumMeshes;++i) { + in.meshes += sizeof(aiMesh); + if (mScene->mMeshes[i]->HasPositions()) { + in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices; + } + + if (mScene->mMeshes[i]->HasNormals()) { + in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices; + } + + if (mScene->mMeshes[i]->HasTangentsAndBitangents()) { + in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices * 2; + } + + for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS;++a) { + if (mScene->mMeshes[i]->HasVertexColors(a)) { + in.meshes += sizeof(aiColor4D) * mScene->mMeshes[i]->mNumVertices; + } else { + break; + } + } + for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS;++a) { + if (mScene->mMeshes[i]->HasTextureCoords(a)) { + in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices; + } else { + break; + } + } + if (mScene->mMeshes[i]->HasBones()) { + in.meshes += sizeof(void*) * mScene->mMeshes[i]->mNumBones; + for (unsigned int p = 0; p < mScene->mMeshes[i]->mNumBones;++p) { + in.meshes += sizeof(aiBone); + in.meshes += mScene->mMeshes[i]->mBones[p]->mNumWeights * sizeof(aiVertexWeight); + } + } + in.meshes += (sizeof(aiFace) + 3 * sizeof(unsigned int))*mScene->mMeshes[i]->mNumFaces; + } + in.total += in.meshes; + + // add all embedded textures + for (unsigned int i = 0; i < mScene->mNumTextures;++i) { + const aiTexture* pc = mScene->mTextures[i]; + in.textures += sizeof(aiTexture); + if (pc->mHeight) { + in.textures += 4 * pc->mHeight * pc->mWidth; + } else { + in.textures += pc->mWidth; + } + } + in.total += in.textures; + + // add all animations + for (unsigned int i = 0; i < mScene->mNumAnimations;++i) { + const aiAnimation* pc = mScene->mAnimations[i]; + in.animations += sizeof(aiAnimation); + + // add all bone anims + for (unsigned int a = 0; a < pc->mNumChannels; ++a) { + const aiNodeAnim* pc2 = pc->mChannels[a]; + in.animations += sizeof(aiNodeAnim); + in.animations += pc2->mNumPositionKeys * sizeof(aiVectorKey); + in.animations += pc2->mNumScalingKeys * sizeof(aiVectorKey); + in.animations += pc2->mNumRotationKeys * sizeof(aiQuatKey); + } + } + in.total += in.animations; + + // add all cameras and all lights + in.total += in.cameras = sizeof(aiCamera) * mScene->mNumCameras; + in.total += in.lights = sizeof(aiLight) * mScene->mNumLights; + + // add all nodes + AddNodeWeight(in.nodes,mScene->mRootNode); + in.total += in.nodes; + + // add all materials + for (unsigned int i = 0; i < mScene->mNumMaterials;++i) { + const aiMaterial* pc = mScene->mMaterials[i]; + in.materials += sizeof(aiMaterial); + in.materials += pc->mNumAllocated * sizeof(void*); + + for (unsigned int a = 0; a < pc->mNumProperties;++a) { + in.materials += pc->mProperties[a]->mDataLength; + } + } + + in.total += in.materials; +} diff --git a/libs/assimp/code/Common/Importer.h b/libs/assimp/code/Common/Importer.h new file mode 100644 index 0000000..ab7e3a6 --- /dev/null +++ b/libs/assimp/code/Common/Importer.h @@ -0,0 +1,250 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Importer.h mostly internal stuff for use by #Assimp::Importer */ +#pragma once +#ifndef INCLUDED_AI_IMPORTER_H +#define INCLUDED_AI_IMPORTER_H + +#include <exception> +#include <map> +#include <vector> +#include <string> +#include <assimp/matrix4x4.h> + +struct aiScene; + +namespace Assimp { + class ProgressHandler; + class IOSystem; + class BaseImporter; + class BaseProcess; + class SharedPostProcessInfo; + + +//! @cond never +// --------------------------------------------------------------------------- +/** @brief Internal PIMPL implementation for Assimp::Importer + * + * Using this idiom here allows us to drop the dependency from + * std::vector and std::map in the public headers. Furthermore we are dropping + * any STL interface problems caused by mismatching STL settings. All + * size calculation are now done by us, not the app heap. */ +class ImporterPimpl { +public: + // Data type to store the key hash + typedef unsigned int KeyType; + + // typedefs for our configuration maps. + typedef std::map<KeyType, int> IntPropertyMap; + typedef std::map<KeyType, ai_real> FloatPropertyMap; + typedef std::map<KeyType, std::string> StringPropertyMap; + typedef std::map<KeyType, aiMatrix4x4> MatrixPropertyMap; + typedef std::map<KeyType, void*> PointerPropertyMap; + + /** IO handler to use for all file accesses. */ + IOSystem* mIOHandler; + bool mIsDefaultHandler; + + /** Progress handler for feedback. */ + ProgressHandler* mProgressHandler; + bool mIsDefaultProgressHandler; + + /** Format-specific importer worker objects - one for each format we can read.*/ + std::vector< BaseImporter* > mImporter; + + /** Post processing steps we can apply at the imported data. */ + std::vector< BaseProcess* > mPostProcessingSteps; + + /** The imported data, if ReadFile() was successful, nullptr otherwise. */ + aiScene* mScene; + + /** The error description, if there was one. In the case of an exception, + * mException will carry the full details. */ + std::string mErrorString; + + /** Any exception which occurred */ + std::exception_ptr mException; + + /** List of integer properties */ + IntPropertyMap mIntProperties; + + /** List of floating-point properties */ + FloatPropertyMap mFloatProperties; + + /** List of string properties */ + StringPropertyMap mStringProperties; + + /** List of Matrix properties */ + MatrixPropertyMap mMatrixProperties; + + /** List of pointer properties */ + PointerPropertyMap mPointerProperties; + + /** Used for testing - extra verbose mode causes the ValidateDataStructure-Step + * to be executed before and after every single post-process step */ + bool bExtraVerbose; + + /** Used by post-process steps to share data */ + SharedPostProcessInfo* mPPShared; + + /// The default class constructor. + ImporterPimpl() AI_NO_EXCEPT; +}; + +inline +ImporterPimpl::ImporterPimpl() AI_NO_EXCEPT : + mIOHandler( nullptr ), + mIsDefaultHandler( false ), + mProgressHandler( nullptr ), + mIsDefaultProgressHandler( false ), + mImporter(), + mPostProcessingSteps(), + mScene( nullptr ), + mErrorString(), + mException(), + mIntProperties(), + mFloatProperties(), + mStringProperties(), + mMatrixProperties(), + mPointerProperties(), + bExtraVerbose( false ), + mPPShared( nullptr ) { + // empty +} +//! @endcond + +struct BatchData; + +// --------------------------------------------------------------------------- +/** FOR IMPORTER PLUGINS ONLY: A helper class to the pleasure of importers + * that need to load many external meshes recursively. + * + * The class uses several threads to load these meshes (or at least it + * could, this has not yet been implemented at the moment). + * + * @note The class may not be used by more than one thread*/ +class ASSIMP_API BatchLoader { +public: + //! @cond never + // ------------------------------------------------------------------- + /** Wraps a full list of configuration properties for an importer. + * Properties can be set using SetGenericProperty */ + struct PropertyMap { + ImporterPimpl::IntPropertyMap ints; + ImporterPimpl::FloatPropertyMap floats; + ImporterPimpl::StringPropertyMap strings; + ImporterPimpl::MatrixPropertyMap matrices; + + bool operator == (const PropertyMap& prop) const { + // fixme: really isocpp? gcc complains + return ints == prop.ints && floats == prop.floats && strings == prop.strings && matrices == prop.matrices; + } + + bool empty () const { + return ints.empty() && floats.empty() && strings.empty() && matrices.empty(); + } + }; + //! @endcond + + // ------------------------------------------------------------------- + /** Construct a batch loader from a given IO system to be used + * to access external files + */ + explicit BatchLoader(IOSystem* pIO, bool validate = false ); + + // ------------------------------------------------------------------- + /** The class destructor. + */ + ~BatchLoader(); + + // ------------------------------------------------------------------- + /** Sets the validation step. True for enable validation during postprocess. + * @param enable True for validation. + */ + void setValidation( bool enabled ); + + // ------------------------------------------------------------------- + /** Returns the current validation step. + * @return The current validation step. + */ + bool getValidation() const; + + // ------------------------------------------------------------------- + /** Add a new file to the list of files to be loaded. + * @param file File to be loaded + * @param steps Post-processing steps to be executed on the file + * @param map Optional configuration properties + * @return 'Load request channel' - an unique ID that can later + * be used to access the imported file data. + * @see GetImport */ + unsigned int AddLoadRequest ( + const std::string& file, + unsigned int steps = 0, + const PropertyMap *map = nullptr + ); + + // ------------------------------------------------------------------- + /** Get an imported scene. + * This polls the import from the internal request list. + * If an import is requested several times, this function + * can be called several times, too. + * + * @param which LRWC returned by AddLoadRequest(). + * @return nullptr if there is no scene with this file name + * in the queue of the scene hasn't been loaded yet. */ + aiScene* GetImport( + unsigned int which + ); + + // ------------------------------------------------------------------- + /** Waits until all scenes have been loaded. This returns + * immediately if no scenes are queued.*/ + void LoadAll(); + +private: + // No need to have that in the public API ... + BatchData *m_data; +}; + +} // Namespace Assimp + +#endif // INCLUDED_AI_IMPORTER_H diff --git a/libs/assimp/code/Common/ImporterRegistry.cpp b/libs/assimp/code/Common/ImporterRegistry.cpp new file mode 100644 index 0000000..78c02d9 --- /dev/null +++ b/libs/assimp/code/Common/ImporterRegistry.cpp @@ -0,0 +1,393 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 ImporterRegistry.cpp + +Central registry for all importers available. Do not edit this file +directly (unless you are adding new loaders), instead use the +corresponding preprocessor flag to selectively disable formats. +*/ + +#include <assimp/anim.h> +#include <assimp/BaseImporter.h> +#include <vector> +#include <cstdlib> + +// ------------------------------------------------------------------------------------------------ +// Importers +// (include_new_importers_here) +// ------------------------------------------------------------------------------------------------ +#ifndef ASSIMP_BUILD_NO_X_IMPORTER +#include "AssetLib/X/XFileImporter.h" +#endif +#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER +#include "AssetLib/AMF/AMFImporter.hpp" +#endif +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER +#include "AssetLib/3DS/3DSLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_MD3_IMPORTER +#include "AssetLib/MD3/MD3Loader.h" +#endif +#ifndef ASSIMP_BUILD_NO_MDL_IMPORTER +#include "AssetLib/MDL/MDLLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_MD2_IMPORTER +#include "AssetLib/MD2/MD2Loader.h" +#endif +#ifndef ASSIMP_BUILD_NO_PLY_IMPORTER +#include "AssetLib/Ply/PlyLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_ASE_IMPORTER +#include "AssetLib/ASE/ASELoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER +#include "AssetLib/Obj/ObjFileImporter.h" +#endif +#ifndef ASSIMP_BUILD_NO_HMP_IMPORTER +#include "AssetLib/HMP/HMPLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_SMD_IMPORTER +#include "AssetLib/SMD/SMDLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_MDC_IMPORTER +#include "AssetLib/MDC/MDCLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_MD5_IMPORTER +#include "AssetLib/MD5/MD5Loader.h" +#endif +#ifndef ASSIMP_BUILD_NO_STL_IMPORTER +#include "AssetLib/STL/STLLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_LWO_IMPORTER +#include "AssetLib/LWO/LWOLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_DXF_IMPORTER +#include "AssetLib/DXF/DXFLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_NFF_IMPORTER +#include "AssetLib/NFF/NFFLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_RAW_IMPORTER +#include "AssetLib/Raw/RawLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_SIB_IMPORTER +#include "AssetLib/SIB/SIBImporter.h" +#endif +#ifndef ASSIMP_BUILD_NO_OFF_IMPORTER +#include "AssetLib/OFF/OFFLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_AC_IMPORTER +#include "AssetLib/AC/ACLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_BVH_IMPORTER +#include "AssetLib/BVH/BVHLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_IRRMESH_IMPORTER +#include "AssetLib/Irr/IRRMeshLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_IRR_IMPORTER +#include "AssetLib/Irr/IRRLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_Q3D_IMPORTER +#include "AssetLib/Q3D/Q3DLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_B3D_IMPORTER +#include "AssetLib/B3D/B3DImporter.h" +#endif +#ifndef ASSIMP_BUILD_NO_COLLADA_IMPORTER +#include "AssetLib/Collada/ColladaLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_TERRAGEN_IMPORTER +#include "AssetLib/Terragen/TerragenLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_CSM_IMPORTER +#include "AssetLib/CSM/CSMLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_3D_IMPORTER +#include "AssetLib/Unreal/UnrealLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_LWS_IMPORTER +#include "AssetLib/LWS/LWSLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER +#include "AssetLib/Ogre/OgreImporter.h" +#endif +#ifndef ASSIMP_BUILD_NO_OPENGEX_IMPORTER +#include "AssetLib/OpenGEX/OpenGEXImporter.h" +#endif +#ifndef ASSIMP_BUILD_NO_MS3D_IMPORTER +#include "AssetLib/MS3D/MS3DLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_COB_IMPORTER +#include "AssetLib/COB/COBLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER +#include "AssetLib/Blender/BlenderLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_Q3BSP_IMPORTER +#include "AssetLib/Q3BSP/Q3BSPFileImporter.h" +#endif +#ifndef ASSIMP_BUILD_NO_NDO_IMPORTER +#include "AssetLib/NDO/NDOLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER +#include "AssetLib/IFC/IFCLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_XGL_IMPORTER +#include "AssetLib/XGL/XGLLoader.h" +#endif +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER +#include "AssetLib/FBX/FBXImporter.h" +#endif +#ifndef ASSIMP_BUILD_NO_ASSBIN_IMPORTER +#include "AssetLib/Assbin/AssbinLoader.h" +#endif +#if !defined(ASSIMP_BUILD_NO_GLTF_IMPORTER) && !defined(ASSIMP_BUILD_NO_GLTF1_IMPORTER) +#include "AssetLib/glTF/glTFImporter.h" +#endif +#if !defined(ASSIMP_BUILD_NO_GLTF_IMPORTER) && !defined(ASSIMP_BUILD_NO_GLTF2_IMPORTER) +#include "AssetLib/glTF2/glTF2Importer.h" +#endif +#ifndef ASSIMP_BUILD_NO_C4D_IMPORTER +#include "AssetLib/C4D/C4DImporter.h" +#endif +#ifndef ASSIMP_BUILD_NO_3MF_IMPORTER +#include "AssetLib/3MF/D3MFImporter.h" +#endif +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER +#include "AssetLib/X3D/X3DImporter.hpp" +#endif +#ifndef ASSIMP_BUILD_NO_MMD_IMPORTER +#include "AssetLib/MMD/MMDImporter.h" +#endif +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER +#include "AssetLib/M3D/M3DImporter.h" +#endif +#ifndef ASSIMP_BUILD_NO_IQM_IMPORTER +#include "AssetLib/IQM/IQMImporter.h" +#endif + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +void GetImporterInstanceList(std::vector<BaseImporter *> &out) { + + // Some importers may be unimplemented or otherwise unsuitable for general use + // in their current state. Devs can set ASSIMP_ENABLE_DEV_IMPORTERS in their + // local environment to enable them, otherwise they're left out of the registry. + const char *envStr = std::getenv("ASSIMP_ENABLE_DEV_IMPORTERS"); + bool devImportersEnabled = envStr && strcmp(envStr, "0"); + + // Ensure no unused var warnings if all uses are #ifndef'd away below: + (void)devImportersEnabled; + + // ---------------------------------------------------------------------------- + // Add an instance of each worker class here + // (register_new_importers_here) + // ---------------------------------------------------------------------------- + out.reserve(64); +#if (!defined ASSIMP_BUILD_NO_X_IMPORTER) + out.push_back(new XFileImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_OBJ_IMPORTER) + out.push_back(new ObjFileImporter()); +#endif +#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER + out.push_back(new AMFImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_3DS_IMPORTER) + out.push_back(new Discreet3DSImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_M3D_IMPORTER) + out.push_back(new M3DImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_MD3_IMPORTER) + out.push_back(new MD3Importer()); +#endif +#if (!defined ASSIMP_BUILD_NO_MD2_IMPORTER) + out.push_back(new MD2Importer()); +#endif +#if (!defined ASSIMP_BUILD_NO_PLY_IMPORTER) + out.push_back(new PLYImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_MDL_IMPORTER) + out.push_back(new MDLImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_ASE_IMPORTER) +#if (!defined ASSIMP_BUILD_NO_3DS_IMPORTER) + out.push_back(new ASEImporter()); +#endif +#endif +#if (!defined ASSIMP_BUILD_NO_HMP_IMPORTER) + out.push_back(new HMPImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_SMD_IMPORTER) + out.push_back(new SMDImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_MDC_IMPORTER) + out.push_back(new MDCImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_MD5_IMPORTER) + out.push_back(new MD5Importer()); +#endif +#if (!defined ASSIMP_BUILD_NO_STL_IMPORTER) + out.push_back(new STLImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_LWO_IMPORTER) + out.push_back(new LWOImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_DXF_IMPORTER) + out.push_back(new DXFImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_NFF_IMPORTER) + out.push_back(new NFFImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_RAW_IMPORTER) + out.push_back(new RAWImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_SIB_IMPORTER) + out.push_back(new SIBImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_OFF_IMPORTER) + out.push_back(new OFFImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_AC_IMPORTER) + out.push_back(new AC3DImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_BVH_IMPORTER) + out.push_back(new BVHLoader()); +#endif +#if (!defined ASSIMP_BUILD_NO_IRRMESH_IMPORTER) + out.push_back(new IRRMeshImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_IRR_IMPORTER) + out.push_back(new IRRImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_Q3D_IMPORTER) + out.push_back(new Q3DImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_B3D_IMPORTER) + out.push_back(new B3DImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_COLLADA_IMPORTER) + out.push_back(new ColladaLoader()); +#endif +#if (!defined ASSIMP_BUILD_NO_TERRAGEN_IMPORTER) + out.push_back(new TerragenImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_CSM_IMPORTER) + out.push_back(new CSMImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_3D_IMPORTER) + out.push_back(new UnrealImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_LWS_IMPORTER) + out.push_back(new LWSImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_OGRE_IMPORTER) + out.push_back(new Ogre::OgreImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_OPENGEX_IMPORTER) + out.push_back(new OpenGEX::OpenGEXImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_MS3D_IMPORTER) + out.push_back(new MS3DImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_COB_IMPORTER) + out.push_back(new COBImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_BLEND_IMPORTER) + out.push_back(new BlenderImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_Q3BSP_IMPORTER) + out.push_back(new Q3BSPFileImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_NDO_IMPORTER) + out.push_back(new NDOImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_IFC_IMPORTER) + out.push_back(new IFCImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_XGL_IMPORTER) + out.push_back(new XGLImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_FBX_IMPORTER) + out.push_back(new FBXImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_ASSBIN_IMPORTER) + out.push_back(new AssbinImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_GLTF_IMPORTER && !defined ASSIMP_BUILD_NO_GLTF1_IMPORTER) + out.push_back(new glTFImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_GLTF_IMPORTER && !defined ASSIMP_BUILD_NO_GLTF2_IMPORTER) + out.push_back(new glTF2Importer()); +#endif +#if (!defined ASSIMP_BUILD_NO_C4D_IMPORTER) + out.push_back(new C4DImporter()); +#endif +#if (!defined ASSIMP_BUILD_NO_3MF_IMPORTER) + out.push_back(new D3MFImporter()); +#endif +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + out.push_back(new X3DImporter()); +#endif +#ifndef ASSIMP_BUILD_NO_MMD_IMPORTER + out.push_back(new MMDImporter()); +#endif +#ifndef ASSIMP_BUILD_NO_IQM_IMPORTER + out.push_back(new IQMImporter()); +#endif + //#ifndef ASSIMP_BUILD_NO_STEP_IMPORTER + // out.push_back(new StepFile::StepFileImporter()); + //#endif +} + +/** will delete all registered importers. */ +void DeleteImporterInstanceList(std::vector<BaseImporter *> &deleteList) { + for (size_t i = 0; i < deleteList.size(); ++i) { + delete deleteList[i]; + deleteList[i] = nullptr; + } //for +} + +} // namespace Assimp diff --git a/libs/assimp/code/Common/PolyTools.h b/libs/assimp/code/Common/PolyTools.h new file mode 100644 index 0000000..11f6273 --- /dev/null +++ b/libs/assimp/code/Common/PolyTools.h @@ -0,0 +1,226 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 PolyTools.h, various utilities for our dealings with arbitrary polygons */ + +#pragma once +#ifndef AI_POLYTOOLS_H_INCLUDED +#define AI_POLYTOOLS_H_INCLUDED + +#include <assimp/material.h> +#include <assimp/ai_assert.h> + +namespace Assimp { + +// ------------------------------------------------------------------------------- +/** Compute the signed area of a triangle. + * The function accepts an unconstrained template parameter for use with + * both aiVector3D and aiVector2D, but generally ignores the third coordinate.*/ +template <typename T> +inline double GetArea2D(const T& v1, const T& v2, const T& v3) { + return 0.5 * (v1.x * ((double)v3.y - v2.y) + v2.x * ((double)v1.y - v3.y) + v3.x * ((double)v2.y - v1.y)); +} + +// ------------------------------------------------------------------------------- +/** Test if a given point p2 is on the left side of the line formed by p0-p1. + * The function accepts an unconstrained template parameter for use with + * both aiVector3D and aiVector2D, but generally ignores the third coordinate.*/ +template <typename T> +inline bool OnLeftSideOfLine2D(const T& p0, const T& p1,const T& p2) { + return GetArea2D(p0,p2,p1) > 0; +} + +// ------------------------------------------------------------------------------- +/** Test if a given point is inside a given triangle in R2. + * The function accepts an unconstrained template parameter for use with + * both aiVector3D and aiVector2D, but generally ignores the third coordinate.*/ +template <typename T> +inline bool PointInTriangle2D(const T& p0, const T& p1,const T& p2, const T& pp) { + // Point in triangle test using baryzentric coordinates + const aiVector2D v0 = p1 - p0; + const aiVector2D v1 = p2 - p0; + const aiVector2D v2 = pp - p0; + + double dot00 = v0 * v0; + double dot11 = v1 * v1; + const double dot01 = v0 * v1; + const double dot02 = v0 * v2; + const double dot12 = v1 * v2; + const double denom = dot00 * dot11 - dot01 * dot01; + if (denom == 0.0) { + return false; + } + + const double invDenom = 1.0 / denom; + dot11 = (dot11 * dot02 - dot01 * dot12) * invDenom; + dot00 = (dot00 * dot12 - dot01 * dot02) * invDenom; + + return (dot11 > 0) && (dot00 > 0) && (dot11 + dot00 < 1); +} + + +// ------------------------------------------------------------------------------- +/** Check whether the winding order of a given polygon is counter-clockwise. + * The function accepts an unconstrained template parameter, but is intended + * to be used only with aiVector2D and aiVector3D (z axis is ignored, only + * x and y are taken into account). + * @note Code taken from http://cgm.cs.mcgill.ca/~godfried/teaching/cg-projects/97/Ian/applet1.html and translated to C++ + */ +template <typename T> +inline bool IsCCW(T* in, size_t npoints) { + double aa, bb, cc, b, c, theta; + double convex_turn; + double convex_sum = 0; + + ai_assert(npoints >= 3); + + for (size_t i = 0; i < npoints - 2; i++) { + aa = ((in[i+2].x - in[i].x) * (in[i+2].x - in[i].x)) + + ((-in[i+2].y + in[i].y) * (-in[i+2].y + in[i].y)); + + bb = ((in[i+1].x - in[i].x) * (in[i+1].x - in[i].x)) + + ((-in[i+1].y + in[i].y) * (-in[i+1].y + in[i].y)); + + cc = ((in[i+2].x - in[i+1].x) * + (in[i+2].x - in[i+1].x)) + + ((-in[i+2].y + in[i+1].y) * + (-in[i+2].y + in[i+1].y)); + + b = std::sqrt(bb); + c = std::sqrt(cc); + theta = std::acos((bb + cc - aa) / (2 * b * c)); + + if (OnLeftSideOfLine2D(in[i],in[i+2],in[i+1])) { + // if (convex(in[i].x, in[i].y, + // in[i+1].x, in[i+1].y, + // in[i+2].x, in[i+2].y)) { + convex_turn = AI_MATH_PI_F - theta; + convex_sum += convex_turn; + } else { + convex_sum -= AI_MATH_PI_F - theta; + } + } + aa = ((in[1].x - in[npoints-2].x) * + (in[1].x - in[npoints-2].x)) + + ((-in[1].y + in[npoints-2].y) * + (-in[1].y + in[npoints-2].y)); + + bb = ((in[0].x - in[npoints-2].x) * + (in[0].x - in[npoints-2].x)) + + ((-in[0].y + in[npoints-2].y) * + (-in[0].y + in[npoints-2].y)); + + cc = ((in[1].x - in[0].x) * (in[1].x - in[0].x)) + + ((-in[1].y + in[0].y) * (-in[1].y + in[0].y)); + + b = std::sqrt(bb); + c = std::sqrt(cc); + theta = std::acos((bb + cc - aa) / (2 * b * c)); + + //if (convex(in[npoints-2].x, in[npoints-2].y, + // in[0].x, in[0].y, + // in[1].x, in[1].y)) { + if (OnLeftSideOfLine2D(in[npoints-2],in[1],in[0])) { + convex_turn = AI_MATH_PI_F - theta; + convex_sum += convex_turn; + } else { + convex_sum -= AI_MATH_PI_F - theta; + } + + return convex_sum >= (2 * AI_MATH_PI_F); +} + +// ------------------------------------------------------------------------------- +/** Compute the normal of an arbitrary polygon in R3. + * + * The code is based on Newell's formula, that is a polygons normal is the ratio + * of its area when projected onto the three coordinate axes. + * + * @param out Receives the output normal + * @param num Number of input vertices + * @param x X data source. x[ofs_x*n] is the n'th element. + * @param y Y data source. y[ofs_y*n] is the y'th element + * @param z Z data source. z[ofs_z*n] is the z'th element + * + * @note The data arrays must have storage for at least num+2 elements. Using + * this method is much faster than the 'other' NewellNormal() + */ +template <int ofs_x, int ofs_y, int ofs_z, typename TReal> +inline void NewellNormal (aiVector3t<TReal>& out, int num, TReal* x, TReal* y, TReal* z) { + // Duplicate the first two vertices at the end + x[(num+0)*ofs_x] = x[0]; + x[(num+1)*ofs_x] = x[ofs_x]; + + y[(num+0)*ofs_y] = y[0]; + y[(num+1)*ofs_y] = y[ofs_y]; + + z[(num+0)*ofs_z] = z[0]; + z[(num+1)*ofs_z] = z[ofs_z]; + + TReal sum_xy = 0.0, sum_yz = 0.0, sum_zx = 0.0; + + TReal *xptr = x +ofs_x, *xlow = x, *xhigh = x + ofs_x*2; + TReal *yptr = y +ofs_y, *ylow = y, *yhigh = y + ofs_y*2; + TReal *zptr = z +ofs_z, *zlow = z, *zhigh = z + ofs_z*2; + + for (int tmp=0; tmp < num; tmp++) { + sum_xy += (*xptr) * ( (*yhigh) - (*ylow) ); + sum_yz += (*yptr) * ( (*zhigh) - (*zlow) ); + sum_zx += (*zptr) * ( (*xhigh) - (*xlow) ); + + xptr += ofs_x; + xlow += ofs_x; + xhigh += ofs_x; + + yptr += ofs_y; + ylow += ofs_y; + yhigh += ofs_y; + + zptr += ofs_z; + zlow += ofs_z; + zhigh += ofs_z; + } + out = aiVector3t<TReal>(sum_yz,sum_zx,sum_xy); +} + +} // ! namespace Assimp + +#endif // AI_POLYTOOLS_H_INCLUDED diff --git a/libs/assimp/code/Common/PostStepRegistry.cpp b/libs/assimp/code/Common/PostStepRegistry.cpp new file mode 100644 index 0000000..de4f390 --- /dev/null +++ b/libs/assimp/code/Common/PostStepRegistry.cpp @@ -0,0 +1,265 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 ImporterRegistry.cpp + +Central registry for all postprocessing steps available. Do not edit this file +directly (unless you are adding new steps), instead use the +corresponding preprocessor flag to selectively disable steps. +*/ + +#include "PostProcessing/ProcessHelper.h" + +#ifndef ASSIMP_BUILD_NO_CALCTANGENTS_PROCESS +# include "PostProcessing/CalcTangentsProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_JOINVERTICES_PROCESS +# include "PostProcessing/JoinVerticesProcess.h" +#endif +#if !(defined ASSIMP_BUILD_NO_MAKELEFTHANDED_PROCESS && defined ASSIMP_BUILD_NO_FLIPUVS_PROCESS && defined ASSIMP_BUILD_NO_FLIPWINDINGORDER_PROCESS) +# include "PostProcessing/ConvertToLHProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_TRIANGULATE_PROCESS +# include "PostProcessing/TriangulateProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_DROPFACENORMALS_PROCESS +# include "PostProcessing/DropFaceNormalsProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_GENFACENORMALS_PROCESS +# include "PostProcessing/GenFaceNormalsProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_GENVERTEXNORMALS_PROCESS +# include "PostProcessing/GenVertexNormalsProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_REMOVEVC_PROCESS +# include "PostProcessing/RemoveVCProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_SPLITLARGEMESHES_PROCESS +# include "PostProcessing/SplitLargeMeshes.h" +#endif +#ifndef ASSIMP_BUILD_NO_PRETRANSFORMVERTICES_PROCESS +# include "PostProcessing/PretransformVertices.h" +#endif +#ifndef ASSIMP_BUILD_NO_LIMITBONEWEIGHTS_PROCESS +# include "PostProcessing/LimitBoneWeightsProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS +# include "PostProcessing/ValidateDataStructure.h" +#endif +#ifndef ASSIMP_BUILD_NO_IMPROVECACHELOCALITY_PROCESS +# include "PostProcessing/ImproveCacheLocality.h" +#endif +#ifndef ASSIMP_BUILD_NO_FIXINFACINGNORMALS_PROCESS +# include "PostProcessing/FixNormalsStep.h" +#endif +#ifndef ASSIMP_BUILD_NO_REMOVE_REDUNDANTMATERIALS_PROCESS +# include "PostProcessing/RemoveRedundantMaterials.h" +#endif +#if (!defined ASSIMP_BUILD_NO_EMBEDTEXTURES_PROCESS) +# include "PostProcessing/EmbedTexturesProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_FINDINVALIDDATA_PROCESS +# include "PostProcessing/FindInvalidDataProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_FINDDEGENERATES_PROCESS +# include "PostProcessing/FindDegenerates.h" +#endif +#ifndef ASSIMP_BUILD_NO_SORTBYPTYPE_PROCESS +# include "PostProcessing/SortByPTypeProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_GENUVCOORDS_PROCESS +# include "PostProcessing/ComputeUVMappingProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_TRANSFORMTEXCOORDS_PROCESS +# include "PostProcessing/TextureTransform.h" +#endif +#ifndef ASSIMP_BUILD_NO_FINDINSTANCES_PROCESS +# include "PostProcessing/FindInstancesProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_OPTIMIZEMESHES_PROCESS +# include "PostProcessing/OptimizeMeshes.h" +#endif +#ifndef ASSIMP_BUILD_NO_OPTIMIZEGRAPH_PROCESS +# include "PostProcessing/OptimizeGraph.h" +#endif +#ifndef ASSIMP_BUILD_NO_SPLITBYBONECOUNT_PROCESS +# include "PostProcessing/SplitByBoneCountProcess.h" +#endif +#ifndef ASSIMP_BUILD_NO_DEBONE_PROCESS +# include "PostProcessing/DeboneProcess.h" +#endif +#if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS) +# include "PostProcessing/ScaleProcess.h" +#endif +#if (!defined ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS) +# include "PostProcessing/ArmaturePopulate.h" +#endif +#if (!defined ASSIMP_BUILD_NO_GENBOUNDINGBOXES_PROCESS) +# include "PostProcessing/GenBoundingBoxesProcess.h" +#endif + + + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out) +{ + // ---------------------------------------------------------------------------- + // Add an instance of each post processing step here in the order + // of sequence it is executed. Steps that are added here are not + // validated - as RegisterPPStep() does - all dependencies must be given. + // ---------------------------------------------------------------------------- + out.reserve(31); +#if (!defined ASSIMP_BUILD_NO_MAKELEFTHANDED_PROCESS) + out.push_back( new MakeLeftHandedProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_FLIPUVS_PROCESS) + out.push_back( new FlipUVsProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_FLIPWINDINGORDER_PROCESS) + out.push_back( new FlipWindingOrderProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_REMOVEVC_PROCESS) + out.push_back( new RemoveVCProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_REMOVE_REDUNDANTMATERIALS_PROCESS) + out.push_back( new RemoveRedundantMatsProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_EMBEDTEXTURES_PROCESS) + out.push_back( new EmbedTexturesProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_FINDINSTANCES_PROCESS) + out.push_back( new FindInstancesProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_OPTIMIZEGRAPH_PROCESS) + out.push_back( new OptimizeGraphProcess()); +#endif +#ifndef ASSIMP_BUILD_NO_GENUVCOORDS_PROCESS + out.push_back( new ComputeUVMappingProcess()); +#endif +#ifndef ASSIMP_BUILD_NO_TRANSFORMTEXCOORDS_PROCESS + out.push_back( new TextureTransformStep()); +#endif +#if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS) + out.push_back( new ScaleProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS) + out.push_back( new ArmaturePopulate()); +#endif +#if (!defined ASSIMP_BUILD_NO_PRETRANSFORMVERTICES_PROCESS) + out.push_back( new PretransformVertices()); +#endif +#if (!defined ASSIMP_BUILD_NO_TRIANGULATE_PROCESS) + out.push_back( new TriangulateProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_FINDDEGENERATES_PROCESS) + //find degenerates should run after triangulation (to sort out small + //generated triangles) but before sort by p types (in case there are lines + //and points generated and inserted into a mesh) + out.push_back( new FindDegeneratesProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_SORTBYPTYPE_PROCESS) + out.push_back( new SortByPTypeProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_FINDINVALIDDATA_PROCESS) + out.push_back( new FindInvalidDataProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_OPTIMIZEMESHES_PROCESS) + out.push_back( new OptimizeMeshesProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_FIXINFACINGNORMALS_PROCESS) + out.push_back( new FixInfacingNormalsProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_SPLITBYBONECOUNT_PROCESS) + out.push_back( new SplitByBoneCountProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_SPLITLARGEMESHES_PROCESS) + out.push_back( new SplitLargeMeshesProcess_Triangle()); +#endif +#if (!defined ASSIMP_BUILD_NO_GENFACENORMALS_PROCESS) + out.push_back( new DropFaceNormalsProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_GENFACENORMALS_PROCESS) + out.push_back( new GenFaceNormalsProcess()); +#endif + // ......................................................................... + // DON'T change the order of these five .. + // XXX this is actually a design weakness that dates back to the time + // when Importer would maintain the postprocessing step list exclusively. + // Now that others access it too, we need a better solution. + out.push_back( new ComputeSpatialSortProcess()); + // ......................................................................... + +#if (!defined ASSIMP_BUILD_NO_GENVERTEXNORMALS_PROCESS) + out.push_back( new GenVertexNormalsProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_CALCTANGENTS_PROCESS) + out.push_back( new CalcTangentsProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_JOINVERTICES_PROCESS) + out.push_back( new JoinVerticesProcess()); +#endif + + // ......................................................................... + out.push_back( new DestroySpatialSortProcess()); + // ......................................................................... + +#if (!defined ASSIMP_BUILD_NO_SPLITLARGEMESHES_PROCESS) + out.push_back( new SplitLargeMeshesProcess_Vertex()); +#endif +#if (!defined ASSIMP_BUILD_NO_DEBONE_PROCESS) + out.push_back( new DeboneProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_LIMITBONEWEIGHTS_PROCESS) + out.push_back( new LimitBoneWeightsProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_IMPROVECACHELOCALITY_PROCESS) + out.push_back( new ImproveCacheLocalityProcess()); +#endif +#if (!defined ASSIMP_BUILD_NO_GENBOUNDINGBOXES_PROCESS) + out.push_back(new GenBoundingBoxesProcess); +#endif +} + +} diff --git a/libs/assimp/code/Common/RemoveComments.cpp b/libs/assimp/code/Common/RemoveComments.cpp new file mode 100644 index 0000000..4fae21c --- /dev/null +++ b/libs/assimp/code/Common/RemoveComments.cpp @@ -0,0 +1,121 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 RemoveComments.cpp + * @brief Defines the CommentRemover utility class + */ + +#include <assimp/RemoveComments.h> +#include <assimp/ParsingUtils.h> + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +// Remove line comments from a file +void CommentRemover::RemoveLineComments(const char* szComment, char* szBuffer, char chReplacement /* = ' ' */) { + // validate parameters + ai_assert(nullptr != szComment); + ai_assert(nullptr != szBuffer); + ai_assert(*szComment); + + size_t len = strlen(szComment); + const size_t lenBuffer = strlen(szBuffer); + if (len > lenBuffer) { + len = lenBuffer; + } + + for(size_t i = 0; i < lenBuffer; i++) { + // skip over quotes + if (szBuffer[i] == '\"' || szBuffer[i] == '\'') + while (++i < lenBuffer && szBuffer[i] != '\"' && szBuffer[i] != '\''); + + if(lenBuffer - i < len) { + break; + } + + if (!strncmp(szBuffer + i,szComment,len)) { + while (i < lenBuffer && !IsLineEnd(szBuffer[i])) { + szBuffer[i++] = chReplacement; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Remove multi-line comments from a file +void CommentRemover::RemoveMultiLineComments(const char* szCommentStart, + const char* szCommentEnd,char* szBuffer, + char chReplacement) { + // validate parameters + ai_assert(nullptr != szCommentStart); + ai_assert(nullptr != szCommentEnd); + ai_assert(nullptr != szBuffer); + ai_assert(*szCommentStart); + ai_assert(*szCommentEnd); + + const size_t len = strlen(szCommentEnd); + const size_t len2 = strlen(szCommentStart); + + while (*szBuffer) { + // skip over quotes + if (*szBuffer == '\"' || *szBuffer == '\'') { + while (*szBuffer++ && *szBuffer != '\"' && *szBuffer != '\''); + } + + if (!strncmp(szBuffer,szCommentStart,len2)) { + while (*szBuffer) { + if (!::strncmp(szBuffer,szCommentEnd,len)) { + for (unsigned int i = 0; i < len;++i) { + *szBuffer++ = chReplacement; + } + + break; + } + *szBuffer++ = chReplacement; + } + continue; + } + ++szBuffer; + } +} + +} // !! Assimp diff --git a/libs/assimp/code/Common/SGSpatialSort.cpp b/libs/assimp/code/Common/SGSpatialSort.cpp new file mode 100644 index 0000000..0d16d6f --- /dev/null +++ b/libs/assimp/code/Common/SGSpatialSort.cpp @@ -0,0 +1,168 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the helper class to quickly find +vertices close to a given position. Special implementation for +the 3ds loader handling smooth groups correctly */ + +#include <assimp/SGSpatialSort.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +SGSpatialSort::SGSpatialSort() +{ + // define the reference plane. We choose some arbitrary vector away from all basic axes + // in the hope that no model spreads all its vertices along this plane. + mPlaneNormal.Set( 0.8523f, 0.34321f, 0.5736f); + mPlaneNormal.Normalize(); +} +// ------------------------------------------------------------------------------------------------ +// Destructor +SGSpatialSort::~SGSpatialSort() +{ + // nothing to do here, everything destructs automatically +} +// ------------------------------------------------------------------------------------------------ +void SGSpatialSort::Add(const aiVector3D& vPosition, unsigned int index, + unsigned int smoothingGroup) +{ + // store position by index and distance + float distance = vPosition * mPlaneNormal; + mPositions.push_back( Entry( index, vPosition, + distance, smoothingGroup)); +} +// ------------------------------------------------------------------------------------------------ +void SGSpatialSort::Prepare() +{ + // now sort the array ascending by distance. + std::sort( this->mPositions.begin(), this->mPositions.end()); +} +// ------------------------------------------------------------------------------------------------ +// Returns an iterator for all positions close to the given position. +void SGSpatialSort::FindPositions( const aiVector3D& pPosition, + uint32_t pSG, + float pRadius, + std::vector<unsigned int>& poResults, + bool exactMatch /*= false*/) const +{ + float dist = pPosition * mPlaneNormal; + float minDist = dist - pRadius, maxDist = dist + pRadius; + + // clear the array + poResults.clear(); + + // quick check for positions outside the range + if( mPositions.empty() ) + return; + if( maxDist < mPositions.front().mDistance) + return; + if( minDist > mPositions.back().mDistance) + return; + + // do a binary search for the minimal distance to start the iteration there + unsigned int index = (unsigned int)mPositions.size() / 2; + unsigned int binaryStepSize = (unsigned int)mPositions.size() / 4; + while( binaryStepSize > 1) + { + if( mPositions[index].mDistance < minDist) + index += binaryStepSize; + else + index -= binaryStepSize; + + binaryStepSize /= 2; + } + + // depending on the direction of the last step we need to single step a bit back or forth + // to find the actual beginning element of the range + while( index > 0 && mPositions[index].mDistance > minDist) + index--; + while( index < (mPositions.size() - 1) && mPositions[index].mDistance < minDist) + index++; + + // Mow start iterating from there until the first position lays outside of the distance range. + // Add all positions inside the distance range within the given radius to the result array + + float squareEpsilon = pRadius * pRadius; + std::vector<Entry>::const_iterator it = mPositions.begin() + index; + std::vector<Entry>::const_iterator end = mPositions.end(); + + if (exactMatch) + { + while( it->mDistance < maxDist) + { + if((it->mPosition - pPosition).SquareLength() < squareEpsilon && it->mSmoothGroups == pSG) + { + poResults.push_back( it->mIndex); + } + ++it; + if( end == it )break; + } + } + else + { + // if the given smoothing group is 0, we'll return all surrounding vertices + if (!pSG) + { + while( it->mDistance < maxDist) + { + if((it->mPosition - pPosition).SquareLength() < squareEpsilon) + poResults.push_back( it->mIndex); + ++it; + if( end == it)break; + } + } + else while( it->mDistance < maxDist) + { + if((it->mPosition - pPosition).SquareLength() < squareEpsilon && + (it->mSmoothGroups & pSG || !it->mSmoothGroups)) + { + poResults.push_back( it->mIndex); + } + ++it; + if( end == it)break; + } + } +} + + diff --git a/libs/assimp/code/Common/SceneCombiner.cpp b/libs/assimp/code/Common/SceneCombiner.cpp new file mode 100644 index 0000000..2c2539e --- /dev/null +++ b/libs/assimp/code/Common/SceneCombiner.cpp @@ -0,0 +1,1375 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +// TODO: refactor entire file to get rid of the "flat-copy" first approach +// to copying structures. This easily breaks in the most unintuitive way +// possible as new fields are added to assimp structures. + +// ---------------------------------------------------------------------------- +/** + * @file Implements Assimp::SceneCombiner. This is a smart utility + * class that combines multiple scenes, meshes, ... into one. Currently + * these utilities are used by the IRR and LWS loaders and the + * OptimizeGraph step. + */ +// ---------------------------------------------------------------------------- +#include "ScenePrivate.h" +#include "time.h" +#include <assimp/Hash.h> +#include <assimp/SceneCombiner.h> +#include <assimp/StringUtils.h> +#include <assimp/fast_atof.h> +#include <assimp/mesh.h> +#include <assimp/metadata.h> +#include <assimp/scene.h> +#include <stdio.h> +#include <assimp/DefaultLogger.hpp> + +namespace Assimp { + +#if (__GNUC__ >= 8 && __GNUC_MINOR__ >= 0) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclass-memaccess" +#endif + +// ------------------------------------------------------------------------------------------------ +// Add a prefix to a string +inline void PrefixString(aiString &string, const char *prefix, unsigned int len) { + // If the string is already prefixed, we won't prefix it a second time + if (string.length >= 1 && string.data[0] == '$') + return; + + if (len + string.length >= MAXLEN - 1) { + ASSIMP_LOG_VERBOSE_DEBUG("Can't add an unique prefix because the string is too long"); + ai_assert(false); + return; + } + + // Add the prefix + ::memmove(string.data + len, string.data, string.length + 1); + ::memcpy(string.data, prefix, len); + + // And update the string's length + string.length += len; +} + +// ------------------------------------------------------------------------------------------------ +// Add node identifiers to a hashing set +void SceneCombiner::AddNodeHashes(aiNode *node, std::set<unsigned int> &hashes) { + // Add node name to hashing set if it is non-empty - empty nodes are allowed + // and they can't have any anims assigned so its absolutely safe to duplicate them. + if (node->mName.length) { + hashes.insert(SuperFastHash(node->mName.data, static_cast<uint32_t>(node->mName.length))); + } + + // Process all children recursively + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + AddNodeHashes(node->mChildren[i], hashes); + } +} + +// ------------------------------------------------------------------------------------------------ +// Add a name prefix to all nodes in a hierarchy +void SceneCombiner::AddNodePrefixes(aiNode *node, const char *prefix, unsigned int len) { + ai_assert(nullptr != prefix); + + PrefixString(node->mName, prefix, len); + + // Process all children recursively + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + AddNodePrefixes(node->mChildren[i], prefix, len); + } +} + +// ------------------------------------------------------------------------------------------------ +// Search for matching names +bool SceneCombiner::FindNameMatch(const aiString &name, std::vector<SceneHelper> &input, unsigned int cur) { + const unsigned int hash = SuperFastHash(name.data, static_cast<uint32_t>(name.length)); + + // Check whether we find a positive match in one of the given sets + for (unsigned int i = 0; i < input.size(); ++i) { + if (cur != i && input[i].hashes.find(hash) != input[i].hashes.end()) { + return true; + } + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +// Add a name prefix to all nodes in a hierarchy if a hash match is found +void SceneCombiner::AddNodePrefixesChecked(aiNode *node, const char *prefix, unsigned int len, + std::vector<SceneHelper> &input, unsigned int cur) { + ai_assert(nullptr != prefix); + + const unsigned int hash = SuperFastHash(node->mName.data, static_cast<uint32_t>(node->mName.length)); + + // Check whether we find a positive match in one of the given sets + for (unsigned int i = 0; i < input.size(); ++i) { + if (cur != i && input[i].hashes.find(hash) != input[i].hashes.end()) { + PrefixString(node->mName, prefix, len); + break; + } + } + + // Process all children recursively + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + AddNodePrefixesChecked(node->mChildren[i], prefix, len, input, cur); + } +} + +// ------------------------------------------------------------------------------------------------ +// Add an offset to all mesh indices in a node graph +void SceneCombiner::OffsetNodeMeshIndices(aiNode *node, unsigned int offset) { + for (unsigned int i = 0; i < node->mNumMeshes; ++i) + node->mMeshes[i] += offset; + + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + OffsetNodeMeshIndices(node->mChildren[i], offset); + } +} + +// ------------------------------------------------------------------------------------------------ +// Merges two scenes. Currently only used by the LWS loader. +void SceneCombiner::MergeScenes(aiScene **_dest, std::vector<aiScene *> &src, unsigned int flags) { + if (nullptr == _dest) { + return; + } + + // if _dest points to nullptr allocate a new scene. Otherwise clear the old and reuse it + if (src.empty()) { + if (*_dest) { + (*_dest)->~aiScene(); + SceneCombiner::CopySceneFlat(_dest, src[0]); + } else + *_dest = src[0]; + return; + } + if (*_dest) { + (*_dest)->~aiScene(); + new (*_dest) aiScene(); + } else + *_dest = new aiScene(); + + // Create a dummy scene to serve as master for the others + aiScene *master = new aiScene(); + master->mRootNode = new aiNode(); + master->mRootNode->mName.Set("<MergeRoot>"); + + std::vector<AttachmentInfo> srcList(src.size()); + for (unsigned int i = 0; i < srcList.size(); ++i) { + srcList[i] = AttachmentInfo(src[i], master->mRootNode); + } + + // 'master' will be deleted afterwards + MergeScenes(_dest, master, srcList, flags); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::AttachToGraph(aiNode *attach, std::vector<NodeAttachmentInfo> &srcList) { + unsigned int cnt; + for (cnt = 0; cnt < attach->mNumChildren; ++cnt) { + AttachToGraph(attach->mChildren[cnt], srcList); + } + + cnt = 0; + for (std::vector<NodeAttachmentInfo>::iterator it = srcList.begin(); + it != srcList.end(); ++it) { + if ((*it).attachToNode == attach && !(*it).resolved) + ++cnt; + } + + if (cnt) { + aiNode **n = new aiNode *[cnt + attach->mNumChildren]; + if (attach->mNumChildren) { + ::memcpy(n, attach->mChildren, sizeof(void *) * attach->mNumChildren); + delete[] attach->mChildren; + } + attach->mChildren = n; + + n += attach->mNumChildren; + attach->mNumChildren += cnt; + + for (unsigned int i = 0; i < srcList.size(); ++i) { + NodeAttachmentInfo &att = srcList[i]; + if (att.attachToNode == attach && !att.resolved) { + *n = att.node; + (**n).mParent = attach; + ++n; + + // mark this attachment as resolved + att.resolved = true; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::AttachToGraph(aiScene *master, std::vector<NodeAttachmentInfo> &src) { + ai_assert(nullptr != master); + + AttachToGraph(master->mRootNode, src); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::MergeScenes(aiScene **_dest, aiScene *master, std::vector<AttachmentInfo> &srcList, unsigned int flags) { + if (nullptr == _dest) { + return; + } + + // if _dest points to nullptr allocate a new scene. Otherwise clear the old and reuse it + if (srcList.empty()) { + if (*_dest) { + SceneCombiner::CopySceneFlat(_dest, master); + } else + *_dest = master; + return; + } + if (*_dest) { + (*_dest)->~aiScene(); + new (*_dest) aiScene(); + } else + *_dest = new aiScene(); + + aiScene *dest = *_dest; + + std::vector<SceneHelper> src(srcList.size() + 1); + src[0].scene = master; + for (unsigned int i = 0; i < srcList.size(); ++i) { + src[i + 1] = SceneHelper(srcList[i].scene); + } + + // this helper array specifies which scenes are duplicates of others + std::vector<unsigned int> duplicates(src.size(), UINT_MAX); + + // this helper array is used as lookup table several times + std::vector<unsigned int> offset(src.size()); + + // Find duplicate scenes + for (unsigned int i = 0; i < src.size(); ++i) { + if (duplicates[i] != i && duplicates[i] != UINT_MAX) { + continue; + } + + duplicates[i] = i; + for (unsigned int a = i + 1; a < src.size(); ++a) { + if (src[i].scene == src[a].scene) { + duplicates[a] = i; + } + } + } + + // Generate unique names for all named stuff? + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) { +#if 0 + // Construct a proper random number generator + boost::mt19937 rng( ); + boost::uniform_int<> dist(1u,1 << 24u); + boost::variate_generator<boost::mt19937&, boost::uniform_int<> > rndGen(rng, dist); +#endif + for (unsigned int i = 1; i < src.size(); ++i) { + //if (i != duplicates[i]) + //{ + // // duplicate scenes share the same UID + // ::strcpy( src[i].id, src[duplicates[i]].id ); + // src[i].idlen = src[duplicates[i]].idlen; + + // continue; + //} + + src[i].idlen = ai_snprintf(src[i].id, 32, "$%.6X$_", i); + + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { + + // Compute hashes for all identifiers in this scene and store them + // in a sorted table (for convenience I'm using std::set). We hash + // just the node and animation channel names, all identifiers except + // the material names should be caught by doing this. + AddNodeHashes(src[i]->mRootNode, src[i].hashes); + + for (unsigned int a = 0; a < src[i]->mNumAnimations; ++a) { + aiAnimation *anim = src[i]->mAnimations[a]; + src[i].hashes.insert(SuperFastHash(anim->mName.data, static_cast<uint32_t>(anim->mName.length))); + } + } + } + } + + unsigned int cnt; + + // First find out how large the respective output arrays must be + for (unsigned int n = 0; n < src.size(); ++n) { + SceneHelper *cur = &src[n]; + + if (n == duplicates[n] || flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) { + dest->mNumTextures += (*cur)->mNumTextures; + dest->mNumMaterials += (*cur)->mNumMaterials; + dest->mNumMeshes += (*cur)->mNumMeshes; + } + + dest->mNumLights += (*cur)->mNumLights; + dest->mNumCameras += (*cur)->mNumCameras; + dest->mNumAnimations += (*cur)->mNumAnimations; + + // Combine the flags of all scenes + // We need to process them flag-by-flag here to get correct results + // dest->mFlags ; //|= (*cur)->mFlags; + if ((*cur)->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) { + dest->mFlags |= AI_SCENE_FLAGS_NON_VERBOSE_FORMAT; + } + } + + // generate the output texture list + an offset table for all texture indices + if (dest->mNumTextures) { + aiTexture **pip = dest->mTextures = new aiTexture *[dest->mNumTextures]; + cnt = 0; + for (unsigned int n = 0; n < src.size(); ++n) { + SceneHelper *cur = &src[n]; + for (unsigned int i = 0; i < (*cur)->mNumTextures; ++i) { + if (n != duplicates[n]) { + if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) + Copy(pip, (*cur)->mTextures[i]); + + else + continue; + } else + *pip = (*cur)->mTextures[i]; + ++pip; + } + + offset[n] = cnt; + cnt = (unsigned int)(pip - dest->mTextures); + } + } + + // generate the output material list + an offset table for all material indices + if (dest->mNumMaterials) { + aiMaterial **pip = dest->mMaterials = new aiMaterial *[dest->mNumMaterials]; + cnt = 0; + for (unsigned int n = 0; n < src.size(); ++n) { + SceneHelper *cur = &src[n]; + for (unsigned int i = 0; i < (*cur)->mNumMaterials; ++i) { + if (n != duplicates[n]) { + if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) + Copy(pip, (*cur)->mMaterials[i]); + + else + continue; + } else + *pip = (*cur)->mMaterials[i]; + + if ((*cur)->mNumTextures != dest->mNumTextures) { + // We need to update all texture indices of the mesh. So we need to search for + // a material property called '$tex.file' + + for (unsigned int a = 0; a < (*pip)->mNumProperties; ++a) { + aiMaterialProperty *prop = (*pip)->mProperties[a]; + if (!strncmp(prop->mKey.data, "$tex.file", 9)) { + // Check whether this texture is an embedded texture. + // In this case the property looks like this: *<n>, + // where n is the index of the texture. + // Copy here because we overwrite the string data in-place and the buffer inside of aiString + // will be a lie if we just reinterpret from prop->mData. The size of mData is not guaranteed to be + // MAXLEN in size. + aiString s(*(aiString *)prop->mData); + if ('*' == s.data[0]) { + // Offset the index and write it back .. + const unsigned int idx = strtoul10(&s.data[1]) + offset[n]; + const unsigned int oldLen = s.length; + + s.length = 1 + ASSIMP_itoa10(&s.data[1], sizeof(s.data) - 1, idx); + + // The string changed in size so we need to reallocate the buffer for the property. + if (oldLen < s.length) { + prop->mDataLength += s.length - oldLen; + delete[] prop->mData; + prop->mData = new char[prop->mDataLength]; + } + + memcpy(prop->mData, static_cast<void*>(&s), prop->mDataLength); + } + } + + // Need to generate new, unique material names? + else if (!::strcmp(prop->mKey.data, "$mat.name") && flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) { + aiString *pcSrc = (aiString *)prop->mData; + PrefixString(*pcSrc, (*cur).id, (*cur).idlen); + } + } + } + ++pip; + } + + offset[n] = cnt; + cnt = (unsigned int)(pip - dest->mMaterials); + } + } + + // generate the output mesh list + again an offset table for all mesh indices + if (dest->mNumMeshes) { + aiMesh **pip = dest->mMeshes = new aiMesh *[dest->mNumMeshes]; + cnt = 0; + for (unsigned int n = 0; n < src.size(); ++n) { + SceneHelper *cur = &src[n]; + for (unsigned int i = 0; i < (*cur)->mNumMeshes; ++i) { + if (n != duplicates[n]) { + if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) + Copy(pip, (*cur)->mMeshes[i]); + + else + continue; + } else + *pip = (*cur)->mMeshes[i]; + + // update the material index of the mesh + (*pip)->mMaterialIndex += offset[n]; + ++pip; + } + + // reuse the offset array - store now the mesh offset in it + offset[n] = cnt; + cnt = (unsigned int)(pip - dest->mMeshes); + } + } + + std::vector<NodeAttachmentInfo> nodes; + nodes.reserve(srcList.size()); + + // ---------------------------------------------------------------------------- + // Now generate the output node graph. We need to make those + // names in the graph that are referenced by anims or lights + // or cameras unique. So we add a prefix to them ... $<rand>_ + // We could also use a counter, but using a random value allows us to + // use just one prefix if we are joining multiple scene hierarchies recursively. + // Chances are quite good we don't collide, so we try that ... + // ---------------------------------------------------------------------------- + + // Allocate space for light sources, cameras and animations + aiLight **ppLights = dest->mLights = (dest->mNumLights ? new aiLight *[dest->mNumLights] : nullptr); + + aiCamera **ppCameras = dest->mCameras = (dest->mNumCameras ? new aiCamera *[dest->mNumCameras] : nullptr); + + aiAnimation **ppAnims = dest->mAnimations = (dest->mNumAnimations ? new aiAnimation *[dest->mNumAnimations] : nullptr); + + for (int n = static_cast<int>(src.size() - 1); n >= 0; --n) /* !!! important !!! */ + { + SceneHelper *cur = &src[n]; + aiNode *node; + + // To offset or not to offset, this is the question + if (n != (int)duplicates[n]) { + // Get full scene-graph copy + Copy(&node, (*cur)->mRootNode); + OffsetNodeMeshIndices(node, offset[duplicates[n]]); + + if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) { + // (note:) they are already 'offseted' by offset[duplicates[n]] + OffsetNodeMeshIndices(node, offset[n] - offset[duplicates[n]]); + } + } else // if (n == duplicates[n]) + { + node = (*cur)->mRootNode; + OffsetNodeMeshIndices(node, offset[n]); + } + if (n) // src[0] is the master node + nodes.push_back(NodeAttachmentInfo(node, srcList[n - 1].attachToNode, n)); + + // add name prefixes? + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) { + + // or the whole scenegraph + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { + AddNodePrefixesChecked(node, (*cur).id, (*cur).idlen, src, n); + } else + AddNodePrefixes(node, (*cur).id, (*cur).idlen); + + // meshes + for (unsigned int i = 0; i < (*cur)->mNumMeshes; ++i) { + aiMesh *mesh = (*cur)->mMeshes[i]; + + // rename all bones + for (unsigned int a = 0; a < mesh->mNumBones; ++a) { + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { + if (!FindNameMatch(mesh->mBones[a]->mName, src, n)) + continue; + } + PrefixString(mesh->mBones[a]->mName, (*cur).id, (*cur).idlen); + } + } + } + + // -------------------------------------------------------------------- + // Copy light sources + for (unsigned int i = 0; i < (*cur)->mNumLights; ++i, ++ppLights) { + if (n != (int)duplicates[n]) // duplicate scene? + { + Copy(ppLights, (*cur)->mLights[i]); + } else + *ppLights = (*cur)->mLights[i]; + + // Add name prefixes? + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) { + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { + if (!FindNameMatch((*ppLights)->mName, src, n)) + continue; + } + + PrefixString((*ppLights)->mName, (*cur).id, (*cur).idlen); + } + } + + // -------------------------------------------------------------------- + // Copy cameras + for (unsigned int i = 0; i < (*cur)->mNumCameras; ++i, ++ppCameras) { + if (n != (int)duplicates[n]) // duplicate scene? + { + Copy(ppCameras, (*cur)->mCameras[i]); + } else + *ppCameras = (*cur)->mCameras[i]; + + // Add name prefixes? + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) { + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { + if (!FindNameMatch((*ppCameras)->mName, src, n)) + continue; + } + + PrefixString((*ppCameras)->mName, (*cur).id, (*cur).idlen); + } + } + + // -------------------------------------------------------------------- + // Copy animations + for (unsigned int i = 0; i < (*cur)->mNumAnimations; ++i, ++ppAnims) { + if (n != (int)duplicates[n]) // duplicate scene? + { + Copy(ppAnims, (*cur)->mAnimations[i]); + } else + *ppAnims = (*cur)->mAnimations[i]; + + // Add name prefixes? + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) { + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { + if (!FindNameMatch((*ppAnims)->mName, src, n)) + continue; + } + + PrefixString((*ppAnims)->mName, (*cur).id, (*cur).idlen); + + // don't forget to update all node animation channels + for (unsigned int a = 0; a < (*ppAnims)->mNumChannels; ++a) { + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { + if (!FindNameMatch((*ppAnims)->mChannels[a]->mNodeName, src, n)) + continue; + } + + PrefixString((*ppAnims)->mChannels[a]->mNodeName, (*cur).id, (*cur).idlen); + } + } + } + } + + // Now build the output graph + AttachToGraph(master, nodes); + dest->mRootNode = master->mRootNode; + + // Check whether we succeeded at building the output graph + for (std::vector<NodeAttachmentInfo>::iterator it = nodes.begin(); + it != nodes.end(); ++it) { + if (!(*it).resolved) { + if (flags & AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS) { + // search for this attachment point in all other imported scenes, too. + for (unsigned int n = 0; n < src.size(); ++n) { + if (n != (*it).src_idx) { + AttachToGraph(src[n].scene, nodes); + if ((*it).resolved) + break; + } + } + } + if (!(*it).resolved) { + ASSIMP_LOG_ERROR("SceneCombiner: Failed to resolve attachment ", (*it).node->mName.data, + " ", (*it).attachToNode->mName.data); + } + } + } + + // now delete all input scenes. Make sure duplicate scenes aren't + // deleted more than one time + for (unsigned int n = 0; n < src.size(); ++n) { + if (n != duplicates[n]) // duplicate scene? + continue; + + aiScene *deleteMe = src[n].scene; + + // We need to delete the arrays before the destructor is called - + // we are reusing the array members + delete[] deleteMe->mMeshes; + deleteMe->mMeshes = nullptr; + delete[] deleteMe->mCameras; + deleteMe->mCameras = nullptr; + delete[] deleteMe->mLights; + deleteMe->mLights = nullptr; + delete[] deleteMe->mMaterials; + deleteMe->mMaterials = nullptr; + delete[] deleteMe->mAnimations; + deleteMe->mAnimations = nullptr; + delete[] deleteMe->mTextures; + deleteMe->mTextures = nullptr; + + deleteMe->mRootNode = nullptr; + + // Now we can safely delete the scene + delete deleteMe; + } + + // Check flags + if (!dest->mNumMeshes || !dest->mNumMaterials) { + dest->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } + + // We're finished +} + +// ------------------------------------------------------------------------------------------------ +// Build a list of unique bones +void SceneCombiner::BuildUniqueBoneList(std::list<BoneWithHash> &asBones, + std::vector<aiMesh *>::const_iterator it, + std::vector<aiMesh *>::const_iterator end) { + unsigned int iOffset = 0; + for (; it != end; ++it) { + for (unsigned int l = 0; l < (*it)->mNumBones; ++l) { + aiBone *p = (*it)->mBones[l]; + uint32_t itml = SuperFastHash(p->mName.data, (unsigned int)p->mName.length); + + std::list<BoneWithHash>::iterator it2 = asBones.begin(); + std::list<BoneWithHash>::iterator end2 = asBones.end(); + + for (; it2 != end2; ++it2) { + if ((*it2).first == itml) { + (*it2).pSrcBones.push_back(BoneSrcIndex(p, iOffset)); + break; + } + } + if (end2 == it2) { + // need to begin a new bone entry + asBones.push_back(BoneWithHash()); + BoneWithHash &btz = asBones.back(); + + // setup members + btz.first = itml; + btz.second = &p->mName; + btz.pSrcBones.push_back(BoneSrcIndex(p, iOffset)); + } + } + iOffset += (*it)->mNumVertices; + } +} + +// ------------------------------------------------------------------------------------------------ +// Merge a list of bones +void SceneCombiner::MergeBones(aiMesh *out, std::vector<aiMesh *>::const_iterator it, + std::vector<aiMesh *>::const_iterator end) { + if (nullptr == out || out->mNumBones == 0) { + return; + } + + // find we need to build an unique list of all bones. + // we work with hashes to make the comparisons MUCH faster, + // at least if we have many bones. + std::list<BoneWithHash> asBones; + BuildUniqueBoneList(asBones, it, end); + + // now create the output bones + out->mNumBones = 0; + out->mBones = new aiBone *[asBones.size()]; + + for (std::list<BoneWithHash>::const_iterator boneIt = asBones.begin(), boneEnd = asBones.end(); boneIt != boneEnd; ++boneIt) { + // Allocate a bone and setup it's name + aiBone *pc = out->mBones[out->mNumBones++] = new aiBone(); + pc->mName = aiString(*(boneIt->second)); + + std::vector<BoneSrcIndex>::const_iterator wend = boneIt->pSrcBones.end(); + + // Loop through all bones to be joined for this bone + for (std::vector<BoneSrcIndex>::const_iterator wmit = boneIt->pSrcBones.begin(); wmit != wend; ++wmit) { + pc->mNumWeights += (*wmit).first->mNumWeights; + + // NOTE: different offset matrices for bones with equal names + // are - at the moment - not handled correctly. + if (wmit != boneIt->pSrcBones.begin() && pc->mOffsetMatrix != wmit->first->mOffsetMatrix) { + ASSIMP_LOG_WARN("Bones with equal names but different offset matrices can't be joined at the moment"); + continue; + } + pc->mOffsetMatrix = wmit->first->mOffsetMatrix; + } + + // Allocate the vertex weight array + aiVertexWeight *avw = pc->mWeights = new aiVertexWeight[pc->mNumWeights]; + + // And copy the final weights - adjust the vertex IDs by the + // face index offset of the corresponding mesh. + for (std::vector<BoneSrcIndex>::const_iterator wmit = (*boneIt).pSrcBones.begin(); wmit != (*boneIt).pSrcBones.end(); ++wmit) { + if (wmit == wend) { + break; + } + + aiBone *pip = (*wmit).first; + for (unsigned int mp = 0; mp < pip->mNumWeights; ++mp, ++avw) { + const aiVertexWeight &vfi = pip->mWeights[mp]; + avw->mWeight = vfi.mWeight; + avw->mVertexId = vfi.mVertexId + (*wmit).second; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Merge a list of meshes +void SceneCombiner::MergeMeshes(aiMesh **_out, unsigned int /*flags*/, + std::vector<aiMesh *>::const_iterator begin, + std::vector<aiMesh *>::const_iterator end) { + if (nullptr == _out) { + return; + } + + if (begin == end) { + *_out = nullptr; // no meshes ... + return; + } + + // Allocate the output mesh + aiMesh *out = *_out = new aiMesh(); + out->mMaterialIndex = (*begin)->mMaterialIndex; + + std::string name; + // Find out how much output storage we'll need + for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { + const char *meshName((*it)->mName.C_Str()); + name += std::string(meshName); + if (it != end - 1) { + name += "."; + } + out->mNumVertices += (*it)->mNumVertices; + out->mNumFaces += (*it)->mNumFaces; + out->mNumBones += (*it)->mNumBones; + + // combine primitive type flags + out->mPrimitiveTypes |= (*it)->mPrimitiveTypes; + } + out->mName.Set(name.c_str()); + + if (out->mNumVertices) { + aiVector3D *pv2; + + // copy vertex positions + if ((**begin).HasPositions()) { + + pv2 = out->mVertices = new aiVector3D[out->mNumVertices]; + for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { + if ((*it)->mVertices) { + ::memcpy(pv2, (*it)->mVertices, (*it)->mNumVertices * sizeof(aiVector3D)); + } else + ASSIMP_LOG_WARN("JoinMeshes: Positions expected but input mesh contains no positions"); + pv2 += (*it)->mNumVertices; + } + } + // copy normals + if ((**begin).HasNormals()) { + + pv2 = out->mNormals = new aiVector3D[out->mNumVertices]; + for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { + if ((*it)->mNormals) { + ::memcpy(pv2, (*it)->mNormals, (*it)->mNumVertices * sizeof(aiVector3D)); + } else { + ASSIMP_LOG_WARN("JoinMeshes: Normals expected but input mesh contains no normals"); + } + pv2 += (*it)->mNumVertices; + } + } + // copy tangents and bi-tangents + if ((**begin).HasTangentsAndBitangents()) { + + pv2 = out->mTangents = new aiVector3D[out->mNumVertices]; + aiVector3D *pv2b = out->mBitangents = new aiVector3D[out->mNumVertices]; + + for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { + if ((*it)->mTangents) { + ::memcpy(pv2, (*it)->mTangents, (*it)->mNumVertices * sizeof(aiVector3D)); + ::memcpy(pv2b, (*it)->mBitangents, (*it)->mNumVertices * sizeof(aiVector3D)); + } else { + ASSIMP_LOG_WARN("JoinMeshes: Tangents expected but input mesh contains no tangents"); + } + pv2 += (*it)->mNumVertices; + pv2b += (*it)->mNumVertices; + } + } + // copy texture coordinates + unsigned int n = 0; + while ((**begin).HasTextureCoords(n)) { + out->mNumUVComponents[n] = (*begin)->mNumUVComponents[n]; + + pv2 = out->mTextureCoords[n] = new aiVector3D[out->mNumVertices]; + for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { + if ((*it)->mTextureCoords[n]) { + ::memcpy(pv2, (*it)->mTextureCoords[n], (*it)->mNumVertices * sizeof(aiVector3D)); + } else { + ASSIMP_LOG_WARN("JoinMeshes: UVs expected but input mesh contains no UVs"); + } + pv2 += (*it)->mNumVertices; + } + ++n; + } + // copy vertex colors + n = 0; + while ((**begin).HasVertexColors(n)) { + aiColor4D *pVec2 = out->mColors[n] = new aiColor4D[out->mNumVertices]; + for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { + if ((*it)->mColors[n]) { + ::memcpy(pVec2, (*it)->mColors[n], (*it)->mNumVertices * sizeof(aiColor4D)); + } else { + ASSIMP_LOG_WARN("JoinMeshes: VCs expected but input mesh contains no VCs"); + } + pVec2 += (*it)->mNumVertices; + } + ++n; + } + } + + if (out->mNumFaces) // just for safety + { + // copy faces + out->mFaces = new aiFace[out->mNumFaces]; + aiFace *pf2 = out->mFaces; + + unsigned int ofs = 0; + for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { + for (unsigned int m = 0; m < (*it)->mNumFaces; ++m, ++pf2) { + aiFace &face = (*it)->mFaces[m]; + pf2->mNumIndices = face.mNumIndices; + pf2->mIndices = face.mIndices; + + if (ofs) { + // add the offset to the vertex + for (unsigned int q = 0; q < face.mNumIndices; ++q) { + face.mIndices[q] += ofs; + } + } + face.mIndices = nullptr; + } + ofs += (*it)->mNumVertices; + } + } + + // bones - as this is quite lengthy, I moved the code to a separate function + if (out->mNumBones) + MergeBones(out, begin, end); + + // delete all source meshes + for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) + delete *it; +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::MergeMaterials(aiMaterial **dest, + std::vector<aiMaterial *>::const_iterator begin, + std::vector<aiMaterial *>::const_iterator end) { + if (nullptr == dest) { + return; + } + + if (begin == end) { + *dest = nullptr; // no materials ... + return; + } + + // Allocate the output material + aiMaterial *out = *dest = new aiMaterial(); + + // Get the maximal number of properties + unsigned int size = 0; + for (std::vector<aiMaterial *>::const_iterator it = begin; it != end; ++it) { + size += (*it)->mNumProperties; + } + + out->Clear(); + delete[] out->mProperties; + + out->mNumAllocated = size; + out->mNumProperties = 0; + out->mProperties = new aiMaterialProperty *[out->mNumAllocated]; + + for (std::vector<aiMaterial *>::const_iterator it = begin; it != end; ++it) { + for (unsigned int i = 0; i < (*it)->mNumProperties; ++i) { + aiMaterialProperty *sprop = (*it)->mProperties[i]; + + // Test if we already have a matching property + const aiMaterialProperty *prop_exist; + if (aiGetMaterialProperty(out, sprop->mKey.C_Str(), sprop->mSemantic, sprop->mIndex, &prop_exist) != AI_SUCCESS) { + // If not, we add it to the new material + aiMaterialProperty *prop = out->mProperties[out->mNumProperties] = new aiMaterialProperty(); + + prop->mDataLength = sprop->mDataLength; + prop->mData = new char[prop->mDataLength]; + ::memcpy(prop->mData, sprop->mData, prop->mDataLength); + + prop->mIndex = sprop->mIndex; + prop->mSemantic = sprop->mSemantic; + prop->mKey = sprop->mKey; + prop->mType = sprop->mType; + + out->mNumProperties++; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +template <typename Type> +inline void CopyPtrArray(Type **&dest, const Type *const *src, ai_uint num) { + if (!num) { + dest = nullptr; + return; + } + dest = new Type *[num]; + for (ai_uint i = 0; i < num; ++i) { + SceneCombiner::Copy(&dest[i], src[i]); + } +} + +// ------------------------------------------------------------------------------------------------ +template <typename Type> +inline void GetArrayCopy(Type *&dest, ai_uint num) { + if (!dest) { + return; + } + Type *old = dest; + + dest = new Type[num]; + ::memcpy(dest, old, sizeof(Type) * num); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::CopySceneFlat(aiScene **_dest, const aiScene *src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + // reuse the old scene or allocate a new? + if (*_dest) { + (*_dest)->~aiScene(); + new (*_dest) aiScene(); + } else { + *_dest = new aiScene(); + } + CopyScene(_dest, src, false); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::CopyScene(aiScene **_dest, const aiScene *src, bool allocate) { + if (nullptr == _dest || nullptr == src) { + return; + } + + if (allocate) { + *_dest = new aiScene(); + } + aiScene *dest = *_dest; + ai_assert(nullptr != dest); + + // copy metadata + if (nullptr != src->mMetaData) { + dest->mMetaData = new aiMetadata(*src->mMetaData); + } + + // copy animations + dest->mNumAnimations = src->mNumAnimations; + CopyPtrArray(dest->mAnimations, src->mAnimations, + dest->mNumAnimations); + + // copy textures + dest->mNumTextures = src->mNumTextures; + CopyPtrArray(dest->mTextures, src->mTextures, + dest->mNumTextures); + + // copy materials + dest->mNumMaterials = src->mNumMaterials; + CopyPtrArray(dest->mMaterials, src->mMaterials, + dest->mNumMaterials); + + // copy lights + dest->mNumLights = src->mNumLights; + CopyPtrArray(dest->mLights, src->mLights, + dest->mNumLights); + + // copy cameras + dest->mNumCameras = src->mNumCameras; + CopyPtrArray(dest->mCameras, src->mCameras, + dest->mNumCameras); + + // copy meshes + dest->mNumMeshes = src->mNumMeshes; + CopyPtrArray(dest->mMeshes, src->mMeshes, + dest->mNumMeshes); + + // now - copy the root node of the scene (deep copy, too) + Copy(&dest->mRootNode, src->mRootNode); + + // and keep the flags ... + dest->mFlags = src->mFlags; + + // source private data might be nullptr if the scene is user-allocated (i.e. for use with the export API) + if (dest->mPrivate != nullptr) { + ScenePriv(dest)->mPPStepsApplied = ScenePriv(src) ? ScenePriv(src)->mPPStepsApplied : 0; + } +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy(aiMesh **_dest, const aiMesh *src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + aiMesh *dest = *_dest = new aiMesh(); + + // get a flat copy + *dest = *src; + + // and reallocate all arrays + GetArrayCopy(dest->mVertices, dest->mNumVertices); + GetArrayCopy(dest->mNormals, dest->mNumVertices); + GetArrayCopy(dest->mTangents, dest->mNumVertices); + GetArrayCopy(dest->mBitangents, dest->mNumVertices); + + unsigned int n = 0; + while (dest->HasTextureCoords(n)) { + GetArrayCopy(dest->mTextureCoords[n++], dest->mNumVertices); + } + + n = 0; + while (dest->HasVertexColors(n)) { + GetArrayCopy(dest->mColors[n++], dest->mNumVertices); + } + + // make a deep copy of all bones + CopyPtrArray(dest->mBones, dest->mBones, dest->mNumBones); + + // make a deep copy of all faces + GetArrayCopy(dest->mFaces, dest->mNumFaces); + for (unsigned int i = 0; i < dest->mNumFaces; ++i) { + aiFace &f = dest->mFaces[i]; + GetArrayCopy(f.mIndices, f.mNumIndices); + } + + // make a deep copy of all blend shapes + CopyPtrArray(dest->mAnimMeshes, dest->mAnimMeshes, dest->mNumAnimMeshes); + + // make a deep copy of all texture coordinate names + if (src->mTextureCoordsNames != nullptr) { + dest->mTextureCoordsNames = new aiString *[AI_MAX_NUMBER_OF_TEXTURECOORDS] {}; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + Copy(&dest->mTextureCoordsNames[i], src->mTextureCoordsNames[i]); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy(aiAnimMesh **_dest, const aiAnimMesh *src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + aiAnimMesh *dest = *_dest = new aiAnimMesh(); + + // get a flat copy + *dest = *src; + + // and reallocate all arrays + GetArrayCopy(dest->mVertices, dest->mNumVertices); + GetArrayCopy(dest->mNormals, dest->mNumVertices); + GetArrayCopy(dest->mTangents, dest->mNumVertices); + GetArrayCopy(dest->mBitangents, dest->mNumVertices); + + unsigned int n = 0; + while (dest->HasTextureCoords(n)) + GetArrayCopy(dest->mTextureCoords[n++], dest->mNumVertices); + + n = 0; + while (dest->HasVertexColors(n)) + GetArrayCopy(dest->mColors[n++], dest->mNumVertices); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy(aiMaterial **_dest, const aiMaterial *src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + aiMaterial *dest = (aiMaterial *)(*_dest = new aiMaterial()); + + dest->Clear(); + delete[] dest->mProperties; + + dest->mNumAllocated = src->mNumAllocated; + dest->mNumProperties = src->mNumProperties; + dest->mProperties = new aiMaterialProperty *[dest->mNumAllocated]; + + for (unsigned int i = 0; i < dest->mNumProperties; ++i) { + aiMaterialProperty *prop = dest->mProperties[i] = new aiMaterialProperty(); + aiMaterialProperty *sprop = src->mProperties[i]; + + prop->mDataLength = sprop->mDataLength; + prop->mData = new char[prop->mDataLength]; + ::memcpy(prop->mData, sprop->mData, prop->mDataLength); + + prop->mIndex = sprop->mIndex; + prop->mSemantic = sprop->mSemantic; + prop->mKey = sprop->mKey; + prop->mType = sprop->mType; + } +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy(aiTexture **_dest, const aiTexture *src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + aiTexture *dest = *_dest = new aiTexture(); + + // get a flat copy + *dest = *src; + + // and reallocate all arrays. We must do it manually here + const char *old = (const char *)dest->pcData; + if (old) { + unsigned int cpy; + if (!dest->mHeight) + cpy = dest->mWidth; + else + cpy = dest->mHeight * dest->mWidth * sizeof(aiTexel); + + if (!cpy) { + dest->pcData = nullptr; + return; + } + // the cast is legal, the aiTexel c'tor does nothing important + dest->pcData = (aiTexel *)new char[cpy]; + ::memcpy(dest->pcData, old, cpy); + } +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy(aiAnimation **_dest, const aiAnimation *src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + aiAnimation *dest = *_dest = new aiAnimation(); + + // get a flat copy + *dest = *src; + + // and reallocate all arrays + CopyPtrArray(dest->mChannels, src->mChannels, dest->mNumChannels); + CopyPtrArray(dest->mMorphMeshChannels, src->mMorphMeshChannels, dest->mNumMorphMeshChannels); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy(aiNodeAnim **_dest, const aiNodeAnim *src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + aiNodeAnim *dest = *_dest = new aiNodeAnim(); + + // get a flat copy + *dest = *src; + + // and reallocate all arrays + GetArrayCopy(dest->mPositionKeys, dest->mNumPositionKeys); + GetArrayCopy(dest->mScalingKeys, dest->mNumScalingKeys); + GetArrayCopy(dest->mRotationKeys, dest->mNumRotationKeys); +} + +void SceneCombiner::Copy(aiMeshMorphAnim **_dest, const aiMeshMorphAnim *src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + aiMeshMorphAnim *dest = *_dest = new aiMeshMorphAnim(); + + // get a flat copy + *dest = *src; + + // and reallocate all arrays + GetArrayCopy(dest->mKeys, dest->mNumKeys); + for (ai_uint i = 0; i < dest->mNumKeys; ++i) { + dest->mKeys[i].mValues = new unsigned int[dest->mKeys[i].mNumValuesAndWeights]; + dest->mKeys[i].mWeights = new double[dest->mKeys[i].mNumValuesAndWeights]; + ::memcpy(dest->mKeys[i].mValues, src->mKeys[i].mValues, dest->mKeys[i].mNumValuesAndWeights * sizeof(unsigned int)); + ::memcpy(dest->mKeys[i].mWeights, src->mKeys[i].mWeights, dest->mKeys[i].mNumValuesAndWeights * sizeof(double)); + } +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy(aiCamera **_dest, const aiCamera *src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + aiCamera *dest = *_dest = new aiCamera(); + + // get a flat copy, that's already OK + *dest = *src; +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy(aiLight **_dest, const aiLight *src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + aiLight *dest = *_dest = new aiLight(); + + // get a flat copy, that's already OK + *dest = *src; +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy(aiBone **_dest, const aiBone *src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + aiBone *dest = *_dest = new aiBone(); + + // get a flat copy + *dest = *src; +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy(aiNode **_dest, const aiNode *src) { + ai_assert(nullptr != _dest); + ai_assert(nullptr != src); + + aiNode *dest = *_dest = new aiNode(); + + // get a flat copy + *dest = *src; + + if (src->mMetaData) { + Copy(&dest->mMetaData, src->mMetaData); + } + + // and reallocate all arrays + GetArrayCopy(dest->mMeshes, dest->mNumMeshes); + CopyPtrArray(dest->mChildren, src->mChildren, dest->mNumChildren); + + // need to set the mParent fields to the created aiNode. + for (unsigned int i = 0; i < dest->mNumChildren; i++) { + dest->mChildren[i]->mParent = dest; + } +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy(aiMetadata **_dest, const aiMetadata *src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + if (0 == src->mNumProperties) { + return; + } + + aiMetadata *dest = *_dest = aiMetadata::Alloc(src->mNumProperties); + std::copy(src->mKeys, src->mKeys + src->mNumProperties, dest->mKeys); + + for (unsigned int i = 0; i < src->mNumProperties; ++i) { + aiMetadataEntry &in = src->mValues[i]; + aiMetadataEntry &out = dest->mValues[i]; + out.mType = in.mType; + switch (dest->mValues[i].mType) { + case AI_BOOL: + out.mData = new bool(*static_cast<bool *>(in.mData)); + break; + case AI_INT32: + out.mData = new int32_t(*static_cast<int32_t *>(in.mData)); + break; + case AI_UINT64: + out.mData = new uint64_t(*static_cast<uint64_t *>(in.mData)); + break; + case AI_FLOAT: + out.mData = new float(*static_cast<float *>(in.mData)); + break; + case AI_DOUBLE: + out.mData = new double(*static_cast<double *>(in.mData)); + break; + case AI_AISTRING: + out.mData = new aiString(*static_cast<aiString *>(in.mData)); + break; + case AI_AIVECTOR3D: + out.mData = new aiVector3D(*static_cast<aiVector3D *>(in.mData)); + break; + default: + ai_assert(false); + break; + } + } +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy(aiString **_dest, const aiString *src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + aiString *dest = *_dest = new aiString(); + + // get a flat copy + *dest = *src; +} + +#if (__GNUC__ >= 8 && __GNUC_MINOR__ >= 0) +#pragma GCC diagnostic pop +#endif + +} // Namespace Assimp diff --git a/libs/assimp/code/Common/ScenePreprocessor.cpp b/libs/assimp/code/Common/ScenePreprocessor.cpp new file mode 100644 index 0000000..2ef291e --- /dev/null +++ b/libs/assimp/code/Common/ScenePreprocessor.cpp @@ -0,0 +1,285 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#include "ScenePreprocessor.h" +#include <assimp/ai_assert.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> + +using namespace Assimp; + +// --------------------------------------------------------------------------------------------- +void ScenePreprocessor::ProcessScene() { + ai_assert(scene != nullptr); + + // Process all meshes + for (unsigned int i = 0; i < scene->mNumMeshes; ++i) { + if (nullptr == scene->mMeshes[i]) { + continue; + } + ProcessMesh(scene->mMeshes[i]); + } + + // - nothing to do for nodes for the moment + // - nothing to do for textures for the moment + // - nothing to do for lights for the moment + // - nothing to do for cameras for the moment + + // Process all animations + for (unsigned int i = 0; i < scene->mNumAnimations; ++i) { + if (nullptr == scene->mAnimations[i]) { + continue; + } + ProcessAnimation(scene->mAnimations[i]); + } + + // Generate a default material if none was specified + if (!scene->mNumMaterials && scene->mNumMeshes) { + scene->mMaterials = new aiMaterial *[2]; + aiMaterial *helper; + + aiString name; + + scene->mMaterials[scene->mNumMaterials] = helper = new aiMaterial(); + aiColor3D clr(0.6f, 0.6f, 0.6f); + helper->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); + + // setup the default name to make this material identifiable + name.Set(AI_DEFAULT_MATERIAL_NAME); + helper->AddProperty(&name, AI_MATKEY_NAME); + + ASSIMP_LOG_DEBUG("ScenePreprocessor: Adding default material \'" AI_DEFAULT_MATERIAL_NAME "\'"); + + for (unsigned int i = 0; i < scene->mNumMeshes; ++i) { + if (nullptr == scene->mMeshes[i]) { + continue; + } + scene->mMeshes[i]->mMaterialIndex = scene->mNumMaterials; + } + + scene->mNumMaterials++; + } +} + +// --------------------------------------------------------------------------------------------- +void ScenePreprocessor::ProcessMesh(aiMesh *mesh) { + // If aiMesh::mNumUVComponents is *not* set assign the default value of 2 + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (!mesh->mTextureCoords[i]) { + mesh->mNumUVComponents[i] = 0; + } else { + if (!mesh->mNumUVComponents[i]) { + mesh->mNumUVComponents[i] = 2; + } + + aiVector3D *p = mesh->mTextureCoords[i], *end = p + mesh->mNumVertices; + + // Ensure unused components are zeroed. This will make 1D texture channels work + // as if they were 2D channels .. just in case an application doesn't handle + // this case + if (2 == mesh->mNumUVComponents[i]) { + for (; p != end; ++p) { + p->z = 0.f; + } + } else if (1 == mesh->mNumUVComponents[i]) { + for (; p != end; ++p) { + p->z = p->y = 0.f; + } + } else if (3 == mesh->mNumUVComponents[i]) { + // Really 3D coordinates? Check whether the third coordinate is != 0 for at least one element + for (; p != end; ++p) { + if (p->z != 0) { + break; + } + } + if (p == end) { + ASSIMP_LOG_WARN("ScenePreprocessor: UVs are declared to be 3D but they're obviously not. Reverting to 2D."); + mesh->mNumUVComponents[i] = 2; + } + } + } + } + + // If the information which primitive types are there in the + // mesh is currently not available, compute it. + if (!mesh->mPrimitiveTypes) { + for (unsigned int a = 0; a < mesh->mNumFaces; ++a) { + aiFace &face = mesh->mFaces[a]; + switch (face.mNumIndices) { + case 3u: + mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + + case 2u: + mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + + case 1u: + mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + + default: + mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + break; + } + } + } + + // If tangents and normals are given but no bitangents compute them + if (mesh->mTangents && mesh->mNormals && !mesh->mBitangents) { + mesh->mBitangents = new aiVector3D[mesh->mNumVertices]; + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + mesh->mBitangents[i] = mesh->mNormals[i] ^ mesh->mTangents[i]; + } + } +} + +// --------------------------------------------------------------------------------------------- +void ScenePreprocessor::ProcessAnimation(aiAnimation *anim) { + double first = 10e10, last = -10e10; + for (unsigned int i = 0; i < anim->mNumChannels; ++i) { + aiNodeAnim *channel = anim->mChannels[i]; + + // If the exact duration of the animation is not given + // compute it now. + if (anim->mDuration == -1.) { + // Position keys + for (unsigned int j = 0; j < channel->mNumPositionKeys; ++j) { + aiVectorKey &key = channel->mPositionKeys[j]; + first = std::min(first, key.mTime); + last = std::max(last, key.mTime); + } + + // Scaling keys + for (unsigned int j = 0; j < channel->mNumScalingKeys; ++j) { + aiVectorKey &key = channel->mScalingKeys[j]; + first = std::min(first, key.mTime); + last = std::max(last, key.mTime); + } + + // Rotation keys + for (unsigned int j = 0; j < channel->mNumRotationKeys; ++j) { + aiQuatKey &key = channel->mRotationKeys[j]; + first = std::min(first, key.mTime); + last = std::max(last, key.mTime); + } + } + + // Check whether the animation channel has no rotation + // or position tracks. In this case we generate a dummy + // track from the information we have in the transformation + // matrix of the corresponding node. + if (!channel->mNumRotationKeys || !channel->mNumPositionKeys || !channel->mNumScalingKeys) { + // Find the node that belongs to this animation + aiNode *node = scene->mRootNode->FindNode(channel->mNodeName); + if (node) // ValidateDS will complain later if 'node' is nullptr + { + // Decompose the transformation matrix of the node + aiVector3D scaling, position; + aiQuaternion rotation; + + node->mTransformation.Decompose(scaling, rotation, position); + + // No rotation keys? Generate a dummy track + if (!channel->mNumRotationKeys) { + if (channel->mRotationKeys) { + delete[] channel->mRotationKeys; + channel->mRotationKeys = nullptr; + } + ai_assert(!channel->mRotationKeys); + channel->mNumRotationKeys = 1; + channel->mRotationKeys = new aiQuatKey[1]; + aiQuatKey &q = channel->mRotationKeys[0]; + + q.mTime = 0.; + q.mValue = rotation; + + ASSIMP_LOG_VERBOSE_DEBUG("ScenePreprocessor: Dummy rotation track has been generated"); + } else { + ai_assert(channel->mRotationKeys); + } + + // No scaling keys? Generate a dummy track + if (!channel->mNumScalingKeys) { + if (channel->mScalingKeys) { + delete[] channel->mScalingKeys; + channel->mScalingKeys = nullptr; + } + ai_assert(!channel->mScalingKeys); + channel->mNumScalingKeys = 1; + channel->mScalingKeys = new aiVectorKey[1]; + aiVectorKey &q = channel->mScalingKeys[0]; + + q.mTime = 0.; + q.mValue = scaling; + + ASSIMP_LOG_VERBOSE_DEBUG("ScenePreprocessor: Dummy scaling track has been generated"); + } else { + ai_assert(channel->mScalingKeys); + } + + // No position keys? Generate a dummy track + if (!channel->mNumPositionKeys) { + if (channel->mPositionKeys) { + delete[] channel->mPositionKeys; + channel->mPositionKeys = nullptr; + } + ai_assert(!channel->mPositionKeys); + channel->mNumPositionKeys = 1; + channel->mPositionKeys = new aiVectorKey[1]; + aiVectorKey &q = channel->mPositionKeys[0]; + + q.mTime = 0.; + q.mValue = position; + + ASSIMP_LOG_VERBOSE_DEBUG("ScenePreprocessor: Dummy position track has been generated"); + } else { + ai_assert(channel->mPositionKeys); + } + } + } + } + + if (anim->mDuration == -1.) { + ASSIMP_LOG_VERBOSE_DEBUG("ScenePreprocessor: Setting animation duration"); + anim->mDuration = last - std::min(first, 0.); + } +} diff --git a/libs/assimp/code/Common/ScenePreprocessor.h b/libs/assimp/code/Common/ScenePreprocessor.h new file mode 100644 index 0000000..49e06ed --- /dev/null +++ b/libs/assimp/code/Common/ScenePreprocessor.h @@ -0,0 +1,118 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to search all meshes for + degenerated faces */ +#ifndef AI_SCENE_PREPROCESSOR_H_INC +#define AI_SCENE_PREPROCESSOR_H_INC + +#include <assimp/defs.h> +#include <stddef.h> + +struct aiScene; +struct aiAnimation; +struct aiMesh; + +class ScenePreprocessorTest; +namespace Assimp { + +// ---------------------------------------------------------------------------------- +/** ScenePreprocessor: Preprocess a scene before any post-processing + * steps are executed. + * + * The step computes data that needn't necessarily be provided by the + * importer, such as aiMesh::mPrimitiveTypes. +*/ +// ---------------------------------------------------------------------------------- +class ASSIMP_API ScenePreprocessor { + // Make ourselves a friend of the corresponding test unit. + friend class ::ScenePreprocessorTest; + +public: + // ---------------------------------------------------------------- + /** Default c'tpr. Use SetScene() to assign a scene to the object. + */ + ScenePreprocessor() : + scene(nullptr) {} + + /** Constructs the object and assigns a specific scene to it + */ + ScenePreprocessor(aiScene *_scene) : + scene(_scene) {} + + // ---------------------------------------------------------------- + /** Assign a (new) scene to the object. + * + * One 'SceneProcessor' can be used for multiple scenes. + * Call ProcessScene to have the scene preprocessed. + * @param sc Scene to be processed. + */ + void SetScene(aiScene *sc) { + scene = sc; + } + + // ---------------------------------------------------------------- + /** Preprocess the current scene + */ + void ProcessScene(); + +protected: + // ---------------------------------------------------------------- + /** Preprocess an animation in the scene + * @param anim Anim to be preprocessed. + */ + void ProcessAnimation(aiAnimation *anim); + + // ---------------------------------------------------------------- + /** Preprocess a mesh in the scene + * @param mesh Mesh to be preprocessed. + */ + void ProcessMesh(aiMesh *mesh); + +protected: + //! Scene we're currently working on + aiScene *scene; +}; + +} // namespace Assimp + +#endif // include guard diff --git a/libs/assimp/code/Common/ScenePrivate.h b/libs/assimp/code/Common/ScenePrivate.h new file mode 100644 index 0000000..3910db7 --- /dev/null +++ b/libs/assimp/code/Common/ScenePrivate.h @@ -0,0 +1,105 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Stuff to deal with aiScene::mPrivate + */ +#pragma once +#ifndef AI_SCENEPRIVATE_H_INCLUDED +#define AI_SCENEPRIVATE_H_INCLUDED + +#include <assimp/ai_assert.h> +#include <assimp/scene.h> + +namespace Assimp { + +// Forward declarations +class Importer; + +struct ScenePrivateData { + // The struct constructor. + ScenePrivateData() AI_NO_EXCEPT; + + // Importer that originally loaded the scene though the C-API + // If set, this object is owned by this private data instance. + Assimp::Importer* mOrigImporter; + + // List of post-processing steps already applied to the scene. + unsigned int mPPStepsApplied; + + // true if the scene is a copy made with aiCopyScene() + // or the corresponding C++ API. This means that user code + // may have made modifications to it, so mPPStepsApplied + // and mOrigImporter are no longer safe to rely on and only + // serve informative purposes. + bool mIsCopy; +}; + +inline +ScenePrivateData::ScenePrivateData() AI_NO_EXCEPT +: mOrigImporter( nullptr ) +, mPPStepsApplied( 0 ) +, mIsCopy( false ) { + // empty +} + +// Access private data stored in the scene +inline +ScenePrivateData* ScenePriv(aiScene* in) { + ai_assert( nullptr != in ); + if ( nullptr == in ) { + return nullptr; + } + return static_cast<ScenePrivateData*>(in->mPrivate); +} + +inline +const ScenePrivateData* ScenePriv(const aiScene* in) { + ai_assert( nullptr != in ); + if ( nullptr == in ) { + return nullptr; + } + return static_cast<const ScenePrivateData*>(in->mPrivate); +} + +} // Namespace Assimp + +#endif // AI_SCENEPRIVATE_H_INCLUDED diff --git a/libs/assimp/code/Common/SkeletonMeshBuilder.cpp b/libs/assimp/code/Common/SkeletonMeshBuilder.cpp new file mode 100644 index 0000000..5ea30d9 --- /dev/null +++ b/libs/assimp/code/Common/SkeletonMeshBuilder.cpp @@ -0,0 +1,262 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 SkeletonMeshBuilder.cpp + * @brief Implementation of a little class to construct a dummy mesh for a skeleton + */ + +#include <assimp/SkeletonMeshBuilder.h> +#include <assimp/scene.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// The constructor processes the given scene and adds a mesh there. +SkeletonMeshBuilder::SkeletonMeshBuilder(aiScene *pScene, aiNode *root, bool bKnobsOnly) { + // nothing to do if there's mesh data already present at the scene + if (pScene->mNumMeshes > 0 || pScene->mRootNode == nullptr) { + return; + } + + if (!root) { + root = pScene->mRootNode; + } + + mKnobsOnly = bKnobsOnly; + + // build some faces around each node + CreateGeometry(root); + + // create a mesh to hold all the generated faces + pScene->mNumMeshes = 1; + pScene->mMeshes = new aiMesh *[1]; + pScene->mMeshes[0] = CreateMesh(); + // and install it at the root node + root->mNumMeshes = 1; + root->mMeshes = new unsigned int[1]; + root->mMeshes[0] = 0; + + // create a dummy material for the mesh + if (pScene->mNumMaterials == 0) { + pScene->mNumMaterials = 1; + pScene->mMaterials = new aiMaterial *[1]; + pScene->mMaterials[0] = CreateMaterial(); + } +} + +// ------------------------------------------------------------------------------------------------ +// Recursively builds a simple mesh representation for the given node +void SkeletonMeshBuilder::CreateGeometry(const aiNode *pNode) { + // add a joint entry for the node. + const unsigned int vertexStartIndex = static_cast<unsigned int>(mVertices.size()); + + // now build the geometry. + if (pNode->mNumChildren > 0 && !mKnobsOnly) { + // If the node has children, we build little pointers to each of them + for (unsigned int a = 0; a < pNode->mNumChildren; a++) { + // find a suitable coordinate system + const aiMatrix4x4 &childTransform = pNode->mChildren[a]->mTransformation; + aiVector3D childpos(childTransform.a4, childTransform.b4, childTransform.c4); + ai_real distanceToChild = childpos.Length(); + if (distanceToChild < 0.0001) + continue; + aiVector3D up = aiVector3D(childpos).Normalize(); + + aiVector3D orth(1.0, 0.0, 0.0); + if (std::fabs(orth * up) > 0.99) + orth.Set(0.0, 1.0, 0.0); + + aiVector3D front = (up ^ orth).Normalize(); + aiVector3D side = (front ^ up).Normalize(); + + unsigned int localVertexStart = static_cast<unsigned int>(mVertices.size()); + mVertices.push_back(-front * distanceToChild * (ai_real)0.1); + mVertices.push_back(childpos); + mVertices.push_back(-side * distanceToChild * (ai_real)0.1); + mVertices.push_back(-side * distanceToChild * (ai_real)0.1); + mVertices.push_back(childpos); + mVertices.push_back(front * distanceToChild * (ai_real)0.1); + mVertices.push_back(front * distanceToChild * (ai_real)0.1); + mVertices.push_back(childpos); + mVertices.push_back(side * distanceToChild * (ai_real)0.1); + mVertices.push_back(side * distanceToChild * (ai_real)0.1); + mVertices.push_back(childpos); + mVertices.push_back(-front * distanceToChild * (ai_real)0.1); + + mFaces.push_back(Face(localVertexStart + 0, localVertexStart + 1, localVertexStart + 2)); + mFaces.push_back(Face(localVertexStart + 3, localVertexStart + 4, localVertexStart + 5)); + mFaces.push_back(Face(localVertexStart + 6, localVertexStart + 7, localVertexStart + 8)); + mFaces.push_back(Face(localVertexStart + 9, localVertexStart + 10, localVertexStart + 11)); + } + } else { + // if the node has no children, it's an end node. Put a little knob there instead + aiVector3D ownpos(pNode->mTransformation.a4, pNode->mTransformation.b4, pNode->mTransformation.c4); + ai_real sizeEstimate = ownpos.Length() * ai_real(0.18); + + mVertices.push_back(aiVector3D(-sizeEstimate, 0.0, 0.0)); + mVertices.push_back(aiVector3D(0.0, sizeEstimate, 0.0)); + mVertices.push_back(aiVector3D(0.0, 0.0, -sizeEstimate)); + mVertices.push_back(aiVector3D(0.0, sizeEstimate, 0.0)); + mVertices.push_back(aiVector3D(sizeEstimate, 0.0, 0.0)); + mVertices.push_back(aiVector3D(0.0, 0.0, -sizeEstimate)); + mVertices.push_back(aiVector3D(sizeEstimate, 0.0, 0.0)); + mVertices.push_back(aiVector3D(0.0, -sizeEstimate, 0.0)); + mVertices.push_back(aiVector3D(0.0, 0.0, -sizeEstimate)); + mVertices.push_back(aiVector3D(0.0, -sizeEstimate, 0.0)); + mVertices.push_back(aiVector3D(-sizeEstimate, 0.0, 0.0)); + mVertices.push_back(aiVector3D(0.0, 0.0, -sizeEstimate)); + + mVertices.push_back(aiVector3D(-sizeEstimate, 0.0, 0.0)); + mVertices.push_back(aiVector3D(0.0, 0.0, sizeEstimate)); + mVertices.push_back(aiVector3D(0.0, sizeEstimate, 0.0)); + mVertices.push_back(aiVector3D(0.0, sizeEstimate, 0.0)); + mVertices.push_back(aiVector3D(0.0, 0.0, sizeEstimate)); + mVertices.push_back(aiVector3D(sizeEstimate, 0.0, 0.0)); + mVertices.push_back(aiVector3D(sizeEstimate, 0.0, 0.0)); + mVertices.push_back(aiVector3D(0.0, 0.0, sizeEstimate)); + mVertices.push_back(aiVector3D(0.0, -sizeEstimate, 0.0)); + mVertices.push_back(aiVector3D(0.0, -sizeEstimate, 0.0)); + mVertices.push_back(aiVector3D(0.0, 0.0, sizeEstimate)); + mVertices.push_back(aiVector3D(-sizeEstimate, 0.0, 0.0)); + + mFaces.push_back(Face(vertexStartIndex + 0, vertexStartIndex + 1, vertexStartIndex + 2)); + mFaces.push_back(Face(vertexStartIndex + 3, vertexStartIndex + 4, vertexStartIndex + 5)); + mFaces.push_back(Face(vertexStartIndex + 6, vertexStartIndex + 7, vertexStartIndex + 8)); + mFaces.push_back(Face(vertexStartIndex + 9, vertexStartIndex + 10, vertexStartIndex + 11)); + mFaces.push_back(Face(vertexStartIndex + 12, vertexStartIndex + 13, vertexStartIndex + 14)); + mFaces.push_back(Face(vertexStartIndex + 15, vertexStartIndex + 16, vertexStartIndex + 17)); + mFaces.push_back(Face(vertexStartIndex + 18, vertexStartIndex + 19, vertexStartIndex + 20)); + mFaces.push_back(Face(vertexStartIndex + 21, vertexStartIndex + 22, vertexStartIndex + 23)); + } + + unsigned int numVertices = static_cast<unsigned int>(mVertices.size() - vertexStartIndex); + if (numVertices > 0) { + // create a bone affecting all the newly created vertices + aiBone *bone = new aiBone; + mBones.push_back(bone); + bone->mName = pNode->mName; + + // calculate the bone offset matrix by concatenating the inverse transformations of all parents + bone->mOffsetMatrix = aiMatrix4x4(pNode->mTransformation).Inverse(); + for (aiNode *parent = pNode->mParent; parent != nullptr; parent = parent->mParent) + bone->mOffsetMatrix = aiMatrix4x4(parent->mTransformation).Inverse() * bone->mOffsetMatrix; + + // add all the vertices to the bone's influences + bone->mNumWeights = numVertices; + bone->mWeights = new aiVertexWeight[numVertices]; + for (unsigned int a = 0; a < numVertices; a++) + bone->mWeights[a] = aiVertexWeight(vertexStartIndex + a, 1.0); + + // HACK: (thom) transform all vertices to the bone's local space. Should be done before adding + // them to the array, but I'm tired now and I'm annoyed. + aiMatrix4x4 boneToMeshTransform = aiMatrix4x4(bone->mOffsetMatrix).Inverse(); + for (unsigned int a = vertexStartIndex; a < mVertices.size(); a++) + mVertices[a] = boneToMeshTransform * mVertices[a]; + } + + // and finally recurse into the children list + for (unsigned int a = 0; a < pNode->mNumChildren; a++) + CreateGeometry(pNode->mChildren[a]); +} + +// ------------------------------------------------------------------------------------------------ +// Creates the mesh from the internally accumulated stuff and returns it. +aiMesh *SkeletonMeshBuilder::CreateMesh() { + aiMesh *mesh = new aiMesh(); + + // add points + mesh->mNumVertices = static_cast<unsigned int>(mVertices.size()); + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + std::copy(mVertices.begin(), mVertices.end(), mesh->mVertices); + + mesh->mNormals = new aiVector3D[mesh->mNumVertices]; + + // add faces + mesh->mNumFaces = static_cast<unsigned int>(mFaces.size()); + mesh->mFaces = new aiFace[mesh->mNumFaces]; + for (unsigned int a = 0; a < mesh->mNumFaces; a++) { + const Face &inface = mFaces[a]; + aiFace &outface = mesh->mFaces[a]; + outface.mNumIndices = 3; + outface.mIndices = new unsigned int[3]; + outface.mIndices[0] = inface.mIndices[0]; + outface.mIndices[1] = inface.mIndices[1]; + outface.mIndices[2] = inface.mIndices[2]; + + // Compute per-face normals ... we don't want the bones to be smoothed ... they're built to visualize + // the skeleton, so it's good if there's a visual difference to the rest of the geometry + aiVector3D nor = ((mVertices[inface.mIndices[2]] - mVertices[inface.mIndices[0]]) ^ + (mVertices[inface.mIndices[1]] - mVertices[inface.mIndices[0]])); + + if (nor.Length() < 1e-5) /* ensure that FindInvalidData won't remove us ...*/ + nor = aiVector3D(1.0, 0.0, 0.0); + + for (unsigned int n = 0; n < 3; ++n) + mesh->mNormals[inface.mIndices[n]] = nor; + } + + // add the bones + mesh->mNumBones = static_cast<unsigned int>(mBones.size()); + mesh->mBones = new aiBone *[mesh->mNumBones]; + std::copy(mBones.begin(), mBones.end(), mesh->mBones); + + // default + mesh->mMaterialIndex = 0; + + return mesh; +} + +// ------------------------------------------------------------------------------------------------ +// Creates a dummy material and returns it. +aiMaterial *SkeletonMeshBuilder::CreateMaterial() { + aiMaterial *matHelper = new aiMaterial; + + // Name + aiString matName(std::string("SkeletonMaterial")); + matHelper->AddProperty(&matName, AI_MATKEY_NAME); + + // Prevent backface culling + const int no_cull = 1; + matHelper->AddProperty(&no_cull, 1, AI_MATKEY_TWOSIDED); + + return matHelper; +} diff --git a/libs/assimp/code/Common/SpatialSort.cpp b/libs/assimp/code/Common/SpatialSort.cpp new file mode 100644 index 0000000..6103002 --- /dev/null +++ b/libs/assimp/code/Common/SpatialSort.cpp @@ -0,0 +1,344 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the helper class to quickly find vertices close to a given position */ + +#include <assimp/SpatialSort.h> +#include <assimp/ai_assert.h> + +using namespace Assimp; + +// CHAR_BIT seems to be defined under MVSC, but not under GCC. Pray that the correct value is 8. +#ifndef CHAR_BIT +#define CHAR_BIT 8 +#endif + +const aiVector3D PlaneInit(0.8523f, 0.34321f, 0.5736f); + +// ------------------------------------------------------------------------------------------------ +// Constructs a spatially sorted representation from the given position array. +// define the reference plane. We choose some arbitrary vector away from all basic axes +// in the hope that no model spreads all its vertices along this plane. +SpatialSort::SpatialSort(const aiVector3D *pPositions, unsigned int pNumPositions, unsigned int pElementOffset) : + mPlaneNormal(PlaneInit), + mFinalized(false) { + mPlaneNormal.Normalize(); + Fill(pPositions, pNumPositions, pElementOffset); +} + +// ------------------------------------------------------------------------------------------------ +SpatialSort::SpatialSort() : + mPlaneNormal(PlaneInit), + mFinalized(false) { + mPlaneNormal.Normalize(); +} + +// ------------------------------------------------------------------------------------------------ +// Destructor +SpatialSort::~SpatialSort() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +void SpatialSort::Fill(const aiVector3D *pPositions, unsigned int pNumPositions, + unsigned int pElementOffset, + bool pFinalize /*= true */) { + mPositions.clear(); + mFinalized = false; + Append(pPositions, pNumPositions, pElementOffset, pFinalize); + mFinalized = pFinalize; +} + +// ------------------------------------------------------------------------------------------------ +ai_real SpatialSort::CalculateDistance(const aiVector3D &pPosition) const { + return (pPosition - mCentroid) * mPlaneNormal; +} + +// ------------------------------------------------------------------------------------------------ +void SpatialSort::Finalize() { + const ai_real scale = 1.0f / mPositions.size(); + for (unsigned int i = 0; i < mPositions.size(); i++) { + mCentroid += scale * mPositions[i].mPosition; + } + for (unsigned int i = 0; i < mPositions.size(); i++) { + mPositions[i].mDistance = CalculateDistance(mPositions[i].mPosition); + } + std::sort(mPositions.begin(), mPositions.end()); + mFinalized = true; +} + +// ------------------------------------------------------------------------------------------------ +void SpatialSort::Append(const aiVector3D *pPositions, unsigned int pNumPositions, + unsigned int pElementOffset, + bool pFinalize /*= true */) { + ai_assert(!mFinalized && "You cannot add positions to the SpatialSort object after it has been finalized."); + // store references to all given positions along with their distance to the reference plane + const size_t initial = mPositions.size(); + mPositions.reserve(initial + pNumPositions); + for (unsigned int a = 0; a < pNumPositions; a++) { + const char *tempPointer = reinterpret_cast<const char *>(pPositions); + const aiVector3D *vec = reinterpret_cast<const aiVector3D *>(tempPointer + a * pElementOffset); + mPositions.push_back(Entry(static_cast<unsigned int>(a + initial), *vec)); + } + + if (pFinalize) { + // now sort the array ascending by distance. + Finalize(); + } +} + +// ------------------------------------------------------------------------------------------------ +// Returns an iterator for all positions close to the given position. +void SpatialSort::FindPositions(const aiVector3D &pPosition, + ai_real pRadius, std::vector<unsigned int> &poResults) const { + ai_assert(mFinalized && "The SpatialSort object must be finalized before FindPositions can be called."); + const ai_real dist = CalculateDistance(pPosition); + const ai_real minDist = dist - pRadius, maxDist = dist + pRadius; + + // clear the array + poResults.clear(); + + // quick check for positions outside the range + if (mPositions.size() == 0) + return; + if (maxDist < mPositions.front().mDistance) + return; + if (minDist > mPositions.back().mDistance) + return; + + // do a binary search for the minimal distance to start the iteration there + unsigned int index = (unsigned int)mPositions.size() / 2; + unsigned int binaryStepSize = (unsigned int)mPositions.size() / 4; + while (binaryStepSize > 1) { + if (mPositions[index].mDistance < minDist) + index += binaryStepSize; + else + index -= binaryStepSize; + + binaryStepSize /= 2; + } + + // depending on the direction of the last step we need to single step a bit back or forth + // to find the actual beginning element of the range + while (index > 0 && mPositions[index].mDistance > minDist) + index--; + while (index < (mPositions.size() - 1) && mPositions[index].mDistance < minDist) + index++; + + // Mow start iterating from there until the first position lays outside of the distance range. + // Add all positions inside the distance range within the given radius to the result array + std::vector<Entry>::const_iterator it = mPositions.begin() + index; + const ai_real pSquared = pRadius * pRadius; + while (it->mDistance < maxDist) { + if ((it->mPosition - pPosition).SquareLength() < pSquared) + poResults.push_back(it->mIndex); + ++it; + if (it == mPositions.end()) + break; + } + + // that's it +} + +namespace { + +// Binary, signed-integer representation of a single-precision floating-point value. +// IEEE 754 says: "If two floating-point numbers in the same format are ordered then they are +// ordered the same way when their bits are reinterpreted as sign-magnitude integers." +// This allows us to convert all floating-point numbers to signed integers of arbitrary size +// and then use them to work with ULPs (Units in the Last Place, for high-precision +// computations) or to compare them (integer comparisons are faster than floating-point +// comparisons on many platforms). +typedef ai_int BinFloat; + +// -------------------------------------------------------------------------------------------- +// Converts the bit pattern of a floating-point number to its signed integer representation. +BinFloat ToBinary(const ai_real &pValue) { + + // If this assertion fails, signed int is not big enough to store a float on your platform. + // Please correct the declaration of BinFloat a few lines above - but do it in a portable, + // #ifdef'd manner! + static_assert(sizeof(BinFloat) >= sizeof(ai_real), "sizeof(BinFloat) >= sizeof(ai_real)"); + +#if defined(_MSC_VER) + // If this assertion fails, Visual C++ has finally moved to ILP64. This means that this + // code has just become legacy code! Find out the current value of _MSC_VER and modify + // the #if above so it evaluates false on the current and all upcoming VC versions (or + // on the current platform, if LP64 or LLP64 are still used on other platforms). + static_assert(sizeof(BinFloat) == sizeof(ai_real), "sizeof(BinFloat) == sizeof(ai_real)"); + + // This works best on Visual C++, but other compilers have their problems with it. + const BinFloat binValue = reinterpret_cast<BinFloat const &>(pValue); + //::memcpy(&binValue, &pValue, sizeof(pValue)); + //return binValue; +#else + // On many compilers, reinterpreting a float address as an integer causes aliasing + // problems. This is an ugly but more or less safe way of doing it. + union { + ai_real asFloat; + BinFloat asBin; + } conversion; + conversion.asBin = 0; // zero empty space in case sizeof(BinFloat) > sizeof(float) + conversion.asFloat = pValue; + const BinFloat binValue = conversion.asBin; +#endif + + // floating-point numbers are of sign-magnitude format, so find out what signed number + // representation we must convert negative values to. + // See http://en.wikipedia.org/wiki/Signed_number_representations. + const BinFloat mask = BinFloat(1) << (CHAR_BIT * sizeof(BinFloat) - 1); + + // Two's complement? + const bool DefaultValue = ((-42 == (~42 + 1)) && (binValue & mask)); + const bool OneComplement = ((-42 == ~42) && (binValue & mask)); + + if (DefaultValue) + return mask - binValue; + // One's complement? + else if (OneComplement) + return BinFloat(-0) - binValue; + // Sign-magnitude? -0 = 1000... binary + return binValue; +} + +} // namespace + +// ------------------------------------------------------------------------------------------------ +// Fills an array with indices of all positions identical to the given position. In opposite to +// FindPositions(), not an epsilon is used but a (very low) tolerance of four floating-point units. +void SpatialSort::FindIdenticalPositions(const aiVector3D &pPosition, std::vector<unsigned int> &poResults) const { + ai_assert(mFinalized && "The SpatialSort object must be finalized before FindIdenticalPositions can be called."); + // Epsilons have a huge disadvantage: they are of constant precision, while floating-point + // values are of log2 precision. If you apply e=0.01 to 100, the epsilon is rather small, but + // if you apply it to 0.001, it is enormous. + + // The best way to overcome this is the unit in the last place (ULP). A precision of 2 ULPs + // tells us that a float does not differ more than 2 bits from the "real" value. ULPs are of + // logarithmic precision - around 1, they are 1*(2^24) and around 10000, they are 0.00125. + + // For standard C math, we can assume a precision of 0.5 ULPs according to IEEE 754. The + // incoming vertex positions might have already been transformed, probably using rather + // inaccurate SSE instructions, so we assume a tolerance of 4 ULPs to safely identify + // identical vertex positions. + static const int toleranceInULPs = 4; + // An interesting point is that the inaccuracy grows linear with the number of operations: + // multiplying to numbers, each inaccurate to four ULPs, results in an inaccuracy of four ULPs + // plus 0.5 ULPs for the multiplication. + // To compute the distance to the plane, a dot product is needed - that is a multiplication and + // an addition on each number. + static const int distanceToleranceInULPs = toleranceInULPs + 1; + // The squared distance between two 3D vectors is computed the same way, but with an additional + // subtraction. + static const int distance3DToleranceInULPs = distanceToleranceInULPs + 1; + + // Convert the plane distance to its signed integer representation so the ULPs tolerance can be + // applied. For some reason, VC won't optimize two calls of the bit pattern conversion. + const BinFloat minDistBinary = ToBinary(CalculateDistance(pPosition)) - distanceToleranceInULPs; + const BinFloat maxDistBinary = minDistBinary + 2 * distanceToleranceInULPs; + + // clear the array in this strange fashion because a simple clear() would also deallocate + // the array which we want to avoid + poResults.resize(0); + + // do a binary search for the minimal distance to start the iteration there + unsigned int index = (unsigned int)mPositions.size() / 2; + unsigned int binaryStepSize = (unsigned int)mPositions.size() / 4; + while (binaryStepSize > 1) { + // Ugly, but conditional jumps are faster with integers than with floats + if (minDistBinary > ToBinary(mPositions[index].mDistance)) + index += binaryStepSize; + else + index -= binaryStepSize; + + binaryStepSize /= 2; + } + + // depending on the direction of the last step we need to single step a bit back or forth + // to find the actual beginning element of the range + while (index > 0 && minDistBinary < ToBinary(mPositions[index].mDistance)) + index--; + while (index < (mPositions.size() - 1) && minDistBinary > ToBinary(mPositions[index].mDistance)) + index++; + + // Now start iterating from there until the first position lays outside of the distance range. + // Add all positions inside the distance range within the tolerance to the result array + std::vector<Entry>::const_iterator it = mPositions.begin() + index; + while (ToBinary(it->mDistance) < maxDistBinary) { + if (distance3DToleranceInULPs >= ToBinary((it->mPosition - pPosition).SquareLength())) + poResults.push_back(it->mIndex); + ++it; + if (it == mPositions.end()) + break; + } + + // that's it +} + +// ------------------------------------------------------------------------------------------------ +unsigned int SpatialSort::GenerateMappingTable(std::vector<unsigned int> &fill, ai_real pRadius) const { + ai_assert(mFinalized && "The SpatialSort object must be finalized before GenerateMappingTable can be called."); + fill.resize(mPositions.size(), UINT_MAX); + ai_real dist, maxDist; + + unsigned int t = 0; + const ai_real pSquared = pRadius * pRadius; + for (size_t i = 0; i < mPositions.size();) { + dist = (mPositions[i].mPosition - mCentroid) * mPlaneNormal; + maxDist = dist + pRadius; + + fill[mPositions[i].mIndex] = t; + const aiVector3D &oldpos = mPositions[i].mPosition; + for (++i; i < fill.size() && mPositions[i].mDistance < maxDist && (mPositions[i].mPosition - oldpos).SquareLength() < pSquared; ++i) { + fill[mPositions[i].mIndex] = t; + } + ++t; + } + +#ifdef ASSIMP_BUILD_DEBUG + + // debug invariant: mPositions[i].mIndex values must range from 0 to mPositions.size()-1 + for (size_t i = 0; i < fill.size(); ++i) { + ai_assert(fill[i] < mPositions.size()); + } + +#endif + return t; +} diff --git a/libs/assimp/code/Common/StandardShapes.cpp b/libs/assimp/code/Common/StandardShapes.cpp new file mode 100644 index 0000000..e94b5b9 --- /dev/null +++ b/libs/assimp/code/Common/StandardShapes.cpp @@ -0,0 +1,479 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 StandardShapes.cpp + * @brief Implementation of the StandardShapes class + * + * The primitive geometry data comes from + * http://geometrictools.com/Documentation/PlatonicSolids.pdf. + */ + +#include <assimp/StandardShapes.h> +#include <assimp/StringComparison.h> +#include <assimp/mesh.h> + +namespace Assimp { + +#define ADD_TRIANGLE(n0, n1, n2) \ + positions.push_back(n0); \ + positions.push_back(n1); \ + positions.push_back(n2); + +#define ADD_PENTAGON(n0, n1, n2, n3, n4) \ + if (polygons) { \ + positions.push_back(n0); \ + positions.push_back(n1); \ + positions.push_back(n2); \ + positions.push_back(n3); \ + positions.push_back(n4); \ + } else { \ + ADD_TRIANGLE(n0, n1, n2) \ + ADD_TRIANGLE(n0, n2, n3) \ + ADD_TRIANGLE(n0, n3, n4) \ + } + +#define ADD_QUAD(n0, n1, n2, n3) \ + if (polygons) { \ + positions.push_back(n0); \ + positions.push_back(n1); \ + positions.push_back(n2); \ + positions.push_back(n3); \ + } else { \ + ADD_TRIANGLE(n0, n1, n2) \ + ADD_TRIANGLE(n0, n2, n3) \ + } + +// ------------------------------------------------------------------------------------------------ +// Fast subdivision for a mesh whose verts have a magnitude of 1 +void Subdivide(std::vector<aiVector3D> &positions) { + // assume this to be constant - (fixme: must be 1.0? I think so) + const ai_real fl1 = positions[0].Length(); + + unsigned int origSize = (unsigned int)positions.size(); + for (unsigned int i = 0; i < origSize; i += 3) { + aiVector3D &tv0 = positions[i]; + aiVector3D &tv1 = positions[i + 1]; + aiVector3D &tv2 = positions[i + 2]; + + aiVector3D a = tv0, b = tv1, c = tv2; + aiVector3D v1 = aiVector3D(a.x + b.x, a.y + b.y, a.z + b.z).Normalize() * fl1; + aiVector3D v2 = aiVector3D(a.x + c.x, a.y + c.y, a.z + c.z).Normalize() * fl1; + aiVector3D v3 = aiVector3D(b.x + c.x, b.y + c.y, b.z + c.z).Normalize() * fl1; + + tv0 = v1; + tv1 = v3; + tv2 = v2; // overwrite the original + ADD_TRIANGLE(v1, v2, a); + ADD_TRIANGLE(v2, v3, c); + ADD_TRIANGLE(v3, v1, b); + } +} + +// ------------------------------------------------------------------------------------------------ +// Construct a mesh from given vertex positions +aiMesh *StandardShapes::MakeMesh(const std::vector<aiVector3D> &positions, + unsigned int numIndices) { + if (positions.empty() || !numIndices) { + return nullptr; + } + + // Determine which kinds of primitives the mesh consists of + aiMesh *out = new aiMesh(); + switch (numIndices) { + case 1: + out->mPrimitiveTypes = aiPrimitiveType_POINT; + break; + case 2: + out->mPrimitiveTypes = aiPrimitiveType_LINE; + break; + case 3: + out->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + break; + default: + out->mPrimitiveTypes = aiPrimitiveType_POLYGON; + break; + }; + + out->mNumFaces = (unsigned int)positions.size() / numIndices; + out->mFaces = new aiFace[out->mNumFaces]; + for (unsigned int i = 0, a = 0; i < out->mNumFaces; ++i) { + aiFace &f = out->mFaces[i]; + f.mNumIndices = numIndices; + f.mIndices = new unsigned int[numIndices]; + for (unsigned int j = 0; j < numIndices; ++j, ++a) { + f.mIndices[j] = a; + } + } + out->mNumVertices = (unsigned int)positions.size(); + out->mVertices = new aiVector3D[out->mNumVertices]; + ::memcpy(out->mVertices, &positions[0], out->mNumVertices * sizeof(aiVector3D)); + + return out; +} + +// ------------------------------------------------------------------------------------------------ +// Construct a mesh with a specific shape (callback) +aiMesh *StandardShapes::MakeMesh(unsigned int (*GenerateFunc)( + std::vector<aiVector3D> &)) { + std::vector<aiVector3D> temp; + unsigned num = (*GenerateFunc)(temp); + return MakeMesh(temp, num); +} + +// ------------------------------------------------------------------------------------------------ +// Construct a mesh with a specific shape (callback) +aiMesh *StandardShapes::MakeMesh(unsigned int (*GenerateFunc)( + std::vector<aiVector3D> &, bool)) { + std::vector<aiVector3D> temp; + unsigned num = (*GenerateFunc)(temp, true); + return MakeMesh(temp, num); +} + +// ------------------------------------------------------------------------------------------------ +// Construct a mesh with a specific shape (callback) +aiMesh *StandardShapes::MakeMesh(unsigned int num, void (*GenerateFunc)( + unsigned int, std::vector<aiVector3D> &)) { + std::vector<aiVector3D> temp; + (*GenerateFunc)(num, temp); + return MakeMesh(temp, 3); +} + +// ------------------------------------------------------------------------------------------------ +// Build an incosahedron with points.magnitude == 1 +unsigned int StandardShapes::MakeIcosahedron(std::vector<aiVector3D> &positions) { + positions.reserve(positions.size() + 60); + + const ai_real t = (ai_real(1.0) + ai_real(2.236067977)) / ai_real(2.0); + const ai_real s = std::sqrt(ai_real(1.0) + t * t); + + const aiVector3D v0 = aiVector3D(t, 1.0, 0.0) / s; + const aiVector3D v1 = aiVector3D(-t, 1.0, 0.0) / s; + const aiVector3D v2 = aiVector3D(t, -1.0, 0.0) / s; + const aiVector3D v3 = aiVector3D(-t, -1.0, 0.0) / s; + const aiVector3D v4 = aiVector3D(1.0, 0.0, t) / s; + const aiVector3D v5 = aiVector3D(1.0, 0.0, -t) / s; + const aiVector3D v6 = aiVector3D(-1.0, 0.0, t) / s; + const aiVector3D v7 = aiVector3D(-1.0, 0.0, -t) / s; + const aiVector3D v8 = aiVector3D(0.0, t, 1.0) / s; + const aiVector3D v9 = aiVector3D(0.0, -t, 1.0) / s; + const aiVector3D v10 = aiVector3D(0.0, t, -1.0) / s; + const aiVector3D v11 = aiVector3D(0.0, -t, -1.0) / s; + + ADD_TRIANGLE(v0, v8, v4); + ADD_TRIANGLE(v0, v5, v10); + ADD_TRIANGLE(v2, v4, v9); + ADD_TRIANGLE(v2, v11, v5); + + ADD_TRIANGLE(v1, v6, v8); + ADD_TRIANGLE(v1, v10, v7); + ADD_TRIANGLE(v3, v9, v6); + ADD_TRIANGLE(v3, v7, v11); + + ADD_TRIANGLE(v0, v10, v8); + ADD_TRIANGLE(v1, v8, v10); + ADD_TRIANGLE(v2, v9, v11); + ADD_TRIANGLE(v3, v11, v9); + + ADD_TRIANGLE(v4, v2, v0); + ADD_TRIANGLE(v5, v0, v2); + ADD_TRIANGLE(v6, v1, v3); + ADD_TRIANGLE(v7, v3, v1); + + ADD_TRIANGLE(v8, v6, v4); + ADD_TRIANGLE(v9, v4, v6); + ADD_TRIANGLE(v10, v5, v7); + ADD_TRIANGLE(v11, v7, v5); + return 3; +} + +// ------------------------------------------------------------------------------------------------ +// Build a dodecahedron with points.magnitude == 1 +unsigned int StandardShapes::MakeDodecahedron(std::vector<aiVector3D> &positions, + bool polygons /*= false*/) { + positions.reserve(positions.size() + 108); + + const ai_real a = ai_real(1.0) / ai_real(1.7320508); + const ai_real b = std::sqrt((ai_real(3.0) - ai_real(2.23606797)) / ai_real(6.0)); + const ai_real c = std::sqrt((ai_real(3.0) + ai_real(2.23606797f)) / ai_real(6.0)); + + const aiVector3D v0 = aiVector3D(a, a, a); + const aiVector3D v1 = aiVector3D(a, a, -a); + const aiVector3D v2 = aiVector3D(a, -a, a); + const aiVector3D v3 = aiVector3D(a, -a, -a); + const aiVector3D v4 = aiVector3D(-a, a, a); + const aiVector3D v5 = aiVector3D(-a, a, -a); + const aiVector3D v6 = aiVector3D(-a, -a, a); + const aiVector3D v7 = aiVector3D(-a, -a, -a); + const aiVector3D v8 = aiVector3D(b, c, 0.0); + const aiVector3D v9 = aiVector3D(-b, c, 0.0); + const aiVector3D v10 = aiVector3D(b, -c, 0.0); + const aiVector3D v11 = aiVector3D(-b, -c, 0.0); + const aiVector3D v12 = aiVector3D(c, 0.0, b); + const aiVector3D v13 = aiVector3D(c, 0.0, -b); + const aiVector3D v14 = aiVector3D(-c, 0.0, b); + const aiVector3D v15 = aiVector3D(-c, 0.0, -b); + const aiVector3D v16 = aiVector3D(0.0, b, c); + const aiVector3D v17 = aiVector3D(0.0, -b, c); + const aiVector3D v18 = aiVector3D(0.0, b, -c); + const aiVector3D v19 = aiVector3D(0.0, -b, -c); + + ADD_PENTAGON(v0, v8, v9, v4, v16); + ADD_PENTAGON(v0, v12, v13, v1, v8); + ADD_PENTAGON(v0, v16, v17, v2, v12); + ADD_PENTAGON(v8, v1, v18, v5, v9); + ADD_PENTAGON(v12, v2, v10, v3, v13); + ADD_PENTAGON(v16, v4, v14, v6, v17); + ADD_PENTAGON(v9, v5, v15, v14, v4); + + ADD_PENTAGON(v6, v11, v10, v2, v17); + ADD_PENTAGON(v3, v19, v18, v1, v13); + ADD_PENTAGON(v7, v15, v5, v18, v19); + ADD_PENTAGON(v7, v11, v6, v14, v15); + ADD_PENTAGON(v7, v19, v3, v10, v11); + return (polygons ? 5 : 3); +} + +// ------------------------------------------------------------------------------------------------ +// Build an octahedron with points.magnitude == 1 +unsigned int StandardShapes::MakeOctahedron(std::vector<aiVector3D> &positions) { + positions.reserve(positions.size() + 24); + + const aiVector3D v0 = aiVector3D(1.0, 0.0, 0.0); + const aiVector3D v1 = aiVector3D(-1.0, 0.0, 0.0); + const aiVector3D v2 = aiVector3D(0.0, 1.0, 0.0); + const aiVector3D v3 = aiVector3D(0.0, -1.0, 0.0); + const aiVector3D v4 = aiVector3D(0.0, 0.0, 1.0); + const aiVector3D v5 = aiVector3D(0.0, 0.0, -1.0); + + ADD_TRIANGLE(v4, v0, v2); + ADD_TRIANGLE(v4, v2, v1); + ADD_TRIANGLE(v4, v1, v3); + ADD_TRIANGLE(v4, v3, v0); + + ADD_TRIANGLE(v5, v2, v0); + ADD_TRIANGLE(v5, v1, v2); + ADD_TRIANGLE(v5, v3, v1); + ADD_TRIANGLE(v5, v0, v3); + return 3; +} + +// ------------------------------------------------------------------------------------------------ +// Build a tetrahedron with points.magnitude == 1 +unsigned int StandardShapes::MakeTetrahedron(std::vector<aiVector3D> &positions) { + positions.reserve(positions.size() + 9); + + const ai_real invThree = ai_real(1.0) / ai_real(3.0); + const ai_real a = ai_real(1.41421) * invThree; + const ai_real b = ai_real(2.4494) * invThree; + + const aiVector3D v0 = aiVector3D(0.0, 0.0, 1.0); + const aiVector3D v1 = aiVector3D(2 * a, 0, -invThree); + const aiVector3D v2 = aiVector3D(-a, b, -invThree); + const aiVector3D v3 = aiVector3D(-a, -b, -invThree); + + ADD_TRIANGLE(v0, v1, v2); + ADD_TRIANGLE(v0, v2, v3); + ADD_TRIANGLE(v0, v3, v1); + ADD_TRIANGLE(v1, v3, v2); + return 3; +} + +// ------------------------------------------------------------------------------------------------ +// Build a hexahedron with points.magnitude == 1 +unsigned int StandardShapes::MakeHexahedron(std::vector<aiVector3D> &positions, + bool polygons /*= false*/) { + positions.reserve(positions.size() + 36); + const ai_real length = ai_real(1.0) / ai_real(1.73205080); + + const aiVector3D v0 = aiVector3D(-1.0, -1.0, -1.0) * length; + const aiVector3D v1 = aiVector3D(1.0, -1.0, -1.0) * length; + const aiVector3D v2 = aiVector3D(1.0, 1.0, -1.0) * length; + const aiVector3D v3 = aiVector3D(-1.0, 1.0, -1.0) * length; + const aiVector3D v4 = aiVector3D(-1.0, -1.0, 1.0) * length; + const aiVector3D v5 = aiVector3D(1.0, -1.0, 1.0) * length; + const aiVector3D v6 = aiVector3D(1.0, 1.0, 1.0) * length; + const aiVector3D v7 = aiVector3D(-1.0, 1.0, 1.0) * length; + + ADD_QUAD(v0, v3, v2, v1); + ADD_QUAD(v0, v1, v5, v4); + ADD_QUAD(v0, v4, v7, v3); + ADD_QUAD(v6, v5, v1, v2); + ADD_QUAD(v6, v2, v3, v7); + ADD_QUAD(v6, v7, v4, v5); + return (polygons ? 4 : 3); +} + +// Cleanup ... +#undef ADD_TRIANGLE +#undef ADD_QUAD +#undef ADD_PENTAGON + +// ------------------------------------------------------------------------------------------------ +// Create a subdivision sphere +void StandardShapes::MakeSphere(unsigned int tess, + std::vector<aiVector3D> &positions) { + // Reserve enough storage. Every subdivision + // splits each triangle in 4, the icosahedron consists of 60 verts + positions.reserve(positions.size() + 60 * integer_pow(4, tess)); + + // Construct an icosahedron to start with + MakeIcosahedron(positions); + + // ... and subdivide it until the requested output + // tessellation is reached + for (unsigned int i = 0; i < tess; ++i) + Subdivide(positions); +} + +// ------------------------------------------------------------------------------------------------ +// Build a cone +void StandardShapes::MakeCone(ai_real height, ai_real radius1, + ai_real radius2, unsigned int tess, + std::vector<aiVector3D> &positions, bool bOpen /*= false */) { + // Sorry, a cone with less than 3 segments makes ABSOLUTELY NO SENSE + if (tess < 3 || !height) + return; + + size_t old = positions.size(); + + // No negative radii + radius1 = std::fabs(radius1); + radius2 = std::fabs(radius2); + + ai_real halfHeight = height / ai_real(2.0); + + // radius1 is always the smaller one + if (radius2 > radius1) { + std::swap(radius2, radius1); + halfHeight = -halfHeight; + } else + old = SIZE_MAX; + + // Use a large epsilon to check whether the cone is pointy + if (radius1 < (radius2 - radius1) * 10e-3) radius1 = 0.0; + + // We will need 3*2 verts per segment + 3*2 verts per segment + // if the cone is closed + const unsigned int mem = tess * 6 + (!bOpen ? tess * 3 * (radius1 ? 2 : 1) : 0); + positions.reserve(positions.size() + mem); + + // Now construct all segments + const ai_real angle_delta = (ai_real)AI_MATH_TWO_PI / tess; + const ai_real angle_max = (ai_real)AI_MATH_TWO_PI; + + ai_real s = 1.0; // std::cos(angle == 0); + ai_real t = 0.0; // std::sin(angle == 0); + + for (ai_real angle = 0.0; angle < angle_max;) { + const aiVector3D v1 = aiVector3D(s * radius1, -halfHeight, t * radius1); + const aiVector3D v2 = aiVector3D(s * radius2, halfHeight, t * radius2); + + const ai_real next = angle + angle_delta; + ai_real s2 = std::cos(next); + ai_real t2 = std::sin(next); + + const aiVector3D v3 = aiVector3D(s2 * radius2, halfHeight, t2 * radius2); + const aiVector3D v4 = aiVector3D(s2 * radius1, -halfHeight, t2 * radius1); + + positions.push_back(v1); + positions.push_back(v2); + positions.push_back(v3); + positions.push_back(v4); + positions.push_back(v1); + positions.push_back(v3); + + if (!bOpen) { + // generate the end 'cap' + positions.push_back(aiVector3D(s * radius2, halfHeight, t * radius2)); + positions.push_back(aiVector3D(s2 * radius2, halfHeight, t2 * radius2)); + positions.push_back(aiVector3D(0.0, halfHeight, 0.0)); + + if (radius1) { + // generate the other end 'cap' + positions.push_back(aiVector3D(s * radius1, -halfHeight, t * radius1)); + positions.push_back(aiVector3D(s2 * radius1, -halfHeight, t2 * radius1)); + positions.push_back(aiVector3D(0.0, -halfHeight, 0.0)); + } + } + s = s2; + t = t2; + angle = next; + } + + // Need to flip face order? + if (SIZE_MAX != old) { + for (size_t p = old; p < positions.size(); p += 3) { + std::swap(positions[p], positions[p + 1]); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Build a circle +void StandardShapes::MakeCircle(ai_real radius, unsigned int tess, + std::vector<aiVector3D> &positions) { + // Sorry, a circle with less than 3 segments makes ABSOLUTELY NO SENSE + if (tess < 3 || !radius) + return; + + radius = std::fabs(radius); + + // We will need 3 vertices per segment + positions.reserve(positions.size() + tess * 3); + + const ai_real angle_delta = (ai_real)AI_MATH_TWO_PI / tess; + const ai_real angle_max = (ai_real)AI_MATH_TWO_PI; + + ai_real s = 1.0; // std::cos(angle == 0); + ai_real t = 0.0; // std::sin(angle == 0); + + for (ai_real angle = 0.0; angle < angle_max;) { + positions.push_back(aiVector3D(s * radius, 0.0, t * radius)); + angle += angle_delta; + s = std::cos(angle); + t = std::sin(angle); + positions.push_back(aiVector3D(s * radius, 0.0, t * radius)); + + positions.push_back(aiVector3D(0.0, 0.0, 0.0)); + } +} + +} // namespace Assimp diff --git a/libs/assimp/code/Common/StdOStreamLogStream.h b/libs/assimp/code/Common/StdOStreamLogStream.h new file mode 100644 index 0000000..cc0e062 --- /dev/null +++ b/libs/assimp/code/Common/StdOStreamLogStream.h @@ -0,0 +1,101 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 StdOStreamLogStream.h +* @brief Implementation of StdOStreamLogStream +*/ + +#ifndef AI_STROSTREAMLOGSTREAM_H_INC +#define AI_STROSTREAMLOGSTREAM_H_INC + +#include <assimp/LogStream.hpp> +#include <ostream> + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** @class StdOStreamLogStream + * @brief Logs into a std::ostream + */ +class StdOStreamLogStream : public LogStream { +public: + /** @brief Construction from an existing std::ostream + * @param _ostream Output stream to be used + */ + explicit StdOStreamLogStream(std::ostream& _ostream); + + /** @brief Destructor */ + ~StdOStreamLogStream(); + + /** @brief Writer */ + void write(const char* message); + +private: + std::ostream& mOstream; +}; + +// --------------------------------------------------------------------------- +// Default constructor +inline StdOStreamLogStream::StdOStreamLogStream(std::ostream& _ostream) +: mOstream (_ostream){ + // empty +} + +// --------------------------------------------------------------------------- +// Default constructor +inline StdOStreamLogStream::~StdOStreamLogStream() { + // empty +} + +// --------------------------------------------------------------------------- +// Write method +inline void StdOStreamLogStream::write(const char* message) { + mOstream << message; + mOstream.flush(); +} + +// --------------------------------------------------------------------------- + +} // Namespace Assimp + +#endif // guard diff --git a/libs/assimp/code/Common/Subdivision.cpp b/libs/assimp/code/Common/Subdivision.cpp new file mode 100644 index 0000000..705ea3f --- /dev/null +++ b/libs/assimp/code/Common/Subdivision.cpp @@ -0,0 +1,581 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#include <assimp/Subdivision.h> +#include <assimp/SceneCombiner.h> +#include <assimp/SpatialSort.h> +#include <assimp/Vertex.h> +#include <assimp/ai_assert.h> + +#include "PostProcessing/ProcessHelper.h" + +#include <stdio.h> + +using namespace Assimp; +void mydummy() {} + +#ifdef _MSC_VER +#pragma warning(disable : 4709) +#endif // _MSC_VER +// ------------------------------------------------------------------------------------------------ +/** Subdivider stub class to implement the Catmull-Clarke subdivision algorithm. The + * implementation is basing on recursive refinement. Directly evaluating the result is also + * possible and much quicker, but it depends on lengthy matrix lookup tables. */ +// ------------------------------------------------------------------------------------------------ +class CatmullClarkSubdivider : public Subdivider { +public: + void Subdivide(aiMesh *mesh, aiMesh *&out, unsigned int num, bool discard_input); + void Subdivide(aiMesh **smesh, size_t nmesh, + aiMesh **out, unsigned int num, bool discard_input); + + // --------------------------------------------------------------------------- + /** Intermediate description of an edge between two corners of a polygon*/ + // --------------------------------------------------------------------------- + struct Edge { + Edge() : + ref(0) {} + Vertex edge_point, midpoint; + unsigned int ref; + }; + + typedef std::vector<unsigned int> UIntVector; + typedef std::map<uint64_t, Edge> EdgeMap; + + // --------------------------------------------------------------------------- + // Hashing function to derive an index into an #EdgeMap from two given + // 'unsigned int' vertex coordinates (!!distinct coordinates - same + // vertex position == same index!!). + // NOTE - this leads to rare hash collisions if a) sizeof(unsigned int)>4 + // and (id[0]>2^32-1 or id[0]>2^32-1). + // MAKE_EDGE_HASH() uses temporaries, so INIT_EDGE_HASH() needs to be put + // at the head of every function which is about to use MAKE_EDGE_HASH(). + // Reason is that the hash is that hash construction needs to hold the + // invariant id0<id1 to identify an edge - else two hashes would refer + // to the same edge. + // --------------------------------------------------------------------------- +#define MAKE_EDGE_HASH(id0, id1) (eh_tmp0__ = id0, eh_tmp1__ = id1, \ + (eh_tmp0__ < eh_tmp1__ ? std::swap(eh_tmp0__, eh_tmp1__) : mydummy()), (uint64_t)eh_tmp0__ ^ ((uint64_t)eh_tmp1__ << 32u)) + +#define INIT_EDGE_HASH_TEMPORARIES() \ + unsigned int eh_tmp0__, eh_tmp1__; + +private: + void InternSubdivide(const aiMesh *const *smesh, + size_t nmesh, aiMesh **out, unsigned int num); +}; + +// ------------------------------------------------------------------------------------------------ +// Construct a subdivider of a specific type +Subdivider *Subdivider::Create(Algorithm algo) { + switch (algo) { + case CATMULL_CLARKE: + return new CatmullClarkSubdivider(); + }; + + ai_assert(false); + + return nullptr; // shouldn't happen +} + +// ------------------------------------------------------------------------------------------------ +// Call the Catmull Clark subdivision algorithm for one mesh +void CatmullClarkSubdivider::Subdivide( + aiMesh *mesh, + aiMesh *&out, + unsigned int num, + bool discard_input) { + ai_assert(mesh != out); + + Subdivide(&mesh, 1, &out, num, discard_input); +} + +// ------------------------------------------------------------------------------------------------ +// Call the Catmull Clark subdivision algorithm for multiple meshes +void CatmullClarkSubdivider::Subdivide( + aiMesh **smesh, + size_t nmesh, + aiMesh **out, + unsigned int num, + bool discard_input) { + ai_assert(nullptr != smesh); + ai_assert(nullptr != out); + + // course, both regions may not overlap + ai_assert(smesh < out || smesh + nmesh > out + nmesh); + if (!num) { + // No subdivision at all. Need to copy all the meshes .. argh. + if (discard_input) { + for (size_t s = 0; s < nmesh; ++s) { + out[s] = smesh[s]; + smesh[s] = nullptr; + } + } else { + for (size_t s = 0; s < nmesh; ++s) { + SceneCombiner::Copy(out + s, smesh[s]); + } + } + return; + } + + std::vector<aiMesh *> inmeshes; + std::vector<aiMesh *> outmeshes; + std::vector<unsigned int> maptbl; + + inmeshes.reserve(nmesh); + outmeshes.reserve(nmesh); + maptbl.reserve(nmesh); + + // Remove pure line and point meshes from the working set to reduce the + // number of edge cases the subdivider is forced to deal with. Line and + // point meshes are simply passed through. + for (size_t s = 0; s < nmesh; ++s) { + aiMesh *i = smesh[s]; + // FIX - mPrimitiveTypes might not yet be initialized + if (i->mPrimitiveTypes && (i->mPrimitiveTypes & (aiPrimitiveType_LINE | aiPrimitiveType_POINT)) == i->mPrimitiveTypes) { + ASSIMP_LOG_VERBOSE_DEBUG("Catmull-Clark Subdivider: Skipping pure line/point mesh"); + + if (discard_input) { + out[s] = i; + smesh[s] = nullptr; + } else { + SceneCombiner::Copy(out + s, i); + } + continue; + } + + outmeshes.push_back(nullptr); + inmeshes.push_back(i); + maptbl.push_back(static_cast<unsigned int>(s)); + } + + // Do the actual subdivision on the preallocated storage. InternSubdivide + // *always* assumes that enough storage is available, it does not bother + // checking any ranges. + ai_assert(inmeshes.size() == outmeshes.size()); + ai_assert(inmeshes.size() == maptbl.size()); + if (inmeshes.empty()) { + ASSIMP_LOG_WARN("Catmull-Clark Subdivider: Pure point/line scene, I can't do anything"); + return; + } + InternSubdivide(&inmeshes.front(), inmeshes.size(), &outmeshes.front(), num); + for (unsigned int i = 0; i < maptbl.size(); ++i) { + ai_assert(nullptr != outmeshes[i]); + out[maptbl[i]] = outmeshes[i]; + } + + if (discard_input) { + for (size_t s = 0; s < nmesh; ++s) { + delete smesh[s]; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Note - this is an implementation of the standard (recursive) Cm-Cl algorithm without further +// optimizations (except we're using some nice LUTs). A description of the algorithm can be found +// here: http://en.wikipedia.org/wiki/Catmull-Clark_subdivision_surface +// +// The code is mostly O(n), however parts are O(nlogn) which is therefore the algorithm's +// expected total runtime complexity. The implementation is able to work in-place on the same +// mesh arrays. Calling #InternSubdivide() directly is not encouraged. The code can operate +// in-place unless 'smesh' and 'out' are equal (no strange overlaps or reorderings). +// Previous data is replaced/deleted then. +// ------------------------------------------------------------------------------------------------ +void CatmullClarkSubdivider::InternSubdivide( + const aiMesh *const *smesh, + size_t nmesh, + aiMesh **out, + unsigned int num) { + ai_assert(nullptr != smesh); + ai_assert(nullptr != out); + + INIT_EDGE_HASH_TEMPORARIES(); + + // no subdivision requested or end of recursive refinement + if (!num) { + return; + } + + UIntVector maptbl; + SpatialSort spatial; + + // --------------------------------------------------------------------- + // 0. Offset table to index all meshes continuously, generate a spatially + // sorted representation of all vertices in all meshes. + // --------------------------------------------------------------------- + typedef std::pair<unsigned int, unsigned int> IntPair; + std::vector<IntPair> moffsets(nmesh); + unsigned int totfaces = 0, totvert = 0; + for (size_t t = 0; t < nmesh; ++t) { + const aiMesh *mesh = smesh[t]; + + spatial.Append(mesh->mVertices, mesh->mNumVertices, sizeof(aiVector3D), false); + moffsets[t] = IntPair(totfaces, totvert); + + totfaces += mesh->mNumFaces; + totvert += mesh->mNumVertices; + } + + spatial.Finalize(); + const unsigned int num_unique = spatial.GenerateMappingTable(maptbl, ComputePositionEpsilon(smesh, nmesh)); + +#define FLATTEN_VERTEX_IDX(mesh_idx, vert_idx) (moffsets[mesh_idx].second + vert_idx) +#define FLATTEN_FACE_IDX(mesh_idx, face_idx) (moffsets[mesh_idx].first + face_idx) + + // --------------------------------------------------------------------- + // 1. Compute the centroid point for all faces + // --------------------------------------------------------------------- + std::vector<Vertex> centroids(totfaces); + unsigned int nfacesout = 0; + for (size_t t = 0, n = 0; t < nmesh; ++t) { + const aiMesh *mesh = smesh[t]; + for (unsigned int i = 0; i < mesh->mNumFaces; ++i, ++n) { + const aiFace &face = mesh->mFaces[i]; + Vertex &c = centroids[n]; + + for (unsigned int a = 0; a < face.mNumIndices; ++a) { + c += Vertex(mesh, face.mIndices[a]); + } + + c /= static_cast<float>(face.mNumIndices); + nfacesout += face.mNumIndices; + } + } + + { + // we want edges to go away before the recursive calls so begin a new scope + EdgeMap edges; + + // --------------------------------------------------------------------- + // 2. Set each edge point to be the average of all neighbouring + // face points and original points. Every edge exists twice + // if there is a neighboring face. + // --------------------------------------------------------------------- + for (size_t t = 0; t < nmesh; ++t) { + const aiMesh *mesh = smesh[t]; + + for (unsigned int i = 0; i < mesh->mNumFaces; ++i) { + const aiFace &face = mesh->mFaces[i]; + + for (unsigned int p = 0; p < face.mNumIndices; ++p) { + const unsigned int id[] = { + face.mIndices[p], + face.mIndices[p == face.mNumIndices - 1 ? 0 : p + 1] + }; + const unsigned int mp[] = { + maptbl[FLATTEN_VERTEX_IDX(t, id[0])], + maptbl[FLATTEN_VERTEX_IDX(t, id[1])] + }; + + Edge &e = edges[MAKE_EDGE_HASH(mp[0], mp[1])]; + e.ref++; + if (e.ref <= 2) { + if (e.ref == 1) { // original points (end points) - add only once + e.edge_point = e.midpoint = Vertex(mesh, id[0]) + Vertex(mesh, id[1]); + e.midpoint *= 0.5f; + } + e.edge_point += centroids[FLATTEN_FACE_IDX(t, i)]; + } + } + } + } + + // --------------------------------------------------------------------- + // 3. Normalize edge points + // --------------------------------------------------------------------- + { + unsigned int bad_cnt = 0; + for (EdgeMap::iterator it = edges.begin(); it != edges.end(); ++it) { + if ((*it).second.ref < 2) { + ai_assert((*it).second.ref); + ++bad_cnt; + } + (*it).second.edge_point *= 1.f / ((*it).second.ref + 2.f); + } + + if (bad_cnt) { + // Report the number of bad edges. bad edges are referenced by less than two + // faces in the mesh. They occur at outer model boundaries in non-closed + // shapes. + ASSIMP_LOG_VERBOSE_DEBUG("Catmull-Clark Subdivider: got ", bad_cnt, " bad edges touching only one face (totally ", + static_cast<unsigned int>(edges.size()), " edges). "); + } + } + + // --------------------------------------------------------------------- + // 4. Compute a vertex-face adjacency table. We can't reuse the code + // from VertexTriangleAdjacency because we need the table for multiple + // meshes and out vertex indices need to be mapped to distinct values + // first. + // --------------------------------------------------------------------- + UIntVector faceadjac(nfacesout), cntadjfac(maptbl.size(), 0), ofsadjvec(maptbl.size() + 1, 0); + { + for (size_t t = 0; t < nmesh; ++t) { + const aiMesh *const minp = smesh[t]; + for (unsigned int i = 0; i < minp->mNumFaces; ++i) { + + const aiFace &f = minp->mFaces[i]; + for (unsigned int n = 0; n < f.mNumIndices; ++n) { + ++cntadjfac[maptbl[FLATTEN_VERTEX_IDX(t, f.mIndices[n])]]; + } + } + } + unsigned int cur = 0; + for (size_t i = 0; i < cntadjfac.size(); ++i) { + ofsadjvec[i + 1] = cur; + cur += cntadjfac[i]; + } + for (size_t t = 0; t < nmesh; ++t) { + const aiMesh *const minp = smesh[t]; + for (unsigned int i = 0; i < minp->mNumFaces; ++i) { + + const aiFace &f = minp->mFaces[i]; + for (unsigned int n = 0; n < f.mNumIndices; ++n) { + faceadjac[ofsadjvec[1 + maptbl[FLATTEN_VERTEX_IDX(t, f.mIndices[n])]]++] = FLATTEN_FACE_IDX(t, i); + } + } + } + + // check the other way round for consistency +#ifdef ASSIMP_BUILD_DEBUG + + for (size_t t = 0; t < ofsadjvec.size() - 1; ++t) { + for (unsigned int m = 0; m < cntadjfac[t]; ++m) { + const unsigned int fidx = faceadjac[ofsadjvec[t] + m]; + ai_assert(fidx < totfaces); + for (size_t n = 1; n < nmesh; ++n) { + + if (moffsets[n].first > fidx) { + const aiMesh *msh = smesh[--n]; + const aiFace &f = msh->mFaces[fidx - moffsets[n].first]; + + bool haveit = false; + for (unsigned int i = 0; i < f.mNumIndices; ++i) { + if (maptbl[FLATTEN_VERTEX_IDX(n, f.mIndices[i])] == (unsigned int)t) { + haveit = true; + break; + } + } + ai_assert(haveit); + if (!haveit) { + ASSIMP_LOG_VERBOSE_DEBUG("Catmull-Clark Subdivider: Index not used"); + } + break; + } + } + } + } + +#endif + } + +#define GET_ADJACENT_FACES_AND_CNT(vidx, fstartout, numout) \ + fstartout = &faceadjac[ofsadjvec[vidx]], numout = cntadjfac[vidx] + + typedef std::pair<bool, Vertex> TouchedOVertex; + std::vector<TouchedOVertex> new_points(num_unique, TouchedOVertex(false, Vertex())); + // --------------------------------------------------------------------- + // 5. Spawn a quad from each face point to the corresponding edge points + // the original points being the fourth quad points. + // --------------------------------------------------------------------- + for (size_t t = 0; t < nmesh; ++t) { + const aiMesh *const minp = smesh[t]; + aiMesh *const mout = out[t] = new aiMesh(); + + for (unsigned int a = 0; a < minp->mNumFaces; ++a) { + mout->mNumFaces += minp->mFaces[a].mNumIndices; + } + + // We need random access to the old face buffer, so reuse is not possible. + mout->mFaces = new aiFace[mout->mNumFaces]; + + mout->mNumVertices = mout->mNumFaces * 4; + mout->mVertices = new aiVector3D[mout->mNumVertices]; + + // quads only, keep material index + mout->mPrimitiveTypes = aiPrimitiveType_POLYGON; + mout->mMaterialIndex = minp->mMaterialIndex; + + if (minp->HasNormals()) { + mout->mNormals = new aiVector3D[mout->mNumVertices]; + } + + if (minp->HasTangentsAndBitangents()) { + mout->mTangents = new aiVector3D[mout->mNumVertices]; + mout->mBitangents = new aiVector3D[mout->mNumVertices]; + } + + for (unsigned int i = 0; minp->HasTextureCoords(i); ++i) { + mout->mTextureCoords[i] = new aiVector3D[mout->mNumVertices]; + mout->mNumUVComponents[i] = minp->mNumUVComponents[i]; + } + + for (unsigned int i = 0; minp->HasVertexColors(i); ++i) { + mout->mColors[i] = new aiColor4D[mout->mNumVertices]; + } + + mout->mNumVertices = mout->mNumFaces << 2u; + for (unsigned int i = 0, v = 0, n = 0; i < minp->mNumFaces; ++i) { + + const aiFace &face = minp->mFaces[i]; + for (unsigned int a = 0; a < face.mNumIndices; ++a) { + + // Get a clean new face. + aiFace &faceOut = mout->mFaces[n++]; + faceOut.mIndices = new unsigned int[faceOut.mNumIndices = 4]; + + // Spawn a new quadrilateral (ccw winding) for this original point between: + // a) face centroid + centroids[FLATTEN_FACE_IDX(t, i)].SortBack(mout, faceOut.mIndices[0] = v++); + + // b) adjacent edge on the left, seen from the centroid + const Edge &e0 = edges[MAKE_EDGE_HASH(maptbl[FLATTEN_VERTEX_IDX(t, face.mIndices[a])], + maptbl[FLATTEN_VERTEX_IDX(t, face.mIndices[a == face.mNumIndices - 1 ? 0 : a + 1])])]; // fixme: replace with mod face.mNumIndices? + + // c) adjacent edge on the right, seen from the centroid + const Edge &e1 = edges[MAKE_EDGE_HASH(maptbl[FLATTEN_VERTEX_IDX(t, face.mIndices[a])], + maptbl[FLATTEN_VERTEX_IDX(t, face.mIndices[!a ? face.mNumIndices - 1 : a - 1])])]; // fixme: replace with mod face.mNumIndices? + + e0.edge_point.SortBack(mout, faceOut.mIndices[3] = v++); + e1.edge_point.SortBack(mout, faceOut.mIndices[1] = v++); + + // d= original point P with distinct index i + // F := 0 + // R := 0 + // n := 0 + // for each face f containing i + // F := F+ centroid of f + // R := R+ midpoint of edge of f from i to i+1 + // n := n+1 + // + // (F+2R+(n-3)P)/n + const unsigned int org = maptbl[FLATTEN_VERTEX_IDX(t, face.mIndices[a])]; + TouchedOVertex &ov = new_points[org]; + + if (!ov.first) { + ov.first = true; + + const unsigned int *adj; + unsigned int cnt; + GET_ADJACENT_FACES_AND_CNT(org, adj, cnt); + + if (cnt < 3) { + ov.second = Vertex(minp, face.mIndices[a]); + } else { + + Vertex F, R; + for (unsigned int o = 0; o < cnt; ++o) { + ai_assert(adj[o] < totfaces); + F += centroids[adj[o]]; + + // adj[0] is a global face index - search the face in the mesh list + const aiMesh *mp = nullptr; + size_t nidx; + + if (adj[o] < moffsets[0].first) { + mp = smesh[nidx = 0]; + } else { + for (nidx = 1; nidx <= nmesh; ++nidx) { + if (nidx == nmesh || moffsets[nidx].first > adj[o]) { + mp = smesh[--nidx]; + break; + } + } + } + + ai_assert(adj[o] - moffsets[nidx].first < mp->mNumFaces); + const aiFace &f = mp->mFaces[adj[o] - moffsets[nidx].first]; + bool haveit = false; + + // find our original point in the face + for (unsigned int m = 0; m < f.mNumIndices; ++m) { + if (maptbl[FLATTEN_VERTEX_IDX(nidx, f.mIndices[m])] == org) { + + // add *both* edges. this way, we can be sure that we add + // *all* adjacent edges to R. In a closed shape, every + // edge is added twice - so we simply leave out the + // factor 2.f in the amove formula and get the right + // result. + + const Edge &c0 = edges[MAKE_EDGE_HASH(org, maptbl[FLATTEN_VERTEX_IDX( + nidx, f.mIndices[!m ? f.mNumIndices - 1 : m - 1])])]; + // fixme: replace with mod face.mNumIndices? + + const Edge &c1 = edges[MAKE_EDGE_HASH(org, maptbl[FLATTEN_VERTEX_IDX( + nidx, f.mIndices[m == f.mNumIndices - 1 ? 0 : m + 1])])]; + // fixme: replace with mod face.mNumIndices? + R += c0.midpoint + c1.midpoint; + + haveit = true; + break; + } + } + + // this invariant *must* hold if the vertex-to-face adjacency table is valid + ai_assert(haveit); + if (!haveit) { + ASSIMP_LOG_WARN("OBJ: no name for material library specified."); + } + } + + const float div = static_cast<float>(cnt), divsq = 1.f / (div * div); + ov.second = Vertex(minp, face.mIndices[a]) * ((div - 3.f) / div) + R * divsq + F * divsq; + } + } + ov.second.SortBack(mout, faceOut.mIndices[2] = v++); + } + } + } + } // end of scope for edges, freeing its memory + + // --------------------------------------------------------------------- + // 7. Apply the next subdivision step. + // --------------------------------------------------------------------- + if (num != 1) { + std::vector<aiMesh *> tmp(nmesh); + InternSubdivide(out, nmesh, &tmp.front(), num - 1); + for (size_t i = 0; i < nmesh; ++i) { + delete out[i]; + out[i] = tmp[i]; + } + } +} diff --git a/libs/assimp/code/Common/TargetAnimation.cpp b/libs/assimp/code/Common/TargetAnimation.cpp new file mode 100644 index 0000000..5f6d9ba --- /dev/null +++ b/libs/assimp/code/Common/TargetAnimation.cpp @@ -0,0 +1,226 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#include "TargetAnimation.h" +#include <assimp/ai_assert.h> +#include <algorithm> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +KeyIterator::KeyIterator(const std::vector<aiVectorKey> *_objPos, + const std::vector<aiVectorKey> *_targetObjPos, + const aiVector3D *defaultObjectPos /*= nullptr*/, + const aiVector3D *defaultTargetPos /*= nullptr*/) : + reachedEnd(false), + curTime(-1.), + objPos(_objPos), + targetObjPos(_targetObjPos), + nextObjPos(0), + nextTargetObjPos(0) { + // Generate default transformation tracks if necessary + if (!objPos || objPos->empty()) { + defaultObjPos.resize(1); + defaultObjPos.front().mTime = 10e10; + + if (defaultObjectPos) + defaultObjPos.front().mValue = *defaultObjectPos; + + objPos = &defaultObjPos; + } + if (!targetObjPos || targetObjPos->empty()) { + defaultTargetObjPos.resize(1); + defaultTargetObjPos.front().mTime = 10e10; + + if (defaultTargetPos) + defaultTargetObjPos.front().mValue = *defaultTargetPos; + + targetObjPos = &defaultTargetObjPos; + } +} + +// ------------------------------------------------------------------------------------------------ +template <class T> +inline T Interpolate(const T &one, const T &two, ai_real val) { + return one + (two - one) * val; +} + +// ------------------------------------------------------------------------------------------------ +void KeyIterator::operator++() { + // If we are already at the end of all keyframes, return + if (reachedEnd) { + return; + } + + // Now search in all arrays for the time value closest + // to our current position on the time line + double d0, d1; + + d0 = objPos->at(std::min(nextObjPos, static_cast<unsigned int>(objPos->size() - 1))).mTime; + d1 = targetObjPos->at(std::min(nextTargetObjPos, static_cast<unsigned int>(targetObjPos->size() - 1))).mTime; + + // Easiest case - all are identical. In this + // case we don't need to interpolate so we can + // return earlier + if (d0 == d1) { + curTime = d0; + curPosition = objPos->at(nextObjPos).mValue; + curTargetPosition = targetObjPos->at(nextTargetObjPos).mValue; + + // increment counters + if (objPos->size() != nextObjPos - 1) + ++nextObjPos; + + if (targetObjPos->size() != nextTargetObjPos - 1) + ++nextTargetObjPos; + } + + // An object position key is closest to us + else if (d0 < d1) { + curTime = d0; + + // interpolate the other + if (1 == targetObjPos->size() || !nextTargetObjPos) { + curTargetPosition = targetObjPos->at(0).mValue; + } else { + const aiVectorKey &last = targetObjPos->at(nextTargetObjPos); + const aiVectorKey &first = targetObjPos->at(nextTargetObjPos - 1); + + curTargetPosition = Interpolate(first.mValue, last.mValue, (ai_real)((curTime - first.mTime) / (last.mTime - first.mTime))); + } + + if (objPos->size() != nextObjPos - 1) + ++nextObjPos; + } + // A target position key is closest to us + else { + curTime = d1; + + // interpolate the other + if (1 == objPos->size() || !nextObjPos) { + curPosition = objPos->at(0).mValue; + } else { + const aiVectorKey &last = objPos->at(nextObjPos); + const aiVectorKey &first = objPos->at(nextObjPos - 1); + + curPosition = Interpolate(first.mValue, last.mValue, (ai_real)((curTime - first.mTime) / (last.mTime - first.mTime))); + } + + if (targetObjPos->size() != nextTargetObjPos - 1) + ++nextTargetObjPos; + } + + if (nextObjPos >= objPos->size() - 1 && + nextTargetObjPos >= targetObjPos->size() - 1) { + // We reached the very last keyframe + reachedEnd = true; + } +} + +// ------------------------------------------------------------------------------------------------ +void TargetAnimationHelper::SetTargetAnimationChannel( + const std::vector<aiVectorKey> *_targetPositions) { + ai_assert(nullptr != _targetPositions); + + targetPositions = _targetPositions; +} + +// ------------------------------------------------------------------------------------------------ +void TargetAnimationHelper::SetMainAnimationChannel( + const std::vector<aiVectorKey> *_objectPositions) { + ai_assert(nullptr != _objectPositions); + + objectPositions = _objectPositions; +} + +// ------------------------------------------------------------------------------------------------ +void TargetAnimationHelper::SetFixedMainAnimationChannel( + const aiVector3D &fixed) { + objectPositions = nullptr; // just to avoid confusion + fixedMain = fixed; +} + +// ------------------------------------------------------------------------------------------------ +void TargetAnimationHelper::Process(std::vector<aiVectorKey> *distanceTrack) { + ai_assert(nullptr != targetPositions); + ai_assert(nullptr != distanceTrack); + + // TODO: in most cases we won't need the extra array + std::vector<aiVectorKey> real; + + std::vector<aiVectorKey> *fill = (distanceTrack == objectPositions ? &real : distanceTrack); + fill->reserve(std::max(objectPositions->size(), targetPositions->size())); + + // Iterate through all object keys and interpolate their values if necessary. + // Then get the corresponding target position, compute the difference + // vector between object and target position. Then compute a rotation matrix + // that rotates the base vector of the object coordinate system at that time + // to match the diff vector. + + KeyIterator iter(objectPositions, targetPositions, &fixedMain); + for (; !iter.Finished(); ++iter) { + const aiVector3D &position = iter.GetCurPosition(); + const aiVector3D &tposition = iter.GetCurTargetPosition(); + + // diff vector + aiVector3D diff = tposition - position; + ai_real f = diff.Length(); + + // output distance vector + if (f) { + fill->push_back(aiVectorKey()); + aiVectorKey &v = fill->back(); + v.mTime = iter.GetCurTime(); + v.mValue = diff; + + diff /= f; + } else { + // FIXME: handle this + } + + // diff is now the vector in which our camera is pointing + } + + if (real.size()) { + *distanceTrack = real; + } +} diff --git a/libs/assimp/code/Common/TargetAnimation.h b/libs/assimp/code/Common/TargetAnimation.h new file mode 100644 index 0000000..863406b --- /dev/null +++ b/libs/assimp/code/Common/TargetAnimation.h @@ -0,0 +1,161 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a helper class for the ASE and 3DS loaders to + help them compute camera and spot light animation channels */ +#ifndef AI_TARGET_ANIMATION_H_INC +#define AI_TARGET_ANIMATION_H_INC + +#include <assimp/anim.h> +#include <vector> + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** Helper class to iterate through all keys in an animation channel. + * + * Missing tracks are interpolated. This is a helper class for + * TargetAnimationHelper, but it can be freely used for other purposes. +*/ +class KeyIterator { +public: + // ------------------------------------------------------------------ + /** Constructs a new key iterator + * + * @param _objPos Object position track. May be nullptr. + * @param _targetObjPos Target object position track. May be nullptr. + * @param defaultObjectPos Default object position to be used if + * no animated track is available. May be nullptr. + * @param defaultTargetPos Default target position to be used if + * no animated track is available. May be nullptr. + */ + KeyIterator(const std::vector<aiVectorKey> *_objPos, + const std::vector<aiVectorKey> *_targetObjPos, + const aiVector3D *defaultObjectPos = nullptr, + const aiVector3D *defaultTargetPos = nullptr); + + // ------------------------------------------------------------------ + /** Returns true if all keys have been processed + */ + bool Finished() const { return reachedEnd; } + + // ------------------------------------------------------------------ + /** Increment the iterator + */ + void operator++(); + inline void operator++(int) { return ++(*this); } + + // ------------------------------------------------------------------ + /** Getters to retrieve the current state of the iterator + */ + inline const aiVector3D &GetCurPosition() const { return curPosition; } + + inline const aiVector3D &GetCurTargetPosition() const { return curTargetPosition; } + + inline double GetCurTime() const { return curTime; } + +private: + //! Did we reach the end? + bool reachedEnd; + + //! Represents the current position of the iterator + aiVector3D curPosition, curTargetPosition; + + double curTime; + + //! Input tracks and the next key to process + const std::vector<aiVectorKey> *objPos, *targetObjPos; + + unsigned int nextObjPos, nextTargetObjPos; + std::vector<aiVectorKey> defaultObjPos, defaultTargetObjPos; +}; + +// --------------------------------------------------------------------------- +/** Helper class for the 3DS and ASE loaders to compute camera and spot light + * animations. + * + * 3DS and ASE store the differently to Assimp - there is an animation + * channel for the camera/spot light itself and a separate position + * animation channels specifying the position of the camera/spot light + * look-at target */ +class TargetAnimationHelper { +public: + TargetAnimationHelper() : + targetPositions(nullptr), + objectPositions(nullptr) { + // empty + } + + // ------------------------------------------------------------------ + /** Sets the target animation channel + * + * This channel specifies the position of the camera/spot light + * target at a specific position. + * + * @param targetPositions Translation channel*/ + void SetTargetAnimationChannel(const std::vector<aiVectorKey> *targetPositions); + + // ------------------------------------------------------------------ + /** Sets the main animation channel + * + * @param objectPositions Translation channel */ + void SetMainAnimationChannel(const std::vector<aiVectorKey> *objectPositions); + + // ------------------------------------------------------------------ + /** Sets the main animation channel to a fixed value + * + * @param fixed Fixed value for the main animation channel*/ + void SetFixedMainAnimationChannel(const aiVector3D &fixed); + + // ------------------------------------------------------------------ + /** Computes final animation channels + * @param distanceTrack Receive camera translation keys ... != nullptr. */ + void Process(std::vector<aiVectorKey> *distanceTrack); + +private: + const std::vector<aiVectorKey> *targetPositions, *objectPositions; + aiVector3D fixedMain; +}; + +} // namespace Assimp + +#endif // include guard diff --git a/libs/assimp/code/Common/Version.cpp b/libs/assimp/code/Common/Version.cpp new file mode 100644 index 0000000..86d3f22 --- /dev/null +++ b/libs/assimp/code/Common/Version.cpp @@ -0,0 +1,186 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. +--------------------------------------------------------------------------- +*/ + +// Actually just a dummy, used by the compiler to build the pre-compiled header. + +#include "ScenePrivate.h" +#include <assimp/scene.h> +#include <assimp/version.h> + +#include "revision.h" + +// -------------------------------------------------------------------------------- +// Legal information string - don't remove this. +static const char *LEGAL_INFORMATION = + "Open Asset Import Library (Assimp).\n" + "A free C/C++ library to import various 3D file formats into applications\n\n" + "(c) 2006-2022, Assimp team\n" + "License under the terms and conditions of the 3-clause BSD license\n" + "https://www.assimp.org\n"; + +// ------------------------------------------------------------------------------------------------ +// Get legal string +ASSIMP_API const char *aiGetLegalString() { + return LEGAL_INFORMATION; +} + +// ------------------------------------------------------------------------------------------------ +// Get Assimp patch version +ASSIMP_API unsigned int aiGetVersionPatch() { + return VER_PATCH; +} + +// ------------------------------------------------------------------------------------------------ +// Get Assimp minor version +ASSIMP_API unsigned int aiGetVersionMinor() { + return VER_MINOR; +} + +// ------------------------------------------------------------------------------------------------ +// Get Assimp major version +ASSIMP_API unsigned int aiGetVersionMajor() { + return VER_MAJOR; +} + +// ------------------------------------------------------------------------------------------------ +// Get flags used for compilation +ASSIMP_API unsigned int aiGetCompileFlags() { + + unsigned int flags = 0; + +#ifdef ASSIMP_BUILD_BOOST_WORKAROUND + flags |= ASSIMP_CFLAGS_NOBOOST; +#endif +#ifdef ASSIMP_BUILD_SINGLETHREADED + flags |= ASSIMP_CFLAGS_SINGLETHREADED; +#endif +#ifdef ASSIMP_BUILD_DEBUG + flags |= ASSIMP_CFLAGS_DEBUG; +#endif +#ifdef ASSIMP_BUILD_DLL_EXPORT + flags |= ASSIMP_CFLAGS_SHARED; +#endif +#ifdef _STLPORT_VERSION + flags |= ASSIMP_CFLAGS_STLPORT; +#endif +#ifdef ASSIMP_DOUBLE_PRECISION + flags |= ASSIMP_CFLAGS_DOUBLE_SUPPORT; +#endif + + return flags; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API unsigned int aiGetVersionRevision() { + return GitVersion; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API const char *aiGetBranchName() { + return GitBranch; +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API aiScene::aiScene() : + mFlags(0), + mRootNode(nullptr), + mNumMeshes(0), + mMeshes(nullptr), + mNumMaterials(0), + mMaterials(nullptr), + mNumAnimations(0), + mAnimations(nullptr), + mNumTextures(0), + mTextures(nullptr), + mNumLights(0), + mLights(nullptr), + mNumCameras(0), + mCameras(nullptr), + mMetaData(nullptr), + mPrivate(new Assimp::ScenePrivateData()) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +ASSIMP_API aiScene::~aiScene() { + // delete all sub-objects recursively + delete mRootNode; + + // To make sure we won't crash if the data is invalid it's + // much better to check whether both mNumXXX and mXXX are + // valid instead of relying on just one of them. + if (mNumMeshes && mMeshes) + for (unsigned int a = 0; a < mNumMeshes; a++) + delete mMeshes[a]; + delete[] mMeshes; + + if (mNumMaterials && mMaterials) { + for (unsigned int a = 0; a < mNumMaterials; ++a) { + delete mMaterials[a]; + } + } + delete[] mMaterials; + + if (mNumAnimations && mAnimations) + for (unsigned int a = 0; a < mNumAnimations; a++) + delete mAnimations[a]; + delete[] mAnimations; + + if (mNumTextures && mTextures) + for (unsigned int a = 0; a < mNumTextures; a++) + delete mTextures[a]; + delete[] mTextures; + + if (mNumLights && mLights) + for (unsigned int a = 0; a < mNumLights; a++) + delete mLights[a]; + delete[] mLights; + + if (mNumCameras && mCameras) + for (unsigned int a = 0; a < mNumCameras; a++) + delete mCameras[a]; + delete[] mCameras; + + aiMetadata::Dealloc(mMetaData); + mMetaData = nullptr; + + delete static_cast<Assimp::ScenePrivateData *>(mPrivate); +} diff --git a/libs/assimp/code/Common/VertexTriangleAdjacency.cpp b/libs/assimp/code/Common/VertexTriangleAdjacency.cpp new file mode 100644 index 0000000..555e3e3 --- /dev/null +++ b/libs/assimp/code/Common/VertexTriangleAdjacency.cpp @@ -0,0 +1,131 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the VertexTriangleAdjacency helper class + */ + +// internal headers +#include "VertexTriangleAdjacency.h" +#include <assimp/mesh.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +VertexTriangleAdjacency::VertexTriangleAdjacency(aiFace *pcFaces, + unsigned int iNumFaces, + unsigned int iNumVertices /*= 0*/, + bool bComputeNumTriangles /*= false*/) { + // compute the number of referenced vertices if it wasn't specified by the caller + const aiFace *const pcFaceEnd = pcFaces + iNumFaces; + if (0 == iNumVertices) { + for (aiFace *pcFace = pcFaces; pcFace != pcFaceEnd; ++pcFace) { + ai_assert(nullptr != pcFace); + ai_assert(3 == pcFace->mNumIndices); + iNumVertices = std::max(iNumVertices, pcFace->mIndices[0]); + iNumVertices = std::max(iNumVertices, pcFace->mIndices[1]); + iNumVertices = std::max(iNumVertices, pcFace->mIndices[2]); + } + } + + mNumVertices = iNumVertices + 1; + + unsigned int *pi; + + // allocate storage + if (bComputeNumTriangles) { + pi = mLiveTriangles = new unsigned int[iNumVertices + 1]; + ::memset(mLiveTriangles, 0, sizeof(unsigned int) * (iNumVertices + 1)); + mOffsetTable = new unsigned int[iNumVertices + 2] + 1; + } else { + pi = mOffsetTable = new unsigned int[iNumVertices + 2] + 1; + ::memset(mOffsetTable, 0, sizeof(unsigned int) * (iNumVertices + 1)); + mLiveTriangles = nullptr; // important, otherwise the d'tor would crash + } + + // get a pointer to the end of the buffer + unsigned int *piEnd = pi + iNumVertices; + *piEnd++ = 0u; + + // first pass: compute the number of faces referencing each vertex + for (aiFace *pcFace = pcFaces; pcFace != pcFaceEnd; ++pcFace) { + unsigned nind = pcFace->mNumIndices; + unsigned *ind = pcFace->mIndices; + if (nind > 0) pi[ind[0]]++; + if (nind > 1) pi[ind[1]]++; + if (nind > 2) pi[ind[2]]++; + } + + // second pass: compute the final offset table + unsigned int iSum = 0; + unsigned int *piCurOut = this->mOffsetTable; + for (unsigned int *piCur = pi; piCur != piEnd; ++piCur, ++piCurOut) { + + unsigned int iLastSum = iSum; + iSum += *piCur; + *piCurOut = iLastSum; + } + pi = this->mOffsetTable; + + // third pass: compute the final table + this->mAdjacencyTable = new unsigned int[iSum]; + iSum = 0; + for (aiFace *pcFace = pcFaces; pcFace != pcFaceEnd; ++pcFace, ++iSum) { + unsigned nind = pcFace->mNumIndices; + unsigned *ind = pcFace->mIndices; + + if (nind > 0) mAdjacencyTable[pi[ind[0]]++] = iSum; + if (nind > 1) mAdjacencyTable[pi[ind[1]]++] = iSum; + if (nind > 2) mAdjacencyTable[pi[ind[2]]++] = iSum; + } + // fourth pass: undo the offset computations made during the third pass + // We could do this in a separate buffer, but this would be TIMES slower. + --mOffsetTable; + *mOffsetTable = 0u; +} +// ------------------------------------------------------------------------------------------------ +VertexTriangleAdjacency::~VertexTriangleAdjacency() { + // delete allocated storage + delete[] mOffsetTable; + delete[] mAdjacencyTable; + delete[] mLiveTriangles; +} diff --git a/libs/assimp/code/Common/VertexTriangleAdjacency.h b/libs/assimp/code/Common/VertexTriangleAdjacency.h new file mode 100644 index 0000000..41d8094 --- /dev/null +++ b/libs/assimp/code/Common/VertexTriangleAdjacency.h @@ -0,0 +1,117 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a helper class to compute a vertex-triangle adjacency map */ +#ifndef AI_VTADJACENCY_H_INC +#define AI_VTADJACENCY_H_INC + +#include "BaseProcess.h" +#include <assimp/types.h> +#include <assimp/ai_assert.h> + +struct aiMesh; +struct aiFace; + +namespace Assimp { + +// -------------------------------------------------------------------------------------------- +/** @brief The VertexTriangleAdjacency class computes a vertex-triangle + * adjacency map from a given index buffer. + * + * @note Although it is called #VertexTriangleAdjacency, the current version does also + * support arbitrary polygons. */ +// -------------------------------------------------------------------------------------------- +class ASSIMP_API VertexTriangleAdjacency { +public: + // ---------------------------------------------------------------------------- + /** @brief Construction from an existing index buffer + * @param pcFaces Index buffer + * @param iNumFaces Number of faces in the buffer + * @param iNumVertices Number of referenced vertices. This value + * is computed automatically if 0 is specified. + * @param bComputeNumTriangles If you want the class to compute + * a list containing the number of referenced triangles per vertex + * per vertex - pass true. */ + VertexTriangleAdjacency(aiFace* pcFaces,unsigned int iNumFaces, + unsigned int iNumVertices = 0, + bool bComputeNumTriangles = true); + + // ---------------------------------------------------------------------------- + /** @brief Destructor */ + ~VertexTriangleAdjacency(); + + // ---------------------------------------------------------------------------- + /** @brief Get all triangles adjacent to a vertex + * @param iVertIndex Index of the vertex + * @return A pointer to the adjacency list. */ + unsigned int* GetAdjacentTriangles(unsigned int iVertIndex) const { + ai_assert(iVertIndex < mNumVertices); + return &mAdjacencyTable[ mOffsetTable[iVertIndex]]; + } + + // ---------------------------------------------------------------------------- + /** @brief Get the number of triangles that are referenced by + * a vertex. This function returns a reference that can be modified + * @param iVertIndex Index of the vertex + * @return Number of referenced triangles */ + unsigned int& GetNumTrianglesPtr(unsigned int iVertIndex) { + ai_assert( iVertIndex < mNumVertices ); + ai_assert( nullptr != mLiveTriangles ); + return mLiveTriangles[iVertIndex]; + } + + //! Offset table + unsigned int* mOffsetTable; + + //! Adjacency table + unsigned int* mAdjacencyTable; + + //! Table containing the number of referenced triangles per vertex + unsigned int* mLiveTriangles; + + //! Debug: Number of referenced vertices + unsigned int mNumVertices; +}; + +} //! ns Assimp + +#endif // !! AI_VTADJACENCY_H_INC diff --git a/libs/assimp/code/Common/Win32DebugLogStream.h b/libs/assimp/code/Common/Win32DebugLogStream.h new file mode 100644 index 0000000..34d849e --- /dev/null +++ b/libs/assimp/code/Common/Win32DebugLogStream.h @@ -0,0 +1,95 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Win32DebugLogStream.h +* @brief Implementation of Win32DebugLogStream +*/ +#ifndef AI_WIN32DEBUGLOGSTREAM_H_INC +#define AI_WIN32DEBUGLOGSTREAM_H_INC + +#ifdef _WIN32 + +#include <assimp/LogStream.hpp> +#include "windows.h" + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** @class Win32DebugLogStream + * @brief Logs into the debug stream from win32. + */ +class Win32DebugLogStream : public LogStream { +public: + /** @brief Default constructor */ + Win32DebugLogStream(); + + /** @brief Destructor */ + ~Win32DebugLogStream(); + + /** @brief Writer */ + void write(const char* messgae); +}; + +// --------------------------------------------------------------------------- +inline +Win32DebugLogStream::Win32DebugLogStream(){ + // empty +} + +// --------------------------------------------------------------------------- +inline +Win32DebugLogStream::~Win32DebugLogStream(){ + // empty +} + +// --------------------------------------------------------------------------- +inline +void Win32DebugLogStream::write(const char* message) { + ::OutputDebugStringA( message); +} + +// --------------------------------------------------------------------------- +} // Namespace Assimp + +#endif // ! _WIN32 +#endif // guard diff --git a/libs/assimp/code/Common/ZipArchiveIOSystem.cpp b/libs/assimp/code/Common/ZipArchiveIOSystem.cpp new file mode 100644 index 0000000..c322b14 --- /dev/null +++ b/libs/assimp/code/Common/ZipArchiveIOSystem.cpp @@ -0,0 +1,563 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 ZipArchiveIOSystem.cpp + * @brief Zip File I/O implementation for #Importer + */ + +#include <assimp/BaseImporter.h> +#include <assimp/ZipArchiveIOSystem.h> + +#include <assimp/ai_assert.h> + +#include <map> +#include <memory> + +#ifdef ASSIMP_USE_HUNTER +# include <minizip/unzip.h> +#else +# include <unzip.h> +#endif + +namespace Assimp { + +// ---------------------------------------------------------------- +// A read-only file inside a ZIP + +class ZipFile : public IOStream { + friend class ZipFileInfo; + explicit ZipFile(std::string &filename, size_t size); + +public: + std::string m_Filename; + virtual ~ZipFile(); + + // IOStream interface + size_t Read(void *pvBuffer, size_t pSize, size_t pCount) override; + size_t Write(const void * /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/) override { return 0; } + size_t FileSize() const override; + aiReturn Seek(size_t pOffset, aiOrigin pOrigin) override; + size_t Tell() const override; + void Flush() override {} + +private: + size_t m_Size = 0; + size_t m_SeekPtr = 0; + std::unique_ptr<uint8_t[]> m_Buffer; +}; + + +// ---------------------------------------------------------------- +// Wraps an existing Assimp::IOSystem for unzip +class IOSystem2Unzip { +public: + static voidpf open(voidpf opaque, const char *filename, int mode); + static voidpf opendisk(voidpf opaque, voidpf stream, uint32_t number_disk, int mode); + static uLong read(voidpf opaque, voidpf stream, void *buf, uLong size); + static uLong write(voidpf opaque, voidpf stream, const void *buf, uLong size); + static long tell(voidpf opaque, voidpf stream); + static long seek(voidpf opaque, voidpf stream, uLong offset, int origin); + static int close(voidpf opaque, voidpf stream); + static int testerror(voidpf opaque, voidpf stream); + static zlib_filefunc_def get(IOSystem *pIOHandler); +}; + +voidpf IOSystem2Unzip::open(voidpf opaque, const char *filename, int mode) { + IOSystem *io_system = reinterpret_cast<IOSystem *>(opaque); + + const char *mode_fopen = nullptr; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) { + mode_fopen = "rb"; + } else { + if (mode & ZLIB_FILEFUNC_MODE_EXISTING) { + mode_fopen = "r+b"; + } else { + if (mode & ZLIB_FILEFUNC_MODE_CREATE) { + mode_fopen = "wb"; + } + } + } + + return (voidpf)io_system->Open(filename, mode_fopen); +} + +voidpf IOSystem2Unzip::opendisk(voidpf opaque, voidpf stream, uint32_t number_disk, int mode) { + ZipFile *io_stream = (ZipFile *)stream; + voidpf ret = NULL; + size_t i; + + char *disk_filename = (char*)malloc(io_stream->m_Filename.length() + 1); + strncpy(disk_filename, io_stream->m_Filename.c_str(), io_stream->m_Filename.length() + 1); + for (i = io_stream->m_Filename.length() - 1; i >= 0; i -= 1) + { + if (disk_filename[i] != '.') + continue; + snprintf(&disk_filename[i], io_stream->m_Filename.length() - i, ".z%02u", number_disk + 1); + break; + } + + if (i >= 0) + ret = open(opaque, disk_filename, mode); + + free(disk_filename); + return ret; +} + +uLong IOSystem2Unzip::read(voidpf /*opaque*/, voidpf stream, void *buf, uLong size) { + IOStream *io_stream = (IOStream *)stream; + + return static_cast<uLong>(io_stream->Read(buf, 1, size)); +} + +uLong IOSystem2Unzip::write(voidpf /*opaque*/, voidpf stream, const void *buf, uLong size) { + IOStream *io_stream = (IOStream *)stream; + + return static_cast<uLong>(io_stream->Write(buf, 1, size)); +} + +long IOSystem2Unzip::tell(voidpf /*opaque*/, voidpf stream) { + IOStream *io_stream = (IOStream *)stream; + + return static_cast<long>(io_stream->Tell()); +} + +long IOSystem2Unzip::seek(voidpf /*opaque*/, voidpf stream, uLong offset, int origin) { + IOStream *io_stream = (IOStream *)stream; + + aiOrigin assimp_origin; + switch (origin) { + default: + case ZLIB_FILEFUNC_SEEK_CUR: + assimp_origin = aiOrigin_CUR; + break; + case ZLIB_FILEFUNC_SEEK_END: + assimp_origin = aiOrigin_END; + break; + case ZLIB_FILEFUNC_SEEK_SET: + assimp_origin = aiOrigin_SET; + break; + } + + return (io_stream->Seek(offset, assimp_origin) == aiReturn_SUCCESS ? 0 : -1); +} + +int IOSystem2Unzip::close(voidpf opaque, voidpf stream) { + IOSystem *io_system = (IOSystem *)opaque; + IOStream *io_stream = (IOStream *)stream; + + io_system->Close(io_stream); + + return 0; +} + +int IOSystem2Unzip::testerror(voidpf /*opaque*/, voidpf /*stream*/) { + return 0; +} + +zlib_filefunc_def IOSystem2Unzip::get(IOSystem *pIOHandler) { + zlib_filefunc_def mapping; + + mapping.zopen_file = (open_file_func)open; + mapping.zopendisk_file = (opendisk_file_func)opendisk; + mapping.zread_file = (read_file_func)read; + mapping.zwrite_file = (write_file_func)write; + mapping.ztell_file = (tell_file_func)tell; + mapping.zseek_file = (seek_file_func)seek; + mapping.zclose_file = (close_file_func)close; + mapping.zerror_file = testerror; + + mapping.opaque = reinterpret_cast<voidpf>(pIOHandler); + + return mapping; +} + +// ---------------------------------------------------------------- +// Info about a read-only file inside a ZIP +class ZipFileInfo { +public: + explicit ZipFileInfo(unzFile zip_handle, size_t size); + + // Allocate and Extract data from the ZIP + ZipFile *Extract(std::string &filename, unzFile zip_handle) const; + +private: + size_t m_Size = 0; + unz_file_pos_s m_ZipFilePos; +}; + +ZipFileInfo::ZipFileInfo(unzFile zip_handle, size_t size) : + m_Size(size) { + ai_assert(m_Size != 0); + // Workaround for MSVC 2013 - C2797 + m_ZipFilePos.num_of_file = 0; + m_ZipFilePos.pos_in_zip_directory = 0; + unzGetFilePos(zip_handle, &(m_ZipFilePos)); +} + +ZipFile *ZipFileInfo::Extract(std::string &filename, unzFile zip_handle) const { + // Find in the ZIP. This cannot fail + unz_file_pos_s *filepos = const_cast<unz_file_pos_s *>(&(m_ZipFilePos)); + if (unzGoToFilePos(zip_handle, filepos) != UNZ_OK) + return nullptr; + + if (unzOpenCurrentFile(zip_handle) != UNZ_OK) + return nullptr; + + ZipFile *zip_file = new ZipFile(filename, m_Size); + + // Unzip has a limit of UINT16_MAX bytes buffer + uint16_t unzipBufferSize = zip_file->m_Size <= UINT16_MAX ? static_cast<uint16_t>(zip_file->m_Size) : UINT16_MAX; + std::unique_ptr<uint8_t[]> unzipBuffer = std::unique_ptr<uint8_t[]>(new uint8_t[unzipBufferSize]); + size_t readCount = 0; + while (readCount < zip_file->m_Size) + { + size_t bufferSize = zip_file->m_Size - readCount; + if (bufferSize > UINT16_MAX) { + bufferSize = UINT16_MAX; + } + + int ret = unzReadCurrentFile(zip_handle, unzipBuffer.get(), static_cast<unsigned int>(bufferSize)); + if (ret != static_cast<int>(bufferSize)) + { + // Failed, release the memory + delete zip_file; + zip_file = nullptr; + break; + } + + std::memcpy(zip_file->m_Buffer.get() + readCount, unzipBuffer.get(), ret); + readCount += ret; + } + + ai_assert(unzCloseCurrentFile(zip_handle) == UNZ_OK); + return zip_file; +} + +ZipFile::ZipFile(std::string &filename, size_t size) : + m_Filename(filename), m_Size(size) { + ai_assert(m_Size != 0); + m_Buffer = std::unique_ptr<uint8_t[]>(new uint8_t[m_Size]); +} + +ZipFile::~ZipFile() { +} + +size_t ZipFile::Read(void *pvBuffer, size_t pSize, size_t pCount) { + // Should be impossible + ai_assert(m_Buffer != nullptr); + ai_assert(nullptr != pvBuffer); + ai_assert(0 != pSize); + ai_assert(0 != pCount); + + // Clip down to file size + size_t byteSize = pSize * pCount; + if ((byteSize + m_SeekPtr) > m_Size) { + pCount = (m_Size - m_SeekPtr) / pSize; + byteSize = pSize * pCount; + if (byteSize == 0) { + return 0; + } + } + + std::memcpy(pvBuffer, m_Buffer.get() + m_SeekPtr, byteSize); + + m_SeekPtr += byteSize; + + return pCount; +} + +size_t ZipFile::FileSize() const { + return m_Size; +} + +aiReturn ZipFile::Seek(size_t pOffset, aiOrigin pOrigin) { + switch (pOrigin) { + case aiOrigin_SET: { + if (pOffset > m_Size) return aiReturn_FAILURE; + m_SeekPtr = pOffset; + return aiReturn_SUCCESS; + } + + case aiOrigin_CUR: { + if ((pOffset + m_SeekPtr) > m_Size) return aiReturn_FAILURE; + m_SeekPtr += pOffset; + return aiReturn_SUCCESS; + } + + case aiOrigin_END: { + if (pOffset > m_Size) return aiReturn_FAILURE; + m_SeekPtr = m_Size - pOffset; + return aiReturn_SUCCESS; + } + default:; + } + + return aiReturn_FAILURE; +} + +size_t ZipFile::Tell() const { + return m_SeekPtr; +} + +// ---------------------------------------------------------------- +// pImpl of the Zip Archive IO +class ZipArchiveIOSystem::Implement { +public: + static const unsigned int FileNameSize = 256; + + Implement(IOSystem *pIOHandler, const char *pFilename, const char *pMode); + ~Implement(); + + bool isOpen() const; + void getFileList(std::vector<std::string> &rFileList); + void getFileListExtension(std::vector<std::string> &rFileList, const std::string &extension); + bool Exists(std::string &filename); + IOStream *OpenFile(std::string &filename); + + static void SimplifyFilename(std::string &filename); + +private: + void MapArchive(); + +private: + typedef std::map<std::string, ZipFileInfo> ZipFileInfoMap; + + unzFile m_ZipFileHandle = nullptr; + ZipFileInfoMap m_ArchiveMap; +}; + +ZipArchiveIOSystem::Implement::Implement(IOSystem *pIOHandler, const char *pFilename, const char *pMode) { + ai_assert(strcmp(pMode, "r") == 0); + ai_assert(pFilename != nullptr); + if (pFilename[0] == 0 || nullptr == pMode) { + return; + } + + zlib_filefunc_def mapping = IOSystem2Unzip::get(pIOHandler); + m_ZipFileHandle = unzOpen2(pFilename, &mapping); +} + +ZipArchiveIOSystem::Implement::~Implement() { + if (m_ZipFileHandle != nullptr) { + unzClose(m_ZipFileHandle); + } +} + +void ZipArchiveIOSystem::Implement::MapArchive() { + if (m_ZipFileHandle == nullptr) + return; + + if (!m_ArchiveMap.empty()) + return; + + // At first ensure file is already open + if (unzGoToFirstFile(m_ZipFileHandle) != UNZ_OK) + return; + + // Loop over all files + do { + char filename[FileNameSize]; + unz_file_info fileInfo; + + if (unzGetCurrentFileInfo(m_ZipFileHandle, &fileInfo, filename, FileNameSize, nullptr, 0, nullptr, 0) == UNZ_OK) { + if (fileInfo.uncompressed_size != 0 && fileInfo.size_filename <= FileNameSize) { + std::string filename_string(filename, fileInfo.size_filename); + SimplifyFilename(filename_string); + m_ArchiveMap.emplace(filename_string, ZipFileInfo(m_ZipFileHandle, fileInfo.uncompressed_size)); + } + } + } while (unzGoToNextFile(m_ZipFileHandle) != UNZ_END_OF_LIST_OF_FILE); +} + +bool ZipArchiveIOSystem::Implement::isOpen() const { + return (m_ZipFileHandle != nullptr); +} + +void ZipArchiveIOSystem::Implement::getFileList(std::vector<std::string> &rFileList) { + MapArchive(); + rFileList.clear(); + + for (const auto &file : m_ArchiveMap) { + rFileList.push_back(file.first); + } +} + +void ZipArchiveIOSystem::Implement::getFileListExtension(std::vector<std::string> &rFileList, const std::string &extension) { + MapArchive(); + rFileList.clear(); + + for (const auto &file : m_ArchiveMap) { + if (extension == BaseImporter::GetExtension(file.first)) + rFileList.push_back(file.first); + } +} + +bool ZipArchiveIOSystem::Implement::Exists(std::string &filename) { + MapArchive(); + + ZipFileInfoMap::const_iterator it = m_ArchiveMap.find(filename); + return (it != m_ArchiveMap.end()); +} + +IOStream *ZipArchiveIOSystem::Implement::OpenFile(std::string &filename) { + MapArchive(); + + SimplifyFilename(filename); + + // Find in the map + ZipFileInfoMap::const_iterator zip_it = m_ArchiveMap.find(filename); + if (zip_it == m_ArchiveMap.cend()) + return nullptr; + + const ZipFileInfo &zip_file = (*zip_it).second; + return zip_file.Extract(filename, m_ZipFileHandle); +} + +inline void ReplaceAll(std::string &data, const std::string &before, const std::string &after) { + size_t pos = data.find(before); + while (pos != std::string::npos) { + data.replace(pos, before.size(), after); + pos = data.find(before, pos + after.size()); + } +} + +inline void ReplaceAllChar(std::string &data, const char before, const char after) { + size_t pos = data.find(before); + while (pos != std::string::npos) { + data[pos] = after; + pos = data.find(before, pos + 1); + } +} + +void ZipArchiveIOSystem::Implement::SimplifyFilename(std::string &filename) { + ReplaceAllChar(filename, '\\', '/'); + + // Remove all . and / from the beginning of the path + size_t pos = filename.find_first_not_of("./"); + if (pos != 0) + filename.erase(0, pos); + + // Simplify "my/folder/../file.png" constructions, if any + static const std::string relative("/../"); + const size_t relsize = relative.size() - 1; + pos = filename.find(relative); + while (pos != std::string::npos) { + // Previous slash + size_t prevpos = filename.rfind('/', pos - 1); + if (prevpos == pos) + filename.erase(0, pos + relative.size()); + else + filename.erase(prevpos, pos + relsize - prevpos); + + pos = filename.find(relative); + } +} + +ZipArchiveIOSystem::ZipArchiveIOSystem(IOSystem *pIOHandler, const char *pFilename, const char *pMode) : + pImpl(new Implement(pIOHandler, pFilename, pMode)) { +} + +// ---------------------------------------------------------------- +// The ZipArchiveIO +ZipArchiveIOSystem::ZipArchiveIOSystem(IOSystem *pIOHandler, const std::string &rFilename, const char *pMode) : + pImpl(new Implement(pIOHandler, rFilename.c_str(), pMode)) { +} + +ZipArchiveIOSystem::~ZipArchiveIOSystem() { + delete pImpl; +} + +bool ZipArchiveIOSystem::Exists(const char *pFilename) const { + ai_assert(pFilename != nullptr); + + if (pFilename == nullptr) { + return false; + } + + std::string filename(pFilename); + return pImpl->Exists(filename); +} + +// This is always '/' in a ZIP +char ZipArchiveIOSystem::getOsSeparator() const { + return '/'; +} + +// Only supports Reading +IOStream *ZipArchiveIOSystem::Open(const char *pFilename, const char *pMode) { + ai_assert(pFilename != nullptr); + + for (size_t i = 0; pMode[i] != 0; ++i) { + ai_assert(pMode[i] != 'w'); + if (pMode[i] == 'w') + return nullptr; + } + + std::string filename(pFilename); + return pImpl->OpenFile(filename); +} + +void ZipArchiveIOSystem::Close(IOStream *pFile) { + delete pFile; +} + +bool ZipArchiveIOSystem::isOpen() const { + return (pImpl->isOpen()); +} + +void ZipArchiveIOSystem::getFileList(std::vector<std::string> &rFileList) const { + return pImpl->getFileList(rFileList); +} + +void ZipArchiveIOSystem::getFileListExtension(std::vector<std::string> &rFileList, const std::string &extension) const { + return pImpl->getFileListExtension(rFileList, extension); +} + +bool ZipArchiveIOSystem::isZipArchive(IOSystem *pIOHandler, const char *pFilename) { + Implement tmp(pIOHandler, pFilename, "r"); + return tmp.isOpen(); +} + +bool ZipArchiveIOSystem::isZipArchive(IOSystem *pIOHandler, const std::string &rFilename) { + return isZipArchive(pIOHandler, rFilename.c_str()); +} + +} // namespace Assimp diff --git a/libs/assimp/code/Common/assbin_chunks.h b/libs/assimp/code/Common/assbin_chunks.h new file mode 100644 index 0000000..822df51 --- /dev/null +++ b/libs/assimp/code/Common/assbin_chunks.h @@ -0,0 +1,196 @@ +#ifndef INCLUDED_ASSBIN_CHUNKS_H +#define INCLUDED_ASSBIN_CHUNKS_H + +#define ASSBIN_VERSION_MAJOR 1 +#define ASSBIN_VERSION_MINOR 0 + +/** +@page assfile .ASS File formats + +@section over Overview +Assimp provides its own interchange format, which is intended to applications which need +to serialize 3D-models and to reload them quickly. Assimp's file formats are designed to +be read by Assimp itself. They encode additional information needed by Assimp to optimize +its postprocessing pipeline. If you once apply specific steps to a scene, then save it +and reread it from an ASS format using the same post processing settings, they won't +be executed again. + +The format comes in two flavours: XML and binary - both of them hold a complete dump of +the 'aiScene' data structure returned by the APIs. The focus for the binary format +(<tt>.assbin</tt>) is fast loading. Optional deflate compression helps reduce file size. The XML +flavour, <tt>.assxml</tt> or simply .xml, is just a plain-to-xml conversion of aiScene. + +ASSBIN is Assimp's binary interchange format. assimp_cmd (<tt><root>/tools/assimp_cmd</tt>) is able to +write it and the core library provides a loader for it. + +@section assxml XML File format + +The format is pretty much self-explanatory due to its similarity to the in-memory aiScene structure. +With few exceptions, C structures are wrapped in XML elements. + +The DTD for ASSXML can be found in <tt><root>/doc/AssXML_Scheme.xml</tt>. Or have look +at the output files generated by assimp_cmd. + +@section assbin Binary file format + +The ASSBIN file format is composed of chunks to represent the hierarchical aiScene data structure. +This makes the format extensible and allows backward-compatibility with future data structure +versions. The <tt><root>/code/assbin_chunks.h</tt> header contains some magic constants +for use by stand-alone ASSBIN loaders. Also, Assimp's own file writer can be found +in <tt><root>/tools/assimp_cmd/WriteDump.cpp</tt> (yes, the 'b' is no typo ...). + +@verbatim + +------------------------------------------------------------------------------- +1. File structure: +------------------------------------------------------------------------------- + +---------------------- +| Header (512 bytes) | +---------------------- +| Variable chunks | +---------------------- + +------------------------------------------------------------------------------- +2. Definitions: +------------------------------------------------------------------------------- + +integer is four bytes wide, stored in little-endian byte order. +short is two bytes wide, stored in little-endian byte order. +byte is a single byte. +string is an integer n followed by n UTF-8 characters, not terminated by zero +float is an IEEE 754 single-precision floating-point value +double is an IEEE 754 double-precision floating-point value +t[n] is an array of n elements of type t + +------------------------------------------------------------------------------- +2. Header: +------------------------------------------------------------------------------- + +byte[44] Magic identification string for ASSBIN files. + 'ASSIMP.binary' + +integer Major version of the Assimp library which wrote the file +integer Minor version of the Assimp library which wrote the file + match these against ASSBIN_VERSION_MAJOR and ASSBIN_VERSION_MINOR + +integer SVN revision of the Assimp library (intended for our internal + debugging - if you write Ass files from your own APPs, set this value to 0. +integer Assimp compile flags + +short 0 for normal files, 1 for shortened dumps for regression tests + these should have the file extension assbin.regress + +short 1 if the data after the header is compressed with the DEFLATE algorithm, + 0 for uncompressed files. + For compressed files, the first integer after the header is + always the uncompressed data size + +byte[256] Zero-terminated source file name, UTF-8 +byte[128] Zero-terminated command line parameters passed to assimp_cmd, UTF-8 + +byte[64] Reserved for future use +---> Total length: 512 bytes + +------------------------------------------------------------------------------- +3. Chunks: +------------------------------------------------------------------------------- + +integer Magic chunk ID (ASSBIN_CHUNK_XXX) +integer Chunk data length, in bytes + (unknown chunks are possible, a good reader skips over them) + (chunk-data-length does not include the first two integers) + +byte[n] chunk-data-length bytes of data, depending on the chunk type + +Chunks can contain nested chunks. Nested chunks are ALWAYS at the end of the chunk, +their size is included in chunk-data-length. + +The chunk layout for all ASSIMP data structures is derived from their C declarations. +The general 'rule' to get from Assimp headers to the serialized layout is: + + 1. POD members (i.e. aiMesh::mPrimitiveTypes, aiMesh::mNumVertices), + in order of declaration. + + 2. Array-members (aiMesh::mFaces, aiMesh::mVertices, aiBone::mWeights), + in order of declaration. + + 2. Object array members (i.e aiMesh::mBones, aiScene::mMeshes) are stored in + subchunks directly following the data written in 1.) and 2.) + + + Of course, there are some exceptions to this general order: + +[[aiScene]] + + - The root node holding the scene structure is naturally stored in + a ASSBIN_CHUNK_AINODE subchunk following 1.) and 2.) (which is + empty for aiScene). + +[[aiMesh]] + + - mTextureCoords and mNumUVComponents are serialized as follows: + + [number of used uv channels times] + integer mNumUVComponents[n] + float mTextureCoords[n][3] + + -> more than AI_MAX_TEXCOORD_CHANNELS can be stored. This allows Assimp + builds with different settings for AI_MAX_TEXCOORD_CHANNELS to exchange + data. + -> the on-disk format always uses 3 floats to write UV coordinates. + If mNumUVComponents[0] is 1, the corresponding mTextureCoords array + consists of 3 floats. + + - The array member block of aiMesh is prefixed with an integer that specifies + the kinds of vertex components actually present in the mesh. This is a + bitwise combination of the ASSBIN_MESH_HAS_xxx constants. + +[[aiFace]] + + - mNumIndices is stored as short + - mIndices are written as short, if aiMesh::mNumVertices<65536 + +[[aiNode]] + + - mParent is omitted + +[[aiLight]] + + - mAttenuationXXX not written if aiLight::mType == aiLightSource_DIRECTIONAL + - mAngleXXX not written if aiLight::mType != aiLightSource_SPOT + +[[aiMaterial]] + + - mNumAllocated is omitted, for obvious reasons :-) + + + @endverbatim*/ + + +#define ASSBIN_HEADER_LENGTH 512 + +// these are the magic chunk identifiers for the binary ASS file format +#define ASSBIN_CHUNK_AICAMERA 0x1234 +#define ASSBIN_CHUNK_AILIGHT 0x1235 +#define ASSBIN_CHUNK_AITEXTURE 0x1236 +#define ASSBIN_CHUNK_AIMESH 0x1237 +#define ASSBIN_CHUNK_AINODEANIM 0x1238 +#define ASSBIN_CHUNK_AISCENE 0x1239 +#define ASSBIN_CHUNK_AIBONE 0x123a +#define ASSBIN_CHUNK_AIANIMATION 0x123b +#define ASSBIN_CHUNK_AINODE 0x123c +#define ASSBIN_CHUNK_AIMATERIAL 0x123d +#define ASSBIN_CHUNK_AIMATERIALPROPERTY 0x123e + +#define ASSBIN_MESH_HAS_POSITIONS 0x1 +#define ASSBIN_MESH_HAS_NORMALS 0x2 +#define ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS 0x4 +#define ASSBIN_MESH_HAS_TEXCOORD_BASE 0x100 +#define ASSBIN_MESH_HAS_COLOR_BASE 0x10000 + +#define ASSBIN_MESH_HAS_TEXCOORD(n) (ASSBIN_MESH_HAS_TEXCOORD_BASE << n) +#define ASSBIN_MESH_HAS_COLOR(n) (ASSBIN_MESH_HAS_COLOR_BASE << n) + + +#endif // INCLUDED_ASSBIN_CHUNKS_H diff --git a/libs/assimp/code/Common/material.cpp b/libs/assimp/code/Common/material.cpp new file mode 100644 index 0000000..a7541d4 --- /dev/null +++ b/libs/assimp/code/Common/material.cpp @@ -0,0 +1,100 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 material.cpp +/** Implement common material related functions. */ + +#include <assimp/ai_assert.h> +#include <assimp/material.h> + +// ------------------------------------------------------------------------------- +const char *TextureTypeToString(aiTextureType in) { + switch (in) { + case aiTextureType_NONE: + return "n/a"; + case aiTextureType_DIFFUSE: + return "Diffuse"; + case aiTextureType_SPECULAR: + return "Specular"; + case aiTextureType_AMBIENT: + return "Ambient"; + case aiTextureType_EMISSIVE: + return "Emissive"; + case aiTextureType_OPACITY: + return "Opacity"; + case aiTextureType_NORMALS: + return "Normals"; + case aiTextureType_HEIGHT: + return "Height"; + case aiTextureType_SHININESS: + return "Shininess"; + case aiTextureType_DISPLACEMENT: + return "Displacement"; + case aiTextureType_LIGHTMAP: + return "Lightmap"; + case aiTextureType_REFLECTION: + return "Reflection"; + case aiTextureType_BASE_COLOR: + return "BaseColor"; + case aiTextureType_NORMAL_CAMERA: + return "NormalCamera"; + case aiTextureType_EMISSION_COLOR: + return "EmissionColor"; + case aiTextureType_METALNESS: + return "Metalness"; + case aiTextureType_DIFFUSE_ROUGHNESS: + return "DiffuseRoughness"; + case aiTextureType_AMBIENT_OCCLUSION: + return "AmbientOcclusion"; + case aiTextureType_SHEEN: + return "Sheen"; + case aiTextureType_CLEARCOAT: + return "Clearcoat"; + case aiTextureType_TRANSMISSION: + return "Transmission"; + case aiTextureType_UNKNOWN: + return "Unknown"; + default: + break; + } + ai_assert(false); + return "BUG"; +} diff --git a/libs/assimp/code/Common/scene.cpp b/libs/assimp/code/Common/scene.cpp new file mode 100644 index 0000000..b0e8821 --- /dev/null +++ b/libs/assimp/code/Common/scene.cpp @@ -0,0 +1,135 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. +--------------------------------------------------------------------------- +*/ +#include <assimp/scene.h> + +aiNode::aiNode() : + mName(""), + mParent(nullptr), + mNumChildren(0), + mChildren(nullptr), + mNumMeshes(0), + mMeshes(nullptr), + mMetaData(nullptr) { + // empty +} + +aiNode::aiNode(const std::string &name) : + mName(name), + mParent(nullptr), + mNumChildren(0), + mChildren(nullptr), + mNumMeshes(0), + mMeshes(nullptr), + mMetaData(nullptr) { + // empty +} + +/** Destructor */ +aiNode::~aiNode() { + // delete all children recursively + // to make sure we won't crash if the data is invalid ... + if (mNumChildren && mChildren) { + for (unsigned int a = 0; a < mNumChildren; a++) + delete mChildren[a]; + } + delete[] mChildren; + delete[] mMeshes; + delete mMetaData; +} + +const aiNode *aiNode::FindNode(const char *name) const { + if (nullptr == name) { + return nullptr; + } + if (!::strcmp(mName.data, name)) { + return this; + } + for (unsigned int i = 0; i < mNumChildren; ++i) { + const aiNode *const p = mChildren[i]->FindNode(name); + if (p) { + return p; + } + } + // there is definitely no sub-node with this name + return nullptr; +} + +aiNode *aiNode::FindNode(const char *name) { + if (!::strcmp(mName.data, name)) return this; + for (unsigned int i = 0; i < mNumChildren; ++i) { + aiNode *const p = mChildren[i]->FindNode(name); + if (p) { + return p; + } + } + // there is definitely no sub-node with this name + return nullptr; +} + +void aiNode::addChildren(unsigned int numChildren, aiNode **children) { + if (nullptr == children || 0 == numChildren) { + return; + } + + for (unsigned int i = 0; i < numChildren; i++) { + aiNode *child = children[i]; + if (nullptr != child) { + child->mParent = this; + } + } + + if (mNumChildren > 0) { + aiNode **tmp = new aiNode *[mNumChildren]; + ::memcpy(tmp, mChildren, sizeof(aiNode *) * mNumChildren); + delete[] mChildren; + mChildren = new aiNode *[mNumChildren + numChildren]; + ::memcpy(mChildren, tmp, sizeof(aiNode *) * mNumChildren); + ::memcpy(&mChildren[mNumChildren], children, sizeof(aiNode *) * numChildren); + mNumChildren += numChildren; + delete[] tmp; + } else { + mChildren = new aiNode *[numChildren]; + for (unsigned int i = 0; i < numChildren; i++) { + mChildren[i] = children[i]; + } + mNumChildren = numChildren; + } +} diff --git a/libs/assimp/code/Common/simd.cpp b/libs/assimp/code/Common/simd.cpp new file mode 100644 index 0000000..0dd437d --- /dev/null +++ b/libs/assimp/code/Common/simd.cpp @@ -0,0 +1,77 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. +--------------------------------------------------------------------------- +*/ +#include "simd.h" + +namespace Assimp { + +bool CPUSupportsSSE2() { +#if defined(__x86_64__) || defined(_M_X64) + //* x86_64 always has SSE2 instructions */ + return true; +#elif defined(__GNUC__) && defined(i386) + // for GCC x86 we check cpuid + unsigned int d; + __asm__( + "pushl %%ebx\n\t" + "cpuid\n\t" + "popl %%ebx\n\t" + : "=d" ( d ) + :"a" ( 1 ) ); + return ( d & 0x04000000 ) != 0; +#elif (defined(_MSC_VER) && defined(_M_IX86)) + // also check cpuid for MSVC x86 + unsigned int d; + __asm { + xor eax, eax + inc eax + push ebx + cpuid + pop ebx + mov d, edx + } + return ( d & 0x04000000 ) != 0; +#else + return false; +#endif +} + + +} // Namespace Assimp diff --git a/libs/assimp/code/Common/simd.h b/libs/assimp/code/Common/simd.h new file mode 100644 index 0000000..0a062a2 --- /dev/null +++ b/libs/assimp/code/Common/simd.h @@ -0,0 +1,53 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. +--------------------------------------------------------------------------- +*/ +#pragma once + +#include <assimp/defs.h> + +namespace Assimp { + +/// @brief Checks if the platform supports SSE2 optimization +/// @return true, if SSE2 is supported. false if SSE2 is not supported. +bool ASSIMP_API CPUSupportsSSE2(); + +} // Namespace Assimp diff --git a/libs/assimp/code/Material/MaterialSystem.cpp b/libs/assimp/code/Material/MaterialSystem.cpp new file mode 100644 index 0000000..b2f7389 --- /dev/null +++ b/libs/assimp/code/Material/MaterialSystem.cpp @@ -0,0 +1,614 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MaterialSystem.cpp + * @brief Implementation of the material system of the library + */ + +#include "MaterialSystem.h" +#include <assimp/Hash.h> +#include <assimp/ParsingUtils.h> +#include <assimp/fast_atof.h> +#include <assimp/material.h> +#include <assimp/types.h> +#include <assimp/DefaultLogger.hpp> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Get a specific property from a material +aiReturn aiGetMaterialProperty(const aiMaterial *pMat, + const char *pKey, + unsigned int type, + unsigned int index, + const aiMaterialProperty **pPropOut) { + ai_assert(pMat != nullptr); + ai_assert(pKey != nullptr); + ai_assert(pPropOut != nullptr); + + /* Just search for a property with exactly this name .. + * could be improved by hashing, but it's possibly + * no worth the effort (we're bound to C structures, + * thus std::map or derivates are not applicable. */ + for (unsigned int i = 0; i < pMat->mNumProperties; ++i) { + aiMaterialProperty *prop = pMat->mProperties[i]; + + if (prop /* just for safety ... */ + && 0 == strcmp(prop->mKey.data, pKey) && (UINT_MAX == type || prop->mSemantic == type) /* UINT_MAX is a wild-card, but this is undocumented :-) */ + && (UINT_MAX == index || prop->mIndex == index)) { + *pPropOut = pMat->mProperties[i]; + return AI_SUCCESS; + } + } + *pPropOut = nullptr; + return AI_FAILURE; +} + +// ------------------------------------------------------------------------------------------------ +// Get an array of floating-point values from the material. +aiReturn aiGetMaterialFloatArray(const aiMaterial *pMat, + const char *pKey, + unsigned int type, + unsigned int index, + ai_real *pOut, + unsigned int *pMax) { + ai_assert(pOut != nullptr); + ai_assert(pMat != nullptr); + + const aiMaterialProperty *prop; + aiGetMaterialProperty(pMat, pKey, type, index, (const aiMaterialProperty **)&prop); + if (nullptr == prop) { + return AI_FAILURE; + } + + // data is given in floats, convert to ai_real + unsigned int iWrite = 0; + if (aiPTI_Float == prop->mType || aiPTI_Buffer == prop->mType) { + iWrite = prop->mDataLength / sizeof(float); + if (pMax) { + iWrite = std::min(*pMax, iWrite); + ; + } + + for (unsigned int a = 0; a < iWrite; ++a) { + pOut[a] = static_cast<ai_real>(reinterpret_cast<float *>(prop->mData)[a]); + } + + if (pMax) { + *pMax = iWrite; + } + } + // data is given in doubles, convert to float + else if (aiPTI_Double == prop->mType) { + iWrite = prop->mDataLength / sizeof(double); + if (pMax) { + iWrite = std::min(*pMax, iWrite); + ; + } + for (unsigned int a = 0; a < iWrite; ++a) { + pOut[a] = static_cast<ai_real>(reinterpret_cast<double *>(prop->mData)[a]); + } + if (pMax) { + *pMax = iWrite; + } + } + // data is given in ints, convert to float + else if (aiPTI_Integer == prop->mType) { + iWrite = prop->mDataLength / sizeof(int32_t); + if (pMax) { + iWrite = std::min(*pMax, iWrite); + ; + } + for (unsigned int a = 0; a < iWrite; ++a) { + pOut[a] = static_cast<ai_real>(reinterpret_cast<int32_t *>(prop->mData)[a]); + } + if (pMax) { + *pMax = iWrite; + } + } + // a string ... read floats separated by spaces + else { + if (pMax) { + iWrite = *pMax; + } + // strings are zero-terminated with a 32 bit length prefix, so this is safe + const char *cur = prop->mData + 4; + ai_assert(prop->mDataLength >= 5); + ai_assert(!prop->mData[prop->mDataLength - 1]); + for (unsigned int a = 0;; ++a) { + cur = fast_atoreal_move<ai_real>(cur, pOut[a]); + if (a == iWrite - 1) { + break; + } + if (!IsSpace(*cur)) { + ASSIMP_LOG_ERROR("Material property", pKey, + " is a string; failed to parse a float array out of it."); + return AI_FAILURE; + } + } + + if (pMax) { + *pMax = iWrite; + } + } + return AI_SUCCESS; +} + +// ------------------------------------------------------------------------------------------------ +// Get an array if integers from the material +aiReturn aiGetMaterialIntegerArray(const aiMaterial *pMat, + const char *pKey, + unsigned int type, + unsigned int index, + int *pOut, + unsigned int *pMax) { + ai_assert(pOut != nullptr); + ai_assert(pMat != nullptr); + + const aiMaterialProperty *prop; + aiGetMaterialProperty(pMat, pKey, type, index, (const aiMaterialProperty **)&prop); + if (!prop) { + return AI_FAILURE; + } + + // data is given in ints, simply copy it + unsigned int iWrite = 0; + if (aiPTI_Integer == prop->mType || aiPTI_Buffer == prop->mType) { + iWrite = std::max(static_cast<unsigned int>(prop->mDataLength / sizeof(int32_t)), 1u); + if (pMax) { + iWrite = std::min(*pMax, iWrite); + } + if (1 == prop->mDataLength) { + // bool type, 1 byte + *pOut = static_cast<int>(*prop->mData); + } else { + for (unsigned int a = 0; a < iWrite; ++a) { + pOut[a] = static_cast<int>(reinterpret_cast<int32_t *>(prop->mData)[a]); + } + } + if (pMax) { + *pMax = iWrite; + } + } + // data is given in floats convert to int + else if (aiPTI_Float == prop->mType) { + iWrite = prop->mDataLength / sizeof(float); + if (pMax) { + iWrite = std::min(*pMax, iWrite); + ; + } + for (unsigned int a = 0; a < iWrite; ++a) { + pOut[a] = static_cast<int>(reinterpret_cast<float *>(prop->mData)[a]); + } + if (pMax) { + *pMax = iWrite; + } + } + // it is a string ... no way to read something out of this + else { + if (pMax) { + iWrite = *pMax; + } + // strings are zero-terminated with a 32 bit length prefix, so this is safe + const char *cur = prop->mData + 4; + ai_assert(prop->mDataLength >= 5); + ai_assert(!prop->mData[prop->mDataLength - 1]); + for (unsigned int a = 0;; ++a) { + pOut[a] = strtol10(cur, &cur); + if (a == iWrite - 1) { + break; + } + if (!IsSpace(*cur)) { + ASSIMP_LOG_ERROR("Material property", pKey, + " is a string; failed to parse an integer array out of it."); + return AI_FAILURE; + } + } + + if (pMax) { + *pMax = iWrite; + } + } + return AI_SUCCESS; +} + +// ------------------------------------------------------------------------------------------------ +// Get a color (3 or 4 floats) from the material +aiReturn aiGetMaterialColor(const aiMaterial *pMat, + const char *pKey, + unsigned int type, + unsigned int index, + aiColor4D *pOut) { + unsigned int iMax = 4; + const aiReturn eRet = aiGetMaterialFloatArray(pMat, pKey, type, index, (ai_real *)pOut, &iMax); + + // if no alpha channel is defined: set it to 1.0 + if (3 == iMax) { + pOut->a = 1.0; + } + + return eRet; +} + +// ------------------------------------------------------------------------------------------------ +// Get a aiUVTransform (5 floats) from the material +aiReturn aiGetMaterialUVTransform(const aiMaterial *pMat, + const char *pKey, + unsigned int type, + unsigned int index, + aiUVTransform *pOut) { + unsigned int iMax = 5; + return aiGetMaterialFloatArray(pMat, pKey, type, index, (ai_real *)pOut, &iMax); +} + +// ------------------------------------------------------------------------------------------------ +// Get a string from the material +aiReturn aiGetMaterialString(const aiMaterial *pMat, + const char *pKey, + unsigned int type, + unsigned int index, + aiString *pOut) { + ai_assert(pOut != nullptr); + + const aiMaterialProperty *prop; + aiGetMaterialProperty(pMat, pKey, type, index, (const aiMaterialProperty **)&prop); + if (!prop) { + return AI_FAILURE; + } + + if (aiPTI_String == prop->mType) { + ai_assert(prop->mDataLength >= 5); + + // The string is stored as 32 but length prefix followed by zero-terminated UTF8 data + pOut->length = static_cast<unsigned int>(*reinterpret_cast<uint32_t *>(prop->mData)); + + ai_assert(pOut->length + 1 + 4 == prop->mDataLength); + ai_assert(!prop->mData[prop->mDataLength - 1]); + memcpy(pOut->data, prop->mData + 4, pOut->length + 1); + } else { + // TODO - implement lexical cast as well + ASSIMP_LOG_ERROR("Material property", pKey, " was found, but is no string"); + return AI_FAILURE; + } + return AI_SUCCESS; +} + +// ------------------------------------------------------------------------------------------------ +// Get the number of textures on a particular texture stack +unsigned int aiGetMaterialTextureCount(const C_STRUCT aiMaterial *pMat, C_ENUM aiTextureType type) { + ai_assert(pMat != nullptr); + + // Textures are always stored with ascending indices (ValidateDS provides a check, so we don't need to do it again) + unsigned int max = 0; + for (unsigned int i = 0; i < pMat->mNumProperties; ++i) { + aiMaterialProperty *prop = pMat->mProperties[i]; + + if (prop /* just a sanity check ... */ + && 0 == strcmp(prop->mKey.data, _AI_MATKEY_TEXTURE_BASE) && static_cast<aiTextureType>(prop->mSemantic) == type) { + + max = std::max(max, prop->mIndex + 1); + } + } + return max; +} + +// ------------------------------------------------------------------------------------------------ +aiReturn aiGetMaterialTexture(const C_STRUCT aiMaterial *mat, + aiTextureType type, + unsigned int index, + C_STRUCT aiString *path, + aiTextureMapping *_mapping /*= nullptr*/, + unsigned int *uvindex /*= nullptr*/, + ai_real *blend /*= nullptr*/, + aiTextureOp *op /*= nullptr*/, + aiTextureMapMode *mapmode /*= nullptr*/, + unsigned int *flags /*= nullptr*/ +) { + ai_assert(nullptr != mat); + ai_assert(nullptr != path); + + // Get the path to the texture + if (AI_SUCCESS != aiGetMaterialString(mat, AI_MATKEY_TEXTURE(type, index), path)) { + return AI_FAILURE; + } + + // Determine mapping type + int mapping_ = static_cast<int>(aiTextureMapping_UV); + aiGetMaterialInteger(mat, AI_MATKEY_MAPPING(type, index), &mapping_); + aiTextureMapping mapping = static_cast<aiTextureMapping>(mapping_); + if (_mapping) + *_mapping = mapping; + + // Get UV index + if (aiTextureMapping_UV == mapping && uvindex) { + aiGetMaterialInteger(mat, AI_MATKEY_UVWSRC(type, index), (int *)uvindex); + } + // Get blend factor + if (blend) { + aiGetMaterialFloat(mat, AI_MATKEY_TEXBLEND(type, index), blend); + } + // Get texture operation + if (op) { + aiGetMaterialInteger(mat, AI_MATKEY_TEXOP(type, index), (int *)op); + } + // Get texture mapping modes + if (mapmode) { + aiGetMaterialInteger(mat, AI_MATKEY_MAPPINGMODE_U(type, index), (int *)&mapmode[0]); + aiGetMaterialInteger(mat, AI_MATKEY_MAPPINGMODE_V(type, index), (int *)&mapmode[1]); + } + // Get texture flags + if (flags) { + aiGetMaterialInteger(mat, AI_MATKEY_TEXFLAGS(type, index), (int *)flags); + } + + return AI_SUCCESS; +} + +static const unsigned int DefaultNumAllocated = 5; + +// ------------------------------------------------------------------------------------------------ +// Construction. Actually the one and only way to get an aiMaterial instance +aiMaterial::aiMaterial() : + mProperties(nullptr), mNumProperties(0), mNumAllocated(DefaultNumAllocated) { + // Allocate 5 entries by default + mProperties = new aiMaterialProperty *[DefaultNumAllocated]; +} + +// ------------------------------------------------------------------------------------------------ +aiMaterial::~aiMaterial() { + Clear(); + + delete[] mProperties; +} + +// ------------------------------------------------------------------------------------------------ +aiString aiMaterial::GetName() const { + aiString name; + Get(AI_MATKEY_NAME, name); + + return name; +} + +// ------------------------------------------------------------------------------------------------ +void aiMaterial::Clear() { + for (unsigned int i = 0; i < mNumProperties; ++i) { + // delete this entry + delete mProperties[i]; + AI_DEBUG_INVALIDATE_PTR(mProperties[i]); + } + mNumProperties = 0; + + // The array remains allocated, we just invalidated its contents +} + +// ------------------------------------------------------------------------------------------------ +aiReturn aiMaterial::RemoveProperty(const char *pKey, unsigned int type, unsigned int index) { + ai_assert(nullptr != pKey); + + for (unsigned int i = 0; i < mNumProperties; ++i) { + aiMaterialProperty *prop = mProperties[i]; + + if (prop && !strcmp(prop->mKey.data, pKey) && + prop->mSemantic == type && prop->mIndex == index) { + // Delete this entry + delete mProperties[i]; + + // collapse the array behind --. + --mNumProperties; + for (unsigned int a = i; a < mNumProperties; ++a) { + mProperties[a] = mProperties[a + 1]; + } + return AI_SUCCESS; + } + } + + return AI_FAILURE; +} + +// ------------------------------------------------------------------------------------------------ +aiReturn aiMaterial::AddBinaryProperty(const void *pInput, + unsigned int pSizeInBytes, + const char *pKey, + unsigned int type, + unsigned int index, + aiPropertyTypeInfo pType) { + ai_assert(pInput != nullptr); + ai_assert(pKey != nullptr); + ai_assert(0 != pSizeInBytes); + + if (0 == pSizeInBytes) { + return AI_FAILURE; + } + + // first search the list whether there is already an entry with this key + unsigned int iOutIndex(UINT_MAX); + for (unsigned int i = 0; i < mNumProperties; ++i) { + aiMaterialProperty *prop(mProperties[i]); + + if (prop /* just for safety */ && !strcmp(prop->mKey.data, pKey) && + prop->mSemantic == type && prop->mIndex == index) { + + delete mProperties[i]; + iOutIndex = i; + } + } + + // Allocate a new material property + aiMaterialProperty *pcNew = new aiMaterialProperty(); + + // .. and fill it + pcNew->mType = pType; + pcNew->mSemantic = type; + pcNew->mIndex = index; + + pcNew->mDataLength = pSizeInBytes; + pcNew->mData = new char[pSizeInBytes]; + memcpy(pcNew->mData, pInput, pSizeInBytes); + + pcNew->mKey.length = static_cast<ai_uint32>(::strlen(pKey)); + ai_assert(MAXLEN > pcNew->mKey.length); + strcpy(pcNew->mKey.data, pKey); + + if (UINT_MAX != iOutIndex) { + mProperties[iOutIndex] = pcNew; + return AI_SUCCESS; + } + + // resize the array ... double the storage allocated + if (mNumProperties == mNumAllocated) { + const unsigned int iOld = mNumAllocated; + mNumAllocated *= 2; + + aiMaterialProperty **ppTemp; + try { + ppTemp = new aiMaterialProperty *[mNumAllocated]; + } catch (std::bad_alloc &) { + delete pcNew; + return AI_OUTOFMEMORY; + } + + // just copy all items over; then replace the old array + memcpy(ppTemp, mProperties, iOld * sizeof(void *)); + + delete[] mProperties; + mProperties = ppTemp; + } + // push back ... + mProperties[mNumProperties++] = pcNew; + + return AI_SUCCESS; +} + +// ------------------------------------------------------------------------------------------------ +aiReturn aiMaterial::AddProperty(const aiString *pInput, + const char *pKey, + unsigned int type, + unsigned int index) { + ai_assert(sizeof(ai_uint32) == 4); + return AddBinaryProperty(pInput, + static_cast<unsigned int>(pInput->length + 1 + 4), + pKey, + type, + index, + aiPTI_String); +} + +// ------------------------------------------------------------------------------------------------ +uint32_t Assimp::ComputeMaterialHash(const aiMaterial *mat, bool includeMatName /*= false*/) { + uint32_t hash = 1503; // magic start value, chosen to be my birthday :-) + for (unsigned int i = 0; i < mat->mNumProperties; ++i) { + aiMaterialProperty *prop; + + // Exclude all properties whose first character is '?' from the hash + // See doc for aiMaterialProperty. + prop = mat->mProperties[i]; + if (nullptr != prop && (includeMatName || prop->mKey.data[0] != '?')) { + + hash = SuperFastHash(prop->mKey.data, (unsigned int)prop->mKey.length, hash); + hash = SuperFastHash(prop->mData, prop->mDataLength, hash); + + // Combine the semantic and the index with the hash + hash = SuperFastHash((const char *)&prop->mSemantic, sizeof(unsigned int), hash); + hash = SuperFastHash((const char *)&prop->mIndex, sizeof(unsigned int), hash); + } + } + return hash; +} + +// ------------------------------------------------------------------------------------------------ +void aiMaterial::CopyPropertyList(aiMaterial *const pcDest, + const aiMaterial *pcSrc) { + ai_assert(nullptr != pcDest); + ai_assert(nullptr != pcSrc); + ai_assert(pcDest->mNumProperties <= pcDest->mNumAllocated); + ai_assert(pcSrc->mNumProperties <= pcSrc->mNumAllocated); + + const unsigned int iOldNum = pcDest->mNumProperties; + pcDest->mNumAllocated += pcSrc->mNumAllocated; + pcDest->mNumProperties += pcSrc->mNumProperties; + + const unsigned int numAllocated = pcDest->mNumAllocated; + aiMaterialProperty **pcOld = pcDest->mProperties; + pcDest->mProperties = new aiMaterialProperty *[numAllocated]; + + ai_assert(!iOldNum || pcOld); + ai_assert(iOldNum < numAllocated); + + if (iOldNum && pcOld) { + for (unsigned int i = 0; i < iOldNum; ++i) { + pcDest->mProperties[i] = pcOld[i]; + } + } + + if (pcOld) { + delete[] pcOld; + } + + for (unsigned int i = iOldNum; i < pcDest->mNumProperties; ++i) { + aiMaterialProperty *propSrc = pcSrc->mProperties[i]; + + // search whether we have already a property with this name -> if yes, overwrite it + aiMaterialProperty *prop; + for (unsigned int q = 0; q < iOldNum; ++q) { + prop = pcDest->mProperties[q]; + if (prop /* just for safety */ && prop->mKey == propSrc->mKey && prop->mSemantic == propSrc->mSemantic && prop->mIndex == propSrc->mIndex) { + delete prop; + + // collapse the whole array ... + memmove(&pcDest->mProperties[q], &pcDest->mProperties[q + 1], i - q); + i--; + pcDest->mNumProperties--; + } + } + + // Allocate the output property and copy the source property + prop = pcDest->mProperties[i] = new aiMaterialProperty(); + prop->mKey = propSrc->mKey; + prop->mDataLength = propSrc->mDataLength; + prop->mType = propSrc->mType; + prop->mSemantic = propSrc->mSemantic; + prop->mIndex = propSrc->mIndex; + + prop->mData = new char[propSrc->mDataLength]; + memcpy(prop->mData, propSrc->mData, prop->mDataLength); + } +} diff --git a/libs/assimp/code/Material/MaterialSystem.h b/libs/assimp/code/Material/MaterialSystem.h new file mode 100644 index 0000000..41891ad --- /dev/null +++ b/libs/assimp/code/Material/MaterialSystem.h @@ -0,0 +1,72 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MaterialSystem.h + * Now that #MaterialHelper is gone, this file only contains some + * internal material utility functions. + */ +#ifndef AI_MATERIALSYSTEM_H_INC +#define AI_MATERIALSYSTEM_H_INC + +#include <stdint.h> + +struct aiMaterial; + +namespace Assimp { + +// ------------------------------------------------------------------------------ +/** Computes a hash (hopefully unique) from all material properties + * The hash value reflects the current property state, so if you add any + * property and call this method again, the resulting hash value will be + * different. The hash is not persistent across different builds and platforms. + * + * @param includeMatName Set to 'true' to take all properties with + * '?' as initial character in their name into account. + * Currently #AI_MATKEY_NAME is the only example. + * @return 32 Bit jash value for the material + */ +uint32_t ComputeMaterialHash(const aiMaterial* mat, bool includeMatName = false); + + +} // ! namespace Assimp + +#endif //!! AI_MATERIALSYSTEM_H_INC diff --git a/libs/assimp/code/Pbrt/PbrtExporter.cpp b/libs/assimp/code/Pbrt/PbrtExporter.cpp new file mode 100644 index 0000000..25061f5 --- /dev/null +++ b/libs/assimp/code/Pbrt/PbrtExporter.cpp @@ -0,0 +1,947 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +/* TODO: + +Material improvements: +- don't export embedded textures that we're not going to use +- diffuse roughness +- what is with the uv mapping, uv transform not coming through?? +- metal? glass? mirror? detect these better? + - eta/k from RGB? +- emissive textures: warn at least + +Other: +- use aiProcess_GenUVCoords if needed to handle spherical/planar uv mapping? +- don't build up a big string in memory but write directly to a file +- aiProcess_Triangulate meshes to get triangles only? +- animation (allow specifying a time) + + */ + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_PBRT_EXPORTER + +#include "PbrtExporter.h" + +#include <assimp/version.h> +#include <assimp/DefaultIOSystem.h> +#include <assimp/IOSystem.hpp> +#include <assimp/Exporter.hpp> +#include <assimp/DefaultLogger.hpp> +#include <assimp/StreamWriter.h> +#include <assimp/Exceptional.h> +#include <assimp/material.h> +#include <assimp/scene.h> +#include <assimp/mesh.h> + +#include <algorithm> +#include <cctype> +#include <cmath> +#include <fstream> +#include <functional> +#include <iostream> +#include <memory> +#include <sstream> +#include <string> + +#include "stb/stb_image.h" + +using namespace Assimp; + +namespace Assimp { + +void ExportScenePbrt ( + const char* pFile, + IOSystem* pIOSystem, + const aiScene* pScene, + const ExportProperties* /*pProperties*/ +){ + std::string path = DefaultIOSystem::absolutePath(std::string(pFile)); + std::string file = DefaultIOSystem::completeBaseName(std::string(pFile)); + + // initialize the exporter + PbrtExporter exporter(pScene, pIOSystem, path, file); +} + +} // end of namespace Assimp + +// Constructor +PbrtExporter::PbrtExporter( + const aiScene *pScene, IOSystem *pIOSystem, + const std::string &path, const std::string &file) : + mScene(pScene), + mIOSystem(pIOSystem), + mPath(path), + mFile(file) { + // Export embedded textures. + if (mScene->mNumTextures > 0) + if (!mIOSystem->CreateDirectory("textures")) + throw DeadlyExportError("Could not create textures/ directory."); + for (unsigned int i = 0; i < mScene->mNumTextures; ++i) { + aiTexture* tex = mScene->mTextures[i]; + std::string fn = CleanTextureFilename(tex->mFilename, false); + std::cerr << "Writing embedded texture: " << tex->mFilename.C_Str() << " -> " + << fn << "\n"; + + std::unique_ptr<IOStream> outfile(mIOSystem->Open(fn, "wb")); + if (!outfile) { + throw DeadlyExportError("could not open output texture file: " + fn); + } + if (tex->mHeight == 0) { + // It's binary data + outfile->Write(tex->pcData, tex->mWidth, 1); + } else { + std::cerr << fn << ": TODO handle uncompressed embedded textures.\n"; + } + } + +#if 0 + // Debugging: print the full node hierarchy + std::function<void(aiNode*, int)> visitNode; + visitNode = [&](aiNode* node, int depth) { + for (int i = 0; i < depth; ++i) std::cerr << " "; + std::cerr << node->mName.C_Str() << "\n"; + for (int i = 0; i < node->mNumChildren; ++i) + visitNode(node->mChildren[i], depth + 1); + }; + visitNode(mScene->mRootNode, 0); +#endif + + mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); + + // Write everything out + WriteMetaData(); + WriteCameras(); + WriteWorldDefinition(); + + // And write the file to disk... + std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath,"wt")); + if (!outfile) { + throw DeadlyExportError("could not open output .pbrt file: " + std::string(mFile)); + } + outfile->Write(mOutput.str().c_str(), mOutput.str().length(), 1); +} + +// Destructor +PbrtExporter::~PbrtExporter() { + // Empty +} + +void PbrtExporter::WriteMetaData() { + mOutput << "#############################\n"; + mOutput << "# Scene metadata:\n"; + + aiMetadata* pMetaData = mScene->mMetaData; + for (unsigned int i = 0; i < pMetaData->mNumProperties; i++) { + mOutput << "# - "; + mOutput << pMetaData->mKeys[i].C_Str() << " :"; + switch(pMetaData->mValues[i].mType) { + case AI_BOOL : { + mOutput << " "; + if (*static_cast<bool*>(pMetaData->mValues[i].mData)) + mOutput << "TRUE\n"; + else + mOutput << "FALSE\n"; + break; + } + case AI_INT32 : { + mOutput << " " << + *static_cast<int32_t*>(pMetaData->mValues[i].mData) << + std::endl; + break; + } + case AI_UINT64 : + mOutput << " " << + *static_cast<uint64_t*>(pMetaData->mValues[i].mData) << + std::endl; + break; + case AI_FLOAT : + mOutput << " " << + *static_cast<float*>(pMetaData->mValues[i].mData) << + std::endl; + break; + case AI_DOUBLE : + mOutput << " " << + *static_cast<double*>(pMetaData->mValues[i].mData) << + std::endl; + break; + case AI_AISTRING : { + aiString* value = + static_cast<aiString*>(pMetaData->mValues[i].mData); + std::string svalue = value->C_Str(); + std::size_t found = svalue.find_first_of('\n'); + mOutput << "\n"; + while (found != std::string::npos) { + mOutput << "# " << svalue.substr(0, found) << "\n"; + svalue = svalue.substr(found + 1); + found = svalue.find_first_of('\n'); + } + mOutput << "# " << svalue << "\n"; + break; + } + case AI_AIVECTOR3D : + // TODO + mOutput << " Vector3D (unable to print)\n"; + break; + default: + // AI_META_MAX and FORCE_32BIT + mOutput << " META_MAX or FORCE_32Bit (unable to print)\n"; + break; + } + } +} + +void PbrtExporter::WriteCameras() { + mOutput << "\n"; + mOutput << "###############################\n"; + mOutput << "# Cameras (" << mScene->mNumCameras << ") total\n\n"; + + if (mScene->mNumCameras == 0) { + std::cerr << "Warning: No cameras found in scene file.\n"; + return; + } + + if (mScene->mNumCameras > 1) { + std::cerr << "Multiple cameras found in scene file; defaulting to first one specified.\n"; + } + + for (unsigned int i = 0; i < mScene->mNumCameras; i++) { + WriteCamera(i); + } +} + +aiMatrix4x4 PbrtExporter::GetNodeTransform(const aiString &name) const { + aiMatrix4x4 m; + auto node = mScene->mRootNode->FindNode(name); + if (!node) { + std::cerr << '"' << name.C_Str() << "\": node not found in scene tree.\n"; + throw DeadlyExportError("Could not find node"); + } + else { + while (node) { + m = node->mTransformation * m; + node = node->mParent; + } + } + return m; +} + +std::string PbrtExporter::TransformAsString(const aiMatrix4x4 &m) { + // Transpose on the way out to match pbrt's expected layout (sanity + // check: the translation component should be the last 3 entries + // before a '1' as the final entry in the matrix, assuming it's + // non-projective.) + std::stringstream s; + s << m.a1 << " " << m.b1 << " " << m.c1 << " " << m.d1 << " " + << m.a2 << " " << m.b2 << " " << m.c2 << " " << m.d2 << " " + << m.a3 << " " << m.b3 << " " << m.c3 << " " << m.d3 << " " + << m.a4 << " " << m.b4 << " " << m.c4 << " " << m.d4; + return s.str(); +} + +void PbrtExporter::WriteCamera(int i) { + auto camera = mScene->mCameras[i]; + bool cameraActive = i == 0; + + mOutput << "# - Camera " << i+1 << ": " + << camera->mName.C_Str() << "\n"; + + // Get camera aspect ratio + float aspect = camera->mAspect; + if (aspect == 0) { + aspect = 4.f/3.f; + mOutput << "# - Aspect ratio : 1.33333 (no aspect found, defaulting to 4/3)\n"; + } else { + mOutput << "# - Aspect ratio : " << aspect << "\n"; + } + + // Get Film xres and yres + int xres = 1920; + int yres = (int)round(xres/aspect); + + // Print Film for this camera + if (!cameraActive) + mOutput << "# "; + mOutput << "Film \"rgb\" \"string filename\" \"" << mFile << ".exr\"\n"; + if (!cameraActive) + mOutput << "# "; + mOutput << " \"integer xresolution\" [" << xres << "]\n"; + if (!cameraActive) + mOutput << "# "; + mOutput << " \"integer yresolution\" [" << yres << "]\n"; + + // Get camera fov + float hfov = AI_RAD_TO_DEG(camera->mHorizontalFOV); + float fov = (aspect >= 1.0) ? hfov : (hfov * aspect); + if (fov < 5) { + std::cerr << fov << ": suspiciously low field of view specified by camera. Setting to 45 degrees.\n"; + fov = 45; + } + + // Get camera transform + aiMatrix4x4 worldFromCamera = GetNodeTransform(camera->mName); + + // Print Camera LookAt + auto position = worldFromCamera * camera->mPosition; + auto lookAt = worldFromCamera * (camera->mPosition + camera->mLookAt); + aiMatrix3x3 worldFromCamera3(worldFromCamera); + auto up = worldFromCamera3 * camera->mUp; + up.Normalize(); + + if (!cameraActive) + mOutput << "# "; + mOutput << "Scale -1 1 1\n"; // right handed -> left handed + if (!cameraActive) + mOutput << "# "; + mOutput << "LookAt " + << position.x << " " << position.y << " " << position.z << "\n"; + if (!cameraActive) + mOutput << "# "; + mOutput << " " + << lookAt.x << " " << lookAt.y << " " << lookAt.z << "\n"; + if (!cameraActive) + mOutput << "# "; + mOutput << " " + << up.x << " " << up.y << " " << up.z << "\n"; + + // Print camera descriptor + if (!cameraActive) + mOutput << "# "; + mOutput << "Camera \"perspective\" \"float fov\" " << "[" << fov << "]\n\n"; +} + +void PbrtExporter::WriteWorldDefinition() { + // Figure out which meshes are referenced multiple times; those will be + // emitted as object instances and the rest will be emitted directly. + std::map<int, int> meshUses; + std::function<void(aiNode*)> visitNode; + visitNode = [&](aiNode* node) { + for (unsigned int i = 0; i < node->mNumMeshes; ++i) + ++meshUses[node->mMeshes[i]]; + for (unsigned int i = 0; i < node->mNumChildren; ++i) + visitNode(node->mChildren[i]); + }; + visitNode(mScene->mRootNode); + int nInstanced = 0, nUnused = 0; + for (const auto &u : meshUses) { + if (u.second == 0) ++nUnused; + else if (u.second > 1) ++nInstanced; + } + std::cerr << nInstanced << " / " << mScene->mNumMeshes << " meshes instanced.\n"; + if (nUnused) + std::cerr << nUnused << " meshes defined but not used in scene.\n"; + + mOutput << "WorldBegin\n"; + + WriteLights(); + WriteTextures(); + WriteMaterials(); + + // Object instance definitions + mOutput << "# Object instance definitions\n\n"; + for (const auto &mu : meshUses) { + if (mu.second > 1) { + WriteInstanceDefinition(mu.first); + } + } + + mOutput << "# Geometry\n\n"; + aiMatrix4x4 worldFromObject; + WriteGeometricObjects(mScene->mRootNode, worldFromObject, meshUses); +} + +void PbrtExporter::WriteTextures() { + mOutput << "###################\n"; + mOutput << "# Textures\n\n"; + + C_STRUCT aiString path; + aiTextureMapping mapping; + unsigned int uvIndex; + ai_real blend; + aiTextureOp op; + aiTextureMapMode mapMode[3]; + + // For every material in the scene, + for (unsigned int m = 0 ; m < mScene->mNumMaterials; m++) { + auto material = mScene->mMaterials[m]; + // Parse through all texture types, + for (int tt = 1; tt <= aiTextureType_UNKNOWN; tt++) { + int ttCount = material->GetTextureCount(aiTextureType(tt)); + // ... and get every texture + for (int t = 0; t < ttCount; t++) { + // TODO write out texture specifics + // TODO UV transforms may be material specific + // so those may need to be baked into unique tex name + if (material->GetTexture(aiTextureType(tt), t, &path, &mapping, + &uvIndex, &blend, &op, mapMode) != AI_SUCCESS) { + std::cerr << "Error getting texture! " << m << " " << tt << " " << t << "\n"; + continue; + } + + std::string filename = CleanTextureFilename(path); + + if (uvIndex != 0) + std::cerr << "Warning: texture \"" << filename << "\" uses uv set #" << + uvIndex << " but the pbrt converter only exports uv set 0.\n"; +#if 0 + if (op != aiTextureOp_Multiply) + std::cerr << "Warning: unexpected texture op " << (int)op << + " encountered for texture \"" << + filename << "\". The resulting scene may have issues...\n"; + if (blend != 1) + std::cerr << "Blend value of " << blend << " found for texture \"" << filename + << "\" but not handled in converter.\n"; +#endif + + std::string mappingString; +#if 0 + if (mapMode[0] != mapMode[1]) + std::cerr << "Different texture boundary mode for u and v for texture \"" << + filename << "\". Using u for both.\n"; + switch (mapMode[0]) { + case aiTextureMapMode_Wrap: + // pbrt's default + break; + case aiTextureMapMode_Clamp: + mappingString = "\"string wrap\" \"clamp\""; + break; + case aiTextureMapMode_Decal: + std::cerr << "Decal texture boundary mode not supported by pbrt for texture \"" << + filename << "\"\n"; + break; + case aiTextureMapMode_Mirror: + std::cerr << "Mirror texture boundary mode not supported by pbrt for texture \"" << + filename << "\"\n"; + break; + default: + std::cerr << "Unexpected map mode " << (int)mapMode[0] << " for texture \"" << + filename << "\"\n"; + //throw DeadlyExportError("Unexpected aiTextureMapMode"); + } +#endif + +#if 0 + aiUVTransform uvTransform; + if (material->Get(AI_MATKEY_TEXTURE(tt, t), uvTransform) == AI_SUCCESS) { + mOutput << "# UV transform " << uvTransform.mTranslation.x << " " + << uvTransform.mTranslation.y << " " << uvTransform.mScaling.x << " " + << uvTransform.mScaling.y << " " << uvTransform.mRotation << "\n"; + } +#endif + + std::string texName, texType, texOptions; + if (aiTextureType(tt) == aiTextureType_SHININESS || + aiTextureType(tt) == aiTextureType_OPACITY || + aiTextureType(tt) == aiTextureType_HEIGHT || + aiTextureType(tt) == aiTextureType_DISPLACEMENT || + aiTextureType(tt) == aiTextureType_METALNESS || + aiTextureType(tt) == aiTextureType_DIFFUSE_ROUGHNESS) { + texType = "float"; + texName = std::string("float:") + RemoveSuffix(filename); + + if (aiTextureType(tt) == aiTextureType_SHININESS) { + texOptions = " \"bool invert\" true\n"; + texName += "_Roughness"; + } + } else if (aiTextureType(tt) == aiTextureType_DIFFUSE || + aiTextureType(tt) == aiTextureType_BASE_COLOR) { + texType = "spectrum"; + texName = std::string("rgb:") + RemoveSuffix(filename); + } + + // Don't export textures we're not actually going to use... + if (texName.empty()) + continue; + + if (mTextureSet.find(texName) == mTextureSet.end()) { + mOutput << "Texture \"" << texName << "\" \"" << texType << "\" \"imagemap\"\n" + << texOptions + << " \"string filename\" \"" << filename << "\" " << mappingString << '\n'; + mTextureSet.insert(texName); + } + + // Also emit a float version for use with alpha testing... + if ((aiTextureType(tt) == aiTextureType_DIFFUSE || + aiTextureType(tt) == aiTextureType_BASE_COLOR) && + TextureHasAlphaMask(filename)) { + texType = "float"; + texName = std::string("alpha:") + filename; + if (mTextureSet.find(texName) == mTextureSet.end()) { + mOutput << "Texture \"" << texName << "\" \"" << texType << "\" \"imagemap\"\n" + << " \"string filename\" \"" << filename << "\" " << mappingString << '\n'; + mTextureSet.insert(texName); + } + } + } + } + } +} + +bool PbrtExporter::TextureHasAlphaMask(const std::string &filename) { + // TODO: STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); + // quick return if it's 3 + + int xSize, ySize, nComponents; + unsigned char *data = stbi_load(filename.c_str(), &xSize, &ySize, &nComponents, 0); + if (!data) { + std::cerr << filename << ": unable to load texture and check for alpha mask in texture. " + "Geometry will not be alpha masked with this texture.\n"; + return false; + } + + bool hasMask = false; + switch (nComponents) { + case 1: + for (int i = 0; i < xSize * ySize; ++i) + if (data[i] != 255) { + hasMask = true; + break; + } + break; + case 2: + for (int y = 0; y < ySize; ++y) + for (int x = 0; x < xSize; ++x) + if (data[2 * (x + y * xSize) + 1] != 255) { + hasMask = true; + break; + } + break; + case 3: + break; + case 4: + for (int y = 0; y < ySize; ++y) + for (int x = 0; x < xSize; ++x) + if (data[4 * (x + y * xSize) + 3] != 255) { + hasMask = true; + break; + } + break; + default: + std::cerr << filename << ": unexpected number of image channels, " << + nComponents << ".\n"; + } + + stbi_image_free(data); + return hasMask; +} + +void PbrtExporter::WriteMaterials() { + mOutput << "\n"; + mOutput << "####################\n"; + mOutput << "# Materials (" << mScene->mNumMaterials << ") total\n\n"; + + for (unsigned int i = 0; i < mScene->mNumMaterials; i++) { + WriteMaterial(i); + } + mOutput << "\n\n"; +} + +void PbrtExporter::WriteMaterial(int m) { + aiMaterial* material = mScene->mMaterials[m]; + + // get material name + auto materialName = material->GetName(); + mOutput << std::endl << "# - Material " << m+1 << ": " << materialName.C_Str() << "\n"; + + // Print out number of properties + mOutput << "# - Number of Material Properties: " << material->mNumProperties << "\n"; + + // Print out texture type counts + mOutput << "# - Non-Zero Texture Type Counts: "; + for (int i = 1; i <= aiTextureType_UNKNOWN; i++) { + int count = material->GetTextureCount(aiTextureType(i)); + if (count > 0) + mOutput << TextureTypeToString(aiTextureType(i)) << ": " << count << " "; + } + mOutput << "\n"; + + auto White = [](const aiColor3D &c) { return c.r == 1 && c.g == 1 && c.b == 1; }; + auto Black = [](const aiColor3D &c) { return c.r == 0 && c.g == 0 && c.b == 0; }; + + aiColor3D diffuse, specular, transparency; + bool constantDiffuse = (material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse) == AI_SUCCESS && + !White(diffuse)); + bool constantSpecular = (material->Get(AI_MATKEY_COLOR_SPECULAR, specular) == AI_SUCCESS && + !White(specular)); + bool constantTransparency = (material->Get(AI_MATKEY_COLOR_TRANSPARENT, transparency) == AI_SUCCESS && + !Black(transparency)); + + float opacity, shininess, shininessStrength, eta; + bool constantOpacity = (material->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS && + opacity != 0); + bool constantShininess = material->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS; + bool constantShininessStrength = material->Get(AI_MATKEY_SHININESS_STRENGTH, shininessStrength) == AI_SUCCESS; + bool constantEta = (material->Get(AI_MATKEY_REFRACTI, eta) == AI_SUCCESS && + eta != 1); + + mOutput << "# - Constants: diffuse " << constantDiffuse << " specular " << constantSpecular << + " transparency " << constantTransparency << " opacity " << constantOpacity << + " shininess " << constantShininess << " shininess strength " << constantShininessStrength << + " eta " << constantEta << "\n"; + + aiString roughnessMap; + if (material->Get(AI_MATKEY_TEXTURE_SHININESS(0), roughnessMap) == AI_SUCCESS) { + std::string roughnessTexture = std::string("float:") + + RemoveSuffix(CleanTextureFilename(roughnessMap)) + "_Roughness"; + mOutput << "MakeNamedMaterial \"" << materialName.C_Str() << "\"" + << " \"string type\" \"coateddiffuse\"\n" + << " \"texture roughness\" \"" << roughnessTexture << "\"\n"; + } else if (constantShininess) { + // Assume plastic for now at least + float roughness = std::max(0.f, 1.f - shininess); + mOutput << "MakeNamedMaterial \"" << materialName.C_Str() << "\"" + << " \"string type\" \"coateddiffuse\"\n" + << " \"float roughness\" " << roughness << "\n"; + } else + // Diffuse + mOutput << "MakeNamedMaterial \"" << materialName.C_Str() << "\"" + << " \"string type\" \"diffuse\"\n"; + + aiString diffuseTexture; + if (material->Get(AI_MATKEY_TEXTURE_DIFFUSE(0), diffuseTexture) == AI_SUCCESS) + mOutput << " \"texture reflectance\" \"rgb:" << RemoveSuffix(CleanTextureFilename(diffuseTexture)) << "\"\n"; + else + mOutput << " \"rgb reflectance\" [ " << diffuse.r << " " << diffuse.g << + " " << diffuse.b << " ]\n"; + + aiString displacementTexture, normalMap; + if (material->Get(AI_MATKEY_TEXTURE_NORMALS(0), displacementTexture) == AI_SUCCESS) + mOutput << " \"string normalmap\" \"" << CleanTextureFilename(displacementTexture) << "\"\n"; + else if (material->Get(AI_MATKEY_TEXTURE_HEIGHT(0), displacementTexture) == AI_SUCCESS) + mOutput << " \"texture displacement\" \"float:" << + RemoveSuffix(CleanTextureFilename(displacementTexture)) << "\"\n"; + else if (material->Get(AI_MATKEY_TEXTURE_DISPLACEMENT(0), displacementTexture) == AI_SUCCESS) + mOutput << " \"texture displacement\" \"float:" << + RemoveSuffix(CleanTextureFilename(displacementTexture)) << "\"\n"; +} + +std::string PbrtExporter::CleanTextureFilename(const aiString &f, bool rewriteExtension) const { + std::string fn = f.C_Str(); + // Remove directory name + size_t offset = fn.find_last_of("/\\"); + if (offset != std::string::npos) { + fn.erase(0, offset + 1); + } + + // Expect all textures in textures + fn = std::string("textures") + mIOSystem->getOsSeparator() + fn; + + // Rewrite extension for unsupported file formats. + if (rewriteExtension) { + offset = fn.rfind('.'); + if (offset != std::string::npos) { + std::string extension = fn; + extension.erase(0, offset + 1); + std::transform(extension.begin(), extension.end(), extension.begin(), + [](unsigned char c) { return (char)std::tolower(c); }); + + if (extension != "tga" && extension != "exr" && extension != "png" && + extension != "pfm" && extension != "hdr") { + std::string orig = fn; + fn.erase(offset + 1); + fn += "png"; + + // Does it already exist? Warn if not. + std::ifstream filestream(fn); + if (!filestream.good()) + std::cerr << orig << ": must convert this texture to PNG.\n"; + } + } + } + + return fn; +} + +std::string PbrtExporter::RemoveSuffix(std::string filename) { + size_t offset = filename.rfind('.'); + if (offset != std::string::npos) + filename.erase(offset); + return filename; +} + +void PbrtExporter::WriteLights() { + mOutput << "\n"; + mOutput << "#################\n"; + mOutput << "# Lights\n\n"; + if (mScene->mNumLights == 0) { + // Skip the default light if no cameras and this is flat up geometry + if (mScene->mNumCameras > 0) { + std::cerr << "No lights specified. Using default infinite light.\n"; + + mOutput << "AttributeBegin\n"; + mOutput << " # default light\n"; + mOutput << " LightSource \"infinite\" \"blackbody L\" [6000 1]\n"; + + mOutput << "AttributeEnd\n\n"; + } + } else { + for (unsigned int i = 0; i < mScene->mNumLights; ++i) { + const aiLight *light = mScene->mLights[i]; + + mOutput << "# Light " << light->mName.C_Str() << "\n"; + mOutput << "AttributeBegin\n"; + + aiMatrix4x4 worldFromLight = GetNodeTransform(light->mName); + mOutput << " Transform [ " << TransformAsString(worldFromLight) << " ]\n"; + + aiColor3D color = light->mColorDiffuse + light->mColorSpecular; + if (light->mAttenuationConstant != 0) + color = color * (ai_real)(1. / light->mAttenuationConstant); + + switch (light->mType) { + case aiLightSource_DIRECTIONAL: { + mOutput << " LightSource \"distant\"\n"; + mOutput << " \"point3 from\" [ " << light->mPosition.x << " " << + light->mPosition.y << " " << light->mPosition.z << " ]\n"; + aiVector3D to = light->mPosition + light->mDirection; + mOutput << " \"point3 to\" [ " << to.x << " " << to.y << " " << to.z << " ]\n"; + mOutput << " \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n"; + break; + } case aiLightSource_POINT: + mOutput << " LightSource \"distant\"\n"; + mOutput << " \"point3 from\" [ " << light->mPosition.x << " " << + light->mPosition.y << " " << light->mPosition.z << " ]\n"; + mOutput << " \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n"; + break; + case aiLightSource_SPOT: { + mOutput << " LightSource \"spot\"\n"; + mOutput << " \"point3 from\" [ " << light->mPosition.x << " " << + light->mPosition.y << " " << light->mPosition.z << " ]\n"; + aiVector3D to = light->mPosition + light->mDirection; + mOutput << " \"point3 to\" [ " << to.x << " " << to.y << " " << to.z << " ]\n"; + mOutput << " \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n"; + mOutput << " \"float coneangle\" [ " << AI_RAD_TO_DEG(light->mAngleOuterCone) << " ]\n"; + mOutput << " \"float conedeltaangle\" [ " << AI_RAD_TO_DEG(light->mAngleOuterCone - + light->mAngleInnerCone) << " ]\n"; + break; + } case aiLightSource_AMBIENT: + mOutput << "# ignored ambient light source\n"; + break; + case aiLightSource_AREA: { + aiVector3D left = light->mDirection ^ light->mUp; + // rectangle. center at position, direction is normal vector + ai_real dLeft = light->mSize.x / 2, dUp = light->mSize.y / 2; + aiVector3D vertices[4] = { + light->mPosition - dLeft * left - dUp * light->mUp, + light->mPosition + dLeft * left - dUp * light->mUp, + light->mPosition - dLeft * left + dUp * light->mUp, + light->mPosition + dLeft * left + dUp * light->mUp }; + mOutput << " AreaLightSource \"diffuse\"\n"; + mOutput << " \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n"; + mOutput << " Shape \"bilinearmesh\"\n"; + mOutput << " \"point3 p\" [ "; + for (int j = 0; j < 4; ++j) + mOutput << vertices[j].x << " " << vertices[j].y << " " << vertices[j].z; + mOutput << " ]\n"; + mOutput << " \"integer indices\" [ 0 1 2 3 ]\n"; + break; + } default: + mOutput << "# ignored undefined light source type\n"; + break; + } + mOutput << "AttributeEnd\n\n"; + } + } +} + +void PbrtExporter::WriteMesh(aiMesh* mesh) { + mOutput << "# - Mesh: "; + if (mesh->mName == aiString("")) + mOutput << "<No Name>\n"; + else + mOutput << mesh->mName.C_Str() << "\n"; + + mOutput << "AttributeBegin\n"; + aiMaterial* material = mScene->mMaterials[mesh->mMaterialIndex]; + mOutput << " NamedMaterial \"" << material->GetName().C_Str() << "\"\n"; + + // Handle area lights + aiColor3D emission; + if (material->Get(AI_MATKEY_COLOR_EMISSIVE, emission) == AI_SUCCESS && + (emission.r > 0 || emission.g > 0 || emission.b > 0)) + mOutput << " AreaLightSource \"diffuse\" \"rgb L\" [ " << emission.r << + " " << emission.g << " " << emission.b << " ]\n"; + + // Check if any types other than tri + if ( (mesh->mPrimitiveTypes & aiPrimitiveType_POINT) + || (mesh->mPrimitiveTypes & aiPrimitiveType_LINE) + || (mesh->mPrimitiveTypes & aiPrimitiveType_POLYGON)) { + std::cerr << "Error: ignoring point / line / polygon mesh " << mesh->mName.C_Str() << ".\n"; + return; + } + + // Alpha mask + std::string alpha; + aiString opacityTexture; + if (material->Get(AI_MATKEY_TEXTURE_OPACITY(0), opacityTexture) == AI_SUCCESS || + material->Get(AI_MATKEY_TEXTURE_DIFFUSE(0), opacityTexture) == AI_SUCCESS) { + // material->Get(AI_MATKEY_TEXTURE_BASE_COLOR(0), opacityTexture) == AI_SUCCESS) + std::string texName = std::string("alpha:") + CleanTextureFilename(opacityTexture); + if (mTextureSet.find(texName) != mTextureSet.end()) + alpha = std::string(" \"texture alpha\" \"") + texName + "\"\n"; + } else { + float opacity = 1; + if (material->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS && opacity < 1) + alpha = std::string(" \"float alpha\" [ ") + std::to_string(opacity) + " ]\n"; + } + + // Output the shape specification + mOutput << "Shape \"trianglemesh\"\n" << + alpha << + " \"integer indices\" ["; + + // Start with faces (which hold indices) + for (unsigned int i = 0; i < mesh->mNumFaces; i++) { + auto face = mesh->mFaces[i]; + if (face.mNumIndices != 3) throw DeadlyExportError("oh no not a tri!"); + + for (unsigned int j = 0; j < face.mNumIndices; j++) { + mOutput << face.mIndices[j] << " "; + } + if ((i % 7) == 6) mOutput << "\n "; + } + mOutput << "]\n"; + + // Then go to vertices + mOutput << " \"point3 P\" ["; + for (unsigned int i = 0; i < mesh->mNumVertices; i++) { + auto vector = mesh->mVertices[i]; + mOutput << vector.x << " " << vector.y << " " << vector.z << " "; + if ((i % 4) == 3) mOutput << "\n "; + } + mOutput << "]\n"; + + // Normals (if present) + if (mesh->mNormals) { + mOutput << " \"normal N\" ["; + for (unsigned int i = 0; i < mesh->mNumVertices; i++) { + auto normal = mesh->mNormals[i]; + mOutput << normal.x << " " << normal.y << " " << normal.z << " "; + if ((i % 4) == 3) mOutput << "\n "; + } + mOutput << "]\n"; + } + + // Tangents (if present) + if (mesh->mTangents) { + mOutput << " \"vector3 S\" ["; + for (unsigned int i = 0; i < mesh->mNumVertices; i++) { + auto tangent = mesh->mTangents[i]; + mOutput << tangent.x << " " << tangent.y << " " << tangent.z << " "; + if ((i % 4) == 3) mOutput << "\n "; + } + mOutput << "]\n"; + } + + // Texture Coords (if present) + // Find the first set of 2D texture coordinates.. + for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (mesh->mNumUVComponents[i] == 2) { + // assert(mesh->mTextureCoords[i] != nullptr); + aiVector3D* uv = mesh->mTextureCoords[i]; + mOutput << " \"point2 uv\" ["; + for (unsigned int j = 0; j < mesh->mNumVertices; ++j) { + mOutput << uv[j].x << " " << uv[j].y << " "; + if ((j % 6) == 5) mOutput << "\n "; + } + mOutput << "]\n"; + break; + } + } + // TODO: issue warning if there are additional UV sets? + + mOutput << "AttributeEnd\n"; +} + +void PbrtExporter::WriteInstanceDefinition(int i) { + aiMesh* mesh = mScene->mMeshes[i]; + + mOutput << "ObjectBegin \""; + if (mesh->mName == aiString("")) + mOutput << "mesh_" << i+1 << "\"\n"; + else + mOutput << mesh->mName.C_Str() << "_" << i+1 << "\"\n"; + + WriteMesh(mesh); + + mOutput << "ObjectEnd\n"; +} + +void PbrtExporter::WriteGeometricObjects(aiNode* node, aiMatrix4x4 worldFromObject, + std::map<int, int> &meshUses) { + // Sometimes interior nodes have degenerate matrices?? + if (node->mTransformation.Determinant() != 0) + worldFromObject = worldFromObject * node->mTransformation; + + if (node->mNumMeshes > 0) { + mOutput << "AttributeBegin\n"; + + mOutput << " Transform [ " << TransformAsString(worldFromObject) << "]\n"; + + for (unsigned int i = 0; i < node->mNumMeshes; i++) { + aiMesh* mesh = mScene->mMeshes[node->mMeshes[i]]; + if (meshUses[node->mMeshes[i]] == 1) { + // If it's only used once in the scene, emit it directly as + // a triangle mesh. + mOutput << " # " << mesh->mName.C_Str(); + WriteMesh(mesh); + } else { + // If it's used multiple times, there will be an object + // instance for it, so emit a reference to that. + mOutput << " ObjectInstance \""; + if (mesh->mName == aiString("")) + mOutput << "mesh_" << node->mMeshes[i] + 1 << "\"\n"; + else + mOutput << mesh->mName.C_Str() << "_" << node->mMeshes[i] + 1 << "\"\n"; + } + } + mOutput << "AttributeEnd\n\n"; + } + + // Recurse through children + for (unsigned int i = 0; i < node->mNumChildren; i++) { + WriteGeometricObjects(node->mChildren[i], worldFromObject, meshUses); + } +} + +#endif // ASSIMP_BUILD_NO_PBRT_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/Pbrt/PbrtExporter.h b/libs/assimp/code/Pbrt/PbrtExporter.h new file mode 100644 index 0000000..4f4e162 --- /dev/null +++ b/libs/assimp/code/Pbrt/PbrtExporter.h @@ -0,0 +1,135 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 PbrtExporter.h +* Declares the exporter class to write a scene to a pbrt file +*/ +#ifndef AI_PBRTEXPORTER_H_INC +#define AI_PBRTEXPORTER_H_INC + +#ifndef ASSIMP_BUILD_NO_PBRT_EXPORTER + +#include <assimp/types.h> +#include <assimp/StreamWriter.h> +#include <assimp/Exceptional.h> + +#include <map> +#include <set> +#include <string> +#include <sstream> + +struct aiScene; +struct aiNode; +struct aiMaterial; +struct aiMesh; + +namespace Assimp { + +class IOSystem; +class IOStream; +class ExportProperties; + +// --------------------------------------------------------------------- +/** Helper class to export a given scene to a Pbrt file. */ +// --------------------------------------------------------------------- +class PbrtExporter +{ +public: + /// Constructor for a specific scene to export + PbrtExporter(const aiScene *pScene, IOSystem *pIOSystem, + const std::string &path, const std::string &file); + + /// Destructor + virtual ~PbrtExporter(); + +private: + // the scene to export + const aiScene* mScene; + + /// Stringstream to write all output into + std::stringstream mOutput; + + /// The IOSystem for output + IOSystem* mIOSystem; + + /// Path of the directory where the scene will be exported + const std::string mPath; + + /// Name of the file (without extension) where the scene will be exported + const std::string mFile; + +private: + // A private set to keep track of which textures have been declared + std::set<std::string> mTextureSet; + + aiMatrix4x4 GetNodeTransform(const aiString& name) const; + static std::string TransformAsString(const aiMatrix4x4& m); + + static std::string RemoveSuffix(std::string filename); + std::string CleanTextureFilename(const aiString &f, bool rewriteExtension = true) const; + + void WriteMetaData(); + + void WriteWorldDefinition(); + + void WriteCameras(); + void WriteCamera(int i); + + void WriteLights(); + + void WriteTextures(); + static bool TextureHasAlphaMask(const std::string &filename); + + void WriteMaterials(); + void WriteMaterial(int i); + + void WriteMesh(aiMesh* mesh); + + void WriteInstanceDefinition(int i); + void WriteGeometricObjects(aiNode* node, aiMatrix4x4 parentTransform, + std::map<int, int> &meshUses); +}; + +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_PBRT_EXPORTER + +#endif // AI_PBRTEXPORTER_H_INC diff --git a/libs/assimp/code/PostProcessing/ArmaturePopulate.cpp b/libs/assimp/code/PostProcessing/ArmaturePopulate.cpp new file mode 100644 index 0000000..3fc1e12 --- /dev/null +++ b/libs/assimp/code/PostProcessing/ArmaturePopulate.cpp @@ -0,0 +1,263 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. +---------------------------------------------------------------------- +*/ +#include "ArmaturePopulate.h" + +#include <assimp/BaseImporter.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <iostream> + +namespace Assimp { + +/// The default class constructor. +ArmaturePopulate::ArmaturePopulate() : + BaseProcess() { + // do nothing +} + +/// The class destructor. +ArmaturePopulate::~ArmaturePopulate() { + // do nothing +} + +bool ArmaturePopulate::IsActive(unsigned int pFlags) const { + return (pFlags & aiProcess_PopulateArmatureData) != 0; +} + +void ArmaturePopulate::SetupProperties(const Importer *) { + // do nothing +} + +void ArmaturePopulate::Execute(aiScene *out) { + + // Now convert all bone positions to the correct mOffsetMatrix + std::vector<aiBone *> bones; + std::vector<aiNode *> nodes; + std::map<aiBone *, aiNode *> bone_stack; + BuildBoneList(out->mRootNode, out->mRootNode, out, bones); + BuildNodeList(out->mRootNode, nodes); + + BuildBoneStack(out->mRootNode, out->mRootNode, out, bones, bone_stack, nodes); + + ASSIMP_LOG_DEBUG("Bone stack size: ", bone_stack.size()); + + for (std::pair<aiBone *, aiNode *> kvp : bone_stack) { + aiBone *bone = kvp.first; + aiNode *bone_node = kvp.second; + ASSIMP_LOG_VERBOSE_DEBUG("active node lookup: ", bone->mName.C_Str()); + // lcl transform grab - done in generate_nodes :) + + // bone->mOffsetMatrix = bone_node->mTransformation; + aiNode *armature = GetArmatureRoot(bone_node, bones); + + ai_assert(armature); + + // set up bone armature id + bone->mArmature = armature; + + // set this bone node to be referenced properly + ai_assert(bone_node); + bone->mNode = bone_node; + } +} + + +// Reprocess all nodes to calculate bone transforms properly based on the REAL +// mOffsetMatrix not the local. +// Before this would use mesh transforms which is wrong for bone transforms +// Before this would work for simple character skeletons but not complex meshes +// with multiple origins +// Source: sketch fab log cutter fbx +void ArmaturePopulate::BuildBoneList(aiNode *current_node, + const aiNode *root_node, + const aiScene *scene, + std::vector<aiBone *> &bones) { + ai_assert(scene); + for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) { + aiNode *child = current_node->mChildren[nodeId]; + ai_assert(child); + + // check for bones + for (unsigned int meshId = 0; meshId < child->mNumMeshes; ++meshId) { + ai_assert(child->mMeshes); + unsigned int mesh_index = child->mMeshes[meshId]; + aiMesh *mesh = scene->mMeshes[mesh_index]; + ai_assert(mesh); + + for (unsigned int boneId = 0; boneId < mesh->mNumBones; ++boneId) { + aiBone *bone = mesh->mBones[boneId]; + ai_assert(nullptr != bone); + + // duplicate mehes exist with the same bones sometimes :) + // so this must be detected + if (std::find(bones.begin(), bones.end(), bone) == bones.end()) { + // add the element once + bones.emplace_back(bone); + } + } + + // find mesh and get bones + // then do recursive lookup for bones in root node hierarchy + } + + BuildBoneList(child, root_node, scene, bones); + } +} + +// Prepare flat node list which can be used for non recursive lookups later +void ArmaturePopulate::BuildNodeList(const aiNode *current_node, + std::vector<aiNode *> &nodes) { + ai_assert(nullptr != current_node); + + for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) { + aiNode *child = current_node->mChildren[nodeId]; + ai_assert(child); + + if (child->mNumMeshes == 0) { + nodes.emplace_back(child); + } + + BuildNodeList(child, nodes); + } +} + +// A bone stack allows us to have multiple armatures, with the same bone names +// A bone stack allows us also to retrieve bones true transform even with +// duplicate names :) +void ArmaturePopulate::BuildBoneStack(aiNode *, + const aiNode *root_node, + const aiScene*, + const std::vector<aiBone *> &bones, + std::map<aiBone *, aiNode *> &bone_stack, + std::vector<aiNode *> &node_stack) { + if (node_stack.empty()) { + return; + } + ai_assert(nullptr != root_node); + + for (aiBone *bone : bones) { + ai_assert(bone); + aiNode *node = GetNodeFromStack(bone->mName, node_stack); + if (node == nullptr) { + node_stack.clear(); + BuildNodeList(root_node, node_stack); + ASSIMP_LOG_VERBOSE_DEBUG("Resetting bone stack: nullptr element ", bone->mName.C_Str()); + + node = GetNodeFromStack(bone->mName, node_stack); + + if (nullptr == node) { + ASSIMP_LOG_ERROR("serious import issue node for bone was not detected"); + continue; + } + } + + ASSIMP_LOG_VERBOSE_DEBUG("Successfully added bone[", bone->mName.C_Str(), "] to stack and bone node is: ", node->mName.C_Str()); + + bone_stack.insert(std::pair<aiBone *, aiNode *>(bone, node)); + } +} + +// Returns the armature root node +// This is required to be detected for a bone initially, it will recurse up +// until it cannot find another bone and return the node No known failure +// points. (yet) +aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node, + std::vector<aiBone *> &bone_list) { + while (nullptr != bone_node) { + if (!IsBoneNode(bone_node->mName, bone_list)) { + ASSIMP_LOG_VERBOSE_DEBUG("GetArmatureRoot() Found valid armature: ", bone_node->mName.C_Str()); + return bone_node; + } + + bone_node = bone_node->mParent; + } + + ASSIMP_LOG_ERROR("GetArmatureRoot() can't find armature!"); + + return nullptr; +} + +// Simple IsBoneNode check if this could be a bone +bool ArmaturePopulate::IsBoneNode(const aiString &bone_name, + std::vector<aiBone *> &bones) { + for (aiBone *bone : bones) { + if (bone->mName == bone_name) { + return true; + } + } + + return false; +} + +// Pop this node by name from the stack if found +// Used in multiple armature situations with duplicate node / bone names +// Known flaw: cannot have nodes with bone names, will be fixed in later release +// (serious to be fixed) Known flaw: nodes which have more than one bone could +// be prematurely dropped from stack +aiNode *ArmaturePopulate::GetNodeFromStack(const aiString &node_name, + std::vector<aiNode *> &nodes) { + std::vector<aiNode *>::iterator iter; + aiNode *found = nullptr; + for (iter = nodes.begin(); iter < nodes.end(); ++iter) { + aiNode *element = *iter; + ai_assert(nullptr != element); + // node valid and node name matches + if (element->mName == node_name) { + found = element; + break; + } + } + + if (found != nullptr) { + ASSIMP_LOG_INFO("Removed node from stack: ", found->mName.C_Str()); + // now pop the element from the node list + nodes.erase(iter); + + return found; + } + + // unique names can cause this problem + ASSIMP_LOG_ERROR("[Serious] GetNodeFromStack() can't find node from stack!"); + + return nullptr; +} + +} // Namespace Assimp diff --git a/libs/assimp/code/PostProcessing/ArmaturePopulate.h b/libs/assimp/code/PostProcessing/ArmaturePopulate.h new file mode 100644 index 0000000..530932f --- /dev/null +++ b/libs/assimp/code/PostProcessing/ArmaturePopulate.h @@ -0,0 +1,112 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef ARMATURE_POPULATE_H_ +#define ARMATURE_POPULATE_H_ + +#include "Common/BaseProcess.h" +#include <assimp/BaseImporter.h> +#include <vector> +#include <map> + + +struct aiNode; +struct aiBone; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** Armature Populate: This is a post process designed + * To save you time when importing models into your game engines + * This was originally designed only for fbx but will work with other formats + * it is intended to auto populate aiBone data with armature and the aiNode + * This is very useful when dealing with skinned meshes + * or when dealing with many different skeletons + * It's off by default but recommend that you try it and use it + * It should reduce down any glue code you have in your + * importers + * You can contact RevoluPowered <gordon@gordonite.tech> + * For more info about this +*/ +class ASSIMP_API ArmaturePopulate : public BaseProcess { +public: + /// The default class constructor. + ArmaturePopulate(); + + /// The class destructor. + virtual ~ArmaturePopulate(); + + /// Overwritten, @see BaseProcess + virtual bool IsActive( unsigned int pFlags ) const; + + /// Overwritten, @see BaseProcess + virtual void SetupProperties( const Importer* pImp ); + + /// Overwritten, @see BaseProcess + virtual void Execute( aiScene* pScene ); + + static aiNode *GetArmatureRoot(aiNode *bone_node, + std::vector<aiBone *> &bone_list); + + static bool IsBoneNode(const aiString &bone_name, + std::vector<aiBone *> &bones); + + static aiNode *GetNodeFromStack(const aiString &node_name, + std::vector<aiNode *> &nodes); + + static void BuildNodeList(const aiNode *current_node, + std::vector<aiNode *> &nodes); + + static void BuildBoneList(aiNode *current_node, const aiNode *root_node, + const aiScene *scene, + std::vector<aiBone *> &bones); + + static void BuildBoneStack(aiNode *current_node, const aiNode *root_node, + const aiScene *scene, + const std::vector<aiBone *> &bones, + std::map<aiBone *, aiNode *> &bone_stack, + std::vector<aiNode *> &node_stack); +}; + +} // Namespace Assimp + + +#endif // SCALE_PROCESS_H_ diff --git a/libs/assimp/code/PostProcessing/CalcTangentsProcess.cpp b/libs/assimp/code/PostProcessing/CalcTangentsProcess.cpp new file mode 100644 index 0000000..b8847e4 --- /dev/null +++ b/libs/assimp/code/PostProcessing/CalcTangentsProcess.cpp @@ -0,0 +1,303 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the post processing step to calculate + * tangents and bitangents for all imported meshes + */ + +// internal headers +#include "CalcTangentsProcess.h" +#include "ProcessHelper.h" +#include <assimp/TinyFormatter.h> +#include <assimp/qnan.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +CalcTangentsProcess::CalcTangentsProcess() : + configMaxAngle(float(AI_DEG_TO_RAD(45.f))), configSourceUV(0) { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +CalcTangentsProcess::~CalcTangentsProcess() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool CalcTangentsProcess::IsActive(unsigned int pFlags) const { + return (pFlags & aiProcess_CalcTangentSpace) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void CalcTangentsProcess::SetupProperties(const Importer *pImp) { + ai_assert(nullptr != pImp); + + // get the current value of the property + configMaxAngle = pImp->GetPropertyFloat(AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE, 45.f); + configMaxAngle = std::max(std::min(configMaxAngle, 45.0f), 0.0f); + configMaxAngle = AI_DEG_TO_RAD(configMaxAngle); + + configSourceUV = pImp->GetPropertyInteger(AI_CONFIG_PP_CT_TEXTURE_CHANNEL_INDEX, 0); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void CalcTangentsProcess::Execute(aiScene *pScene) { + ai_assert(nullptr != pScene); + + ASSIMP_LOG_DEBUG("CalcTangentsProcess begin"); + + bool bHas = false; + for (unsigned int a = 0; a < pScene->mNumMeshes; a++) { + if (ProcessMesh(pScene->mMeshes[a], a)) bHas = true; + } + + if (bHas) { + ASSIMP_LOG_INFO("CalcTangentsProcess finished. Tangents have been calculated"); + } else { + ASSIMP_LOG_DEBUG("CalcTangentsProcess finished"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Calculates tangents and bi-tangents for the given mesh +bool CalcTangentsProcess::ProcessMesh(aiMesh *pMesh, unsigned int meshIndex) { + // we assume that the mesh is still in the verbose vertex format where each face has its own set + // of vertices and no vertices are shared between faces. Sadly I don't know any quick test to + // assert() it here. + // assert( must be verbose, dammit); + + if (pMesh->mTangents) // this implies that mBitangents is also there + return false; + + // If the mesh consists of lines and/or points but not of + // triangles or higher-order polygons the normal vectors + // are undefined. + if (!(pMesh->mPrimitiveTypes & (aiPrimitiveType_TRIANGLE | aiPrimitiveType_POLYGON))) { + ASSIMP_LOG_INFO("Tangents are undefined for line and point meshes"); + return false; + } + + // what we can check, though, is if the mesh has normals and texture coordinates. That's a requirement + if (pMesh->mNormals == nullptr) { + ASSIMP_LOG_ERROR("Failed to compute tangents; need normals"); + return false; + } + if (configSourceUV >= AI_MAX_NUMBER_OF_TEXTURECOORDS || !pMesh->mTextureCoords[configSourceUV]) { + ASSIMP_LOG_ERROR("Failed to compute tangents; need UV data in channel", configSourceUV); + return false; + } + + const float angleEpsilon = 0.9999f; + + std::vector<bool> vertexDone(pMesh->mNumVertices, false); + const float qnan = get_qnan(); + + // create space for the tangents and bitangents + pMesh->mTangents = new aiVector3D[pMesh->mNumVertices]; + pMesh->mBitangents = new aiVector3D[pMesh->mNumVertices]; + + const aiVector3D *meshPos = pMesh->mVertices; + const aiVector3D *meshNorm = pMesh->mNormals; + const aiVector3D *meshTex = pMesh->mTextureCoords[configSourceUV]; + aiVector3D *meshTang = pMesh->mTangents; + aiVector3D *meshBitang = pMesh->mBitangents; + + // calculate the tangent and bitangent for every face + for (unsigned int a = 0; a < pMesh->mNumFaces; a++) { + const aiFace &face = pMesh->mFaces[a]; + if (face.mNumIndices < 3) { + // There are less than three indices, thus the tangent vector + // is not defined. We are finished with these vertices now, + // their tangent vectors are set to qnan. + for (unsigned int i = 0; i < face.mNumIndices; ++i) { + unsigned int idx = face.mIndices[i]; + vertexDone[idx] = true; + meshTang[idx] = aiVector3D(qnan); + meshBitang[idx] = aiVector3D(qnan); + } + + continue; + } + + // triangle or polygon... we always use only the first three indices. A polygon + // is supposed to be planar anyways.... + // FIXME: (thom) create correct calculation for multi-vertex polygons maybe? + const unsigned int p0 = face.mIndices[0], p1 = face.mIndices[1], p2 = face.mIndices[2]; + + // position differences p1->p2 and p1->p3 + aiVector3D v = meshPos[p1] - meshPos[p0], w = meshPos[p2] - meshPos[p0]; + + // texture offset p1->p2 and p1->p3 + float sx = meshTex[p1].x - meshTex[p0].x, sy = meshTex[p1].y - meshTex[p0].y; + float tx = meshTex[p2].x - meshTex[p0].x, ty = meshTex[p2].y - meshTex[p0].y; + float dirCorrection = (tx * sy - ty * sx) < 0.0f ? -1.0f : 1.0f; + // when t1, t2, t3 in same position in UV space, just use default UV direction. + if (sx * ty == sy * tx) { + sx = 0.0; + sy = 1.0; + tx = 1.0; + ty = 0.0; + } + + // tangent points in the direction where to positive X axis of the texture coord's would point in model space + // bitangent's points along the positive Y axis of the texture coord's, respectively + aiVector3D tangent, bitangent; + tangent.x = (w.x * sy - v.x * ty) * dirCorrection; + tangent.y = (w.y * sy - v.y * ty) * dirCorrection; + tangent.z = (w.z * sy - v.z * ty) * dirCorrection; + bitangent.x = (- w.x * sx + v.x * tx) * dirCorrection; + bitangent.y = (- w.y * sx + v.y * tx) * dirCorrection; + bitangent.z = (- w.z * sx + v.z * tx) * dirCorrection; + + // store for every vertex of that face + for (unsigned int b = 0; b < face.mNumIndices; ++b) { + unsigned int p = face.mIndices[b]; + + // project tangent and bitangent into the plane formed by the vertex' normal + aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]); + aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]) - localTangent * (bitangent * localTangent); + localTangent.NormalizeSafe(); + localBitangent.NormalizeSafe(); + + // reconstruct tangent/bitangent according to normal and bitangent/tangent when it's infinite or NaN. + bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z); + bool invalid_bitangent = is_special_float(localBitangent.x) || is_special_float(localBitangent.y) || is_special_float(localBitangent.z); + if (invalid_tangent != invalid_bitangent) { + if (invalid_tangent) { + localTangent = meshNorm[p] ^ localBitangent; + localTangent.NormalizeSafe(); + } else { + localBitangent = localTangent ^ meshNorm[p]; + localBitangent.NormalizeSafe(); + } + } + + // and write it into the mesh. + meshTang[p] = localTangent; + meshBitang[p] = localBitangent; + } + } + + // create a helper to quickly find locally close vertices among the vertex array + // FIX: check whether we can reuse the SpatialSort of a previous step + SpatialSort *vertexFinder = nullptr; + SpatialSort _vertexFinder; + float posEpsilon = 10e-6f; + if (shared) { + std::vector<std::pair<SpatialSort, float>> *avf; + shared->GetProperty(AI_SPP_SPATIAL_SORT, avf); + if (avf) { + std::pair<SpatialSort, float> &blubb = avf->operator[](meshIndex); + vertexFinder = &blubb.first; + posEpsilon = blubb.second; + ; + } + } + if (!vertexFinder) { + _vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof(aiVector3D)); + vertexFinder = &_vertexFinder; + posEpsilon = ComputePositionEpsilon(pMesh); + } + std::vector<unsigned int> verticesFound; + + const float fLimit = std::cos(configMaxAngle); + std::vector<unsigned int> closeVertices; + + // in the second pass we now smooth out all tangents and bitangents at the same local position + // if they are not too far off. + for (unsigned int a = 0; a < pMesh->mNumVertices; a++) { + if (vertexDone[a]) + continue; + + const aiVector3D &origPos = pMesh->mVertices[a]; + const aiVector3D &origNorm = pMesh->mNormals[a]; + const aiVector3D &origTang = pMesh->mTangents[a]; + const aiVector3D &origBitang = pMesh->mBitangents[a]; + closeVertices.resize(0); + + // find all vertices close to that position + vertexFinder->FindPositions(origPos, posEpsilon, verticesFound); + + closeVertices.reserve(verticesFound.size() + 5); + closeVertices.push_back(a); + + // look among them for other vertices sharing the same normal and a close-enough tangent/bitangent + for (unsigned int b = 0; b < verticesFound.size(); b++) { + unsigned int idx = verticesFound[b]; + if (vertexDone[idx]) + continue; + if (meshNorm[idx] * origNorm < angleEpsilon) + continue; + if (meshTang[idx] * origTang < fLimit) + continue; + if (meshBitang[idx] * origBitang < fLimit) + continue; + + // it's similar enough -> add it to the smoothing group + closeVertices.push_back(idx); + vertexDone[idx] = true; + } + + // smooth the tangents and bitangents of all vertices that were found to be close enough + aiVector3D smoothTangent(0, 0, 0), smoothBitangent(0, 0, 0); + for (unsigned int b = 0; b < closeVertices.size(); ++b) { + smoothTangent += meshTang[closeVertices[b]]; + smoothBitangent += meshBitang[closeVertices[b]]; + } + smoothTangent.Normalize(); + smoothBitangent.Normalize(); + + // and write it back into all affected tangents + for (unsigned int b = 0; b < closeVertices.size(); ++b) { + meshTang[closeVertices[b]] = smoothTangent; + meshBitang[closeVertices[b]] = smoothBitangent; + } + } + return true; +} diff --git a/libs/assimp/code/PostProcessing/CalcTangentsProcess.h b/libs/assimp/code/PostProcessing/CalcTangentsProcess.h new file mode 100644 index 0000000..018789b --- /dev/null +++ b/libs/assimp/code/PostProcessing/CalcTangentsProcess.h @@ -0,0 +1,117 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to calculate tangents and + bi-tangents on all imported meshes.*/ +#ifndef AI_CALCTANGENTSPROCESS_H_INC +#define AI_CALCTANGENTSPROCESS_H_INC + +#include "Common/BaseProcess.h" + +struct aiMesh; + +namespace Assimp +{ + +// --------------------------------------------------------------------------- +/** The CalcTangentsProcess calculates the tangent and bitangent for any vertex + * of all meshes. It is expected to be run before the JoinVerticesProcess runs + * because the joining of vertices also considers tangents and bitangents for + * uniqueness. + */ +class ASSIMP_API_WINONLY CalcTangentsProcess : public BaseProcess +{ +public: + + CalcTangentsProcess(); + ~CalcTangentsProcess(); + +public: + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag. + * @param pFlags The processing flags the importer was called with. + * A bitwise combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, + * false if not. + */ + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + /** Called prior to ExecuteOnScene(). + * The function is a request to the process to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer* pImp); + + + // setter for configMaxAngle + inline void SetMaxSmoothAngle(float f) + { + configMaxAngle =f; + } + +protected: + + // ------------------------------------------------------------------- + /** Calculates tangents and bitangents for a specific mesh. + * @param pMesh The mesh to process. + * @param meshIndex Index of the mesh + */ + bool ProcessMesh( aiMesh* pMesh, unsigned int meshIndex); + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + +private: + + /** Configuration option: maximum smoothing angle, in radians*/ + float configMaxAngle; + unsigned int configSourceUV; +}; + +} // end of namespace Assimp + +#endif // AI_CALCTANGENTSPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/ComputeUVMappingProcess.cpp b/libs/assimp/code/PostProcessing/ComputeUVMappingProcess.cpp new file mode 100644 index 0000000..c7456cc --- /dev/null +++ b/libs/assimp/code/PostProcessing/ComputeUVMappingProcess.cpp @@ -0,0 +1,506 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 GenUVCoords step */ + + +#include "ComputeUVMappingProcess.h" +#include "ProcessHelper.h" +#include <assimp/Exceptional.h> + +using namespace Assimp; + +namespace { + + const static aiVector3D base_axis_y(0.0,1.0,0.0); + const static aiVector3D base_axis_x(1.0,0.0,0.0); + const static aiVector3D base_axis_z(0.0,0.0,1.0); + const static ai_real angle_epsilon = ai_real( 0.95 ); +} + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +ComputeUVMappingProcess::ComputeUVMappingProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +ComputeUVMappingProcess::~ComputeUVMappingProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool ComputeUVMappingProcess::IsActive( unsigned int pFlags) const +{ + return (pFlags & aiProcess_GenUVCoords) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Check whether a ray intersects a plane and find the intersection point +inline bool PlaneIntersect(const aiRay& ray, const aiVector3D& planePos, + const aiVector3D& planeNormal, aiVector3D& pos) +{ + const ai_real b = planeNormal * (planePos - ray.pos); + ai_real h = ray.dir * planeNormal; + if ((h < 10e-5 && h > -10e-5) || (h = b/h) < 0) + return false; + + pos = ray.pos + (ray.dir * h); + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Find the first empty UV channel in a mesh +inline unsigned int FindEmptyUVChannel (aiMesh* mesh) +{ + for (unsigned int m = 0; m < AI_MAX_NUMBER_OF_TEXTURECOORDS;++m) + if (!mesh->mTextureCoords[m])return m; + + ASSIMP_LOG_ERROR("Unable to compute UV coordinates, no free UV slot found"); + return UINT_MAX; +} + +// ------------------------------------------------------------------------------------------------ +// Try to remove UV seams +void RemoveUVSeams (aiMesh* mesh, aiVector3D* out) +{ + // TODO: just a very rough algorithm. I think it could be done + // much easier, but I don't know how and am currently too tired to + // to think about a better solution. + + const static ai_real LOWER_LIMIT = ai_real( 0.1 ); + const static ai_real UPPER_LIMIT = ai_real( 0.9 ); + + const static ai_real LOWER_EPSILON = ai_real( 10e-3 ); + const static ai_real UPPER_EPSILON = ai_real( 1.0-10e-3 ); + + for (unsigned int fidx = 0; fidx < mesh->mNumFaces;++fidx) + { + const aiFace& face = mesh->mFaces[fidx]; + if (face.mNumIndices < 3) continue; // triangles and polygons only, please + + unsigned int smallV = face.mNumIndices, large = smallV; + bool zero = false, one = false, round_to_zero = false; + + // Check whether this face lies on a UV seam. We can just guess, + // but the assumption that a face with at least one very small + // on the one side and one very large U coord on the other side + // lies on a UV seam should work for most cases. + for (unsigned int n = 0; n < face.mNumIndices;++n) + { + if (out[face.mIndices[n]].x < LOWER_LIMIT) + { + smallV = n; + + // If we have a U value very close to 0 we can't + // round the others to 0, too. + if (out[face.mIndices[n]].x <= LOWER_EPSILON) + zero = true; + else round_to_zero = true; + } + if (out[face.mIndices[n]].x > UPPER_LIMIT) + { + large = n; + + // If we have a U value very close to 1 we can't + // round the others to 1, too. + if (out[face.mIndices[n]].x >= UPPER_EPSILON) + one = true; + } + } + if (smallV != face.mNumIndices && large != face.mNumIndices) + { + for (unsigned int n = 0; n < face.mNumIndices;++n) + { + // If the u value is over the upper limit and no other u + // value of that face is 0, round it to 0 + if (out[face.mIndices[n]].x > UPPER_LIMIT && !zero) + out[face.mIndices[n]].x = 0.0; + + // If the u value is below the lower limit and no other u + // value of that face is 1, round it to 1 + else if (out[face.mIndices[n]].x < LOWER_LIMIT && !one) + out[face.mIndices[n]].x = 1.0; + + // The face contains both 0 and 1 as UV coords. This can occur + // for faces which have an edge that lies directly on the seam. + // Due to numerical inaccuracies one U coord becomes 0, the + // other 1. But we do still have a third UV coord to determine + // to which side we must round to. + else if (one && zero) + { + if (round_to_zero && out[face.mIndices[n]].x >= UPPER_EPSILON) + out[face.mIndices[n]].x = 0.0; + else if (!round_to_zero && out[face.mIndices[n]].x <= LOWER_EPSILON) + out[face.mIndices[n]].x = 1.0; + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void ComputeUVMappingProcess::ComputeSphereMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out) +{ + aiVector3D center, min, max; + FindMeshCenter(mesh, center, min, max); + + // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ... + // currently the mapping axis will always be one of x,y,z, except if the + // PretransformVertices step is used (it transforms the meshes into worldspace, + // thus changing the mapping axis) + if (axis * base_axis_x >= angle_epsilon) { + + // For each point get a normalized projection vector in the sphere, + // get its longitude and latitude and map them to their respective + // UV axes. Problems occur around the poles ... unsolvable. + // + // The spherical coordinate system looks like this: + // x = cos(lon)*cos(lat) + // y = sin(lon)*cos(lat) + // z = sin(lat) + // + // Thus we can derive: + // lat = arcsin (z) + // lon = arctan (y/x) + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { + const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize(); + out[pnt] = aiVector3D((std::atan2(diff.z, diff.y) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F, + (std::asin (diff.x) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); + } + } + else if (axis * base_axis_y >= angle_epsilon) { + // ... just the same again + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { + const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize(); + out[pnt] = aiVector3D((std::atan2(diff.x, diff.z) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F, + (std::asin (diff.y) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); + } + } + else if (axis * base_axis_z >= angle_epsilon) { + // ... just the same again + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { + const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize(); + out[pnt] = aiVector3D((std::atan2(diff.y, diff.x) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F, + (std::asin (diff.z) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); + } + } + // slower code path in case the mapping axis is not one of the coordinate system axes + else { + aiMatrix4x4 mTrafo; + aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo); + + // again the same, except we're applying a transformation now + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { + const aiVector3D diff = ((mTrafo*mesh->mVertices[pnt])-center).Normalize(); + out[pnt] = aiVector3D((std::atan2(diff.y, diff.x) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F, + (std::asin(diff.z) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); + } + } + + + // Now find and remove UV seams. A seam occurs if a face has a tcoord + // close to zero on the one side, and a tcoord close to one on the + // other side. + RemoveUVSeams(mesh,out); +} + +// ------------------------------------------------------------------------------------------------ +void ComputeUVMappingProcess::ComputeCylinderMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out) +{ + aiVector3D center, min, max; + + // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ... + // currently the mapping axis will always be one of x,y,z, except if the + // PretransformVertices step is used (it transforms the meshes into worldspace, + // thus changing the mapping axis) + if (axis * base_axis_x >= angle_epsilon) { + FindMeshCenter(mesh, center, min, max); + const ai_real diff = max.x - min.x; + + // If the main axis is 'z', the z coordinate of a point 'p' is mapped + // directly to the texture V axis. The other axis is derived from + // the angle between ( p.x - c.x, p.y - c.y ) and (1,0), where + // 'c' is the center point of the mesh. + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { + const aiVector3D& pos = mesh->mVertices[pnt]; + aiVector3D& uv = out[pnt]; + + uv.y = (pos.x - min.x) / diff; + uv.x = (std::atan2( pos.z - center.z, pos.y - center.y) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI; + } + } + else if (axis * base_axis_y >= angle_epsilon) { + FindMeshCenter(mesh, center, min, max); + const ai_real diff = max.y - min.y; + + // just the same ... + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { + const aiVector3D& pos = mesh->mVertices[pnt]; + aiVector3D& uv = out[pnt]; + + uv.y = (pos.y - min.y) / diff; + uv.x = (std::atan2( pos.x - center.x, pos.z - center.z) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI; + } + } + else if (axis * base_axis_z >= angle_epsilon) { + FindMeshCenter(mesh, center, min, max); + const ai_real diff = max.z - min.z; + + // just the same ... + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { + const aiVector3D& pos = mesh->mVertices[pnt]; + aiVector3D& uv = out[pnt]; + + uv.y = (pos.z - min.z) / diff; + uv.x = (std::atan2( pos.y - center.y, pos.x - center.x) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI; + } + } + // slower code path in case the mapping axis is not one of the coordinate system axes + else { + aiMatrix4x4 mTrafo; + aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo); + FindMeshCenterTransformed(mesh, center, min, max,mTrafo); + const ai_real diff = max.y - min.y; + + // again the same, except we're applying a transformation now + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt){ + const aiVector3D pos = mTrafo* mesh->mVertices[pnt]; + aiVector3D& uv = out[pnt]; + + uv.y = (pos.y - min.y) / diff; + uv.x = (std::atan2( pos.x - center.x, pos.z - center.z) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI; + } + } + + // Now find and remove UV seams. A seam occurs if a face has a tcoord + // close to zero on the one side, and a tcoord close to one on the + // other side. + RemoveUVSeams(mesh,out); +} + +// ------------------------------------------------------------------------------------------------ +void ComputeUVMappingProcess::ComputePlaneMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out) +{ + ai_real diffu,diffv; + aiVector3D center, min, max; + + // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ... + // currently the mapping axis will always be one of x,y,z, except if the + // PretransformVertices step is used (it transforms the meshes into worldspace, + // thus changing the mapping axis) + if (axis * base_axis_x >= angle_epsilon) { + FindMeshCenter(mesh, center, min, max); + diffu = max.z - min.z; + diffv = max.y - min.y; + + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { + const aiVector3D& pos = mesh->mVertices[pnt]; + out[pnt].Set((pos.z - min.z) / diffu,(pos.y - min.y) / diffv,0.0); + } + } + else if (axis * base_axis_y >= angle_epsilon) { + FindMeshCenter(mesh, center, min, max); + diffu = max.x - min.x; + diffv = max.z - min.z; + + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { + const aiVector3D& pos = mesh->mVertices[pnt]; + out[pnt].Set((pos.x - min.x) / diffu,(pos.z - min.z) / diffv,0.0); + } + } + else if (axis * base_axis_z >= angle_epsilon) { + FindMeshCenter(mesh, center, min, max); + diffu = max.x - min.x; + diffv = max.y - min.y; + + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { + const aiVector3D& pos = mesh->mVertices[pnt]; + out[pnt].Set((pos.x - min.x) / diffu,(pos.y - min.y) / diffv,0.0); + } + } + // slower code path in case the mapping axis is not one of the coordinate system axes + else + { + aiMatrix4x4 mTrafo; + aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo); + FindMeshCenterTransformed(mesh, center, min, max,mTrafo); + diffu = max.x - min.x; + diffv = max.z - min.z; + + // again the same, except we're applying a transformation now + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { + const aiVector3D pos = mTrafo * mesh->mVertices[pnt]; + out[pnt].Set((pos.x - min.x) / diffu,(pos.z - min.z) / diffv,0.0); + } + } + + // shouldn't be necessary to remove UV seams ... +} + +// ------------------------------------------------------------------------------------------------ +void ComputeUVMappingProcess::ComputeBoxMapping( aiMesh*, aiVector3D* ) +{ + ASSIMP_LOG_ERROR("Mapping type currently not implemented"); +} + +// ------------------------------------------------------------------------------------------------ +void ComputeUVMappingProcess::Execute( aiScene* pScene) +{ + ASSIMP_LOG_DEBUG("GenUVCoordsProcess begin"); + char buffer[1024]; + + if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) + throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here"); + + std::list<MappingInfo> mappingStack; + + /* Iterate through all materials and search for non-UV mapped textures + */ + for (unsigned int i = 0; i < pScene->mNumMaterials;++i) + { + mappingStack.clear(); + aiMaterial* mat = pScene->mMaterials[i]; + for (unsigned int a = 0; a < mat->mNumProperties;++a) + { + aiMaterialProperty* prop = mat->mProperties[a]; + if (!::strcmp( prop->mKey.data, "$tex.mapping")) + { + aiTextureMapping& mapping = *((aiTextureMapping*)prop->mData); + if (aiTextureMapping_UV != mapping) + { + if (!DefaultLogger::isNullLogger()) + { + ai_snprintf(buffer, 1024, "Found non-UV mapped texture (%s,%u). Mapping type: %s", + TextureTypeToString((aiTextureType)prop->mSemantic),prop->mIndex, + MappingTypeToString(mapping)); + + ASSIMP_LOG_INFO(buffer); + } + + if (aiTextureMapping_OTHER == mapping) + continue; + + MappingInfo info (mapping); + + // Get further properties - currently only the major axis + for (unsigned int a2 = 0; a2 < mat->mNumProperties;++a2) + { + aiMaterialProperty* prop2 = mat->mProperties[a2]; + if (prop2->mSemantic != prop->mSemantic || prop2->mIndex != prop->mIndex) + continue; + + if ( !::strcmp( prop2->mKey.data, "$tex.mapaxis")) { + info.axis = *((aiVector3D*)prop2->mData); + break; + } + } + + unsigned int idx( 99999999 ); + + // Check whether we have this mapping mode already + std::list<MappingInfo>::iterator it = std::find (mappingStack.begin(),mappingStack.end(), info); + if (mappingStack.end() != it) + { + idx = (*it).uv; + } + else + { + /* We have found a non-UV mapped texture. Now + * we need to find all meshes using this material + * that we can compute UV channels for them. + */ + for (unsigned int m = 0; m < pScene->mNumMeshes;++m) + { + aiMesh* mesh = pScene->mMeshes[m]; + unsigned int outIdx = 0; + if ( mesh->mMaterialIndex != i || ( outIdx = FindEmptyUVChannel(mesh) ) == UINT_MAX || + !mesh->mNumVertices) + { + continue; + } + + // Allocate output storage + aiVector3D* p = mesh->mTextureCoords[outIdx] = new aiVector3D[mesh->mNumVertices]; + + switch (mapping) + { + case aiTextureMapping_SPHERE: + ComputeSphereMapping(mesh,info.axis,p); + break; + case aiTextureMapping_CYLINDER: + ComputeCylinderMapping(mesh,info.axis,p); + break; + case aiTextureMapping_PLANE: + ComputePlaneMapping(mesh,info.axis,p); + break; + case aiTextureMapping_BOX: + ComputeBoxMapping(mesh,p); + break; + default: + ai_assert(false); + } + if (m && idx != outIdx) + { + ASSIMP_LOG_WARN("UV index mismatch. Not all meshes assigned to " + "this material have equal numbers of UV channels. The UV index stored in " + "the material structure does therefore not apply for all meshes. "); + } + idx = outIdx; + } + info.uv = idx; + mappingStack.push_back(info); + } + + // Update the material property list + mapping = aiTextureMapping_UV; + ((aiMaterial*)mat)->AddProperty(&idx,1,AI_MATKEY_UVWSRC(prop->mSemantic,prop->mIndex)); + } + } + } + } + ASSIMP_LOG_DEBUG("GenUVCoordsProcess finished"); +} diff --git a/libs/assimp/code/PostProcessing/ComputeUVMappingProcess.h b/libs/assimp/code/PostProcessing/ComputeUVMappingProcess.h new file mode 100644 index 0000000..74744be --- /dev/null +++ b/libs/assimp/code/PostProcessing/ComputeUVMappingProcess.h @@ -0,0 +1,149 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to compute UV coordinates + from abstract mappings, such as box or spherical*/ +#ifndef AI_COMPUTEUVMAPPING_H_INC +#define AI_COMPUTEUVMAPPING_H_INC + +#include "Common/BaseProcess.h" + +#include <assimp/mesh.h> +#include <assimp/material.h> +#include <assimp/types.h> + +class ComputeUVMappingTest; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** ComputeUVMappingProcess - converts special mappings, such as spherical, + * cylindrical or boxed to proper UV coordinates for rendering. +*/ +class ComputeUVMappingProcess : public BaseProcess +{ +public: + ComputeUVMappingProcess(); + ~ComputeUVMappingProcess(); + +public: + + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag field. + * @param pFlags The processing flags the importer was called with. A bitwise + * combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, false if not. + */ + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + +protected: + + // ------------------------------------------------------------------- + /** Computes spherical UV coordinates for a mesh + * + * @param mesh Mesh to be processed + * @param axis Main axis + * @param out Receives output UV coordinates + */ + void ComputeSphereMapping(aiMesh* mesh,const aiVector3D& axis, + aiVector3D* out); + + // ------------------------------------------------------------------- + /** Computes cylindrical UV coordinates for a mesh + * + * @param mesh Mesh to be processed + * @param axis Main axis + * @param out Receives output UV coordinates + */ + void ComputeCylinderMapping(aiMesh* mesh,const aiVector3D& axis, + aiVector3D* out); + + // ------------------------------------------------------------------- + /** Computes planar UV coordinates for a mesh + * + * @param mesh Mesh to be processed + * @param axis Main axis + * @param out Receives output UV coordinates + */ + void ComputePlaneMapping(aiMesh* mesh,const aiVector3D& axis, + aiVector3D* out); + + // ------------------------------------------------------------------- + /** Computes cubic UV coordinates for a mesh + * + * @param mesh Mesh to be processed + * @param out Receives output UV coordinates + */ + void ComputeBoxMapping(aiMesh* mesh, aiVector3D* out); + +private: + + // temporary structure to describe a mapping + struct MappingInfo + { + explicit MappingInfo(aiTextureMapping _type) + : type (_type) + , axis (0.f,1.f,0.f) + , uv (0u) + {} + + aiTextureMapping type; + aiVector3D axis; + unsigned int uv; + + bool operator== (const MappingInfo& other) + { + return type == other.type && axis == other.axis; + } + }; +}; + +} // end of namespace Assimp + +#endif // AI_COMPUTEUVMAPPING_H_INC diff --git a/libs/assimp/code/PostProcessing/ConvertToLHProcess.cpp b/libs/assimp/code/PostProcessing/ConvertToLHProcess.cpp new file mode 100644 index 0000000..4ded0fc --- /dev/null +++ b/libs/assimp/code/PostProcessing/ConvertToLHProcess.cpp @@ -0,0 +1,384 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MakeLeftHandedProcess.cpp + * @brief Implementation of the post processing step to convert all + * imported data to a left-handed coordinate system. + * + * Face order & UV flip are also implemented here, for the sake of a + * better location. + */ + +#include "ConvertToLHProcess.h" +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> + +using namespace Assimp; + +#ifndef ASSIMP_BUILD_NO_MAKELEFTHANDED_PROCESS + +namespace { + +template <typename aiMeshType> +void flipUVs(aiMeshType *pMesh) { + if (pMesh == nullptr) { + return; + } + // mirror texture y coordinate + for (unsigned int tcIdx = 0; tcIdx < AI_MAX_NUMBER_OF_TEXTURECOORDS; tcIdx++) { + if (!pMesh->HasTextureCoords(tcIdx)) { + break; + } + + for (unsigned int vIdx = 0; vIdx < pMesh->mNumVertices; vIdx++) { + pMesh->mTextureCoords[tcIdx][vIdx].y = 1.0f - pMesh->mTextureCoords[tcIdx][vIdx].y; + } + } +} + +} // namespace + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +MakeLeftHandedProcess::MakeLeftHandedProcess() : + BaseProcess() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +MakeLeftHandedProcess::~MakeLeftHandedProcess() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool MakeLeftHandedProcess::IsActive(unsigned int pFlags) const { + return 0 != (pFlags & aiProcess_MakeLeftHanded); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void MakeLeftHandedProcess::Execute(aiScene *pScene) { + // Check for an existent root node to proceed + ai_assert(pScene->mRootNode != nullptr); + ASSIMP_LOG_DEBUG("MakeLeftHandedProcess begin"); + + // recursively convert all the nodes + ProcessNode(pScene->mRootNode, aiMatrix4x4()); + + // process the meshes accordingly + for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) { + ProcessMesh(pScene->mMeshes[a]); + } + + // process the materials accordingly + for (unsigned int a = 0; a < pScene->mNumMaterials; ++a) { + ProcessMaterial(pScene->mMaterials[a]); + } + + // transform all animation channels as well + for (unsigned int a = 0; a < pScene->mNumAnimations; a++) { + aiAnimation *anim = pScene->mAnimations[a]; + for (unsigned int b = 0; b < anim->mNumChannels; b++) { + aiNodeAnim *nodeAnim = anim->mChannels[b]; + ProcessAnimation(nodeAnim); + } + } + ASSIMP_LOG_DEBUG("MakeLeftHandedProcess finished"); +} + +// ------------------------------------------------------------------------------------------------ +// Recursively converts a node, all of its children and all of its meshes +void MakeLeftHandedProcess::ProcessNode(aiNode *pNode, const aiMatrix4x4 &pParentGlobalRotation) { + // mirror all base vectors at the local Z axis + pNode->mTransformation.c1 = -pNode->mTransformation.c1; + pNode->mTransformation.c2 = -pNode->mTransformation.c2; + pNode->mTransformation.c3 = -pNode->mTransformation.c3; + pNode->mTransformation.c4 = -pNode->mTransformation.c4; + + // now invert the Z axis again to keep the matrix determinant positive. + // The local meshes will be inverted accordingly so that the result should look just fine again. + pNode->mTransformation.a3 = -pNode->mTransformation.a3; + pNode->mTransformation.b3 = -pNode->mTransformation.b3; + pNode->mTransformation.c3 = -pNode->mTransformation.c3; + pNode->mTransformation.d3 = -pNode->mTransformation.d3; // useless, but anyways... + + // continue for all children + for (size_t a = 0; a < pNode->mNumChildren; ++a) { + ProcessNode(pNode->mChildren[a], pParentGlobalRotation * pNode->mTransformation); + } +} + +// ------------------------------------------------------------------------------------------------ +// Converts a single mesh to left handed coordinates. +void MakeLeftHandedProcess::ProcessMesh(aiMesh *pMesh) { + if (nullptr == pMesh) { + ASSIMP_LOG_ERROR("Nullptr to mesh found."); + return; + } + // mirror positions, normals and stuff along the Z axis + for (size_t a = 0; a < pMesh->mNumVertices; ++a) { + pMesh->mVertices[a].z *= -1.0f; + if (pMesh->HasNormals()) { + pMesh->mNormals[a].z *= -1.0f; + } + if (pMesh->HasTangentsAndBitangents()) { + pMesh->mTangents[a].z *= -1.0f; + pMesh->mBitangents[a].z *= -1.0f; + } + } + + // mirror anim meshes positions, normals and stuff along the Z axis + for (size_t m = 0; m < pMesh->mNumAnimMeshes; ++m) { + for (size_t a = 0; a < pMesh->mAnimMeshes[m]->mNumVertices; ++a) { + pMesh->mAnimMeshes[m]->mVertices[a].z *= -1.0f; + if (pMesh->mAnimMeshes[m]->HasNormals()) { + pMesh->mAnimMeshes[m]->mNormals[a].z *= -1.0f; + } + if (pMesh->mAnimMeshes[m]->HasTangentsAndBitangents()) { + pMesh->mAnimMeshes[m]->mTangents[a].z *= -1.0f; + pMesh->mAnimMeshes[m]->mBitangents[a].z *= -1.0f; + } + } + } + + // mirror offset matrices of all bones + for (size_t a = 0; a < pMesh->mNumBones; ++a) { + aiBone *bone = pMesh->mBones[a]; + bone->mOffsetMatrix.a3 = -bone->mOffsetMatrix.a3; + bone->mOffsetMatrix.b3 = -bone->mOffsetMatrix.b3; + bone->mOffsetMatrix.d3 = -bone->mOffsetMatrix.d3; + bone->mOffsetMatrix.c1 = -bone->mOffsetMatrix.c1; + bone->mOffsetMatrix.c2 = -bone->mOffsetMatrix.c2; + bone->mOffsetMatrix.c4 = -bone->mOffsetMatrix.c4; + } + + // mirror bitangents as well as they're derived from the texture coords + if (pMesh->HasTangentsAndBitangents()) { + for (unsigned int a = 0; a < pMesh->mNumVertices; a++) + pMesh->mBitangents[a] *= -1.0f; + } +} + +// ------------------------------------------------------------------------------------------------ +// Converts a single material to left handed coordinates. +void MakeLeftHandedProcess::ProcessMaterial(aiMaterial *_mat) { + if (nullptr == _mat) { + ASSIMP_LOG_ERROR("Nullptr to aiMaterial found."); + return; + } + + aiMaterial *mat = (aiMaterial *)_mat; + for (unsigned int a = 0; a < mat->mNumProperties; ++a) { + aiMaterialProperty *prop = mat->mProperties[a]; + + // Mapping axis for UV mappings? + if (!::strcmp(prop->mKey.data, "$tex.mapaxis")) { + ai_assert(prop->mDataLength >= sizeof(aiVector3D)); // something is wrong with the validation if we end up here + aiVector3D *pff = (aiVector3D *)prop->mData; + pff->z *= -1.f; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Converts the given animation to LH coordinates. +void MakeLeftHandedProcess::ProcessAnimation(aiNodeAnim *pAnim) { + // position keys + for (unsigned int a = 0; a < pAnim->mNumPositionKeys; a++) + pAnim->mPositionKeys[a].mValue.z *= -1.0f; + + // rotation keys + for (unsigned int a = 0; a < pAnim->mNumRotationKeys; a++) { + /* That's the safe version, but the float errors add up. So we try the short version instead + aiMatrix3x3 rotmat = pAnim->mRotationKeys[a].mValue.GetMatrix(); + rotmat.a3 = -rotmat.a3; rotmat.b3 = -rotmat.b3; + rotmat.c1 = -rotmat.c1; rotmat.c2 = -rotmat.c2; + aiQuaternion rotquat( rotmat); + pAnim->mRotationKeys[a].mValue = rotquat; + */ + pAnim->mRotationKeys[a].mValue.x *= -1.0f; + pAnim->mRotationKeys[a].mValue.y *= -1.0f; + } +} + +#endif // !! ASSIMP_BUILD_NO_MAKELEFTHANDED_PROCESS +#ifndef ASSIMP_BUILD_NO_FLIPUVS_PROCESS +// # FlipUVsProcess + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +FlipUVsProcess::FlipUVsProcess() {} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +FlipUVsProcess::~FlipUVsProcess() {} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool FlipUVsProcess::IsActive(unsigned int pFlags) const { + return 0 != (pFlags & aiProcess_FlipUVs); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void FlipUVsProcess::Execute(aiScene *pScene) { + ASSIMP_LOG_DEBUG("FlipUVsProcess begin"); + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) + ProcessMesh(pScene->mMeshes[i]); + + for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) + ProcessMaterial(pScene->mMaterials[i]); + ASSIMP_LOG_DEBUG("FlipUVsProcess finished"); +} + +// ------------------------------------------------------------------------------------------------ +// Converts a single material +void FlipUVsProcess::ProcessMaterial(aiMaterial *_mat) { + aiMaterial *mat = (aiMaterial *)_mat; + for (unsigned int a = 0; a < mat->mNumProperties; ++a) { + aiMaterialProperty *prop = mat->mProperties[a]; + if (!prop) { + ASSIMP_LOG_VERBOSE_DEBUG("Property is null"); + continue; + } + + // UV transformation key? + if (!::strcmp(prop->mKey.data, "$tex.uvtrafo")) { + ai_assert(prop->mDataLength >= sizeof(aiUVTransform)); // something is wrong with the validation if we end up here + aiUVTransform *uv = (aiUVTransform *)prop->mData; + + // just flip it, that's everything + uv->mTranslation.y *= -1.f; + uv->mRotation *= -1.f; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Converts a single mesh +void FlipUVsProcess::ProcessMesh(aiMesh *pMesh) { + flipUVs(pMesh); + for (unsigned int idx = 0; idx < pMesh->mNumAnimMeshes; idx++) { + flipUVs(pMesh->mAnimMeshes[idx]); + } +} + +#endif // !ASSIMP_BUILD_NO_FLIPUVS_PROCESS +#ifndef ASSIMP_BUILD_NO_FLIPWINDING_PROCESS +// # FlipWindingOrderProcess + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +FlipWindingOrderProcess::FlipWindingOrderProcess() {} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +FlipWindingOrderProcess::~FlipWindingOrderProcess() {} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool FlipWindingOrderProcess::IsActive(unsigned int pFlags) const { + return 0 != (pFlags & aiProcess_FlipWindingOrder); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void FlipWindingOrderProcess::Execute(aiScene *pScene) { + ASSIMP_LOG_DEBUG("FlipWindingOrderProcess begin"); + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) + ProcessMesh(pScene->mMeshes[i]); + ASSIMP_LOG_DEBUG("FlipWindingOrderProcess finished"); +} + +// ------------------------------------------------------------------------------------------------ +// Converts a single mesh +void FlipWindingOrderProcess::ProcessMesh(aiMesh *pMesh) { + // invert the order of all faces in this mesh + for (unsigned int a = 0; a < pMesh->mNumFaces; a++) { + aiFace &face = pMesh->mFaces[a]; + for (unsigned int b = 0; b < face.mNumIndices / 2; b++) { + std::swap(face.mIndices[b], face.mIndices[face.mNumIndices - 1 - b]); + } + } + + // invert the order of all components in this mesh anim meshes + for (unsigned int m = 0; m < pMesh->mNumAnimMeshes; m++) { + aiAnimMesh *animMesh = pMesh->mAnimMeshes[m]; + unsigned int numVertices = animMesh->mNumVertices; + if (animMesh->HasPositions()) { + for (unsigned int a = 0; a < numVertices; a++) { + std::swap(animMesh->mVertices[a], animMesh->mVertices[numVertices - 1 - a]); + } + } + if (animMesh->HasNormals()) { + for (unsigned int a = 0; a < numVertices; a++) { + std::swap(animMesh->mNormals[a], animMesh->mNormals[numVertices - 1 - a]); + } + } + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; i++) { + if (animMesh->HasTextureCoords(i)) { + for (unsigned int a = 0; a < numVertices; a++) { + std::swap(animMesh->mTextureCoords[i][a], animMesh->mTextureCoords[i][numVertices - 1 - a]); + } + } + } + if (animMesh->HasTangentsAndBitangents()) { + for (unsigned int a = 0; a < numVertices; a++) { + std::swap(animMesh->mTangents[a], animMesh->mTangents[numVertices - 1 - a]); + std::swap(animMesh->mBitangents[a], animMesh->mBitangents[numVertices - 1 - a]); + } + } + for (unsigned int v = 0; v < AI_MAX_NUMBER_OF_COLOR_SETS; v++) { + if (animMesh->HasVertexColors(v)) { + for (unsigned int a = 0; a < numVertices; a++) { + std::swap(animMesh->mColors[v][a], animMesh->mColors[v][numVertices - 1 - a]); + } + } + } + } +} + +#endif // !! ASSIMP_BUILD_NO_FLIPWINDING_PROCESS diff --git a/libs/assimp/code/PostProcessing/ConvertToLHProcess.h b/libs/assimp/code/PostProcessing/ConvertToLHProcess.h new file mode 100644 index 0000000..474056c --- /dev/null +++ b/libs/assimp/code/PostProcessing/ConvertToLHProcess.h @@ -0,0 +1,172 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 MakeLeftHandedProcess.h + * @brief Defines a bunch of post-processing steps to handle + * coordinate system conversions. + * + * - LH to RH + * - UV origin upper-left to lower-left + * - face order cw to ccw + */ +#ifndef AI_CONVERTTOLHPROCESS_H_INC +#define AI_CONVERTTOLHPROCESS_H_INC + +#include <assimp/types.h> + +#include "Common/BaseProcess.h" + +struct aiMesh; +struct aiNodeAnim; +struct aiNode; +struct aiMaterial; + +namespace Assimp { + +// ----------------------------------------------------------------------------------- +/** @brief The MakeLeftHandedProcess converts all imported data to a left-handed + * coordinate system. + * + * This implies a mirroring of the Z axis of the coordinate system. But to keep + * transformation matrices free from reflections we shift the reflection to other + * places. We mirror the meshes and adapt the rotations. + * + * @note RH-LH and LH-RH is the same, so this class can be used for both + */ +class MakeLeftHandedProcess : public BaseProcess +{ + + +public: + MakeLeftHandedProcess(); + ~MakeLeftHandedProcess(); + + // ------------------------------------------------------------------- + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + void Execute( aiScene* pScene); + +protected: + + // ------------------------------------------------------------------- + /** Recursively converts a node and all of its children + */ + void ProcessNode( aiNode* pNode, const aiMatrix4x4& pParentGlobalRotation); + + // ------------------------------------------------------------------- + /** Converts a single mesh to left handed coordinates. + * This means that positions, normals and tangents are mirrored at + * the local Z axis and the order of all faces are inverted. + * @param pMesh The mesh to convert. + */ + void ProcessMesh( aiMesh* pMesh); + + // ------------------------------------------------------------------- + /** Converts a single material to left-handed coordinates + * @param pMat Material to convert + */ + void ProcessMaterial( aiMaterial* pMat); + + // ------------------------------------------------------------------- + /** Converts the given animation to LH coordinates. + * The rotation and translation keys are transformed, the scale keys + * work in local space and can therefore be left untouched. + * @param pAnim The bone animation to transform + */ + void ProcessAnimation( aiNodeAnim* pAnim); +}; + + +// --------------------------------------------------------------------------- +/** Postprocessing step to flip the face order of the imported data + */ +class FlipWindingOrderProcess : public BaseProcess +{ + friend class Importer; + +public: + /** Constructor to be privately used by Importer */ + FlipWindingOrderProcess(); + + /** Destructor, private as well */ + ~FlipWindingOrderProcess(); + + // ------------------------------------------------------------------- + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + void Execute( aiScene* pScene); + +public: + /** Some other types of post-processing require winding order flips */ + static void ProcessMesh( aiMesh* pMesh); +}; + +// --------------------------------------------------------------------------- +/** Postprocessing step to flip the UV coordinate system of the import data + */ +class FlipUVsProcess : public BaseProcess +{ + friend class Importer; + +public: + /** Constructor to be privately used by Importer */ + FlipUVsProcess(); + + /** Destructor, private as well */ + ~FlipUVsProcess(); + + // ------------------------------------------------------------------- + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + void Execute( aiScene* pScene); + +protected: + void ProcessMesh( aiMesh* pMesh); + void ProcessMaterial( aiMaterial* mat); +}; + +} // end of namespace Assimp + +#endif // AI_CONVERTTOLHPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/DeboneProcess.cpp b/libs/assimp/code/PostProcessing/DeboneProcess.cpp new file mode 100644 index 0000000..e8cfb16 --- /dev/null +++ b/libs/assimp/code/PostProcessing/DeboneProcess.cpp @@ -0,0 +1,466 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 DeboneProcess.cpp +/** Implementation of the DeboneProcess post processing step */ + + + +// internal headers of the post-processing framework +#include "ProcessHelper.h" +#include "DeboneProcess.h" +#include <stdio.h> + + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +DeboneProcess::DeboneProcess() +{ + mNumBones = 0; + mNumBonesCanDoWithout = 0; + + mThreshold = AI_DEBONE_THRESHOLD; + mAllOrNone = false; +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +DeboneProcess::~DeboneProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool DeboneProcess::IsActive( unsigned int pFlags) const +{ + return (pFlags & aiProcess_Debone) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void DeboneProcess::SetupProperties(const Importer* pImp) +{ + // get the current value of the property + mAllOrNone = pImp->GetPropertyInteger(AI_CONFIG_PP_DB_ALL_OR_NONE,0)?true:false; + mThreshold = pImp->GetPropertyFloat(AI_CONFIG_PP_DB_THRESHOLD,AI_DEBONE_THRESHOLD); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void DeboneProcess::Execute( aiScene* pScene) +{ + ASSIMP_LOG_DEBUG("DeboneProcess begin"); + + if(!pScene->mNumMeshes) { + return; + } + + std::vector<bool> splitList(pScene->mNumMeshes); + for( unsigned int a = 0; a < pScene->mNumMeshes; a++) { + splitList[a] = ConsiderMesh( pScene->mMeshes[a] ); + } + + int numSplits = 0; + + if(!!mNumBonesCanDoWithout && (!mAllOrNone||mNumBonesCanDoWithout==mNumBones)) { + for(unsigned int a = 0; a < pScene->mNumMeshes; a++) { + if(splitList[a]) { + numSplits++; + } + } + } + + if(numSplits) { + // we need to do something. Let's go. + //mSubMeshIndices.clear(); // really needed? + mSubMeshIndices.resize(pScene->mNumMeshes); // because we're doing it here anyway + + // build a new array of meshes for the scene + std::vector<aiMesh*> meshes; + + for(unsigned int a=0;a<pScene->mNumMeshes;a++) + { + aiMesh* srcMesh = pScene->mMeshes[a]; + + std::vector<std::pair<aiMesh*,const aiBone*> > newMeshes; + + if(splitList[a]) { + SplitMesh(srcMesh,newMeshes); + } + + // mesh was split + if(!newMeshes.empty()) { + unsigned int out = 0, in = srcMesh->mNumBones; + + // store new meshes and indices of the new meshes + for(unsigned int b=0;b<newMeshes.size();b++) { + const aiString *find = newMeshes[b].second?&newMeshes[b].second->mName:0; + + aiNode *theNode = find?pScene->mRootNode->FindNode(*find):0; + std::pair<unsigned int,aiNode*> push_pair(static_cast<unsigned int>(meshes.size()),theNode); + + mSubMeshIndices[a].push_back(push_pair); + meshes.push_back(newMeshes[b].first); + + out+=newMeshes[b].first->mNumBones; + } + + if(!DefaultLogger::isNullLogger()) { + ASSIMP_LOG_INFO("Removed %u bones. Input bones:", in - out, ". Output bones: ", out); + } + + // and destroy the source mesh. It should be completely contained inside the new submeshes + delete srcMesh; + } + else { + // Mesh is kept unchanged - store it's new place in the mesh array + mSubMeshIndices[a].push_back(std::pair<unsigned int,aiNode*>(static_cast<unsigned int>(meshes.size()),(aiNode*)0)); + meshes.push_back(srcMesh); + } + } + + // rebuild the scene's mesh array + pScene->mNumMeshes = static_cast<unsigned int>(meshes.size()); + delete [] pScene->mMeshes; + pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; + std::copy( meshes.begin(), meshes.end(), pScene->mMeshes); + + // recurse through all nodes and translate the node's mesh indices to fit the new mesh array + UpdateNode( pScene->mRootNode); + } + + ASSIMP_LOG_DEBUG("DeboneProcess end"); +} + +// ------------------------------------------------------------------------------------------------ +// Counts bones total/removable in a given mesh. +bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) +{ + if(!pMesh->HasBones()) { + return false; + } + + bool split = false; + + //interstitial faces not permitted + bool isInterstitialRequired = false; + + std::vector<bool> isBoneNecessary(pMesh->mNumBones,false); + std::vector<unsigned int> vertexBones(pMesh->mNumVertices,UINT_MAX); + + const unsigned int cUnowned = UINT_MAX; + const unsigned int cCoowned = UINT_MAX-1; + + for(unsigned int i=0;i<pMesh->mNumBones;i++) { + for(unsigned int j=0;j<pMesh->mBones[i]->mNumWeights;j++) { + float w = pMesh->mBones[i]->mWeights[j].mWeight; + + if(w==0.0f) { + continue; + } + + unsigned int vid = pMesh->mBones[i]->mWeights[j].mVertexId; + if(w>=mThreshold) { + + if(vertexBones[vid]!=cUnowned) { + if(vertexBones[vid]==i) //double entry + { + ASSIMP_LOG_WARN("Encountered double entry in bone weights"); + } + else //TODO: track attraction in order to break tie + { + vertexBones[vid] = cCoowned; + } + } + else vertexBones[vid] = i; + } + + if(!isBoneNecessary[i]) { + isBoneNecessary[i] = w<mThreshold; + } + } + + if(!isBoneNecessary[i]) { + isInterstitialRequired = true; + } + } + + if(isInterstitialRequired) { + for(unsigned int i=0;i<pMesh->mNumFaces;i++) { + unsigned int v = vertexBones[pMesh->mFaces[i].mIndices[0]]; + + for(unsigned int j=1;j<pMesh->mFaces[i].mNumIndices;j++) { + unsigned int w = vertexBones[pMesh->mFaces[i].mIndices[j]]; + + if(v!=w) { + if(v<pMesh->mNumBones) isBoneNecessary[v] = true; + if(w<pMesh->mNumBones) isBoneNecessary[w] = true; + } + } + } + } + + for(unsigned int i=0;i<pMesh->mNumBones;i++) { + if(!isBoneNecessary[i]) { + mNumBonesCanDoWithout++; + split = true; + } + + mNumBones++; + } + return split; +} + +// ------------------------------------------------------------------------------------------------ +// Splits the given mesh by bone count. +void DeboneProcess::SplitMesh( const aiMesh* pMesh, std::vector< std::pair< aiMesh*,const aiBone* > >& poNewMeshes) const +{ + // same deal here as ConsiderMesh basically + + std::vector<bool> isBoneNecessary(pMesh->mNumBones,false); + std::vector<unsigned int> vertexBones(pMesh->mNumVertices,UINT_MAX); + + const unsigned int cUnowned = UINT_MAX; + const unsigned int cCoowned = UINT_MAX-1; + + for(unsigned int i=0;i<pMesh->mNumBones;i++) { + for(unsigned int j=0;j<pMesh->mBones[i]->mNumWeights;j++) { + float w = pMesh->mBones[i]->mWeights[j].mWeight; + + if(w==0.0f) { + continue; + } + + unsigned int vid = pMesh->mBones[i]->mWeights[j].mVertexId; + + if(w>=mThreshold) { + if(vertexBones[vid]!=cUnowned) { + if(vertexBones[vid]==i) //double entry + { + ASSIMP_LOG_WARN("Encountered double entry in bone weights"); + } + else //TODO: track attraction in order to break tie + { + vertexBones[vid] = cCoowned; + } + } + else vertexBones[vid] = i; + } + + if(!isBoneNecessary[i]) { + isBoneNecessary[i] = w<mThreshold; + } + } + } + + unsigned int nFacesUnowned = 0; + + std::vector<unsigned int> faceBones(pMesh->mNumFaces,UINT_MAX); + std::vector<unsigned int> facesPerBone(pMesh->mNumBones,0); + + for(unsigned int i=0;i<pMesh->mNumFaces;i++) { + unsigned int nInterstitial = 1; + + unsigned int v = vertexBones[pMesh->mFaces[i].mIndices[0]]; + + for(unsigned int j=1;j<pMesh->mFaces[i].mNumIndices;j++) { + unsigned int w = vertexBones[pMesh->mFaces[i].mIndices[j]]; + + if(v!=w) { + if(v<pMesh->mNumBones) isBoneNecessary[v] = true; + if(w<pMesh->mNumBones) isBoneNecessary[w] = true; + } + else nInterstitial++; + } + + if(v<pMesh->mNumBones &&nInterstitial==pMesh->mFaces[i].mNumIndices) { + faceBones[i] = v; //primitive belongs to bone #v + facesPerBone[v]++; + } + else nFacesUnowned++; + } + + // invalidate any "cojoined" faces + for(unsigned int i=0;i<pMesh->mNumFaces;i++) { + if(faceBones[i]<pMesh->mNumBones&&isBoneNecessary[faceBones[i]]) + { + ai_assert(facesPerBone[faceBones[i]]>0); + facesPerBone[faceBones[i]]--; + + nFacesUnowned++; + faceBones[i] = cUnowned; + } + } + + if(nFacesUnowned) { + std::vector<unsigned int> subFaces; + + for(unsigned int i=0;i<pMesh->mNumFaces;i++) { + if(faceBones[i]==cUnowned) { + subFaces.push_back(i); + } + } + + aiMesh *baseMesh = MakeSubmesh(pMesh,subFaces,0); + std::pair<aiMesh*,const aiBone*> push_pair(baseMesh,(const aiBone*)0); + + poNewMeshes.push_back(push_pair); + } + + for(unsigned int i=0;i<pMesh->mNumBones;i++) { + + if(!isBoneNecessary[i]&&facesPerBone[i]>0) { + std::vector<unsigned int> subFaces; + + for(unsigned int j=0;j<pMesh->mNumFaces;j++) { + if(faceBones[j]==i) { + subFaces.push_back(j); + } + } + + unsigned int f = AI_SUBMESH_FLAGS_SANS_BONES; + aiMesh *subMesh =MakeSubmesh(pMesh,subFaces,f); + + //Lifted from PretransformVertices.cpp + ApplyTransform(subMesh,pMesh->mBones[i]->mOffsetMatrix); + std::pair<aiMesh*,const aiBone*> push_pair(subMesh,pMesh->mBones[i]); + + poNewMeshes.push_back(push_pair); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Recursively updates the node's mesh list to account for the changed mesh list +void DeboneProcess::UpdateNode(aiNode* pNode) const +{ + // rebuild the node's mesh index list + + std::vector<unsigned int> newMeshList; + + // this will require two passes + + unsigned int m = static_cast<unsigned int>(pNode->mNumMeshes), n = static_cast<unsigned int>(mSubMeshIndices.size()); + + // first pass, look for meshes which have not moved + + for(unsigned int a=0;a<m;a++) { + + unsigned int srcIndex = pNode->mMeshes[a]; + const std::vector< std::pair< unsigned int,aiNode* > > &subMeshes = mSubMeshIndices[srcIndex]; + unsigned int nSubmeshes = static_cast<unsigned int>(subMeshes.size()); + + for(unsigned int b=0;b<nSubmeshes;b++) { + if(!subMeshes[b].second) { + newMeshList.push_back(subMeshes[b].first); + } + } + } + + // second pass, collect deboned meshes + + for(unsigned int a=0;a<n;a++) + { + const std::vector< std::pair< unsigned int,aiNode* > > &subMeshes = mSubMeshIndices[a]; + unsigned int nSubmeshes = static_cast<unsigned int>(subMeshes.size()); + + for(unsigned int b=0;b<nSubmeshes;b++) { + if(subMeshes[b].second == pNode) { + newMeshList.push_back(subMeshes[b].first); + } + } + } + + if( pNode->mNumMeshes > 0 ) { + delete[] pNode->mMeshes; + pNode->mMeshes = nullptr; + } + + pNode->mNumMeshes = static_cast<unsigned int>(newMeshList.size()); + + if(pNode->mNumMeshes) { + pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; + std::copy( newMeshList.begin(), newMeshList.end(), pNode->mMeshes); + } + + // do that also recursively for all children + for( unsigned int a = 0; a < pNode->mNumChildren; ++a ) { + UpdateNode( pNode->mChildren[a]); + } +} + +// ------------------------------------------------------------------------------------------------ +// Apply the node transformation to a mesh +void DeboneProcess::ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat)const +{ + // Check whether we need to transform the coordinates at all + if (!mat.IsIdentity()) { + + if (mesh->HasPositions()) { + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + mesh->mVertices[i] = mat * mesh->mVertices[i]; + } + } + if (mesh->HasNormals() || mesh->HasTangentsAndBitangents()) { + aiMatrix4x4 mWorldIT = mat; + mWorldIT.Inverse().Transpose(); + + // TODO: implement Inverse() for aiMatrix3x3 + aiMatrix3x3 m = aiMatrix3x3(mWorldIT); + + if (mesh->HasNormals()) { + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + mesh->mNormals[i] = (m * mesh->mNormals[i]).Normalize(); + } + } + if (mesh->HasTangentsAndBitangents()) { + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + mesh->mTangents[i] = (m * mesh->mTangents[i]).Normalize(); + mesh->mBitangents[i] = (m * mesh->mBitangents[i]).Normalize(); + } + } + } + } +} diff --git a/libs/assimp/code/PostProcessing/DeboneProcess.h b/libs/assimp/code/PostProcessing/DeboneProcess.h new file mode 100644 index 0000000..cb072b7 --- /dev/null +++ b/libs/assimp/code/PostProcessing/DeboneProcess.h @@ -0,0 +1,131 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +/** Defines a post processing step to limit the number of bones affecting a single vertex. */ +#ifndef AI_DEBONEPROCESS_H_INC +#define AI_DEBONEPROCESS_H_INC + +#include "Common/BaseProcess.h" + +#include <assimp/mesh.h> +#include <assimp/scene.h> + +#include <vector> +#include <utility> + +#// Forward declarations +class DeboneTest; + +namespace Assimp { + +#if (!defined AI_DEBONE_THRESHOLD) +# define AI_DEBONE_THRESHOLD 1.0f +#endif // !! AI_DEBONE_THRESHOLD + +// --------------------------------------------------------------------------- +/** This post processing step removes bones nearly losslessly or according to +* a configured threshold. In order to remove the bone, the primitives affected by +* the bone are split from the mesh. The split off (new) mesh is boneless. At any +* point in time, bones without affect upon a given mesh are to be removed. +*/ +class DeboneProcess : public BaseProcess { +public: + DeboneProcess(); + ~DeboneProcess(); + + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag. + * @param pFlags The processing flags the importer was called with. + * A bitwise combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, + * false if not. + */ + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + /** Called prior to ExecuteOnScene(). + * The function is a request to the process to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer* pImp); + +protected: + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + /** Counts bones total/removable in a given mesh. + * @param pMesh The mesh to process. + */ + bool ConsiderMesh( const aiMesh* pMesh); + + /// Splits the given mesh by bone count. + /// @param pMesh the Mesh to split. Is not changed at all, but might be superfluous in case it was split. + /// @param poNewMeshes Array of submeshes created in the process. Empty if splitting was not necessary. + void SplitMesh(const aiMesh* pMesh, std::vector< std::pair< aiMesh*,const aiBone* > >& poNewMeshes) const; + + /// Recursively updates the node's mesh list to account for the changed mesh list + void UpdateNode(aiNode* pNode) const; + + // ------------------------------------------------------------------- + // Apply transformation to a mesh + void ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat)const; + +public: + /** Number of bones present in the scene. */ + unsigned int mNumBones; + unsigned int mNumBonesCanDoWithout; + + float mThreshold; + bool mAllOrNone; + + /// Per mesh index: Array of indices of the new submeshes. + std::vector< std::vector< std::pair< unsigned int,aiNode* > > > mSubMeshIndices; +}; + +} // end of namespace Assimp + +#endif // AI_DEBONEPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/DropFaceNormalsProcess.cpp b/libs/assimp/code/PostProcessing/DropFaceNormalsProcess.cpp new file mode 100644 index 0000000..0cf17f9 --- /dev/null +++ b/libs/assimp/code/PostProcessing/DropFaceNormalsProcess.cpp @@ -0,0 +1,111 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the post processing step to drop face +* normals for all imported faces. +*/ + + +#include "DropFaceNormalsProcess.h" +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Exceptional.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +DropFaceNormalsProcess::DropFaceNormalsProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +DropFaceNormalsProcess::~DropFaceNormalsProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool DropFaceNormalsProcess::IsActive( unsigned int pFlags) const { + return (pFlags & aiProcess_DropNormals) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void DropFaceNormalsProcess::Execute( aiScene* pScene) { + ASSIMP_LOG_DEBUG("DropFaceNormalsProcess begin"); + + if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) { + throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here"); + } + + bool bHas = false; + for( unsigned int a = 0; a < pScene->mNumMeshes; a++) { + bHas |= this->DropMeshFaceNormals( pScene->mMeshes[a]); + } + if (bHas) { + ASSIMP_LOG_INFO("DropFaceNormalsProcess finished. " + "Face normals have been removed"); + } else { + ASSIMP_LOG_DEBUG("DropFaceNormalsProcess finished. " + "No normals were present"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +bool DropFaceNormalsProcess::DropMeshFaceNormals (aiMesh* mesh) { + ai_assert(nullptr != mesh); + + if (nullptr == mesh->mNormals) { + return false; + } + + delete[] mesh->mNormals; + mesh->mNormals = nullptr; + return true; +} diff --git a/libs/assimp/code/PostProcessing/DropFaceNormalsProcess.h b/libs/assimp/code/PostProcessing/DropFaceNormalsProcess.h new file mode 100644 index 0000000..50abdc7 --- /dev/null +++ b/libs/assimp/code/PostProcessing/DropFaceNormalsProcess.h @@ -0,0 +1,83 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to compute face normals for all loaded faces*/ +#ifndef AI_DROPFACENORMALPROCESS_H_INC +#define AI_DROPFACENORMALPROCESS_H_INC + +#include "Common/BaseProcess.h" + +#include <assimp/mesh.h> + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** The DropFaceNormalsProcess computes face normals for all faces of all meshes +*/ +class ASSIMP_API_WINONLY DropFaceNormalsProcess : public BaseProcess { +public: + DropFaceNormalsProcess(); + ~DropFaceNormalsProcess(); + + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag field. + * @param pFlags The processing flags the importer was called with. A bitwise + * combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, false if not. + */ + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + + +private: + bool DropMeshFaceNormals(aiMesh* pcMesh); +}; + +} // end of namespace Assimp + +#endif // !!AI_DROPFACENORMALPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/EmbedTexturesProcess.cpp b/libs/assimp/code/PostProcessing/EmbedTexturesProcess.cpp new file mode 100644 index 0000000..2f80c90 --- /dev/null +++ b/libs/assimp/code/PostProcessing/EmbedTexturesProcess.cpp @@ -0,0 +1,161 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#include "EmbedTexturesProcess.h" +#include <assimp/IOStream.hpp> +#include <assimp/IOSystem.hpp> +#include <assimp/ParsingUtils.h> +#include "ProcessHelper.h" + +#include <fstream> + +using namespace Assimp; + +EmbedTexturesProcess::EmbedTexturesProcess() : + BaseProcess() { + // empty +} + +EmbedTexturesProcess::~EmbedTexturesProcess() { + // empty +} + +bool EmbedTexturesProcess::IsActive(unsigned int pFlags) const { + return (pFlags & aiProcess_EmbedTextures) != 0; +} + +void EmbedTexturesProcess::SetupProperties(const Importer* pImp) { + mRootPath = pImp->GetPropertyString("sourceFilePath"); + mRootPath = mRootPath.substr(0, mRootPath.find_last_of("\\/") + 1u); + mIOHandler = pImp->GetIOHandler(); +} + +void EmbedTexturesProcess::Execute(aiScene* pScene) { + if (pScene == nullptr || pScene->mRootNode == nullptr || mIOHandler == nullptr){ + return; + } + + aiString path; + uint32_t embeddedTexturesCount = 0u; + for (auto matId = 0u; matId < pScene->mNumMaterials; ++matId) { + auto material = pScene->mMaterials[matId]; + + for (auto ttId = 1u; ttId < AI_TEXTURE_TYPE_MAX; ++ttId) { + auto tt = static_cast<aiTextureType>(ttId); + auto texturesCount = material->GetTextureCount(tt); + + for (auto texId = 0u; texId < texturesCount; ++texId) { + material->GetTexture(tt, texId, &path); + if (path.data[0] == '*') continue; // Already embedded + + // Indeed embed + if (addTexture(pScene, path.data)) { + auto embeddedTextureId = pScene->mNumTextures - 1u; + path.length = ::ai_snprintf(path.data, 1024, "*%u", embeddedTextureId); + material->AddProperty(&path, AI_MATKEY_TEXTURE(tt, texId)); + embeddedTexturesCount++; + } + } + } + } + + ASSIMP_LOG_INFO("EmbedTexturesProcess finished. Embedded ", embeddedTexturesCount, " textures." ); +} + +bool EmbedTexturesProcess::addTexture(aiScene *pScene, const std::string &path) const { + std::streampos imageSize = 0; + std::string imagePath = path; + + // Test path directly + if (!mIOHandler->Exists(imagePath)) { + ASSIMP_LOG_WARN("EmbedTexturesProcess: Cannot find image: ", imagePath, ". Will try to find it in root folder."); + + // Test path in root path + imagePath = mRootPath + path; + if (!mIOHandler->Exists(imagePath)) { + // Test path basename in root path + imagePath = mRootPath + path.substr(path.find_last_of("\\/") + 1u); + if (!mIOHandler->Exists(imagePath)) { + ASSIMP_LOG_ERROR("EmbedTexturesProcess: Unable to embed texture: ", path, "."); + return false; + } + } + } + IOStream* pFile = mIOHandler->Open(imagePath); + if (pFile == nullptr) { + ASSIMP_LOG_ERROR("EmbedTexturesProcess: Unable to embed texture: ", path, "."); + return false; + } + imageSize = pFile->FileSize(); + + aiTexel* imageContent = new aiTexel[ 1ul + static_cast<unsigned long>( imageSize ) / sizeof(aiTexel)]; + pFile->Seek(0, aiOrigin_SET); + pFile->Read(reinterpret_cast<char*>(imageContent), imageSize, 1); + mIOHandler->Close(pFile); + + // Enlarging the textures table + unsigned int textureId = pScene->mNumTextures++; + auto oldTextures = pScene->mTextures; + pScene->mTextures = new aiTexture*[pScene->mNumTextures]; + ::memmove(pScene->mTextures, oldTextures, sizeof(aiTexture*) * (pScene->mNumTextures - 1u)); + delete [] oldTextures; + + // Add the new texture + auto pTexture = new aiTexture; + pTexture->mHeight = 0; // Means that this is still compressed + pTexture->mWidth = static_cast<uint32_t>(imageSize); + pTexture->pcData = imageContent; + + auto extension = path.substr(path.find_last_of('.') + 1u); + extension = ai_tolower(extension); + if (extension == "jpeg") { + extension = "jpg"; + } + + size_t len = extension.size(); + if (len > HINTMAXTEXTURELEN -1 ) { + len = HINTMAXTEXTURELEN - 1; + } + ::strncpy(pTexture->achFormatHint, extension.c_str(), len); + pScene->mTextures[textureId] = pTexture; + + return true; +} diff --git a/libs/assimp/code/PostProcessing/EmbedTexturesProcess.h b/libs/assimp/code/PostProcessing/EmbedTexturesProcess.h new file mode 100644 index 0000000..c3e6361 --- /dev/null +++ b/libs/assimp/code/PostProcessing/EmbedTexturesProcess.h @@ -0,0 +1,88 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#pragma once + +#include "Common/BaseProcess.h" + +#include <string> + +struct aiNode; + +class IOSystem; + +namespace Assimp { + +/** + * Force embedding of textures (using the path = "*1" convention). + * If a texture's file does not exist at the specified path + * (due, for instance, to an absolute path generated on another system), + * it will check if a file with the same name exists at the root folder + * of the imported model. And if so, it uses that. + */ +class ASSIMP_API EmbedTexturesProcess : public BaseProcess { +public: + /// The default class constructor. + EmbedTexturesProcess(); + + /// The class destructor. + virtual ~EmbedTexturesProcess(); + + /// Overwritten, @see BaseProcess + virtual bool IsActive(unsigned int pFlags) const; + + /// Overwritten, @see BaseProcess + virtual void SetupProperties(const Importer* pImp); + + /// Overwritten, @see BaseProcess + virtual void Execute(aiScene* pScene); + +private: + // Resolve the path and add the file content to the scene as a texture. + bool addTexture(aiScene *pScene, const std::string &path) const; + +private: + std::string mRootPath; + IOSystem* mIOHandler = nullptr; +}; + +} // namespace Assimp diff --git a/libs/assimp/code/PostProcessing/FindDegenerates.cpp b/libs/assimp/code/PostProcessing/FindDegenerates.cpp new file mode 100644 index 0000000..0bbf421 --- /dev/null +++ b/libs/assimp/code/PostProcessing/FindDegenerates.cpp @@ -0,0 +1,297 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FindDegenerates.cpp + * @brief Implementation of the FindDegenerates post-process step. +*/ + +#include "ProcessHelper.h" +#include "FindDegenerates.h" + +#include <assimp/Exceptional.h> + +#include <unordered_map> + +using namespace Assimp; + +// Correct node indices to meshes and remove references to deleted mesh +static void updateSceneGraph(aiNode* pNode, const std::unordered_map<unsigned int, unsigned int>& meshMap); + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +FindDegeneratesProcess::FindDegeneratesProcess() : + mConfigRemoveDegenerates( false ), + mConfigCheckAreaOfTriangle( false ){ + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +FindDegeneratesProcess::~FindDegeneratesProcess() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool FindDegeneratesProcess::IsActive( unsigned int pFlags) const { + return 0 != (pFlags & aiProcess_FindDegenerates); +} + +// ------------------------------------------------------------------------------------------------ +// Setup import configuration +void FindDegeneratesProcess::SetupProperties(const Importer* pImp) { + // Get the current value of AI_CONFIG_PP_FD_REMOVE + mConfigRemoveDegenerates = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_FD_REMOVE,0)); + mConfigCheckAreaOfTriangle = ( 0 != pImp->GetPropertyInteger(AI_CONFIG_PP_FD_CHECKAREA) ); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void FindDegeneratesProcess::Execute( aiScene* pScene) { + ASSIMP_LOG_DEBUG("FindDegeneratesProcess begin"); + if ( nullptr == pScene) { + return; + } + + std::unordered_map<unsigned int, unsigned int> meshMap; + meshMap.reserve(pScene->mNumMeshes); + + const unsigned int originalNumMeshes = pScene->mNumMeshes; + unsigned int targetIndex = 0; + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + // Do not process point cloud, ExecuteOnMesh works only with faces data + if ((pScene->mMeshes[i]->mPrimitiveTypes != aiPrimitiveType::aiPrimitiveType_POINT) && ExecuteOnMesh(pScene->mMeshes[i])) { + delete pScene->mMeshes[i]; + // Not strictly required, but clean: + pScene->mMeshes[i] = nullptr; + } else { + meshMap[i] = targetIndex; + pScene->mMeshes[targetIndex] = pScene->mMeshes[i]; + ++targetIndex; + } + } + pScene->mNumMeshes = targetIndex; + + if (meshMap.size() < originalNumMeshes) { + updateSceneGraph(pScene->mRootNode, meshMap); + } + + ASSIMP_LOG_DEBUG("FindDegeneratesProcess finished"); +} + +static void updateSceneGraph(aiNode* pNode, const std::unordered_map<unsigned int, unsigned int>& meshMap) { + unsigned int targetIndex = 0; + for (unsigned i = 0; i < pNode->mNumMeshes; ++i) { + const unsigned int sourceMeshIndex = pNode->mMeshes[i]; + auto it = meshMap.find(sourceMeshIndex); + if (it != meshMap.end()) { + pNode->mMeshes[targetIndex] = it->second; + ++targetIndex; + } + } + pNode->mNumMeshes = targetIndex; + //recurse to all children + for (unsigned i = 0; i < pNode->mNumChildren; ++i) { + updateSceneGraph(pNode->mChildren[i], meshMap); + } +} + +static ai_real heron( ai_real a, ai_real b, ai_real c ) { + ai_real s = (a + b + c) / 2; + ai_real area = pow((s * ( s - a ) * ( s - b ) * ( s - c ) ), (ai_real)0.5 ); + return area; +} + +static ai_real distance3D( const aiVector3D &vA, aiVector3D &vB ) { + const ai_real lx = ( vB.x - vA.x ); + const ai_real ly = ( vB.y - vA.y ); + const ai_real lz = ( vB.z - vA.z ); + ai_real a = lx*lx + ly*ly + lz*lz; + ai_real d = pow( a, (ai_real)0.5 ); + + return d; +} + +static ai_real calculateAreaOfTriangle( const aiFace& face, aiMesh* mesh ) { + ai_real area = 0; + + aiVector3D vA( mesh->mVertices[ face.mIndices[ 0 ] ] ); + aiVector3D vB( mesh->mVertices[ face.mIndices[ 1 ] ] ); + aiVector3D vC( mesh->mVertices[ face.mIndices[ 2 ] ] ); + + ai_real a( distance3D( vA, vB ) ); + ai_real b( distance3D( vB, vC ) ); + ai_real c( distance3D( vC, vA ) ); + area = heron( a, b, c ); + + return area; +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported mesh +bool FindDegeneratesProcess::ExecuteOnMesh( aiMesh* mesh) { + mesh->mPrimitiveTypes = 0; + + std::vector<bool> remove_me; + if (mConfigRemoveDegenerates) { + remove_me.resize( mesh->mNumFaces, false ); + } + + unsigned int deg = 0, limit; + for ( unsigned int a = 0; a < mesh->mNumFaces; ++a ) { + aiFace& face = mesh->mFaces[a]; + bool first = true; + + // check whether the face contains degenerated entries + for (unsigned int i = 0; i < face.mNumIndices; ++i) { + // Polygons with more than 4 points are allowed to have double points, that is + // simulating polygons with holes just with concave polygons. However, + // double points may not come directly after another. + limit = face.mNumIndices; + if (face.mNumIndices > 4) { + limit = std::min( limit, i+2 ); + } + + for (unsigned int t = i+1; t < limit; ++t) { + if (mesh->mVertices[face.mIndices[ i ] ] == mesh->mVertices[ face.mIndices[ t ] ]) { + // we have found a matching vertex position + // remove the corresponding index from the array + --face.mNumIndices; + --limit; + for (unsigned int m = t; m < face.mNumIndices; ++m) { + face.mIndices[ m ] = face.mIndices[ m+1 ]; + } + --t; + + // NOTE: we set the removed vertex index to an unique value + // to make sure the developer gets notified when his + // application attempts to access this data. + face.mIndices[ face.mNumIndices ] = 0xdeadbeef; + + if(first) { + ++deg; + first = false; + } + + if ( mConfigRemoveDegenerates ) { + remove_me[ a ] = true; + goto evil_jump_outside; // hrhrhrh ... yeah, this rocks baby! + } + } + } + + if ( mConfigCheckAreaOfTriangle ) { + if ( face.mNumIndices == 3 ) { + ai_real area = calculateAreaOfTriangle( face, mesh ); + if (area < ai_epsilon) { + if ( mConfigRemoveDegenerates ) { + remove_me[ a ] = true; + ++deg; + goto evil_jump_outside; + } + + // todo: check for index which is corrupt. + } + } + } + } + + // We need to update the primitive flags array of the mesh. + switch (face.mNumIndices) + { + case 1u: + mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + case 2u: + mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + case 3u: + mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + default: + mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + break; + }; +evil_jump_outside: + continue; + } + + // If AI_CONFIG_PP_FD_REMOVE is true, remove degenerated faces from the import + if (mConfigRemoveDegenerates && deg) { + unsigned int n = 0; + for (unsigned int a = 0; a < mesh->mNumFaces; ++a) + { + aiFace& face_src = mesh->mFaces[a]; + if (!remove_me[a]) { + aiFace& face_dest = mesh->mFaces[n++]; + + // Do a manual copy, keep the index array + face_dest.mNumIndices = face_src.mNumIndices; + face_dest.mIndices = face_src.mIndices; + + if (&face_src != &face_dest) { + // clear source + face_src.mNumIndices = 0; + face_src.mIndices = nullptr; + } + } + else { + // Otherwise delete it if we don't need this face + delete[] face_src.mIndices; + face_src.mIndices = nullptr; + face_src.mNumIndices = 0; + } + } + // Just leave the rest of the array unreferenced, we don't care for now + mesh->mNumFaces = n; + if (!mesh->mNumFaces) { + //The whole mesh consists of degenerated faces + //signal upward, that this mesh should be deleted. + ASSIMP_LOG_VERBOSE_DEBUG("FindDegeneratesProcess removed a mesh full of degenerated primitives"); + return true; + } + } + + if (deg && !DefaultLogger::isNullLogger()) { + ASSIMP_LOG_WARN( "Found ", deg, " degenerated primitives"); + } + return false; +} diff --git a/libs/assimp/code/PostProcessing/FindDegenerates.h b/libs/assimp/code/PostProcessing/FindDegenerates.h new file mode 100644 index 0000000..6fe1e92 --- /dev/null +++ b/libs/assimp/code/PostProcessing/FindDegenerates.h @@ -0,0 +1,130 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to search all meshes for + degenerated faces */ +#ifndef AI_FINDDEGENERATESPROCESS_H_INC +#define AI_FINDDEGENERATESPROCESS_H_INC + +#include "Common/BaseProcess.h" + +#include <assimp/mesh.h> + +class FindDegeneratesProcessTest; +namespace Assimp { + + +// --------------------------------------------------------------------------- +/** FindDegeneratesProcess: Searches a mesh for degenerated triangles. +*/ +class ASSIMP_API FindDegeneratesProcess : public BaseProcess { +public: + FindDegeneratesProcess(); + ~FindDegeneratesProcess(); + + // ------------------------------------------------------------------- + // Check whether step is active + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + // Execute step on a given scene + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + // Setup import settings + void SetupProperties(const Importer* pImp); + + // ------------------------------------------------------------------- + // Execute step on a given mesh + ///@returns true if the current mesh should be deleted, false otherwise + bool ExecuteOnMesh( aiMesh* mesh); + + // ------------------------------------------------------------------- + /// @brief Enable the instant removal of degenerated primitives + /// @param enabled true for enabled. + void EnableInstantRemoval(bool enabled); + + // ------------------------------------------------------------------- + /// @brief Check whether instant removal is currently enabled + /// @return The instant removal state. + bool IsInstantRemoval() const; + + // ------------------------------------------------------------------- + /// @brief Enable the area check for triangles. + /// @param enabled true for enabled. + void EnableAreaCheck( bool enabled ); + + // ------------------------------------------------------------------- + /// @brief Check whether the area check is enabled. + /// @return The area check state. + bool isAreaCheckEnabled() const; + +private: + //! Configuration option: remove degenerates faces immediately + bool mConfigRemoveDegenerates; + //! Configuration option: check for area + bool mConfigCheckAreaOfTriangle; +}; + +inline +void FindDegeneratesProcess::EnableInstantRemoval(bool enabled) { + mConfigRemoveDegenerates = enabled; +} + +inline +bool FindDegeneratesProcess::IsInstantRemoval() const { + return mConfigRemoveDegenerates; +} + +inline +void FindDegeneratesProcess::EnableAreaCheck( bool enabled ) { + mConfigCheckAreaOfTriangle = enabled; +} + +inline +bool FindDegeneratesProcess::isAreaCheckEnabled() const { + return mConfigCheckAreaOfTriangle; +} + +} // Namespace Assimp + +#endif // !! AI_FINDDEGENERATESPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/FindInstancesProcess.cpp b/libs/assimp/code/PostProcessing/FindInstancesProcess.cpp new file mode 100644 index 0000000..7f8c93f --- /dev/null +++ b/libs/assimp/code/PostProcessing/FindInstancesProcess.cpp @@ -0,0 +1,277 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FindInstancesProcess.cpp + * @brief Implementation of the aiProcess_FindInstances postprocessing step +*/ + + +#include "FindInstancesProcess.h" +#include <memory> +#include <stdio.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +FindInstancesProcess::FindInstancesProcess() +: configSpeedFlag (false) +{} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +FindInstancesProcess::~FindInstancesProcess() +{} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool FindInstancesProcess::IsActive( unsigned int pFlags) const +{ + // FindInstances makes absolutely no sense together with PreTransformVertices + // fixme: spawn error message somewhere else? + return 0 != (pFlags & aiProcess_FindInstances) && 0 == (pFlags & aiProcess_PreTransformVertices); +} + +// ------------------------------------------------------------------------------------------------ +// Setup properties for the step +void FindInstancesProcess::SetupProperties(const Importer* pImp) +{ + // AI_CONFIG_FAVOUR_SPEED + configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0)); +} + +// ------------------------------------------------------------------------------------------------ +// Compare the bones of two meshes +bool CompareBones(const aiMesh* orig, const aiMesh* inst) +{ + for (unsigned int i = 0; i < orig->mNumBones;++i) { + aiBone* aha = orig->mBones[i]; + aiBone* oha = inst->mBones[i]; + + if (aha->mNumWeights != oha->mNumWeights || + aha->mOffsetMatrix != oha->mOffsetMatrix) { + return false; + } + + // compare weight per weight --- + for (unsigned int n = 0; n < aha->mNumWeights;++n) { + if (aha->mWeights[n].mVertexId != oha->mWeights[n].mVertexId || + (aha->mWeights[n].mWeight - oha->mWeights[n].mWeight) < 10e-3f) { + return false; + } + } + } + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Update mesh indices in the node graph +void UpdateMeshIndices(aiNode* node, unsigned int* lookup) +{ + for (unsigned int n = 0; n < node->mNumMeshes;++n) + node->mMeshes[n] = lookup[node->mMeshes[n]]; + + for (unsigned int n = 0; n < node->mNumChildren;++n) + UpdateMeshIndices(node->mChildren[n],lookup); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void FindInstancesProcess::Execute( aiScene* pScene) +{ + ASSIMP_LOG_DEBUG("FindInstancesProcess begin"); + if (pScene->mNumMeshes) { + + // use a pseudo hash for all meshes in the scene to quickly find + // the ones which are possibly equal. This step is executed early + // in the pipeline, so we could, depending on the file format, + // have several thousand small meshes. That's too much for a brute + // everyone-against-everyone check involving up to 10 comparisons + // each. + std::unique_ptr<uint64_t[]> hashes (new uint64_t[pScene->mNumMeshes]); + std::unique_ptr<unsigned int[]> remapping (new unsigned int[pScene->mNumMeshes]); + + unsigned int numMeshesOut = 0; + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + + aiMesh* inst = pScene->mMeshes[i]; + hashes[i] = GetMeshHash(inst); + + // Find an appropriate epsilon + // to compare position differences against + float epsilon = ComputePositionEpsilon(inst); + epsilon *= epsilon; + + for (int a = i-1; a >= 0; --a) { + if (hashes[i] == hashes[a]) + { + aiMesh* orig = pScene->mMeshes[a]; + if (!orig) + continue; + + // check for hash collision .. we needn't check + // the vertex format, it *must* match due to the + // (brilliant) construction of the hash + if (orig->mNumBones != inst->mNumBones || + orig->mNumFaces != inst->mNumFaces || + orig->mNumVertices != inst->mNumVertices || + orig->mMaterialIndex != inst->mMaterialIndex || + orig->mPrimitiveTypes != inst->mPrimitiveTypes) + continue; + + // up to now the meshes are equal. Now compare vertex positions, normals, + // tangents and bitangents using this epsilon. + if (orig->HasPositions()) { + if(!CompareArrays(orig->mVertices,inst->mVertices,orig->mNumVertices,epsilon)) + continue; + } + if (orig->HasNormals()) { + if(!CompareArrays(orig->mNormals,inst->mNormals,orig->mNumVertices,epsilon)) + continue; + } + if (orig->HasTangentsAndBitangents()) { + if (!CompareArrays(orig->mTangents,inst->mTangents,orig->mNumVertices,epsilon) || + !CompareArrays(orig->mBitangents,inst->mBitangents,orig->mNumVertices,epsilon)) + continue; + } + + // use a constant epsilon for colors and UV coordinates + static const float uvEpsilon = 10e-4f; + { + unsigned int j, end = orig->GetNumUVChannels(); + for(j = 0; j < end; ++j) { + if (!orig->mTextureCoords[j]) { + continue; + } + if(!CompareArrays(orig->mTextureCoords[j],inst->mTextureCoords[j],orig->mNumVertices,uvEpsilon)) { + break; + } + } + if (j != end) { + continue; + } + } + { + unsigned int j, end = orig->GetNumColorChannels(); + for(j = 0; j < end; ++j) { + if (!orig->mColors[j]) { + continue; + } + if(!CompareArrays(orig->mColors[j],inst->mColors[j],orig->mNumVertices,uvEpsilon)) { + break; + } + } + if (j != end) { + continue; + } + } + + // These two checks are actually quite expensive and almost *never* required. + // Almost. That's why they're still here. But there's no reason to do them + // in speed-targeted imports. + if (!configSpeedFlag) { + + // It seems to be strange, but we really need to check whether the + // bones are identical too. Although it's extremely unprobable + // that they're not if control reaches here, we need to deal + // with unprobable cases, too. It could still be that there are + // equal shapes which are deformed differently. + if (!CompareBones(orig,inst)) + continue; + + // For completeness ... compare even the index buffers for equality + // face order & winding order doesn't care. Input data is in verbose format. + std::unique_ptr<unsigned int[]> ftbl_orig(new unsigned int[orig->mNumVertices]); + std::unique_ptr<unsigned int[]> ftbl_inst(new unsigned int[orig->mNumVertices]); + + for (unsigned int tt = 0; tt < orig->mNumFaces;++tt) { + aiFace& f = orig->mFaces[tt]; + for (unsigned int nn = 0; nn < f.mNumIndices;++nn) + ftbl_orig[f.mIndices[nn]] = tt; + + aiFace& f2 = inst->mFaces[tt]; + for (unsigned int nn = 0; nn < f2.mNumIndices;++nn) + ftbl_inst[f2.mIndices[nn]] = tt; + } + if (0 != ::memcmp(ftbl_inst.get(),ftbl_orig.get(),orig->mNumVertices*sizeof(unsigned int))) + continue; + } + + // We're still here. Or in other words: 'inst' is an instance of 'orig'. + // Place a marker in our list that we can easily update mesh indices. + remapping[i] = remapping[a]; + + // Delete the instanced mesh, we don't need it anymore + delete inst; + pScene->mMeshes[i] = nullptr; + break; + } + } + + // If we didn't find a match for the current mesh: keep it + if (pScene->mMeshes[i]) { + remapping[i] = numMeshesOut++; + } + } + ai_assert(0 != numMeshesOut); + if (numMeshesOut != pScene->mNumMeshes) { + + // Collapse the meshes array by removing all nullptr entries + for (unsigned int real = 0, i = 0; real < numMeshesOut; ++i) { + if (pScene->mMeshes[i]) + pScene->mMeshes[real++] = pScene->mMeshes[i]; + } + + // And update the node graph with our nice lookup table + UpdateMeshIndices(pScene->mRootNode,remapping.get()); + + // write to log + if (!DefaultLogger::isNullLogger()) { + ASSIMP_LOG_INFO( "FindInstancesProcess finished. Found ", (pScene->mNumMeshes - numMeshesOut), " instances" ); + } + pScene->mNumMeshes = numMeshesOut; + } else { + ASSIMP_LOG_DEBUG("FindInstancesProcess finished. No instanced meshes found"); + } + } +} diff --git a/libs/assimp/code/PostProcessing/FindInstancesProcess.h b/libs/assimp/code/PostProcessing/FindInstancesProcess.h new file mode 100644 index 0000000..b501d88 --- /dev/null +++ b/libs/assimp/code/PostProcessing/FindInstancesProcess.h @@ -0,0 +1,137 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 FindInstancesProcess.h + * @brief Declares the aiProcess_FindInstances post-process step + */ +#ifndef AI_FINDINSTANCES_H_INC +#define AI_FINDINSTANCES_H_INC + +#include "Common/BaseProcess.h" +#include "PostProcessing/ProcessHelper.h" + +class FindInstancesProcessTest; +namespace Assimp { + +// ------------------------------------------------------------------------------- +/** @brief Get a pseudo(!)-hash representing a mesh. + * + * The hash is built from number of vertices, faces, primitive types, + * .... but *not* from the real mesh data. The funcction is not a perfect hash. + * @param in Input mesh + * @return Hash. + */ +inline +uint64_t GetMeshHash(aiMesh* in) { + ai_assert(nullptr != in); + + // ... get an unique value representing the vertex format of the mesh + const unsigned int fhash = GetMeshVFormatUnique(in); + + // and bake it with number of vertices/faces/bones/matidx/ptypes + return ((uint64_t)fhash << 32u) | (( + (in->mNumBones << 16u) ^ (in->mNumVertices) ^ + (in->mNumFaces<<4u) ^ (in->mMaterialIndex<<15) ^ + (in->mPrimitiveTypes<<28)) & 0xffffffff ); +} + +// ------------------------------------------------------------------------------- +/** @brief Perform a component-wise comparison of two arrays + * + * @param first First array + * @param second Second array + * @param size Size of both arrays + * @param e Epsilon + * @return true if the arrays are identical + */ +inline +bool CompareArrays(const aiVector3D* first, const aiVector3D* second, + unsigned int size, float e) { + for (const aiVector3D* end = first+size; first != end; ++first,++second) { + if ( (*first - *second).SquareLength() >= e) + return false; + } + return true; +} + +// and the same for colors ... +inline bool CompareArrays(const aiColor4D* first, const aiColor4D* second, + unsigned int size, float e) +{ + for (const aiColor4D* end = first+size; first != end; ++first,++second) { + if ( GetColorDifference(*first,*second) >= e) + return false; + } + return true; +} + +// --------------------------------------------------------------------------- +/** @brief A post-processing steps to search for instanced meshes +*/ +class FindInstancesProcess : public BaseProcess +{ +public: + + FindInstancesProcess(); + ~FindInstancesProcess(); + +public: + // ------------------------------------------------------------------- + // Check whether step is active in given flags combination + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + // Execute step on a given scene + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + // Setup properties prior to executing the process + void SetupProperties(const Importer* pImp); + +private: + + bool configSpeedFlag; + +}; // ! end class FindInstancesProcess +} // ! end namespace Assimp + +#endif // !! AI_FINDINSTANCES_H_INC diff --git a/libs/assimp/code/PostProcessing/FindInvalidDataProcess.cpp b/libs/assimp/code/PostProcessing/FindInvalidDataProcess.cpp new file mode 100644 index 0000000..b42cbe9 --- /dev/null +++ b/libs/assimp/code/PostProcessing/FindInvalidDataProcess.cpp @@ -0,0 +1,409 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to search an importer's output + for data that is obviously invalid */ + +#ifndef ASSIMP_BUILD_NO_FINDINVALIDDATA_PROCESS + +// internal headers +#include "FindInvalidDataProcess.h" +#include "ProcessHelper.h" + +#include <assimp/Exceptional.h> +#include <assimp/qnan.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +FindInvalidDataProcess::FindInvalidDataProcess() : + configEpsilon(0.0), mIgnoreTexCoods(false) { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +FindInvalidDataProcess::~FindInvalidDataProcess() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool FindInvalidDataProcess::IsActive(unsigned int pFlags) const { + return 0 != (pFlags & aiProcess_FindInvalidData); +} + +// ------------------------------------------------------------------------------------------------ +// Setup import configuration +void FindInvalidDataProcess::SetupProperties(const Importer *pImp) { + // Get the current value of AI_CONFIG_PP_FID_ANIM_ACCURACY + configEpsilon = (0 != pImp->GetPropertyFloat(AI_CONFIG_PP_FID_ANIM_ACCURACY, 0.f)); + mIgnoreTexCoods = pImp->GetPropertyBool(AI_CONFIG_PP_FID_IGNORE_TEXTURECOORDS, false); +} + +// ------------------------------------------------------------------------------------------------ +// Update mesh references in the node graph +void UpdateMeshReferences(aiNode *node, const std::vector<unsigned int> &meshMapping) { + if (node->mNumMeshes) { + unsigned int out = 0; + for (unsigned int a = 0; a < node->mNumMeshes; ++a) { + + unsigned int ref = node->mMeshes[a]; + if (UINT_MAX != (ref = meshMapping[ref])) { + node->mMeshes[out++] = ref; + } + } + // just let the members that are unused, that's much cheaper + // than a full array realloc'n'copy party ... + node->mNumMeshes = out; + if (0 == out) { + delete[] node->mMeshes; + node->mMeshes = nullptr; + } + } + // recursively update all children + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + UpdateMeshReferences(node->mChildren[i], meshMapping); + } +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void FindInvalidDataProcess::Execute(aiScene *pScene) { + ASSIMP_LOG_DEBUG("FindInvalidDataProcess begin"); + + bool out = false; + std::vector<unsigned int> meshMapping(pScene->mNumMeshes); + unsigned int real = 0; + + // Process meshes + for (unsigned int a = 0; a < pScene->mNumMeshes; a++) { + int result = ProcessMesh(pScene->mMeshes[a]); + if (0 == result) { + out = true; + } + if (2 == result) { + // remove this mesh + delete pScene->mMeshes[a]; + pScene->mMeshes[a] = nullptr; + + meshMapping[a] = UINT_MAX; + out = true; + continue; + } + + pScene->mMeshes[real] = pScene->mMeshes[a]; + meshMapping[a] = real++; + } + + // Process animations + for (unsigned int animIdx = 0; animIdx < pScene->mNumAnimations; ++animIdx) { + ProcessAnimation(pScene->mAnimations[animIdx]); + } + + if (out) { + if (real != pScene->mNumMeshes) { + if (!real) { + throw DeadlyImportError("No meshes remaining"); + } + + // we need to remove some meshes. + // therefore we'll also need to remove all references + // to them from the scenegraph + UpdateMeshReferences(pScene->mRootNode, meshMapping); + pScene->mNumMeshes = real; + } + + ASSIMP_LOG_INFO("FindInvalidDataProcess finished. Found issues ..."); + } else { + ASSIMP_LOG_DEBUG("FindInvalidDataProcess finished. Everything seems to be OK."); + } +} + +// ------------------------------------------------------------------------------------------------ +template <typename T> +inline const char *ValidateArrayContents(const T * /*arr*/, unsigned int /*size*/, + const std::vector<bool> & /*dirtyMask*/, bool /*mayBeIdentical = false*/, bool /*mayBeZero = true*/) { + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +template <> +inline const char *ValidateArrayContents<aiVector3D>(const aiVector3D *arr, unsigned int size, + const std::vector<bool> &dirtyMask, bool mayBeIdentical, bool mayBeZero) { + bool b = false; + unsigned int cnt = 0; + for (unsigned int i = 0; i < size; ++i) { + + if (dirtyMask.size() && dirtyMask[i]) { + continue; + } + ++cnt; + + const aiVector3D &v = arr[i]; + if (is_special_float(v.x) || is_special_float(v.y) || is_special_float(v.z)) { + return "INF/NAN was found in a vector component"; + } + if (!mayBeZero && !v.x && !v.y && !v.z) { + return "Found zero-length vector"; + } + if (i && v != arr[i - 1]) b = true; + } + if (cnt > 1 && !b && !mayBeIdentical) { + return "All vectors are identical"; + } + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +template <typename T> +inline bool ProcessArray(T *&in, unsigned int num, const char *name, + const std::vector<bool> &dirtyMask, bool mayBeIdentical = false, bool mayBeZero = true) { + const char *err = ValidateArrayContents(in, num, dirtyMask, mayBeIdentical, mayBeZero); + if (err) { + ASSIMP_LOG_ERROR("FindInvalidDataProcess fails on mesh ", name, ": ", err); + delete[] in; + in = nullptr; + return true; + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +template <typename T> +AI_FORCE_INLINE bool EpsilonCompare(const T &n, const T &s, ai_real epsilon); + +// ------------------------------------------------------------------------------------------------ +AI_FORCE_INLINE bool EpsilonCompare(ai_real n, ai_real s, ai_real epsilon) { + return std::fabs(n - s) > epsilon; +} + +// ------------------------------------------------------------------------------------------------ +template <> +bool EpsilonCompare<aiVectorKey>(const aiVectorKey &n, const aiVectorKey &s, ai_real epsilon) { + return EpsilonCompare(n.mValue.x, s.mValue.x, epsilon) && + EpsilonCompare(n.mValue.y, s.mValue.y, epsilon) && + EpsilonCompare(n.mValue.z, s.mValue.z, epsilon); +} + +// ------------------------------------------------------------------------------------------------ +template <> +bool EpsilonCompare<aiQuatKey>(const aiQuatKey &n, const aiQuatKey &s, ai_real epsilon) { + return EpsilonCompare(n.mValue.x, s.mValue.x, epsilon) && + EpsilonCompare(n.mValue.y, s.mValue.y, epsilon) && + EpsilonCompare(n.mValue.z, s.mValue.z, epsilon) && + EpsilonCompare(n.mValue.w, s.mValue.w, epsilon); +} + +// ------------------------------------------------------------------------------------------------ +template <typename T> +inline bool AllIdentical(T *in, unsigned int num, ai_real epsilon) { + if (num <= 1) { + return true; + } + + if (fabs(epsilon) > 0.f) { + for (unsigned int i = 0; i < num - 1; ++i) { + if (!EpsilonCompare(in[i], in[i + 1], epsilon)) { + return false; + } + } + } else { + for (unsigned int i = 0; i < num - 1; ++i) { + if (in[i] != in[i + 1]) { + return false; + } + } + } + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Search an animation for invalid content +void FindInvalidDataProcess::ProcessAnimation(aiAnimation *anim) { + // Process all animation channels + for (unsigned int a = 0; a < anim->mNumChannels; ++a) { + ProcessAnimationChannel(anim->mChannels[a]); + } +} + +// ------------------------------------------------------------------------------------------------ +void FindInvalidDataProcess::ProcessAnimationChannel(aiNodeAnim *anim) { + ai_assert(nullptr != anim); + if (anim->mNumPositionKeys == 0 && anim->mNumRotationKeys == 0 && anim->mNumScalingKeys == 0) { + ai_assert_entry(); + return; + } + + // Check whether all values in a tracks are identical - in this case + // we can remove al keys except one. + // POSITIONS + int i = 0; + if (anim->mNumPositionKeys > 1 && AllIdentical(anim->mPositionKeys, anim->mNumPositionKeys, configEpsilon)) { + aiVectorKey v = anim->mPositionKeys[0]; + + // Reallocate ... we need just ONE element, it makes no sense to reuse the array + delete[] anim->mPositionKeys; + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys = 1]; + anim->mPositionKeys[0] = v; + i = 1; + } + + // ROTATIONS + if (anim->mNumRotationKeys > 1 && AllIdentical(anim->mRotationKeys, anim->mNumRotationKeys, configEpsilon)) { + aiQuatKey v = anim->mRotationKeys[0]; + + // Reallocate ... we need just ONE element, it makes no sense to reuse the array + delete[] anim->mRotationKeys; + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys = 1]; + anim->mRotationKeys[0] = v; + i = 1; + } + + // SCALINGS + if (anim->mNumScalingKeys > 1 && AllIdentical(anim->mScalingKeys, anim->mNumScalingKeys, configEpsilon)) { + aiVectorKey v = anim->mScalingKeys[0]; + + // Reallocate ... we need just ONE element, it makes no sense to reuse the array + delete[] anim->mScalingKeys; + anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys = 1]; + anim->mScalingKeys[0] = v; + i = 1; + } + if (1 == i) { + ASSIMP_LOG_WARN("Simplified dummy tracks with just one key"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Search a mesh for invalid contents +int FindInvalidDataProcess::ProcessMesh(aiMesh *pMesh) { + bool ret = false; + std::vector<bool> dirtyMask(pMesh->mNumVertices, pMesh->mNumFaces != 0); + + // Ignore elements that are not referenced by vertices. + // (they are, for example, caused by the FindDegenerates step) + for (unsigned int m = 0; m < pMesh->mNumFaces; ++m) { + const aiFace &f = pMesh->mFaces[m]; + + for (unsigned int i = 0; i < f.mNumIndices; ++i) { + dirtyMask[f.mIndices[i]] = false; + } + } + + // Process vertex positions + if (pMesh->mVertices && ProcessArray(pMesh->mVertices, pMesh->mNumVertices, "positions", dirtyMask)) { + ASSIMP_LOG_ERROR("Deleting mesh: Unable to continue without vertex positions"); + + return 2; + } + + // process texture coordinates + if (!mIgnoreTexCoods) { + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS && pMesh->mTextureCoords[i]; ++i) { + if (ProcessArray(pMesh->mTextureCoords[i], pMesh->mNumVertices, "uvcoords", dirtyMask)) { + pMesh->mNumUVComponents[i] = 0; + + // delete all subsequent texture coordinate sets. + for (unsigned int a = i + 1; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { + delete[] pMesh->mTextureCoords[a]; + pMesh->mTextureCoords[a] = nullptr; + pMesh->mNumUVComponents[a] = 0; + } + + ret = true; + } + } + } + + // -- we don't validate vertex colors, it's difficult to say whether + // they are invalid or not. + + // Normals and tangents are undefined for point and line faces. + if (pMesh->mNormals || pMesh->mTangents) { + + if (aiPrimitiveType_POINT & pMesh->mPrimitiveTypes || + aiPrimitiveType_LINE & pMesh->mPrimitiveTypes) { + if (aiPrimitiveType_TRIANGLE & pMesh->mPrimitiveTypes || + aiPrimitiveType_POLYGON & pMesh->mPrimitiveTypes) { + // We need to update the lookup-table + for (unsigned int m = 0; m < pMesh->mNumFaces; ++m) { + const aiFace &f = pMesh->mFaces[m]; + + if (f.mNumIndices < 3) { + dirtyMask[f.mIndices[0]] = true; + if (f.mNumIndices == 2) { + dirtyMask[f.mIndices[1]] = true; + } + } + } + } + // Normals, tangents and bitangents are undefined for + // the whole mesh (and should not even be there) + else { + return ret; + } + } + + // Process mesh normals + if (pMesh->mNormals && ProcessArray(pMesh->mNormals, pMesh->mNumVertices, + "normals", dirtyMask, true, false)) + ret = true; + + // Process mesh tangents + if (pMesh->mTangents && ProcessArray(pMesh->mTangents, pMesh->mNumVertices, "tangents", dirtyMask)) { + delete[] pMesh->mBitangents; + pMesh->mBitangents = nullptr; + ret = true; + } + + // Process mesh bitangents + if (pMesh->mBitangents && ProcessArray(pMesh->mBitangents, pMesh->mNumVertices, "bitangents", dirtyMask)) { + delete[] pMesh->mTangents; + pMesh->mTangents = nullptr; + ret = true; + } + } + return ret ? 1 : 0; +} + +#endif // !! ASSIMP_BUILD_NO_FINDINVALIDDATA_PROCESS diff --git a/libs/assimp/code/PostProcessing/FindInvalidDataProcess.h b/libs/assimp/code/PostProcessing/FindInvalidDataProcess.h new file mode 100644 index 0000000..5ea895c --- /dev/null +++ b/libs/assimp/code/PostProcessing/FindInvalidDataProcess.h @@ -0,0 +1,105 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to search an importer's output + * for data that is obviously invalid + */ +#ifndef AI_FINDINVALIDDATA_H_INC +#define AI_FINDINVALIDDATA_H_INC + +#include "Common/BaseProcess.h" + +#include <assimp/anim.h> +#include <assimp/types.h> + +struct aiMesh; + +class FindInvalidDataProcessTest; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** The FindInvalidData post-processing step. It searches the mesh data + * for parts that are obviously invalid and removes them. + * + * Originally this was a workaround for some models written by Blender + * which have zero normal vectors. */ +class ASSIMP_API FindInvalidDataProcess : public BaseProcess { +public: + FindInvalidDataProcess(); + ~FindInvalidDataProcess(); + + // ------------------------------------------------------------------- + // + bool IsActive(unsigned int pFlags) const; + + // ------------------------------------------------------------------- + // Setup import settings + void SetupProperties(const Importer *pImp); + + // ------------------------------------------------------------------- + // Run the step + void Execute(aiScene *pScene); + + // ------------------------------------------------------------------- + /** Executes the post-processing step on the given mesh + * @param pMesh The mesh to process. + * @return 0 - nothing, 1 - removed sth, 2 - please delete me */ + int ProcessMesh(aiMesh *pMesh); + + // ------------------------------------------------------------------- + /** Executes the post-processing step on the given animation + * @param anim The animation to process. */ + void ProcessAnimation(aiAnimation *anim); + + // ------------------------------------------------------------------- + /** Executes the post-processing step on the given anim channel + * @param anim The animation channel to process.*/ + void ProcessAnimationChannel(aiNodeAnim *anim); + +private: + ai_real configEpsilon; + bool mIgnoreTexCoods; +}; + +} // end of namespace Assimp + +#endif // AI_AI_FINDINVALIDDATA_H_INC diff --git a/libs/assimp/code/PostProcessing/FixNormalsStep.cpp b/libs/assimp/code/PostProcessing/FixNormalsStep.cpp new file mode 100644 index 0000000..be01042 --- /dev/null +++ b/libs/assimp/code/PostProcessing/FixNormalsStep.cpp @@ -0,0 +1,184 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the post processing step to invert + * all normals in meshes with infacing normals. + */ + +// internal headers +#include "FixNormalsStep.h" +#include <assimp/StringUtils.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <stdio.h> + + +using namespace Assimp; + + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +FixInfacingNormalsProcess::FixInfacingNormalsProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +FixInfacingNormalsProcess::~FixInfacingNormalsProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool FixInfacingNormalsProcess::IsActive( unsigned int pFlags) const +{ + return (pFlags & aiProcess_FixInfacingNormals) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void FixInfacingNormalsProcess::Execute( aiScene* pScene) +{ + ASSIMP_LOG_DEBUG("FixInfacingNormalsProcess begin"); + + bool bHas( false ); + for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) { + if (ProcessMesh(pScene->mMeshes[a], a)) { + bHas = true; + } + } + + if (bHas) { + ASSIMP_LOG_DEBUG("FixInfacingNormalsProcess finished. Found issues."); + } else { + ASSIMP_LOG_DEBUG("FixInfacingNormalsProcess finished. No changes to the scene."); + } +} + +// ------------------------------------------------------------------------------------------------ +// Apply the step to the mesh +bool FixInfacingNormalsProcess::ProcessMesh( aiMesh* pcMesh, unsigned int index) +{ + ai_assert(nullptr != pcMesh); + + // Nothing to do if there are no model normals + if (!pcMesh->HasNormals()) { + return false; + } + + // Compute the bounding box of both the model vertices + normals and + // the unmodified model vertices. Then check whether the first BB + // is smaller than the second. In this case we can assume that the + // normals need to be flipped, although there are a few special cases .. + // convex, concave, planar models ... + + aiVector3D vMin0 (1e10f,1e10f,1e10f); + aiVector3D vMin1 (1e10f,1e10f,1e10f); + aiVector3D vMax0 (-1e10f,-1e10f,-1e10f); + aiVector3D vMax1 (-1e10f,-1e10f,-1e10f); + + for (unsigned int i = 0; i < pcMesh->mNumVertices;++i) + { + vMin1.x = std::min(vMin1.x,pcMesh->mVertices[i].x); + vMin1.y = std::min(vMin1.y,pcMesh->mVertices[i].y); + vMin1.z = std::min(vMin1.z,pcMesh->mVertices[i].z); + + vMax1.x = std::max(vMax1.x,pcMesh->mVertices[i].x); + vMax1.y = std::max(vMax1.y,pcMesh->mVertices[i].y); + vMax1.z = std::max(vMax1.z,pcMesh->mVertices[i].z); + + const aiVector3D vWithNormal = pcMesh->mVertices[i] + pcMesh->mNormals[i]; + + vMin0.x = std::min(vMin0.x,vWithNormal.x); + vMin0.y = std::min(vMin0.y,vWithNormal.y); + vMin0.z = std::min(vMin0.z,vWithNormal.z); + + vMax0.x = std::max(vMax0.x,vWithNormal.x); + vMax0.y = std::max(vMax0.y,vWithNormal.y); + vMax0.z = std::max(vMax0.z,vWithNormal.z); + } + + const float fDelta0_x = (vMax0.x - vMin0.x); + const float fDelta0_y = (vMax0.y - vMin0.y); + const float fDelta0_z = (vMax0.z - vMin0.z); + + const float fDelta1_x = (vMax1.x - vMin1.x); + const float fDelta1_y = (vMax1.y - vMin1.y); + const float fDelta1_z = (vMax1.z - vMin1.z); + + // Check whether the boxes are overlapping + if ((fDelta0_x > 0.0f) != (fDelta1_x > 0.0f))return false; + if ((fDelta0_y > 0.0f) != (fDelta1_y > 0.0f))return false; + if ((fDelta0_z > 0.0f) != (fDelta1_z > 0.0f))return false; + + // Check whether this is a planar surface + const float fDelta1_yz = fDelta1_y * fDelta1_z; + + if (fDelta1_x < 0.05f * std::sqrt( fDelta1_yz ))return false; + if (fDelta1_y < 0.05f * std::sqrt( fDelta1_z * fDelta1_x ))return false; + if (fDelta1_z < 0.05f * std::sqrt( fDelta1_y * fDelta1_x ))return false; + + // now compare the volumes of the bounding boxes + if (std::fabs(fDelta0_x * fDelta0_y * fDelta0_z) < std::fabs(fDelta1_x * fDelta1_yz)) { + if (!DefaultLogger::isNullLogger()) { + ASSIMP_LOG_INFO("Mesh ", index, ": Normals are facing inwards (or the mesh is planar)", index); + } + + // Invert normals + for (unsigned int i = 0; i < pcMesh->mNumVertices;++i) + pcMesh->mNormals[i] *= -1.0f; + + // ... and flip faces + for (unsigned int i = 0; i < pcMesh->mNumFaces;++i) + { + aiFace& face = pcMesh->mFaces[i]; + for( unsigned int b = 0; b < face.mNumIndices / 2; b++) + std::swap( face.mIndices[b], face.mIndices[ face.mNumIndices - 1 - b]); + } + return true; + } + return false; +} diff --git a/libs/assimp/code/PostProcessing/FixNormalsStep.h b/libs/assimp/code/PostProcessing/FixNormalsStep.h new file mode 100644 index 0000000..b7d3ba3 --- /dev/null +++ b/libs/assimp/code/PostProcessing/FixNormalsStep.h @@ -0,0 +1,91 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to fix infacing normals */ +#ifndef AI_FIXNORMALSPROCESS_H_INC +#define AI_FIXNORMALSPROCESS_H_INC + +#include "Common/BaseProcess.h" + +struct aiMesh; + +namespace Assimp +{ + +// --------------------------------------------------------------------------- +/** The FixInfacingNormalsProcess tries to determine whether the normal + * vectors of an object are facing inwards. In this case they will be + * flipped. + */ +class FixInfacingNormalsProcess : public BaseProcess { +public: + FixInfacingNormalsProcess(); + ~FixInfacingNormalsProcess(); + + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag field. + * @param pFlags The processing flags the importer was called with. A bitwise + * combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, false if not. + */ + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + +protected: + + // ------------------------------------------------------------------- + /** Executes the step on the given mesh + * @param pMesh The mesh to process. + */ + bool ProcessMesh( aiMesh* pMesh, unsigned int index); +}; + +} // end of namespace Assimp + +#endif // AI_FIXNORMALSPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/GenBoundingBoxesProcess.cpp b/libs/assimp/code/PostProcessing/GenBoundingBoxesProcess.cpp new file mode 100644 index 0000000..1c20b6f --- /dev/null +++ b/libs/assimp/code/PostProcessing/GenBoundingBoxesProcess.cpp @@ -0,0 +1,115 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. +--------------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_GENBOUNDINGBOXES_PROCESS + +#include "PostProcessing/GenBoundingBoxesProcess.h" + +#include <assimp/postprocess.h> +#include <assimp/scene.h> + +namespace Assimp { + +GenBoundingBoxesProcess::GenBoundingBoxesProcess() +: BaseProcess() { + +} + +GenBoundingBoxesProcess::~GenBoundingBoxesProcess() { + // empty +} + +bool GenBoundingBoxesProcess::IsActive(unsigned int pFlags) const { + return 0 != ( pFlags & aiProcess_GenBoundingBoxes ); +} + +void checkMesh(aiMesh* mesh, aiVector3D& min, aiVector3D& max) { + ai_assert(nullptr != mesh); + + if (0 == mesh->mNumVertices) { + return; + } + + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + const aiVector3D &pos = mesh->mVertices[i]; + if (pos.x < min.x) { + min.x = pos.x; + } + if (pos.y < min.y) { + min.y = pos.y; + } + if (pos.z < min.z) { + min.z = pos.z; + } + + if (pos.x > max.x) { + max.x = pos.x; + } + if (pos.y > max.y) { + max.y = pos.y; + } + if (pos.z > max.z) { + max.z = pos.z; + } + } +} + +void GenBoundingBoxesProcess::Execute(aiScene* pScene) { + if (nullptr == pScene) { + return; + } + + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + aiMesh* mesh = pScene->mMeshes[i]; + if (nullptr == mesh) { + continue; + } + + aiVector3D min(999999, 999999, 999999), max(-999999, -999999, -999999); + checkMesh(mesh, min, max); + mesh->mAABB.mMin = min; + mesh->mAABB.mMax = max; + } +} + +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_GENBOUNDINGBOXES_PROCESS diff --git a/libs/assimp/code/PostProcessing/GenBoundingBoxesProcess.h b/libs/assimp/code/PostProcessing/GenBoundingBoxesProcess.h new file mode 100644 index 0000000..0b7591b --- /dev/null +++ b/libs/assimp/code/PostProcessing/GenBoundingBoxesProcess.h @@ -0,0 +1,76 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post-processing step to generate Axis-aligned bounding + * volumes for all meshes. + */ + +#pragma once + +#ifndef AI_GENBOUNDINGBOXESPROCESS_H_INC +#define AI_GENBOUNDINGBOXESPROCESS_H_INC + +#ifndef ASSIMP_BUILD_NO_GENBOUNDINGBOXES_PROCESS + +#include "Common/BaseProcess.h" + +namespace Assimp { + +/** Post-processing process to find axis-aligned bounding volumes for amm meshes + * used in a scene + */ +class ASSIMP_API GenBoundingBoxesProcess : public BaseProcess { +public: + /// The class constructor. + GenBoundingBoxesProcess(); + /// The class destructor. + ~GenBoundingBoxesProcess(); + /// Will return true, if aiProcess_GenBoundingBoxes is defined. + bool IsActive(unsigned int pFlags) const override; + /// The execution callback. + void Execute(aiScene* pScene) override; +}; + +} // Namespace Assimp + +#endif // #ifndef ASSIMP_BUILD_NO_GENBOUNDINGBOXES_PROCESS + +#endif // AI_GENBOUNDINGBOXESPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/GenFaceNormalsProcess.cpp b/libs/assimp/code/PostProcessing/GenFaceNormalsProcess.cpp new file mode 100644 index 0000000..517dd3b --- /dev/null +++ b/libs/assimp/code/PostProcessing/GenFaceNormalsProcess.cpp @@ -0,0 +1,147 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the post processing step to generate face +* normals for all imported faces. +*/ + +#include "GenFaceNormalsProcess.h" +#include <assimp/Exceptional.h> +#include <assimp/postprocess.h> +#include <assimp/qnan.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +GenFaceNormalsProcess::GenFaceNormalsProcess() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +GenFaceNormalsProcess::~GenFaceNormalsProcess() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool GenFaceNormalsProcess::IsActive(unsigned int pFlags) const { + force_ = (pFlags & aiProcess_ForceGenNormals) != 0; + flippedWindingOrder_ = (pFlags & aiProcess_FlipWindingOrder) != 0; + return (pFlags & aiProcess_GenNormals) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void GenFaceNormalsProcess::Execute(aiScene *pScene) { + ASSIMP_LOG_DEBUG("GenFaceNormalsProcess begin"); + + if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) { + throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here"); + } + + bool bHas = false; + for (unsigned int a = 0; a < pScene->mNumMeshes; a++) { + if (this->GenMeshFaceNormals(pScene->mMeshes[a])) { + bHas = true; + } + } + if (bHas) { + ASSIMP_LOG_INFO("GenFaceNormalsProcess finished. " + "Face normals have been calculated"); + } else { + ASSIMP_LOG_DEBUG("GenFaceNormalsProcess finished. " + "Normals are already there"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +bool GenFaceNormalsProcess::GenMeshFaceNormals(aiMesh *pMesh) { + if (nullptr != pMesh->mNormals) { + if (force_) { + delete[] pMesh->mNormals; + } else { + return false; + } + } + + // If the mesh consists of lines and/or points but not of + // triangles or higher-order polygons the normal vectors + // are undefined. + if (!(pMesh->mPrimitiveTypes & (aiPrimitiveType_TRIANGLE | aiPrimitiveType_POLYGON))) { + ASSIMP_LOG_INFO("Normal vectors are undefined for line and point meshes"); + return false; + } + + // allocate an array to hold the output normals + pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + const float qnan = get_qnan(); + + // iterate through all faces and compute per-face normals but store them per-vertex. + for (unsigned int a = 0; a < pMesh->mNumFaces; a++) { + const aiFace &face = pMesh->mFaces[a]; + if (face.mNumIndices < 3) { + // either a point or a line -> no well-defined normal vector + for (unsigned int i = 0; i < face.mNumIndices; ++i) { + pMesh->mNormals[face.mIndices[i]] = aiVector3D(qnan); + } + continue; + } + + const aiVector3D *pV1 = &pMesh->mVertices[face.mIndices[0]]; + const aiVector3D *pV2 = &pMesh->mVertices[face.mIndices[1]]; + const aiVector3D *pV3 = &pMesh->mVertices[face.mIndices[face.mNumIndices - 1]]; + if (flippedWindingOrder_) + std::swap( pV2, pV3 ); + const aiVector3D vNor = ((*pV2 - *pV1) ^ (*pV3 - *pV1)).NormalizeSafe(); + + for (unsigned int i = 0; i < face.mNumIndices; ++i) { + pMesh->mNormals[face.mIndices[i]] = vNor; + } + } + return true; +} diff --git a/libs/assimp/code/PostProcessing/GenFaceNormalsProcess.h b/libs/assimp/code/PostProcessing/GenFaceNormalsProcess.h new file mode 100644 index 0000000..586c490 --- /dev/null +++ b/libs/assimp/code/PostProcessing/GenFaceNormalsProcess.h @@ -0,0 +1,88 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to compute face normals for all loaded faces*/ +#ifndef AI_GENFACENORMALPROCESS_H_INC +#define AI_GENFACENORMALPROCESS_H_INC + +#include "Common/BaseProcess.h" +#include <assimp/mesh.h> + +namespace Assimp +{ + +// --------------------------------------------------------------------------- +/** The GenFaceNormalsProcess computes face normals for all faces of all meshes +*/ +class ASSIMP_API_WINONLY GenFaceNormalsProcess : public BaseProcess +{ +public: + + GenFaceNormalsProcess(); + ~GenFaceNormalsProcess(); + +public: + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag field. + * @param pFlags The processing flags the importer was called with. A bitwise + * combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, false if not. + */ + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + + +private: + bool GenMeshFaceNormals(aiMesh* pcMesh); + mutable bool force_ = false; + mutable bool flippedWindingOrder_ = false; +}; + +} // end of namespace Assimp + +#endif // !!AI_GENFACENORMALPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/GenVertexNormalsProcess.cpp b/libs/assimp/code/PostProcessing/GenVertexNormalsProcess.cpp new file mode 100644 index 0000000..1a8afc5 --- /dev/null +++ b/libs/assimp/code/PostProcessing/GenVertexNormalsProcess.cpp @@ -0,0 +1,233 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the post processing step to generate face +* normals for all imported faces. +*/ + +// internal headers +#include "GenVertexNormalsProcess.h" +#include "ProcessHelper.h" +#include <assimp/Exceptional.h> +#include <assimp/qnan.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +GenVertexNormalsProcess::GenVertexNormalsProcess() : + configMaxAngle(AI_DEG_TO_RAD(175.f)) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +GenVertexNormalsProcess::~GenVertexNormalsProcess() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool GenVertexNormalsProcess::IsActive(unsigned int pFlags) const { + force_ = (pFlags & aiProcess_ForceGenNormals) != 0; + flippedWindingOrder_ = (pFlags & aiProcess_FlipWindingOrder) != 0; + return (pFlags & aiProcess_GenSmoothNormals) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void GenVertexNormalsProcess::SetupProperties(const Importer *pImp) { + // Get the current value of the AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE property + configMaxAngle = pImp->GetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE, (ai_real)175.0); + configMaxAngle = AI_DEG_TO_RAD(std::max(std::min(configMaxAngle, (ai_real)175.0), (ai_real)0.0)); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void GenVertexNormalsProcess::Execute(aiScene *pScene) { + ASSIMP_LOG_DEBUG("GenVertexNormalsProcess begin"); + + if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) { + throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here"); + } + + bool bHas = false; + for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) { + if (GenMeshVertexNormals(pScene->mMeshes[a], a)) + bHas = true; + } + + if (bHas) { + ASSIMP_LOG_INFO("GenVertexNormalsProcess finished. " + "Vertex normals have been calculated"); + } else { + ASSIMP_LOG_DEBUG("GenVertexNormalsProcess finished. " + "Normals are already there"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +bool GenVertexNormalsProcess::GenMeshVertexNormals(aiMesh *pMesh, unsigned int meshIndex) { + if (nullptr != pMesh->mNormals) { + if (force_) + delete[] pMesh->mNormals; + else + return false; + } + + // If the mesh consists of lines and/or points but not of + // triangles or higher-order polygons the normal vectors + // are undefined. + if (!(pMesh->mPrimitiveTypes & (aiPrimitiveType_TRIANGLE | aiPrimitiveType_POLYGON))) { + ASSIMP_LOG_INFO("Normal vectors are undefined for line and point meshes"); + return false; + } + + // Allocate the array to hold the output normals + const float qnan = std::numeric_limits<ai_real>::quiet_NaN(); + pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + + // Compute per-face normals but store them per-vertex + for (unsigned int a = 0; a < pMesh->mNumFaces; a++) { + const aiFace &face = pMesh->mFaces[a]; + if (face.mNumIndices < 3) { + // either a point or a line -> no normal vector + for (unsigned int i = 0; i < face.mNumIndices; ++i) { + pMesh->mNormals[face.mIndices[i]] = aiVector3D(qnan); + } + + continue; + } + + const aiVector3D *pV1 = &pMesh->mVertices[face.mIndices[0]]; + const aiVector3D *pV2 = &pMesh->mVertices[face.mIndices[1]]; + const aiVector3D *pV3 = &pMesh->mVertices[face.mIndices[face.mNumIndices - 1]]; + if (flippedWindingOrder_) + std::swap( pV2, pV3 ); + const aiVector3D vNor = ((*pV2 - *pV1) ^ (*pV3 - *pV1)).NormalizeSafe(); + + for (unsigned int i = 0; i < face.mNumIndices; ++i) { + pMesh->mNormals[face.mIndices[i]] = vNor; + } + } + + // Set up a SpatialSort to quickly find all vertices close to a given position + // check whether we can reuse the SpatialSort of a previous step. + SpatialSort *vertexFinder = nullptr; + SpatialSort _vertexFinder; + ai_real posEpsilon = ai_real(1e-5); + if (shared) { + std::vector<std::pair<SpatialSort, ai_real>> *avf; + shared->GetProperty(AI_SPP_SPATIAL_SORT, avf); + if (avf) { + std::pair<SpatialSort, ai_real> &blubb = avf->operator[](meshIndex); + vertexFinder = &blubb.first; + posEpsilon = blubb.second; + } + } + if (!vertexFinder) { + _vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof(aiVector3D)); + vertexFinder = &_vertexFinder; + posEpsilon = ComputePositionEpsilon(pMesh); + } + std::vector<unsigned int> verticesFound; + aiVector3D *pcNew = new aiVector3D[pMesh->mNumVertices]; + + if (configMaxAngle >= AI_DEG_TO_RAD(175.f)) { + // There is no angle limit. Thus all vertices with positions close + // to each other will receive the same vertex normal. This allows us + // to optimize the whole algorithm a little bit ... + std::vector<bool> abHad(pMesh->mNumVertices, false); + for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) { + if (abHad[i]) { + continue; + } + + // Get all vertices that share this one ... + vertexFinder->FindPositions(pMesh->mVertices[i], posEpsilon, verticesFound); + + aiVector3D pcNor; + for (unsigned int a = 0; a < verticesFound.size(); ++a) { + const aiVector3D &v = pMesh->mNormals[verticesFound[a]]; + if (is_not_qnan(v.x)) pcNor += v; + } + pcNor.NormalizeSafe(); + + // Write the smoothed normal back to all affected normals + for (unsigned int a = 0; a < verticesFound.size(); ++a) { + unsigned int vidx = verticesFound[a]; + pcNew[vidx] = pcNor; + abHad[vidx] = true; + } + } + } + // Slower code path if a smooth angle is set. There are many ways to achieve + // the effect, this one is the most straightforward one. + else { + const ai_real fLimit = std::cos(configMaxAngle); + for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) { + // Get all vertices that share this one ... + vertexFinder->FindPositions(pMesh->mVertices[i], posEpsilon, verticesFound); + + aiVector3D vr = pMesh->mNormals[i]; + + aiVector3D pcNor; + for (unsigned int a = 0; a < verticesFound.size(); ++a) { + aiVector3D v = pMesh->mNormals[verticesFound[a]]; + + // Check whether the angle between the two normals is not too large. + // Skip the angle check on our own normal to avoid false negatives + // (v*v is not guaranteed to be 1.0 for all unit vectors v) + if (is_not_qnan(v.x) && (verticesFound[a] == i || (v * vr >= fLimit))) + pcNor += v; + } + pcNew[i] = pcNor.NormalizeSafe(); + } + } + + delete[] pMesh->mNormals; + pMesh->mNormals = pcNew; + + return true; +} diff --git a/libs/assimp/code/PostProcessing/GenVertexNormalsProcess.h b/libs/assimp/code/PostProcessing/GenVertexNormalsProcess.h new file mode 100644 index 0000000..0dcae79 --- /dev/null +++ b/libs/assimp/code/PostProcessing/GenVertexNormalsProcess.h @@ -0,0 +1,112 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to compute vertex normals + for all loaded vertizes */ +#ifndef AI_GENVERTEXNORMALPROCESS_H_INC +#define AI_GENVERTEXNORMALPROCESS_H_INC + +#include "Common/assbin_chunks.h" +#include "Common/BaseProcess.h" + +#include <assimp/mesh.h> + +// Forward declarations +class GenNormalsTest; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** The GenFaceNormalsProcess computes vertex normals for all vertices +*/ +class ASSIMP_API GenVertexNormalsProcess : public BaseProcess { +public: + GenVertexNormalsProcess(); + ~GenVertexNormalsProcess(); + + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag. + * @param pFlags The processing flags the importer was called with. + * A bitwise combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, + * false if not. + */ + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + /** Called prior to ExecuteOnScene(). + * The function is a request to the process to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer* pImp); + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + + + // setter for configMaxAngle + inline void SetMaxSmoothAngle(ai_real f) { + configMaxAngle =f; + } + + // ------------------------------------------------------------------- + /** Computes normals for a specific mesh + * @param pcMesh Mesh + * @param meshIndex Index of the mesh + * @return true if vertex normals have been computed + */ + bool GenMeshVertexNormals (aiMesh* pcMesh, unsigned int meshIndex); + +private: + /** Configuration option: maximum smoothing angle, in radians*/ + ai_real configMaxAngle; + mutable bool force_ = false; + mutable bool flippedWindingOrder_ = false; +}; + +} // end of namespace Assimp + +#endif // !!AI_GENVERTEXNORMALPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/ImproveCacheLocality.cpp b/libs/assimp/code/PostProcessing/ImproveCacheLocality.cpp new file mode 100644 index 0000000..56bdfc4 --- /dev/null +++ b/libs/assimp/code/PostProcessing/ImproveCacheLocality.cpp @@ -0,0 +1,379 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the post processing step to improve the cache locality of a mesh. + * <br> + * The algorithm is roughly basing on this paper: + * http://www.cs.princeton.edu/gfx/pubs/Sander_2007_%3ETR/tipsy.pdf + * .. although overdraw reduction isn't implemented yet ... + */ + +// internal headers +#include "PostProcessing/ImproveCacheLocality.h" +#include "Common/VertexTriangleAdjacency.h" + +#include <assimp/StringUtils.h> +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <stdio.h> +#include <stack> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +ImproveCacheLocalityProcess::ImproveCacheLocalityProcess() +: mConfigCacheDepth(PP_ICL_PTCACHE_SIZE) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +ImproveCacheLocalityProcess::~ImproveCacheLocalityProcess() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool ImproveCacheLocalityProcess::IsActive( unsigned int pFlags) const { + return (pFlags & aiProcess_ImproveCacheLocality) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration +void ImproveCacheLocalityProcess::SetupProperties(const Importer* pImp) { + // AI_CONFIG_PP_ICL_PTCACHE_SIZE controls the target cache size for the optimizer + mConfigCacheDepth = pImp->GetPropertyInteger(AI_CONFIG_PP_ICL_PTCACHE_SIZE,PP_ICL_PTCACHE_SIZE); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void ImproveCacheLocalityProcess::Execute( aiScene* pScene) { + if (!pScene->mNumMeshes) { + ASSIMP_LOG_DEBUG("ImproveCacheLocalityProcess skipped; there are no meshes"); + return; + } + + ASSIMP_LOG_DEBUG("ImproveCacheLocalityProcess begin"); + + float out = 0.f; + unsigned int numf = 0, numm = 0; + for( unsigned int a = 0; a < pScene->mNumMeshes; ++a ){ + const float res = ProcessMesh( pScene->mMeshes[a],a); + if (res) { + numf += pScene->mMeshes[a]->mNumFaces; + out += res; + ++numm; + } + } + if (!DefaultLogger::isNullLogger()) { + if (numf > 0) { + ASSIMP_LOG_INFO("Cache relevant are ", numm, " meshes (", numf, " faces). Average output ACMR is ", out / numf); + } + ASSIMP_LOG_DEBUG("ImproveCacheLocalityProcess finished. "); + } +} + +// ------------------------------------------------------------------------------------------------ +// Improves the cache coherency of a specific mesh +ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshNum) { + // TODO: rewrite this to use std::vector or boost::shared_array + ai_assert(nullptr != pMesh); + + // Check whether the input data is valid + // - there must be vertices and faces + // - all faces must be triangulated or we can't operate on them + if (!pMesh->HasFaces() || !pMesh->HasPositions()) + return static_cast<ai_real>(0.f); + + if (pMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE) { + ASSIMP_LOG_ERROR("This algorithm works on triangle meshes only"); + return static_cast<ai_real>(0.f); + } + + if(pMesh->mNumVertices <= mConfigCacheDepth) { + return static_cast<ai_real>(0.f); + } + + ai_real fACMR = 3.f; + const aiFace* const pcEnd = pMesh->mFaces+pMesh->mNumFaces; + + // Input ACMR is for logging purposes only + if (!DefaultLogger::isNullLogger()) { + + unsigned int* piFIFOStack = new unsigned int[mConfigCacheDepth]; + memset(piFIFOStack,0xff,mConfigCacheDepth*sizeof(unsigned int)); + unsigned int* piCur = piFIFOStack; + const unsigned int* const piCurEnd = piFIFOStack + mConfigCacheDepth; + + // count the number of cache misses + unsigned int iCacheMisses = 0; + for (const aiFace* pcFace = pMesh->mFaces;pcFace != pcEnd;++pcFace) { + for (unsigned int qq = 0; qq < 3;++qq) { + bool bInCache = false; + for (unsigned int* pp = piFIFOStack;pp < piCurEnd;++pp) { + if (*pp == pcFace->mIndices[qq]) { + // the vertex is in cache + bInCache = true; + break; + } + } + if (!bInCache) { + ++iCacheMisses; + if (piCurEnd == piCur) { + piCur = piFIFOStack; + } + *piCur++ = pcFace->mIndices[qq]; + } + } + } + delete[] piFIFOStack; + fACMR = (ai_real) iCacheMisses / pMesh->mNumFaces; + if (3.0 == fACMR) { + char szBuff[128]; // should be sufficiently large in every case + + // the JoinIdenticalVertices process has not been executed on this + // mesh, otherwise this value would normally be at least minimally + // smaller than 3.0 ... + ai_snprintf(szBuff,128,"Mesh %u: Not suitable for vcache optimization",meshNum); + ASSIMP_LOG_WARN(szBuff); + return static_cast<ai_real>(0.f); + } + } + + // first we need to build a vertex-triangle adjacency list + VertexTriangleAdjacency adj(pMesh->mFaces,pMesh->mNumFaces, pMesh->mNumVertices,true); + + // build a list to store per-vertex caching time stamps + unsigned int* const piCachingStamps = new unsigned int[pMesh->mNumVertices]; + memset(piCachingStamps,0x0,pMesh->mNumVertices*sizeof(unsigned int)); + + // allocate an empty output index buffer. We store the output indices in one large array. + // Since the number of triangles won't change the input faces can be reused. This is how + // we save thousands of redundant mini allocations for aiFace::mIndices + const unsigned int iIdxCnt = pMesh->mNumFaces*3; + unsigned int* const piIBOutput = new unsigned int[iIdxCnt]; + unsigned int* piCSIter = piIBOutput; + + // allocate the flag array to hold the information + // whether a face has already been emitted or not + std::vector<bool> abEmitted(pMesh->mNumFaces,false); + + // dead-end vertex index stack + std::stack<unsigned int, std::vector<unsigned int> > sDeadEndVStack; + + // create a copy of the piNumTriPtr buffer + unsigned int* const piNumTriPtr = adj.mLiveTriangles; + const std::vector<unsigned int> piNumTriPtrNoModify(piNumTriPtr, piNumTriPtr + pMesh->mNumVertices); + + // get the largest number of referenced triangles and allocate the "candidate buffer" + unsigned int iMaxRefTris = 0; { + const unsigned int* piCur = adj.mLiveTriangles; + const unsigned int* const piCurEnd = adj.mLiveTriangles+pMesh->mNumVertices; + for (;piCur != piCurEnd;++piCur) { + iMaxRefTris = std::max(iMaxRefTris,*piCur); + } + } + ai_assert(iMaxRefTris > 0); + unsigned int* piCandidates = new unsigned int[iMaxRefTris*3]; + unsigned int iCacheMisses = 0; + + // ................................................................................... + /** PSEUDOCODE for the algorithm + + A = Build-Adjacency(I) Vertex-triangle adjacency + L = Get-Triangle-Counts(A) Per-vertex live triangle counts + C = Zero(Vertex-Count(I)) Per-vertex caching time stamps + D = Empty-Stack() Dead-end vertex stack + E = False(Triangle-Count(I)) Per triangle emitted flag + O = Empty-Index-Buffer() Empty output buffer + f = 0 Arbitrary starting vertex + s = k+1, i = 1 Time stamp and cursor + while f >= 0 For all valid fanning vertices + N = Empty-Set() 1-ring of next candidates + for each Triangle t in Neighbors(A, f) + if !Emitted(E,t) + for each Vertex v in t + Append(O,v) Output vertex + Push(D,v) Add to dead-end stack + Insert(N,v) Register as candidate + L[v] = L[v]-1 Decrease live triangle count + if s-C[v] > k If not in cache + C[v] = s Set time stamp + s = s+1 Increment time stamp + E[t] = true Flag triangle as emitted + Select next fanning vertex + f = Get-Next-Vertex(I,i,k,N,C,s,L,D) + return O + */ + // ................................................................................... + + int ivdx = 0; + int ics = 1; + int iStampCnt = mConfigCacheDepth+1; + while (ivdx >= 0) { + + unsigned int icnt = piNumTriPtrNoModify[ivdx]; + unsigned int* piList = adj.GetAdjacentTriangles(ivdx); + unsigned int* piCurCandidate = piCandidates; + + // get all triangles in the neighborhood + for (unsigned int tri = 0; tri < icnt;++tri) { + + // if they have not yet been emitted, add them to the output IB + const unsigned int fidx = *piList++; + if (!abEmitted[fidx]) { + + // so iterate through all vertices of the current triangle + const aiFace* pcFace = &pMesh->mFaces[ fidx ]; + unsigned nind = pcFace->mNumIndices; + for (unsigned ind = 0; ind < nind; ind++) { + unsigned dp = pcFace->mIndices[ind]; + + // the current vertex won't have any free triangles after this step + if (ivdx != (int)dp) { + // append the vertex to the dead-end stack + sDeadEndVStack.push(dp); + + // register as candidate for the next step + *piCurCandidate++ = dp; + + // decrease the per-vertex triangle counts + piNumTriPtr[dp]--; + } + + // append the vertex to the output index buffer + *piCSIter++ = dp; + + // if the vertex is not yet in cache, set its cache count + if (iStampCnt-piCachingStamps[dp] > mConfigCacheDepth) { + piCachingStamps[dp] = iStampCnt++; + ++iCacheMisses; + } + } + // flag triangle as emitted + abEmitted[fidx] = true; + } + } + + // the vertex has now no living adjacent triangles anymore + piNumTriPtr[ivdx] = 0; + + // get next fanning vertex + ivdx = -1; + int max_priority = -1; + for (unsigned int* piCur = piCandidates;piCur != piCurCandidate;++piCur) { + const unsigned int dp = *piCur; + + // must have live triangles + if (piNumTriPtr[dp] > 0) { + int priority = 0; + + // will the vertex be in cache, even after fanning occurs? + unsigned int tmp; + if ((tmp = iStampCnt-piCachingStamps[dp]) + 2*piNumTriPtr[dp] <= mConfigCacheDepth) { + priority = tmp; + } + + // keep best candidate + if (priority > max_priority) { + max_priority = priority; + ivdx = dp; + } + } + } + // did we reach a dead end? + if (-1 == ivdx) { + // need to get a non-local vertex for which we have a good chance that it is still + // in the cache ... + while (!sDeadEndVStack.empty()) { + unsigned int iCachedIdx = sDeadEndVStack.top(); + sDeadEndVStack.pop(); + if (piNumTriPtr[ iCachedIdx ] > 0) { + ivdx = iCachedIdx; + break; + } + } + + if (-1 == ivdx) { + // well, there isn't such a vertex. Simply get the next vertex in input order and + // hope it is not too bad ... + while (ics < (int)pMesh->mNumVertices) { + ++ics; + if (piNumTriPtr[ics] > 0) { + ivdx = ics; + break; + } + } + } + } + } + ai_real fACMR2 = 0.0f; + if (!DefaultLogger::isNullLogger()) { + fACMR2 = (float)iCacheMisses / pMesh->mNumFaces; + + // very intense verbose logging ... prepare for much text if there are many meshes + if ( DefaultLogger::get()->getLogSeverity() == Logger::VERBOSE) { + ASSIMP_LOG_VERBOSE_DEBUG("Mesh %u | ACMR in: ", meshNum, " out: ", fACMR, " | ~", fACMR2, ((fACMR - fACMR2) / fACMR) * 100.f); + } + + fACMR2 *= pMesh->mNumFaces; + } + // sort the output index buffer back to the input array + piCSIter = piIBOutput; + for (aiFace* pcFace = pMesh->mFaces; pcFace != pcEnd;++pcFace) { + unsigned nind = pcFace->mNumIndices; + unsigned * ind = pcFace->mIndices; + if (nind > 0) ind[0] = *piCSIter++; + if (nind > 1) ind[1] = *piCSIter++; + if (nind > 2) ind[2] = *piCSIter++; + } + + // delete temporary storage + delete[] piCachingStamps; + delete[] piIBOutput; + delete[] piCandidates; + + return fACMR2; +} diff --git a/libs/assimp/code/PostProcessing/ImproveCacheLocality.h b/libs/assimp/code/PostProcessing/ImproveCacheLocality.h new file mode 100644 index 0000000..b2074a1 --- /dev/null +++ b/libs/assimp/code/PostProcessing/ImproveCacheLocality.h @@ -0,0 +1,101 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to reorder faces for + better cache locality*/ +#ifndef AI_IMPROVECACHELOCALITY_H_INC +#define AI_IMPROVECACHELOCALITY_H_INC + +#include "Common/BaseProcess.h" + +#include <assimp/types.h> + +struct aiMesh; + +namespace Assimp +{ + +// --------------------------------------------------------------------------- +/** The ImproveCacheLocalityProcess reorders all faces for improved vertex + * cache locality. It tries to arrange all faces to fans and to render + * faces which share vertices directly one after the other. + * + * @note This step expects triagulated input data. + */ +class ImproveCacheLocalityProcess : public BaseProcess +{ +public: + + ImproveCacheLocalityProcess(); + ~ImproveCacheLocalityProcess(); + +public: + + // ------------------------------------------------------------------- + // Check whether the pp step is active + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + // Executes the pp step on a given scene + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + // Configures the pp step + void SetupProperties(const Importer* pImp); + +protected: + // ------------------------------------------------------------------- + /** Executes the postprocessing step on the given mesh + * @param pMesh The mesh to process. + * @param meshNum Index of the mesh to process + */ + ai_real ProcessMesh( aiMesh* pMesh, unsigned int meshNum); + +private: + //! Configuration parameter: specifies the size of the cache to + //! optimize the vertex data for. + unsigned int mConfigCacheDepth; +}; + +} // end of namespace Assimp + +#endif // AI_IMPROVECACHELOCALITY_H_INC diff --git a/libs/assimp/code/PostProcessing/JoinVerticesProcess.cpp b/libs/assimp/code/PostProcessing/JoinVerticesProcess.cpp new file mode 100644 index 0000000..184e8d7 --- /dev/null +++ b/libs/assimp/code/PostProcessing/JoinVerticesProcess.cpp @@ -0,0 +1,438 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the post processing step to join identical vertices + * for all imported meshes + */ + + +#ifndef ASSIMP_BUILD_NO_JOINVERTICES_PROCESS + +#include "JoinVerticesProcess.h" +#include "ProcessHelper.h" +#include <assimp/Vertex.h> +#include <assimp/TinyFormatter.h> +#include <stdio.h> +#include <unordered_set> + +using namespace Assimp; +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +JoinVerticesProcess::JoinVerticesProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +JoinVerticesProcess::~JoinVerticesProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool JoinVerticesProcess::IsActive( unsigned int pFlags) const +{ + return (pFlags & aiProcess_JoinIdenticalVertices) != 0; +} +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void JoinVerticesProcess::Execute( aiScene* pScene) +{ + ASSIMP_LOG_DEBUG("JoinVerticesProcess begin"); + + // get the total number of vertices BEFORE the step is executed + int iNumOldVertices = 0; + if (!DefaultLogger::isNullLogger()) { + for( unsigned int a = 0; a < pScene->mNumMeshes; a++) { + iNumOldVertices += pScene->mMeshes[a]->mNumVertices; + } + } + + // execute the step + int iNumVertices = 0; + for( unsigned int a = 0; a < pScene->mNumMeshes; a++) + iNumVertices += ProcessMesh( pScene->mMeshes[a],a); + + // if logging is active, print detailed statistics + if (!DefaultLogger::isNullLogger()) { + if (iNumOldVertices == iNumVertices) { + ASSIMP_LOG_DEBUG("JoinVerticesProcess finished "); + } else { + ASSIMP_LOG_INFO("JoinVerticesProcess finished | Verts in: ", iNumOldVertices, + " out: ", iNumVertices, " | ~", + ((iNumOldVertices - iNumVertices) / (float)iNumOldVertices) * 100.f ); + } + } + + pScene->mFlags |= AI_SCENE_FLAGS_NON_VERBOSE_FORMAT; +} + +namespace { + +bool areVerticesEqual(const Vertex &lhs, const Vertex &rhs, bool complex) +{ + // A little helper to find locally close vertices faster. + // Try to reuse the lookup table from the last step. + const static float epsilon = 1e-5f; + // Squared because we check against squared length of the vector difference + static const float squareEpsilon = epsilon * epsilon; + + // Square compare is useful for animeshes vertices compare + if ((lhs.position - rhs.position).SquareLength() > squareEpsilon) { + return false; + } + + // We just test the other attributes even if they're not present in the mesh. + // In this case they're initialized to 0 so the comparison succeeds. + // By this method the non-present attributes are effectively ignored in the comparison. + if ((lhs.normal - rhs.normal).SquareLength() > squareEpsilon) { + return false; + } + + if ((lhs.texcoords[0] - rhs.texcoords[0]).SquareLength() > squareEpsilon) { + return false; + } + + if ((lhs.tangent - rhs.tangent).SquareLength() > squareEpsilon) { + return false; + } + + if ((lhs.bitangent - rhs.bitangent).SquareLength() > squareEpsilon) { + return false; + } + + // Usually we won't have vertex colors or multiple UVs, so we can skip from here + // Actually this increases runtime performance slightly, at least if branch + // prediction is on our side. + if (complex) { + for (int i = 0; i < 8; i++) { + if (i > 0 && (lhs.texcoords[i] - rhs.texcoords[i]).SquareLength() > squareEpsilon) { + return false; + } + if (GetColorDifference(lhs.colors[i], rhs.colors[i]) > squareEpsilon) { + return false; + } + } + } + return true; +} + +template<class XMesh> +void updateXMeshVertices(XMesh *pMesh, std::vector<Vertex> &uniqueVertices) { + // replace vertex data with the unique data sets + pMesh->mNumVertices = (unsigned int)uniqueVertices.size(); + + // ---------------------------------------------------------------------------- + // NOTE - we're *not* calling Vertex::SortBack() because it would check for + // presence of every single vertex component once PER VERTEX. And our CPU + // dislikes branches, even if they're easily predictable. + // ---------------------------------------------------------------------------- + + // Position, if present (check made for aiAnimMesh) + if (pMesh->mVertices) + { + delete [] pMesh->mVertices; + pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; + for (unsigned int a = 0; a < pMesh->mNumVertices; a++) { + pMesh->mVertices[a] = uniqueVertices[a].position; + } + } + + // Normals, if present + if (pMesh->mNormals) + { + delete [] pMesh->mNormals; + pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + for( unsigned int a = 0; a < pMesh->mNumVertices; a++) { + pMesh->mNormals[a] = uniqueVertices[a].normal; + } + } + // Tangents, if present + if (pMesh->mTangents) + { + delete [] pMesh->mTangents; + pMesh->mTangents = new aiVector3D[pMesh->mNumVertices]; + for (unsigned int a = 0; a < pMesh->mNumVertices; a++) { + pMesh->mTangents[a] = uniqueVertices[a].tangent; + } + } + // Bitangents as well + if (pMesh->mBitangents) + { + delete [] pMesh->mBitangents; + pMesh->mBitangents = new aiVector3D[pMesh->mNumVertices]; + for (unsigned int a = 0; a < pMesh->mNumVertices; a++) { + pMesh->mBitangents[a] = uniqueVertices[a].bitangent; + } + } + // Vertex colors + for (unsigned int a = 0; pMesh->HasVertexColors(a); a++) + { + delete [] pMesh->mColors[a]; + pMesh->mColors[a] = new aiColor4D[pMesh->mNumVertices]; + for( unsigned int b = 0; b < pMesh->mNumVertices; b++) { + pMesh->mColors[a][b] = uniqueVertices[b].colors[a]; + } + } + // Texture coords + for (unsigned int a = 0; pMesh->HasTextureCoords(a); a++) + { + delete [] pMesh->mTextureCoords[a]; + pMesh->mTextureCoords[a] = new aiVector3D[pMesh->mNumVertices]; + for (unsigned int b = 0; b < pMesh->mNumVertices; b++) { + pMesh->mTextureCoords[a][b] = uniqueVertices[b].texcoords[a]; + } + } +} +} // namespace + +// ------------------------------------------------------------------------------------------------ +// Unites identical vertices in the given mesh +int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) +{ + static_assert( AI_MAX_NUMBER_OF_COLOR_SETS == 8, "AI_MAX_NUMBER_OF_COLOR_SETS == 8"); + static_assert( AI_MAX_NUMBER_OF_TEXTURECOORDS == 8, "AI_MAX_NUMBER_OF_TEXTURECOORDS == 8"); + + // Return early if we don't have any positions + if (!pMesh->HasPositions() || !pMesh->HasFaces()) { + return 0; + } + + // We should care only about used vertices, not all of them + // (this can happen due to original file vertices buffer being used by + // multiple meshes) + std::unordered_set<unsigned int> usedVertexIndices; + usedVertexIndices.reserve(pMesh->mNumVertices); + for( unsigned int a = 0; a < pMesh->mNumFaces; a++) + { + aiFace& face = pMesh->mFaces[a]; + for( unsigned int b = 0; b < face.mNumIndices; b++) { + usedVertexIndices.insert(face.mIndices[b]); + } + } + + // We'll never have more vertices afterwards. + std::vector<Vertex> uniqueVertices; + uniqueVertices.reserve( pMesh->mNumVertices); + + // For each vertex the index of the vertex it was replaced by. + // Since the maximal number of vertices is 2^31-1, the most significand bit can be used to mark + // whether a new vertex was created for the index (true) or if it was replaced by an existing + // unique vertex (false). This saves an additional std::vector<bool> and greatly enhances + // branching performance. + static_assert(AI_MAX_VERTICES == 0x7fffffff, "AI_MAX_VERTICES == 0x7fffffff"); + std::vector<unsigned int> replaceIndex( pMesh->mNumVertices, 0xffffffff); + + // float posEpsilonSqr; + SpatialSort *vertexFinder = nullptr; + SpatialSort _vertexFinder; + + typedef std::pair<SpatialSort,float> SpatPair; + if (shared) { + std::vector<SpatPair >* avf; + shared->GetProperty(AI_SPP_SPATIAL_SORT,avf); + if (avf) { + SpatPair& blubb = (*avf)[meshIndex]; + vertexFinder = &blubb.first; + // posEpsilonSqr = blubb.second; + } + } + if (!vertexFinder) { + // bad, need to compute it. + _vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof( aiVector3D)); + vertexFinder = &_vertexFinder; + // posEpsilonSqr = ComputePositionEpsilon(pMesh); + } + + // Again, better waste some bytes than a realloc ... + std::vector<unsigned int> verticesFound; + verticesFound.reserve(10); + + // Run an optimized code path if we don't have multiple UVs or vertex colors. + // This should yield false in more than 99% of all imports ... + const bool complex = ( pMesh->GetNumColorChannels() > 0 || pMesh->GetNumUVChannels() > 1); + const bool hasAnimMeshes = pMesh->mNumAnimMeshes > 0; + + // We'll never have more vertices afterwards. + std::vector<std::vector<Vertex>> uniqueAnimatedVertices; + if (hasAnimMeshes) { + uniqueAnimatedVertices.resize(pMesh->mNumAnimMeshes); + for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) { + uniqueAnimatedVertices[animMeshIndex].reserve(pMesh->mNumVertices); + } + } + + // Now check each vertex if it brings something new to the table + for( unsigned int a = 0; a < pMesh->mNumVertices; a++) { + if (usedVertexIndices.find(a) == usedVertexIndices.end()) { + continue; + } + + // collect the vertex data + Vertex v(pMesh,a); + + // collect all vertices that are close enough to the given position + vertexFinder->FindIdenticalPositions( v.position, verticesFound); + unsigned int matchIndex = 0xffffffff; + + // check all unique vertices close to the position if this vertex is already present among them + for( unsigned int b = 0; b < verticesFound.size(); b++) { + const unsigned int vidx = verticesFound[b]; + const unsigned int uidx = replaceIndex[ vidx]; + if( uidx & 0x80000000) + continue; + + const Vertex& uv = uniqueVertices[ uidx]; + + if (!areVerticesEqual(v, uv, complex)) { + continue; + } + + if (hasAnimMeshes) { + // If given vertex is animated, then it has to be preserver 1 to 1 (base mesh and animated mesh require same topology) + // NOTE: not doing this totaly breaks anim meshes as they don't have their own faces (they use pMesh->mFaces) + bool breaksAnimMesh = false; + for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) { + const Vertex& animatedUV = uniqueAnimatedVertices[animMeshIndex][ uidx]; + Vertex aniMeshVertex(pMesh->mAnimMeshes[animMeshIndex], a); + if (!areVerticesEqual(aniMeshVertex, animatedUV, complex)) { + breaksAnimMesh = true; + break; + } + } + if (breaksAnimMesh) { + continue; + } + } + + // we're still here -> this vertex perfectly matches our given vertex + matchIndex = uidx; + break; + } + + // found a replacement vertex among the uniques? + if( matchIndex != 0xffffffff) + { + // store where to found the matching unique vertex + replaceIndex[a] = matchIndex | 0x80000000; + } + else + { + // no unique vertex matches it up to now -> so add it + replaceIndex[a] = (unsigned int)uniqueVertices.size(); + uniqueVertices.push_back( v); + if (hasAnimMeshes) { + for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) { + Vertex aniMeshVertex(pMesh->mAnimMeshes[animMeshIndex], a); + uniqueAnimatedVertices[animMeshIndex].push_back(aniMeshVertex); + } + } + } + } + + if (!DefaultLogger::isNullLogger() && DefaultLogger::get()->getLogSeverity() == Logger::VERBOSE) { + ASSIMP_LOG_VERBOSE_DEBUG( + "Mesh ",meshIndex, + " (", + (pMesh->mName.length ? pMesh->mName.data : "unnamed"), + ") | Verts in: ",pMesh->mNumVertices, + " out: ", + uniqueVertices.size(), + " | ~", + ((pMesh->mNumVertices - uniqueVertices.size()) / (float)pMesh->mNumVertices) * 100.f, + "%" + ); + } + + updateXMeshVertices(pMesh, uniqueVertices); + if (hasAnimMeshes) { + for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) { + updateXMeshVertices(pMesh->mAnimMeshes[animMeshIndex], uniqueAnimatedVertices[animMeshIndex]); + } + } + + // adjust the indices in all faces + for( unsigned int a = 0; a < pMesh->mNumFaces; a++) + { + aiFace& face = pMesh->mFaces[a]; + for( unsigned int b = 0; b < face.mNumIndices; b++) { + face.mIndices[b] = replaceIndex[face.mIndices[b]] & ~0x80000000; + } + } + + // adjust bone vertex weights. + for( int a = 0; a < (int)pMesh->mNumBones; a++) { + aiBone* bone = pMesh->mBones[a]; + std::vector<aiVertexWeight> newWeights; + newWeights.reserve( bone->mNumWeights); + + if (nullptr != bone->mWeights) { + for ( unsigned int b = 0; b < bone->mNumWeights; b++ ) { + const aiVertexWeight& ow = bone->mWeights[ b ]; + // if the vertex is a unique one, translate it + if ( !( replaceIndex[ ow.mVertexId ] & 0x80000000 ) ) { + aiVertexWeight nw; + nw.mVertexId = replaceIndex[ ow.mVertexId ]; + nw.mWeight = ow.mWeight; + newWeights.push_back( nw ); + } + } + } else { + ASSIMP_LOG_ERROR( "X-Export: aiBone shall contain weights, but pointer to them is nullptr." ); + } + + if (newWeights.size() > 0) { + // kill the old and replace them with the translated weights + delete [] bone->mWeights; + bone->mNumWeights = (unsigned int)newWeights.size(); + + bone->mWeights = new aiVertexWeight[bone->mNumWeights]; + memcpy( bone->mWeights, &newWeights[0], bone->mNumWeights * sizeof( aiVertexWeight)); + } + } + return pMesh->mNumVertices; +} + +#endif // !! ASSIMP_BUILD_NO_JOINVERTICES_PROCESS diff --git a/libs/assimp/code/PostProcessing/JoinVerticesProcess.h b/libs/assimp/code/PostProcessing/JoinVerticesProcess.h new file mode 100644 index 0000000..f95236e --- /dev/null +++ b/libs/assimp/code/PostProcessing/JoinVerticesProcess.h @@ -0,0 +1,95 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to join identical vertices + on all imported meshes.*/ +#ifndef AI_JOINVERTICESPROCESS_H_INC +#define AI_JOINVERTICESPROCESS_H_INC + +#include "Common/BaseProcess.h" + +#include <assimp/types.h> + +struct aiMesh; + +namespace Assimp +{ + +// --------------------------------------------------------------------------- +/** The JoinVerticesProcess unites identical vertices in all imported meshes. + * By default the importer returns meshes where each face addressed its own + * set of vertices even if that means that identical vertices are stored multiple + * times. The JoinVerticesProcess finds these identical vertices and + * erases all but one of the copies. This usually reduces the number of vertices + * in a mesh by a serious amount and is the standard form to render a mesh. + */ +class ASSIMP_API JoinVerticesProcess : public BaseProcess { +public: + JoinVerticesProcess(); + ~JoinVerticesProcess(); + + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag field. + * @param pFlags The processing flags the importer was called with. A bitwise + * combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, false if not. + */ + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + /** Unites identical vertices in the given mesh. + * @param pMesh The mesh to process. + * @param meshIndex Index of the mesh to process + */ + int ProcessMesh( aiMesh* pMesh, unsigned int meshIndex); +}; + +} // end of namespace Assimp + +#endif // AI_CALCTANGENTSPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/LimitBoneWeightsProcess.cpp b/libs/assimp/code/PostProcessing/LimitBoneWeightsProcess.cpp new file mode 100644 index 0000000..63f6bf9 --- /dev/null +++ b/libs/assimp/code/PostProcessing/LimitBoneWeightsProcess.cpp @@ -0,0 +1,196 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +/** Implementation of the LimitBoneWeightsProcess post processing step */ + + +#include "LimitBoneWeightsProcess.h" +#include <assimp/SmallVector.h> +#include <assimp/StringUtils.h> +#include <assimp/postprocess.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/scene.h> +#include <stdio.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +LimitBoneWeightsProcess::LimitBoneWeightsProcess() +{ + mMaxWeights = AI_LMW_MAX_WEIGHTS; +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +LimitBoneWeightsProcess::~LimitBoneWeightsProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool LimitBoneWeightsProcess::IsActive( unsigned int pFlags) const +{ + return (pFlags & aiProcess_LimitBoneWeights) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void LimitBoneWeightsProcess::Execute( aiScene* pScene) +{ + ASSIMP_LOG_DEBUG("LimitBoneWeightsProcess begin"); + + for (unsigned int m = 0; m < pScene->mNumMeshes; ++m) { + ProcessMesh(pScene->mMeshes[m]); + } + + ASSIMP_LOG_DEBUG("LimitBoneWeightsProcess end"); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void LimitBoneWeightsProcess::SetupProperties(const Importer* pImp) +{ + // get the current value of the property + this->mMaxWeights = pImp->GetPropertyInteger(AI_CONFIG_PP_LBW_MAX_WEIGHTS,AI_LMW_MAX_WEIGHTS); +} + +// ------------------------------------------------------------------------------------------------ +// Unites identical vertices in the given mesh +void LimitBoneWeightsProcess::ProcessMesh(aiMesh* pMesh) +{ + if (!pMesh->HasBones()) + return; + + // collect all bone weights per vertex + typedef SmallVector<Weight,8> VertexWeightArray; + typedef std::vector<VertexWeightArray> WeightsPerVertex; + WeightsPerVertex vertexWeights(pMesh->mNumVertices); + size_t maxVertexWeights = 0; + + for (unsigned int b = 0; b < pMesh->mNumBones; ++b) + { + const aiBone* bone = pMesh->mBones[b]; + for (unsigned int w = 0; w < bone->mNumWeights; ++w) + { + const aiVertexWeight& vw = bone->mWeights[w]; + + if (vertexWeights.size() <= vw.mVertexId) + continue; + + vertexWeights[vw.mVertexId].push_back(Weight(b, vw.mWeight)); + maxVertexWeights = std::max(maxVertexWeights, vertexWeights[vw.mVertexId].size()); + } + } + + if (maxVertexWeights <= mMaxWeights) + return; + + unsigned int removed = 0, old_bones = pMesh->mNumBones; + + // now cut the weight count if it exceeds the maximum + for (WeightsPerVertex::iterator vit = vertexWeights.begin(); vit != vertexWeights.end(); ++vit) + { + if (vit->size() <= mMaxWeights) + continue; + + // more than the defined maximum -> first sort by weight in descending order. That's + // why we defined the < operator in such a weird way. + std::sort(vit->begin(), vit->end()); + + // now kill everything beyond the maximum count + unsigned int m = static_cast<unsigned int>(vit->size()); + vit->resize(mMaxWeights); + removed += static_cast<unsigned int>(m - vit->size()); + + // and renormalize the weights + float sum = 0.0f; + for(const Weight* it = vit->begin(); it != vit->end(); ++it) { + sum += it->mWeight; + } + if (0.0f != sum) { + const float invSum = 1.0f / sum; + for(Weight* it = vit->begin(); it != vit->end(); ++it) { + it->mWeight *= invSum; + } + } + } + + // clear weight count for all bone + for (unsigned int a = 0; a < pMesh->mNumBones; ++a) + { + pMesh->mBones[a]->mNumWeights = 0; + } + + // rebuild the vertex weight array for all bones + for (unsigned int a = 0; a < vertexWeights.size(); ++a) + { + const VertexWeightArray& vw = vertexWeights[a]; + for (const Weight* it = vw.begin(); it != vw.end(); ++it) + { + aiBone* bone = pMesh->mBones[it->mBone]; + bone->mWeights[bone->mNumWeights++] = aiVertexWeight(a, it->mWeight); + } + } + + // remove empty bones + unsigned int writeBone = 0; + + for (unsigned int readBone = 0; readBone< pMesh->mNumBones; ++readBone) + { + aiBone* bone = pMesh->mBones[readBone]; + if (bone->mNumWeights > 0) + { + pMesh->mBones[writeBone++] = bone; + } + else + { + delete bone; + } + } + pMesh->mNumBones = writeBone; + + if (!DefaultLogger::isNullLogger()) { + ASSIMP_LOG_INFO("Removed ", removed, " weights. Input bones: ", old_bones, ". Output bones: ", pMesh->mNumBones); + } +} diff --git a/libs/assimp/code/PostProcessing/LimitBoneWeightsProcess.h b/libs/assimp/code/PostProcessing/LimitBoneWeightsProcess.h new file mode 100644 index 0000000..22d286b --- /dev/null +++ b/libs/assimp/code/PostProcessing/LimitBoneWeightsProcess.h @@ -0,0 +1,138 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +/** Defines a post processing step to limit the number of bones affecting a single vertex. */ +#ifndef AI_LIMITBONEWEIGHTSPROCESS_H_INC +#define AI_LIMITBONEWEIGHTSPROCESS_H_INC + +#include "Common/BaseProcess.h" + +// Forward declarations +struct aiMesh; + +class LimitBoneWeightsTest; + +namespace Assimp { + +// NOTE: If you change these limits, don't forget to change the +// corresponding values in all Assimp ports + +// ********************************************************** +// Java: ConfigProperty.java, +// ConfigProperty.DEFAULT_BONE_WEIGHT_LIMIT +// ********************************************************** + +#if (!defined AI_LMW_MAX_WEIGHTS) +# define AI_LMW_MAX_WEIGHTS 0x4 +#endif // !! AI_LMW_MAX_WEIGHTS + +// --------------------------------------------------------------------------- +/** This post processing step limits the number of bones affecting a vertex +* to a certain maximum value. If a vertex is affected by more than that number +* of bones, the bone weight with the least influence on this vertex are removed. +* The other weights on this bone are then renormalized to assure the sum weight +* to be 1. +*/ +class ASSIMP_API LimitBoneWeightsProcess : public BaseProcess { +public: + LimitBoneWeightsProcess(); + ~LimitBoneWeightsProcess(); + + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag. + * @param pFlags The processing flags the importer was called with. + * A bitwise combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, + * false if not. + */ + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + /** Called prior to ExecuteOnScene(). + * The function is a request to the process to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer* pImp); + + // ------------------------------------------------------------------- + /** Limits the bone weight count for all vertices in the given mesh. + * @param pMesh The mesh to process. + */ + void ProcessMesh( aiMesh* pMesh); + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + /** Describes a bone weight on a vertex */ + struct Weight { + unsigned int mBone; ///< Index of the bone + float mWeight; ///< Weight of that bone on this vertex + Weight() AI_NO_EXCEPT + : mBone(0) + , mWeight(0.0f) { + // empty + } + + Weight( unsigned int pBone, float pWeight) + : mBone(pBone) + , mWeight(pWeight) { + // empty + } + + /** Comparison operator to sort bone weights by descending weight */ + bool operator < (const Weight& pWeight) const { + return mWeight > pWeight.mWeight; + } + }; + + /** Maximum number of bones influencing any single vertex. */ + unsigned int mMaxWeights; +}; + +} // end of namespace Assimp + +#endif // AI_LIMITBONEWEIGHTSPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/MakeVerboseFormat.cpp b/libs/assimp/code/PostProcessing/MakeVerboseFormat.cpp new file mode 100644 index 0000000..75e2a0f --- /dev/null +++ b/libs/assimp/code/PostProcessing/MakeVerboseFormat.cpp @@ -0,0 +1,231 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the post processing step "MakeVerboseFormat" +*/ + +#include "MakeVerboseFormat.h" +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +MakeVerboseFormatProcess::MakeVerboseFormatProcess() { + // nothing to do here +} +// ------------------------------------------------------------------------------------------------ +MakeVerboseFormatProcess::~MakeVerboseFormatProcess() { + // nothing to do here +} +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void MakeVerboseFormatProcess::Execute(aiScene *pScene) { + ai_assert(nullptr != pScene); + ASSIMP_LOG_DEBUG("MakeVerboseFormatProcess begin"); + + bool bHas = false; + for (unsigned int a = 0; a < pScene->mNumMeshes; a++) { + if (MakeVerboseFormat(pScene->mMeshes[a])) + bHas = true; + } + if (bHas) { + ASSIMP_LOG_INFO("MakeVerboseFormatProcess finished. There was much work to do ..."); + } else { + ASSIMP_LOG_DEBUG("MakeVerboseFormatProcess. There was nothing to do."); + } + + pScene->mFlags &= ~AI_SCENE_FLAGS_NON_VERBOSE_FORMAT; +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +bool MakeVerboseFormatProcess::MakeVerboseFormat(aiMesh *pcMesh) { + ai_assert(nullptr != pcMesh); + + unsigned int iOldNumVertices = pcMesh->mNumVertices; + const unsigned int iNumVerts = pcMesh->mNumFaces * 3; + + aiVector3D *pvPositions = new aiVector3D[iNumVerts]; + + aiVector3D *pvNormals = nullptr; + if (pcMesh->HasNormals()) { + pvNormals = new aiVector3D[iNumVerts]; + } + aiVector3D *pvTangents = nullptr, *pvBitangents = nullptr; + if (pcMesh->HasTangentsAndBitangents()) { + pvTangents = new aiVector3D[iNumVerts]; + pvBitangents = new aiVector3D[iNumVerts]; + } + + aiVector3D *apvTextureCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS] = { 0 }; + aiColor4D *apvColorSets[AI_MAX_NUMBER_OF_COLOR_SETS] = { 0 }; + + unsigned int p = 0; + while (pcMesh->HasTextureCoords(p)) + apvTextureCoords[p++] = new aiVector3D[iNumVerts]; + + p = 0; + while (pcMesh->HasVertexColors(p)) + apvColorSets[p++] = new aiColor4D[iNumVerts]; + + // allocate enough memory to hold output bones and vertex weights ... + std::vector<aiVertexWeight> *newWeights = new std::vector<aiVertexWeight>[pcMesh->mNumBones]; + for (unsigned int i = 0; i < pcMesh->mNumBones; ++i) { + newWeights[i].reserve(pcMesh->mBones[i]->mNumWeights * 3); + } + + // iterate through all faces and build a clean list + unsigned int iIndex = 0; + for (unsigned int a = 0; a < pcMesh->mNumFaces; ++a) { + aiFace *pcFace = &pcMesh->mFaces[a]; + for (unsigned int q = 0; q < pcFace->mNumIndices; ++q, ++iIndex) { + // need to build a clean list of bones, too + for (unsigned int i = 0; i < pcMesh->mNumBones; ++i) { + for (unsigned int boneIdx = 0; boneIdx < pcMesh->mBones[i]->mNumWeights; ++boneIdx) { + const aiVertexWeight &w = pcMesh->mBones[i]->mWeights[boneIdx]; + if (pcFace->mIndices[q] == w.mVertexId) { + aiVertexWeight wNew; + wNew.mVertexId = iIndex; + wNew.mWeight = w.mWeight; + newWeights[i].push_back(wNew); + } + } + } + + pvPositions[iIndex] = pcMesh->mVertices[pcFace->mIndices[q]]; + + if (pcMesh->HasNormals()) { + pvNormals[iIndex] = pcMesh->mNormals[pcFace->mIndices[q]]; + } + if (pcMesh->HasTangentsAndBitangents()) { + pvTangents[iIndex] = pcMesh->mTangents[pcFace->mIndices[q]]; + pvBitangents[iIndex] = pcMesh->mBitangents[pcFace->mIndices[q]]; + } + + unsigned int pp = 0; + while (pcMesh->HasTextureCoords(pp)) { + apvTextureCoords[pp][iIndex] = pcMesh->mTextureCoords[pp][pcFace->mIndices[q]]; + ++pp; + } + pp = 0; + while (pcMesh->HasVertexColors(pp)) { + apvColorSets[pp][iIndex] = pcMesh->mColors[pp][pcFace->mIndices[q]]; + ++pp; + } + pcFace->mIndices[q] = iIndex; + } + } + + // build output vertex weights + for (unsigned int i = 0; i < pcMesh->mNumBones; ++i) { + delete[] pcMesh->mBones[i]->mWeights; + if (!newWeights[i].empty()) { + pcMesh->mBones[i]->mWeights = new aiVertexWeight[newWeights[i].size()]; + pcMesh->mBones[i]->mNumWeights = static_cast<unsigned int>(newWeights[i].size()); + aiVertexWeight *weightToCopy = &(newWeights[i][0]); + memcpy(pcMesh->mBones[i]->mWeights, weightToCopy, + sizeof(aiVertexWeight) * newWeights[i].size()); + } else { + pcMesh->mBones[i]->mWeights = nullptr; + } + } + delete[] newWeights; + + // delete the old members + delete[] pcMesh->mVertices; + pcMesh->mVertices = pvPositions; + + p = 0; + while (pcMesh->HasTextureCoords(p)) { + delete[] pcMesh->mTextureCoords[p]; + pcMesh->mTextureCoords[p] = apvTextureCoords[p]; + ++p; + } + p = 0; + while (pcMesh->HasVertexColors(p)) { + delete[] pcMesh->mColors[p]; + pcMesh->mColors[p] = apvColorSets[p]; + ++p; + } + pcMesh->mNumVertices = iNumVerts; + + if (pcMesh->HasNormals()) { + delete[] pcMesh->mNormals; + pcMesh->mNormals = pvNormals; + } + if (pcMesh->HasTangentsAndBitangents()) { + delete[] pcMesh->mTangents; + pcMesh->mTangents = pvTangents; + delete[] pcMesh->mBitangents; + pcMesh->mBitangents = pvBitangents; + } + return (pcMesh->mNumVertices != iOldNumVertices); +} + +// ------------------------------------------------------------------------------------------------ +bool IsMeshInVerboseFormat(const aiMesh *mesh) { + // avoid slow vector<bool> specialization + std::vector<unsigned int> seen(mesh->mNumVertices, 0); + for (unsigned int i = 0; i < mesh->mNumFaces; ++i) { + const aiFace &f = mesh->mFaces[i]; + for (unsigned int j = 0; j < f.mNumIndices; ++j) { + if (++seen[f.mIndices[j]] == 2) { + // found a duplicate index + return false; + } + } + } + + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool MakeVerboseFormatProcess::IsVerboseFormat(const aiScene *pScene) { + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + if (!IsMeshInVerboseFormat(pScene->mMeshes[i])) { + return false; + } + } + + return true; +} diff --git a/libs/assimp/code/PostProcessing/MakeVerboseFormat.h b/libs/assimp/code/PostProcessing/MakeVerboseFormat.h new file mode 100644 index 0000000..6b81da6 --- /dev/null +++ b/libs/assimp/code/PostProcessing/MakeVerboseFormat.h @@ -0,0 +1,113 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to bring a given scene + into the verbose format that is expected by most postprocess steps. + This is the inverse of the "JoinIdenticalVertices" step. */ +#ifndef AI_MAKEVERBOSEFORMAT_H_INC +#define AI_MAKEVERBOSEFORMAT_H_INC + +#include "Common/BaseProcess.h" + +struct aiMesh; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** MakeVerboseFormatProcess: Class to convert an asset to the verbose + * format which is expected by most postprocess steps. + * + * This is the inverse of what the "JoinIdenticalVertices" step is doing. + * This step has no official flag (since it wouldn't make sense to run it + * during import). It is intended for applications intending to modify the + * returned aiScene. After this step has been executed, they can execute + * other postprocess steps on the data. The code might also be useful to + * quickly adapt code that doesn't result in a verbose representation of + * the scene data. + * The step has been added because it was required by the viewer, however + * it has been moved to the main library since others might find it + * useful, too. */ +class ASSIMP_API_WINONLY MakeVerboseFormatProcess : public BaseProcess +{ +public: + + + MakeVerboseFormatProcess(); + ~MakeVerboseFormatProcess(); + +public: + + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag field. + * @param pFlags The processing flags the importer was called with. A bitwise + * combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, false if not */ + bool IsActive( unsigned int /*pFlags*/ ) const + { + // NOTE: There is no direct flag that corresponds to + // this postprocess step. + return false; + } + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. */ + void Execute( aiScene* pScene); + +public: + + // ------------------------------------------------------------------- + /** Checks whether the scene is already in verbose format. + * @param pScene The data to check. + * @return true if the scene is already in verbose format. */ + static bool IsVerboseFormat(const aiScene* pScene); + +private: + + //! Apply the postprocess step to a given submesh + bool MakeVerboseFormat (aiMesh* pcMesh); +}; + +} // end of namespace Assimp + +#endif // !!AI_KILLNORMALPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/OptimizeGraph.cpp b/libs/assimp/code/PostProcessing/OptimizeGraph.cpp new file mode 100644 index 0000000..ea44eb3 --- /dev/null +++ b/libs/assimp/code/PostProcessing/OptimizeGraph.cpp @@ -0,0 +1,358 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 OptimizeGraph.cpp + * @brief Implementation of the aiProcess_OptimizGraph step + */ + +#ifndef ASSIMP_BUILD_NO_OPTIMIZEGRAPH_PROCESS + +#include "OptimizeGraph.h" +#include "ProcessHelper.h" +#include "ConvertToLHProcess.h" +#include <assimp/Exceptional.h> +#include <assimp/SceneCombiner.h> +#include <stdio.h> + +using namespace Assimp; + +#define AI_RESERVED_NODE_NAME "$Reserved_And_Evil" + +/* AI_OG_USE_HASHING enables the use of hashing to speed-up std::set lookups. + * The unhashed variant should be faster, except for *very* large data sets + */ +#ifdef AI_OG_USE_HASHING +// Use our standard hashing function to compute the hash +#define AI_OG_GETKEY(str) SuperFastHash(str.data, str.length) +#else +// Otherwise hope that std::string will utilize a static buffer +// for shorter node names. This would avoid endless heap copying. +#define AI_OG_GETKEY(str) std::string(str.data) +#endif + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +OptimizeGraphProcess::OptimizeGraphProcess() : + mScene(), + nodes_in(), + nodes_out(), + count_merged() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +OptimizeGraphProcess::~OptimizeGraphProcess() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool OptimizeGraphProcess::IsActive(unsigned int pFlags) const { + return (0 != (pFlags & aiProcess_OptimizeGraph)); +} + +// ------------------------------------------------------------------------------------------------ +// Setup properties for the post-processing step +void OptimizeGraphProcess::SetupProperties(const Importer *pImp) { + // Get value of AI_CONFIG_PP_OG_EXCLUDE_LIST + std::string tmp = pImp->GetPropertyString(AI_CONFIG_PP_OG_EXCLUDE_LIST, ""); + AddLockedNodeList(tmp); +} + +// ------------------------------------------------------------------------------------------------ +// Collect new children +void OptimizeGraphProcess::CollectNewChildren(aiNode *nd, std::list<aiNode *> &nodes) { + nodes_in += nd->mNumChildren; + + // Process children + std::list<aiNode *> child_nodes; + for (unsigned int i = 0; i < nd->mNumChildren; ++i) { + CollectNewChildren(nd->mChildren[i], child_nodes); + nd->mChildren[i] = nullptr; + } + + // Check whether we need this node; if not we can replace it by our own children (warn, danger of incest). + if (locked.find(AI_OG_GETKEY(nd->mName)) == locked.end()) { + for (std::list<aiNode *>::iterator it = child_nodes.begin(); it != child_nodes.end();) { + + if (locked.find(AI_OG_GETKEY((*it)->mName)) == locked.end()) { + (*it)->mTransformation = nd->mTransformation * (*it)->mTransformation; + nodes.push_back(*it); + + it = child_nodes.erase(it); + continue; + } + ++it; + } + + if (nd->mNumMeshes || !child_nodes.empty()) { + nodes.push_back(nd); + } else { + delete nd; /* bye, node */ + return; + } + } else { + + // Retain our current position in the hierarchy + nodes.push_back(nd); + + // Now check for possible optimizations in our list of child nodes. join as many as possible + aiNode *join_master = nullptr; + aiMatrix4x4 inv; + + const LockedSetType::const_iterator end = locked.end(); + + std::list<aiNode *> join; + for (std::list<aiNode *>::iterator it = child_nodes.begin(); it != child_nodes.end();) { + aiNode *child = *it; + if (child->mNumChildren == 0 && locked.find(AI_OG_GETKEY(child->mName)) == end) { + + // There may be no instanced meshes + unsigned int n = 0; + for (; n < child->mNumMeshes; ++n) { + if (meshes[child->mMeshes[n]] > 1) { + break; + } + } + if (n == child->mNumMeshes) { + if (!join_master) { + join_master = child; + inv = join_master->mTransformation; + inv.Inverse(); + } else { + child->mTransformation = inv * child->mTransformation; + + join.push_back(child); + it = child_nodes.erase(it); + continue; + } + } + } + ++it; + } + if (join_master && !join.empty()) { + join_master->mName.length = ::ai_snprintf(join_master->mName.data, MAXLEN, "$MergedNode_%u", count_merged++); + + unsigned int out_meshes = 0; + for (std::list<aiNode *>::const_iterator it = join.cbegin(); it != join.cend(); ++it) { + out_meshes += (*it)->mNumMeshes; + } + + // copy all mesh references in one array + if (out_meshes) { + unsigned int *meshIdxs = new unsigned int[out_meshes + join_master->mNumMeshes], *tmp = meshIdxs; + for (unsigned int n = 0; n < join_master->mNumMeshes; ++n) { + *tmp++ = join_master->mMeshes[n]; + } + + for (const aiNode *join_node : join) { + for (unsigned int n = 0; n < join_node->mNumMeshes; ++n) { + + *tmp = join_node->mMeshes[n]; + aiMesh *mesh = mScene->mMeshes[*tmp++]; + + // Assume the transformation is affine + // manually move the mesh into the right coordinate system + + // Check for odd negative scale (mirror) + if (join_node->mTransformation.Determinant() < 0) { + // Reverse the mesh face winding order + FlipWindingOrderProcess::ProcessMesh(mesh); + } + + // Update positions, normals and tangents + const aiMatrix3x3 IT = aiMatrix3x3(join_node->mTransformation).Inverse().Transpose(); + for (unsigned int a = 0; a < mesh->mNumVertices; ++a) { + + mesh->mVertices[a] *= join_node->mTransformation; + + if (mesh->HasNormals()) + mesh->mNormals[a] *= IT; + + if (mesh->HasTangentsAndBitangents()) { + mesh->mTangents[a] *= IT; + mesh->mBitangents[a] *= IT; + } + } + } + delete join_node; // bye, node + } + delete[] join_master->mMeshes; + join_master->mMeshes = meshIdxs; + join_master->mNumMeshes += out_meshes; + } + } + } + // reassign children if something changed + if (child_nodes.empty() || child_nodes.size() > nd->mNumChildren) { + + delete[] nd->mChildren; + + if (!child_nodes.empty()) { + nd->mChildren = new aiNode *[child_nodes.size()]; + } else + nd->mChildren = nullptr; + } + + nd->mNumChildren = static_cast<unsigned int>(child_nodes.size()); + + if (nd->mChildren) { + aiNode **tmp = nd->mChildren; + for (std::list<aiNode *>::iterator it = child_nodes.begin(); it != child_nodes.end(); ++it) { + aiNode *node = *tmp++ = *it; + node->mParent = nd; + } + } + + nodes_out += static_cast<unsigned int>(child_nodes.size()); +} + +// ------------------------------------------------------------------------------------------------ +// Execute the post-processing step on the given scene +void OptimizeGraphProcess::Execute(aiScene *pScene) { + ASSIMP_LOG_DEBUG("OptimizeGraphProcess begin"); + nodes_in = nodes_out = count_merged = 0; + mScene = pScene; + + meshes.resize(pScene->mNumMeshes, 0); + FindInstancedMeshes(pScene->mRootNode); + + // build a blacklist of identifiers. If the name of a node matches one of these, we won't touch it + locked.clear(); + for (std::list<std::string>::const_iterator it = locked_nodes.begin(); it != locked_nodes.end(); ++it) { +#ifdef AI_OG_USE_HASHING + locked.insert(SuperFastHash((*it).c_str())); +#else + locked.insert(*it); +#endif + } + + for (unsigned int i = 0; i < pScene->mNumAnimations; ++i) { + for (unsigned int a = 0; a < pScene->mAnimations[i]->mNumChannels; ++a) { + aiNodeAnim *anim = pScene->mAnimations[i]->mChannels[a]; + locked.insert(AI_OG_GETKEY(anim->mNodeName)); + } + } + + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + for (unsigned int a = 0; a < pScene->mMeshes[i]->mNumBones; ++a) { + + aiBone *bone = pScene->mMeshes[i]->mBones[a]; + locked.insert(AI_OG_GETKEY(bone->mName)); + + // HACK: Meshes referencing bones may not be transformed; we need to look them. + // The easiest way to do this is to increase their reference counters ... + meshes[i] += 2; + } + } + + for (unsigned int i = 0; i < pScene->mNumCameras; ++i) { + aiCamera *cam = pScene->mCameras[i]; + locked.insert(AI_OG_GETKEY(cam->mName)); + } + + for (unsigned int i = 0; i < pScene->mNumLights; ++i) { + aiLight *lgh = pScene->mLights[i]; + locked.insert(AI_OG_GETKEY(lgh->mName)); + } + + // Insert a dummy master node and make it read-only + aiNode *dummy_root = new aiNode(AI_RESERVED_NODE_NAME); + locked.insert(AI_OG_GETKEY(dummy_root->mName)); + + const aiString prev = pScene->mRootNode->mName; + pScene->mRootNode->mParent = dummy_root; + + dummy_root->mChildren = new aiNode *[dummy_root->mNumChildren = 1]; + dummy_root->mChildren[0] = pScene->mRootNode; + + // Do our recursive processing of scenegraph nodes. For each node collect + // a fully new list of children and allow their children to place themselves + // on the same hierarchy layer as their parents. + std::list<aiNode *> nodes; + CollectNewChildren(dummy_root, nodes); + + ai_assert(nodes.size() == 1); + + if (dummy_root->mNumChildren == 0) { + pScene->mRootNode = nullptr; + throw DeadlyImportError("After optimizing the scene graph, no data remains"); + } + + if (dummy_root->mNumChildren > 1) { + pScene->mRootNode = dummy_root; + + // Keep the dummy node but assign the name of the old root node to it + pScene->mRootNode->mName = prev; + } else { + + // Remove the dummy root node again. + pScene->mRootNode = dummy_root->mChildren[0]; + + dummy_root->mChildren[0] = nullptr; + delete dummy_root; + } + + pScene->mRootNode->mParent = nullptr; + if (!DefaultLogger::isNullLogger()) { + if (nodes_in != nodes_out) { + ASSIMP_LOG_INFO("OptimizeGraphProcess finished; Input nodes: ", nodes_in, ", Output nodes: ", nodes_out); + } else { + ASSIMP_LOG_DEBUG("OptimizeGraphProcess finished"); + } + } + meshes.clear(); + locked.clear(); +} + +// ------------------------------------------------------------------------------------------------ +// Build a LUT of all instanced meshes +void OptimizeGraphProcess::FindInstancedMeshes(aiNode *pNode) { + for (unsigned int i = 0; i < pNode->mNumMeshes; ++i) { + ++meshes[pNode->mMeshes[i]]; + } + + for (unsigned int i = 0; i < pNode->mNumChildren; ++i) + FindInstancedMeshes(pNode->mChildren[i]); +} + +#endif // !! ASSIMP_BUILD_NO_OPTIMIZEGRAPH_PROCESS diff --git a/libs/assimp/code/PostProcessing/OptimizeGraph.h b/libs/assimp/code/PostProcessing/OptimizeGraph.h new file mode 100644 index 0000000..f5caa13 --- /dev/null +++ b/libs/assimp/code/PostProcessing/OptimizeGraph.h @@ -0,0 +1,140 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 OptimizeGraph.h + * @brief Declares a post processing step to optimize the scenegraph + */ +#ifndef AI_OPTIMIZEGRAPHPROCESS_H_INC +#define AI_OPTIMIZEGRAPHPROCESS_H_INC + +#include "Common/BaseProcess.h" +#include "PostProcessing/ProcessHelper.h" + +#include <assimp/types.h> + +#include <set> + +// Forward declarations +struct aiMesh; + +class OptimizeGraphProcessTest; + +namespace Assimp { + +// ----------------------------------------------------------------------------- +/** @brief Postprocessing step to optimize the scenegraph + * + * The implementation tries to merge nodes, even if they use different + * transformations. Animations are preserved. + * + * @see aiProcess_OptimizeGraph for a detailed description of the + * algorithm being applied. + */ +class OptimizeGraphProcess : public BaseProcess { +public: + OptimizeGraphProcess(); + ~OptimizeGraphProcess(); + + // ------------------------------------------------------------------- + bool IsActive( unsigned int pFlags) const override; + + // ------------------------------------------------------------------- + void Execute( aiScene* pScene) override; + + // ------------------------------------------------------------------- + void SetupProperties(const Importer* pImp) override; + + // ------------------------------------------------------------------- + /** @brief Add a list of node names to be locked and not modified. + * @param in List of nodes. See #AI_CONFIG_PP_OG_EXCLUDE_LIST for + * format explanations. + */ + inline void AddLockedNodeList(std::string& in) { + ConvertListToStrings (in,locked_nodes); + } + + // ------------------------------------------------------------------- + /** @brief Add another node to be locked and not modified. + * @param name Name to be locked + */ + inline void AddLockedNode(std::string& name) { + locked_nodes.push_back(name); + } + + // ------------------------------------------------------------------- + /** @brief Remove a node from the list of locked nodes. + * @param name Name to be unlocked + */ + inline void RemoveLockedNode(std::string& name) { + locked_nodes.remove(name); + } + +protected: + void CollectNewChildren(aiNode* nd, std::list<aiNode*>& nodes); + void FindInstancedMeshes (aiNode* pNode); + +private: +#ifdef AI_OG_USE_HASHING + typedef std::set<unsigned int> LockedSetType; +#else + typedef std::set<std::string> LockedSetType; +#endif + + //! Scene we're working with + aiScene* mScene; + + //! List of locked names. Stored is the hash of the name + LockedSetType locked; + + //! List of nodes to be locked in addition to those with animations, lights or cameras assigned. + std::list<std::string> locked_nodes; + + //! Node counters for logging purposes + unsigned int nodes_in,nodes_out, count_merged; + + //! Reference counters for meshes + std::vector<unsigned int> meshes; +}; + +} // end of namespace Assimp + +#endif // AI_OPTIMIZEGRAPHPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/OptimizeMeshes.cpp b/libs/assimp/code/PostProcessing/OptimizeMeshes.cpp new file mode 100644 index 0000000..e624bb1 --- /dev/null +++ b/libs/assimp/code/PostProcessing/OptimizeMeshes.cpp @@ -0,0 +1,256 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 OptimizeMeshes.cpp + * @brief Implementation of the aiProcess_OptimizeMeshes step + */ + + +#ifndef ASSIMP_BUILD_NO_OPTIMIZEMESHES_PROCESS + + +#include "OptimizeMeshes.h" +#include "ProcessHelper.h" +#include <assimp/SceneCombiner.h> +#include <assimp/Exceptional.h> + +using namespace Assimp; + +static const unsigned int NotSet = 0xffffffff; +static const unsigned int DeadBeef = 0xdeadbeef; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +OptimizeMeshesProcess::OptimizeMeshesProcess() + : mScene() + , pts(false) + , max_verts( NotSet ) + , max_faces( NotSet ) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +OptimizeMeshesProcess::~OptimizeMeshesProcess() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool OptimizeMeshesProcess::IsActive( unsigned int pFlags) const +{ + // Our behaviour needs to be different if the SortByPType or SplitLargeMeshes + // steps are active. Thus we need to query their flags here and store the + // information, although we're breaking const-correctness. + // That's a serious design flaw, consider redesign. + if( 0 != (pFlags & aiProcess_OptimizeMeshes) ) { + pts = (0 != (pFlags & aiProcess_SortByPType)); + max_verts = ( 0 != ( pFlags & aiProcess_SplitLargeMeshes ) ) ? DeadBeef : max_verts; + return true; + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +// Setup properties for the post-processing step +void OptimizeMeshesProcess::SetupProperties(const Importer* pImp) +{ + if( max_verts == DeadBeef /* magic hack */ ) { + max_faces = pImp->GetPropertyInteger(AI_CONFIG_PP_SLM_TRIANGLE_LIMIT,AI_SLM_DEFAULT_MAX_TRIANGLES); + max_verts = pImp->GetPropertyInteger(AI_CONFIG_PP_SLM_VERTEX_LIMIT,AI_SLM_DEFAULT_MAX_VERTICES); + } +} + +// ------------------------------------------------------------------------------------------------ +// Execute step +void OptimizeMeshesProcess::Execute( aiScene* pScene) +{ + const unsigned int num_old = pScene->mNumMeshes; + if (num_old <= 1) { + ASSIMP_LOG_DEBUG("Skipping OptimizeMeshesProcess"); + return; + } + + ASSIMP_LOG_DEBUG("OptimizeMeshesProcess begin"); + mScene = pScene; + + // need to clear persistent members from previous runs + merge_list.resize( 0 ); + output.resize( 0 ); + + // ensure we have the right sizes + merge_list.reserve(pScene->mNumMeshes); + output.reserve(pScene->mNumMeshes); + + // Prepare lookup tables + meshes.resize(pScene->mNumMeshes); + FindInstancedMeshes(pScene->mRootNode); + if( max_verts == DeadBeef ) /* undo the magic hack */ + max_verts = NotSet; + + // ... instanced meshes are immediately processed and added to the output list + for (unsigned int i = 0, n = 0; i < pScene->mNumMeshes;++i) { + meshes[i].vertex_format = GetMeshVFormatUnique(pScene->mMeshes[i]); + + if (meshes[i].instance_cnt > 1 && meshes[i].output_id == NotSet ) { + meshes[i].output_id = n++; + output.push_back(mScene->mMeshes[i]); + } + } + + // and process all nodes in the scenegraph recursively + ProcessNode(pScene->mRootNode); + if (!output.size()) { + throw DeadlyImportError("OptimizeMeshes: No meshes remaining; there's definitely something wrong"); + } + + meshes.resize( 0 ); + ai_assert(output.size() <= num_old); + + mScene->mNumMeshes = static_cast<unsigned int>(output.size()); + std::copy(output.begin(),output.end(),mScene->mMeshes); + + if (output.size() != num_old) { + ASSIMP_LOG_DEBUG("OptimizeMeshesProcess finished. Input meshes: ", num_old, ", Output meshes: ", pScene->mNumMeshes); + } else { + ASSIMP_LOG_DEBUG( "OptimizeMeshesProcess finished" ); + } +} + +// ------------------------------------------------------------------------------------------------ +// Process meshes for a single node +void OptimizeMeshesProcess::ProcessNode( aiNode* pNode) +{ + for (unsigned int i = 0; i < pNode->mNumMeshes;++i) { + unsigned int& im = pNode->mMeshes[i]; + + if (meshes[im].instance_cnt > 1) { + im = meshes[im].output_id; + } + else { + merge_list.resize( 0 ); + unsigned int verts = 0, faces = 0; + + // Find meshes to merge with us + for (unsigned int a = i+1; a < pNode->mNumMeshes;++a) { + unsigned int am = pNode->mMeshes[a]; + if (meshes[am].instance_cnt == 1 && CanJoin(im,am,verts,faces)) { + + merge_list.push_back(mScene->mMeshes[am]); + verts += mScene->mMeshes[am]->mNumVertices; + faces += mScene->mMeshes[am]->mNumFaces; + + pNode->mMeshes[a] = pNode->mMeshes[pNode->mNumMeshes - 1]; + --pNode->mNumMeshes; + --a; + } + } + + // and merge all meshes which we found, replace the old ones + if (!merge_list.empty()) { + merge_list.push_back(mScene->mMeshes[im]); + + aiMesh* out; + SceneCombiner::MergeMeshes(&out,0,merge_list.begin(),merge_list.end()); + output.push_back(out); + } else { + output.push_back(mScene->mMeshes[im]); + } + im = static_cast<unsigned int>(output.size()-1); + } + } + + + for( unsigned int i = 0; i < pNode->mNumChildren; ++i ) { + ProcessNode( pNode->mChildren[ i ] ); + } +} + +// ------------------------------------------------------------------------------------------------ +// Check whether two meshes can be joined +bool OptimizeMeshesProcess::CanJoin ( unsigned int a, unsigned int b, unsigned int verts, unsigned int faces ) +{ + if (meshes[a].vertex_format != meshes[b].vertex_format) + return false; + + aiMesh* ma = mScene->mMeshes[a], *mb = mScene->mMeshes[b]; + + if ((NotSet != max_verts && verts+mb->mNumVertices > max_verts) || + (NotSet != max_faces && faces+mb->mNumFaces > max_faces)) { + return false; + } + + // Never merge unskinned meshes with skinned meshes + if (ma->mMaterialIndex != mb->mMaterialIndex || ma->HasBones() != mb->HasBones()) + return false; + + // Never merge meshes with different kinds of primitives if SortByPType did already + // do its work. We would destroy everything again ... + if (pts && ma->mPrimitiveTypes != mb->mPrimitiveTypes) + return false; + + // If both meshes are skinned, check whether we have many bones defined in both meshes. + // If yes, we can join them. + if (ma->HasBones()) { + // TODO + return false; + } + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Build a LUT of all instanced meshes +void OptimizeMeshesProcess::FindInstancedMeshes (aiNode* pNode) +{ + for( unsigned int i = 0; i < pNode->mNumMeshes; ++i ) { + ++meshes[ pNode->mMeshes[ i ] ].instance_cnt; + } + + for( unsigned int i = 0; i < pNode->mNumChildren; ++i ) { + FindInstancedMeshes( pNode->mChildren[ i ] ); + } +} + +// ------------------------------------------------------------------------------------------------ + +#endif // !! ASSIMP_BUILD_NO_OPTIMIZEMESHES_PROCESS diff --git a/libs/assimp/code/PostProcessing/OptimizeMeshes.h b/libs/assimp/code/PostProcessing/OptimizeMeshes.h new file mode 100644 index 0000000..b80f98d --- /dev/null +++ b/libs/assimp/code/PostProcessing/OptimizeMeshes.h @@ -0,0 +1,186 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 OptimizeMeshes.h + * @brief Declares a post processing step to join meshes, if possible + */ +#ifndef AI_OPTIMIZEMESHESPROCESS_H_INC +#define AI_OPTIMIZEMESHESPROCESS_H_INC + +#include "Common/BaseProcess.h" + +#include <assimp/types.h> + +#include <vector> + +struct aiMesh; +struct aiNode; +class OptimizeMeshesProcessTest; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** @brief Postprocessing step to optimize mesh usage + * + * The implementation looks for meshes that could be joined and joins them. + * Usually this will reduce the number of drawcalls. + * + * @note Instanced meshes are currently not processed. + */ +class OptimizeMeshesProcess : public BaseProcess { +public: + /// @brief The class constructor. + OptimizeMeshesProcess(); + + /// @brief The class destructor. + ~OptimizeMeshesProcess(); + + /** @brief Internal utility to store additional mesh info + */ + struct MeshInfo { + MeshInfo() AI_NO_EXCEPT + : instance_cnt(0) + , vertex_format(0) + , output_id(0xffffffff) { + // empty + } + + //! Number of times this mesh is referenced + unsigned int instance_cnt; + + //! Vertex format id + unsigned int vertex_format; + + //! Output ID + unsigned int output_id; + }; + +public: + // ------------------------------------------------------------------- + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + void SetupProperties(const Importer* pImp); + + + // ------------------------------------------------------------------- + /** @brief Specify whether you want meshes with different + * primitive types to be merged as well. + * + * IsActive() sets this property automatically to true if the + * aiProcess_SortByPType flag is found. + */ + void EnablePrimitiveTypeSorting(bool enable) { + pts = enable; + } + + // Getter + bool IsPrimitiveTypeSortingEnabled () const { + return pts; + } + + + // ------------------------------------------------------------------- + /** @brief Specify a maximum size of a single output mesh. + * + * If a single input mesh already exceeds this limit, it won't + * be split. + * @param verts Maximum number of vertices per mesh + * @param faces Maximum number of faces per mesh + */ + void SetPreferredMeshSizeLimit (unsigned int verts, unsigned int faces) + { + max_verts = verts; + max_faces = faces; + } + + +protected: + + // ------------------------------------------------------------------- + /** @brief Do the actual optimization on all meshes of this node + * @param pNode Node we're working with + */ + void ProcessNode( aiNode* pNode); + + // ------------------------------------------------------------------- + /** @brief Returns true if b can be joined with a + * + * @param verts Number of output verts up to now + * @param faces Number of output faces up to now + */ + bool CanJoin ( unsigned int a, unsigned int b, + unsigned int verts, unsigned int faces ); + + // ------------------------------------------------------------------- + /** @brief Find instanced meshes, for the moment we're excluding + * them from all optimizations + */ + void FindInstancedMeshes (aiNode* pNode); + +private: + + //! Scene we're working with + aiScene* mScene; + + //! Per mesh info + std::vector<MeshInfo> meshes; + + //! Output meshes + std::vector<aiMesh*> output; + + //! @see EnablePrimitiveTypeSorting + mutable bool pts; + + //! @see SetPreferredMeshSizeLimit + mutable unsigned int max_verts,max_faces; + + //! Temporary storage + std::vector<aiMesh*> merge_list; +}; + +} // end of namespace Assimp + +#endif // AI_CALCTANGENTSPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/PretransformVertices.cpp b/libs/assimp/code/PostProcessing/PretransformVertices.cpp new file mode 100644 index 0000000..ec7b878 --- /dev/null +++ b/libs/assimp/code/PostProcessing/PretransformVertices.cpp @@ -0,0 +1,688 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 PretransformVertices.cpp + * @brief Implementation of the "PretransformVertices" post processing step +*/ + +#include "PretransformVertices.h" +#include "ConvertToLHProcess.h" +#include "ProcessHelper.h" +#include <assimp/Exceptional.h> +#include <assimp/SceneCombiner.h> + +using namespace Assimp; + +// some array offsets +#define AI_PTVS_VERTEX 0x0 +#define AI_PTVS_FACE 0x1 + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +PretransformVertices::PretransformVertices() : + configKeepHierarchy(false), + configNormalize(false), + configTransform(false), + configTransformation(), + mConfigPointCloud(false) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +PretransformVertices::~PretransformVertices() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool PretransformVertices::IsActive(unsigned int pFlags) const { + return (pFlags & aiProcess_PreTransformVertices) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Setup import configuration +void PretransformVertices::SetupProperties(const Importer *pImp) { + // Get the current value of AI_CONFIG_PP_PTV_KEEP_HIERARCHY, AI_CONFIG_PP_PTV_NORMALIZE, + // AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION and AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION + configKeepHierarchy = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_KEEP_HIERARCHY, 0)); + configNormalize = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_NORMALIZE, 0)); + configTransform = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION, 0)); + + configTransformation = pImp->GetPropertyMatrix(AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION, aiMatrix4x4()); + + mConfigPointCloud = pImp->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS); +} + +// ------------------------------------------------------------------------------------------------ +// Count the number of nodes +unsigned int PretransformVertices::CountNodes(const aiNode *pcNode) const { + unsigned int iRet = 1; + for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) { + iRet += CountNodes(pcNode->mChildren[i]); + } + return iRet; +} + +// ------------------------------------------------------------------------------------------------ +// Get a bitwise combination identifying the vertex format of a mesh +unsigned int PretransformVertices::GetMeshVFormat(aiMesh *pcMesh) const { + // the vertex format is stored in aiMesh::mBones for later retrieval. + // there isn't a good reason to compute it a few hundred times + // from scratch. The pointer is unused as animations are lost + // during PretransformVertices. + if (pcMesh->mBones) + return (unsigned int)(uint64_t)pcMesh->mBones; + + const unsigned int iRet = GetMeshVFormatUnique(pcMesh); + + // store the value for later use + pcMesh->mBones = (aiBone **)(uint64_t)iRet; + return iRet; +} + +// ------------------------------------------------------------------------------------------------ +// Count the number of vertices in the whole scene and a given +// material index +void PretransformVertices::CountVerticesAndFaces(const aiScene *pcScene, const aiNode *pcNode, unsigned int iMat, + unsigned int iVFormat, unsigned int *piFaces, unsigned int *piVertices) const { + for (unsigned int i = 0; i < pcNode->mNumMeshes; ++i) { + aiMesh *pcMesh = pcScene->mMeshes[pcNode->mMeshes[i]]; + if (iMat == pcMesh->mMaterialIndex && iVFormat == GetMeshVFormat(pcMesh)) { + *piVertices += pcMesh->mNumVertices; + *piFaces += pcMesh->mNumFaces; + } + } + for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) { + CountVerticesAndFaces(pcScene, pcNode->mChildren[i], iMat, + iVFormat, piFaces, piVertices); + } +} + +// ------------------------------------------------------------------------------------------------ +// Collect vertex/face data +void PretransformVertices::CollectData(const aiScene *pcScene, const aiNode *pcNode, unsigned int iMat, + unsigned int iVFormat, aiMesh *pcMeshOut, + unsigned int aiCurrent[2], unsigned int *num_refs) const { + // No need to multiply if there's no transformation + const bool identity = pcNode->mTransformation.IsIdentity(); + for (unsigned int i = 0; i < pcNode->mNumMeshes; ++i) { + aiMesh *pcMesh = pcScene->mMeshes[pcNode->mMeshes[i]]; + if (iMat == pcMesh->mMaterialIndex && iVFormat == GetMeshVFormat(pcMesh)) { + // Decrement mesh reference counter + unsigned int &num_ref = num_refs[pcNode->mMeshes[i]]; + ai_assert(0 != num_ref); + --num_ref; + // Save the name of the last mesh + if (num_ref == 0) { + pcMeshOut->mName = pcMesh->mName; + } + + if (identity) { + // copy positions without modifying them + ::memcpy(pcMeshOut->mVertices + aiCurrent[AI_PTVS_VERTEX], + pcMesh->mVertices, + pcMesh->mNumVertices * sizeof(aiVector3D)); + + if (iVFormat & 0x2) { + // copy normals without modifying them + ::memcpy(pcMeshOut->mNormals + aiCurrent[AI_PTVS_VERTEX], + pcMesh->mNormals, + pcMesh->mNumVertices * sizeof(aiVector3D)); + } + if (iVFormat & 0x4) { + // copy tangents without modifying them + ::memcpy(pcMeshOut->mTangents + aiCurrent[AI_PTVS_VERTEX], + pcMesh->mTangents, + pcMesh->mNumVertices * sizeof(aiVector3D)); + // copy bitangents without modifying them + ::memcpy(pcMeshOut->mBitangents + aiCurrent[AI_PTVS_VERTEX], + pcMesh->mBitangents, + pcMesh->mNumVertices * sizeof(aiVector3D)); + } + } else { + // copy positions, transform them to worldspace + for (unsigned int n = 0; n < pcMesh->mNumVertices; ++n) { + pcMeshOut->mVertices[aiCurrent[AI_PTVS_VERTEX] + n] = pcNode->mTransformation * pcMesh->mVertices[n]; + } + aiMatrix4x4 mWorldIT = pcNode->mTransformation; + mWorldIT.Inverse().Transpose(); + + // TODO: implement Inverse() for aiMatrix3x3 + aiMatrix3x3 m = aiMatrix3x3(mWorldIT); + + if (iVFormat & 0x2) { + // copy normals, transform them to worldspace + for (unsigned int n = 0; n < pcMesh->mNumVertices; ++n) { + pcMeshOut->mNormals[aiCurrent[AI_PTVS_VERTEX] + n] = + (m * pcMesh->mNormals[n]).Normalize(); + } + } + if (iVFormat & 0x4) { + // copy tangents and bitangents, transform them to worldspace + for (unsigned int n = 0; n < pcMesh->mNumVertices; ++n) { + pcMeshOut->mTangents[aiCurrent[AI_PTVS_VERTEX] + n] = (m * pcMesh->mTangents[n]).Normalize(); + pcMeshOut->mBitangents[aiCurrent[AI_PTVS_VERTEX] + n] = (m * pcMesh->mBitangents[n]).Normalize(); + } + } + } + unsigned int p = 0; + while (iVFormat & (0x100 << p)) { + // copy texture coordinates + memcpy(pcMeshOut->mTextureCoords[p] + aiCurrent[AI_PTVS_VERTEX], + pcMesh->mTextureCoords[p], + pcMesh->mNumVertices * sizeof(aiVector3D)); + ++p; + } + p = 0; + while (iVFormat & (0x1000000 << p)) { + // copy vertex colors + memcpy(pcMeshOut->mColors[p] + aiCurrent[AI_PTVS_VERTEX], + pcMesh->mColors[p], + pcMesh->mNumVertices * sizeof(aiColor4D)); + ++p; + } + // now we need to copy all faces. since we will delete the source mesh afterwards, + // we don't need to reallocate the array of indices except if this mesh is + // referenced multiple times. + for (unsigned int planck = 0; planck < pcMesh->mNumFaces; ++planck) { + aiFace &f_src = pcMesh->mFaces[planck]; + aiFace &f_dst = pcMeshOut->mFaces[aiCurrent[AI_PTVS_FACE] + planck]; + + const unsigned int num_idx = f_src.mNumIndices; + + f_dst.mNumIndices = num_idx; + + unsigned int *pi; + if (!num_ref) { /* if last time the mesh is referenced -> no reallocation */ + pi = f_dst.mIndices = f_src.mIndices; + + // offset all vertex indices + for (unsigned int hahn = 0; hahn < num_idx; ++hahn) { + pi[hahn] += aiCurrent[AI_PTVS_VERTEX]; + } + } else { + pi = f_dst.mIndices = new unsigned int[num_idx]; + + // copy and offset all vertex indices + for (unsigned int hahn = 0; hahn < num_idx; ++hahn) { + pi[hahn] = f_src.mIndices[hahn] + aiCurrent[AI_PTVS_VERTEX]; + } + } + + // Update the mPrimitiveTypes member of the mesh + switch (pcMesh->mFaces[planck].mNumIndices) { + case 0x1: + pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + case 0x2: + pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + case 0x3: + pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + default: + pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + break; + }; + } + aiCurrent[AI_PTVS_VERTEX] += pcMesh->mNumVertices; + aiCurrent[AI_PTVS_FACE] += pcMesh->mNumFaces; + } + } + + // append all children of us + for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) { + CollectData(pcScene, pcNode->mChildren[i], iMat, + iVFormat, pcMeshOut, aiCurrent, num_refs); + } +} + +// ------------------------------------------------------------------------------------------------ +// Get a list of all vertex formats that occur for a given material index +// The output list contains duplicate elements +void PretransformVertices::GetVFormatList(const aiScene *pcScene, unsigned int iMat, + std::list<unsigned int> &aiOut) const { + for (unsigned int i = 0; i < pcScene->mNumMeshes; ++i) { + aiMesh *pcMesh = pcScene->mMeshes[i]; + if (iMat == pcMesh->mMaterialIndex) { + aiOut.push_back(GetMeshVFormat(pcMesh)); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Compute the absolute transformation matrices of each node +void PretransformVertices::ComputeAbsoluteTransform(aiNode *pcNode) { + if (pcNode->mParent) { + pcNode->mTransformation = pcNode->mParent->mTransformation * pcNode->mTransformation; + } + + for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) { + ComputeAbsoluteTransform(pcNode->mChildren[i]); + } +} + +// ------------------------------------------------------------------------------------------------ +// Apply the node transformation to a mesh +void PretransformVertices::ApplyTransform(aiMesh *mesh, const aiMatrix4x4 &mat) const { + // Check whether we need to transform the coordinates at all + if (!mat.IsIdentity()) { + + // Check for odd negative scale (mirror) + if (mesh->HasFaces() && mat.Determinant() < 0) { + // Reverse the mesh face winding order + FlipWindingOrderProcess::ProcessMesh(mesh); + } + + // Update positions + if (mesh->HasPositions()) { + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + mesh->mVertices[i] = mat * mesh->mVertices[i]; + } + } + + // Update normals and tangents + if (mesh->HasNormals() || mesh->HasTangentsAndBitangents()) { + const aiMatrix3x3 m = aiMatrix3x3(mat).Inverse().Transpose(); + + if (mesh->HasNormals()) { + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + mesh->mNormals[i] = (m * mesh->mNormals[i]).Normalize(); + } + } + if (mesh->HasTangentsAndBitangents()) { + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + mesh->mTangents[i] = (m * mesh->mTangents[i]).Normalize(); + mesh->mBitangents[i] = (m * mesh->mBitangents[i]).Normalize(); + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Simple routine to build meshes in worldspace, no further optimization +void PretransformVertices::BuildWCSMeshes(std::vector<aiMesh *> &out, aiMesh **in, + unsigned int numIn, aiNode *node) const { + // NOTE: + // aiMesh::mNumBones store original source mesh, or UINT_MAX if not a copy + // aiMesh::mBones store reference to abs. transform we multiplied with + + // process meshes + for (unsigned int i = 0; i < node->mNumMeshes; ++i) { + aiMesh *mesh = in[node->mMeshes[i]]; + + // check whether we can operate on this mesh + if (!mesh->mBones || *reinterpret_cast<aiMatrix4x4 *>(mesh->mBones) == node->mTransformation) { + // yes, we can. + mesh->mBones = reinterpret_cast<aiBone **>(&node->mTransformation); + mesh->mNumBones = UINT_MAX; + } else { + + // try to find us in the list of newly created meshes + for (unsigned int n = 0; n < out.size(); ++n) { + aiMesh *ctz = out[n]; + if (ctz->mNumBones == node->mMeshes[i] && *reinterpret_cast<aiMatrix4x4 *>(ctz->mBones) == node->mTransformation) { + + // ok, use this one. Update node mesh index + node->mMeshes[i] = numIn + n; + } + } + if (node->mMeshes[i] < numIn) { + // Worst case. Need to operate on a full copy of the mesh + ASSIMP_LOG_INFO("PretransformVertices: Copying mesh due to mismatching transforms"); + aiMesh *ntz; + + const unsigned int tmp = mesh->mNumBones; // + mesh->mNumBones = 0; + SceneCombiner::Copy(&ntz, mesh); + mesh->mNumBones = tmp; + + ntz->mNumBones = node->mMeshes[i]; + ntz->mBones = reinterpret_cast<aiBone **>(&node->mTransformation); + + out.push_back(ntz); + + node->mMeshes[i] = static_cast<unsigned int>(numIn + out.size() - 1); + } + } + } + + // call children + for (unsigned int i = 0; i < node->mNumChildren; ++i) + BuildWCSMeshes(out, in, numIn, node->mChildren[i]); +} + +// ------------------------------------------------------------------------------------------------ +// Reset transformation matrices to identity +void PretransformVertices::MakeIdentityTransform(aiNode *nd) const { + nd->mTransformation = aiMatrix4x4(); + + // call children + for (unsigned int i = 0; i < nd->mNumChildren; ++i) + MakeIdentityTransform(nd->mChildren[i]); +} + +// ------------------------------------------------------------------------------------------------ +// Build reference counters for all meshes +void PretransformVertices::BuildMeshRefCountArray(const aiNode *nd, unsigned int *refs) const { + for (unsigned int i = 0; i < nd->mNumMeshes; ++i) + refs[nd->mMeshes[i]]++; + + // call children + for (unsigned int i = 0; i < nd->mNumChildren; ++i) + BuildMeshRefCountArray(nd->mChildren[i], refs); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void PretransformVertices::Execute(aiScene *pScene) { + ASSIMP_LOG_DEBUG("PretransformVerticesProcess begin"); + + // Return immediately if we have no meshes + if (!pScene->mNumMeshes) + return; + + const unsigned int iOldMeshes = pScene->mNumMeshes; + const unsigned int iOldAnimationChannels = pScene->mNumAnimations; + const unsigned int iOldNodes = CountNodes(pScene->mRootNode); + + if (configTransform) { + pScene->mRootNode->mTransformation = configTransformation * pScene->mRootNode->mTransformation; + } + + // first compute absolute transformation matrices for all nodes + ComputeAbsoluteTransform(pScene->mRootNode); + + // Delete aiMesh::mBones for all meshes. The bones are + // removed during this step and we need the pointer as + // temporary storage + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + aiMesh *mesh = pScene->mMeshes[i]; + + for (unsigned int a = 0; a < mesh->mNumBones; ++a) + delete mesh->mBones[a]; + + delete[] mesh->mBones; + mesh->mBones = nullptr; + } + + // now build a list of output meshes + std::vector<aiMesh *> apcOutMeshes; + + // Keep scene hierarchy? It's an easy job in this case ... + // we go on and transform all meshes, if one is referenced by nodes + // with different absolute transformations a depth copy of the mesh + // is required. + if (configKeepHierarchy) { + + // Hack: store the matrix we're transforming a mesh with in aiMesh::mBones + BuildWCSMeshes(apcOutMeshes, pScene->mMeshes, pScene->mNumMeshes, pScene->mRootNode); + + // ... if new meshes have been generated, append them to the end of the scene + if (apcOutMeshes.size() > 0) { + aiMesh **npp = new aiMesh *[pScene->mNumMeshes + apcOutMeshes.size()]; + + memcpy(npp, pScene->mMeshes, sizeof(aiMesh *) * pScene->mNumMeshes); + memcpy(npp + pScene->mNumMeshes, &apcOutMeshes[0], sizeof(aiMesh *) * apcOutMeshes.size()); + + pScene->mNumMeshes += static_cast<unsigned int>(apcOutMeshes.size()); + delete[] pScene->mMeshes; + pScene->mMeshes = npp; + } + + // now iterate through all meshes and transform them to world-space + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + ApplyTransform(pScene->mMeshes[i], *reinterpret_cast<aiMatrix4x4 *>(pScene->mMeshes[i]->mBones)); + + // prevent improper destruction + pScene->mMeshes[i]->mBones = nullptr; + pScene->mMeshes[i]->mNumBones = 0; + } + } else { + apcOutMeshes.reserve(static_cast<size_t>(pScene->mNumMaterials) << 1u); + std::list<unsigned int> aiVFormats; + + std::vector<unsigned int> s(pScene->mNumMeshes, 0); + BuildMeshRefCountArray(pScene->mRootNode, &s[0]); + + for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) { + // get the list of all vertex formats for this material + aiVFormats.clear(); + GetVFormatList(pScene, i, aiVFormats); + aiVFormats.sort(); + aiVFormats.unique(); + for (std::list<unsigned int>::const_iterator j = aiVFormats.begin(); j != aiVFormats.end(); ++j) { + unsigned int iVertices = 0; + unsigned int iFaces = 0; + CountVerticesAndFaces(pScene, pScene->mRootNode, i, *j, &iFaces, &iVertices); + if (0 != iFaces && 0 != iVertices) { + apcOutMeshes.push_back(new aiMesh()); + aiMesh *pcMesh = apcOutMeshes.back(); + pcMesh->mNumFaces = iFaces; + pcMesh->mNumVertices = iVertices; + pcMesh->mFaces = new aiFace[iFaces]; + pcMesh->mVertices = new aiVector3D[iVertices]; + pcMesh->mMaterialIndex = i; + if ((*j) & 0x2) pcMesh->mNormals = new aiVector3D[iVertices]; + if ((*j) & 0x4) { + pcMesh->mTangents = new aiVector3D[iVertices]; + pcMesh->mBitangents = new aiVector3D[iVertices]; + } + iFaces = 0; + while ((*j) & (0x100 << iFaces)) { + pcMesh->mTextureCoords[iFaces] = new aiVector3D[iVertices]; + if ((*j) & (0x10000 << iFaces)) + pcMesh->mNumUVComponents[iFaces] = 3; + else + pcMesh->mNumUVComponents[iFaces] = 2; + iFaces++; + } + iFaces = 0; + while ((*j) & (0x1000000 << iFaces)) + pcMesh->mColors[iFaces++] = new aiColor4D[iVertices]; + + // fill the mesh ... + unsigned int aiTemp[2] = { 0, 0 }; + CollectData(pScene, pScene->mRootNode, i, *j, pcMesh, aiTemp, &s[0]); + } + } + } + + // If no meshes are referenced in the node graph it is possible that we get no output meshes. + if (apcOutMeshes.empty()) { + + throw DeadlyImportError("No output meshes: all meshes are orphaned and are not referenced by any nodes"); + } else { + // now delete all meshes in the scene and build a new mesh list + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + aiMesh *mesh = pScene->mMeshes[i]; + mesh->mNumBones = 0; + mesh->mBones = nullptr; + + // we're reusing the face index arrays. avoid destruction + for (unsigned int a = 0; a < mesh->mNumFaces; ++a) { + mesh->mFaces[a].mNumIndices = 0; + mesh->mFaces[a].mIndices = nullptr; + } + + delete mesh; + + // Invalidate the contents of the old mesh array. We will most + // likely have less output meshes now, so the last entries of + // the mesh array are not overridden. We set them to nullptr to + // make sure the developer gets notified when his application + // attempts to access these fields ... + mesh = nullptr; + } + + // It is impossible that we have more output meshes than + // input meshes, so we can easily reuse the old mesh array + pScene->mNumMeshes = (unsigned int)apcOutMeshes.size(); + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + pScene->mMeshes[i] = apcOutMeshes[i]; + } + } + } + + // remove all animations from the scene + for (unsigned int i = 0; i < pScene->mNumAnimations; ++i) + delete pScene->mAnimations[i]; + delete[] pScene->mAnimations; + + pScene->mAnimations = nullptr; + pScene->mNumAnimations = 0; + + // --- we need to keep all cameras and lights + for (unsigned int i = 0; i < pScene->mNumCameras; ++i) { + aiCamera *cam = pScene->mCameras[i]; + const aiNode *nd = pScene->mRootNode->FindNode(cam->mName); + ai_assert(nullptr != nd); + + // multiply all properties of the camera with the absolute + // transformation of the corresponding node + cam->mPosition = nd->mTransformation * cam->mPosition; + cam->mLookAt = aiMatrix3x3(nd->mTransformation) * cam->mLookAt; + cam->mUp = aiMatrix3x3(nd->mTransformation) * cam->mUp; + } + + for (unsigned int i = 0; i < pScene->mNumLights; ++i) { + aiLight *l = pScene->mLights[i]; + const aiNode *nd = pScene->mRootNode->FindNode(l->mName); + ai_assert(nullptr != nd); + + // multiply all properties of the camera with the absolute + // transformation of the corresponding node + l->mPosition = nd->mTransformation * l->mPosition; + l->mDirection = aiMatrix3x3(nd->mTransformation) * l->mDirection; + l->mUp = aiMatrix3x3(nd->mTransformation) * l->mUp; + } + + if (!configKeepHierarchy) { + + // now delete all nodes in the scene and build a new + // flat node graph with a root node and some level 1 children + aiNode *newRoot = new aiNode(); + newRoot->mName = pScene->mRootNode->mName; + delete pScene->mRootNode; + pScene->mRootNode = newRoot; + + if (1 == pScene->mNumMeshes && !pScene->mNumLights && !pScene->mNumCameras) { + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mRootNode->mMeshes[0] = 0; + } else { + pScene->mRootNode->mNumChildren = pScene->mNumMeshes + pScene->mNumLights + pScene->mNumCameras; + aiNode **nodes = pScene->mRootNode->mChildren = new aiNode *[pScene->mRootNode->mNumChildren]; + + // generate mesh nodes + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i, ++nodes) { + aiNode *pcNode = new aiNode(); + *nodes = pcNode; + pcNode->mParent = pScene->mRootNode; + pcNode->mName = pScene->mMeshes[i]->mName; + + // setup mesh indices + pcNode->mNumMeshes = 1; + pcNode->mMeshes = new unsigned int[1]; + pcNode->mMeshes[0] = i; + } + // generate light nodes + for (unsigned int i = 0; i < pScene->mNumLights; ++i, ++nodes) { + aiNode *pcNode = new aiNode(); + *nodes = pcNode; + pcNode->mParent = pScene->mRootNode; + pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "light_%u", i); + pScene->mLights[i]->mName = pcNode->mName; + } + // generate camera nodes + for (unsigned int i = 0; i < pScene->mNumCameras; ++i, ++nodes) { + aiNode *pcNode = new aiNode(); + *nodes = pcNode; + pcNode->mParent = pScene->mRootNode; + pcNode->mName.length = ::ai_snprintf(pcNode->mName.data, MAXLEN, "cam_%u", i); + pScene->mCameras[i]->mName = pcNode->mName; + } + } + } else { + // ... and finally set the transformation matrix of all nodes to identity + MakeIdentityTransform(pScene->mRootNode); + } + + if (configNormalize) { + // compute the boundary of all meshes + aiVector3D min, max; + MinMaxChooser<aiVector3D>()(min, max); + + for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) { + aiMesh *m = pScene->mMeshes[a]; + for (unsigned int i = 0; i < m->mNumVertices; ++i) { + min = std::min(m->mVertices[i], min); + max = std::max(m->mVertices[i], max); + } + } + + // find the dominant axis + aiVector3D d = max - min; + const ai_real div = std::max(d.x, std::max(d.y, d.z)) * ai_real(0.5); + + d = min + d * (ai_real)0.5; + for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) { + aiMesh *m = pScene->mMeshes[a]; + for (unsigned int i = 0; i < m->mNumVertices; ++i) { + m->mVertices[i] = (m->mVertices[i] - d) / div; + } + } + } + + // print statistics + if (!DefaultLogger::isNullLogger()) { + ASSIMP_LOG_DEBUG("PretransformVerticesProcess finished"); + + ASSIMP_LOG_INFO("Removed ", iOldNodes, " nodes and ", iOldAnimationChannels, " animation channels (", + CountNodes(pScene->mRootNode), " output nodes)"); + ASSIMP_LOG_INFO("Kept ", pScene->mNumLights, " lights and ", pScene->mNumCameras, " cameras."); + ASSIMP_LOG_INFO("Moved ", iOldMeshes, " meshes to WCS (number of output meshes: ", pScene->mNumMeshes, ")"); + } +} diff --git a/libs/assimp/code/PostProcessing/PretransformVertices.h b/libs/assimp/code/PostProcessing/PretransformVertices.h new file mode 100644 index 0000000..14e5139 --- /dev/null +++ b/libs/assimp/code/PostProcessing/PretransformVertices.h @@ -0,0 +1,166 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 PretransformVertices.h + * @brief Defines a post processing step to pretransform all + * vertices in the scenegraph + */ +#ifndef AI_PRETRANSFORMVERTICES_H_INC +#define AI_PRETRANSFORMVERTICES_H_INC + +#include "Common/BaseProcess.h" + +#include <assimp/mesh.h> + +#include <list> +#include <vector> + +// Forward declarations +struct aiNode; + +class PretransformVerticesTest; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** The PretransformVertices pre-transforms all vertices in the node tree + * and removes the whole graph. The output is a list of meshes, one for + * each material. +*/ +class ASSIMP_API PretransformVertices : public BaseProcess { +public: + PretransformVertices(); + ~PretransformVertices(); + + // ------------------------------------------------------------------- + // Check whether step is active + bool IsActive(unsigned int pFlags) const override; + + // ------------------------------------------------------------------- + // Execute step on a given scene + void Execute(aiScene *pScene) override; + + // ------------------------------------------------------------------- + // Setup import settings + void SetupProperties(const Importer *pImp) override; + + // ------------------------------------------------------------------- + /** @brief Toggle the 'keep hierarchy' option + * @param keep true for keep configuration. + */ + void KeepHierarchy(bool keep) { + configKeepHierarchy = keep; + } + + // ------------------------------------------------------------------- + /** @brief Check whether 'keep hierarchy' is currently enabled. + * @return ... + */ + bool IsHierarchyKept() const { + return configKeepHierarchy; + } + +private: + // ------------------------------------------------------------------- + // Count the number of nodes + unsigned int CountNodes(const aiNode *pcNode) const; + + // ------------------------------------------------------------------- + // Get a bitwise combination identifying the vertex format of a mesh + unsigned int GetMeshVFormat(aiMesh *pcMesh) const; + + // ------------------------------------------------------------------- + // Count the number of vertices in the whole scene and a given + // material index + void CountVerticesAndFaces(const aiScene *pcScene, const aiNode *pcNode, + unsigned int iMat, + unsigned int iVFormat, + unsigned int *piFaces, + unsigned int *piVertices) const; + + // ------------------------------------------------------------------- + // Collect vertex/face data + void CollectData(const aiScene *pcScene, const aiNode *pcNode, + unsigned int iMat, + unsigned int iVFormat, + aiMesh *pcMeshOut, + unsigned int aiCurrent[2], + unsigned int *num_refs) const; + + // ------------------------------------------------------------------- + // Get a list of all vertex formats that occur for a given material + // The output list contains duplicate elements + void GetVFormatList(const aiScene *pcScene, unsigned int iMat, + std::list<unsigned int> &aiOut) const; + + // ------------------------------------------------------------------- + // Compute the absolute transformation matrices of each node + void ComputeAbsoluteTransform(aiNode *pcNode); + + // ------------------------------------------------------------------- + // Simple routine to build meshes in worldspace, no further optimization + void BuildWCSMeshes(std::vector<aiMesh *> &out, aiMesh **in, + unsigned int numIn, aiNode *node) const; + + // ------------------------------------------------------------------- + // Apply the node transformation to a mesh + void ApplyTransform(aiMesh *mesh, const aiMatrix4x4 &mat) const; + + // ------------------------------------------------------------------- + // Reset transformation matrices to identity + void MakeIdentityTransform(aiNode *nd) const; + + // ------------------------------------------------------------------- + // Build reference counters for all meshes + void BuildMeshRefCountArray(const aiNode *nd, unsigned int *refs) const; + + //! Configuration option: keep scene hierarchy as long as possible + bool configKeepHierarchy; + bool configNormalize; + bool configTransform; + aiMatrix4x4 configTransformation; + bool mConfigPointCloud; +}; + +} // end of namespace Assimp + +#endif // !!AI_GENFACENORMALPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/ProcessHelper.cpp b/libs/assimp/code/PostProcessing/ProcessHelper.cpp new file mode 100644 index 0000000..6f0a798 --- /dev/null +++ b/libs/assimp/code/PostProcessing/ProcessHelper.cpp @@ -0,0 +1,383 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 ProcessHelper.cpp +/** Implement shared utility functions for postprocessing steps */ + +#include "ProcessHelper.h" + +#include <limits> + +namespace Assimp { + +// ------------------------------------------------------------------------------- +void ConvertListToStrings(const std::string &in, std::list<std::string> &out) { + const char *s = in.c_str(); + while (*s) { + SkipSpacesAndLineEnd(&s); + if (*s == '\'') { + const char *base = ++s; + while (*s != '\'') { + ++s; + if (*s == '\0') { + ASSIMP_LOG_ERROR("ConvertListToString: String list is ill-formatted"); + return; + } + } + out.push_back(std::string(base, (size_t)(s - base))); + ++s; + } else { + out.push_back(GetNextToken(s)); + } + } +} + +// ------------------------------------------------------------------------------- +void FindAABBTransformed(const aiMesh *mesh, aiVector3D &min, aiVector3D &max, + const aiMatrix4x4 &m) { + min = aiVector3D(ai_real(10e10), ai_real(10e10), ai_real(10e10)); + max = aiVector3D(ai_real(-10e10), ai_real(-10e10), ai_real(-10e10)); + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + const aiVector3D v = m * mesh->mVertices[i]; + min = std::min(v, min); + max = std::max(v, max); + } +} + +// ------------------------------------------------------------------------------- +void FindMeshCenter(aiMesh *mesh, aiVector3D &out, aiVector3D &min, aiVector3D &max) { + ArrayBounds(mesh->mVertices, mesh->mNumVertices, min, max); + out = min + (max - min) * (ai_real)0.5; +} + +// ------------------------------------------------------------------------------- +void FindSceneCenter(aiScene *scene, aiVector3D &out, aiVector3D &min, aiVector3D &max) { + if (nullptr == scene) { + return; + } + + if (0 == scene->mNumMeshes) { + return; + } + FindMeshCenter(scene->mMeshes[0], out, min, max); + for (unsigned int i = 1; i < scene->mNumMeshes; ++i) { + aiVector3D tout, tmin, tmax; + FindMeshCenter(scene->mMeshes[i], tout, tmin, tmax); + if (min[0] > tmin[0]) min[0] = tmin[0]; + if (min[1] > tmin[1]) min[1] = tmin[1]; + if (min[2] > tmin[2]) min[2] = tmin[2]; + if (max[0] < tmax[0]) max[0] = tmax[0]; + if (max[1] < tmax[1]) max[1] = tmax[1]; + if (max[2] < tmax[2]) max[2] = tmax[2]; + } + out = min + (max - min) * (ai_real)0.5; +} + +// ------------------------------------------------------------------------------- +void FindMeshCenterTransformed(aiMesh *mesh, aiVector3D &out, aiVector3D &min, + aiVector3D &max, const aiMatrix4x4 &m) { + FindAABBTransformed(mesh, min, max, m); + out = min + (max - min) * (ai_real)0.5; +} + +// ------------------------------------------------------------------------------- +void FindMeshCenter(aiMesh *mesh, aiVector3D &out) { + aiVector3D min, max; + FindMeshCenter(mesh, out, min, max); +} + +// ------------------------------------------------------------------------------- +void FindMeshCenterTransformed(aiMesh *mesh, aiVector3D &out, + const aiMatrix4x4 &m) { + aiVector3D min, max; + FindMeshCenterTransformed(mesh, out, min, max, m); +} + +// ------------------------------------------------------------------------------- +ai_real ComputePositionEpsilon(const aiMesh *pMesh) { + const ai_real epsilon = ai_real(1e-4); + + // calculate the position bounds so we have a reliable epsilon to check position differences against + aiVector3D minVec, maxVec; + ArrayBounds(pMesh->mVertices, pMesh->mNumVertices, minVec, maxVec); + return (maxVec - minVec).Length() * epsilon; +} + +// ------------------------------------------------------------------------------- +ai_real ComputePositionEpsilon(const aiMesh *const *pMeshes, size_t num) { + ai_assert(nullptr != pMeshes); + + const ai_real epsilon = ai_real(1e-4); + + // calculate the position bounds so we have a reliable epsilon to check position differences against + aiVector3D minVec, maxVec, mi, ma; + MinMaxChooser<aiVector3D>()(minVec, maxVec); + + for (size_t a = 0; a < num; ++a) { + const aiMesh *pMesh = pMeshes[a]; + ArrayBounds(pMesh->mVertices, pMesh->mNumVertices, mi, ma); + + minVec = std::min(minVec, mi); + maxVec = std::max(maxVec, ma); + } + return (maxVec - minVec).Length() * epsilon; +} + +// ------------------------------------------------------------------------------- +unsigned int GetMeshVFormatUnique(const aiMesh *pcMesh) { + ai_assert(nullptr != pcMesh); + + // FIX: the hash may never be 0. Otherwise a comparison against + // nullptr could be successful + unsigned int iRet = 1; + + // normals + if (pcMesh->HasNormals()) iRet |= 0x2; + // tangents and bitangents + if (pcMesh->HasTangentsAndBitangents()) iRet |= 0x4; + +#ifdef BOOST_STATIC_ASSERT + BOOST_STATIC_ASSERT(8 >= AI_MAX_NUMBER_OF_COLOR_SETS); + BOOST_STATIC_ASSERT(8 >= AI_MAX_NUMBER_OF_TEXTURECOORDS); +#endif + + // texture coordinates + unsigned int p = 0; + while (pcMesh->HasTextureCoords(p)) { + iRet |= (0x100 << p); + if (3 == pcMesh->mNumUVComponents[p]) + iRet |= (0x10000 << p); + + ++p; + } + // vertex colors + p = 0; + while (pcMesh->HasVertexColors(p)) + iRet |= (0x1000000 << p++); + return iRet; +} + +// ------------------------------------------------------------------------------- +VertexWeightTable *ComputeVertexBoneWeightTable(const aiMesh *pMesh) { + if (!pMesh || !pMesh->mNumVertices || !pMesh->mNumBones) { + return nullptr; + } + + VertexWeightTable *avPerVertexWeights = new VertexWeightTable[pMesh->mNumVertices]; + for (unsigned int i = 0; i < pMesh->mNumBones; ++i) { + + aiBone *bone = pMesh->mBones[i]; + for (unsigned int a = 0; a < bone->mNumWeights; ++a) { + const aiVertexWeight &weight = bone->mWeights[a]; + avPerVertexWeights[weight.mVertexId].push_back(std::pair<unsigned int, float>(i, weight.mWeight)); + } + } + return avPerVertexWeights; +} + +// ------------------------------------------------------------------------------- +const char *MappingTypeToString(aiTextureMapping in) { + switch (in) { + case aiTextureMapping_UV: + return "UV"; + case aiTextureMapping_BOX: + return "Box"; + case aiTextureMapping_SPHERE: + return "Sphere"; + case aiTextureMapping_CYLINDER: + return "Cylinder"; + case aiTextureMapping_PLANE: + return "Plane"; + case aiTextureMapping_OTHER: + return "Other"; + default: + break; + } + + ai_assert(false); + return "BUG"; +} + +// ------------------------------------------------------------------------------- +aiMesh *MakeSubmesh(const aiMesh *pMesh, const std::vector<unsigned int> &subMeshFaces, unsigned int subFlags) { + aiMesh *oMesh = new aiMesh(); + std::vector<unsigned int> vMap(pMesh->mNumVertices, UINT_MAX); + + size_t numSubVerts = 0; + size_t numSubFaces = subMeshFaces.size(); + + for (unsigned int i = 0; i < numSubFaces; i++) { + const aiFace &f = pMesh->mFaces[subMeshFaces[i]]; + + for (unsigned int j = 0; j < f.mNumIndices; j++) { + if (vMap[f.mIndices[j]] == UINT_MAX) { + vMap[f.mIndices[j]] = static_cast<unsigned int>(numSubVerts++); + } + } + } + + oMesh->mName = pMesh->mName; + + oMesh->mMaterialIndex = pMesh->mMaterialIndex; + oMesh->mPrimitiveTypes = pMesh->mPrimitiveTypes; + + // create all the arrays for this mesh if the old mesh contained them + + oMesh->mNumFaces = static_cast<unsigned int>(subMeshFaces.size()); + oMesh->mNumVertices = static_cast<unsigned int>(numSubVerts); + oMesh->mVertices = new aiVector3D[numSubVerts]; + if (pMesh->HasNormals()) { + oMesh->mNormals = new aiVector3D[numSubVerts]; + } + + if (pMesh->HasTangentsAndBitangents()) { + oMesh->mTangents = new aiVector3D[numSubVerts]; + oMesh->mBitangents = new aiVector3D[numSubVerts]; + } + + for (size_t a = 0; pMesh->HasTextureCoords(static_cast<unsigned int>(a)); ++a) { + oMesh->mTextureCoords[a] = new aiVector3D[numSubVerts]; + oMesh->mNumUVComponents[a] = pMesh->mNumUVComponents[a]; + } + + for (size_t a = 0; pMesh->HasVertexColors(static_cast<unsigned int>(a)); ++a) { + oMesh->mColors[a] = new aiColor4D[numSubVerts]; + } + + // and copy over the data, generating faces with linear indices along the way + oMesh->mFaces = new aiFace[numSubFaces]; + + for (unsigned int a = 0; a < numSubFaces; ++a) { + + const aiFace &srcFace = pMesh->mFaces[subMeshFaces[a]]; + aiFace &dstFace = oMesh->mFaces[a]; + dstFace.mNumIndices = srcFace.mNumIndices; + dstFace.mIndices = new unsigned int[dstFace.mNumIndices]; + + // accumulate linearly all the vertices of the source face + for (size_t b = 0; b < dstFace.mNumIndices; ++b) { + dstFace.mIndices[b] = vMap[srcFace.mIndices[b]]; + } + } + + for (unsigned int srcIndex = 0; srcIndex < pMesh->mNumVertices; ++srcIndex) { + unsigned int nvi = vMap[srcIndex]; + if (nvi == UINT_MAX) { + continue; + } + + oMesh->mVertices[nvi] = pMesh->mVertices[srcIndex]; + if (pMesh->HasNormals()) { + oMesh->mNormals[nvi] = pMesh->mNormals[srcIndex]; + } + + if (pMesh->HasTangentsAndBitangents()) { + oMesh->mTangents[nvi] = pMesh->mTangents[srcIndex]; + oMesh->mBitangents[nvi] = pMesh->mBitangents[srcIndex]; + } + for (size_t c = 0, cc = pMesh->GetNumUVChannels(); c < cc; ++c) { + oMesh->mTextureCoords[c][nvi] = pMesh->mTextureCoords[c][srcIndex]; + } + for (size_t c = 0, cc = pMesh->GetNumColorChannels(); c < cc; ++c) { + oMesh->mColors[c][nvi] = pMesh->mColors[c][srcIndex]; + } + } + + if (~subFlags & AI_SUBMESH_FLAGS_SANS_BONES) { + std::vector<unsigned int> subBones(pMesh->mNumBones, 0); + + for (unsigned int a = 0; a < pMesh->mNumBones; ++a) { + const aiBone *bone = pMesh->mBones[a]; + + for (unsigned int b = 0; b < bone->mNumWeights; b++) { + unsigned int v = vMap[bone->mWeights[b].mVertexId]; + + if (v != UINT_MAX) { + subBones[a]++; + } + } + } + + for (unsigned int a = 0; a < pMesh->mNumBones; ++a) { + if (subBones[a] > 0) { + oMesh->mNumBones++; + } + } + + if (oMesh->mNumBones) { + oMesh->mBones = new aiBone *[oMesh->mNumBones](); + unsigned int nbParanoia = oMesh->mNumBones; + + oMesh->mNumBones = 0; //rewind + + for (unsigned int a = 0; a < pMesh->mNumBones; ++a) { + if (subBones[a] == 0) { + continue; + } + aiBone *newBone = new aiBone; + oMesh->mBones[oMesh->mNumBones++] = newBone; + + const aiBone *bone = pMesh->mBones[a]; + + newBone->mName = bone->mName; + newBone->mOffsetMatrix = bone->mOffsetMatrix; + newBone->mWeights = new aiVertexWeight[subBones[a]]; + + for (unsigned int b = 0; b < bone->mNumWeights; b++) { + const unsigned int v = vMap[bone->mWeights[b].mVertexId]; + + if (v != UINT_MAX) { + aiVertexWeight w(v, bone->mWeights[b].mWeight); + newBone->mWeights[newBone->mNumWeights++] = w; + } + } + } + + ai_assert(nbParanoia == oMesh->mNumBones); + (void)nbParanoia; // remove compiler warning on release build + } + } + + return oMesh; +} + +} // namespace Assimp diff --git a/libs/assimp/code/PostProcessing/ProcessHelper.h b/libs/assimp/code/PostProcessing/ProcessHelper.h new file mode 100644 index 0000000..78df94c --- /dev/null +++ b/libs/assimp/code/PostProcessing/ProcessHelper.h @@ -0,0 +1,376 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ + +#ifndef AI_PROCESS_HELPER_H_INCLUDED +#define AI_PROCESS_HELPER_H_INCLUDED + +#include <assimp/anim.h> +#include <assimp/material.h> +#include <assimp/mesh.h> +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> + +#include "Common/BaseProcess.h" +#include <assimp/ParsingUtils.h> +#include <assimp/SpatialSort.h> + +#include <list> + +// ------------------------------------------------------------------------------- +// Some extensions to std namespace. Mainly std::min and std::max for all +// flat data types in the aiScene. They're used to quickly determine the +// min/max bounds of data arrays. +#ifdef __cplusplus +namespace std { + +// std::min for aiVector3D +template <typename TReal> +inline ::aiVector3t<TReal> min(const ::aiVector3t<TReal> &a, const ::aiVector3t<TReal> &b) { + return ::aiVector3t<TReal>(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +// std::max for aiVector3t<TReal> +template <typename TReal> +inline ::aiVector3t<TReal> max(const ::aiVector3t<TReal> &a, const ::aiVector3t<TReal> &b) { + return ::aiVector3t<TReal>(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +// std::min for aiVector2t<TReal> +template <typename TReal> +inline ::aiVector2t<TReal> min(const ::aiVector2t<TReal> &a, const ::aiVector2t<TReal> &b) { + return ::aiVector2t<TReal>(min(a.x, b.x), min(a.y, b.y)); +} + +// std::max for aiVector2t<TReal> +template <typename TReal> +inline ::aiVector2t<TReal> max(const ::aiVector2t<TReal> &a, const ::aiVector2t<TReal> &b) { + return ::aiVector2t<TReal>(max(a.x, b.x), max(a.y, b.y)); +} + +// std::min for aiColor4D +template <typename TReal> +inline ::aiColor4t<TReal> min(const ::aiColor4t<TReal> &a, const ::aiColor4t<TReal> &b) { + return ::aiColor4t<TReal>(min(a.r, b.r), min(a.g, b.g), min(a.b, b.b), min(a.a, b.a)); +} + +// std::max for aiColor4D +template <typename TReal> +inline ::aiColor4t<TReal> max(const ::aiColor4t<TReal> &a, const ::aiColor4t<TReal> &b) { + return ::aiColor4t<TReal>(max(a.r, b.r), max(a.g, b.g), max(a.b, b.b), max(a.a, b.a)); +} + +// std::min for aiQuaterniont<TReal> +template <typename TReal> +inline ::aiQuaterniont<TReal> min(const ::aiQuaterniont<TReal> &a, const ::aiQuaterniont<TReal> &b) { + return ::aiQuaterniont<TReal>(min(a.w, b.w), min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} + +// std::max for aiQuaterniont<TReal> +template <typename TReal> +inline ::aiQuaterniont<TReal> max(const ::aiQuaterniont<TReal> &a, const ::aiQuaterniont<TReal> &b) { + return ::aiQuaterniont<TReal>(max(a.w, b.w), max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +// std::min for aiVectorKey +inline ::aiVectorKey min(const ::aiVectorKey &a, const ::aiVectorKey &b) { + return ::aiVectorKey(min(a.mTime, b.mTime), min(a.mValue, b.mValue)); +} + +// std::max for aiVectorKey +inline ::aiVectorKey max(const ::aiVectorKey &a, const ::aiVectorKey &b) { + return ::aiVectorKey(max(a.mTime, b.mTime), max(a.mValue, b.mValue)); +} + +// std::min for aiQuatKey +inline ::aiQuatKey min(const ::aiQuatKey &a, const ::aiQuatKey &b) { + return ::aiQuatKey(min(a.mTime, b.mTime), min(a.mValue, b.mValue)); +} + +// std::max for aiQuatKey +inline ::aiQuatKey max(const ::aiQuatKey &a, const ::aiQuatKey &b) { + return ::aiQuatKey(max(a.mTime, b.mTime), max(a.mValue, b.mValue)); +} + +// std::min for aiVertexWeight +inline ::aiVertexWeight min(const ::aiVertexWeight &a, const ::aiVertexWeight &b) { + return ::aiVertexWeight(min(a.mVertexId, b.mVertexId),static_cast<ai_real>(min(a.mWeight, b.mWeight))); +} + +// std::max for aiVertexWeight +inline ::aiVertexWeight max(const ::aiVertexWeight &a, const ::aiVertexWeight &b) { + return ::aiVertexWeight(max(a.mVertexId, b.mVertexId), static_cast<ai_real>(max(a.mWeight, b.mWeight))); +} + +} // end namespace std +#endif // !! C++ + +namespace Assimp { + +// ------------------------------------------------------------------------------- +// Start points for ArrayBounds<T> for all supported Ts +template <typename T> +struct MinMaxChooser; + +template <> +struct MinMaxChooser<float> { + void operator()(float &min, float &max) { + max = -1e10f; + min = 1e10f; + } +}; +template <> +struct MinMaxChooser<double> { + void operator()(double &min, double &max) { + max = -1e10; + min = 1e10; + } +}; +template <> +struct MinMaxChooser<unsigned int> { + void operator()(unsigned int &min, unsigned int &max) { + max = 0; + min = (1u << (sizeof(unsigned int) * 8 - 1)); + } +}; + +template <typename T> +struct MinMaxChooser<aiVector3t<T>> { + void operator()(aiVector3t<T> &min, aiVector3t<T> &max) { + max = aiVector3t<T>(-1e10f, -1e10f, -1e10f); + min = aiVector3t<T>(1e10f, 1e10f, 1e10f); + } +}; +template <typename T> +struct MinMaxChooser<aiVector2t<T>> { + void operator()(aiVector2t<T> &min, aiVector2t<T> &max) { + max = aiVector2t<T>(-1e10f, -1e10f); + min = aiVector2t<T>(1e10f, 1e10f); + } +}; +template <typename T> +struct MinMaxChooser<aiColor4t<T>> { + void operator()(aiColor4t<T> &min, aiColor4t<T> &max) { + max = aiColor4t<T>(-1e10f, -1e10f, -1e10f, -1e10f); + min = aiColor4t<T>(1e10f, 1e10f, 1e10f, 1e10f); + } +}; + +template <typename T> +struct MinMaxChooser<aiQuaterniont<T>> { + void operator()(aiQuaterniont<T> &min, aiQuaterniont<T> &max) { + max = aiQuaterniont<T>(-1e10f, -1e10f, -1e10f, -1e10f); + min = aiQuaterniont<T>(1e10f, 1e10f, 1e10f, 1e10f); + } +}; + +template <> +struct MinMaxChooser<aiVectorKey> { + void operator()(aiVectorKey &min, aiVectorKey &max) { + MinMaxChooser<double>()(min.mTime, max.mTime); + MinMaxChooser<aiVector3D>()(min.mValue, max.mValue); + } +}; +template <> +struct MinMaxChooser<aiQuatKey> { + void operator()(aiQuatKey &min, aiQuatKey &max) { + MinMaxChooser<double>()(min.mTime, max.mTime); + MinMaxChooser<aiQuaternion>()(min.mValue, max.mValue); + } +}; + +template <> +struct MinMaxChooser<aiVertexWeight> { + void operator()(aiVertexWeight &min, aiVertexWeight &max) { + MinMaxChooser<unsigned int>()(min.mVertexId, max.mVertexId); + MinMaxChooser<ai_real>()(min.mWeight, max.mWeight); + } +}; + +// ------------------------------------------------------------------------------- +/** @brief Find the min/max values of an array of Ts + * @param in Input array + * @param size Number of elements to process + * @param[out] min minimum value + * @param[out] max maximum value + */ +template <typename T> +inline void ArrayBounds(const T *in, unsigned int size, T &min, T &max) { + MinMaxChooser<T>()(min, max); + for (unsigned int i = 0; i < size; ++i) { + min = std::min(in[i], min); + max = std::max(in[i], max); + } +} + +// ------------------------------------------------------------------------------- +/** Little helper function to calculate the quadratic difference + * of two colors. + * @param pColor1 First color + * @param pColor2 second color + * @return Quadratic color difference */ +inline ai_real GetColorDifference(const aiColor4D &pColor1, const aiColor4D &pColor2) { + const aiColor4D c(pColor1.r - pColor2.r, pColor1.g - pColor2.g, pColor1.b - pColor2.b, pColor1.a - pColor2.a); + return c.r * c.r + c.g * c.g + c.b * c.b + c.a * c.a; +} + +// ------------------------------------------------------------------------------- +/** @brief Extract single strings from a list of identifiers + * @param in Input string list. + * @param out Receives a list of clean output strings + * @sdee #AI_CONFIG_PP_OG_EXCLUDE_LIST */ +void ConvertListToStrings(const std::string &in, std::list<std::string> &out); + +// ------------------------------------------------------------------------------- +/** @brief Compute the AABB of a mesh after applying a given transform + * @param mesh Input mesh + * @param[out] min Receives minimum transformed vertex + * @param[out] max Receives maximum transformed vertex + * @param m Transformation matrix to be applied */ +void FindAABBTransformed(const aiMesh *mesh, aiVector3D &min, aiVector3D &max, const aiMatrix4x4 &m); + +// ------------------------------------------------------------------------------- +/** @brief Helper function to determine the 'real' center of a mesh + * + * That is the center of its axis-aligned bounding box. + * @param mesh Input mesh + * @param[out] min Minimum vertex of the mesh + * @param[out] max maximum vertex of the mesh + * @param[out] out Center point */ +void FindMeshCenter(aiMesh *mesh, aiVector3D &out, aiVector3D &min, aiVector3D &max); + +// ------------------------------------------------------------------------------- +/** @brief Helper function to determine the 'real' center of a scene + * + * That is the center of its axis-aligned bounding box. + * @param scene Input scene + * @param[out] min Minimum vertex of the scene + * @param[out] max maximum vertex of the scene + * @param[out] out Center point */ +void FindSceneCenter(aiScene *scene, aiVector3D &out, aiVector3D &min, aiVector3D &max); + +// ------------------------------------------------------------------------------- +// Helper function to determine the 'real' center of a mesh after applying a given transform +void FindMeshCenterTransformed(aiMesh *mesh, aiVector3D &out, aiVector3D &min, aiVector3D &max, const aiMatrix4x4 &m); + +// ------------------------------------------------------------------------------- +// Helper function to determine the 'real' center of a mesh +void FindMeshCenter(aiMesh *mesh, aiVector3D &out); + +// ------------------------------------------------------------------------------- +// Helper function to determine the 'real' center of a mesh after applying a given transform +void FindMeshCenterTransformed(aiMesh *mesh, aiVector3D &out, const aiMatrix4x4 &m); + +// ------------------------------------------------------------------------------- +// Compute a good epsilon value for position comparisons on a mesh +ai_real ComputePositionEpsilon(const aiMesh *pMesh); + +// ------------------------------------------------------------------------------- +// Compute a good epsilon value for position comparisons on a array of meshes +ai_real ComputePositionEpsilon(const aiMesh *const *pMeshes, size_t num); + +// ------------------------------------------------------------------------------- +// Compute an unique value for the vertex format of a mesh +unsigned int GetMeshVFormatUnique(const aiMesh *pcMesh); + +// defs for ComputeVertexBoneWeightTable() +using PerVertexWeight = std::pair<unsigned int, float>; +using VertexWeightTable = std::vector<PerVertexWeight>; + +// ------------------------------------------------------------------------------- +// Compute a per-vertex bone weight table +VertexWeightTable *ComputeVertexBoneWeightTable(const aiMesh *pMesh); + +// ------------------------------------------------------------------------------- +// Get a string for a given aiTextureMapping +const char *MappingTypeToString(aiTextureMapping in); + +// flags for MakeSubmesh() +#define AI_SUBMESH_FLAGS_SANS_BONES 0x1 + +// ------------------------------------------------------------------------------- +// Split a mesh given a list of faces to be contained in the sub mesh +aiMesh *MakeSubmesh(const aiMesh *superMesh, const std::vector<unsigned int> &subMeshFaces, unsigned int subFlags); + +// ------------------------------------------------------------------------------- +// Utility post-process step to share the spatial sort tree between +// all steps which use it to speedup its computations. +class ComputeSpatialSortProcess : public BaseProcess { + bool IsActive(unsigned int pFlags) const { + return nullptr != shared && 0 != (pFlags & (aiProcess_CalcTangentSpace | + aiProcess_GenNormals | aiProcess_JoinIdenticalVertices)); + } + + void Execute(aiScene *pScene) { + typedef std::pair<SpatialSort, ai_real> _Type; + ASSIMP_LOG_DEBUG("Generate spatially-sorted vertex cache"); + + std::vector<_Type> *p = new std::vector<_Type>(pScene->mNumMeshes); + std::vector<_Type>::iterator it = p->begin(); + + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i, ++it) { + aiMesh *mesh = pScene->mMeshes[i]; + _Type &blubb = *it; + blubb.first.Fill(mesh->mVertices, mesh->mNumVertices, sizeof(aiVector3D)); + blubb.second = ComputePositionEpsilon(mesh); + } + + shared->AddProperty(AI_SPP_SPATIAL_SORT, p); + } +}; + +// ------------------------------------------------------------------------------- +// ... and the same again to cleanup the whole stuff +class DestroySpatialSortProcess : public BaseProcess { + bool IsActive(unsigned int pFlags) const { + return nullptr != shared && 0 != (pFlags & (aiProcess_CalcTangentSpace | + aiProcess_GenNormals | aiProcess_JoinIdenticalVertices)); + } + + void Execute(aiScene * /*pScene*/) { + shared->RemoveProperty(AI_SPP_SPATIAL_SORT); + } +}; + +} // namespace Assimp + +#endif // !! AI_PROCESS_HELPER_H_INCLUDED diff --git a/libs/assimp/code/PostProcessing/RemoveRedundantMaterials.cpp b/libs/assimp/code/PostProcessing/RemoveRedundantMaterials.cpp new file mode 100644 index 0000000..ac2fa97 --- /dev/null +++ b/libs/assimp/code/PostProcessing/RemoveRedundantMaterials.cpp @@ -0,0 +1,224 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 RemoveRedundantMaterials.cpp + * @brief Implementation of the "RemoveRedundantMaterials" post processing step +*/ + +// internal headers + +#include "RemoveRedundantMaterials.h" +#include <assimp/ParsingUtils.h> +#include "ProcessHelper.h" +#include "Material/MaterialSystem.h" +#include <assimp/Exceptional.h> +#include <stdio.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +RemoveRedundantMatsProcess::RemoveRedundantMatsProcess() +: mConfigFixedMaterials() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +RemoveRedundantMatsProcess::~RemoveRedundantMatsProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool RemoveRedundantMatsProcess::IsActive( unsigned int pFlags) const +{ + return (pFlags & aiProcess_RemoveRedundantMaterials) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Setup import properties +void RemoveRedundantMatsProcess::SetupProperties(const Importer* pImp) +{ + // Get value of AI_CONFIG_PP_RRM_EXCLUDE_LIST + mConfigFixedMaterials = pImp->GetPropertyString(AI_CONFIG_PP_RRM_EXCLUDE_LIST,""); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void RemoveRedundantMatsProcess::Execute( aiScene* pScene) +{ + ASSIMP_LOG_DEBUG("RemoveRedundantMatsProcess begin"); + + unsigned int redundantRemoved = 0, unreferencedRemoved = 0; + if (pScene->mNumMaterials) + { + // Find out which materials are referenced by meshes + std::vector<bool> abReferenced(pScene->mNumMaterials,false); + for (unsigned int i = 0;i < pScene->mNumMeshes;++i) + abReferenced[pScene->mMeshes[i]->mMaterialIndex] = true; + + // If a list of materials to be excluded was given, match the list with + // our imported materials and 'salt' all positive matches to ensure that + // we get unique hashes later. + if (mConfigFixedMaterials.length()) { + + std::list<std::string> strings; + ConvertListToStrings(mConfigFixedMaterials,strings); + + for (unsigned int i = 0; i < pScene->mNumMaterials;++i) { + aiMaterial* mat = pScene->mMaterials[i]; + + aiString name; + mat->Get(AI_MATKEY_NAME,name); + + if (name.length) { + std::list<std::string>::const_iterator it = std::find(strings.begin(), strings.end(), name.data); + if (it != strings.end()) { + + // Our brilliant 'salt': A single material property with ~ as first + // character to mark it as internal and temporary. + const int dummy = 1; + ((aiMaterial*)mat)->AddProperty(&dummy,1,"~RRM.UniqueMaterial",0,0); + + // Keep this material even if no mesh references it + abReferenced[i] = true; + ASSIMP_LOG_VERBOSE_DEBUG( "Found positive match in exclusion list: \'", name.data, "\'"); + } + } + } + } + + // TODO: re-implement this algorithm to work in-place + unsigned int *aiMappingTable = new unsigned int[pScene->mNumMaterials]; + for ( unsigned int i=0; i<pScene->mNumMaterials; i++ ) { + aiMappingTable[ i ] = 0; + } + unsigned int iNewNum = 0; + + // Iterate through all materials and calculate a hash for them + // store all hashes in a list and so a quick search whether + // we do already have a specific hash. This allows us to + // determine which materials are identical. + uint32_t *aiHashes = new uint32_t[ pScene->mNumMaterials ];; + for (unsigned int i = 0; i < pScene->mNumMaterials;++i) + { + // No mesh is referencing this material, remove it. + if (!abReferenced[i]) { + ++unreferencedRemoved; + delete pScene->mMaterials[i]; + pScene->mMaterials[i] = nullptr; + continue; + } + + // Check all previously mapped materials for a matching hash. + // On a match we can delete this material and just make it ref to the same index. + uint32_t me = aiHashes[i] = ComputeMaterialHash(pScene->mMaterials[i]); + for (unsigned int a = 0; a < i;++a) + { + if (abReferenced[a] && me == aiHashes[a]) { + ++redundantRemoved; + me = 0; + aiMappingTable[i] = aiMappingTable[a]; + delete pScene->mMaterials[i]; + pScene->mMaterials[i] = nullptr; + break; + } + } + // This is a new material that is referenced, add to the map. + if (me) { + aiMappingTable[i] = iNewNum++; + } + } + // If the new material count differs from the original, + // we need to rebuild the material list and remap mesh material indexes. + if(iNewNum < 1) + throw DeadlyImportError("No materials remaining"); + if (iNewNum != pScene->mNumMaterials) { + ai_assert(iNewNum > 0); + aiMaterial** ppcMaterials = new aiMaterial*[iNewNum]; + ::memset(ppcMaterials,0,sizeof(void*)*iNewNum); + for (unsigned int p = 0; p < pScene->mNumMaterials;++p) + { + // if the material is not referenced ... remove it + if (!abReferenced[p]) { + continue; + } + + // generate new names for modified materials that had no names + const unsigned int idx = aiMappingTable[p]; + if (ppcMaterials[idx]) { + aiString sz; + if( ppcMaterials[idx]->Get(AI_MATKEY_NAME, sz) != AI_SUCCESS ) { + sz.length = ::ai_snprintf(sz.data,MAXLEN,"JoinedMaterial_#%u",p); + ((aiMaterial*)ppcMaterials[idx])->AddProperty(&sz,AI_MATKEY_NAME); + } + } else { + ppcMaterials[idx] = pScene->mMaterials[p]; + } + } + // update all material indices + for (unsigned int p = 0; p < pScene->mNumMeshes;++p) { + aiMesh* mesh = pScene->mMeshes[p]; + ai_assert(nullptr != mesh); + mesh->mMaterialIndex = aiMappingTable[mesh->mMaterialIndex]; + } + // delete the old material list + delete[] pScene->mMaterials; + pScene->mMaterials = ppcMaterials; + pScene->mNumMaterials = iNewNum; + } + // delete temporary storage + delete[] aiHashes; + delete[] aiMappingTable; + } + if (redundantRemoved == 0 && unreferencedRemoved == 0) + { + ASSIMP_LOG_DEBUG("RemoveRedundantMatsProcess finished "); + } + else + { + ASSIMP_LOG_INFO("RemoveRedundantMatsProcess finished. Removed ", redundantRemoved, " redundant and ", + unreferencedRemoved, " unused materials."); + } +} diff --git a/libs/assimp/code/PostProcessing/RemoveRedundantMaterials.h b/libs/assimp/code/PostProcessing/RemoveRedundantMaterials.h new file mode 100644 index 0000000..e8c1478 --- /dev/null +++ b/libs/assimp/code/PostProcessing/RemoveRedundantMaterials.h @@ -0,0 +1,103 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 RemoveRedundantMaterials.h + * @brief Defines a post processing step to remove redundant materials + */ +#ifndef AI_REMOVEREDUNDANTMATERIALS_H_INC +#define AI_REMOVEREDUNDANTMATERIALS_H_INC + +#include "Common/BaseProcess.h" +#include <assimp/mesh.h> + +class RemoveRedundantMatsTest; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** RemoveRedundantMatsProcess: Post-processing step to remove redundant + * materials from the imported scene. + */ +class ASSIMP_API RemoveRedundantMatsProcess : public BaseProcess { +public: + /// The default class constructor. + RemoveRedundantMatsProcess(); + + /// The class destructor. + ~RemoveRedundantMatsProcess(); + + // ------------------------------------------------------------------- + // Check whether step is active + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + // Execute step on a given scene + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + // Setup import settings + void SetupProperties(const Importer* pImp); + + // ------------------------------------------------------------------- + /** @brief Set list of fixed (inmutable) materials + * @param fixed See #AI_CONFIG_PP_RRM_EXCLUDE_LIST + */ + void SetFixedMaterialsString(const std::string& fixed = std::string()) { + mConfigFixedMaterials = fixed; + } + + // ------------------------------------------------------------------- + /** @brief Get list of fixed (inmutable) materials + * @return See #AI_CONFIG_PP_RRM_EXCLUDE_LIST + */ + const std::string& GetFixedMaterialsString() const { + return mConfigFixedMaterials; + } + +private: + //! Configuration option: list of all fixed materials + std::string mConfigFixedMaterials; +}; + +} // end of namespace Assimp + +#endif // !!AI_REMOVEREDUNDANTMATERIALS_H_INC diff --git a/libs/assimp/code/PostProcessing/RemoveVCProcess.cpp b/libs/assimp/code/PostProcessing/RemoveVCProcess.cpp new file mode 100644 index 0000000..1107736 --- /dev/null +++ b/libs/assimp/code/PostProcessing/RemoveVCProcess.cpp @@ -0,0 +1,307 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the post processing step to remove + * any parts of the mesh structure from the imported data. +*/ + +#include "RemoveVCProcess.h" +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +RemoveVCProcess::RemoveVCProcess() : + configDeleteFlags(), mScene() {} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +RemoveVCProcess::~RemoveVCProcess() {} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool RemoveVCProcess::IsActive(unsigned int pFlags) const { + return (pFlags & aiProcess_RemoveComponent) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Small helper function to delete all elements in a T** array using delete +template <typename T> +inline void ArrayDelete(T **&in, unsigned int &num) { + for (unsigned int i = 0; i < num; ++i) + delete in[i]; + + delete[] in; + in = nullptr; + num = 0; +} + +#if 0 +// ------------------------------------------------------------------------------------------------ +// Updates the node graph - removes all nodes which have the "remove" flag set and the +// "don't remove" flag not set. Nodes with meshes are never deleted. +bool UpdateNodeGraph(aiNode* node,std::list<aiNode*>& childsOfParent,bool root) +{ + bool b = false; + + std::list<aiNode*> mine; + for (unsigned int i = 0; i < node->mNumChildren;++i) + { + if(UpdateNodeGraph(node->mChildren[i],mine,false)) + b = true; + } + + // somewhat tricky ... mNumMeshes must be originally 0 and MSB2 may not be set, + // so we can do a simple comparison against MSB here + if (!root && AI_RC_UINT_MSB == node->mNumMeshes ) + { + // this node needs to be removed + if(node->mNumChildren) + { + childsOfParent.insert(childsOfParent.end(),mine.begin(),mine.end()); + + // set all children to nullptr to make sure they are not deleted when we delete ourself + for (unsigned int i = 0; i < node->mNumChildren;++i) + node->mChildren[i] = nullptr; + } + b = true; + delete node; + } + else + { + AI_RC_UNMASK(node->mNumMeshes); + childsOfParent.push_back(node); + + if (b) + { + // reallocate the array of our children here + node->mNumChildren = (unsigned int)mine.size(); + aiNode** const children = new aiNode*[mine.size()]; + aiNode** ptr = children; + + for (std::list<aiNode*>::iterator it = mine.begin(), end = mine.end(); + it != end; ++it) + { + *ptr++ = *it; + } + delete[] node->mChildren; + node->mChildren = children; + return false; + } + } + return b; +} +#endif + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void RemoveVCProcess::Execute(aiScene *pScene) { + ASSIMP_LOG_DEBUG("RemoveVCProcess begin"); + bool bHas = false; //,bMasked = false; + + mScene = pScene; + + // handle animations + if (configDeleteFlags & aiComponent_ANIMATIONS) { + + bHas = true; + ArrayDelete(pScene->mAnimations, pScene->mNumAnimations); + } + + // handle textures + if (configDeleteFlags & aiComponent_TEXTURES) { + bHas = true; + ArrayDelete(pScene->mTextures, pScene->mNumTextures); + } + + // handle materials + if (configDeleteFlags & aiComponent_MATERIALS && pScene->mNumMaterials) { + bHas = true; + for (unsigned int i = 1; i < pScene->mNumMaterials; ++i) + delete pScene->mMaterials[i]; + + pScene->mNumMaterials = 1; + aiMaterial *helper = (aiMaterial *)pScene->mMaterials[0]; + ai_assert(nullptr != helper); + helper->Clear(); + + // gray + aiColor3D clr(0.6f, 0.6f, 0.6f); + helper->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); + + // add a small ambient color value + clr = aiColor3D(0.05f, 0.05f, 0.05f); + helper->AddProperty(&clr, 1, AI_MATKEY_COLOR_AMBIENT); + + aiString s; + s.Set("Dummy_MaterialsRemoved"); + helper->AddProperty(&s, AI_MATKEY_NAME); + } + + // handle light sources + if (configDeleteFlags & aiComponent_LIGHTS) { + bHas = true; + ArrayDelete(pScene->mLights, pScene->mNumLights); + } + + // handle camneras + if (configDeleteFlags & aiComponent_CAMERAS) { + bHas = true; + ArrayDelete(pScene->mCameras, pScene->mNumCameras); + } + + // handle meshes + if (configDeleteFlags & aiComponent_MESHES) { + bHas = true; + ArrayDelete(pScene->mMeshes, pScene->mNumMeshes); + } else { + for (unsigned int a = 0; a < pScene->mNumMeshes; a++) { + if (ProcessMesh(pScene->mMeshes[a])) + bHas = true; + } + } + + // now check whether the result is still a full scene + if (!pScene->mNumMeshes || !pScene->mNumMaterials) { + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + ASSIMP_LOG_DEBUG("Setting AI_SCENE_FLAGS_INCOMPLETE flag"); + + // If we have no meshes anymore we should also clear another flag ... + if (!pScene->mNumMeshes) + pScene->mFlags &= ~AI_SCENE_FLAGS_NON_VERBOSE_FORMAT; + } + + if (bHas) { + ASSIMP_LOG_INFO("RemoveVCProcess finished. Data structure cleanup has been done."); + } else { + ASSIMP_LOG_DEBUG("RemoveVCProcess finished. Nothing to be done ..."); + } +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties for the step +void RemoveVCProcess::SetupProperties(const Importer *pImp) { + configDeleteFlags = pImp->GetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, 0x0); + if (!configDeleteFlags) { + ASSIMP_LOG_WARN("RemoveVCProcess: AI_CONFIG_PP_RVC_FLAGS is zero."); + } +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +bool RemoveVCProcess::ProcessMesh(aiMesh *pMesh) { + bool ret = false; + + // if all materials have been deleted let the material + // index of the mesh point to the created default material + if (configDeleteFlags & aiComponent_MATERIALS) + pMesh->mMaterialIndex = 0; + + // handle normals + if (configDeleteFlags & aiComponent_NORMALS && pMesh->mNormals) { + delete[] pMesh->mNormals; + pMesh->mNormals = nullptr; + ret = true; + } + + // handle tangents and bitangents + if (configDeleteFlags & aiComponent_TANGENTS_AND_BITANGENTS && pMesh->mTangents) { + delete[] pMesh->mTangents; + pMesh->mTangents = nullptr; + + delete[] pMesh->mBitangents; + pMesh->mBitangents = nullptr; + ret = true; + } + + // handle texture coordinates + bool b = (0 != (configDeleteFlags & aiComponent_TEXCOORDS)); + for (unsigned int i = 0, real = 0; real < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++real) { + if (!pMesh->mTextureCoords[i]) break; + if (configDeleteFlags & aiComponent_TEXCOORDSn(real) || b) { + delete[] pMesh->mTextureCoords[i]; + pMesh->mTextureCoords[i] = nullptr; + ret = true; + + if (!b) { + // collapse the rest of the array + for (unsigned int a = i + 1; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) + pMesh->mTextureCoords[a - 1] = pMesh->mTextureCoords[a]; + + pMesh->mTextureCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS - 1] = nullptr; + continue; + } + } + ++i; + } + + // handle vertex colors + b = (0 != (configDeleteFlags & aiComponent_COLORS)); + for (unsigned int i = 0, real = 0; real < AI_MAX_NUMBER_OF_COLOR_SETS; ++real) { + if (!pMesh->mColors[i]) break; + if (configDeleteFlags & aiComponent_COLORSn(i) || b) { + delete[] pMesh->mColors[i]; + pMesh->mColors[i] = nullptr; + ret = true; + + if (!b) { + // collapse the rest of the array + for (unsigned int a = i + 1; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) + pMesh->mColors[a - 1] = pMesh->mColors[a]; + + pMesh->mColors[AI_MAX_NUMBER_OF_COLOR_SETS - 1] = nullptr; + continue; + } + } + ++i; + } + + // handle bones + if (configDeleteFlags & aiComponent_BONEWEIGHTS && pMesh->mBones) { + ArrayDelete(pMesh->mBones, pMesh->mNumBones); + ret = true; + } + return ret; +} diff --git a/libs/assimp/code/PostProcessing/RemoveVCProcess.h b/libs/assimp/code/PostProcessing/RemoveVCProcess.h new file mode 100644 index 0000000..cf10868 --- /dev/null +++ b/libs/assimp/code/PostProcessing/RemoveVCProcess.h @@ -0,0 +1,124 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to remove specific parts of the scene */ +#ifndef AI_REMOVEVCPROCESS_H_INCLUDED +#define AI_REMOVEVCPROCESS_H_INCLUDED + +#include "Common/BaseProcess.h" + +#include <assimp/mesh.h> + +class RemoveVCProcessTest; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** RemoveVCProcess: Class to exclude specific parts of the data structure + * from further processing by removing them, +*/ +class ASSIMP_API RemoveVCProcess : public BaseProcess { +public: + /// The default class constructor. + RemoveVCProcess(); + + /// The class destructor. + ~RemoveVCProcess(); + + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag field. + * @param pFlags The processing flags the importer was called with. A bitwise + * combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, false if not. + */ + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + /** Called prior to ExecuteOnScene(). + * The function is a request to the process to update its configuration + * basing on the Importer's configuration property list. + */ + virtual void SetupProperties(const Importer* pImp); + + // ------------------------------------------------------------------- + /** Manually setup the configuration flags for the step + * + * @param Bitwise combination of the #aiComponent enumerated values. + */ + void SetDeleteFlags(unsigned int f) + { + configDeleteFlags = f; + } + + // ------------------------------------------------------------------- + /** Query the current configuration. + */ + unsigned int GetDeleteFlags() const + { + return configDeleteFlags; + } + +private: + + bool ProcessMesh (aiMesh* pcMesh); + + /** Configuration flag + */ + unsigned int configDeleteFlags; + + /** The scene we're working with + */ + aiScene* mScene; +}; + +// --------------------------------------------------------------------------- + +} // end of namespace Assimp + +#endif // !!AI_REMOVEVCPROCESS_H_INCLUDED diff --git a/libs/assimp/code/PostProcessing/ScaleProcess.cpp b/libs/assimp/code/PostProcessing/ScaleProcess.cpp new file mode 100644 index 0000000..0090f8d --- /dev/null +++ b/libs/assimp/code/PostProcessing/ScaleProcess.cpp @@ -0,0 +1,208 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#include "ScaleProcess.h" + +#include <assimp/scene.h> +#include <assimp/postprocess.h> +#include <assimp/BaseImporter.h> + +namespace Assimp { + +ScaleProcess::ScaleProcess() +: BaseProcess() +, mScale( AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT ) { +} + +ScaleProcess::~ScaleProcess() { + // empty +} + +void ScaleProcess::setScale( ai_real scale ) { + mScale = scale; +} + +ai_real ScaleProcess::getScale() const { + return mScale; +} + +bool ScaleProcess::IsActive( unsigned int pFlags ) const { + return ( pFlags & aiProcess_GlobalScale ) != 0; +} + +void ScaleProcess::SetupProperties( const Importer* pImp ) { + // User scaling + mScale = pImp->GetPropertyFloat( AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY, 1.0f ); + + // File scaling * Application Scaling + float importerScale = pImp->GetPropertyFloat( AI_CONFIG_APP_SCALE_KEY, 1.0f ); + + // apply scale to the scale + // helps prevent bugs with backward compatibility for anyone using normal scaling. + mScale *= importerScale; +} + +void ScaleProcess::Execute( aiScene* pScene ) { + if(mScale == 1.0f) { + return; // nothing to scale + } + + ai_assert( mScale != 0 ); + ai_assert( nullptr != pScene ); + ai_assert( nullptr != pScene->mRootNode ); + + if ( nullptr == pScene ) { + return; + } + + if ( nullptr == pScene->mRootNode ) { + return; + } + + // Process animations and update position transform to new unit system + for( unsigned int animationID = 0; animationID < pScene->mNumAnimations; animationID++ ) + { + aiAnimation* animation = pScene->mAnimations[animationID]; + + for( unsigned int animationChannel = 0; animationChannel < animation->mNumChannels; animationChannel++) + { + aiNodeAnim* anim = animation->mChannels[animationChannel]; + + for( unsigned int posKey = 0; posKey < anim->mNumPositionKeys; posKey++) + { + aiVectorKey& vectorKey = anim->mPositionKeys[posKey]; + vectorKey.mValue *= mScale; + } + } + } + + for( unsigned int meshID = 0; meshID < pScene->mNumMeshes; meshID++) + { + aiMesh *mesh = pScene->mMeshes[meshID]; + + // Reconstruct mesh vertices to the new unit system + for( unsigned int vertexID = 0; vertexID < mesh->mNumVertices; vertexID++) + { + aiVector3D& vertex = mesh->mVertices[vertexID]; + vertex *= mScale; + } + + + // bone placement / scaling + for( unsigned int boneID = 0; boneID < mesh->mNumBones; boneID++) + { + // Reconstruct matrix by transform rather than by scale + // This prevent scale values being changed which can + // be meaningful in some cases + // like when you want the modeller to see 1:1 compatibility. + aiBone* bone = mesh->mBones[boneID]; + + aiVector3D pos, scale; + aiQuaternion rotation; + + bone->mOffsetMatrix.Decompose( scale, rotation, pos); + + aiMatrix4x4 translation; + aiMatrix4x4::Translation( pos * mScale, translation ); + + aiMatrix4x4 scaling; + aiMatrix4x4::Scaling( aiVector3D(scale), scaling ); + + aiMatrix4x4 RotMatrix = aiMatrix4x4 (rotation.GetMatrix()); + + bone->mOffsetMatrix = translation * RotMatrix * scaling; + } + + + // animation mesh processing + // convert by position rather than scale. + for( unsigned int animMeshID = 0; animMeshID < mesh->mNumAnimMeshes; animMeshID++) + { + aiAnimMesh * animMesh = mesh->mAnimMeshes[animMeshID]; + + for( unsigned int vertexID = 0; vertexID < animMesh->mNumVertices; vertexID++) + { + aiVector3D& vertex = animMesh->mVertices[vertexID]; + vertex *= mScale; + } + } + } + + traverseNodes( pScene->mRootNode ); +} + +void ScaleProcess::traverseNodes( aiNode *node, unsigned int nested_node_id ) { + applyScaling( node ); + + for( size_t i = 0; i < node->mNumChildren; i++) + { + // recurse into the tree until we are done! + traverseNodes( node->mChildren[i], nested_node_id+1 ); + } +} + +void ScaleProcess::applyScaling( aiNode *currentNode ) { + if ( nullptr != currentNode ) { + // Reconstruct matrix by transform rather than by scale + // This prevent scale values being changed which can + // be meaningful in some cases + // like when you want the modeller to + // see 1:1 compatibility. + + aiVector3D pos, scale; + aiQuaternion rotation; + currentNode->mTransformation.Decompose( scale, rotation, pos); + + aiMatrix4x4 translation; + aiMatrix4x4::Translation( pos * mScale, translation ); + + aiMatrix4x4 scaling; + + // note: we do not use mScale here, this is on purpose. + aiMatrix4x4::Scaling( scale, scaling ); + + aiMatrix4x4 RotMatrix = aiMatrix4x4 (rotation.GetMatrix()); + + currentNode->mTransformation = translation * RotMatrix * scaling; + } +} + +} // Namespace Assimp diff --git a/libs/assimp/code/PostProcessing/ScaleProcess.h b/libs/assimp/code/PostProcessing/ScaleProcess.h new file mode 100644 index 0000000..b6eb75d --- /dev/null +++ b/libs/assimp/code/PostProcessing/ScaleProcess.h @@ -0,0 +1,97 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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. + +---------------------------------------------------------------------- +*/ +#ifndef SCALE_PROCESS_H_ +#define SCALE_PROCESS_H_ + +#include "Common/BaseProcess.h" + +struct aiNode; + +#if (!defined AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT) +# define AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT 1.0f +#endif // !! AI_DEBONE_THRESHOLD + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** ScaleProcess: Class to rescale the whole model. + * Now rescales animations, bones, and blend shapes properly. + * Please note this will not write to 'scale' transform it will rewrite mesh + * and matrixes so that your scale values + * from your model package are preserved, so this is completely intentional + * bugs should be reported as soon as they are found. +*/ +class ASSIMP_API ScaleProcess : public BaseProcess { +public: + /// The default class constructor. + ScaleProcess(); + + /// The class destructor. + virtual ~ScaleProcess(); + + /// Will set the scale manually. + void setScale( ai_real scale ); + + /// Returns the current scaling value. + ai_real getScale() const; + + /// Overwritten, @see BaseProcess + virtual bool IsActive( unsigned int pFlags ) const; + + /// Overwritten, @see BaseProcess + virtual void SetupProperties( const Importer* pImp ); + + /// Overwritten, @see BaseProcess + virtual void Execute( aiScene* pScene ); + +private: + void traverseNodes( aiNode *currentNode, unsigned int nested_node_id = 0 ); + void applyScaling( aiNode *currentNode ); + +private: + ai_real mScale; +}; + +} // Namespace Assimp + + +#endif // SCALE_PROCESS_H_ diff --git a/libs/assimp/code/PostProcessing/SortByPTypeProcess.cpp b/libs/assimp/code/PostProcessing/SortByPTypeProcess.cpp new file mode 100644 index 0000000..c926809 --- /dev/null +++ b/libs/assimp/code/PostProcessing/SortByPTypeProcess.cpp @@ -0,0 +1,439 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the DeterminePTypeHelperProcess and + * SortByPTypeProcess post-process steps. +*/ + +// internal headers +#include "SortByPTypeProcess.h" +#include "ProcessHelper.h" +#include <assimp/Exceptional.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +SortByPTypeProcess::SortByPTypeProcess() : + mConfigRemoveMeshes(0) { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +SortByPTypeProcess::~SortByPTypeProcess() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool SortByPTypeProcess::IsActive(unsigned int pFlags) const { + return (pFlags & aiProcess_SortByPType) != 0; +} + +// ------------------------------------------------------------------------------------------------ +void SortByPTypeProcess::SetupProperties(const Importer *pImp) { + mConfigRemoveMeshes = pImp->GetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, 0); +} + +// ------------------------------------------------------------------------------------------------ +// Update changed meshes in all nodes +void UpdateNodes(const std::vector<unsigned int> &replaceMeshIndex, aiNode *node) { + if (node->mNumMeshes) { + unsigned int newSize = 0; + for (unsigned int m = 0; m < node->mNumMeshes; ++m) { + unsigned int add = node->mMeshes[m] << 2; + for (unsigned int i = 0; i < 4; ++i) { + if (UINT_MAX != replaceMeshIndex[add + i]) ++newSize; + } + } + if (!newSize) { + delete[] node->mMeshes; + node->mNumMeshes = 0; + node->mMeshes = nullptr; + } else { + // Try to reuse the old array if possible + unsigned int *newMeshes = (newSize > node->mNumMeshes ? new unsigned int[newSize] : node->mMeshes); + + for (unsigned int m = 0; m < node->mNumMeshes; ++m) { + unsigned int add = node->mMeshes[m] << 2; + for (unsigned int i = 0; i < 4; ++i) { + if (UINT_MAX != replaceMeshIndex[add + i]) + *newMeshes++ = replaceMeshIndex[add + i]; + } + } + if (newSize > node->mNumMeshes) + delete[] node->mMeshes; + + node->mMeshes = newMeshes - (node->mNumMeshes = newSize); + } + } + + // call all subnodes recursively + for (unsigned int m = 0; m < node->mNumChildren; ++m) + UpdateNodes(replaceMeshIndex, node->mChildren[m]); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void SortByPTypeProcess::Execute(aiScene *pScene) { + if (0 == pScene->mNumMeshes) { + ASSIMP_LOG_DEBUG("SortByPTypeProcess skipped, there are no meshes"); + return; + } + + ASSIMP_LOG_DEBUG("SortByPTypeProcess begin"); + + unsigned int aiNumMeshesPerPType[4] = { 0, 0, 0, 0 }; + + std::vector<aiMesh *> outMeshes; + outMeshes.reserve(static_cast<size_t>(pScene->mNumMeshes) << 1u); + + bool bAnyChanges = false; + + std::vector<unsigned int> replaceMeshIndex(pScene->mNumMeshes * 4, UINT_MAX); + std::vector<unsigned int>::iterator meshIdx = replaceMeshIndex.begin(); + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + aiMesh *const mesh = pScene->mMeshes[i]; + if (mesh->mPrimitiveTypes == 0) { + throw DeadlyImportError("Mesh with invalid primitive type: ", mesh->mName.C_Str()); + } + + // if there's just one primitive type in the mesh there's nothing to do for us + unsigned int num = 0; + if (mesh->mPrimitiveTypes & aiPrimitiveType_POINT) { + ++aiNumMeshesPerPType[0]; + ++num; + } + if (mesh->mPrimitiveTypes & aiPrimitiveType_LINE) { + ++aiNumMeshesPerPType[1]; + ++num; + } + if (mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE) { + ++aiNumMeshesPerPType[2]; + ++num; + } + if (mesh->mPrimitiveTypes & aiPrimitiveType_POLYGON) { + ++aiNumMeshesPerPType[3]; + ++num; + } + + if (1 == num) { + if (!(mConfigRemoveMeshes & mesh->mPrimitiveTypes)) { + *meshIdx = static_cast<unsigned int>(outMeshes.size()); + outMeshes.push_back(mesh); + } else { + delete mesh; + pScene->mMeshes[i] = nullptr; + bAnyChanges = true; + } + + meshIdx += 4; + continue; + } + bAnyChanges = true; + + // reuse our current mesh arrays for the submesh + // with the largest number of primitives + unsigned int aiNumPerPType[4] = { 0, 0, 0, 0 }; + aiFace *pFirstFace = mesh->mFaces; + aiFace *const pLastFace = pFirstFace + mesh->mNumFaces; + + unsigned int numPolyVerts = 0; + for (; pFirstFace != pLastFace; ++pFirstFace) { + if (pFirstFace->mNumIndices <= 3) + ++aiNumPerPType[pFirstFace->mNumIndices - 1]; + else { + ++aiNumPerPType[3]; + numPolyVerts += pFirstFace->mNumIndices; + } + } + + VertexWeightTable *avw = ComputeVertexBoneWeightTable(mesh); + for (unsigned int real = 0; real < 4; ++real, ++meshIdx) { + if (!aiNumPerPType[real] || mConfigRemoveMeshes & (1u << real)) { + continue; + } + + *meshIdx = (unsigned int)outMeshes.size(); + outMeshes.push_back(new aiMesh()); + aiMesh *out = outMeshes.back(); + + // the name carries the adjacency information between the meshes + out->mName = mesh->mName; + + // copy data members + out->mPrimitiveTypes = 1u << real; + out->mMaterialIndex = mesh->mMaterialIndex; + + // allocate output storage + out->mNumFaces = aiNumPerPType[real]; + aiFace *outFaces = out->mFaces = new aiFace[out->mNumFaces]; + + out->mNumVertices = (3 == real ? numPolyVerts : out->mNumFaces * (real + 1)); + + aiVector3D *vert(nullptr), *nor(nullptr), *tan(nullptr), *bit(nullptr); + aiVector3D *uv[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + aiColor4D *cols[AI_MAX_NUMBER_OF_COLOR_SETS]; + + if (mesh->mVertices) { + vert = out->mVertices = new aiVector3D[out->mNumVertices]; + } + + if (mesh->mNormals) { + nor = out->mNormals = new aiVector3D[out->mNumVertices]; + } + + if (mesh->mTangents) { + tan = out->mTangents = new aiVector3D[out->mNumVertices]; + bit = out->mBitangents = new aiVector3D[out->mNumVertices]; + } + + for (unsigned int j = 0; j < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++j) { + uv[j] = nullptr; + if (mesh->mTextureCoords[j]) { + uv[j] = out->mTextureCoords[j] = new aiVector3D[out->mNumVertices]; + } + + out->mNumUVComponents[j] = mesh->mNumUVComponents[j]; + } + + for (unsigned int j = 0; j < AI_MAX_NUMBER_OF_COLOR_SETS; ++j) { + cols[j] = nullptr; + if (mesh->mColors[j]) { + cols[j] = out->mColors[j] = new aiColor4D[out->mNumVertices]; + } + } + + if (mesh->mNumAnimMeshes > 0 && mesh->mAnimMeshes) { + out->mNumAnimMeshes = mesh->mNumAnimMeshes; + out->mAnimMeshes = new aiAnimMesh *[out->mNumAnimMeshes]; + } + + for (unsigned int j = 0; j < mesh->mNumAnimMeshes; ++j) { + aiAnimMesh *animMesh = mesh->mAnimMeshes[j]; + aiAnimMesh *outAnimMesh = out->mAnimMeshes[j] = new aiAnimMesh; + outAnimMesh->mNumVertices = out->mNumVertices; + if (animMesh->mVertices) + outAnimMesh->mVertices = new aiVector3D[out->mNumVertices]; + else + outAnimMesh->mVertices = nullptr; + if (animMesh->mNormals) + outAnimMesh->mNormals = new aiVector3D[out->mNumVertices]; + else + outAnimMesh->mNormals = nullptr; + if (animMesh->mTangents) + outAnimMesh->mTangents = new aiVector3D[out->mNumVertices]; + else + outAnimMesh->mTangents = nullptr; + if (animMesh->mBitangents) + outAnimMesh->mBitangents = new aiVector3D[out->mNumVertices]; + else + outAnimMesh->mBitangents = nullptr; + for (int jj = 0; jj < AI_MAX_NUMBER_OF_COLOR_SETS; ++jj) { + if (animMesh->mColors[jj]) + outAnimMesh->mColors[jj] = new aiColor4D[out->mNumVertices]; + else + outAnimMesh->mColors[jj] = nullptr; + } + for (int jj = 0; jj < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++jj) { + if (animMesh->mTextureCoords[jj]) + outAnimMesh->mTextureCoords[jj] = new aiVector3D[out->mNumVertices]; + else + outAnimMesh->mTextureCoords[jj] = nullptr; + } + } + + typedef std::vector<aiVertexWeight> TempBoneInfo; + std::vector<TempBoneInfo> tempBones(mesh->mNumBones); + + // try to guess how much storage we'll need + for (unsigned int q = 0; q < mesh->mNumBones; ++q) { + tempBones[q].reserve(mesh->mBones[q]->mNumWeights / (num - 1)); + } + + unsigned int outIdx = 0; + unsigned int amIdx = 0; // AnimMesh index + for (unsigned int m = 0; m < mesh->mNumFaces; ++m) { + aiFace &in = mesh->mFaces[m]; + if ((real == 3 && in.mNumIndices <= 3) || (real != 3 && in.mNumIndices != real + 1)) { + continue; + } + + outFaces->mNumIndices = in.mNumIndices; + outFaces->mIndices = in.mIndices; + + for (unsigned int q = 0; q < in.mNumIndices; ++q) { + unsigned int idx = in.mIndices[q]; + + // process all bones of this index + if (avw) { + VertexWeightTable &tbl = avw[idx]; + for (VertexWeightTable::const_iterator it = tbl.begin(), end = tbl.end(); + it != end; ++it) { + tempBones[(*it).first].push_back(aiVertexWeight(outIdx, (*it).second)); + } + } + + if (vert) { + *vert++ = mesh->mVertices[idx]; + //mesh->mVertices[idx].x = get_qnan(); + } + if (nor) *nor++ = mesh->mNormals[idx]; + if (tan) { + *tan++ = mesh->mTangents[idx]; + *bit++ = mesh->mBitangents[idx]; + } + + for (unsigned int pp = 0; pp < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++pp) { + if (!uv[pp]) break; + *uv[pp]++ = mesh->mTextureCoords[pp][idx]; + } + + for (unsigned int pp = 0; pp < AI_MAX_NUMBER_OF_COLOR_SETS; ++pp) { + if (!cols[pp]) break; + *cols[pp]++ = mesh->mColors[pp][idx]; + } + + unsigned int pp = 0; + for (; pp < mesh->mNumAnimMeshes; ++pp) { + aiAnimMesh *animMesh = mesh->mAnimMeshes[pp]; + aiAnimMesh *outAnimMesh = out->mAnimMeshes[pp]; + if (animMesh->mVertices) + outAnimMesh->mVertices[amIdx] = animMesh->mVertices[idx]; + if (animMesh->mNormals) + outAnimMesh->mNormals[amIdx] = animMesh->mNormals[idx]; + if (animMesh->mTangents) + outAnimMesh->mTangents[amIdx] = animMesh->mTangents[idx]; + if (animMesh->mBitangents) + outAnimMesh->mBitangents[amIdx] = animMesh->mBitangents[idx]; + for (int jj = 0; jj < AI_MAX_NUMBER_OF_COLOR_SETS; ++jj) { + if (animMesh->mColors[jj]) + outAnimMesh->mColors[jj][amIdx] = animMesh->mColors[jj][idx]; + } + for (int jj = 0; jj < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++jj) { + if (animMesh->mTextureCoords[jj]) + outAnimMesh->mTextureCoords[jj][amIdx] = animMesh->mTextureCoords[jj][idx]; + } + } + if (pp == mesh->mNumAnimMeshes) + amIdx++; + + in.mIndices[q] = outIdx++; + } + + in.mIndices = nullptr; + ++outFaces; + } + ai_assert(outFaces == out->mFaces + out->mNumFaces); + + // now generate output bones + for (unsigned int q = 0; q < mesh->mNumBones; ++q) { + if (!tempBones[q].empty()) { + ++out->mNumBones; + } + } + + if (out->mNumBones) { + out->mBones = new aiBone *[out->mNumBones]; + for (unsigned int q = 0, boneIdx = 0; q < mesh->mNumBones; ++q) { + TempBoneInfo &in = tempBones[q]; + if (in.empty()) { + continue; + } + + aiBone *srcBone = mesh->mBones[q]; + aiBone *bone = out->mBones[boneIdx] = new aiBone(); + + bone->mName = srcBone->mName; + bone->mOffsetMatrix = srcBone->mOffsetMatrix; + + bone->mNumWeights = (unsigned int)in.size(); + bone->mWeights = new aiVertexWeight[bone->mNumWeights]; + + ::memcpy(bone->mWeights, &in[0], bone->mNumWeights * sizeof(aiVertexWeight)); + + ++boneIdx; + } + } + } + + // delete the per-vertex bone weights table + delete[] avw; + + // delete the input mesh + delete mesh; + + // avoid invalid pointer + pScene->mMeshes[i] = nullptr; + } + + if (outMeshes.empty()) { + // This should not occur + throw DeadlyImportError("No meshes remaining"); + } + + // If we added at least one mesh process all nodes in the node + // graph and update their respective mesh indices. + if (bAnyChanges) { + UpdateNodes(replaceMeshIndex, pScene->mRootNode); + } + + if (outMeshes.size() != pScene->mNumMeshes) { + delete[] pScene->mMeshes; + pScene->mNumMeshes = (unsigned int)outMeshes.size(); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + } + ::memcpy(pScene->mMeshes, &outMeshes[0], pScene->mNumMeshes * sizeof(void *)); + + if (!DefaultLogger::isNullLogger()) { + char buffer[1024]; + ::ai_snprintf(buffer, 1024, "Points: %u%s, Lines: %u%s, Triangles: %u%s, Polygons: %u%s (Meshes, X = removed)", + aiNumMeshesPerPType[0], ((mConfigRemoveMeshes & aiPrimitiveType_POINT) ? "X" : ""), + aiNumMeshesPerPType[1], ((mConfigRemoveMeshes & aiPrimitiveType_LINE) ? "X" : ""), + aiNumMeshesPerPType[2], ((mConfigRemoveMeshes & aiPrimitiveType_TRIANGLE) ? "X" : ""), + aiNumMeshesPerPType[3], ((mConfigRemoveMeshes & aiPrimitiveType_POLYGON) ? "X" : "")); + ASSIMP_LOG_INFO(buffer); + ASSIMP_LOG_DEBUG("SortByPTypeProcess finished"); + } +} diff --git a/libs/assimp/code/PostProcessing/SortByPTypeProcess.h b/libs/assimp/code/PostProcessing/SortByPTypeProcess.h new file mode 100644 index 0000000..e30342a --- /dev/null +++ b/libs/assimp/code/PostProcessing/SortByPTypeProcess.h @@ -0,0 +1,82 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to sort meshes by the types + of primitives they contain */ +#ifndef AI_SORTBYPTYPEPROCESS_H_INC +#define AI_SORTBYPTYPEPROCESS_H_INC + +#include "Common/BaseProcess.h" +#include <assimp/mesh.h> + +class SortByPTypeProcessTest; + +namespace Assimp { + + +// --------------------------------------------------------------------------- +/** SortByPTypeProcess: Sorts meshes by the types of primitives they contain. + * A mesh with 5 lines, 3 points and 145 triangles would be split in 3 + * submeshes. +*/ +class ASSIMP_API SortByPTypeProcess : public BaseProcess { +public: + SortByPTypeProcess(); + ~SortByPTypeProcess(); + + // ------------------------------------------------------------------- + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + void SetupProperties(const Importer* pImp); + +private: + int mConfigRemoveMeshes; +}; + + +} // end of namespace Assimp + +#endif // !!AI_SORTBYPTYPEPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/SplitByBoneCountProcess.cpp b/libs/assimp/code/PostProcessing/SplitByBoneCountProcess.cpp new file mode 100644 index 0000000..ace62ae --- /dev/null +++ b/libs/assimp/code/PostProcessing/SplitByBoneCountProcess.cpp @@ -0,0 +1,480 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 SplitByBoneCountProcess.cpp +/// Implementation of the SplitByBoneCount postprocessing step + +// internal headers of the post-processing framework +#include "SplitByBoneCountProcess.h" +#include <assimp/postprocess.h> +#include <assimp/DefaultLogger.hpp> + +#include <limits> +#include <assimp/TinyFormatter.h> +#include <assimp/Exceptional.h> +#include <set> + +using namespace Assimp; +using namespace Assimp::Formatter; + +// ------------------------------------------------------------------------------------------------ +// Constructor +SplitByBoneCountProcess::SplitByBoneCountProcess() +{ + // set default, might be overridden by importer config + mMaxBoneCount = AI_SBBC_DEFAULT_MAX_BONES; +} + +// ------------------------------------------------------------------------------------------------ +// Destructor +SplitByBoneCountProcess::~SplitByBoneCountProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag. +bool SplitByBoneCountProcess::IsActive( unsigned int pFlags) const +{ + return !!(pFlags & aiProcess_SplitByBoneCount); +} + +// ------------------------------------------------------------------------------------------------ +// Updates internal properties +void SplitByBoneCountProcess::SetupProperties(const Importer* pImp) +{ + mMaxBoneCount = pImp->GetPropertyInteger(AI_CONFIG_PP_SBBC_MAX_BONES,AI_SBBC_DEFAULT_MAX_BONES); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void SplitByBoneCountProcess::Execute( aiScene* pScene) +{ + ASSIMP_LOG_DEBUG("SplitByBoneCountProcess begin"); + + // early out + bool isNecessary = false; + for( unsigned int a = 0; a < pScene->mNumMeshes; ++a) + if( pScene->mMeshes[a]->mNumBones > mMaxBoneCount ) + { + isNecessary = true; + break; + } + + if( !isNecessary ) + { + ASSIMP_LOG_DEBUG("SplitByBoneCountProcess early-out: no meshes with more than ", mMaxBoneCount, " bones." ); + return; + } + + // we need to do something. Let's go. + mSubMeshIndices.clear(); + mSubMeshIndices.resize( pScene->mNumMeshes); + + // build a new array of meshes for the scene + std::vector<aiMesh*> meshes; + + for( unsigned int a = 0; a < pScene->mNumMeshes; ++a) + { + aiMesh* srcMesh = pScene->mMeshes[a]; + + std::vector<aiMesh*> newMeshes; + SplitMesh( pScene->mMeshes[a], newMeshes); + + // mesh was split + if( !newMeshes.empty() ) + { + // store new meshes and indices of the new meshes + for( unsigned int b = 0; b < newMeshes.size(); ++b) + { + mSubMeshIndices[a].push_back( static_cast<unsigned int>(meshes.size())); + meshes.push_back( newMeshes[b]); + } + + // and destroy the source mesh. It should be completely contained inside the new submeshes + delete srcMesh; + } + else + { + // Mesh is kept unchanged - store it's new place in the mesh array + mSubMeshIndices[a].push_back( static_cast<unsigned int>(meshes.size())); + meshes.push_back( srcMesh); + } + } + + // rebuild the scene's mesh array + pScene->mNumMeshes = static_cast<unsigned int>(meshes.size()); + delete [] pScene->mMeshes; + pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; + std::copy( meshes.begin(), meshes.end(), pScene->mMeshes); + + // recurse through all nodes and translate the node's mesh indices to fit the new mesh array + UpdateNode( pScene->mRootNode); + + ASSIMP_LOG_DEBUG( "SplitByBoneCountProcess end: split ", mSubMeshIndices.size(), " meshes into ", meshes.size(), " submeshes." ); +} + +// ------------------------------------------------------------------------------------------------ +// Splits the given mesh by bone count. +void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vector<aiMesh*>& poNewMeshes) const +{ + // skip if not necessary + if( pMesh->mNumBones <= mMaxBoneCount ) + { + return; + } + + // necessary optimisation: build a list of all affecting bones for each vertex + // TODO: (thom) maybe add a custom allocator here to avoid allocating tens of thousands of small arrays + typedef std::pair<unsigned int, float> BoneWeight; + std::vector< std::vector<BoneWeight> > vertexBones( pMesh->mNumVertices); + for( unsigned int a = 0; a < pMesh->mNumBones; ++a) + { + const aiBone* bone = pMesh->mBones[a]; + for( unsigned int b = 0; b < bone->mNumWeights; ++b) + { + if (bone->mWeights[b].mWeight > 0.0f) + { + int vertexId = bone->mWeights[b].mVertexId; + vertexBones[vertexId].push_back( BoneWeight( a, bone->mWeights[b].mWeight)); + if (vertexBones[vertexId].size() > mMaxBoneCount) + { + throw DeadlyImportError("SplitByBoneCountProcess: Single face requires more bones than specified max bone count!"); + } + } + } + } + + unsigned int numFacesHandled = 0; + std::vector<bool> isFaceHandled( pMesh->mNumFaces, false); + while( numFacesHandled < pMesh->mNumFaces ) + { + // which bones are used in the current submesh + unsigned int numBones = 0; + std::vector<bool> isBoneUsed( pMesh->mNumBones, false); + // indices of the faces which are going to go into this submesh + std::vector<unsigned int> subMeshFaces; + subMeshFaces.reserve( pMesh->mNumFaces); + // accumulated vertex count of all the faces in this submesh + unsigned int numSubMeshVertices = 0; + + // add faces to the new submesh as long as all bones affecting the faces' vertices fit in the limit + for( unsigned int a = 0; a < pMesh->mNumFaces; ++a) + { + // skip if the face is already stored in a submesh + if( isFaceHandled[a] ) + { + continue; + } + // a small local set of new bones for the current face. State of all used bones for that face + // can only be updated AFTER the face is completely analysed. Thanks to imre for the fix. + std::set<unsigned int> newBonesAtCurrentFace; + + const aiFace& face = pMesh->mFaces[a]; + // check every vertex if its bones would still fit into the current submesh + for( unsigned int b = 0; b < face.mNumIndices; ++b ) + { + const std::vector<BoneWeight>& vb = vertexBones[face.mIndices[b]]; + for( unsigned int c = 0; c < vb.size(); ++c) + { + unsigned int boneIndex = vb[c].first; + if( !isBoneUsed[boneIndex] ) + { + newBonesAtCurrentFace.insert(boneIndex); + } + } + } + + // leave out the face if the new bones required for this face don't fit the bone count limit anymore + if( numBones + newBonesAtCurrentFace.size() > mMaxBoneCount ) + { + continue; + } + + // mark all new bones as necessary + for (std::set<unsigned int>::iterator it = newBonesAtCurrentFace.begin(); it != newBonesAtCurrentFace.end(); ++it) + { + if (!isBoneUsed[*it]) + { + isBoneUsed[*it] = true; + numBones++; + } + } + + // store the face index and the vertex count + subMeshFaces.push_back( a); + numSubMeshVertices += face.mNumIndices; + + // remember that this face is handled + isFaceHandled[a] = true; + numFacesHandled++; + } + + // create a new mesh to hold this subset of the source mesh + aiMesh* newMesh = new aiMesh; + if( pMesh->mName.length > 0 ) + { + newMesh->mName.Set( format() << pMesh->mName.data << "_sub" << poNewMeshes.size()); + } + newMesh->mMaterialIndex = pMesh->mMaterialIndex; + newMesh->mPrimitiveTypes = pMesh->mPrimitiveTypes; + poNewMeshes.push_back( newMesh); + + // create all the arrays for this mesh if the old mesh contained them + newMesh->mNumVertices = numSubMeshVertices; + newMesh->mNumFaces = static_cast<unsigned int>(subMeshFaces.size()); + newMesh->mVertices = new aiVector3D[newMesh->mNumVertices]; + if( pMesh->HasNormals() ) + { + newMesh->mNormals = new aiVector3D[newMesh->mNumVertices]; + } + if( pMesh->HasTangentsAndBitangents() ) + { + newMesh->mTangents = new aiVector3D[newMesh->mNumVertices]; + newMesh->mBitangents = new aiVector3D[newMesh->mNumVertices]; + } + for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) + { + if( pMesh->HasTextureCoords( a) ) + { + newMesh->mTextureCoords[a] = new aiVector3D[newMesh->mNumVertices]; + } + newMesh->mNumUVComponents[a] = pMesh->mNumUVComponents[a]; + } + for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a ) + { + if( pMesh->HasVertexColors( a) ) + { + newMesh->mColors[a] = new aiColor4D[newMesh->mNumVertices]; + } + } + + // and copy over the data, generating faces with linear indices along the way + newMesh->mFaces = new aiFace[subMeshFaces.size()]; + unsigned int nvi = 0; // next vertex index + std::vector<unsigned int> previousVertexIndices( numSubMeshVertices, std::numeric_limits<unsigned int>::max()); // per new vertex: its index in the source mesh + for( unsigned int a = 0; a < subMeshFaces.size(); ++a ) + { + const aiFace& srcFace = pMesh->mFaces[subMeshFaces[a]]; + aiFace& dstFace = newMesh->mFaces[a]; + dstFace.mNumIndices = srcFace.mNumIndices; + dstFace.mIndices = new unsigned int[dstFace.mNumIndices]; + + // accumulate linearly all the vertices of the source face + for( unsigned int b = 0; b < dstFace.mNumIndices; ++b ) + { + unsigned int srcIndex = srcFace.mIndices[b]; + dstFace.mIndices[b] = nvi; + previousVertexIndices[nvi] = srcIndex; + + newMesh->mVertices[nvi] = pMesh->mVertices[srcIndex]; + if( pMesh->HasNormals() ) + { + newMesh->mNormals[nvi] = pMesh->mNormals[srcIndex]; + } + if( pMesh->HasTangentsAndBitangents() ) + { + newMesh->mTangents[nvi] = pMesh->mTangents[srcIndex]; + newMesh->mBitangents[nvi] = pMesh->mBitangents[srcIndex]; + } + for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c ) + { + if( pMesh->HasTextureCoords( c) ) + { + newMesh->mTextureCoords[c][nvi] = pMesh->mTextureCoords[c][srcIndex]; + } + } + for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c ) + { + if( pMesh->HasVertexColors( c) ) + { + newMesh->mColors[c][nvi] = pMesh->mColors[c][srcIndex]; + } + } + + nvi++; + } + } + + ai_assert( nvi == numSubMeshVertices ); + + // Create the bones for the new submesh: first create the bone array + newMesh->mNumBones = 0; + newMesh->mBones = new aiBone*[numBones]; + + std::vector<unsigned int> mappedBoneIndex( pMesh->mNumBones, std::numeric_limits<unsigned int>::max()); + for( unsigned int a = 0; a < pMesh->mNumBones; ++a ) + { + if( !isBoneUsed[a] ) + { + continue; + } + + // create the new bone + const aiBone* srcBone = pMesh->mBones[a]; + aiBone* dstBone = new aiBone; + mappedBoneIndex[a] = newMesh->mNumBones; + newMesh->mBones[newMesh->mNumBones++] = dstBone; + dstBone->mName = srcBone->mName; + dstBone->mOffsetMatrix = srcBone->mOffsetMatrix; + dstBone->mNumWeights = 0; + } + + ai_assert( newMesh->mNumBones == numBones ); + + // iterate over all new vertices and count which bones affected its old vertex in the source mesh + for( unsigned int a = 0; a < numSubMeshVertices; ++a ) + { + unsigned int oldIndex = previousVertexIndices[a]; + const std::vector<BoneWeight>& bonesOnThisVertex = vertexBones[oldIndex]; + + for( unsigned int b = 0; b < bonesOnThisVertex.size(); ++b ) + { + unsigned int newBoneIndex = mappedBoneIndex[ bonesOnThisVertex[b].first ]; + if( newBoneIndex != std::numeric_limits<unsigned int>::max() ) + { + newMesh->mBones[newBoneIndex]->mNumWeights++; + } + } + } + + // allocate all bone weight arrays accordingly + for( unsigned int a = 0; a < newMesh->mNumBones; ++a ) + { + aiBone* bone = newMesh->mBones[a]; + ai_assert( bone->mNumWeights > 0 ); + bone->mWeights = new aiVertexWeight[bone->mNumWeights]; + bone->mNumWeights = 0; // for counting up in the next step + } + + // now copy all the bone vertex weights for all the vertices which made it into the new submesh + for( unsigned int a = 0; a < numSubMeshVertices; ++a) + { + // find the source vertex for it in the source mesh + unsigned int previousIndex = previousVertexIndices[a]; + // these bones were affecting it + const std::vector<BoneWeight>& bonesOnThisVertex = vertexBones[previousIndex]; + // all of the bones affecting it should be present in the new submesh, or else + // the face it comprises shouldn't be present + for( unsigned int b = 0; b < bonesOnThisVertex.size(); ++b) + { + unsigned int newBoneIndex = mappedBoneIndex[ bonesOnThisVertex[b].first ]; + ai_assert( newBoneIndex != std::numeric_limits<unsigned int>::max() ); + aiVertexWeight* dstWeight = newMesh->mBones[newBoneIndex]->mWeights + newMesh->mBones[newBoneIndex]->mNumWeights; + newMesh->mBones[newBoneIndex]->mNumWeights++; + + dstWeight->mVertexId = a; + dstWeight->mWeight = bonesOnThisVertex[b].second; + } + } + + // ... and copy all the morph targets for all the vertices which made it into the new submesh + if (pMesh->mNumAnimMeshes > 0) { + newMesh->mNumAnimMeshes = pMesh->mNumAnimMeshes; + newMesh->mAnimMeshes = new aiAnimMesh*[newMesh->mNumAnimMeshes]; + + for (unsigned int morphIdx = 0; morphIdx < newMesh->mNumAnimMeshes; ++morphIdx) { + aiAnimMesh* origTarget = pMesh->mAnimMeshes[morphIdx]; + aiAnimMesh* newTarget = new aiAnimMesh; + newTarget->mName = origTarget->mName; + newTarget->mWeight = origTarget->mWeight; + newTarget->mNumVertices = numSubMeshVertices; + newTarget->mVertices = new aiVector3D[numSubMeshVertices]; + newMesh->mAnimMeshes[morphIdx] = newTarget; + + if (origTarget->HasNormals()) { + newTarget->mNormals = new aiVector3D[numSubMeshVertices]; + } + + if (origTarget->HasTangentsAndBitangents()) { + newTarget->mTangents = new aiVector3D[numSubMeshVertices]; + newTarget->mBitangents = new aiVector3D[numSubMeshVertices]; + } + + for( unsigned int vi = 0; vi < numSubMeshVertices; ++vi) { + // find the source vertex for it in the source mesh + unsigned int previousIndex = previousVertexIndices[vi]; + newTarget->mVertices[vi] = origTarget->mVertices[previousIndex]; + + if (newTarget->HasNormals()) { + newTarget->mNormals[vi] = origTarget->mNormals[previousIndex]; + } + if (newTarget->HasTangentsAndBitangents()) { + newTarget->mTangents[vi] = origTarget->mTangents[previousIndex]; + newTarget->mBitangents[vi] = origTarget->mBitangents[previousIndex]; + } + } + } + } + + // I have the strange feeling that this will break apart at some point in time... + } +} + +// ------------------------------------------------------------------------------------------------ +// Recursively updates the node's mesh list to account for the changed mesh list +void SplitByBoneCountProcess::UpdateNode( aiNode* pNode) const +{ + // rebuild the node's mesh index list + if( pNode->mNumMeshes > 0 ) + { + std::vector<unsigned int> newMeshList; + for( unsigned int a = 0; a < pNode->mNumMeshes; ++a) + { + unsigned int srcIndex = pNode->mMeshes[a]; + const std::vector<unsigned int>& replaceMeshes = mSubMeshIndices[srcIndex]; + newMeshList.insert( newMeshList.end(), replaceMeshes.begin(), replaceMeshes.end()); + } + + delete [] pNode->mMeshes; + pNode->mNumMeshes = static_cast<unsigned int>(newMeshList.size()); + pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; + std::copy( newMeshList.begin(), newMeshList.end(), pNode->mMeshes); + } + + // do that also recursively for all children + for( unsigned int a = 0; a < pNode->mNumChildren; ++a ) + { + UpdateNode( pNode->mChildren[a]); + } +} diff --git a/libs/assimp/code/PostProcessing/SplitByBoneCountProcess.h b/libs/assimp/code/PostProcessing/SplitByBoneCountProcess.h new file mode 100644 index 0000000..938b00c --- /dev/null +++ b/libs/assimp/code/PostProcessing/SplitByBoneCountProcess.h @@ -0,0 +1,111 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 SplitByBoneCountProcess.h +/// Defines a post processing step to split meshes with many bones into submeshes +#ifndef AI_SPLITBYBONECOUNTPROCESS_H_INC +#define AI_SPLITBYBONECOUNTPROCESS_H_INC + +#include <vector> +#include "Common/BaseProcess.h" + +#include <assimp/mesh.h> +#include <assimp/scene.h> + +namespace Assimp +{ + + +/** Postprocessing filter to split meshes with many bones into submeshes + * so that each submesh has a certain max bone count. + * + * Applied BEFORE the JoinVertices-Step occurs. + * Returns NON-UNIQUE vertices, splits by bone count. +*/ +class SplitByBoneCountProcess : public BaseProcess +{ +public: + + SplitByBoneCountProcess(); + ~SplitByBoneCountProcess(); + +public: + /** Returns whether the processing step is present in the given flag. + * @param pFlags The processing flags the importer was called with. A + * bitwise combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, + * false if not. + */ + bool IsActive( unsigned int pFlags) const; + + /** Called prior to ExecuteOnScene(). + * The function is a request to the process to update its configuration + * basing on the Importer's configuration property list. + */ + virtual void SetupProperties(const Importer* pImp); + +protected: + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + + /// Splits the given mesh by bone count. + /// @param pMesh the Mesh to split. Is not changed at all, but might be superfluous in case it was split. + /// @param poNewMeshes Array of submeshes created in the process. Empty if splitting was not necessary. + void SplitMesh( const aiMesh* pMesh, std::vector<aiMesh*>& poNewMeshes) const; + + /// Recursively updates the node's mesh list to account for the changed mesh list + void UpdateNode( aiNode* pNode) const; + +public: + /// Max bone count. Splitting occurs if a mesh has more than that number of bones. + size_t mMaxBoneCount; + + /// Per mesh index: Array of indices of the new submeshes. + std::vector< std::vector<unsigned int> > mSubMeshIndices; +}; + +} // end of namespace Assimp + +#endif // !!AI_SPLITBYBONECOUNTPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/SplitLargeMeshes.cpp b/libs/assimp/code/PostProcessing/SplitLargeMeshes.cpp new file mode 100644 index 0000000..508a826 --- /dev/null +++ b/libs/assimp/code/PostProcessing/SplitLargeMeshes.cpp @@ -0,0 +1,624 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Implementation of the SplitLargeMeshes postprocessing step + */ + +// internal headers of the post-processing framework +#include "SplitLargeMeshes.h" +#include "ProcessHelper.h" + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +SplitLargeMeshesProcess_Triangle::SplitLargeMeshesProcess_Triangle() { + LIMIT = AI_SLM_DEFAULT_MAX_TRIANGLES; +} + +// ------------------------------------------------------------------------------------------------ +SplitLargeMeshesProcess_Triangle::~SplitLargeMeshesProcess_Triangle() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool SplitLargeMeshesProcess_Triangle::IsActive( unsigned int pFlags) const { + return (pFlags & aiProcess_SplitLargeMeshes) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void SplitLargeMeshesProcess_Triangle::Execute( aiScene* pScene) { + if (0xffffffff == this->LIMIT || nullptr == pScene ) { + return; + } + + ASSIMP_LOG_DEBUG("SplitLargeMeshesProcess_Triangle begin"); + std::vector<std::pair<aiMesh*, unsigned int> > avList; + + for( unsigned int a = 0; a < pScene->mNumMeshes; ++a) { + this->SplitMesh(a, pScene->mMeshes[a],avList); + } + + if (avList.size() != pScene->mNumMeshes) { + // it seems something has been split. rebuild the mesh list + delete[] pScene->mMeshes; + pScene->mNumMeshes = (unsigned int)avList.size(); + pScene->mMeshes = new aiMesh*[avList.size()]; + + for (unsigned int i = 0; i < avList.size();++i) { + pScene->mMeshes[i] = avList[i].first; + } + + // now we need to update all nodes + this->UpdateNode(pScene->mRootNode,avList); + ASSIMP_LOG_INFO("SplitLargeMeshesProcess_Triangle finished. Meshes have been split"); + } else { + ASSIMP_LOG_DEBUG("SplitLargeMeshesProcess_Triangle finished. There was nothing to do"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Setup properties +void SplitLargeMeshesProcess_Triangle::SetupProperties( const Importer* pImp) { + // get the current value of the split property + this->LIMIT = pImp->GetPropertyInteger(AI_CONFIG_PP_SLM_TRIANGLE_LIMIT,AI_SLM_DEFAULT_MAX_TRIANGLES); +} + +// ------------------------------------------------------------------------------------------------ +// Update a node after some meshes have been split +void SplitLargeMeshesProcess_Triangle::UpdateNode(aiNode* pcNode, + const std::vector<std::pair<aiMesh*, unsigned int> >& avList) { + // for every index in out list build a new entry + std::vector<unsigned int> aiEntries; + aiEntries.reserve(pcNode->mNumMeshes + 1); + for (unsigned int i = 0; i < pcNode->mNumMeshes;++i) { + for (unsigned int a = 0; a < avList.size();++a) { + if (avList[a].second == pcNode->mMeshes[i]) { + aiEntries.push_back(a); + } + } + } + + // now build the new list + delete[] pcNode->mMeshes; + pcNode->mNumMeshes = (unsigned int)aiEntries.size(); + pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes]; + + for (unsigned int b = 0; b < pcNode->mNumMeshes;++b) { + pcNode->mMeshes[b] = aiEntries[b]; + } + + // recursively update all other nodes + for (unsigned int i = 0; i < pcNode->mNumChildren;++i) { + UpdateNode ( pcNode->mChildren[i], avList ); + } +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void SplitLargeMeshesProcess_Triangle::SplitMesh( + unsigned int a, + aiMesh* pMesh, + std::vector<std::pair<aiMesh*, unsigned int> >& avList) { + if (pMesh->mNumFaces > SplitLargeMeshesProcess_Triangle::LIMIT) { + ASSIMP_LOG_INFO("Mesh exceeds the triangle limit. It will be split ..."); + + // we need to split this mesh into sub meshes + // determine the size of a submesh + const unsigned int iSubMeshes = (pMesh->mNumFaces / LIMIT) + 1; + + const unsigned int iOutFaceNum = pMesh->mNumFaces / iSubMeshes; + const unsigned int iOutVertexNum = iOutFaceNum * 3; + + // now generate all submeshes + for (unsigned int i = 0; i < iSubMeshes;++i) { + aiMesh* pcMesh = new aiMesh; + pcMesh->mNumFaces = iOutFaceNum; + pcMesh->mMaterialIndex = pMesh->mMaterialIndex; + + // the name carries the adjacency information between the meshes + pcMesh->mName = pMesh->mName; + + if (i == iSubMeshes-1) { + pcMesh->mNumFaces = iOutFaceNum + ( + pMesh->mNumFaces - iOutFaceNum * iSubMeshes); + } + // copy the list of faces + pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; + + const unsigned int iBase = iOutFaceNum * i; + + // get the total number of indices + unsigned int iCnt = 0; + for (unsigned int p = iBase; p < pcMesh->mNumFaces + iBase;++p) { + iCnt += pMesh->mFaces[p].mNumIndices; + } + pcMesh->mNumVertices = iCnt; + + // allocate storage + if (pMesh->mVertices != nullptr) { + pcMesh->mVertices = new aiVector3D[iCnt]; + } + + if (pMesh->HasNormals()) { + pcMesh->mNormals = new aiVector3D[iCnt]; + } + + if (pMesh->HasTangentsAndBitangents()) { + pcMesh->mTangents = new aiVector3D[iCnt]; + pcMesh->mBitangents = new aiVector3D[iCnt]; + } + + // texture coordinates + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) { + pcMesh->mNumUVComponents[c] = pMesh->mNumUVComponents[c]; + if (pMesh->HasTextureCoords( c)) { + pcMesh->mTextureCoords[c] = new aiVector3D[iCnt]; + } + } + + // vertex colors + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS;++c) { + if (pMesh->HasVertexColors( c)) { + pcMesh->mColors[c] = new aiColor4D[iCnt]; + } + } + + if (pMesh->HasBones()) { + // assume the number of bones won't change in most cases + pcMesh->mBones = new aiBone*[pMesh->mNumBones]; + + // iterate through all bones of the mesh and find those which + // need to be copied to the split mesh + std::vector<aiVertexWeight> avTempWeights; + for (unsigned int p = 0; p < pcMesh->mNumBones;++p) { + aiBone* const bone = pcMesh->mBones[p]; + avTempWeights.clear(); + avTempWeights.reserve(bone->mNumWeights / iSubMeshes); + + for (unsigned int q = 0; q < bone->mNumWeights;++q) { + aiVertexWeight& weight = bone->mWeights[q]; + if(weight.mVertexId >= iBase && weight.mVertexId < iBase + iOutVertexNum) { + avTempWeights.push_back(weight); + weight = avTempWeights.back(); + weight.mVertexId -= iBase; + } + } + + if (!avTempWeights.empty()) { + // we'll need this bone. Copy it ... + aiBone* pc = new aiBone(); + pcMesh->mBones[pcMesh->mNumBones++] = pc; + pc->mName = aiString(bone->mName); + pc->mNumWeights = (unsigned int)avTempWeights.size(); + pc->mOffsetMatrix = bone->mOffsetMatrix; + + // no need to reallocate the array for the last submesh. + // Here we can reuse the (large) source array, although + // we'll waste some memory + if (iSubMeshes-1 == i) { + pc->mWeights = bone->mWeights; + bone->mWeights = nullptr; + } else { + pc->mWeights = new aiVertexWeight[pc->mNumWeights]; + } + + // copy the weights + ::memcpy(pc->mWeights,&avTempWeights[0],sizeof(aiVertexWeight)*pc->mNumWeights); + } + } + } + + // (we will also need to copy the array of indices) + unsigned int iCurrent = 0; + for (unsigned int p = 0; p < pcMesh->mNumFaces;++p) { + pcMesh->mFaces[p].mNumIndices = 3; + // allocate a new array + const unsigned int iTemp = p + iBase; + const unsigned int iNumIndices = pMesh->mFaces[iTemp].mNumIndices; + + // setup face type and number of indices + pcMesh->mFaces[p].mNumIndices = iNumIndices; + unsigned int* pi = pMesh->mFaces[iTemp].mIndices; + unsigned int* piOut = pcMesh->mFaces[p].mIndices = new unsigned int[iNumIndices]; + + // need to update the output primitive types + switch (iNumIndices) { + case 1: + pcMesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + case 2: + pcMesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + case 3: + pcMesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + default: + pcMesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + } + + // and copy the contents of the old array, offset by current base + for (unsigned int v = 0; v < iNumIndices;++v) { + unsigned int iIndex = pi[v]; + unsigned int iIndexOut = iCurrent++; + piOut[v] = iIndexOut; + + // copy positions + if (pMesh->mVertices != nullptr) { + pcMesh->mVertices[iIndexOut] = pMesh->mVertices[iIndex]; + } + + // copy normals + if (pMesh->HasNormals()) { + pcMesh->mNormals[iIndexOut] = pMesh->mNormals[iIndex]; + } + + // copy tangents/bitangents + if (pMesh->HasTangentsAndBitangents()) { + pcMesh->mTangents[iIndexOut] = pMesh->mTangents[iIndex]; + pcMesh->mBitangents[iIndexOut] = pMesh->mBitangents[iIndex]; + } + + // texture coordinates + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) { + if (pMesh->HasTextureCoords( c ) ) { + pcMesh->mTextureCoords[c][iIndexOut] = pMesh->mTextureCoords[c][iIndex]; + } + } + // vertex colors + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS;++c) { + if (pMesh->HasVertexColors( c)) { + pcMesh->mColors[c][iIndexOut] = pMesh->mColors[c][iIndex]; + } + } + } + } + + // add the newly created mesh to the list + avList.push_back(std::pair<aiMesh*, unsigned int>(pcMesh,a)); + } + + // now delete the old mesh data + delete pMesh; + } else { + avList.push_back(std::pair<aiMesh*, unsigned int>(pMesh,a)); + } +} + +// ------------------------------------------------------------------------------------------------ +SplitLargeMeshesProcess_Vertex::SplitLargeMeshesProcess_Vertex() { + LIMIT = AI_SLM_DEFAULT_MAX_VERTICES; +} + +// ------------------------------------------------------------------------------------------------ +SplitLargeMeshesProcess_Vertex::~SplitLargeMeshesProcess_Vertex() { + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool SplitLargeMeshesProcess_Vertex::IsActive( unsigned int pFlags) const { + return (pFlags & aiProcess_SplitLargeMeshes) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void SplitLargeMeshesProcess_Vertex::Execute( aiScene* pScene) { + if (0xffffffff == this->LIMIT || nullptr == pScene ) { + return; + } + + ASSIMP_LOG_DEBUG("SplitLargeMeshesProcess_Vertex begin"); + + std::vector<std::pair<aiMesh*, unsigned int> > avList; + + //Check for point cloud first, + //Do not process point cloud, splitMesh works only with faces data + for (unsigned int a = 0; a < pScene->mNumMeshes; a++) { + if ( pScene->mMeshes[a]->mPrimitiveTypes == aiPrimitiveType_POINT ) { + return; + } + } + + for( unsigned int a = 0; a < pScene->mNumMeshes; ++a ) { + this->SplitMesh(a, pScene->mMeshes[a], avList); + } + + if (avList.size() != pScene->mNumMeshes) { + // it seems something has been split. rebuild the mesh list + delete[] pScene->mMeshes; + pScene->mNumMeshes = (unsigned int)avList.size(); + pScene->mMeshes = new aiMesh*[avList.size()]; + + for (unsigned int i = 0; i < avList.size();++i) { + pScene->mMeshes[i] = avList[i].first; + } + + // now we need to update all nodes + SplitLargeMeshesProcess_Triangle::UpdateNode(pScene->mRootNode,avList); + ASSIMP_LOG_INFO("SplitLargeMeshesProcess_Vertex finished. Meshes have been split"); + } else { + ASSIMP_LOG_DEBUG("SplitLargeMeshesProcess_Vertex finished. There was nothing to do"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Setup properties +void SplitLargeMeshesProcess_Vertex::SetupProperties( const Importer* pImp) { + this->LIMIT = pImp->GetPropertyInteger(AI_CONFIG_PP_SLM_VERTEX_LIMIT,AI_SLM_DEFAULT_MAX_VERTICES); +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void SplitLargeMeshesProcess_Vertex::SplitMesh( + unsigned int a, + aiMesh* pMesh, + std::vector<std::pair<aiMesh*, unsigned int> >& avList) { + if (pMesh->mNumVertices > SplitLargeMeshesProcess_Vertex::LIMIT) { + typedef std::vector< std::pair<unsigned int,float> > VertexWeightTable; + + // build a per-vertex weight list if necessary + VertexWeightTable* avPerVertexWeights = ComputeVertexBoneWeightTable(pMesh); + + // we need to split this mesh into sub meshes + // determine the estimated size of a submesh + // (this could be too large. Max waste is a single digit percentage) + const unsigned int iSubMeshes = (pMesh->mNumVertices / SplitLargeMeshesProcess_Vertex::LIMIT) + 1; + + // create a std::vector<unsigned int> to indicate which vertices + // have already been copied + std::vector<unsigned int> avWasCopied; + avWasCopied.resize(pMesh->mNumVertices,0xFFFFFFFF); + + // try to find a good estimate for the number of output faces + // per mesh. Add 12.5% as buffer + unsigned int iEstimatedSize = pMesh->mNumFaces / iSubMeshes; + iEstimatedSize += iEstimatedSize >> 3; + + // now generate all submeshes + unsigned int iBase( 0 ); + while (true) { + const unsigned int iOutVertexNum = SplitLargeMeshesProcess_Vertex::LIMIT; + aiMesh* pcMesh = new aiMesh; + pcMesh->mNumVertices = 0; + pcMesh->mMaterialIndex = pMesh->mMaterialIndex; + + // the name carries the adjacency information between the meshes + pcMesh->mName = pMesh->mName; + + typedef std::vector<aiVertexWeight> BoneWeightList; + if (pMesh->HasBones()) { + pcMesh->mBones = new aiBone*[pMesh->mNumBones]; + ::memset(pcMesh->mBones,0,sizeof(void*)*pMesh->mNumBones); + } + + // clear the temporary helper array + if (iBase) { + // we can't use memset here we unsigned int needn' be 32 bits + for (auto &elem : avWasCopied) { + elem = 0xffffffff; + } + } + + // output vectors + std::vector<aiFace> vFaces; + + // reserve enough storage for most cases + if (pMesh->HasPositions()) { + pcMesh->mVertices = new aiVector3D[iOutVertexNum]; + } + if (pMesh->HasNormals()) { + pcMesh->mNormals = new aiVector3D[iOutVertexNum]; + } + if (pMesh->HasTangentsAndBitangents()) { + pcMesh->mTangents = new aiVector3D[iOutVertexNum]; + pcMesh->mBitangents = new aiVector3D[iOutVertexNum]; + } + for (unsigned int c = 0; pMesh->HasVertexColors(c);++c) { + pcMesh->mColors[c] = new aiColor4D[iOutVertexNum]; + } + for (unsigned int c = 0; pMesh->HasTextureCoords(c);++c) { + pcMesh->mNumUVComponents[c] = pMesh->mNumUVComponents[c]; + pcMesh->mTextureCoords[c] = new aiVector3D[iOutVertexNum]; + } + vFaces.reserve(iEstimatedSize); + + // (we will also need to copy the array of indices) + while (iBase < pMesh->mNumFaces) { + // allocate a new array + const unsigned int iNumIndices = pMesh->mFaces[iBase].mNumIndices; + + // doesn't catch degenerates but is quite fast + unsigned int iNeed = 0; + for (unsigned int v = 0; v < iNumIndices;++v) { + unsigned int iIndex = pMesh->mFaces[iBase].mIndices[v]; + + // check whether we do already have this vertex + if (0xFFFFFFFF == avWasCopied[iIndex]) { + iNeed++; + } + } + if (pcMesh->mNumVertices + iNeed > iOutVertexNum) { + // don't use this face + break; + } + + vFaces.push_back(aiFace()); + aiFace& rFace = vFaces.back(); + + // setup face type and number of indices + rFace.mNumIndices = iNumIndices; + rFace.mIndices = new unsigned int[iNumIndices]; + + // need to update the output primitive types + switch (rFace.mNumIndices) { + case 1: + pcMesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + case 2: + pcMesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + case 3: + pcMesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + default: + pcMesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + } + + // and copy the contents of the old array, offset by current base + for (unsigned int v = 0; v < iNumIndices;++v) { + unsigned int iIndex = pMesh->mFaces[iBase].mIndices[v]; + + // check whether we do already have this vertex + if (0xFFFFFFFF != avWasCopied[iIndex]) { + rFace.mIndices[v] = avWasCopied[iIndex]; + continue; + } + + // copy positions + pcMesh->mVertices[pcMesh->mNumVertices] = (pMesh->mVertices[iIndex]); + + // copy normals + if (pMesh->HasNormals()) { + pcMesh->mNormals[pcMesh->mNumVertices] = (pMesh->mNormals[iIndex]); + } + + // copy tangents/bitangents + if (pMesh->HasTangentsAndBitangents()) { + pcMesh->mTangents[pcMesh->mNumVertices] = (pMesh->mTangents[iIndex]); + pcMesh->mBitangents[pcMesh->mNumVertices] = (pMesh->mBitangents[iIndex]); + } + + // texture coordinates + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) { + if (pMesh->HasTextureCoords( c)) { + pcMesh->mTextureCoords[c][pcMesh->mNumVertices] = pMesh->mTextureCoords[c][iIndex]; + } + } + // vertex colors + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS;++c) { + if (pMesh->HasVertexColors( c)) { + pcMesh->mColors[c][pcMesh->mNumVertices] = pMesh->mColors[c][iIndex]; + } + } + // check whether we have bone weights assigned to this vertex + rFace.mIndices[v] = pcMesh->mNumVertices; + if (avPerVertexWeights) { + VertexWeightTable& table = avPerVertexWeights[ pcMesh->mNumVertices ]; + if( !table.empty() ) { + for (VertexWeightTable::const_iterator iter = table.begin(); + iter != table.end();++iter) { + // allocate the bone weight array if necessary + BoneWeightList* pcWeightList = (BoneWeightList*)pcMesh->mBones[(*iter).first]; + if (nullptr == pcWeightList) { + pcMesh->mBones[(*iter).first] = (aiBone*)(pcWeightList = new BoneWeightList()); + } + pcWeightList->push_back(aiVertexWeight(pcMesh->mNumVertices,(*iter).second)); + } + } + } + + avWasCopied[iIndex] = pcMesh->mNumVertices; + pcMesh->mNumVertices++; + } + ++iBase; + if(pcMesh->mNumVertices == iOutVertexNum) { + // break here. The face is only added if it was complete + break; + } + } + + // check which bones we'll need to create for this submesh + if (pMesh->HasBones()) { + aiBone** ppCurrent = pcMesh->mBones; + for (unsigned int k = 0; k < pMesh->mNumBones;++k) { + // check whether the bone is existing + BoneWeightList* pcWeightList; + pcWeightList = (BoneWeightList *)pcMesh->mBones[k]; + if (nullptr != pcWeightList) { + aiBone *pcOldBone = pMesh->mBones[k]; + aiBone* pcOut( nullptr ); + *ppCurrent++ = pcOut = new aiBone(); + pcOut->mName = aiString(pcOldBone->mName); + pcOut->mOffsetMatrix = pcOldBone->mOffsetMatrix; + pcOut->mNumWeights = (unsigned int)pcWeightList->size(); + pcOut->mWeights = new aiVertexWeight[pcOut->mNumWeights]; + + // copy the vertex weights + ::memcpy(pcOut->mWeights,&pcWeightList->operator[](0), + pcOut->mNumWeights * sizeof(aiVertexWeight)); + + // delete the temporary bone weight list + delete pcWeightList; + pcMesh->mNumBones++; + } + } + } + + // copy the face list to the mesh + pcMesh->mFaces = new aiFace[vFaces.size()]; + pcMesh->mNumFaces = (unsigned int)vFaces.size(); + + for (unsigned int p = 0; p < pcMesh->mNumFaces;++p) { + pcMesh->mFaces[p] = vFaces[p]; + } + + // add the newly created mesh to the list + avList.push_back(std::pair<aiMesh*, unsigned int>(pcMesh,a)); + + if (iBase == pMesh->mNumFaces) { + // have all faces ... finish the outer loop, too + break; + } + } + + // delete the per-vertex weight list again + delete[] avPerVertexWeights; + + // now delete the old mesh data + delete pMesh; + return; + } + avList.push_back(std::pair<aiMesh*, unsigned int>(pMesh,a)); +} diff --git a/libs/assimp/code/PostProcessing/SplitLargeMeshes.h b/libs/assimp/code/PostProcessing/SplitLargeMeshes.h new file mode 100644 index 0000000..e5a8d4c --- /dev/null +++ b/libs/assimp/code/PostProcessing/SplitLargeMeshes.h @@ -0,0 +1,209 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to split large meshes into sub-meshes + */ +#ifndef AI_SPLITLARGEMESHES_H_INC +#define AI_SPLITLARGEMESHES_H_INC + +#include <vector> +#include "Common/BaseProcess.h" + +#include <assimp/mesh.h> +#include <assimp/scene.h> + +// Forward declarations +class SplitLargeMeshesTest; + +namespace Assimp { + +class SplitLargeMeshesProcess_Triangle; +class SplitLargeMeshesProcess_Vertex; + +// NOTE: If you change these limits, don't forget to change the +// corresponding values in all Assimp ports + +// ********************************************************** +// Java: ConfigProperty.java, +// ConfigProperty.DEFAULT_VERTEX_SPLIT_LIMIT +// ConfigProperty.DEFAULT_TRIANGLE_SPLIT_LIMIT +// ********************************************************** + +// default limit for vertices +#if (!defined AI_SLM_DEFAULT_MAX_VERTICES) +# define AI_SLM_DEFAULT_MAX_VERTICES 1000000 +#endif + +// default limit for triangles +#if (!defined AI_SLM_DEFAULT_MAX_TRIANGLES) +# define AI_SLM_DEFAULT_MAX_TRIANGLES 1000000 +#endif + +// --------------------------------------------------------------------------- +/** Post-processing filter to split large meshes into sub-meshes + * + * Applied BEFORE the JoinVertices-Step occurs. + * Returns NON-UNIQUE vertices, splits by triangle number. +*/ +class ASSIMP_API SplitLargeMeshesProcess_Triangle : public BaseProcess +{ + friend class SplitLargeMeshesProcess_Vertex; + +public: + + SplitLargeMeshesProcess_Triangle(); + ~SplitLargeMeshesProcess_Triangle(); + +public: + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag. + * @param pFlags The processing flags the importer was called with. A + * bitwise combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, + * false if not. + */ + bool IsActive( unsigned int pFlags) const; + + + // ------------------------------------------------------------------- + /** Called prior to ExecuteOnScene(). + * The function is a request to the process to update its configuration + * basing on the Importer's configuration property list. + */ + virtual void SetupProperties(const Importer* pImp); + + + //! Set the split limit - needed for unit testing + inline void SetLimit(unsigned int l) + {LIMIT = l;} + + //! Get the split limit + inline unsigned int GetLimit() const + {return LIMIT;} + +public: + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + //! Apply the algorithm to a given mesh + void SplitMesh (unsigned int a, aiMesh* pcMesh, + std::vector<std::pair<aiMesh*, unsigned int> >& avList); + + // ------------------------------------------------------------------- + //! Update a node in the asset after a few of its meshes + //! have been split + static void UpdateNode(aiNode* pcNode, + const std::vector<std::pair<aiMesh*, unsigned int> >& avList); + +public: + //! Triangle limit + unsigned int LIMIT; +}; + + +// --------------------------------------------------------------------------- +/** Post-processing filter to split large meshes into sub-meshes + * + * Applied AFTER the JoinVertices-Step occurs. + * Returns UNIQUE vertices, splits by vertex number. +*/ +class ASSIMP_API SplitLargeMeshesProcess_Vertex : public BaseProcess +{ +public: + + SplitLargeMeshesProcess_Vertex(); + ~SplitLargeMeshesProcess_Vertex(); + +public: + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag field. + * @param pFlags The processing flags the importer was called with. A bitwise + * combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, false if not. + */ + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + /** Called prior to ExecuteOnScene(). + * The function is a request to the process to update its configuration + * basing on the Importer's configuration property list. + */ + virtual void SetupProperties(const Importer* pImp); + + + //! Set the split limit - needed for unit testing + inline void SetLimit(unsigned int l) + {LIMIT = l;} + + //! Get the split limit + inline unsigned int GetLimit() const + {return LIMIT;} + +public: + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + //! Apply the algorithm to a given mesh + void SplitMesh (unsigned int a, aiMesh* pcMesh, + std::vector<std::pair<aiMesh*, unsigned int> >& avList); + + // NOTE: Reuse SplitLargeMeshesProcess_Triangle::UpdateNode() + +public: + //! Triangle limit + unsigned int LIMIT; +}; + +} // end of namespace Assimp + +#endif // !!AI_SPLITLARGEMESHES_H_INC diff --git a/libs/assimp/code/PostProcessing/TextureTransform.cpp b/libs/assimp/code/PostProcessing/TextureTransform.cpp new file mode 100644 index 0000000..653506e --- /dev/null +++ b/libs/assimp/code/PostProcessing/TextureTransform.cpp @@ -0,0 +1,567 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 A helper class that processes texture transformations */ + + + +#include <assimp/Importer.hpp> +#include <assimp/postprocess.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/scene.h> + +#include "TextureTransform.h" +#include <assimp/StringUtils.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +TextureTransformStep::TextureTransformStep() : + configFlags() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +TextureTransformStep::~TextureTransformStep() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool TextureTransformStep::IsActive( unsigned int pFlags) const +{ + return (pFlags & aiProcess_TransformUVCoords) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Setup properties +void TextureTransformStep::SetupProperties(const Importer* pImp) +{ + configFlags = pImp->GetPropertyInteger(AI_CONFIG_PP_TUV_EVALUATE,AI_UVTRAFO_ALL); +} + +// ------------------------------------------------------------------------------------------------ +void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) +{ + /* This function tries to simplify the input UV transformation. + * That's very important as it allows us to reduce the number + * of output UV channels. The order in which the transformations + * are applied is - as always - scaling, rotation, translation. + */ + + int rounded; + char szTemp[512]; + + /* Optimize the rotation angle. That's slightly difficult as + * we have an inprecise floating-point number (when comparing + * UV transformations we'll take that into account by using + * an epsilon of 5 degrees). If there is a rotation value, we can't + * perform any further optimizations. + */ + if (info.mRotation) + { + float out = info.mRotation; + rounded = static_cast<int>((info.mRotation / static_cast<float>(AI_MATH_TWO_PI))); + if (rounded) + { + out -= rounded * static_cast<float>(AI_MATH_PI); + ASSIMP_LOG_INFO("Texture coordinate rotation ", info.mRotation, " can be simplified to ", out); + } + + // Next step - convert negative rotation angles to positives + if (out < 0.f) + out = (float)AI_MATH_TWO_PI * 2 + out; + + info.mRotation = out; + return; + } + + + /* Optimize UV translation in the U direction. To determine whether + * or not we can optimize we need to look at the requested mapping + * type (e.g. if mirroring is active there IS a difference between + * offset 2 and 3) + */ + rounded = (int)info.mTranslation.x; + if (rounded) { + float out = 0.0f; + szTemp[0] = 0; + if (aiTextureMapMode_Wrap == info.mapU) { + // Wrap - simple take the fraction of the field + out = info.mTranslation.x-(float)rounded; + ai_snprintf(szTemp, 512, "[w] UV U offset %f can be simplified to %f", info.mTranslation.x, out); + } + else if (aiTextureMapMode_Mirror == info.mapU && 1 != rounded) { + // Mirror + if (rounded % 2) + rounded--; + out = info.mTranslation.x-(float)rounded; + + ai_snprintf(szTemp,512,"[m/d] UV U offset %f can be simplified to %f",info.mTranslation.x,out); + } + else if (aiTextureMapMode_Clamp == info.mapU || aiTextureMapMode_Decal == info.mapU) { + // Clamp - translations beyond 1,1 are senseless + ai_snprintf(szTemp,512,"[c] UV U offset %f can be clamped to 1.0f",info.mTranslation.x); + + out = 1.f; + } + if (szTemp[0]) { + ASSIMP_LOG_INFO(szTemp); + info.mTranslation.x = out; + } + } + + /* Optimize UV translation in the V direction. To determine whether + * or not we can optimize we need to look at the requested mapping + * type (e.g. if mirroring is active there IS a difference between + * offset 2 and 3) + */ + rounded = (int)info.mTranslation.y; + if (rounded) { + float out = 0.0f; + szTemp[0] = 0; + if (aiTextureMapMode_Wrap == info.mapV) { + // Wrap - simple take the fraction of the field + out = info.mTranslation.y-(float)rounded; + ::ai_snprintf(szTemp,512,"[w] UV V offset %f can be simplified to %f",info.mTranslation.y,out); + } + else if (aiTextureMapMode_Mirror == info.mapV && 1 != rounded) { + // Mirror + if (rounded % 2) + rounded--; + out = info.mTranslation.x-(float)rounded; + + ::ai_snprintf(szTemp,512,"[m/d] UV V offset %f can be simplified to %f",info.mTranslation.y,out); + } + else if (aiTextureMapMode_Clamp == info.mapV || aiTextureMapMode_Decal == info.mapV) { + // Clamp - translations beyond 1,1 are senseless + ::ai_snprintf(szTemp,512,"[c] UV V offset %f can be clamped to 1.0f",info.mTranslation.y); + + out = 1.f; + } + if (szTemp[0]) { + ASSIMP_LOG_INFO(szTemp); + info.mTranslation.y = out; + } + } +} + +// ------------------------------------------------------------------------------------------------ +void UpdateUVIndex(const std::list<TTUpdateInfo>& l, unsigned int n) +{ + // Don't set if == 0 && wasn't set before + for (std::list<TTUpdateInfo>::const_iterator it = l.begin();it != l.end(); ++it) { + const TTUpdateInfo& info = *it; + + if (info.directShortcut) + *info.directShortcut = n; + else if (!n) + { + info.mat->AddProperty<int>((int*)&n,1,AI_MATKEY_UVWSRC(info.semantic,info.index)); + } + } +} + +// ------------------------------------------------------------------------------------------------ +inline const char* MappingModeToChar(aiTextureMapMode map) +{ + if (aiTextureMapMode_Wrap == map) + return "-w"; + + if (aiTextureMapMode_Mirror == map) + return "-m"; + + return "-c"; +} + +// ------------------------------------------------------------------------------------------------ +void TextureTransformStep::Execute( aiScene* pScene) +{ + ASSIMP_LOG_DEBUG("TransformUVCoordsProcess begin"); + + + /* We build a per-mesh list of texture transformations we'll need + * to apply. To achieve this, we iterate through all materials, + * find all textures and get their transformations and UV indices. + * Then we search for all meshes using this material. + */ + typedef std::list<STransformVecInfo> MeshTrafoList; + std::vector<MeshTrafoList> meshLists(pScene->mNumMeshes); + + for (unsigned int i = 0; i < pScene->mNumMaterials;++i) { + + aiMaterial* mat = pScene->mMaterials[i]; + for (unsigned int a = 0; a < mat->mNumProperties;++a) { + + aiMaterialProperty* prop = mat->mProperties[a]; + if (!::strcmp( prop->mKey.data, "$tex.file")) { + STransformVecInfo info; + + // Setup a shortcut structure to allow for a fast updating + // of the UV index later + TTUpdateInfo update; + update.mat = (aiMaterial*) mat; + update.semantic = prop->mSemantic; + update.index = prop->mIndex; + + // Get textured properties and transform + for (unsigned int a2 = 0; a2 < mat->mNumProperties;++a2) { + aiMaterialProperty* prop2 = mat->mProperties[a2]; + if (prop2->mSemantic != prop->mSemantic || prop2->mIndex != prop->mIndex) { + continue; + } + + if ( !::strcmp( prop2->mKey.data, "$tex.uvwsrc")) { + info.uvIndex = *((int*)prop2->mData); + + // Store a direct pointer for later use + update.directShortcut = (unsigned int*) prop2->mData; + } + + else if ( !::strcmp( prop2->mKey.data, "$tex.mapmodeu")) { + info.mapU = *((aiTextureMapMode*)prop2->mData); + } + else if ( !::strcmp( prop2->mKey.data, "$tex.mapmodev")) { + info.mapV = *((aiTextureMapMode*)prop2->mData); + } + else if ( !::strcmp( prop2->mKey.data, "$tex.uvtrafo")) { + // ValidateDS should check this + ai_assert(prop2->mDataLength >= 20); + ::memcpy(&info.mTranslation.x,prop2->mData,sizeof(float)*5); + + // Directly remove this property from the list + mat->mNumProperties--; + for (unsigned int a3 = a2; a3 < mat->mNumProperties;++a3) { + mat->mProperties[a3] = mat->mProperties[a3+1]; + } + + delete prop2; + + // Warn: could be an underflow, but this does not invoke undefined behaviour + --a2; + } + } + + // Find out which transformations are to be evaluated + if (!(configFlags & AI_UVTRAFO_ROTATION)) { + info.mRotation = 0.f; + } + if (!(configFlags & AI_UVTRAFO_SCALING)) { + info.mScaling = aiVector2D(1.f,1.f); + } + if (!(configFlags & AI_UVTRAFO_TRANSLATION)) { + info.mTranslation = aiVector2D(0.f,0.f); + } + + // Do some preprocessing + PreProcessUVTransform(info); + info.uvIndex = std::min(info.uvIndex,AI_MAX_NUMBER_OF_TEXTURECOORDS -1u); + + // Find out whether this material is used by more than + // one mesh. This will make our task much, much more difficult! + unsigned int cnt = 0; + for (unsigned int n = 0; n < pScene->mNumMeshes;++n) { + if (pScene->mMeshes[n]->mMaterialIndex == i) + ++cnt; + } + + if (!cnt) + continue; + else if (1 != cnt) { + // This material is referenced by more than one mesh! + // So we need to make sure the UV index for the texture + // is identical for each of it ... + info.lockedPos = AI_TT_UV_IDX_LOCK_TBD; + } + + // Get all corresponding meshes + for (unsigned int n = 0; n < pScene->mNumMeshes;++n) { + aiMesh* mesh = pScene->mMeshes[n]; + if (mesh->mMaterialIndex != i || !mesh->mTextureCoords[0]) + continue; + + unsigned int uv = info.uvIndex; + if (!mesh->mTextureCoords[uv]) { + // If the requested UV index is not available, take the first one instead. + uv = 0; + } + + if (mesh->mNumUVComponents[info.uvIndex] >= 3){ + ASSIMP_LOG_WARN("UV transformations on 3D mapping channels are not supported"); + continue; + } + + MeshTrafoList::iterator it; + + // Check whether we have this transform setup already + for (it = meshLists[n].begin();it != meshLists[n].end(); ++it) { + + if ((*it) == info && (*it).uvIndex == uv) { + (*it).updateList.push_back(update); + break; + } + } + + if (it == meshLists[n].end()) { + meshLists[n].push_back(info); + meshLists[n].back().uvIndex = uv; + meshLists[n].back().updateList.push_back(update); + } + } + } + } + } + + char buffer[1024]; // should be sufficiently large + unsigned int outChannels = 0, inChannels = 0, transformedChannels = 0; + + // Now process all meshes. Important: we don't remove unreferenced UV channels. + // This is a job for the RemoveUnreferencedData-Step. + for (unsigned int q = 0; q < pScene->mNumMeshes;++q) { + + aiMesh* mesh = pScene->mMeshes[q]; + MeshTrafoList& trafo = meshLists[q]; + + inChannels += mesh->GetNumUVChannels(); + + if (!mesh->mTextureCoords[0] || trafo.empty() || (trafo.size() == 1 && trafo.begin()->IsUntransformed())) { + outChannels += mesh->GetNumUVChannels(); + continue; + } + + // Move untransformed UV channels to the first position in the list .... + // except if we need a new locked index which should be as small as possible + bool veto = false, need = false; + unsigned int cnt = 0; + unsigned int untransformed = 0; + + MeshTrafoList::iterator it,it2; + for (it = trafo.begin();it != trafo.end(); ++it,++cnt) { + + if (!(*it).IsUntransformed()) { + need = true; + } + + if ((*it).lockedPos == AI_TT_UV_IDX_LOCK_TBD) { + // Lock this index and make sure it won't be changed + (*it).lockedPos = cnt; + veto = true; + continue; + } + + if (!veto && it != trafo.begin() && (*it).IsUntransformed()) { + for (it2 = trafo.begin();it2 != it; ++it2) { + if (!(*it2).IsUntransformed()) + break; + } + trafo.insert(it2,*it); + trafo.erase(it); + break; + } + } + if (!need) + continue; + + // Find all that are not at their 'locked' position and move them to it. + // Conflicts are possible but quite unlikely. + cnt = 0; + for (it = trafo.begin();it != trafo.end(); ++it,++cnt) { + if ((*it).lockedPos != AI_TT_UV_IDX_LOCK_NONE && (*it).lockedPos != cnt) { + it2 = trafo.begin();unsigned int t = 0; + while (t != (*it).lockedPos) + ++it2; + + if ((*it2).lockedPos != AI_TT_UV_IDX_LOCK_NONE) { + ASSIMP_LOG_ERROR("Channel mismatch, can't compute all transformations properly [design bug]"); + continue; + } + + std::swap(*it2,*it); + if ((*it).lockedPos == untransformed) + untransformed = cnt; + } + } + + // ... and add dummies for all unreferenced channels + // at the end of the list + bool ref[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n) + ref[n] = !mesh->mTextureCoords[n]; + + for (it = trafo.begin();it != trafo.end(); ++it) + ref[(*it).uvIndex] = true; + + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n) { + if (ref[n]) + continue; + trafo.push_back(STransformVecInfo()); + trafo.back().uvIndex = n; + } + + // Then check whether this list breaks the channel limit. + // The unimportant ones are at the end of the list, so + // it shouldn't be too worse if we remove them. + unsigned int size = (unsigned int)trafo.size(); + if (size > AI_MAX_NUMBER_OF_TEXTURECOORDS) { + + if (!DefaultLogger::isNullLogger()) { + ASSIMP_LOG_ERROR(static_cast<unsigned int>(trafo.size()), " UV channels required but just ", + AI_MAX_NUMBER_OF_TEXTURECOORDS, " available"); + } + size = AI_MAX_NUMBER_OF_TEXTURECOORDS; + } + + + aiVector3D* old[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n) + old[n] = mesh->mTextureCoords[n]; + + // Now continue and generate the output channels. Channels + // that we're not going to need later can be overridden. + it = trafo.begin(); + for (unsigned int n = 0; n < trafo.size();++n,++it) { + + if (n >= size) { + // Try to use an untransformed channel for all channels we threw over board + UpdateUVIndex((*it).updateList,untransformed); + continue; + } + + outChannels++; + + // Write to the log + if (!DefaultLogger::isNullLogger()) { + ::ai_snprintf(buffer,1024,"Mesh %u, channel %u: t(%.3f,%.3f), s(%.3f,%.3f), r(%.3f), %s%s", + q,n, + (*it).mTranslation.x, + (*it).mTranslation.y, + (*it).mScaling.x, + (*it).mScaling.y, + AI_RAD_TO_DEG( (*it).mRotation), + MappingModeToChar ((*it).mapU), + MappingModeToChar ((*it).mapV)); + + ASSIMP_LOG_INFO(buffer); + } + + // Check whether we need a new buffer here + if (mesh->mTextureCoords[n]) { + + it2 = it;++it2; + for (unsigned int m = n+1; m < size;++m, ++it2) { + + if ((*it2).uvIndex == n){ + it2 = trafo.begin(); + break; + } + } + if (it2 == trafo.begin()){ + mesh->mTextureCoords[n] = new aiVector3D[mesh->mNumVertices]; + } + } + else mesh->mTextureCoords[n] = new aiVector3D[mesh->mNumVertices]; + + aiVector3D* src = old[(*it).uvIndex]; + aiVector3D* dest, *end; + dest = mesh->mTextureCoords[n]; + + ai_assert(nullptr != src); + + // Copy the data to the destination array + if (dest != src) + ::memcpy(dest,src,sizeof(aiVector3D)*mesh->mNumVertices); + + end = dest + mesh->mNumVertices; + + // Build a transformation matrix and transform all UV coords with it + if (!(*it).IsUntransformed()) { + const aiVector2D& trl = (*it).mTranslation; + const aiVector2D& scl = (*it).mScaling; + + // fixme: simplify .. + ++transformedChannels; + aiMatrix3x3 matrix; + + aiMatrix3x3 m2,m3,m4,m5; + + m4.a1 = scl.x; + m4.b2 = scl.y; + + m2.a3 = m2.b3 = 0.5f; + m3.a3 = m3.b3 = -0.5f; + + if ((*it).mRotation > AI_TT_ROTATION_EPSILON ) + aiMatrix3x3::RotationZ((*it).mRotation,matrix); + + m5.a3 += trl.x; m5.b3 += trl.y; + matrix = m2 * m4 * matrix * m3 * m5; + + for (src = dest; src != end; ++src) { /* manual homogeneous divide */ + src->z = 1.f; + *src = matrix * *src; + src->x /= src->z; + src->y /= src->z; + src->z = 0.f; + } + } + + // Update all UV indices + UpdateUVIndex((*it).updateList,n); + } + } + + // Print some detailed statistics into the log + if (!DefaultLogger::isNullLogger()) { + + if (transformedChannels) { + ASSIMP_LOG_INFO("TransformUVCoordsProcess end: ", outChannels, " output channels (in: ", inChannels, ", modified: ", transformedChannels,")"); + } else { + ASSIMP_LOG_DEBUG("TransformUVCoordsProcess finished"); + } + } +} + + diff --git a/libs/assimp/code/PostProcessing/TextureTransform.h b/libs/assimp/code/PostProcessing/TextureTransform.h new file mode 100644 index 0000000..c1cccf8 --- /dev/null +++ b/libs/assimp/code/PostProcessing/TextureTransform.h @@ -0,0 +1,232 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Definition of a helper step that processes texture transformations */ +#ifndef AI_TEXTURE_TRANSFORM_H_INCLUDED +#define AI_TEXTURE_TRANSFORM_H_INCLUDED + +#include <assimp/BaseImporter.h> +#include "Common/BaseProcess.h" + +#include <assimp/material.h> +#include <list> + +struct aiNode; +struct aiMaterial; + +namespace Assimp { + +#define AI_TT_UV_IDX_LOCK_TBD 0xffffffff +#define AI_TT_UV_IDX_LOCK_NONE 0xeeeeeeee + + +#define AI_TT_ROTATION_EPSILON ((float)AI_DEG_TO_RAD(0.5)) + +// --------------------------------------------------------------------------- +/** Small helper structure representing a shortcut into the material list + * to be able to update some values quickly. +*/ +struct TTUpdateInfo { + TTUpdateInfo() AI_NO_EXCEPT + : directShortcut(nullptr) + , mat(nullptr) + , semantic(0) + , index(0) { + // empty + } + + //! Direct shortcut, if available + unsigned int* directShortcut; + + //! Material + aiMaterial *mat; + + //! Texture type and index + unsigned int semantic, index; +}; + + +// --------------------------------------------------------------------------- +/** Helper class representing texture coordinate transformations +*/ +struct STransformVecInfo : public aiUVTransform { + STransformVecInfo() AI_NO_EXCEPT + : uvIndex(0) + , mapU(aiTextureMapMode_Wrap) + , mapV(aiTextureMapMode_Wrap) + , lockedPos(AI_TT_UV_IDX_LOCK_NONE) { + // empty + } + + //! Source texture coordinate index + unsigned int uvIndex; + + //! Texture mapping mode in the u, v direction + aiTextureMapMode mapU,mapV; + + //! Locked destination UV index + //! AI_TT_UV_IDX_LOCK_TBD - to be determined + //! AI_TT_UV_IDX_LOCK_NONE - none (default) + unsigned int lockedPos; + + //! Update info - shortcuts into all materials + //! that are referencing this transform setup + std::list<TTUpdateInfo> updateList; + + + // ------------------------------------------------------------------- + /** Compare two transform setups + */ + inline bool operator== (const STransformVecInfo& other) const + { + // We use a small epsilon here + const static float epsilon = 0.05f; + + if (std::fabs( mTranslation.x - other.mTranslation.x ) > epsilon || + std::fabs( mTranslation.y - other.mTranslation.y ) > epsilon) + { + return false; + } + + if (std::fabs( mScaling.x - other.mScaling.x ) > epsilon || + std::fabs( mScaling.y - other.mScaling.y ) > epsilon) + { + return false; + } + + if (std::fabs( mRotation - other.mRotation) > epsilon) + { + return false; + } + return true; + } + + inline bool operator!= (const STransformVecInfo& other) const + { + return !(*this == other); + } + + + // ------------------------------------------------------------------- + /** Returns whether this is an untransformed texture coordinate set + */ + inline bool IsUntransformed() const + { + return (1.0f == mScaling.x && 1.f == mScaling.y && + !mTranslation.x && !mTranslation.y && + mRotation < AI_TT_ROTATION_EPSILON); + } + + // ------------------------------------------------------------------- + /** Build a 3x3 matrix from the transformations + */ + inline void GetMatrix(aiMatrix3x3& mOut) + { + mOut = aiMatrix3x3(); + + if (1.0f != mScaling.x || 1.0f != mScaling.y) + { + aiMatrix3x3 mScale; + mScale.a1 = mScaling.x; + mScale.b2 = mScaling.y; + mOut = mScale; + } + if (mRotation) + { + aiMatrix3x3 mRot; + mRot.a1 = mRot.b2 = std::cos(mRotation); + mRot.a2 = mRot.b1 = std::sin(mRotation); + mRot.a2 = -mRot.a2; + mOut *= mRot; + } + if (mTranslation.x || mTranslation.y) + { + aiMatrix3x3 mTrans; + mTrans.a3 = mTranslation.x; + mTrans.b3 = mTranslation.y; + mOut *= mTrans; + } + } +}; + + +// --------------------------------------------------------------------------- +/** Helper step to compute final UV coordinate sets if there are scalings + * or rotations in the original data read from the file. +*/ +class TextureTransformStep : public BaseProcess +{ +public: + + TextureTransformStep(); + ~TextureTransformStep(); + +public: + + // ------------------------------------------------------------------- + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + void SetupProperties(const Importer* pImp); + + +protected: + + + // ------------------------------------------------------------------- + /** Preprocess a specific UV transformation setup + * + * @param info Transformation setup to be preprocessed. + */ + void PreProcessUVTransform(STransformVecInfo& info); + +private: + + unsigned int configFlags; +}; + +} + +#endif //! AI_TEXTURE_TRANSFORM_H_INCLUDED diff --git a/libs/assimp/code/PostProcessing/TriangulateProcess.cpp b/libs/assimp/code/PostProcessing/TriangulateProcess.cpp new file mode 100644 index 0000000..a18bf1c --- /dev/null +++ b/libs/assimp/code/PostProcessing/TriangulateProcess.cpp @@ -0,0 +1,628 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 TriangulateProcess.cpp + * @brief Implementation of the post processing step to split up + * all faces with more than three indices into triangles. + * + * + * The triangulation algorithm will handle concave or convex polygons. + * Self-intersecting or non-planar polygons are not rejected, but + * they're probably not triangulated correctly. + * + * DEBUG SWITCHES - do not enable any of them in release builds: + * + * AI_BUILD_TRIANGULATE_COLOR_FACE_WINDING + * - generates vertex colors to represent the face winding order. + * the first vertex of a polygon becomes red, the last blue. + * AI_BUILD_TRIANGULATE_DEBUG_POLYS + * - dump all polygons and their triangulation sequences to + * a file + */ +#ifndef ASSIMP_BUILD_NO_TRIANGULATE_PROCESS + +#include "PostProcessing/TriangulateProcess.h" +#include "PostProcessing/ProcessHelper.h" +#include "Common/PolyTools.h" + +#include <memory> +#include <cstdint> + +//#define AI_BUILD_TRIANGULATE_COLOR_FACE_WINDING +//#define AI_BUILD_TRIANGULATE_DEBUG_POLYS + +#define POLY_GRID_Y 40 +#define POLY_GRID_X 70 +#define POLY_GRID_XPAD 20 +#define POLY_OUTPUT_FILE "assimp_polygons_debug.txt" + +using namespace Assimp; + +namespace { + + /** + * @brief Helper struct used to simplify NGON encoding functions. + */ + struct NGONEncoder { + NGONEncoder() : mLastNGONFirstIndex((unsigned int)-1) {} + + /** + * @brief Encode the current triangle, and make sure it is recognized as a triangle. + * + * This method will rotate indices in tri if needed in order to avoid tri to be considered + * part of the previous ngon. This method is to be used whenever you want to emit a real triangle, + * and make sure it is seen as a triangle. + * + * @param tri Triangle to encode. + */ + void ngonEncodeTriangle(aiFace * tri) { + ai_assert(tri->mNumIndices == 3); + + // Rotate indices in new triangle to avoid ngon encoding false ngons + // Otherwise, the new triangle would be considered part of the previous NGON. + if (isConsideredSameAsLastNgon(tri)) { + std::swap(tri->mIndices[0], tri->mIndices[2]); + std::swap(tri->mIndices[1], tri->mIndices[2]); + } + + mLastNGONFirstIndex = tri->mIndices[0]; + } + + /** + * @brief Encode a quad (2 triangles) in ngon encoding, and make sure they are seen as a single ngon. + * + * @param tri1 First quad triangle + * @param tri2 Second quad triangle + * + * @pre Triangles must be properly fanned from the most appropriate vertex. + */ + void ngonEncodeQuad(aiFace *tri1, aiFace *tri2) { + ai_assert(tri1->mNumIndices == 3); + ai_assert(tri2->mNumIndices == 3); + ai_assert(tri1->mIndices[0] == tri2->mIndices[0]); + + // If the selected fanning vertex is the same as the previously + // emitted ngon, we use the opposite vertex which also happens to work + // for tri-fanning a concave quad. + // ref: https://github.com/assimp/assimp/pull/3695#issuecomment-805999760 + if (isConsideredSameAsLastNgon(tri1)) { + // Right-rotate indices for tri1 (index 2 becomes the new fanning vertex) + std::swap(tri1->mIndices[0], tri1->mIndices[2]); + std::swap(tri1->mIndices[1], tri1->mIndices[2]); + + // Left-rotate indices for tri2 (index 2 becomes the new fanning vertex) + std::swap(tri2->mIndices[1], tri2->mIndices[2]); + std::swap(tri2->mIndices[0], tri2->mIndices[2]); + + ai_assert(tri1->mIndices[0] == tri2->mIndices[0]); + } + + mLastNGONFirstIndex = tri1->mIndices[0]; + } + + /** + * @brief Check whether this triangle would be considered part of the lastly emitted ngon or not. + * + * @param tri Current triangle. + * @return true If used as is, this triangle will be part of last ngon. + * @return false If used as is, this triangle is not considered part of the last ngon. + */ + bool isConsideredSameAsLastNgon(const aiFace * tri) const { + ai_assert(tri->mNumIndices == 3); + return tri->mIndices[0] == mLastNGONFirstIndex; + } + + private: + unsigned int mLastNGONFirstIndex; + }; + +} + + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +TriangulateProcess::TriangulateProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +TriangulateProcess::~TriangulateProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool TriangulateProcess::IsActive( unsigned int pFlags) const +{ + return (pFlags & aiProcess_Triangulate) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void TriangulateProcess::Execute( aiScene* pScene) +{ + ASSIMP_LOG_DEBUG("TriangulateProcess begin"); + + bool bHas = false; + for( unsigned int a = 0; a < pScene->mNumMeshes; a++) + { + if (pScene->mMeshes[ a ]) { + if ( TriangulateMesh( pScene->mMeshes[ a ] ) ) { + bHas = true; + } + } + } + if ( bHas ) { + ASSIMP_LOG_INFO( "TriangulateProcess finished. All polygons have been triangulated." ); + } else { + ASSIMP_LOG_DEBUG( "TriangulateProcess finished. There was nothing to be done." ); + } +} + +// ------------------------------------------------------------------------------------------------ +// Triangulates the given mesh. +bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) +{ + // Now we have aiMesh::mPrimitiveTypes, so this is only here for test cases + if (!pMesh->mPrimitiveTypes) { + bool bNeed = false; + + for( unsigned int a = 0; a < pMesh->mNumFaces; a++) { + const aiFace& face = pMesh->mFaces[a]; + + if( face.mNumIndices != 3) { + bNeed = true; + } + } + if (!bNeed) + return false; + } + else if (!(pMesh->mPrimitiveTypes & aiPrimitiveType_POLYGON)) { + return false; + } + + // Find out how many output faces we'll get + uint32_t numOut = 0, max_out = 0; + bool get_normals = true; + for( unsigned int a = 0; a < pMesh->mNumFaces; a++) { + aiFace& face = pMesh->mFaces[a]; + if (face.mNumIndices <= 4) { + get_normals = false; + } + if( face.mNumIndices <= 3) { + numOut++; + + } + else { + numOut += face.mNumIndices-2; + max_out = std::max(max_out,face.mNumIndices); + } + } + + // Just another check whether aiMesh::mPrimitiveTypes is correct + ai_assert(numOut != pMesh->mNumFaces); + + aiVector3D *nor_out = nullptr; + + // if we don't have normals yet, but expect them to be a cheap side + // product of triangulation anyway, allocate storage for them. + if (!pMesh->mNormals && get_normals) { + // XXX need a mechanism to inform the GenVertexNormals process to treat these normals as preprocessed per-face normals + // nor_out = pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + } + + // the output mesh will contain triangles, but no polys anymore + pMesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + pMesh->mPrimitiveTypes &= ~aiPrimitiveType_POLYGON; + + // The mesh becomes NGON encoded now, during the triangulation process. + pMesh->mPrimitiveTypes |= aiPrimitiveType_NGONEncodingFlag; + + aiFace* out = new aiFace[numOut](), *curOut = out; + std::vector<aiVector3D> temp_verts3d(max_out+2); /* temporary storage for vertices */ + std::vector<aiVector2D> temp_verts(max_out+2); + + NGONEncoder ngonEncoder; + + // Apply vertex colors to represent the face winding? +#ifdef AI_BUILD_TRIANGULATE_COLOR_FACE_WINDING + if (!pMesh->mColors[0]) + pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices]; + else + new(pMesh->mColors[0]) aiColor4D[pMesh->mNumVertices]; + + aiColor4D* clr = pMesh->mColors[0]; +#endif + +#ifdef AI_BUILD_TRIANGULATE_DEBUG_POLYS + FILE* fout = fopen(POLY_OUTPUT_FILE,"a"); +#endif + + const aiVector3D* verts = pMesh->mVertices; + + // use std::unique_ptr to avoid slow std::vector<bool> specialiations + std::unique_ptr<bool[]> done(new bool[max_out]); + for( unsigned int a = 0; a < pMesh->mNumFaces; a++) { + aiFace& face = pMesh->mFaces[a]; + + unsigned int* idx = face.mIndices; + int num = (int)face.mNumIndices, ear = 0, tmp, prev = num-1, next = 0, max = num; + + // Apply vertex colors to represent the face winding? +#ifdef AI_BUILD_TRIANGULATE_COLOR_FACE_WINDING + for (unsigned int i = 0; i < face.mNumIndices; ++i) { + aiColor4D& c = clr[idx[i]]; + c.r = (i+1) / (float)max; + c.b = 1.f - c.r; + } +#endif + + aiFace* const last_face = curOut; + + // if it's a simple point,line or triangle: just copy it + if( face.mNumIndices <= 3) + { + aiFace& nface = *curOut++; + nface.mNumIndices = face.mNumIndices; + nface.mIndices = face.mIndices; + face.mIndices = nullptr; + + // points and lines don't require ngon encoding (and are not supported either!) + if (nface.mNumIndices == 3) ngonEncoder.ngonEncodeTriangle(&nface); + + continue; + } + // optimized code for quadrilaterals + else if ( face.mNumIndices == 4) { + + // quads can have at maximum one concave vertex. Determine + // this vertex (if it exists) and start tri-fanning from + // it. + unsigned int start_vertex = 0; + for (unsigned int i = 0; i < 4; ++i) { + const aiVector3D& v0 = verts[face.mIndices[(i+3) % 4]]; + const aiVector3D& v1 = verts[face.mIndices[(i+2) % 4]]; + const aiVector3D& v2 = verts[face.mIndices[(i+1) % 4]]; + + const aiVector3D& v = verts[face.mIndices[i]]; + + aiVector3D left = (v0-v); + aiVector3D diag = (v1-v); + aiVector3D right = (v2-v); + + left.Normalize(); + diag.Normalize(); + right.Normalize(); + + const float angle = std::acos(left*diag) + std::acos(right*diag); + if (angle > AI_MATH_PI_F) { + // this is the concave point + start_vertex = i; + break; + } + } + + const unsigned int temp[] = {face.mIndices[0], face.mIndices[1], face.mIndices[2], face.mIndices[3]}; + + aiFace& nface = *curOut++; + nface.mNumIndices = 3; + nface.mIndices = face.mIndices; + + nface.mIndices[0] = temp[start_vertex]; + nface.mIndices[1] = temp[(start_vertex + 1) % 4]; + nface.mIndices[2] = temp[(start_vertex + 2) % 4]; + + aiFace& sface = *curOut++; + sface.mNumIndices = 3; + sface.mIndices = new unsigned int[3]; + + sface.mIndices[0] = temp[start_vertex]; + sface.mIndices[1] = temp[(start_vertex + 2) % 4]; + sface.mIndices[2] = temp[(start_vertex + 3) % 4]; + + // prevent double deletion of the indices field + face.mIndices = nullptr; + + ngonEncoder.ngonEncodeQuad(&nface, &sface); + + continue; + } + else + { + // A polygon with more than 3 vertices can be either concave or convex. + // Usually everything we're getting is convex and we could easily + // triangulate by tri-fanning. However, LightWave is probably the only + // modeling suite to make extensive use of highly concave, monster polygons ... + // so we need to apply the full 'ear cutting' algorithm to get it right. + + // REQUIREMENT: polygon is expected to be simple and *nearly* planar. + // We project it onto a plane to get a 2d triangle. + + // Collect all vertices of of the polygon. + for (tmp = 0; tmp < max; ++tmp) { + temp_verts3d[tmp] = verts[idx[tmp]]; + } + + // Get newell normal of the polygon. Store it for future use if it's a polygon-only mesh + aiVector3D n; + NewellNormal<3,3,3>(n,max,&temp_verts3d.front().x,&temp_verts3d.front().y,&temp_verts3d.front().z); + if (nor_out) { + for (tmp = 0; tmp < max; ++tmp) + nor_out[idx[tmp]] = n; + } + + // Select largest normal coordinate to ignore for projection + const float ax = (n.x>0 ? n.x : -n.x); + const float ay = (n.y>0 ? n.y : -n.y); + const float az = (n.z>0 ? n.z : -n.z); + + unsigned int ac = 0, bc = 1; /* no z coord. projection to xy */ + float inv = n.z; + if (ax > ay) { + if (ax > az) { /* no x coord. projection to yz */ + ac = 1; bc = 2; + inv = n.x; + } + } + else if (ay > az) { /* no y coord. projection to zy */ + ac = 2; bc = 0; + inv = n.y; + } + + // Swap projection axes to take the negated projection vector into account + if (inv < 0.f) { + std::swap(ac,bc); + } + + for (tmp =0; tmp < max; ++tmp) { + temp_verts[tmp].x = verts[idx[tmp]][ac]; + temp_verts[tmp].y = verts[idx[tmp]][bc]; + done[tmp] = false; + } + +#ifdef AI_BUILD_TRIANGULATE_DEBUG_POLYS + // plot the plane onto which we mapped the polygon to a 2D ASCII pic + aiVector2D bmin,bmax; + ArrayBounds(&temp_verts[0],max,bmin,bmax); + + char grid[POLY_GRID_Y][POLY_GRID_X+POLY_GRID_XPAD]; + std::fill_n((char*)grid,POLY_GRID_Y*(POLY_GRID_X+POLY_GRID_XPAD),' '); + + for (int i =0; i < max; ++i) { + const aiVector2D& v = (temp_verts[i] - bmin) / (bmax-bmin); + const size_t x = static_cast<size_t>(v.x*(POLY_GRID_X-1)), y = static_cast<size_t>(v.y*(POLY_GRID_Y-1)); + char* loc = grid[y]+x; + if (grid[y][x] != ' ') { + for(;*loc != ' '; ++loc); + *loc++ = '_'; + } + *(loc+::ai_snprintf(loc, POLY_GRID_XPAD,"%i",i)) = ' '; + } + + + for(size_t y = 0; y < POLY_GRID_Y; ++y) { + grid[y][POLY_GRID_X+POLY_GRID_XPAD-1] = '\0'; + fprintf(fout,"%s\n",grid[y]); + } + + fprintf(fout,"\ntriangulation sequence: "); +#endif + + // + // FIXME: currently this is the slow O(kn) variant with a worst case + // complexity of O(n^2) (I think). Can be done in O(n). + while (num > 3) { + + // Find the next ear of the polygon + int num_found = 0; + for (ear = next;;prev = ear,ear = next) { + + // break after we looped two times without a positive match + for (next=ear+1;done[(next>=max?next=0:next)];++next); + if (next < ear) { + if (++num_found == 2) { + break; + } + } + const aiVector2D* pnt1 = &temp_verts[ear], + *pnt0 = &temp_verts[prev], + *pnt2 = &temp_verts[next]; + + // Must be a convex point. Assuming ccw winding, it must be on the right of the line between p-1 and p+1. + if (OnLeftSideOfLine2D(*pnt0,*pnt2,*pnt1)) { + continue; + } + + // and no other point may be contained in this triangle + for ( tmp = 0; tmp < max; ++tmp) { + + // We need to compare the actual values because it's possible that multiple indexes in + // the polygon are referring to the same position. concave_polygon.obj is a sample + // + // FIXME: Use 'epsiloned' comparisons instead? Due to numeric inaccuracies in + // PointInTriangle() I'm guessing that it's actually possible to construct + // input data that would cause us to end up with no ears. The problem is, + // which epsilon? If we chose a too large value, we'd get wrong results + const aiVector2D& vtmp = temp_verts[tmp]; + if ( vtmp != *pnt1 && vtmp != *pnt2 && vtmp != *pnt0 && PointInTriangle2D(*pnt0,*pnt1,*pnt2,vtmp)) { + break; + } + } + if (tmp != max) { + continue; + } + + // this vertex is an ear + break; + } + if (num_found == 2) { + + // Due to the 'two ear theorem', every simple polygon with more than three points must + // have 2 'ears'. Here's definitely something wrong ... but we don't give up yet. + // + + // Instead we're continuing with the standard tri-fanning algorithm which we'd + // use if we had only convex polygons. That's life. + ASSIMP_LOG_ERROR("Failed to triangulate polygon (no ear found). Probably not a simple polygon?"); + +#ifdef AI_BUILD_TRIANGULATE_DEBUG_POLYS + fprintf(fout,"critical error here, no ear found! "); +#endif + num = 0; + break; + + /*curOut -= (max-num); // undo all previous work + for (tmp = 0; tmp < max-2; ++tmp) { + aiFace& nface = *curOut++; + + nface.mNumIndices = 3; + if (!nface.mIndices) + nface.mIndices = new unsigned int[3]; + + nface.mIndices[0] = 0; + nface.mIndices[1] = tmp+1; + nface.mIndices[2] = tmp+2; + + } + num = 0; + break;*/ + } + + aiFace& nface = *curOut++; + nface.mNumIndices = 3; + + if (!nface.mIndices) { + nface.mIndices = new unsigned int[3]; + } + + // setup indices for the new triangle ... + nface.mIndices[0] = prev; + nface.mIndices[1] = ear; + nface.mIndices[2] = next; + + // exclude the ear from most further processing + done[ear] = true; + --num; + } + if (num > 0) { + // We have three indices forming the last 'ear' remaining. Collect them. + aiFace& nface = *curOut++; + nface.mNumIndices = 3; + if (!nface.mIndices) { + nface.mIndices = new unsigned int[3]; + } + + for (tmp = 0; done[tmp]; ++tmp); + nface.mIndices[0] = tmp; + + for (++tmp; done[tmp]; ++tmp); + nface.mIndices[1] = tmp; + + for (++tmp; done[tmp]; ++tmp); + nface.mIndices[2] = tmp; + + } + } + +#ifdef AI_BUILD_TRIANGULATE_DEBUG_POLYS + + for(aiFace* f = last_face; f != curOut; ++f) { + unsigned int* i = f->mIndices; + fprintf(fout," (%i %i %i)",i[0],i[1],i[2]); + } + + fprintf(fout,"\n*********************************************************************\n"); + fflush(fout); + +#endif + + for(aiFace* f = last_face; f != curOut; ) { + unsigned int* i = f->mIndices; + + // drop dumb 0-area triangles - deactivated for now: + //FindDegenerates post processing step can do the same thing + //if (std::fabs(GetArea2D(temp_verts[i[0]],temp_verts[i[1]],temp_verts[i[2]])) < 1e-5f) { + // ASSIMP_LOG_VERBOSE_DEBUG("Dropping triangle with area 0"); + // --curOut; + + // delete[] f->mIndices; + // f->mIndices = nullptr; + + // for(aiFace* ff = f; ff != curOut; ++ff) { + // ff->mNumIndices = (ff+1)->mNumIndices; + // ff->mIndices = (ff+1)->mIndices; + // (ff+1)->mIndices = nullptr; + // } + // continue; + //} + + i[0] = idx[i[0]]; + i[1] = idx[i[1]]; + i[2] = idx[i[2]]; + + // IMPROVEMENT: Polygons are not supported yet by this ngon encoding + triangulation step. + // So we encode polygons as regular triangles. No way to reconstruct the original + // polygon in this case. + ngonEncoder.ngonEncodeTriangle(f); + ++f; + } + + delete[] face.mIndices; + face.mIndices = nullptr; + } + +#ifdef AI_BUILD_TRIANGULATE_DEBUG_POLYS + fclose(fout); +#endif + + // kill the old faces + delete [] pMesh->mFaces; + + // ... and store the new ones + pMesh->mFaces = out; + pMesh->mNumFaces = (unsigned int)(curOut-out); /* not necessarily equal to numOut */ + return true; +} + +#endif // !! ASSIMP_BUILD_NO_TRIANGULATE_PROCESS diff --git a/libs/assimp/code/PostProcessing/TriangulateProcess.h b/libs/assimp/code/PostProcessing/TriangulateProcess.h new file mode 100644 index 0000000..ed5f4a5 --- /dev/null +++ b/libs/assimp/code/PostProcessing/TriangulateProcess.h @@ -0,0 +1,91 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a post processing step to triangulate all faces + with more than three vertices. + */ +#ifndef AI_TRIANGULATEPROCESS_H_INC +#define AI_TRIANGULATEPROCESS_H_INC + +#include "Common/BaseProcess.h" + +struct aiMesh; + +class TriangulateProcessTest; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** The TriangulateProcess splits up all faces with more than three indices + * into triangles. You usually want this to happen because the graphics cards + * need their data as triangles. + */ +class ASSIMP_API TriangulateProcess : public BaseProcess { +public: + TriangulateProcess(); + ~TriangulateProcess(); + + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag field. + * @param pFlags The processing flags the importer was called with. A bitwise + * combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, false if not. + */ + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + + // ------------------------------------------------------------------- + /** Triangulates the given mesh. + * @param pMesh The mesh to triangulate. + */ + bool TriangulateMesh( aiMesh* pMesh); +}; + +} // end of namespace Assimp + +#endif // AI_TRIANGULATEPROCESS_H_INC diff --git a/libs/assimp/code/PostProcessing/ValidateDataStructure.cpp b/libs/assimp/code/PostProcessing/ValidateDataStructure.cpp new file mode 100644 index 0000000..e99f6a6 --- /dev/null +++ b/libs/assimp/code/PostProcessing/ValidateDataStructure.cpp @@ -0,0 +1,942 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 ValidateDataStructure.cpp + * @brief Implementation of the post processing step to validate + * the data structure returned by Assimp. + */ + +// internal headers +#include "ValidateDataStructure.h" +#include "ProcessHelper.h" +#include <assimp/BaseImporter.h> +#include <assimp/fast_atof.h> +#include <memory> + +// CRT headers +#include <stdarg.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +ValidateDSProcess::ValidateDSProcess() : + mScene() {} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +ValidateDSProcess::~ValidateDSProcess() {} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool ValidateDSProcess::IsActive(unsigned int pFlags) const { + return (pFlags & aiProcess_ValidateDataStructure) != 0; +} +// ------------------------------------------------------------------------------------------------ +AI_WONT_RETURN void ValidateDSProcess::ReportError(const char *msg, ...) { + ai_assert(nullptr != msg); + + va_list args; + va_start(args, msg); + + char szBuffer[3000]; + const int iLen = vsprintf(szBuffer, msg, args); + ai_assert(iLen > 0); + + va_end(args); + + throw DeadlyImportError("Validation failed: ", std::string(szBuffer, iLen)); +} +// ------------------------------------------------------------------------------------------------ +void ValidateDSProcess::ReportWarning(const char *msg, ...) { + ai_assert(nullptr != msg); + + va_list args; + va_start(args, msg); + + char szBuffer[3000]; + const int iLen = vsprintf(szBuffer, msg, args); + ai_assert(iLen > 0); + + va_end(args); + ASSIMP_LOG_WARN("Validation warning: ", std::string(szBuffer, iLen)); +} + +// ------------------------------------------------------------------------------------------------ +inline int HasNameMatch(const aiString &in, aiNode *node) { + int result = (node->mName == in ? 1 : 0); + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + result += HasNameMatch(in, node->mChildren[i]); + } + return result; +} + +// ------------------------------------------------------------------------------------------------ +template <typename T> +inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) { + // validate all entries + if (size) { + if (!parray) { + ReportError("aiScene::%s is nullptr (aiScene::%s is %i)", + firstName, secondName, size); + } + for (unsigned int i = 0; i < size; ++i) { + if (!parray[i]) { + ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)", + firstName, i, secondName, size); + } + Validate(parray[i]); + } + } +} + +// ------------------------------------------------------------------------------------------------ +template <typename T> +inline void ValidateDSProcess::DoValidationEx(T **parray, unsigned int size, + const char *firstName, const char *secondName) { + // validate all entries + if (size) { + if (!parray) { + ReportError("aiScene::%s is nullptr (aiScene::%s is %i)", + firstName, secondName, size); + } + for (unsigned int i = 0; i < size; ++i) { + if (!parray[i]) { + ReportError("aiScene::%s[%u] is nullptr (aiScene::%s is %u)", + firstName, i, secondName, size); + } + Validate(parray[i]); + + // check whether there are duplicate names + for (unsigned int a = i + 1; a < size; ++a) { + if (parray[i]->mName == parray[a]->mName) { + ReportError("aiScene::%s[%u] has the same name as " + "aiScene::%s[%u]", + firstName, i, secondName, a); + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +template <typename T> +inline void ValidateDSProcess::DoValidationWithNameCheck(T **array, unsigned int size, const char *firstName, + const char *secondName) { + // validate all entries + DoValidationEx(array, size, firstName, secondName); + + for (unsigned int i = 0; i < size; ++i) { + int res = HasNameMatch(array[i]->mName, mScene->mRootNode); + if (0 == res) { + const std::string name = static_cast<char *>(array[i]->mName.data); + ReportError("aiScene::%s[%i] has no corresponding node in the scene graph (%s)", + firstName, i, name.c_str()); + } else if (1 != res) { + const std::string name = static_cast<char *>(array[i]->mName.data); + ReportError("aiScene::%s[%i]: there are more than one nodes with %s as name", + firstName, i, name.c_str()); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void ValidateDSProcess::Execute(aiScene *pScene) { + mScene = pScene; + ASSIMP_LOG_DEBUG("ValidateDataStructureProcess begin"); + + // validate the node graph of the scene + Validate(pScene->mRootNode); + + // validate all meshes + if (pScene->mNumMeshes) { + DoValidation(pScene->mMeshes, pScene->mNumMeshes, "mMeshes", "mNumMeshes"); + } else if (!(mScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE)) { + ReportError("aiScene::mNumMeshes is 0. At least one mesh must be there"); + } else if (pScene->mMeshes) { + ReportError("aiScene::mMeshes is non-null although there are no meshes"); + } + + // validate all animations + if (pScene->mNumAnimations) { + DoValidation(pScene->mAnimations, pScene->mNumAnimations, + "mAnimations", "mNumAnimations"); + } else if (pScene->mAnimations) { + ReportError("aiScene::mAnimations is non-null although there are no animations"); + } + + // validate all cameras + if (pScene->mNumCameras) { + DoValidationWithNameCheck(pScene->mCameras, pScene->mNumCameras, + "mCameras", "mNumCameras"); + } else if (pScene->mCameras) { + ReportError("aiScene::mCameras is non-null although there are no cameras"); + } + + // validate all lights + if (pScene->mNumLights) { + DoValidationWithNameCheck(pScene->mLights, pScene->mNumLights, + "mLights", "mNumLights"); + } else if (pScene->mLights) { + ReportError("aiScene::mLights is non-null although there are no lights"); + } + + // validate all textures + if (pScene->mNumTextures) { + DoValidation(pScene->mTextures, pScene->mNumTextures, + "mTextures", "mNumTextures"); + } else if (pScene->mTextures) { + ReportError("aiScene::mTextures is non-null although there are no textures"); + } + + // validate all materials + if (pScene->mNumMaterials) { + DoValidation(pScene->mMaterials, pScene->mNumMaterials, "mMaterials", "mNumMaterials"); + } +#if 0 + // NOTE: ScenePreprocessor generates a default material if none is there + else if (!(mScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE)) { + ReportError("aiScene::mNumMaterials is 0. At least one material must be there"); + } +#endif + else if (pScene->mMaterials) { + ReportError("aiScene::mMaterials is non-null although there are no materials"); + } + + // if (!has)ReportError("The aiScene data structure is empty"); + ASSIMP_LOG_DEBUG("ValidateDataStructureProcess end"); +} + +// ------------------------------------------------------------------------------------------------ +void ValidateDSProcess::Validate(const aiLight *pLight) { + if (pLight->mType == aiLightSource_UNDEFINED) + ReportWarning("aiLight::mType is aiLightSource_UNDEFINED"); + + if (!pLight->mAttenuationConstant && + !pLight->mAttenuationLinear && + !pLight->mAttenuationQuadratic) { + ReportWarning("aiLight::mAttenuationXXX - all are zero"); + } + + if (pLight->mAngleInnerCone > pLight->mAngleOuterCone) + ReportError("aiLight::mAngleInnerCone is larger than aiLight::mAngleOuterCone"); + + if (pLight->mColorDiffuse.IsBlack() && pLight->mColorAmbient.IsBlack() && pLight->mColorSpecular.IsBlack()) { + ReportWarning("aiLight::mColorXXX - all are black and won't have any influence"); + } +} + +// ------------------------------------------------------------------------------------------------ +void ValidateDSProcess::Validate(const aiCamera *pCamera) { + if (pCamera->mClipPlaneFar <= pCamera->mClipPlaneNear) + ReportError("aiCamera::mClipPlaneFar must be >= aiCamera::mClipPlaneNear"); + + // FIX: there are many 3ds files with invalid FOVs. No reason to + // reject them at all ... a warning is appropriate. + if (!pCamera->mHorizontalFOV || pCamera->mHorizontalFOV >= (float)AI_MATH_PI) + ReportWarning("%f is not a valid value for aiCamera::mHorizontalFOV", pCamera->mHorizontalFOV); +} + +// ------------------------------------------------------------------------------------------------ +void ValidateDSProcess::Validate(const aiMesh *pMesh) { + // validate the material index of the mesh + if (mScene->mNumMaterials && pMesh->mMaterialIndex >= mScene->mNumMaterials) { + ReportError("aiMesh::mMaterialIndex is invalid (value: %i maximum: %i)", + pMesh->mMaterialIndex, mScene->mNumMaterials - 1); + } + + Validate(&pMesh->mName); + + for (unsigned int i = 0; i < pMesh->mNumFaces; ++i) { + aiFace &face = pMesh->mFaces[i]; + + if (pMesh->mPrimitiveTypes) { + switch (face.mNumIndices) { + case 0: + ReportError("aiMesh::mFaces[%i].mNumIndices is 0", i); + break; + case 1: + if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_POINT)) { + ReportError("aiMesh::mFaces[%i] is a POINT but aiMesh::mPrimitiveTypes " + "does not report the POINT flag", + i); + } + break; + case 2: + if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_LINE)) { + ReportError("aiMesh::mFaces[%i] is a LINE but aiMesh::mPrimitiveTypes " + "does not report the LINE flag", + i); + } + break; + case 3: + if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE)) { + ReportError("aiMesh::mFaces[%i] is a TRIANGLE but aiMesh::mPrimitiveTypes " + "does not report the TRIANGLE flag", + i); + } + break; + default: + if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_POLYGON)) { + this->ReportError("aiMesh::mFaces[%i] is a POLYGON but aiMesh::mPrimitiveTypes " + "does not report the POLYGON flag", + i); + } + break; + }; + } + + if (!face.mIndices) + ReportError("aiMesh::mFaces[%i].mIndices is nullptr", i); + } + + // positions must always be there ... + if (!pMesh->mNumVertices || (!pMesh->mVertices && !mScene->mFlags)) { + ReportError("The mesh %s contains no vertices", pMesh->mName.C_Str()); + } + + if (pMesh->mNumVertices > AI_MAX_VERTICES) { + ReportError("Mesh has too many vertices: %u, but the limit is %u", pMesh->mNumVertices, AI_MAX_VERTICES); + } + if (pMesh->mNumFaces > AI_MAX_FACES) { + ReportError("Mesh has too many faces: %u, but the limit is %u", pMesh->mNumFaces, AI_MAX_FACES); + } + + // if tangents are there there must also be bitangent vectors ... + if ((pMesh->mTangents != nullptr) != (pMesh->mBitangents != nullptr)) { + ReportError("If there are tangents, bitangent vectors must be present as well"); + } + + // faces, too + if (!pMesh->mNumFaces || (!pMesh->mFaces && !mScene->mFlags)) { + ReportError("Mesh %s contains no faces", pMesh->mName.C_Str()); + } + + // now check whether the face indexing layout is correct: + // unique vertices, pseudo-indexed. + std::vector<bool> abRefList; + abRefList.resize(pMesh->mNumVertices, false); + for (unsigned int i = 0; i < pMesh->mNumFaces; ++i) { + aiFace &face = pMesh->mFaces[i]; + if (face.mNumIndices > AI_MAX_FACE_INDICES) { + ReportError("Face %u has too many faces: %u, but the limit is %u", i, face.mNumIndices, AI_MAX_FACE_INDICES); + } + + for (unsigned int a = 0; a < face.mNumIndices; ++a) { + if (face.mIndices[a] >= pMesh->mNumVertices) { + ReportError("aiMesh::mFaces[%i]::mIndices[%i] is out of range", i, a); + } + // the MSB flag is temporarily used by the extra verbose + // mode to tell us that the JoinVerticesProcess might have + // been executed already. + /*if ( !(this->mScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT ) && !(this->mScene->mFlags & AI_SCENE_FLAGS_ALLOW_SHARED) && + abRefList[face.mIndices[a]]) + { + ReportError("aiMesh::mVertices[%i] is referenced twice - second " + "time by aiMesh::mFaces[%i]::mIndices[%i]",face.mIndices[a],i,a); + }*/ + abRefList[face.mIndices[a]] = true; + } + } + + // check whether there are vertices that aren't referenced by a face + bool b = false; + for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) { + if (!abRefList[i]) b = true; + } + abRefList.clear(); + if (b) { + ReportWarning("There are unreferenced vertices"); + } + + // texture channel 2 may not be set if channel 1 is zero ... + { + unsigned int i = 0; + for (; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (!pMesh->HasTextureCoords(i)) break; + } + for (; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) + if (pMesh->HasTextureCoords(i)) { + ReportError("Texture coordinate channel %i exists " + "although the previous channel was nullptr.", + i); + } + } + // the same for the vertex colors + { + unsigned int i = 0; + for (; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) { + if (!pMesh->HasVertexColors(i)) break; + } + for (; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) + if (pMesh->HasVertexColors(i)) { + ReportError("Vertex color channel %i is exists " + "although the previous channel was nullptr.", + i); + } + } + + // now validate all bones + if (pMesh->mNumBones) { + if (!pMesh->mBones) { + ReportError("aiMesh::mBones is nullptr (aiMesh::mNumBones is %i)", + pMesh->mNumBones); + } + std::unique_ptr<float[]> afSum(nullptr); + if (pMesh->mNumVertices) { + afSum.reset(new float[pMesh->mNumVertices]); + for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) + afSum[i] = 0.0f; + } + + // check whether there are duplicate bone names + for (unsigned int i = 0; i < pMesh->mNumBones; ++i) { + const aiBone *bone = pMesh->mBones[i]; + if (bone->mNumWeights > AI_MAX_BONE_WEIGHTS) { + ReportError("Bone %u has too many weights: %u, but the limit is %u", i, bone->mNumWeights, AI_MAX_BONE_WEIGHTS); + } + + if (!pMesh->mBones[i]) { + ReportError("aiMesh::mBones[%i] is nullptr (aiMesh::mNumBones is %i)", + i, pMesh->mNumBones); + } + Validate(pMesh, pMesh->mBones[i], afSum.get()); + + for (unsigned int a = i + 1; a < pMesh->mNumBones; ++a) { + if (pMesh->mBones[i]->mName == pMesh->mBones[a]->mName) { + const char *name = "unknown"; + if (nullptr != pMesh->mBones[i]->mName.C_Str()) { + name = pMesh->mBones[i]->mName.C_Str(); + } + ReportError("aiMesh::mBones[%i], name = \"%s\" has the same name as " + "aiMesh::mBones[%i]", + i, name, a); + } + } + } + // check whether all bone weights for a vertex sum to 1.0 ... + for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) { + if (afSum[i] && (afSum[i] <= 0.94 || afSum[i] >= 1.05)) { + ReportWarning("aiMesh::mVertices[%i]: bone weight sum != 1.0 (sum is %f)", i, afSum[i]); + } + } + } else if (pMesh->mBones) { + ReportError("aiMesh::mBones is non-null although there are no bones"); + } +} + +// ------------------------------------------------------------------------------------------------ +void ValidateDSProcess::Validate(const aiMesh *pMesh, const aiBone *pBone, float *afSum) { + this->Validate(&pBone->mName); + + if (!pBone->mNumWeights) { + //ReportError("aiBone::mNumWeights is zero"); + } + + // check whether all vertices affected by this bone are valid + for (unsigned int i = 0; i < pBone->mNumWeights; ++i) { + if (pBone->mWeights[i].mVertexId >= pMesh->mNumVertices) { + ReportError("aiBone::mWeights[%i].mVertexId is out of range", i); + } else if (!pBone->mWeights[i].mWeight || pBone->mWeights[i].mWeight > 1.0f) { + ReportWarning("aiBone::mWeights[%i].mWeight has an invalid value", i); + } + afSum[pBone->mWeights[i].mVertexId] += pBone->mWeights[i].mWeight; + } +} + +// ------------------------------------------------------------------------------------------------ +void ValidateDSProcess::Validate(const aiAnimation *pAnimation) { + Validate(&pAnimation->mName); + + // validate all animations + if (pAnimation->mNumChannels || pAnimation->mNumMorphMeshChannels) { + if (!pAnimation->mChannels && pAnimation->mNumChannels) { + ReportError("aiAnimation::mChannels is nullptr (aiAnimation::mNumChannels is %i)", + pAnimation->mNumChannels); + } + if (!pAnimation->mMorphMeshChannels && pAnimation->mNumMorphMeshChannels) { + ReportError("aiAnimation::mMorphMeshChannels is nullptr (aiAnimation::mNumMorphMeshChannels is %i)", + pAnimation->mNumMorphMeshChannels); + } + for (unsigned int i = 0; i < pAnimation->mNumChannels; ++i) { + if (!pAnimation->mChannels[i]) { + ReportError("aiAnimation::mChannels[%i] is nullptr (aiAnimation::mNumChannels is %i)", + i, pAnimation->mNumChannels); + } + Validate(pAnimation, pAnimation->mChannels[i]); + } + for (unsigned int i = 0; i < pAnimation->mNumMorphMeshChannels; ++i) { + if (!pAnimation->mMorphMeshChannels[i]) { + ReportError("aiAnimation::mMorphMeshChannels[%i] is nullptr (aiAnimation::mNumMorphMeshChannels is %i)", + i, pAnimation->mNumMorphMeshChannels); + } + Validate(pAnimation, pAnimation->mMorphMeshChannels[i]); + } + } else { + ReportError("aiAnimation::mNumChannels is 0. At least one node animation channel must be there."); + } +} + +// ------------------------------------------------------------------------------------------------ +void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial *pMaterial, + aiTextureType type) { + const char *szType = TextureTypeToString(type); + + // **************************************************************************** + // Search all keys of the material ... + // textures must be specified with ascending indices + // (e.g. diffuse #2 may not be specified if diffuse #1 is not there ...) + // **************************************************************************** + + int iNumIndices = 0; + int iIndex = -1; + for (unsigned int i = 0; i < pMaterial->mNumProperties; ++i) { + aiMaterialProperty *prop = pMaterial->mProperties[i]; + ai_assert(nullptr != prop); + if (!::strcmp(prop->mKey.data, "$tex.file") && prop->mSemantic == static_cast<unsigned int>(type)) { + iIndex = std::max(iIndex, (int)prop->mIndex); + ++iNumIndices; + + if (aiPTI_String != prop->mType) { + ReportError("Material property %s is expected to be a string", prop->mKey.data); + } + } + } + if (iIndex + 1 != iNumIndices) { + ReportError("%s #%i is set, but there are only %i %s textures", + szType, iIndex, iNumIndices, szType); + } + if (!iNumIndices) { + return; + } + std::vector<aiTextureMapping> mappings(iNumIndices); + + // Now check whether all UV indices are valid ... + bool bNoSpecified = true; + for (unsigned int i = 0; i < pMaterial->mNumProperties; ++i) { + aiMaterialProperty *prop = pMaterial->mProperties[i]; + if (static_cast<aiTextureType>(prop->mSemantic) != type) { + continue; + } + + if ((int)prop->mIndex >= iNumIndices) { + ReportError("Found texture property with index %i, although there " + "are only %i textures of type %s", + prop->mIndex, iNumIndices, szType); + } + + if (!::strcmp(prop->mKey.data, "$tex.mapping")) { + if (aiPTI_Integer != prop->mType || prop->mDataLength < sizeof(aiTextureMapping)) { + ReportError("Material property %s%i is expected to be an integer (size is %i)", + prop->mKey.data, prop->mIndex, prop->mDataLength); + } + mappings[prop->mIndex] = *((aiTextureMapping *)prop->mData); + } else if (!::strcmp(prop->mKey.data, "$tex.uvtrafo")) { + if (aiPTI_Float != prop->mType || prop->mDataLength < sizeof(aiUVTransform)) { + ReportError("Material property %s%i is expected to be 5 floats large (size is %i)", + prop->mKey.data, prop->mIndex, prop->mDataLength); + } + //mappings[prop->mIndex] = ((aiUVTransform*)prop->mData); + } else if (!::strcmp(prop->mKey.data, "$tex.uvwsrc")) { + if (aiPTI_Integer != prop->mType || sizeof(int) > prop->mDataLength) { + ReportError("Material property %s%i is expected to be an integer (size is %i)", + prop->mKey.data, prop->mIndex, prop->mDataLength); + } + bNoSpecified = false; + + // Ignore UV indices for texture channels that are not there ... + + // Get the value + iIndex = *((unsigned int *)prop->mData); + + // Check whether there is a mesh using this material + // which has not enough UV channels ... + for (unsigned int a = 0; a < mScene->mNumMeshes; ++a) { + aiMesh *mesh = this->mScene->mMeshes[a]; + if (mesh->mMaterialIndex == (unsigned int)i) { + int iChannels = 0; + while (mesh->HasTextureCoords(iChannels)) + ++iChannels; + if (iIndex >= iChannels) { + ReportWarning("Invalid UV index: %i (key %s). Mesh %i has only %i UV channels", + iIndex, prop->mKey.data, a, iChannels); + } + } + } + } + } + if (bNoSpecified) { + // Assume that all textures are using the first UV channel + for (unsigned int a = 0; a < mScene->mNumMeshes; ++a) { + aiMesh *mesh = mScene->mMeshes[a]; + if (mesh->mMaterialIndex == (unsigned int)iIndex && mappings[0] == aiTextureMapping_UV) { + if (!mesh->mTextureCoords[0]) { + // This is a special case ... it could be that the + // original mesh format intended the use of a special + // mapping here. + ReportWarning("UV-mapped texture, but there are no UV coords"); + } + } + } + } +} +// ------------------------------------------------------------------------------------------------ +void ValidateDSProcess::Validate(const aiMaterial *pMaterial) { + // check whether there are material keys that are obviously not legal + for (unsigned int i = 0; i < pMaterial->mNumProperties; ++i) { + const aiMaterialProperty *prop = pMaterial->mProperties[i]; + if (!prop) { + ReportError("aiMaterial::mProperties[%i] is nullptr (aiMaterial::mNumProperties is %i)", + i, pMaterial->mNumProperties); + } + if (!prop->mDataLength || !prop->mData) { + ReportError("aiMaterial::mProperties[%i].mDataLength or " + "aiMaterial::mProperties[%i].mData is 0", + i, i); + } + // check all predefined types + if (aiPTI_String == prop->mType) { + // FIX: strings are now stored in a less expensive way, but we can't use the + // validation routine for 'normal' aiStrings + if (prop->mDataLength < 5 || prop->mDataLength < 4 + (*reinterpret_cast<uint32_t *>(prop->mData)) + 1) { + ReportError("aiMaterial::mProperties[%i].mDataLength is " + "too small to contain a string (%i, needed: %i)", + i, prop->mDataLength, static_cast<int>(sizeof(aiString))); + } + if (prop->mData[prop->mDataLength - 1]) { + ReportError("Missing null-terminator in string material property"); + } + // Validate((const aiString*)prop->mData); + } else if (aiPTI_Float == prop->mType) { + if (prop->mDataLength < sizeof(float)) { + ReportError("aiMaterial::mProperties[%i].mDataLength is " + "too small to contain a float (%i, needed: %i)", + i, prop->mDataLength, static_cast<int>(sizeof(float))); + } + } else if (aiPTI_Integer == prop->mType) { + if (prop->mDataLength < sizeof(int)) { + ReportError("aiMaterial::mProperties[%i].mDataLength is " + "too small to contain an integer (%i, needed: %i)", + i, prop->mDataLength, static_cast<int>(sizeof(int))); + } + } + // TODO: check whether there is a key with an unknown name ... + } + + // make some more specific tests + ai_real fTemp; + int iShading; + if (AI_SUCCESS == aiGetMaterialInteger(pMaterial, AI_MATKEY_SHADING_MODEL, &iShading)) { + switch ((aiShadingMode)iShading) { + case aiShadingMode_Blinn: + case aiShadingMode_CookTorrance: + case aiShadingMode_Phong: + + if (AI_SUCCESS != aiGetMaterialFloat(pMaterial, AI_MATKEY_SHININESS, &fTemp)) { + ReportWarning("A specular shading model is specified but there is no " + "AI_MATKEY_SHININESS key"); + } + if (AI_SUCCESS == aiGetMaterialFloat(pMaterial, AI_MATKEY_SHININESS_STRENGTH, &fTemp) && !fTemp) { + ReportWarning("A specular shading model is specified but the value of the " + "AI_MATKEY_SHININESS_STRENGTH key is 0.0"); + } + break; + default: + break; + } + } + + if (AI_SUCCESS == aiGetMaterialFloat(pMaterial, AI_MATKEY_OPACITY, &fTemp) && (!fTemp || fTemp > 1.01)) { + ReportWarning("Invalid opacity value (must be 0 < opacity < 1.0)"); + } + + // Check whether there are invalid texture keys + // TODO: that's a relict of the past, where texture type and index were baked + // into the material string ... we could do that in one single pass. + SearchForInvalidTextures(pMaterial, aiTextureType_DIFFUSE); + SearchForInvalidTextures(pMaterial, aiTextureType_SPECULAR); + SearchForInvalidTextures(pMaterial, aiTextureType_AMBIENT); + SearchForInvalidTextures(pMaterial, aiTextureType_EMISSIVE); + SearchForInvalidTextures(pMaterial, aiTextureType_OPACITY); + SearchForInvalidTextures(pMaterial, aiTextureType_SHININESS); + SearchForInvalidTextures(pMaterial, aiTextureType_HEIGHT); + SearchForInvalidTextures(pMaterial, aiTextureType_NORMALS); + SearchForInvalidTextures(pMaterial, aiTextureType_DISPLACEMENT); + SearchForInvalidTextures(pMaterial, aiTextureType_LIGHTMAP); + SearchForInvalidTextures(pMaterial, aiTextureType_REFLECTION); + SearchForInvalidTextures(pMaterial, aiTextureType_BASE_COLOR); + SearchForInvalidTextures(pMaterial, aiTextureType_NORMAL_CAMERA); + SearchForInvalidTextures(pMaterial, aiTextureType_EMISSION_COLOR); + SearchForInvalidTextures(pMaterial, aiTextureType_METALNESS); + SearchForInvalidTextures(pMaterial, aiTextureType_DIFFUSE_ROUGHNESS); + SearchForInvalidTextures(pMaterial, aiTextureType_AMBIENT_OCCLUSION); +} + +// ------------------------------------------------------------------------------------------------ +void ValidateDSProcess::Validate(const aiTexture *pTexture) { + // the data section may NEVER be nullptr + if (nullptr == pTexture->pcData) { + ReportError("aiTexture::pcData is nullptr"); + } + if (pTexture->mHeight) { + if (!pTexture->mWidth) { + ReportError("aiTexture::mWidth is zero (aiTexture::mHeight is %i, uncompressed texture)", + pTexture->mHeight); + } + } else { + if (!pTexture->mWidth) { + ReportError("aiTexture::mWidth is zero (compressed texture)"); + } + if ('\0' != pTexture->achFormatHint[HINTMAXTEXTURELEN - 1]) { + ReportWarning("aiTexture::achFormatHint must be zero-terminated"); + } else if ('.' == pTexture->achFormatHint[0]) { + ReportWarning("aiTexture::achFormatHint should contain a file extension " + "without a leading dot (format hint: %s).", + pTexture->achFormatHint); + } + } + + const char *sz = pTexture->achFormatHint; + if ((sz[0] >= 'A' && sz[0] <= 'Z') || + (sz[1] >= 'A' && sz[1] <= 'Z') || + (sz[2] >= 'A' && sz[2] <= 'Z') || + (sz[3] >= 'A' && sz[3] <= 'Z')) { + ReportError("aiTexture::achFormatHint contains non-lowercase letters"); + } +} + +// ------------------------------------------------------------------------------------------------ +void ValidateDSProcess::Validate(const aiAnimation *pAnimation, + const aiNodeAnim *pNodeAnim) { + Validate(&pNodeAnim->mNodeName); + + if (!pNodeAnim->mNumPositionKeys && !pNodeAnim->mScalingKeys && !pNodeAnim->mNumRotationKeys) { + ReportError("Empty node animation channel"); + } + // otherwise check whether one of the keys exceeds the total duration of the animation + if (pNodeAnim->mNumPositionKeys) { + if (!pNodeAnim->mPositionKeys) { + ReportError("aiNodeAnim::mPositionKeys is nullptr (aiNodeAnim::mNumPositionKeys is %i)", + pNodeAnim->mNumPositionKeys); + } + double dLast = -10e10; + for (unsigned int i = 0; i < pNodeAnim->mNumPositionKeys; ++i) { + // ScenePreprocessor will compute the duration if still the default value + // (Aramis) Add small epsilon, comparison tended to fail if max_time == duration, + // seems to be due the compilers register usage/width. + if (pAnimation->mDuration > 0. && pNodeAnim->mPositionKeys[i].mTime > pAnimation->mDuration + 0.001) { + ReportError("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is larger " + "than aiAnimation::mDuration (which is %.5f)", + i, + (float)pNodeAnim->mPositionKeys[i].mTime, + (float)pAnimation->mDuration); + } + if (i && pNodeAnim->mPositionKeys[i].mTime <= dLast) { + ReportWarning("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is smaller " + "than aiAnimation::mPositionKeys[%i] (which is %.5f)", + i, + (float)pNodeAnim->mPositionKeys[i].mTime, + i - 1, (float)dLast); + } + dLast = pNodeAnim->mPositionKeys[i].mTime; + } + } + // rotation keys + if (pNodeAnim->mNumRotationKeys) { + if (!pNodeAnim->mRotationKeys) { + ReportError("aiNodeAnim::mRotationKeys is nullptr (aiNodeAnim::mNumRotationKeys is %i)", + pNodeAnim->mNumRotationKeys); + } + double dLast = -10e10; + for (unsigned int i = 0; i < pNodeAnim->mNumRotationKeys; ++i) { + if (pAnimation->mDuration > 0. && pNodeAnim->mRotationKeys[i].mTime > pAnimation->mDuration + 0.001) { + ReportError("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is larger " + "than aiAnimation::mDuration (which is %.5f)", + i, + (float)pNodeAnim->mRotationKeys[i].mTime, + (float)pAnimation->mDuration); + } + if (i && pNodeAnim->mRotationKeys[i].mTime <= dLast) { + ReportWarning("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is smaller " + "than aiAnimation::mRotationKeys[%i] (which is %.5f)", + i, + (float)pNodeAnim->mRotationKeys[i].mTime, + i - 1, (float)dLast); + } + dLast = pNodeAnim->mRotationKeys[i].mTime; + } + } + // scaling keys + if (pNodeAnim->mNumScalingKeys) { + if (!pNodeAnim->mScalingKeys) { + ReportError("aiNodeAnim::mScalingKeys is nullptr (aiNodeAnim::mNumScalingKeys is %i)", + pNodeAnim->mNumScalingKeys); + } + double dLast = -10e10; + for (unsigned int i = 0; i < pNodeAnim->mNumScalingKeys; ++i) { + if (pAnimation->mDuration > 0. && pNodeAnim->mScalingKeys[i].mTime > pAnimation->mDuration + 0.001) { + ReportError("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is larger " + "than aiAnimation::mDuration (which is %.5f)", + i, + (float)pNodeAnim->mScalingKeys[i].mTime, + (float)pAnimation->mDuration); + } + if (i && pNodeAnim->mScalingKeys[i].mTime <= dLast) { + ReportWarning("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is smaller " + "than aiAnimation::mScalingKeys[%i] (which is %.5f)", + i, + (float)pNodeAnim->mScalingKeys[i].mTime, + i - 1, (float)dLast); + } + dLast = pNodeAnim->mScalingKeys[i].mTime; + } + } + + if (!pNodeAnim->mNumScalingKeys && !pNodeAnim->mNumRotationKeys && + !pNodeAnim->mNumPositionKeys) { + ReportError("A node animation channel must have at least one subtrack"); + } +} + +void ValidateDSProcess::Validate(const aiAnimation *pAnimation, + const aiMeshMorphAnim *pMeshMorphAnim) { + Validate(&pMeshMorphAnim->mName); + + if (!pMeshMorphAnim->mNumKeys) { + ReportWarning("Empty mesh morph animation channel"); + return; + } + + // otherwise check whether one of the keys exceeds the total duration of the animation + if (pMeshMorphAnim->mNumKeys) { + if (!pMeshMorphAnim->mKeys) { + ReportError("aiMeshMorphAnim::mKeys is nullptr (aiMeshMorphAnim::mNumKeys is %i)", + pMeshMorphAnim->mNumKeys); + } + double dLast = -10e10; + for (unsigned int i = 0; i < pMeshMorphAnim->mNumKeys; ++i) { + // ScenePreprocessor will compute the duration if still the default value + // (Aramis) Add small epsilon, comparison tended to fail if max_time == duration, + // seems to be due the compilers register usage/width. + if (pAnimation->mDuration > 0. && pMeshMorphAnim->mKeys[i].mTime > pAnimation->mDuration + 0.001) { + ReportError("aiMeshMorphAnim::mKeys[%i].mTime (%.5f) is larger " + "than aiAnimation::mDuration (which is %.5f)", + i, + (float)pMeshMorphAnim->mKeys[i].mTime, + (float)pAnimation->mDuration); + } + if (i && pMeshMorphAnim->mKeys[i].mTime <= dLast) { + ReportWarning("aiMeshMorphAnim::mKeys[%i].mTime (%.5f) is smaller " + "than aiMeshMorphAnim::mKeys[%i] (which is %.5f)", + i, + (float)pMeshMorphAnim->mKeys[i].mTime, + i - 1, (float)dLast); + } + dLast = pMeshMorphAnim->mKeys[i].mTime; + } + } +} + +// ------------------------------------------------------------------------------------------------ +void ValidateDSProcess::Validate(const aiNode *pNode) { + if (!pNode) { + ReportError("A node of the scene-graph is nullptr"); + } + // Validate node name string first so that it's safe to use in below expressions + this->Validate(&pNode->mName); + const char *nodeName = (&pNode->mName)->C_Str(); + if (pNode != mScene->mRootNode && !pNode->mParent) { + ReportError("Non-root node %s lacks a valid parent (aiNode::mParent is nullptr) ", nodeName); + } + + // validate all meshes + if (pNode->mNumMeshes) { + if (!pNode->mMeshes) { + ReportError("aiNode::mMeshes is nullptr for node %s (aiNode::mNumMeshes is %i)", + nodeName, pNode->mNumMeshes); + } + std::vector<bool> abHadMesh; + abHadMesh.resize(mScene->mNumMeshes, false); + for (unsigned int i = 0; i < pNode->mNumMeshes; ++i) { + if (pNode->mMeshes[i] >= mScene->mNumMeshes) { + ReportError("aiNode::mMeshes[%i] is out of range for node %s (maximum is %i)", + pNode->mMeshes[i], nodeName, mScene->mNumMeshes - 1); + } + if (abHadMesh[pNode->mMeshes[i]]) { + ReportError("aiNode::mMeshes[%i] is already referenced by this node %s (value: %i)", + i, nodeName, pNode->mMeshes[i]); + } + abHadMesh[pNode->mMeshes[i]] = true; + } + } + if (pNode->mNumChildren) { + if (!pNode->mChildren) { + ReportError("aiNode::mChildren is nullptr for node %s (aiNode::mNumChildren is %i)", + nodeName, pNode->mNumChildren); + } + for (unsigned int i = 0; i < pNode->mNumChildren; ++i) { + Validate(pNode->mChildren[i]); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void ValidateDSProcess::Validate(const aiString *pString) { + if (pString->length > MAXLEN) { + ReportError("aiString::length is too large (%u, maximum is %lu)", + pString->length, MAXLEN); + } + const char *sz = pString->data; + while (true) { + if ('\0' == *sz) { + if (pString->length != (unsigned int)(sz - pString->data)) { + ReportError("aiString::data is invalid: the terminal zero is at a wrong offset"); + } + break; + } else if (sz >= &pString->data[MAXLEN]) { + ReportError("aiString::data is invalid. There is no terminal character"); + } + ++sz; + } +} diff --git a/libs/assimp/code/PostProcessing/ValidateDataStructure.h b/libs/assimp/code/PostProcessing/ValidateDataStructure.h new file mode 100644 index 0000000..077a47b --- /dev/null +++ b/libs/assimp/code/PostProcessing/ValidateDataStructure.h @@ -0,0 +1,197 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 Defines a (dummy) post processing step to validate the loader's + * output data structure (for debugging) + */ +#ifndef AI_VALIDATEPROCESS_H_INC +#define AI_VALIDATEPROCESS_H_INC + +#include <assimp/types.h> +#include <assimp/material.h> + +#include "Common/BaseProcess.h" + +struct aiBone; +struct aiMesh; +struct aiAnimation; +struct aiNodeAnim; +struct aiMeshMorphAnim; +struct aiTexture; +struct aiMaterial; +struct aiNode; +struct aiString; +struct aiCamera; +struct aiLight; + +namespace Assimp { + +// -------------------------------------------------------------------------------------- +/** Validates the whole ASSIMP scene data structure for correctness. + * ImportErrorException is thrown of the scene is corrupt.*/ +// -------------------------------------------------------------------------------------- +class ValidateDSProcess : public BaseProcess +{ +public: + + ValidateDSProcess(); + ~ValidateDSProcess(); + +public: + // ------------------------------------------------------------------- + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + void Execute( aiScene* pScene); + +protected: + + // ------------------------------------------------------------------- + /** Report a validation error. This will throw an exception, + * control won't return. + * @param msg Format string for sprintf().*/ + AI_WONT_RETURN void ReportError(const char* msg,...) AI_WONT_RETURN_SUFFIX; + + + // ------------------------------------------------------------------- + /** Report a validation warning. This won't throw an exception, + * control will return to the caller. + * @param msg Format string for sprintf().*/ + void ReportWarning(const char* msg,...); + + + // ------------------------------------------------------------------- + /** Validates a mesh + * @param pMesh Input mesh*/ + void Validate( const aiMesh* pMesh); + + // ------------------------------------------------------------------- + /** Validates a bone + * @param pMesh Input mesh + * @param pBone Input bone*/ + void Validate( const aiMesh* pMesh,const aiBone* pBone,float* afSum); + + // ------------------------------------------------------------------- + /** Validates an animation + * @param pAnimation Input animation*/ + void Validate( const aiAnimation* pAnimation); + + // ------------------------------------------------------------------- + /** Validates a material + * @param pMaterial Input material*/ + void Validate( const aiMaterial* pMaterial); + + // ------------------------------------------------------------------- + /** Search the material data structure for invalid or corrupt + * texture keys. + * @param pMaterial Input material + * @param type Type of the texture*/ + void SearchForInvalidTextures(const aiMaterial* pMaterial, + aiTextureType type); + + // ------------------------------------------------------------------- + /** Validates a texture + * @param pTexture Input texture*/ + void Validate( const aiTexture* pTexture); + + // ------------------------------------------------------------------- + /** Validates a light source + * @param pLight Input light + */ + void Validate( const aiLight* pLight); + + // ------------------------------------------------------------------- + /** Validates a camera + * @param pCamera Input camera*/ + void Validate( const aiCamera* pCamera); + + // ------------------------------------------------------------------- + /** Validates a bone animation channel + * @param pAnimation Animation channel. + * @param pBoneAnim Input bone animation */ + void Validate( const aiAnimation* pAnimation, + const aiNodeAnim* pBoneAnim); + + /** Validates a mesh morph animation channel. + * @param pAnimation Input animation. + * @param pMeshMorphAnim Mesh morph animation channel. + * */ + void Validate( const aiAnimation* pAnimation, + const aiMeshMorphAnim* pMeshMorphAnim); + + // ------------------------------------------------------------------- + /** Validates a node and all of its subnodes + * @param Node Input node*/ + void Validate( const aiNode* pNode); + + // ------------------------------------------------------------------- + /** Validates a string + * @param pString Input string*/ + void Validate( const aiString* pString); + +private: + + // template to validate one of the aiScene::mXXX arrays + template <typename T> + inline void DoValidation(T** array, unsigned int size, + const char* firstName, const char* secondName); + + // extended version: checks whether T::mName occurs twice + template <typename T> + inline void DoValidationEx(T** array, unsigned int size, + const char* firstName, const char* secondName); + + // extension to the first template which does also search + // the nodegraph for an item with the same name + template <typename T> + inline void DoValidationWithNameCheck(T** array, unsigned int size, + const char* firstName, const char* secondName); + + aiScene* mScene; +}; + + + + +} // end of namespace Assimp + +#endif // AI_VALIDATEPROCESS_H_INC diff --git a/libs/assimp/code/res/assimp.rc b/libs/assimp/code/res/assimp.rc new file mode 100644 index 0000000..9ae821b --- /dev/null +++ b/libs/assimp/code/res/assimp.rc @@ -0,0 +1,80 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +#include "revision.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#define APSTUDIO_HIDDEN_SYMBOLS +#include "windows.h" +#undef APSTUDIO_HIDDEN_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Deutsch (Deutschland) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DEU) +#ifdef _WIN32 +LANGUAGE LANG_GERMAN, SUBLANG_GERMAN +#pragma code_page(1252) +#endif //_WIN32 + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VER_FILEVERSION + PRODUCTVERSION VER_FILEVERSION + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x7L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040704b0" + BEGIN + VALUE "Comments", "Licensed under a 3-clause BSD license" + VALUE "CompanyName", "assimp team" + VALUE "FileDescription", "Open Asset Import Library" + VALUE "FileVersion", VER_FILEVERSION + VALUE "InternalName", "assimp " + VALUE "LegalCopyright", "Copyright (C) 2006-2020" + VALUE "OriginalFilename", VER_ORIGINAL_FILENAME_STR + VALUE "ProductName", "Open Asset Import Library" + VALUE "ProductVersion", VER_FILEVERSION_STR + ,0 + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x407, 1200 + END +END + +#endif // Deutsch (Deutschland) resources +///////////////////////////////////////////////////////////////////////////// + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/libs/assimp/code/res/resource.h b/libs/assimp/code/res/resource.h new file mode 100644 index 0000000..c28c050 --- /dev/null +++ b/libs/assimp/code/res/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by assimp.rc + +// Next standard values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif |