123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942 |
- /*
- ---------------------------------------------------------------------------
- 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 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;
- }
- }
|