/src/assimp/code/PostProcessing/ValidateDataStructure.cpp
Line | Count | Source |
1 | | /* |
2 | | --------------------------------------------------------------------------- |
3 | | Open Asset Import Library (assimp) |
4 | | --------------------------------------------------------------------------- |
5 | | |
6 | | Copyright (c) 2006-2025, assimp team |
7 | | |
8 | | All rights reserved. |
9 | | |
10 | | Redistribution and use of this software in source and binary forms, |
11 | | with or without modification, are permitted provided that the following |
12 | | conditions are met: |
13 | | |
14 | | * Redistributions of source code must retain the above |
15 | | copyright notice, this list of conditions and the |
16 | | following disclaimer. |
17 | | |
18 | | * Redistributions in binary form must reproduce the above |
19 | | copyright notice, this list of conditions and the |
20 | | following disclaimer in the documentation and/or other |
21 | | materials provided with the distribution. |
22 | | |
23 | | * Neither the name of the assimp team, nor the names of its |
24 | | contributors may be used to endorse or promote products |
25 | | derived from this software without specific prior |
26 | | written permission of the assimp team. |
27 | | |
28 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
29 | | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
30 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
31 | | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
32 | | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
33 | | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
34 | | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
35 | | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
36 | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
37 | | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
38 | | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
39 | | --------------------------------------------------------------------------- |
40 | | */ |
41 | | |
42 | | /** @file ValidateDataStructure.cpp |
43 | | * @brief Implementation of the post processing step to validate |
44 | | * the data structure returned by Assimp. |
45 | | */ |
46 | | |
47 | | // internal headers |
48 | | #include "ValidateDataStructure.h" |
49 | | #include "ProcessHelper.h" |
50 | | #include <assimp/BaseImporter.h> |
51 | | #include <assimp/fast_atof.h> |
52 | | #include <memory> |
53 | | |
54 | | // CRT headers |
55 | | #include <stdarg.h> |
56 | | |
57 | | using namespace Assimp; |
58 | | |
59 | | // ------------------------------------------------------------------------------------------------ |
60 | | // Constructor to be privately used by Importer |
61 | 130 | ValidateDSProcess::ValidateDSProcess() : mScene(nullptr) {} |
62 | | |
63 | | // ------------------------------------------------------------------------------------------------ |
64 | | // Returns whether the processing step is present in the given flag field. |
65 | 0 | bool ValidateDSProcess::IsActive(unsigned int pFlags) const { |
66 | 0 | return (pFlags & aiProcess_ValidateDataStructure) != 0; |
67 | 0 | } |
68 | | // ------------------------------------------------------------------------------------------------ |
69 | 61 | AI_WONT_RETURN void ValidateDSProcess::ReportError(const char *msg, ...) { |
70 | 61 | ai_assert(nullptr != msg); |
71 | | |
72 | 61 | va_list args; |
73 | 61 | va_start(args, msg); |
74 | | |
75 | 61 | char szBuffer[3000]; |
76 | 61 | const int iLen = vsnprintf(szBuffer, sizeof(szBuffer), msg, args); |
77 | 61 | ai_assert(iLen > 0); |
78 | | |
79 | 61 | va_end(args); |
80 | | |
81 | 61 | throw DeadlyImportError("Validation failed: ", std::string(szBuffer, iLen)); |
82 | 61 | } |
83 | | // ------------------------------------------------------------------------------------------------ |
84 | 204 | void ValidateDSProcess::ReportWarning(const char *msg, ...) { |
85 | 204 | ai_assert(nullptr != msg); |
86 | | |
87 | 204 | va_list args; |
88 | 204 | va_start(args, msg); |
89 | | |
90 | 204 | char szBuffer[3000]; |
91 | 204 | const int iLen = vsnprintf(szBuffer, sizeof(szBuffer), msg, args); |
92 | 204 | ai_assert(iLen > 0); |
93 | | |
94 | 204 | va_end(args); |
95 | 204 | ASSIMP_LOG_WARN("Validation warning: ", std::string(szBuffer, iLen)); |
96 | 204 | } |
97 | | |
98 | | // ------------------------------------------------------------------------------------------------ |
99 | 11.5M | inline int HasNameMatch(const aiString &in, aiNode *node) { |
100 | 11.5M | int result = (node->mName == in ? 1 : 0); |
101 | 23.1M | for (unsigned int i = 0; i < node->mNumChildren; ++i) { |
102 | 11.5M | result += HasNameMatch(in, node->mChildren[i]); |
103 | 11.5M | } |
104 | 11.5M | return result; |
105 | 11.5M | } |
106 | | |
107 | | // ------------------------------------------------------------------------------------------------ |
108 | | template <typename T> |
109 | 140 | inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) { |
110 | | // validate all entries |
111 | 140 | if (size == 0) { |
112 | 0 | return; |
113 | 0 | } |
114 | | |
115 | 140 | if (!parray) { |
116 | 0 | ReportError("aiScene::%s is nullptr (aiScene::%s is %i)", |
117 | 0 | firstName, secondName, size); |
118 | 0 | } |
119 | | |
120 | 2.34k | for (unsigned int i = 0; i < size; ++i) { |
121 | 2.20k | if (!parray[i]) { |
122 | 0 | ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)", |
123 | 0 | firstName, i, secondName, size); |
124 | 0 | } |
125 | 2.20k | Validate(parray[i]); |
126 | 2.20k | } |
127 | 140 | } void Assimp::ValidateDSProcess::DoValidation<aiMesh>(aiMesh**, unsigned int, char const*, char const*) Line | Count | Source | 109 | 73 | inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) { | 110 | | // validate all entries | 111 | 73 | if (size == 0) { | 112 | 0 | return; | 113 | 0 | } | 114 | | | 115 | 73 | if (!parray) { | 116 | 0 | ReportError("aiScene::%s is nullptr (aiScene::%s is %i)", | 117 | 0 | firstName, secondName, size); | 118 | 0 | } | 119 | | | 120 | 1.82k | for (unsigned int i = 0; i < size; ++i) { | 121 | 1.75k | if (!parray[i]) { | 122 | 0 | ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)", | 123 | 0 | firstName, i, secondName, size); | 124 | 0 | } | 125 | 1.75k | Validate(parray[i]); | 126 | 1.75k | } | 127 | 73 | } |
void Assimp::ValidateDSProcess::DoValidation<aiAnimation>(aiAnimation**, unsigned int, char const*, char const*) Line | Count | Source | 109 | 11 | inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) { | 110 | | // validate all entries | 111 | 11 | if (size == 0) { | 112 | 0 | return; | 113 | 0 | } | 114 | | | 115 | 11 | if (!parray) { | 116 | 0 | ReportError("aiScene::%s is nullptr (aiScene::%s is %i)", | 117 | 0 | firstName, secondName, size); | 118 | 0 | } | 119 | | | 120 | 22 | for (unsigned int i = 0; i < size; ++i) { | 121 | 11 | if (!parray[i]) { | 122 | 0 | ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)", | 123 | 0 | firstName, i, secondName, size); | 124 | 0 | } | 125 | 11 | Validate(parray[i]); | 126 | 11 | } | 127 | 11 | } |
Unexecuted instantiation: void Assimp::ValidateDSProcess::DoValidation<aiTexture>(aiTexture**, unsigned int, char const*, char const*) void Assimp::ValidateDSProcess::DoValidation<aiMaterial>(aiMaterial**, unsigned int, char const*, char const*) Line | Count | Source | 109 | 56 | inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) { | 110 | | // validate all entries | 111 | 56 | if (size == 0) { | 112 | 0 | return; | 113 | 0 | } | 114 | | | 115 | 56 | if (!parray) { | 116 | 0 | ReportError("aiScene::%s is nullptr (aiScene::%s is %i)", | 117 | 0 | firstName, secondName, size); | 118 | 0 | } | 119 | | | 120 | 495 | for (unsigned int i = 0; i < size; ++i) { | 121 | 439 | if (!parray[i]) { | 122 | 0 | ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)", | 123 | 0 | firstName, i, secondName, size); | 124 | 0 | } | 125 | 439 | Validate(parray[i]); | 126 | 439 | } | 127 | 56 | } |
|
128 | | |
129 | | // ------------------------------------------------------------------------------------------------ |
130 | | template <typename T> |
131 | | inline void ValidateDSProcess::DoValidationEx(T **parray, unsigned int size, |
132 | 5 | const char *firstName, const char *secondName) { |
133 | | // validate all entries |
134 | 5 | if (size == 0) { |
135 | 0 | return; |
136 | 0 | } |
137 | | |
138 | 5 | if (!parray) { |
139 | 0 | ReportError("aiScene::%s is nullptr (aiScene::%s is %i)", |
140 | 0 | firstName, secondName, size); |
141 | 0 | } |
142 | 2.78k | for (unsigned int i = 0; i < size; ++i) { |
143 | 2.77k | if (!parray[i]) { |
144 | 0 | ReportError("aiScene::%s[%u] is nullptr (aiScene::%s is %u)", |
145 | 0 | firstName, i, secondName, size); |
146 | 0 | } |
147 | 2.77k | Validate(parray[i]); |
148 | | |
149 | | // check whether there are duplicate names |
150 | 3.74M | for (unsigned int a = i + 1; a < size; ++a) { |
151 | 3.74M | if (parray[i]->mName == parray[a]->mName) { |
152 | 1 | ReportError("aiScene::%s[%u] has the same name as " |
153 | 1 | "aiScene::%s[%u]", |
154 | 1 | firstName, i, secondName, a); |
155 | 1 | } |
156 | 3.74M | } |
157 | 2.77k | } |
158 | 5 | } void Assimp::ValidateDSProcess::DoValidationEx<aiCamera>(aiCamera**, unsigned int, char const*, char const*) Line | Count | Source | 132 | 4 | const char *firstName, const char *secondName) { | 133 | | // validate all entries | 134 | 4 | if (size == 0) { | 135 | 0 | return; | 136 | 0 | } | 137 | | | 138 | 4 | if (!parray) { | 139 | 0 | ReportError("aiScene::%s is nullptr (aiScene::%s is %i)", | 140 | 0 | firstName, secondName, size); | 141 | 0 | } | 142 | 47 | for (unsigned int i = 0; i < size; ++i) { | 143 | 43 | if (!parray[i]) { | 144 | 0 | ReportError("aiScene::%s[%u] is nullptr (aiScene::%s is %u)", | 145 | 0 | firstName, i, secondName, size); | 146 | 0 | } | 147 | 43 | Validate(parray[i]); | 148 | | | 149 | | // check whether there are duplicate names | 150 | 656 | for (unsigned int a = i + 1; a < size; ++a) { | 151 | 613 | if (parray[i]->mName == parray[a]->mName) { | 152 | 1 | ReportError("aiScene::%s[%u] has the same name as " | 153 | 1 | "aiScene::%s[%u]", | 154 | 1 | firstName, i, secondName, a); | 155 | 1 | } | 156 | 613 | } | 157 | 43 | } | 158 | 4 | } |
void Assimp::ValidateDSProcess::DoValidationEx<aiLight>(aiLight**, unsigned int, char const*, char const*) Line | Count | Source | 132 | 1 | const char *firstName, const char *secondName) { | 133 | | // validate all entries | 134 | 1 | if (size == 0) { | 135 | 0 | return; | 136 | 0 | } | 137 | | | 138 | 1 | if (!parray) { | 139 | 0 | ReportError("aiScene::%s is nullptr (aiScene::%s is %i)", | 140 | 0 | firstName, secondName, size); | 141 | 0 | } | 142 | 2.73k | for (unsigned int i = 0; i < size; ++i) { | 143 | 2.73k | if (!parray[i]) { | 144 | 0 | ReportError("aiScene::%s[%u] is nullptr (aiScene::%s is %u)", | 145 | 0 | firstName, i, secondName, size); | 146 | 0 | } | 147 | 2.73k | Validate(parray[i]); | 148 | | | 149 | | // check whether there are duplicate names | 150 | 3.74M | for (unsigned int a = i + 1; a < size; ++a) { | 151 | 3.74M | if (parray[i]->mName == parray[a]->mName) { | 152 | 0 | ReportError("aiScene::%s[%u] has the same name as " | 153 | 0 | "aiScene::%s[%u]", | 154 | 0 | firstName, i, secondName, a); | 155 | 0 | } | 156 | 3.74M | } | 157 | 2.73k | } | 158 | 1 | } |
|
159 | | |
160 | | // ------------------------------------------------------------------------------------------------ |
161 | | template <typename T> |
162 | | inline void ValidateDSProcess::DoValidationWithNameCheck(T **array, unsigned int size, const char *firstName, |
163 | 5 | const char *secondName) { |
164 | | // validate all entries |
165 | 5 | DoValidationEx(array, size, firstName, secondName); |
166 | | |
167 | 2.78k | for (unsigned int i = 0; i < size; ++i) { |
168 | 2.77k | int res = HasNameMatch(array[i]->mName, mScene->mRootNode); |
169 | 2.77k | if (0 == res) { |
170 | 0 | const std::string name = static_cast<char *>(array[i]->mName.data); |
171 | 0 | ReportError("aiScene::%s[%i] has no corresponding node in the scene graph (%s)", |
172 | 0 | firstName, i, name.c_str()); |
173 | 2.77k | } else if (1 != res) { |
174 | 0 | const std::string name = static_cast<char *>(array[i]->mName.data); |
175 | 0 | ReportError("aiScene::%s[%i]: there are more than one nodes with %s as name", |
176 | 0 | firstName, i, name.c_str()); |
177 | 0 | } |
178 | 2.77k | } |
179 | 5 | } void Assimp::ValidateDSProcess::DoValidationWithNameCheck<aiCamera>(aiCamera**, unsigned int, char const*, char const*) Line | Count | Source | 163 | 4 | const char *secondName) { | 164 | | // validate all entries | 165 | 4 | DoValidationEx(array, size, firstName, secondName); | 166 | | | 167 | 46 | for (unsigned int i = 0; i < size; ++i) { | 168 | 42 | int res = HasNameMatch(array[i]->mName, mScene->mRootNode); | 169 | 42 | if (0 == res) { | 170 | 0 | const std::string name = static_cast<char *>(array[i]->mName.data); | 171 | 0 | ReportError("aiScene::%s[%i] has no corresponding node in the scene graph (%s)", | 172 | 0 | firstName, i, name.c_str()); | 173 | 42 | } else if (1 != res) { | 174 | 0 | const std::string name = static_cast<char *>(array[i]->mName.data); | 175 | 0 | ReportError("aiScene::%s[%i]: there are more than one nodes with %s as name", | 176 | 0 | firstName, i, name.c_str()); | 177 | 0 | } | 178 | 42 | } | 179 | 4 | } |
void Assimp::ValidateDSProcess::DoValidationWithNameCheck<aiLight>(aiLight**, unsigned int, char const*, char const*) Line | Count | Source | 163 | 1 | const char *secondName) { | 164 | | // validate all entries | 165 | 1 | DoValidationEx(array, size, firstName, secondName); | 166 | | | 167 | 2.73k | for (unsigned int i = 0; i < size; ++i) { | 168 | 2.73k | int res = HasNameMatch(array[i]->mName, mScene->mRootNode); | 169 | 2.73k | if (0 == res) { | 170 | 0 | const std::string name = static_cast<char *>(array[i]->mName.data); | 171 | 0 | ReportError("aiScene::%s[%i] has no corresponding node in the scene graph (%s)", | 172 | 0 | firstName, i, name.c_str()); | 173 | 2.73k | } else if (1 != res) { | 174 | 0 | const std::string name = static_cast<char *>(array[i]->mName.data); | 175 | 0 | ReportError("aiScene::%s[%i]: there are more than one nodes with %s as name", | 176 | 0 | firstName, i, name.c_str()); | 177 | 0 | } | 178 | 2.73k | } | 179 | 1 | } |
|
180 | | |
181 | | // ------------------------------------------------------------------------------------------------ |
182 | | // Executes the post processing step on the given imported data. |
183 | 130 | void ValidateDSProcess::Execute(aiScene *pScene) { |
184 | 130 | mScene = pScene; |
185 | 130 | ASSIMP_LOG_DEBUG("ValidateDataStructureProcess begin"); |
186 | | |
187 | | // validate the node graph of the scene |
188 | 130 | Validate(pScene->mRootNode); |
189 | | |
190 | | // validate all meshes |
191 | 130 | if (pScene->mNumMeshes) { |
192 | 73 | DoValidation(pScene->mMeshes, pScene->mNumMeshes, "mMeshes", "mNumMeshes"); |
193 | 73 | } else if (!(mScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE)) { |
194 | 28 | ReportError("aiScene::mNumMeshes is 0. At least one mesh must be there"); |
195 | 29 | } else if (pScene->mMeshes) { |
196 | 0 | ReportError("aiScene::mMeshes is non-null although there are no meshes"); |
197 | 0 | } |
198 | | |
199 | | // validate all animations |
200 | 130 | if (pScene->mNumAnimations) { |
201 | 11 | DoValidation(pScene->mAnimations, pScene->mNumAnimations, |
202 | 11 | "mAnimations", "mNumAnimations"); |
203 | 119 | } else if (pScene->mAnimations) { |
204 | 0 | ReportError("aiScene::mAnimations is non-null although there are no animations"); |
205 | 0 | } |
206 | | |
207 | | // validate all cameras |
208 | 130 | if (pScene->mNumCameras) { |
209 | 4 | DoValidationWithNameCheck(pScene->mCameras, pScene->mNumCameras, |
210 | 4 | "mCameras", "mNumCameras"); |
211 | 126 | } else if (pScene->mCameras) { |
212 | 0 | ReportError("aiScene::mCameras is non-null although there are no cameras"); |
213 | 0 | } |
214 | | |
215 | | // validate all lights |
216 | 130 | if (pScene->mNumLights) { |
217 | 1 | DoValidationWithNameCheck(pScene->mLights, pScene->mNumLights, |
218 | 1 | "mLights", "mNumLights"); |
219 | 129 | } else if (pScene->mLights) { |
220 | 0 | ReportError("aiScene::mLights is non-null although there are no lights"); |
221 | 0 | } |
222 | | |
223 | | // validate all textures |
224 | 130 | if (pScene->mNumTextures) { |
225 | 0 | DoValidation(pScene->mTextures, pScene->mNumTextures, |
226 | 0 | "mTextures", "mNumTextures"); |
227 | 130 | } else if (pScene->mTextures) { |
228 | 0 | ReportError("aiScene::mTextures is non-null although there are no textures"); |
229 | 0 | } |
230 | | |
231 | | // validate all materials |
232 | 130 | if (pScene->mNumMaterials) { |
233 | 56 | DoValidation(pScene->mMaterials, pScene->mNumMaterials, "mMaterials", "mNumMaterials"); |
234 | 56 | } |
235 | 74 | else if (pScene->mMaterials) { |
236 | 0 | ReportError("aiScene::mMaterials is non-null although there are no materials"); |
237 | 0 | } |
238 | | |
239 | | // if (!has)ReportError("The aiScene data structure is empty"); |
240 | 130 | ASSIMP_LOG_DEBUG("ValidateDataStructureProcess end"); |
241 | 130 | } |
242 | | |
243 | | // ------------------------------------------------------------------------------------------------ |
244 | 2.73k | void ValidateDSProcess::Validate(const aiLight *pLight) { |
245 | 2.73k | if (pLight->mType == aiLightSource_UNDEFINED) |
246 | 0 | ReportWarning("aiLight::mType is aiLightSource_UNDEFINED"); |
247 | | |
248 | 2.73k | if (!pLight->mAttenuationConstant && |
249 | 2.73k | !pLight->mAttenuationLinear && |
250 | 0 | !pLight->mAttenuationQuadratic) { |
251 | 0 | ReportWarning("aiLight::mAttenuationXXX - all are zero"); |
252 | 0 | } |
253 | | |
254 | 2.73k | if (pLight->mAngleInnerCone > pLight->mAngleOuterCone) |
255 | 0 | ReportError("aiLight::mAngleInnerCone is larger than aiLight::mAngleOuterCone"); |
256 | | |
257 | 2.73k | if (pLight->mColorDiffuse.IsBlack() && pLight->mColorAmbient.IsBlack() && pLight->mColorSpecular.IsBlack()) { |
258 | 144 | ReportWarning("aiLight::mColorXXX - all are black and won't have any influence"); |
259 | 144 | } |
260 | 2.73k | } |
261 | | |
262 | | // ------------------------------------------------------------------------------------------------ |
263 | 43 | void ValidateDSProcess::Validate(const aiCamera *pCamera) { |
264 | 43 | if (pCamera->mClipPlaneFar <= pCamera->mClipPlaneNear) |
265 | 0 | ReportError("aiCamera::mClipPlaneFar must be >= aiCamera::mClipPlaneNear"); |
266 | | |
267 | | // There are many 3ds files with invalid FOVs. No reason to reject them at all ... a warning is appropriate. |
268 | 43 | if (!pCamera->mHorizontalFOV || pCamera->mHorizontalFOV >= (float)AI_MATH_PI) |
269 | 0 | ReportWarning("%f is not a valid value for aiCamera::mHorizontalFOV", pCamera->mHorizontalFOV); |
270 | 43 | } |
271 | | |
272 | | // ------------------------------------------------------------------------------------------------ |
273 | 1.75k | void ValidateDSProcess::Validate(const aiMesh *pMesh) { |
274 | | // validate the material index of the mesh |
275 | 1.75k | if (mScene->mNumMaterials && pMesh->mMaterialIndex >= mScene->mNumMaterials) { |
276 | 0 | ReportError("aiMesh::mMaterialIndex is invalid (value: %i maximum: %i)", |
277 | 0 | pMesh->mMaterialIndex, mScene->mNumMaterials - 1); |
278 | 0 | } |
279 | | |
280 | 1.75k | Validate(&pMesh->mName); |
281 | | |
282 | 57.5k | for (unsigned int i = 0; i < pMesh->mNumFaces; ++i) { |
283 | 55.7k | aiFace &face = pMesh->mFaces[i]; |
284 | | |
285 | 55.7k | if (pMesh->mPrimitiveTypes) { |
286 | 29.0k | switch (face.mNumIndices) { |
287 | 3 | case 0: |
288 | 3 | ReportError("aiMesh::mFaces[%i].mNumIndices is 0", i); |
289 | 250 | case 1: |
290 | 250 | if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_POINT)) { |
291 | 5 | ReportError("aiMesh::mFaces[%i] is a POINT but aiMesh::mPrimitiveTypes " |
292 | 5 | "does not report the POINT flag", |
293 | 5 | i); |
294 | 5 | } |
295 | 250 | break; |
296 | 1.10k | case 2: |
297 | 1.10k | if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_LINE)) { |
298 | 0 | ReportError("aiMesh::mFaces[%i] is a LINE but aiMesh::mPrimitiveTypes " |
299 | 0 | "does not report the LINE flag", |
300 | 0 | i); |
301 | 0 | } |
302 | 1.10k | break; |
303 | 18.3k | case 3: |
304 | 18.3k | if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE)) { |
305 | 0 | ReportError("aiMesh::mFaces[%i] is a TRIANGLE but aiMesh::mPrimitiveTypes " |
306 | 0 | "does not report the TRIANGLE flag", |
307 | 0 | i); |
308 | 0 | } |
309 | 18.3k | break; |
310 | 9.33k | default: |
311 | 9.33k | if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_POLYGON)) { |
312 | 0 | this->ReportError("aiMesh::mFaces[%i] is a POLYGON but aiMesh::mPrimitiveTypes " |
313 | 0 | "does not report the POLYGON flag", |
314 | 0 | i); |
315 | 0 | } |
316 | 9.33k | break; |
317 | 29.0k | }; |
318 | 29.0k | } |
319 | | |
320 | 55.7k | if (!face.mIndices) |
321 | 0 | ReportError("aiMesh::mFaces[%i].mIndices is nullptr", i); |
322 | 55.7k | } |
323 | | |
324 | | // positions must always be there ... |
325 | 1.74k | if (!pMesh->mNumVertices || (!pMesh->mVertices && !mScene->mFlags)) { |
326 | 0 | ReportError("The mesh %s contains no vertices", pMesh->mName.C_Str()); |
327 | 0 | } |
328 | | |
329 | 1.74k | if (pMesh->mNumVertices > AI_MAX_VERTICES) { |
330 | 0 | ReportError("Mesh has too many vertices: %u, but the limit is %u", pMesh->mNumVertices, AI_MAX_VERTICES); |
331 | 0 | } |
332 | 1.74k | if (pMesh->mNumFaces > AI_MAX_FACES) { |
333 | 0 | ReportError("Mesh has too many faces: %u, but the limit is %u", pMesh->mNumFaces, AI_MAX_FACES); |
334 | 0 | } |
335 | | |
336 | | // if tangents are there there must also be bitangent vectors ... |
337 | 1.74k | if ((pMesh->mTangents != nullptr) != (pMesh->mBitangents != nullptr)) { |
338 | 0 | ReportError("If there are tangents, bitangent vectors must be present as well"); |
339 | 0 | } |
340 | | |
341 | | // faces, too |
342 | 1.74k | if (!pMesh->mNumFaces || (!pMesh->mFaces && !mScene->mFlags)) { |
343 | 0 | ReportError("Mesh %s contains no faces", pMesh->mName.C_Str()); |
344 | 0 | } |
345 | | |
346 | | // now check whether the face indexing layout is correct: |
347 | | // unique vertices, pseudo-indexed. |
348 | 1.74k | std::vector<bool> abRefList; |
349 | 1.74k | abRefList.resize(pMesh->mNumVertices, false); |
350 | 57.5k | for (unsigned int i = 0; i < pMesh->mNumFaces; ++i) { |
351 | 55.7k | aiFace &face = pMesh->mFaces[i]; |
352 | 55.7k | if (face.mNumIndices > AI_MAX_FACE_INDICES) { |
353 | 0 | ReportError("Face %u has too many faces: %u, but the limit is %u", i, face.mNumIndices, AI_MAX_FACE_INDICES); |
354 | 0 | } |
355 | | |
356 | 256k | for (unsigned int a = 0; a < face.mNumIndices; ++a) { |
357 | 200k | if (face.mIndices[a] >= pMesh->mNumVertices) { |
358 | 0 | ReportError("aiMesh::mFaces[%i]::mIndices[%i] is out of range", i, a); |
359 | 0 | } |
360 | 200k | abRefList[face.mIndices[a]] = true; |
361 | 200k | } |
362 | 55.7k | } |
363 | | |
364 | | // check whether there are vertices that aren't referenced by a face |
365 | 1.74k | bool b = false; |
366 | 202k | for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) { |
367 | 200k | if (!abRefList[i]) b = true; |
368 | 200k | } |
369 | 1.74k | abRefList.clear(); |
370 | 1.74k | if (b) { |
371 | 0 | ReportWarning("There are unreferenced vertices"); |
372 | 0 | } |
373 | | |
374 | | // vertex color channel 2 may not be set if channel 1 is zero ... |
375 | 1.74k | { |
376 | 1.74k | unsigned int i = 0; |
377 | 2.07k | for (; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) { |
378 | 2.07k | if (!pMesh->HasVertexColors(i)) break; |
379 | 2.07k | } |
380 | 15.3k | for (; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) |
381 | 13.6k | if (pMesh->HasVertexColors(i)) { |
382 | 0 | ReportError("Vertex color channel %i is exists " |
383 | 0 | "although the previous channel was nullptr.", |
384 | 0 | i); |
385 | 0 | } |
386 | 1.74k | } |
387 | | |
388 | | // now validate all bones |
389 | 1.74k | if (pMesh->mNumBones) { |
390 | 17 | if (!pMesh->mBones) { |
391 | 0 | ReportError("aiMesh::mBones is nullptr (aiMesh::mNumBones is %i)", |
392 | 0 | pMesh->mNumBones); |
393 | 0 | } |
394 | 17 | std::unique_ptr<float[]> afSum(nullptr); |
395 | 17 | if (pMesh->mNumVertices) { |
396 | 17 | afSum.reset(new float[pMesh->mNumVertices]); |
397 | 80.0k | for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) |
398 | 80.0k | afSum[i] = 0.0f; |
399 | 17 | } |
400 | | |
401 | | // check whether there are duplicate bone names |
402 | 2.64k | for (unsigned int i = 0; i < pMesh->mNumBones; ++i) { |
403 | 2.62k | const aiBone *bone = pMesh->mBones[i]; |
404 | 2.62k | if (bone->mNumWeights > AI_MAX_BONE_WEIGHTS) { |
405 | 0 | ReportError("Bone %u has too many weights: %u, but the limit is %u", i, bone->mNumWeights, AI_MAX_BONE_WEIGHTS); |
406 | 0 | } |
407 | | |
408 | 2.62k | if (!pMesh->mBones[i]) { |
409 | 0 | ReportError("aiMesh::mBones[%i] is nullptr (aiMesh::mNumBones is %i)", |
410 | 0 | i, pMesh->mNumBones); |
411 | 0 | } |
412 | 2.62k | Validate(pMesh, pMesh->mBones[i], afSum.get()); |
413 | | |
414 | 873k | for (unsigned int a = i + 1; a < pMesh->mNumBones; ++a) { |
415 | 870k | if (pMesh->mBones[i]->mName == pMesh->mBones[a]->mName) { |
416 | 3 | const char *name = "unknown"; |
417 | 3 | if (nullptr != pMesh->mBones[i]->mName.C_Str()) { |
418 | 3 | name = pMesh->mBones[i]->mName.C_Str(); |
419 | 3 | } |
420 | 3 | ReportError("aiMesh::mBones[%i], name = \"%s\" has the same name as " |
421 | 3 | "aiMesh::mBones[%i]", |
422 | 3 | i, name, a); |
423 | 3 | } |
424 | 870k | } |
425 | 2.62k | } |
426 | | // check whether all bone weights for a vertex sum to 1.0 ... |
427 | 63.1k | for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) { |
428 | 63.1k | if (afSum[i] && (afSum[i] <= 0.94 || afSum[i] >= 1.05)) { |
429 | 0 | ReportWarning("aiMesh::mVertices[%i]: bone weight sum != 1.0 (sum is %f)", i, afSum[i]); |
430 | 0 | } |
431 | 63.1k | } |
432 | 1.72k | } else if (pMesh->mBones) { |
433 | 0 | ReportError("aiMesh::mBones is non-null although there are no bones"); |
434 | 0 | } |
435 | 1.74k | } |
436 | | |
437 | | // ------------------------------------------------------------------------------------------------ |
438 | 2.62k | void ValidateDSProcess::Validate(const aiMesh *pMesh, const aiBone *pBone, float *afSum) { |
439 | 2.62k | this->Validate(&pBone->mName); |
440 | | |
441 | 2.62k | if (!pBone->mNumWeights) { |
442 | 0 | ReportWarning("aiBone::mNumWeights is zero"); |
443 | 0 | } |
444 | | |
445 | | // check whether all vertices affected by this bone are valid |
446 | 65.8k | for (unsigned int i = 0; i < pBone->mNumWeights; ++i) { |
447 | 63.2k | if (pBone->mWeights[i].mVertexId >= pMesh->mNumVertices) { |
448 | 0 | ReportError("aiBone::mWeights[%i].mVertexId is out of range", i); |
449 | 63.2k | } else if (!pBone->mWeights[i].mWeight || pBone->mWeights[i].mWeight > 1.0f) { |
450 | 0 | ReportWarning("aiBone::mWeights[%i].mWeight has an invalid value %i. Value must be greater than zero and less than 1.", i, pBone->mWeights[i].mWeight); |
451 | 0 | } |
452 | 63.2k | afSum[pBone->mWeights[i].mVertexId] += pBone->mWeights[i].mWeight; |
453 | 63.2k | } |
454 | 2.62k | } |
455 | | |
456 | | // ------------------------------------------------------------------------------------------------ |
457 | 11 | void ValidateDSProcess::Validate(const aiAnimation *pAnimation) { |
458 | 11 | Validate(&pAnimation->mName); |
459 | | |
460 | | // validate all animations |
461 | 11 | if (pAnimation->mNumChannels || pAnimation->mNumMorphMeshChannels) { |
462 | 11 | if (!pAnimation->mChannels && pAnimation->mNumChannels) { |
463 | 0 | ReportError("aiAnimation::mChannels is nullptr (aiAnimation::mNumChannels is %i)", |
464 | 0 | pAnimation->mNumChannels); |
465 | 0 | } |
466 | 11 | if (!pAnimation->mMorphMeshChannels && pAnimation->mNumMorphMeshChannels) { |
467 | 0 | ReportError("aiAnimation::mMorphMeshChannels is nullptr (aiAnimation::mNumMorphMeshChannels is %i)", |
468 | 0 | pAnimation->mNumMorphMeshChannels); |
469 | 0 | } |
470 | 44 | for (unsigned int i = 0; i < pAnimation->mNumChannels; ++i) { |
471 | 33 | if (!pAnimation->mChannels[i]) { |
472 | 0 | ReportError("aiAnimation::mChannels[%i] is nullptr (aiAnimation::mNumChannels is %i)", |
473 | 0 | i, pAnimation->mNumChannels); |
474 | 0 | } |
475 | 33 | Validate(pAnimation, pAnimation->mChannels[i]); |
476 | 33 | } |
477 | 11 | for (unsigned int i = 0; i < pAnimation->mNumMorphMeshChannels; ++i) { |
478 | 0 | if (!pAnimation->mMorphMeshChannels[i]) { |
479 | 0 | ReportError("aiAnimation::mMorphMeshChannels[%i] is nullptr (aiAnimation::mNumMorphMeshChannels is %i)", |
480 | 0 | i, pAnimation->mNumMorphMeshChannels); |
481 | 0 | } |
482 | 0 | Validate(pAnimation, pAnimation->mMorphMeshChannels[i]); |
483 | 0 | } |
484 | 11 | } else { |
485 | 0 | ReportError("aiAnimation::mNumChannels is 0. At least one node animation channel must be there."); |
486 | 0 | } |
487 | 11 | } |
488 | | |
489 | | // ------------------------------------------------------------------------------------------------ |
490 | | void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial *pMaterial, |
491 | 7.46k | aiTextureType type) { |
492 | 7.46k | const char *szType = aiTextureTypeToString(type); |
493 | | |
494 | | // **************************************************************************** |
495 | | // Search all keys of the material ... |
496 | | // textures must be specified with ascending indices |
497 | | // (e.g. diffuse #2 may not be specified if diffuse #1 is not there ...) |
498 | | // **************************************************************************** |
499 | | |
500 | 7.46k | int iNumIndices = 0; |
501 | 7.46k | int iIndex = -1; |
502 | 88.8k | for (unsigned int i = 0; i < pMaterial->mNumProperties; ++i) { |
503 | 81.4k | aiMaterialProperty *prop = pMaterial->mProperties[i]; |
504 | 81.4k | ai_assert(nullptr != prop); |
505 | 81.4k | if (!::strcmp(prop->mKey.data, "$tex.file") && prop->mSemantic == static_cast<unsigned int>(type)) { |
506 | 13 | iIndex = std::max(iIndex, (int)prop->mIndex); |
507 | 13 | ++iNumIndices; |
508 | | |
509 | 13 | if (aiPTI_String != prop->mType) { |
510 | 0 | ReportError("Material property %s is expected to be a string", prop->mKey.data); |
511 | 0 | } |
512 | 13 | } |
513 | 81.4k | } |
514 | 7.46k | if (iIndex + 1 != iNumIndices) { |
515 | 0 | ReportError("%s #%i is set, but there are only %i %s textures", |
516 | 0 | szType, iIndex, iNumIndices, szType); |
517 | 0 | } |
518 | 7.46k | if (!iNumIndices) { |
519 | 7.45k | return; |
520 | 7.45k | } |
521 | 13 | std::vector<aiTextureMapping> mappings(iNumIndices); |
522 | | |
523 | | // Now check whether all UV indices are valid ... |
524 | 13 | bool bNoSpecified = true; |
525 | 211 | for (unsigned int i = 0; i < pMaterial->mNumProperties; ++i) { |
526 | 198 | aiMaterialProperty *prop = pMaterial->mProperties[i]; |
527 | 198 | if (static_cast<aiTextureType>(prop->mSemantic) != type) { |
528 | 172 | continue; |
529 | 172 | } |
530 | | |
531 | 26 | if ((int)prop->mIndex >= iNumIndices) { |
532 | 0 | ReportError("Found texture property with index %i, although there " |
533 | 0 | "are only %i textures of type %s", |
534 | 0 | prop->mIndex, iNumIndices, szType); |
535 | 0 | } |
536 | | |
537 | 26 | if (!::strcmp(prop->mKey.data, "$tex.mapping")) { |
538 | 0 | if (aiPTI_Integer != prop->mType || prop->mDataLength < sizeof(aiTextureMapping)) { |
539 | 0 | ReportError("Material property %s%i is expected to be an integer (size is %i)", |
540 | 0 | prop->mKey.data, prop->mIndex, prop->mDataLength); |
541 | 0 | } |
542 | 0 | mappings[prop->mIndex] = *((aiTextureMapping *)prop->mData); |
543 | 26 | } else if (!::strcmp(prop->mKey.data, "$tex.uvtrafo")) { |
544 | 0 | if (aiPTI_Float != prop->mType || prop->mDataLength < sizeof(aiUVTransform)) { |
545 | 0 | ReportError("Material property %s%i is expected to be 5 floats large (size is %i)", |
546 | 0 | prop->mKey.data, prop->mIndex, prop->mDataLength); |
547 | 0 | } |
548 | | //mappings[prop->mIndex] = ((aiUVTransform*)prop->mData); |
549 | 26 | } else if (!::strcmp(prop->mKey.data, "$tex.uvwsrc")) { |
550 | 13 | if (aiPTI_Integer != prop->mType || sizeof(int) > prop->mDataLength) { |
551 | 0 | ReportError("Material property %s%i is expected to be an integer (size is %i)", |
552 | 0 | prop->mKey.data, prop->mIndex, prop->mDataLength); |
553 | 0 | } |
554 | 13 | bNoSpecified = false; |
555 | | |
556 | | // Ignore UV indices for texture channels that are not there ... |
557 | | |
558 | | // Get the value |
559 | 13 | iIndex = *((unsigned int *)prop->mData); |
560 | | |
561 | | // Check whether there is a mesh using this material |
562 | | // which has not enough UV channels ... |
563 | 377 | for (unsigned int a = 0; a < mScene->mNumMeshes; ++a) { |
564 | 364 | aiMesh *mesh = this->mScene->mMeshes[a]; |
565 | 364 | if (mesh->mMaterialIndex == (unsigned int)i) { |
566 | 70 | int iChannels = 0; |
567 | 114 | while (mesh->HasTextureCoords(iChannels)) |
568 | 44 | ++iChannels; |
569 | 70 | if (iIndex >= iChannels) { |
570 | 26 | ReportWarning("Invalid UV index: %i (key %s). Mesh %i has only %i UV channels", |
571 | 26 | iIndex, prop->mKey.data, a, iChannels); |
572 | 26 | } |
573 | 70 | } |
574 | 364 | } |
575 | 13 | } |
576 | 26 | } |
577 | 13 | if (bNoSpecified) { |
578 | | // Assume that all textures are using the first UV channel |
579 | 0 | for (unsigned int a = 0; a < mScene->mNumMeshes; ++a) { |
580 | 0 | aiMesh *mesh = mScene->mMeshes[a]; |
581 | 0 | if (mesh->mMaterialIndex == (unsigned int)iIndex && mappings[0] == aiTextureMapping_UV) { |
582 | 0 | if (!mesh->mTextureCoords[0]) { |
583 | | // This is a special case ... it could be that the |
584 | | // original mesh format intended the use of a special |
585 | | // mapping here. |
586 | 0 | ReportWarning("UV-mapped texture, but there are no UV coords"); |
587 | 0 | } |
588 | 0 | } |
589 | 0 | } |
590 | 0 | } |
591 | 13 | } |
592 | | // ------------------------------------------------------------------------------------------------ |
593 | 439 | void ValidateDSProcess::Validate(const aiMaterial *pMaterial) { |
594 | | // check whether there are material keys that are obviously not legal |
595 | 5.22k | for (unsigned int i = 0; i < pMaterial->mNumProperties; ++i) { |
596 | 4.79k | const aiMaterialProperty *prop = pMaterial->mProperties[i]; |
597 | 4.79k | if (!prop) { |
598 | 0 | ReportError("aiMaterial::mProperties[%i] is nullptr (aiMaterial::mNumProperties is %i)", |
599 | 0 | i, pMaterial->mNumProperties); |
600 | 0 | } |
601 | 4.79k | if (!prop->mDataLength || !prop->mData) { |
602 | 0 | ReportError("aiMaterial::mProperties[%i].mDataLength or " |
603 | 0 | "aiMaterial::mProperties[%i].mData is 0", |
604 | 0 | i, i); |
605 | 0 | } |
606 | | // check all predefined types |
607 | 4.79k | if (aiPTI_String == prop->mType) { |
608 | | // FIX: strings are now stored in a less expensive way, but we can't use the |
609 | | // validation routine for 'normal' aiStrings |
610 | 452 | if (prop->mDataLength < 5 || prop->mDataLength < 4 + (*reinterpret_cast<uint32_t *>(prop->mData)) + 1) { |
611 | 0 | ReportError("aiMaterial::mProperties[%i].mDataLength is " |
612 | 0 | "too small to contain a string (%i, needed: %i)", |
613 | 0 | i, prop->mDataLength, static_cast<int>(sizeof(aiString))); |
614 | 0 | } |
615 | 452 | if (prop->mData[prop->mDataLength - 1]) { |
616 | 0 | ReportError("Missing null-terminator in string material property"); |
617 | 0 | } |
618 | | // Validate((const aiString*)prop->mData); |
619 | 4.33k | } else if (aiPTI_Float == prop->mType) { |
620 | 3.54k | if (prop->mDataLength < sizeof(float)) { |
621 | 0 | ReportError("aiMaterial::mProperties[%i].mDataLength is " |
622 | 0 | "too small to contain a float (%i, needed: %i)", |
623 | 0 | i, prop->mDataLength, static_cast<int>(sizeof(float))); |
624 | 0 | } |
625 | 3.54k | } else if (aiPTI_Integer == prop->mType) { |
626 | 795 | if (prop->mDataLength < sizeof(int)) { |
627 | 0 | ReportError("aiMaterial::mProperties[%i].mDataLength is " |
628 | 0 | "too small to contain an integer (%i, needed: %i)", |
629 | 0 | i, prop->mDataLength, static_cast<int>(sizeof(int))); |
630 | 0 | } |
631 | 795 | } |
632 | | // TODO: check whether there is a key with an unknown name ... |
633 | 4.79k | } |
634 | | |
635 | | // make some more specific tests |
636 | 439 | ai_real fTemp; |
637 | 439 | int iShading; |
638 | 439 | if (AI_SUCCESS == aiGetMaterialInteger(pMaterial, AI_MATKEY_SHADING_MODEL, &iShading)) { |
639 | 423 | switch ((aiShadingMode)iShading) { |
640 | 0 | case aiShadingMode_Blinn: |
641 | 0 | case aiShadingMode_CookTorrance: |
642 | 0 | case aiShadingMode_Phong: |
643 | |
|
644 | 0 | if (AI_SUCCESS != aiGetMaterialFloat(pMaterial, AI_MATKEY_SHININESS, &fTemp)) { |
645 | 0 | ReportWarning("A specular shading model is specified but there is no " |
646 | 0 | "AI_MATKEY_SHININESS key"); |
647 | 0 | } |
648 | 0 | if (AI_SUCCESS == aiGetMaterialFloat(pMaterial, AI_MATKEY_SHININESS_STRENGTH, &fTemp) && !fTemp) { |
649 | 0 | ReportWarning("A specular shading model is specified but the value of the " |
650 | 0 | "AI_MATKEY_SHININESS_STRENGTH key is 0.0"); |
651 | 0 | } |
652 | 0 | break; |
653 | 423 | default: |
654 | 423 | break; |
655 | 423 | } |
656 | 423 | } |
657 | | |
658 | 439 | if (AI_SUCCESS == aiGetMaterialFloat(pMaterial, AI_MATKEY_OPACITY, &fTemp) && (!fTemp || fTemp > 1.01)) { |
659 | 17 | ReportWarning("Invalid opacity value (must be 0 < opacity < 1.0)"); |
660 | 17 | } |
661 | | |
662 | | // Check whether there are invalid texture keys |
663 | | // TODO: that's a relict of the past, where texture type and index were baked |
664 | | // into the material string ... we could do that in one single pass. |
665 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_DIFFUSE); |
666 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_SPECULAR); |
667 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_AMBIENT); |
668 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_EMISSIVE); |
669 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_OPACITY); |
670 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_SHININESS); |
671 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_HEIGHT); |
672 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_NORMALS); |
673 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_DISPLACEMENT); |
674 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_LIGHTMAP); |
675 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_REFLECTION); |
676 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_BASE_COLOR); |
677 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_NORMAL_CAMERA); |
678 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_EMISSION_COLOR); |
679 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_METALNESS); |
680 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_DIFFUSE_ROUGHNESS); |
681 | 439 | SearchForInvalidTextures(pMaterial, aiTextureType_AMBIENT_OCCLUSION); |
682 | 439 | } |
683 | | |
684 | | // ------------------------------------------------------------------------------------------------ |
685 | 0 | void ValidateDSProcess::Validate(const aiTexture *pTexture) { |
686 | | // the data section may NEVER be nullptr |
687 | 0 | if (nullptr == pTexture->pcData) { |
688 | 0 | ReportError("aiTexture::pcData is nullptr"); |
689 | 0 | } |
690 | 0 | if (pTexture->mHeight) { |
691 | 0 | if (!pTexture->mWidth) { |
692 | 0 | ReportError("aiTexture::mWidth is zero (aiTexture::mHeight is %i, uncompressed texture)", |
693 | 0 | pTexture->mHeight); |
694 | 0 | } |
695 | 0 | } else { |
696 | 0 | if (!pTexture->mWidth) { |
697 | 0 | ReportError("aiTexture::mWidth is zero (compressed texture)"); |
698 | 0 | } |
699 | 0 | if ('\0' != pTexture->achFormatHint[HINTMAXTEXTURELEN - 1]) { |
700 | 0 | ReportWarning("aiTexture::achFormatHint must be zero-terminated"); |
701 | 0 | } else if ('.' == pTexture->achFormatHint[0]) { |
702 | 0 | ReportWarning("aiTexture::achFormatHint should contain a file extension " |
703 | 0 | "without a leading dot (format hint: %s).", |
704 | 0 | pTexture->achFormatHint); |
705 | 0 | } |
706 | 0 | } |
707 | |
|
708 | 0 | const char *sz = pTexture->achFormatHint; |
709 | 0 | if ((sz[0] >= 'A' && sz[0] <= 'Z') || |
710 | 0 | (sz[1] >= 'A' && sz[1] <= 'Z') || |
711 | 0 | (sz[2] >= 'A' && sz[2] <= 'Z') || |
712 | 0 | (sz[3] >= 'A' && sz[3] <= 'Z')) { |
713 | 0 | ReportError("aiTexture::achFormatHint contains non-lowercase letters"); |
714 | 0 | } |
715 | 0 | } |
716 | | |
717 | | // ------------------------------------------------------------------------------------------------ |
718 | | void ValidateDSProcess::Validate(const aiAnimation *pAnimation, |
719 | 33 | const aiNodeAnim *pNodeAnim) { |
720 | 33 | Validate(&pNodeAnim->mNodeName); |
721 | | |
722 | 33 | if (!pNodeAnim->mNumPositionKeys && !pNodeAnim->mScalingKeys && !pNodeAnim->mNumRotationKeys) { |
723 | 0 | ReportError("Empty node animation channel"); |
724 | 0 | } |
725 | | // otherwise check whether one of the keys exceeds the total duration of the animation |
726 | 33 | if (pNodeAnim->mNumPositionKeys) { |
727 | 29 | if (!pNodeAnim->mPositionKeys) { |
728 | 0 | ReportError("aiNodeAnim::mPositionKeys is nullptr (aiNodeAnim::mNumPositionKeys is %i)", |
729 | 0 | pNodeAnim->mNumPositionKeys); |
730 | 0 | } |
731 | 29 | double dLast = -10e10; |
732 | 112 | for (unsigned int i = 0; i < pNodeAnim->mNumPositionKeys; ++i) { |
733 | | // ScenePreprocessor will compute the duration if still the default value |
734 | | // (Aramis) Add small epsilon, comparison tended to fail if max_time == duration, |
735 | | // seems to be due the compilers register usage/width. |
736 | 83 | if (pAnimation->mDuration > 0. && pNodeAnim->mPositionKeys[i].mTime > pAnimation->mDuration + 0.001) { |
737 | 1 | ReportError("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is larger " |
738 | 1 | "than aiAnimation::mDuration (which is %.5f)", |
739 | 1 | i, |
740 | 1 | (float)pNodeAnim->mPositionKeys[i].mTime, |
741 | 1 | (float)pAnimation->mDuration); |
742 | 1 | } |
743 | 83 | if (i && pNodeAnim->mPositionKeys[i].mTime <= dLast) { |
744 | 11 | ReportWarning("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is smaller " |
745 | 11 | "than aiAnimation::mPositionKeys[%i] (which is %.5f)", |
746 | 11 | i, |
747 | 11 | (float)pNodeAnim->mPositionKeys[i].mTime, |
748 | 11 | i - 1, (float)dLast); |
749 | 11 | } |
750 | 83 | dLast = pNodeAnim->mPositionKeys[i].mTime; |
751 | 83 | } |
752 | 29 | } |
753 | | // rotation keys |
754 | 33 | if (pNodeAnim->mNumRotationKeys) { |
755 | 24 | if (!pNodeAnim->mRotationKeys) { |
756 | 0 | ReportError("aiNodeAnim::mRotationKeys is nullptr (aiNodeAnim::mNumRotationKeys is %i)", |
757 | 0 | pNodeAnim->mNumRotationKeys); |
758 | 0 | } |
759 | 24 | double dLast = -10e10; |
760 | 67 | for (unsigned int i = 0; i < pNodeAnim->mNumRotationKeys; ++i) { |
761 | 43 | if (pAnimation->mDuration > 0. && pNodeAnim->mRotationKeys[i].mTime > pAnimation->mDuration + 0.001) { |
762 | 0 | ReportError("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is larger " |
763 | 0 | "than aiAnimation::mDuration (which is %.5f)", |
764 | 0 | i, |
765 | 0 | (float)pNodeAnim->mRotationKeys[i].mTime, |
766 | 0 | (float)pAnimation->mDuration); |
767 | 0 | } |
768 | 43 | if (i && pNodeAnim->mRotationKeys[i].mTime <= dLast) { |
769 | 6 | ReportWarning("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is smaller " |
770 | 6 | "than aiAnimation::mRotationKeys[%i] (which is %.5f)", |
771 | 6 | i, |
772 | 6 | (float)pNodeAnim->mRotationKeys[i].mTime, |
773 | 6 | i - 1, (float)dLast); |
774 | 6 | } |
775 | 43 | dLast = pNodeAnim->mRotationKeys[i].mTime; |
776 | 43 | } |
777 | 24 | } |
778 | | // scaling keys |
779 | 33 | if (pNodeAnim->mNumScalingKeys) { |
780 | 4 | if (!pNodeAnim->mScalingKeys) { |
781 | 0 | ReportError("aiNodeAnim::mScalingKeys is nullptr (aiNodeAnim::mNumScalingKeys is %i)", |
782 | 0 | pNodeAnim->mNumScalingKeys); |
783 | 0 | } |
784 | 4 | double dLast = -10e10; |
785 | 12 | for (unsigned int i = 0; i < pNodeAnim->mNumScalingKeys; ++i) { |
786 | 8 | if (pAnimation->mDuration > 0. && pNodeAnim->mScalingKeys[i].mTime > pAnimation->mDuration + 0.001) { |
787 | 4 | ReportError("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is larger " |
788 | 4 | "than aiAnimation::mDuration (which is %.5f)", |
789 | 4 | i, |
790 | 4 | (float)pNodeAnim->mScalingKeys[i].mTime, |
791 | 4 | (float)pAnimation->mDuration); |
792 | 4 | } |
793 | 8 | if (i && pNodeAnim->mScalingKeys[i].mTime <= dLast) { |
794 | 0 | ReportWarning("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is smaller " |
795 | 0 | "than aiAnimation::mScalingKeys[%i] (which is %.5f)", |
796 | 0 | i, |
797 | 0 | (float)pNodeAnim->mScalingKeys[i].mTime, |
798 | 0 | i - 1, (float)dLast); |
799 | 0 | } |
800 | 8 | dLast = pNodeAnim->mScalingKeys[i].mTime; |
801 | 8 | } |
802 | 4 | } |
803 | | |
804 | 33 | if (!pNodeAnim->mNumScalingKeys && !pNodeAnim->mNumRotationKeys && |
805 | 4 | !pNodeAnim->mNumPositionKeys) { |
806 | 0 | ReportError("A node animation channel must have at least one subtrack"); |
807 | 0 | } |
808 | 33 | } |
809 | | |
810 | | void ValidateDSProcess::Validate(const aiAnimation *pAnimation, |
811 | 0 | const aiMeshMorphAnim *pMeshMorphAnim) { |
812 | 0 | Validate(&pMeshMorphAnim->mName); |
813 | |
|
814 | 0 | if (!pMeshMorphAnim->mNumKeys) { |
815 | 0 | ReportWarning("Empty mesh morph animation channel"); |
816 | 0 | return; |
817 | 0 | } |
818 | | |
819 | | // otherwise check whether one of the keys exceeds the total duration of the animation |
820 | 0 | if (pMeshMorphAnim->mNumKeys) { |
821 | 0 | if (!pMeshMorphAnim->mKeys) { |
822 | 0 | ReportError("aiMeshMorphAnim::mKeys is nullptr (aiMeshMorphAnim::mNumKeys is %i)", |
823 | 0 | pMeshMorphAnim->mNumKeys); |
824 | 0 | } |
825 | 0 | double dLast = -10e10; |
826 | 0 | for (unsigned int i = 0; i < pMeshMorphAnim->mNumKeys; ++i) { |
827 | | // ScenePreprocessor will compute the duration if still the default value |
828 | | // (Aramis) Add small epsilon, comparison tended to fail if max_time == duration, |
829 | | // seems to be due the compilers register usage/width. |
830 | 0 | if (pAnimation->mDuration > 0. && pMeshMorphAnim->mKeys[i].mTime > pAnimation->mDuration + 0.001) { |
831 | 0 | ReportError("aiMeshMorphAnim::mKeys[%i].mTime (%.5f) is larger " |
832 | 0 | "than aiAnimation::mDuration (which is %.5f)", |
833 | 0 | i, |
834 | 0 | (float)pMeshMorphAnim->mKeys[i].mTime, |
835 | 0 | (float)pAnimation->mDuration); |
836 | 0 | } |
837 | 0 | if (i && pMeshMorphAnim->mKeys[i].mTime <= dLast) { |
838 | 0 | ReportWarning("aiMeshMorphAnim::mKeys[%i].mTime (%.5f) is smaller " |
839 | 0 | "than aiMeshMorphAnim::mKeys[%i] (which is %.5f)", |
840 | 0 | i, |
841 | 0 | (float)pMeshMorphAnim->mKeys[i].mTime, |
842 | 0 | i - 1, (float)dLast); |
843 | 0 | } |
844 | 0 | dLast = pMeshMorphAnim->mKeys[i].mTime; |
845 | 0 | } |
846 | 0 | } |
847 | 0 | } |
848 | | |
849 | | // ------------------------------------------------------------------------------------------------ |
850 | 26.1k | void ValidateDSProcess::Validate(const aiNode *pNode) { |
851 | 26.1k | if (!pNode) { |
852 | 14 | ReportError("A node of the scene-graph is nullptr"); |
853 | 14 | } |
854 | | // Validate node name string first so that it's safe to use in below expressions |
855 | 26.1k | this->Validate(&pNode->mName); |
856 | 26.1k | const char *nodeName = (&pNode->mName)->C_Str(); |
857 | 26.1k | if (pNode != mScene->mRootNode && !pNode->mParent) { |
858 | 0 | ReportError("Non-root node %s lacks a valid parent (aiNode::mParent is nullptr) ", nodeName); |
859 | 0 | } |
860 | | |
861 | | // validate all meshes |
862 | 26.1k | if (pNode->mNumMeshes) { |
863 | 4.95k | if (!pNode->mMeshes) { |
864 | 0 | ReportError("aiNode::mMeshes is nullptr for node %s (aiNode::mNumMeshes is %i)", |
865 | 0 | nodeName, pNode->mNumMeshes); |
866 | 0 | } |
867 | 4.95k | std::vector<bool> abHadMesh; |
868 | 4.95k | abHadMesh.resize(mScene->mNumMeshes, false); |
869 | 11.6k | for (unsigned int i = 0; i < pNode->mNumMeshes; ++i) { |
870 | 6.68k | if (pNode->mMeshes[i] >= mScene->mNumMeshes) { |
871 | 0 | ReportError("aiNode::mMeshes[%i] is out of range for node %s (maximum is %i)", |
872 | 0 | pNode->mMeshes[i], nodeName, mScene->mNumMeshes - 1); |
873 | 0 | } |
874 | 6.68k | if (abHadMesh[pNode->mMeshes[i]]) { |
875 | 0 | ReportError("aiNode::mMeshes[%i] is already referenced by this node %s (value: %i)", |
876 | 0 | i, nodeName, pNode->mMeshes[i]); |
877 | 0 | } |
878 | 6.68k | abHadMesh[pNode->mMeshes[i]] = true; |
879 | 6.68k | } |
880 | 4.95k | } |
881 | 26.1k | if (pNode->mNumChildren) { |
882 | 4.06k | if (!pNode->mChildren) { |
883 | 0 | ReportError("aiNode::mChildren is nullptr for node %s (aiNode::mNumChildren is %i)", |
884 | 0 | nodeName, pNode->mNumChildren); |
885 | 0 | } |
886 | 30.0k | for (unsigned int i = 0; i < pNode->mNumChildren; ++i) { |
887 | 26.0k | const aiNode *pChild = pNode->mChildren[i]; |
888 | 26.0k | Validate(pChild); |
889 | 26.0k | if (pChild->mParent != pNode) { |
890 | 0 | const char *parentName = (pChild->mParent != nullptr) ? pChild->mParent->mName.C_Str() : "null"; |
891 | 0 | ReportError("aiNode \"%s\" child %i \"%s\" parent is someone else: \"%s\"", pNode->mName.C_Str(), i, pChild->mName.C_Str(), parentName); |
892 | 0 | } |
893 | 26.0k | } |
894 | 22.0k | } else if (pNode->mChildren) { |
895 | 0 | ReportError("aiNode::mChildren is not nullptr for empty node %s (aiNode::mNumChildren is %i)", |
896 | 0 | nodeName, pNode->mNumChildren); |
897 | 0 | } |
898 | 26.1k | } |
899 | | |
900 | | // ------------------------------------------------------------------------------------------------ |
901 | 30.5k | void ValidateDSProcess::Validate(const aiString *pString) { |
902 | 30.5k | if (pString->length > AI_MAXLEN) { |
903 | 0 | ReportError("aiString::length is too large (%u, maximum is %lu)", |
904 | 0 | pString->length, AI_MAXLEN); |
905 | 0 | } |
906 | 30.5k | const char *sz = pString->data; |
907 | 557k | while (true) { |
908 | 557k | if ('\0' == *sz) { |
909 | 30.5k | if (pString->length != (unsigned int)(sz - pString->data)) { |
910 | 2 | ReportError("aiString::data is invalid: the terminal zero is at a wrong offset"); |
911 | 2 | } |
912 | 30.5k | break; |
913 | 527k | } else if (sz >= &pString->data[AI_MAXLEN]) { |
914 | 0 | ReportError("aiString::data is invalid. There is no terminal character"); |
915 | 0 | } |
916 | 527k | ++sz; |
917 | 527k | } |
918 | 30.5k | } |