nuclear@0: /* nuclear@0: --------------------------------------------------------------------------- nuclear@0: Open Asset Import Library (assimp) nuclear@0: --------------------------------------------------------------------------- nuclear@0: nuclear@0: Copyright (c) 2006-2012, assimp team nuclear@0: nuclear@0: All rights reserved. nuclear@0: nuclear@0: Redistribution and use of this software in source and binary forms, nuclear@0: with or without modification, are permitted provided that the following nuclear@0: conditions are met: nuclear@0: nuclear@0: * Redistributions of source code must retain the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer. nuclear@0: nuclear@0: * Redistributions in binary form must reproduce the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer in the documentation and/or other nuclear@0: materials provided with the distribution. nuclear@0: nuclear@0: * Neither the name of the assimp team, nor the names of its nuclear@0: contributors may be used to endorse or promote products nuclear@0: derived from this software without specific prior nuclear@0: written permission of the assimp team. nuclear@0: nuclear@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS nuclear@0: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT nuclear@0: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR nuclear@0: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT nuclear@0: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, nuclear@0: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT nuclear@0: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, nuclear@0: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY nuclear@0: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT nuclear@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE nuclear@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. nuclear@0: --------------------------------------------------------------------------- nuclear@0: */ nuclear@0: nuclear@0: /** @file ValidateDataStructure.cpp nuclear@0: * @brief Implementation of the post processing step to validate nuclear@0: * the data structure returned by Assimp. nuclear@0: */ nuclear@0: nuclear@0: #include "AssimpPCH.h" nuclear@0: nuclear@0: // internal headers nuclear@0: #include "ValidateDataStructure.h" nuclear@0: #include "BaseImporter.h" nuclear@0: #include "fast_atof.h" nuclear@0: #include "ProcessHelper.h" nuclear@0: nuclear@0: // CRT headers nuclear@0: #include nuclear@0: nuclear@0: using namespace Assimp; nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Constructor to be privately used by Importer nuclear@0: ValidateDSProcess::ValidateDSProcess() nuclear@0: {} nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Destructor, private as well nuclear@0: ValidateDSProcess::~ValidateDSProcess() nuclear@0: {} nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Returns whether the processing step is present in the given flag field. nuclear@0: bool ValidateDSProcess::IsActive( unsigned int pFlags) const nuclear@0: { nuclear@0: return (pFlags & aiProcess_ValidateDataStructure) != 0; nuclear@0: } nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: AI_WONT_RETURN void ValidateDSProcess::ReportError(const char* msg,...) nuclear@0: { nuclear@0: ai_assert(NULL != msg); nuclear@0: nuclear@0: va_list args; nuclear@0: va_start(args,msg); nuclear@0: nuclear@0: char szBuffer[3000]; nuclear@0: const int iLen = vsprintf(szBuffer,msg,args); nuclear@0: ai_assert(iLen > 0); nuclear@0: nuclear@0: va_end(args); nuclear@0: #ifdef _DEBUG nuclear@0: ai_assert( false ); nuclear@0: #endif nuclear@0: throw DeadlyImportError("Validation failed: " + std::string(szBuffer,iLen)); nuclear@0: } nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ValidateDSProcess::ReportWarning(const char* msg,...) nuclear@0: { nuclear@0: ai_assert(NULL != msg); nuclear@0: nuclear@0: va_list args; nuclear@0: va_start(args,msg); nuclear@0: nuclear@0: char szBuffer[3000]; nuclear@0: const int iLen = vsprintf(szBuffer,msg,args); nuclear@0: ai_assert(iLen > 0); nuclear@0: nuclear@0: va_end(args); nuclear@0: DefaultLogger::get()->warn("Validation warning: " + std::string(szBuffer,iLen)); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: inline int HasNameMatch(const aiString& in, aiNode* node) nuclear@0: { nuclear@0: int result = (node->mName == in ? 1 : 0 ); nuclear@0: for (unsigned int i = 0; i < node->mNumChildren;++i) { nuclear@0: result += HasNameMatch(in,node->mChildren[i]); nuclear@0: } nuclear@0: return result; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: template nuclear@0: inline void ValidateDSProcess::DoValidation(T** parray, unsigned int size, nuclear@0: const char* firstName, const char* secondName) nuclear@0: { nuclear@0: // validate all entries nuclear@0: if (size) nuclear@0: { nuclear@0: if (!parray) nuclear@0: { nuclear@0: ReportError("aiScene::%s is NULL (aiScene::%s is %i)", nuclear@0: firstName, secondName, size); nuclear@0: } nuclear@0: for (unsigned int i = 0; i < size;++i) nuclear@0: { nuclear@0: if (!parray[i]) nuclear@0: { nuclear@0: ReportError("aiScene::%s[%i] is NULL (aiScene::%s is %i)", nuclear@0: firstName,i,secondName,size); nuclear@0: } nuclear@0: Validate(parray[i]); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: template nuclear@0: inline void ValidateDSProcess::DoValidationEx(T** parray, unsigned int size, nuclear@0: const char* firstName, const char* secondName) nuclear@0: { nuclear@0: // validate all entries nuclear@0: if (size) nuclear@0: { nuclear@0: if (!parray) { nuclear@0: ReportError("aiScene::%s is NULL (aiScene::%s is %i)", nuclear@0: firstName, secondName, size); nuclear@0: } nuclear@0: for (unsigned int i = 0; i < size;++i) nuclear@0: { nuclear@0: if (!parray[i]) nuclear@0: { nuclear@0: ReportError("aiScene::%s[%i] is NULL (aiScene::%s is %i)", nuclear@0: firstName,i,secondName,size); nuclear@0: } nuclear@0: Validate(parray[i]); nuclear@0: nuclear@0: // check whether there are duplicate names nuclear@0: for (unsigned int a = i+1; a < size;++a) nuclear@0: { nuclear@0: if (parray[i]->mName == parray[a]->mName) nuclear@0: { nuclear@0: this->ReportError("aiScene::%s[%i] has the same name as " nuclear@0: "aiScene::%s[%i]",firstName, i,secondName, a); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: template nuclear@0: inline void ValidateDSProcess::DoValidationWithNameCheck(T** array, nuclear@0: unsigned int size, const char* firstName, nuclear@0: const char* secondName) nuclear@0: { nuclear@0: // validate all entries nuclear@0: DoValidationEx(array,size,firstName,secondName); nuclear@0: nuclear@0: for (unsigned int i = 0; i < size;++i) nuclear@0: { nuclear@0: int res = HasNameMatch(array[i]->mName,mScene->mRootNode); nuclear@0: if (!res) { nuclear@0: ReportError("aiScene::%s[%i] has no corresponding node in the scene graph (%s)", nuclear@0: firstName,i,array[i]->mName.data); nuclear@0: } nuclear@0: else if (1 != res) { nuclear@0: ReportError("aiScene::%s[%i]: there are more than one nodes with %s as name", nuclear@0: firstName,i,array[i]->mName.data); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Executes the post processing step on the given imported data. nuclear@0: void ValidateDSProcess::Execute( aiScene* pScene) nuclear@0: { nuclear@0: this->mScene = pScene; nuclear@0: DefaultLogger::get()->debug("ValidateDataStructureProcess begin"); nuclear@0: nuclear@0: // validate the node graph of the scene nuclear@0: Validate(pScene->mRootNode); nuclear@0: nuclear@0: // validate all meshes nuclear@0: if (pScene->mNumMeshes) { nuclear@0: DoValidation(pScene->mMeshes,pScene->mNumMeshes,"mMeshes","mNumMeshes"); nuclear@0: } nuclear@0: else if (!(mScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE)) { nuclear@0: ReportError("aiScene::mNumMeshes is 0. At least one mesh must be there"); nuclear@0: } nuclear@0: else if (pScene->mMeshes) { nuclear@0: ReportError("aiScene::mMeshes is non-null although there are no meshes"); nuclear@0: } nuclear@0: nuclear@0: // validate all animations nuclear@0: if (pScene->mNumAnimations) { nuclear@0: DoValidation(pScene->mAnimations,pScene->mNumAnimations, nuclear@0: "mAnimations","mNumAnimations"); nuclear@0: } nuclear@0: else if (pScene->mAnimations) { nuclear@0: ReportError("aiScene::mAnimations is non-null although there are no animations"); nuclear@0: } nuclear@0: nuclear@0: // validate all cameras nuclear@0: if (pScene->mNumCameras) { nuclear@0: DoValidationWithNameCheck(pScene->mCameras,pScene->mNumCameras, nuclear@0: "mCameras","mNumCameras"); nuclear@0: } nuclear@0: else if (pScene->mCameras) { nuclear@0: ReportError("aiScene::mCameras is non-null although there are no cameras"); nuclear@0: } nuclear@0: nuclear@0: // validate all lights nuclear@0: if (pScene->mNumLights) { nuclear@0: DoValidationWithNameCheck(pScene->mLights,pScene->mNumLights, nuclear@0: "mLights","mNumLights"); nuclear@0: } nuclear@0: else if (pScene->mLights) { nuclear@0: ReportError("aiScene::mLights is non-null although there are no lights"); nuclear@0: } nuclear@0: nuclear@0: // validate all textures nuclear@0: if (pScene->mNumTextures) { nuclear@0: DoValidation(pScene->mTextures,pScene->mNumTextures, nuclear@0: "mTextures","mNumTextures"); nuclear@0: } nuclear@0: else if (pScene->mTextures) { nuclear@0: ReportError("aiScene::mTextures is non-null although there are no textures"); nuclear@0: } nuclear@0: nuclear@0: // validate all materials nuclear@0: if (pScene->mNumMaterials) { nuclear@0: DoValidation(pScene->mMaterials,pScene->mNumMaterials,"mMaterials","mNumMaterials"); nuclear@0: } nuclear@0: #if 0 nuclear@0: // NOTE: ScenePreprocessor generates a default material if none is there nuclear@0: else if (!(mScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE)) { nuclear@0: ReportError("aiScene::mNumMaterials is 0. At least one material must be there"); nuclear@0: } nuclear@0: #endif nuclear@0: else if (pScene->mMaterials) { nuclear@0: ReportError("aiScene::mMaterials is non-null although there are no materials"); nuclear@0: } nuclear@0: nuclear@0: // if (!has)ReportError("The aiScene data structure is empty"); nuclear@0: DefaultLogger::get()->debug("ValidateDataStructureProcess end"); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ValidateDSProcess::Validate( const aiLight* pLight) nuclear@0: { nuclear@0: if (pLight->mType == aiLightSource_UNDEFINED) nuclear@0: ReportWarning("aiLight::mType is aiLightSource_UNDEFINED"); nuclear@0: nuclear@0: if (!pLight->mAttenuationConstant && nuclear@0: !pLight->mAttenuationLinear && nuclear@0: !pLight->mAttenuationQuadratic) { nuclear@0: ReportWarning("aiLight::mAttenuationXXX - all are zero"); nuclear@0: } nuclear@0: nuclear@0: if (pLight->mAngleInnerCone > pLight->mAngleOuterCone) nuclear@0: ReportError("aiLight::mAngleInnerCone is larger than aiLight::mAngleOuterCone"); nuclear@0: nuclear@0: if (pLight->mColorDiffuse.IsBlack() && pLight->mColorAmbient.IsBlack() nuclear@0: && pLight->mColorSpecular.IsBlack()) nuclear@0: { nuclear@0: ReportWarning("aiLight::mColorXXX - all are black and won't have any influence"); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ValidateDSProcess::Validate( const aiCamera* pCamera) nuclear@0: { nuclear@0: if (pCamera->mClipPlaneFar <= pCamera->mClipPlaneNear) nuclear@0: ReportError("aiCamera::mClipPlaneFar must be >= aiCamera::mClipPlaneNear"); nuclear@0: nuclear@0: // FIX: there are many 3ds files with invalid FOVs. No reason to nuclear@0: // reject them at all ... a warning is appropriate. nuclear@0: if (!pCamera->mHorizontalFOV || pCamera->mHorizontalFOV >= (float)AI_MATH_PI) nuclear@0: ReportWarning("%f is not a valid value for aiCamera::mHorizontalFOV",pCamera->mHorizontalFOV); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ValidateDSProcess::Validate( const aiMesh* pMesh) nuclear@0: { nuclear@0: // validate the material index of the mesh nuclear@0: if (mScene->mNumMaterials && pMesh->mMaterialIndex >= mScene->mNumMaterials) nuclear@0: { nuclear@0: ReportError("aiMesh::mMaterialIndex is invalid (value: %i maximum: %i)", nuclear@0: pMesh->mMaterialIndex,mScene->mNumMaterials-1); nuclear@0: } nuclear@0: nuclear@0: Validate(&pMesh->mName); nuclear@0: nuclear@0: for (unsigned int i = 0; i < pMesh->mNumFaces; ++i) nuclear@0: { nuclear@0: aiFace& face = pMesh->mFaces[i]; nuclear@0: nuclear@0: if (pMesh->mPrimitiveTypes) nuclear@0: { nuclear@0: switch (face.mNumIndices) nuclear@0: { nuclear@0: case 0: nuclear@0: ReportError("aiMesh::mFaces[%i].mNumIndices is 0",i); nuclear@0: case 1: nuclear@0: if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_POINT)) nuclear@0: { nuclear@0: ReportError("aiMesh::mFaces[%i] is a POINT but aiMesh::mPrimtiveTypes " nuclear@0: "does not report the POINT flag",i); nuclear@0: } nuclear@0: break; nuclear@0: case 2: nuclear@0: if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_LINE)) nuclear@0: { nuclear@0: ReportError("aiMesh::mFaces[%i] is a LINE but aiMesh::mPrimtiveTypes " nuclear@0: "does not report the LINE flag",i); nuclear@0: } nuclear@0: break; nuclear@0: case 3: nuclear@0: if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE)) nuclear@0: { nuclear@0: ReportError("aiMesh::mFaces[%i] is a TRIANGLE but aiMesh::mPrimtiveTypes " nuclear@0: "does not report the TRIANGLE flag",i); nuclear@0: } nuclear@0: break; nuclear@0: default: nuclear@0: if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_POLYGON)) nuclear@0: { nuclear@0: this->ReportError("aiMesh::mFaces[%i] is a POLYGON but aiMesh::mPrimtiveTypes " nuclear@0: "does not report the POLYGON flag",i); nuclear@0: } nuclear@0: break; nuclear@0: }; nuclear@0: } nuclear@0: nuclear@0: if (!face.mIndices) nuclear@0: ReportError("aiMesh::mFaces[%i].mIndices is NULL",i); nuclear@0: } nuclear@0: nuclear@0: // positions must always be there ... nuclear@0: if (!pMesh->mNumVertices || (!pMesh->mVertices && !mScene->mFlags)) { nuclear@0: ReportError("The mesh contains no vertices"); nuclear@0: } nuclear@0: nuclear@0: if (pMesh->mNumVertices > AI_MAX_VERTICES) { nuclear@0: ReportError("Mesh has too many vertices: %u, but the limit is %u",pMesh->mNumVertices,AI_MAX_VERTICES); nuclear@0: } nuclear@0: if (pMesh->mNumFaces > AI_MAX_FACES) { nuclear@0: ReportError("Mesh has too many faces: %u, but the limit is %u",pMesh->mNumFaces,AI_MAX_FACES); nuclear@0: } nuclear@0: nuclear@0: // if tangents are there there must also be bitangent vectors ... nuclear@0: if ((pMesh->mTangents != NULL) != (pMesh->mBitangents != NULL)) { nuclear@0: ReportError("If there are tangents, bitangent vectors must be present as well"); nuclear@0: } nuclear@0: nuclear@0: // faces, too nuclear@0: if (!pMesh->mNumFaces || (!pMesh->mFaces && !mScene->mFlags)) { nuclear@0: ReportError("Mesh contains no faces"); nuclear@0: } nuclear@0: nuclear@0: // now check whether the face indexing layout is correct: nuclear@0: // unique vertices, pseudo-indexed. nuclear@0: std::vector abRefList; nuclear@0: abRefList.resize(pMesh->mNumVertices,false); nuclear@0: for (unsigned int i = 0; i < pMesh->mNumFaces;++i) nuclear@0: { nuclear@0: aiFace& face = pMesh->mFaces[i]; nuclear@0: if (face.mNumIndices > AI_MAX_FACE_INDICES) { nuclear@0: ReportError("Face %u has too many faces: %u, but the limit is %u",i,face.mNumIndices,AI_MAX_FACE_INDICES); nuclear@0: } nuclear@0: nuclear@0: for (unsigned int a = 0; a < face.mNumIndices;++a) nuclear@0: { nuclear@0: if (face.mIndices[a] >= pMesh->mNumVertices) { nuclear@0: ReportError("aiMesh::mFaces[%i]::mIndices[%i] is out of range",i,a); nuclear@0: } nuclear@0: // the MSB flag is temporarily used by the extra verbose nuclear@0: // mode to tell us that the JoinVerticesProcess might have nuclear@0: // been executed already. nuclear@0: if ( !(this->mScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT ) && abRefList[face.mIndices[a]]) nuclear@0: { nuclear@0: ReportError("aiMesh::mVertices[%i] is referenced twice - second " nuclear@0: "time by aiMesh::mFaces[%i]::mIndices[%i]",face.mIndices[a],i,a); nuclear@0: } nuclear@0: abRefList[face.mIndices[a]] = true; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // check whether there are vertices that aren't referenced by a face nuclear@0: bool b = false; nuclear@0: for (unsigned int i = 0; i < pMesh->mNumVertices;++i) { nuclear@0: if (!abRefList[i])b = true; nuclear@0: } nuclear@0: abRefList.clear(); nuclear@0: if (b)ReportWarning("There are unreferenced vertices"); nuclear@0: nuclear@0: // texture channel 2 may not be set if channel 1 is zero ... nuclear@0: { nuclear@0: unsigned int i = 0; nuclear@0: for (;i < AI_MAX_NUMBER_OF_TEXTURECOORDS;++i) nuclear@0: { nuclear@0: if (!pMesh->HasTextureCoords(i))break; nuclear@0: } nuclear@0: for (;i < AI_MAX_NUMBER_OF_TEXTURECOORDS;++i) nuclear@0: if (pMesh->HasTextureCoords(i)) nuclear@0: { nuclear@0: ReportError("Texture coordinate channel %i exists " nuclear@0: "although the previous channel was NULL.",i); nuclear@0: } nuclear@0: } nuclear@0: // the same for the vertex colors nuclear@0: { nuclear@0: unsigned int i = 0; nuclear@0: for (;i < AI_MAX_NUMBER_OF_COLOR_SETS;++i) nuclear@0: { nuclear@0: if (!pMesh->HasVertexColors(i))break; nuclear@0: } nuclear@0: for (;i < AI_MAX_NUMBER_OF_COLOR_SETS;++i) nuclear@0: if (pMesh->HasVertexColors(i)) nuclear@0: { nuclear@0: ReportError("Vertex color channel %i is exists " nuclear@0: "although the previous channel was NULL.",i); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // now validate all bones nuclear@0: if (pMesh->mNumBones) nuclear@0: { nuclear@0: if (!pMesh->mBones) nuclear@0: { nuclear@0: ReportError("aiMesh::mBones is NULL (aiMesh::mNumBones is %i)", nuclear@0: pMesh->mNumBones); nuclear@0: } nuclear@0: boost::scoped_array afSum(NULL); nuclear@0: if (pMesh->mNumVertices) nuclear@0: { nuclear@0: afSum.reset(new float[pMesh->mNumVertices]); nuclear@0: for (unsigned int i = 0; i < pMesh->mNumVertices;++i) nuclear@0: afSum[i] = 0.0f; nuclear@0: } nuclear@0: nuclear@0: // check whether there are duplicate bone names nuclear@0: for (unsigned int i = 0; i < pMesh->mNumBones;++i) nuclear@0: { nuclear@0: const aiBone* bone = pMesh->mBones[i]; nuclear@0: if (bone->mNumWeights > AI_MAX_BONE_WEIGHTS) { nuclear@0: ReportError("Bone %u has too many weights: %u, but the limit is %u",i,bone->mNumWeights,AI_MAX_BONE_WEIGHTS); nuclear@0: } nuclear@0: nuclear@0: if (!pMesh->mBones[i]) nuclear@0: { nuclear@0: ReportError("aiMesh::mBones[%i] is NULL (aiMesh::mNumBones is %i)", nuclear@0: i,pMesh->mNumBones); nuclear@0: } nuclear@0: Validate(pMesh,pMesh->mBones[i],afSum.get()); nuclear@0: nuclear@0: for (unsigned int a = i+1; a < pMesh->mNumBones;++a) nuclear@0: { nuclear@0: if (pMesh->mBones[i]->mName == pMesh->mBones[a]->mName) nuclear@0: { nuclear@0: ReportError("aiMesh::mBones[%i] has the same name as " nuclear@0: "aiMesh::mBones[%i]",i,a); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: // check whether all bone weights for a vertex sum to 1.0 ... nuclear@0: for (unsigned int i = 0; i < pMesh->mNumVertices;++i) nuclear@0: { nuclear@0: if (afSum[i] && (afSum[i] <= 0.94 || afSum[i] >= 1.05)) { nuclear@0: ReportWarning("aiMesh::mVertices[%i]: bone weight sum != 1.0 (sum is %f)",i,afSum[i]); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: else if (pMesh->mBones) nuclear@0: { nuclear@0: ReportError("aiMesh::mBones is non-null although there are no bones"); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ValidateDSProcess::Validate( const aiMesh* pMesh, nuclear@0: const aiBone* pBone,float* afSum) nuclear@0: { nuclear@0: this->Validate(&pBone->mName); nuclear@0: nuclear@0: if (!pBone->mNumWeights) { nuclear@0: ReportError("aiBone::mNumWeights is zero"); nuclear@0: } nuclear@0: nuclear@0: // check whether all vertices affected by this bone are valid nuclear@0: for (unsigned int i = 0; i < pBone->mNumWeights;++i) nuclear@0: { nuclear@0: if (pBone->mWeights[i].mVertexId >= pMesh->mNumVertices) { nuclear@0: ReportError("aiBone::mWeights[%i].mVertexId is out of range",i); nuclear@0: } nuclear@0: else if (!pBone->mWeights[i].mWeight || pBone->mWeights[i].mWeight > 1.0f) { nuclear@0: ReportWarning("aiBone::mWeights[%i].mWeight has an invalid value",i); nuclear@0: } nuclear@0: afSum[pBone->mWeights[i].mVertexId] += pBone->mWeights[i].mWeight; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ValidateDSProcess::Validate( const aiAnimation* pAnimation) nuclear@0: { nuclear@0: Validate(&pAnimation->mName); nuclear@0: nuclear@0: // validate all materials nuclear@0: if (pAnimation->mNumChannels) nuclear@0: { nuclear@0: if (!pAnimation->mChannels) { nuclear@0: ReportError("aiAnimation::mChannels is NULL (aiAnimation::mNumChannels is %i)", nuclear@0: pAnimation->mNumChannels); nuclear@0: } nuclear@0: for (unsigned int i = 0; i < pAnimation->mNumChannels;++i) nuclear@0: { nuclear@0: if (!pAnimation->mChannels[i]) nuclear@0: { nuclear@0: ReportError("aiAnimation::mChannels[%i] is NULL (aiAnimation::mNumChannels is %i)", nuclear@0: i, pAnimation->mNumChannels); nuclear@0: } nuclear@0: Validate(pAnimation, pAnimation->mChannels[i]); nuclear@0: } nuclear@0: } nuclear@0: else ReportError("aiAnimation::mNumChannels is 0. At least one node animation channel must be there."); nuclear@0: nuclear@0: // Animation duration is allowed to be zero in cases where the anim contains only a single key frame. nuclear@0: // if (!pAnimation->mDuration)this->ReportError("aiAnimation::mDuration is zero"); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial, nuclear@0: aiTextureType type) nuclear@0: { nuclear@0: const char* szType = TextureTypeToString(type); nuclear@0: nuclear@0: // **************************************************************************** nuclear@0: // Search all keys of the material ... nuclear@0: // textures must be specified with ascending indices nuclear@0: // (e.g. diffuse #2 may not be specified if diffuse #1 is not there ...) nuclear@0: // **************************************************************************** nuclear@0: nuclear@0: int iNumIndices = 0; nuclear@0: int iIndex = -1; nuclear@0: for (unsigned int i = 0; i < pMaterial->mNumProperties;++i) nuclear@0: { nuclear@0: aiMaterialProperty* prop = pMaterial->mProperties[i]; nuclear@0: if (!::strcmp(prop->mKey.data,"$tex.file") && prop->mSemantic == type) { nuclear@0: iIndex = std::max(iIndex, (int) prop->mIndex); nuclear@0: ++iNumIndices; nuclear@0: nuclear@0: if (aiPTI_String != prop->mType) nuclear@0: ReportError("Material property %s is expected to be a string",prop->mKey.data); nuclear@0: } nuclear@0: } nuclear@0: if (iIndex +1 != iNumIndices) { nuclear@0: ReportError("%s #%i is set, but there are only %i %s textures", nuclear@0: szType,iIndex,iNumIndices,szType); nuclear@0: } nuclear@0: if (!iNumIndices)return; nuclear@0: std::vector mappings(iNumIndices); nuclear@0: nuclear@0: // Now check whether all UV indices are valid ... nuclear@0: bool bNoSpecified = true; nuclear@0: for (unsigned int i = 0; i < pMaterial->mNumProperties;++i) nuclear@0: { nuclear@0: aiMaterialProperty* prop = pMaterial->mProperties[i]; nuclear@0: if (prop->mSemantic != type)continue; nuclear@0: nuclear@0: if ((int)prop->mIndex >= iNumIndices) nuclear@0: { nuclear@0: ReportError("Found texture property with index %i, although there " nuclear@0: "are only %i textures of type %s", nuclear@0: prop->mIndex, iNumIndices, szType); nuclear@0: } nuclear@0: nuclear@0: if (!::strcmp(prop->mKey.data,"$tex.mapping")) { nuclear@0: if (aiPTI_Integer != prop->mType || prop->mDataLength < sizeof(aiTextureMapping)) nuclear@0: { nuclear@0: ReportError("Material property %s%i is expected to be an integer (size is %i)", nuclear@0: prop->mKey.data,prop->mIndex,prop->mDataLength); nuclear@0: } nuclear@0: mappings[prop->mIndex] = *((aiTextureMapping*)prop->mData); nuclear@0: } nuclear@0: else if (!::strcmp(prop->mKey.data,"$tex.uvtrafo")) { nuclear@0: if (aiPTI_Float != prop->mType || prop->mDataLength < sizeof(aiUVTransform)) nuclear@0: { nuclear@0: ReportError("Material property %s%i is expected to be 5 floats large (size is %i)", nuclear@0: prop->mKey.data,prop->mIndex, prop->mDataLength); nuclear@0: } nuclear@0: mappings[prop->mIndex] = *((aiTextureMapping*)prop->mData); nuclear@0: } nuclear@0: else if (!::strcmp(prop->mKey.data,"$tex.uvwsrc")) { nuclear@0: if (aiPTI_Integer != prop->mType || sizeof(int) > prop->mDataLength) nuclear@0: { nuclear@0: ReportError("Material property %s%i is expected to be an integer (size is %i)", nuclear@0: prop->mKey.data,prop->mIndex,prop->mDataLength); nuclear@0: } nuclear@0: bNoSpecified = false; nuclear@0: nuclear@0: // Ignore UV indices for texture channels that are not there ... nuclear@0: nuclear@0: // Get the value nuclear@0: iIndex = *((unsigned int*)prop->mData); nuclear@0: nuclear@0: // Check whether there is a mesh using this material nuclear@0: // which has not enough UV channels ... nuclear@0: for (unsigned int a = 0; a < mScene->mNumMeshes;++a) nuclear@0: { nuclear@0: aiMesh* mesh = this->mScene->mMeshes[a]; nuclear@0: if(mesh->mMaterialIndex == (unsigned int)i) nuclear@0: { nuclear@0: int iChannels = 0; nuclear@0: while (mesh->HasTextureCoords(iChannels))++iChannels; nuclear@0: if (iIndex >= iChannels) nuclear@0: { nuclear@0: ReportWarning("Invalid UV index: %i (key %s). Mesh %i has only %i UV channels", nuclear@0: iIndex,prop->mKey.data,a,iChannels); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: if (bNoSpecified) nuclear@0: { nuclear@0: // Assume that all textures are using the first UV channel nuclear@0: for (unsigned int a = 0; a < mScene->mNumMeshes;++a) nuclear@0: { nuclear@0: aiMesh* mesh = mScene->mMeshes[a]; nuclear@0: if(mesh->mMaterialIndex == (unsigned int)iIndex && mappings[0] == aiTextureMapping_UV) nuclear@0: { nuclear@0: if (!mesh->mTextureCoords[0]) nuclear@0: { nuclear@0: // This is a special case ... it could be that the nuclear@0: // original mesh format intended the use of a special nuclear@0: // mapping here. nuclear@0: ReportWarning("UV-mapped texture, but there are no UV coords"); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ValidateDSProcess::Validate( const aiMaterial* pMaterial) nuclear@0: { nuclear@0: // check whether there are material keys that are obviously not legal nuclear@0: for (unsigned int i = 0; i < pMaterial->mNumProperties;++i) nuclear@0: { nuclear@0: const aiMaterialProperty* prop = pMaterial->mProperties[i]; nuclear@0: if (!prop) { nuclear@0: ReportError("aiMaterial::mProperties[%i] is NULL (aiMaterial::mNumProperties is %i)", nuclear@0: i,pMaterial->mNumProperties); nuclear@0: } nuclear@0: if (!prop->mDataLength || !prop->mData) { nuclear@0: ReportError("aiMaterial::mProperties[%i].mDataLength or " nuclear@0: "aiMaterial::mProperties[%i].mData is 0",i,i); nuclear@0: } nuclear@0: // check all predefined types nuclear@0: if (aiPTI_String == prop->mType) { nuclear@0: // FIX: strings are now stored in a less expensive way, but we can't use the nuclear@0: // validation routine for 'normal' aiStrings nuclear@0: uint32_t len; nuclear@0: if (prop->mDataLength < 5 || prop->mDataLength < 4 + (len=*reinterpret_cast(prop->mData)) + 1) { nuclear@0: ReportError("aiMaterial::mProperties[%i].mDataLength is " nuclear@0: "too small to contain a string (%i, needed: %i)", nuclear@0: i,prop->mDataLength,sizeof(aiString)); nuclear@0: } nuclear@0: if(prop->mData[prop->mDataLength-1]) { nuclear@0: ReportError("Missing null-terminator in string material property"); nuclear@0: } nuclear@0: // Validate((const aiString*)prop->mData); nuclear@0: } nuclear@0: else if (aiPTI_Float == prop->mType) { nuclear@0: if (prop->mDataLength < sizeof(float)) { nuclear@0: ReportError("aiMaterial::mProperties[%i].mDataLength is " nuclear@0: "too small to contain a float (%i, needed: %i)", nuclear@0: i,prop->mDataLength,sizeof(float)); nuclear@0: } nuclear@0: } nuclear@0: else if (aiPTI_Integer == prop->mType) { nuclear@0: if (prop->mDataLength < sizeof(int)) { nuclear@0: ReportError("aiMaterial::mProperties[%i].mDataLength is " nuclear@0: "too small to contain an integer (%i, needed: %i)", nuclear@0: i,prop->mDataLength,sizeof(int)); nuclear@0: } nuclear@0: } nuclear@0: // TODO: check whether there is a key with an unknown name ... nuclear@0: } nuclear@0: nuclear@0: // make some more specific tests nuclear@0: float fTemp; nuclear@0: int iShading; nuclear@0: if (AI_SUCCESS == aiGetMaterialInteger( pMaterial,AI_MATKEY_SHADING_MODEL,&iShading)) { nuclear@0: switch ((aiShadingMode)iShading) nuclear@0: { nuclear@0: case aiShadingMode_Blinn: nuclear@0: case aiShadingMode_CookTorrance: nuclear@0: case aiShadingMode_Phong: nuclear@0: nuclear@0: if (AI_SUCCESS != aiGetMaterialFloat(pMaterial,AI_MATKEY_SHININESS,&fTemp)) { nuclear@0: ReportWarning("A specular shading model is specified but there is no " nuclear@0: "AI_MATKEY_SHININESS key"); nuclear@0: } nuclear@0: if (AI_SUCCESS == aiGetMaterialFloat(pMaterial,AI_MATKEY_SHININESS_STRENGTH,&fTemp) && !fTemp) { nuclear@0: ReportWarning("A specular shading model is specified but the value of the " nuclear@0: "AI_MATKEY_SHININESS_STRENGTH key is 0.0"); nuclear@0: } nuclear@0: break; nuclear@0: default: ; nuclear@0: }; nuclear@0: } nuclear@0: nuclear@0: if (AI_SUCCESS == aiGetMaterialFloat( pMaterial,AI_MATKEY_OPACITY,&fTemp) && (!fTemp || fTemp > 1.01f)) { nuclear@0: ReportWarning("Invalid opacity value (must be 0 < opacity < 1.0)"); nuclear@0: } nuclear@0: nuclear@0: // Check whether there are invalid texture keys nuclear@0: // TODO: that's a relict of the past, where texture type and index were baked nuclear@0: // into the material string ... we could do that in one single pass. nuclear@0: SearchForInvalidTextures(pMaterial,aiTextureType_DIFFUSE); nuclear@0: SearchForInvalidTextures(pMaterial,aiTextureType_SPECULAR); nuclear@0: SearchForInvalidTextures(pMaterial,aiTextureType_AMBIENT); nuclear@0: SearchForInvalidTextures(pMaterial,aiTextureType_EMISSIVE); nuclear@0: SearchForInvalidTextures(pMaterial,aiTextureType_OPACITY); nuclear@0: SearchForInvalidTextures(pMaterial,aiTextureType_SHININESS); nuclear@0: SearchForInvalidTextures(pMaterial,aiTextureType_HEIGHT); nuclear@0: SearchForInvalidTextures(pMaterial,aiTextureType_NORMALS); nuclear@0: SearchForInvalidTextures(pMaterial,aiTextureType_DISPLACEMENT); nuclear@0: SearchForInvalidTextures(pMaterial,aiTextureType_LIGHTMAP); nuclear@0: SearchForInvalidTextures(pMaterial,aiTextureType_REFLECTION); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ValidateDSProcess::Validate( const aiTexture* pTexture) nuclear@0: { nuclear@0: // the data section may NEVER be NULL nuclear@0: if (!pTexture->pcData) { nuclear@0: ReportError("aiTexture::pcData is NULL"); nuclear@0: } nuclear@0: if (pTexture->mHeight) nuclear@0: { nuclear@0: if (!pTexture->mWidth)ReportError("aiTexture::mWidth is zero " nuclear@0: "(aiTexture::mHeight is %i, uncompressed texture)",pTexture->mHeight); nuclear@0: } nuclear@0: else nuclear@0: { nuclear@0: if (!pTexture->mWidth) { nuclear@0: ReportError("aiTexture::mWidth is zero (compressed texture)"); nuclear@0: } nuclear@0: if ('\0' != pTexture->achFormatHint[3]) { nuclear@0: ReportWarning("aiTexture::achFormatHint must be zero-terminated"); nuclear@0: } nuclear@0: else if ('.' == pTexture->achFormatHint[0]) { nuclear@0: ReportWarning("aiTexture::achFormatHint should contain a file extension " nuclear@0: "without a leading dot (format hint: %s).",pTexture->achFormatHint); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: const char* sz = pTexture->achFormatHint; nuclear@0: if ((sz[0] >= 'A' && sz[0] <= 'Z') || nuclear@0: (sz[1] >= 'A' && sz[1] <= 'Z') || nuclear@0: (sz[2] >= 'A' && sz[2] <= 'Z') || nuclear@0: (sz[3] >= 'A' && sz[3] <= 'Z')) { nuclear@0: ReportError("aiTexture::achFormatHint contains non-lowercase letters"); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ValidateDSProcess::Validate( const aiAnimation* pAnimation, nuclear@0: const aiNodeAnim* pNodeAnim) nuclear@0: { nuclear@0: Validate(&pNodeAnim->mNodeName); nuclear@0: nuclear@0: if (!pNodeAnim->mNumPositionKeys && !pNodeAnim->mScalingKeys && !pNodeAnim->mNumRotationKeys) nuclear@0: ReportError("Empty node animation channel"); nuclear@0: nuclear@0: // otherwise check whether one of the keys exceeds the total duration of the animation nuclear@0: if (pNodeAnim->mNumPositionKeys) nuclear@0: { nuclear@0: if (!pNodeAnim->mPositionKeys) nuclear@0: { nuclear@0: this->ReportError("aiNodeAnim::mPositionKeys is NULL (aiNodeAnim::mNumPositionKeys is %i)", nuclear@0: pNodeAnim->mNumPositionKeys); nuclear@0: } nuclear@0: double dLast = -10e10; nuclear@0: for (unsigned int i = 0; i < pNodeAnim->mNumPositionKeys;++i) nuclear@0: { nuclear@0: // ScenePreprocessor will compute the duration if still the default value nuclear@0: // (Aramis) Add small epsilon, comparison tended to fail if max_time == duration, nuclear@0: // seems to be due the compilers register usage/width. nuclear@0: if (pAnimation->mDuration > 0. && pNodeAnim->mPositionKeys[i].mTime > pAnimation->mDuration+0.001) nuclear@0: { nuclear@0: ReportError("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is larger " nuclear@0: "than aiAnimation::mDuration (which is %.5f)",i, nuclear@0: (float)pNodeAnim->mPositionKeys[i].mTime, nuclear@0: (float)pAnimation->mDuration); nuclear@0: } nuclear@0: if (i && pNodeAnim->mPositionKeys[i].mTime <= dLast) nuclear@0: { nuclear@0: ReportWarning("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is smaller " nuclear@0: "than aiAnimation::mPositionKeys[%i] (which is %.5f)",i, nuclear@0: (float)pNodeAnim->mPositionKeys[i].mTime, nuclear@0: i-1, (float)dLast); nuclear@0: } nuclear@0: dLast = pNodeAnim->mPositionKeys[i].mTime; nuclear@0: } nuclear@0: } nuclear@0: // rotation keys nuclear@0: if (pNodeAnim->mNumRotationKeys) nuclear@0: { nuclear@0: if (!pNodeAnim->mRotationKeys) nuclear@0: { nuclear@0: this->ReportError("aiNodeAnim::mRotationKeys is NULL (aiNodeAnim::mNumRotationKeys is %i)", nuclear@0: pNodeAnim->mNumRotationKeys); nuclear@0: } nuclear@0: double dLast = -10e10; nuclear@0: for (unsigned int i = 0; i < pNodeAnim->mNumRotationKeys;++i) nuclear@0: { nuclear@0: if (pAnimation->mDuration > 0. && pNodeAnim->mRotationKeys[i].mTime > pAnimation->mDuration+0.001) nuclear@0: { nuclear@0: ReportError("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is larger " nuclear@0: "than aiAnimation::mDuration (which is %.5f)",i, nuclear@0: (float)pNodeAnim->mRotationKeys[i].mTime, nuclear@0: (float)pAnimation->mDuration); nuclear@0: } nuclear@0: if (i && pNodeAnim->mRotationKeys[i].mTime <= dLast) nuclear@0: { nuclear@0: ReportWarning("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is smaller " nuclear@0: "than aiAnimation::mRotationKeys[%i] (which is %.5f)",i, nuclear@0: (float)pNodeAnim->mRotationKeys[i].mTime, nuclear@0: i-1, (float)dLast); nuclear@0: } nuclear@0: dLast = pNodeAnim->mRotationKeys[i].mTime; nuclear@0: } nuclear@0: } nuclear@0: // scaling keys nuclear@0: if (pNodeAnim->mNumScalingKeys) nuclear@0: { nuclear@0: if (!pNodeAnim->mScalingKeys) { nuclear@0: ReportError("aiNodeAnim::mScalingKeys is NULL (aiNodeAnim::mNumScalingKeys is %i)", nuclear@0: pNodeAnim->mNumScalingKeys); nuclear@0: } nuclear@0: double dLast = -10e10; nuclear@0: for (unsigned int i = 0; i < pNodeAnim->mNumScalingKeys;++i) nuclear@0: { nuclear@0: if (pAnimation->mDuration > 0. && pNodeAnim->mScalingKeys[i].mTime > pAnimation->mDuration+0.001) nuclear@0: { nuclear@0: ReportError("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is larger " nuclear@0: "than aiAnimation::mDuration (which is %.5f)",i, nuclear@0: (float)pNodeAnim->mScalingKeys[i].mTime, nuclear@0: (float)pAnimation->mDuration); nuclear@0: } nuclear@0: if (i && pNodeAnim->mScalingKeys[i].mTime <= dLast) nuclear@0: { nuclear@0: ReportWarning("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is smaller " nuclear@0: "than aiAnimation::mScalingKeys[%i] (which is %.5f)",i, nuclear@0: (float)pNodeAnim->mScalingKeys[i].mTime, nuclear@0: i-1, (float)dLast); nuclear@0: } nuclear@0: dLast = pNodeAnim->mScalingKeys[i].mTime; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if (!pNodeAnim->mNumScalingKeys && !pNodeAnim->mNumRotationKeys && nuclear@0: !pNodeAnim->mNumPositionKeys) nuclear@0: { nuclear@0: ReportError("A node animation channel must have at least one subtrack"); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ValidateDSProcess::Validate( const aiNode* pNode) nuclear@0: { nuclear@0: if (!pNode)ReportError("A node of the scenegraph is NULL"); nuclear@0: if (pNode != mScene->mRootNode && !pNode->mParent) nuclear@0: this->ReportError("A node has no valid parent (aiNode::mParent is NULL)"); nuclear@0: nuclear@0: this->Validate(&pNode->mName); nuclear@0: nuclear@0: // validate all meshes nuclear@0: if (pNode->mNumMeshes) nuclear@0: { nuclear@0: if (!pNode->mMeshes) nuclear@0: { nuclear@0: ReportError("aiNode::mMeshes is NULL (aiNode::mNumMeshes is %i)", nuclear@0: pNode->mNumMeshes); nuclear@0: } nuclear@0: std::vector abHadMesh; nuclear@0: abHadMesh.resize(mScene->mNumMeshes,false); nuclear@0: for (unsigned int i = 0; i < pNode->mNumMeshes;++i) nuclear@0: { nuclear@0: if (pNode->mMeshes[i] >= mScene->mNumMeshes) nuclear@0: { nuclear@0: ReportError("aiNode::mMeshes[%i] is out of range (maximum is %i)", nuclear@0: pNode->mMeshes[i],mScene->mNumMeshes-1); nuclear@0: } nuclear@0: if (abHadMesh[pNode->mMeshes[i]]) nuclear@0: { nuclear@0: ReportError("aiNode::mMeshes[%i] is already referenced by this node (value: %i)", nuclear@0: i,pNode->mMeshes[i]); nuclear@0: } nuclear@0: abHadMesh[pNode->mMeshes[i]] = true; nuclear@0: } nuclear@0: } nuclear@0: if (pNode->mNumChildren) nuclear@0: { nuclear@0: if (!pNode->mChildren) { nuclear@0: ReportError("aiNode::mChildren is NULL (aiNode::mNumChildren is %i)", nuclear@0: pNode->mNumChildren); nuclear@0: } nuclear@0: for (unsigned int i = 0; i < pNode->mNumChildren;++i) { nuclear@0: Validate(pNode->mChildren[i]); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ValidateDSProcess::Validate( const aiString* pString) nuclear@0: { nuclear@0: if (pString->length > MAXLEN) nuclear@0: { nuclear@0: this->ReportError("aiString::length is too large (%i, maximum is %i)", nuclear@0: pString->length,MAXLEN); nuclear@0: } nuclear@0: const char* sz = pString->data; nuclear@0: while (true) nuclear@0: { nuclear@0: if ('\0' == *sz) nuclear@0: { nuclear@0: if (pString->length != (unsigned int)(sz-pString->data)) nuclear@0: ReportError("aiString::data is invalid: the terminal zero is at a wrong offset"); nuclear@0: break; nuclear@0: } nuclear@0: else if (sz >= &pString->data[MAXLEN]) nuclear@0: ReportError("aiString::data is invalid. There is no terminal character"); nuclear@0: ++sz; nuclear@0: } nuclear@0: }