1 | /* |
2 | Open Asset Import Library (assimp) |
3 | ---------------------------------------------------------------------- |
4 | |
5 | Copyright (c) 2006-2017, assimp team |
6 | |
7 | All rights reserved. |
8 | |
9 | Redistribution and use of this software in source and binary forms, |
10 | with or without modification, are permitted provided that the |
11 | following conditions are met: |
12 | |
13 | * Redistributions of source code must retain the above |
14 | copyright notice, this list of conditions and the |
15 | following disclaimer. |
16 | |
17 | * Redistributions in binary form must reproduce the above |
18 | copyright notice, this list of conditions and the |
19 | following disclaimer in the documentation and/or other |
20 | materials provided with the distribution. |
21 | |
22 | * Neither the name of the assimp team, nor the names of its |
23 | contributors may be used to endorse or promote products |
24 | derived from this software without specific prior |
25 | written permission of the assimp team. |
26 | |
27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
28 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
29 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
30 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
31 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
32 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
33 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
34 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
35 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
36 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
37 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
38 | |
39 | ---------------------------------------------------------------------- |
40 | */ |
41 | |
42 | /** @file GenUVCoords step */ |
43 | |
44 | |
45 | #include "ComputeUVMappingProcess.h" |
46 | #include "ProcessHelper.h" |
47 | #include "Exceptional.h" |
48 | |
49 | using namespace Assimp; |
50 | |
51 | namespace { |
52 | |
53 | const static aiVector3D base_axis_y(0.0,1.0,0.0); |
54 | const static aiVector3D base_axis_x(1.0,0.0,0.0); |
55 | const static aiVector3D base_axis_z(0.0,0.0,1.0); |
56 | const static ai_real angle_epsilon = ai_real( 0.95 ); |
57 | } |
58 | |
59 | // ------------------------------------------------------------------------------------------------ |
60 | // Constructor to be privately used by Importer |
61 | ComputeUVMappingProcess::ComputeUVMappingProcess() |
62 | { |
63 | // nothing to do here |
64 | } |
65 | |
66 | // ------------------------------------------------------------------------------------------------ |
67 | // Destructor, private as well |
68 | ComputeUVMappingProcess::~ComputeUVMappingProcess() |
69 | { |
70 | // nothing to do here |
71 | } |
72 | |
73 | // ------------------------------------------------------------------------------------------------ |
74 | // Returns whether the processing step is present in the given flag field. |
75 | bool ComputeUVMappingProcess::IsActive( unsigned int pFlags) const |
76 | { |
77 | return (pFlags & aiProcess_GenUVCoords) != 0; |
78 | } |
79 | |
80 | // ------------------------------------------------------------------------------------------------ |
81 | // Check whether a ray intersects a plane and find the intersection point |
82 | inline bool PlaneIntersect(const aiRay& ray, const aiVector3D& planePos, |
83 | const aiVector3D& planeNormal, aiVector3D& pos) |
84 | { |
85 | const ai_real b = planeNormal * (planePos - ray.pos); |
86 | ai_real h = ray.dir * planeNormal; |
87 | if ((h < 10e-5 && h > -10e-5) || (h = b/h) < 0) |
88 | return false; |
89 | |
90 | pos = ray.pos + (ray.dir * h); |
91 | return true; |
92 | } |
93 | |
94 | // ------------------------------------------------------------------------------------------------ |
95 | // Find the first empty UV channel in a mesh |
96 | inline unsigned int FindEmptyUVChannel (aiMesh* mesh) |
97 | { |
98 | for (unsigned int m = 0; m < AI_MAX_NUMBER_OF_TEXTURECOORDS;++m) |
99 | if (!mesh->mTextureCoords[m])return m; |
100 | |
101 | DefaultLogger::get()->error("Unable to compute UV coordinates, no free UV slot found" ); |
102 | return UINT_MAX; |
103 | } |
104 | |
105 | // ------------------------------------------------------------------------------------------------ |
106 | // Try to remove UV seams |
107 | void RemoveUVSeams (aiMesh* mesh, aiVector3D* out) |
108 | { |
109 | // TODO: just a very rough algorithm. I think it could be done |
110 | // much easier, but I don't know how and am currently too tired to |
111 | // to think about a better solution. |
112 | |
113 | const static ai_real LOWER_LIMIT = ai_real( 0.1 ); |
114 | const static ai_real UPPER_LIMIT = ai_real( 0.9 ); |
115 | |
116 | const static ai_real LOWER_EPSILON = ai_real( 10e-3 ); |
117 | const static ai_real UPPER_EPSILON = ai_real( 1.0-10e-3 ); |
118 | |
119 | for (unsigned int fidx = 0; fidx < mesh->mNumFaces;++fidx) |
120 | { |
121 | const aiFace& face = mesh->mFaces[fidx]; |
122 | if (face.mNumIndices < 3) continue; // triangles and polygons only, please |
123 | |
124 | unsigned int small = face.mNumIndices, large = small; |
125 | bool zero = false, one = false, round_to_zero = false; |
126 | |
127 | // Check whether this face lies on a UV seam. We can just guess, |
128 | // but the assumption that a face with at least one very small |
129 | // on the one side and one very large U coord on the other side |
130 | // lies on a UV seam should work for most cases. |
131 | for (unsigned int n = 0; n < face.mNumIndices;++n) |
132 | { |
133 | if (out[face.mIndices[n]].x < LOWER_LIMIT) |
134 | { |
135 | small = n; |
136 | |
137 | // If we have a U value very close to 0 we can't |
138 | // round the others to 0, too. |
139 | if (out[face.mIndices[n]].x <= LOWER_EPSILON) |
140 | zero = true; |
141 | else round_to_zero = true; |
142 | } |
143 | if (out[face.mIndices[n]].x > UPPER_LIMIT) |
144 | { |
145 | large = n; |
146 | |
147 | // If we have a U value very close to 1 we can't |
148 | // round the others to 1, too. |
149 | if (out[face.mIndices[n]].x >= UPPER_EPSILON) |
150 | one = true; |
151 | } |
152 | } |
153 | if (small != face.mNumIndices && large != face.mNumIndices) |
154 | { |
155 | for (unsigned int n = 0; n < face.mNumIndices;++n) |
156 | { |
157 | // If the u value is over the upper limit and no other u |
158 | // value of that face is 0, round it to 0 |
159 | if (out[face.mIndices[n]].x > UPPER_LIMIT && !zero) |
160 | out[face.mIndices[n]].x = 0.0; |
161 | |
162 | // If the u value is below the lower limit and no other u |
163 | // value of that face is 1, round it to 1 |
164 | else if (out[face.mIndices[n]].x < LOWER_LIMIT && !one) |
165 | out[face.mIndices[n]].x = 1.0; |
166 | |
167 | // The face contains both 0 and 1 as UV coords. This can occur |
168 | // for faces which have an edge that lies directly on the seam. |
169 | // Due to numerical inaccuracies one U coord becomes 0, the |
170 | // other 1. But we do still have a third UV coord to determine |
171 | // to which side we must round to. |
172 | else if (one && zero) |
173 | { |
174 | if (round_to_zero && out[face.mIndices[n]].x >= UPPER_EPSILON) |
175 | out[face.mIndices[n]].x = 0.0; |
176 | else if (!round_to_zero && out[face.mIndices[n]].x <= LOWER_EPSILON) |
177 | out[face.mIndices[n]].x = 1.0; |
178 | } |
179 | } |
180 | } |
181 | } |
182 | } |
183 | |
184 | // ------------------------------------------------------------------------------------------------ |
185 | void ComputeUVMappingProcess::ComputeSphereMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out) |
186 | { |
187 | aiVector3D center, min, max; |
188 | FindMeshCenter(mesh, center, min, max); |
189 | |
190 | // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ... |
191 | // currently the mapping axis will always be one of x,y,z, except if the |
192 | // PretransformVertices step is used (it transforms the meshes into worldspace, |
193 | // thus changing the mapping axis) |
194 | if (axis * base_axis_x >= angle_epsilon) { |
195 | |
196 | // For each point get a normalized projection vector in the sphere, |
197 | // get its longitude and latitude and map them to their respective |
198 | // UV axes. Problems occur around the poles ... unsolvable. |
199 | // |
200 | // The spherical coordinate system looks like this: |
201 | // x = cos(lon)*cos(lat) |
202 | // y = sin(lon)*cos(lat) |
203 | // z = sin(lat) |
204 | // |
205 | // Thus we can derive: |
206 | // lat = arcsin (z) |
207 | // lon = arctan (y/x) |
208 | for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { |
209 | const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize(); |
210 | out[pnt] = aiVector3D((std::atan2(diff.z, diff.y) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F, |
211 | (std::asin (diff.x) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); |
212 | } |
213 | } |
214 | else if (axis * base_axis_y >= angle_epsilon) { |
215 | // ... just the same again |
216 | for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { |
217 | const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize(); |
218 | out[pnt] = aiVector3D((std::atan2(diff.x, diff.z) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F, |
219 | (std::asin (diff.y) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); |
220 | } |
221 | } |
222 | else if (axis * base_axis_z >= angle_epsilon) { |
223 | // ... just the same again |
224 | for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { |
225 | const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize(); |
226 | out[pnt] = aiVector3D((std::atan2(diff.y, diff.x) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F, |
227 | (std::asin (diff.z) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); |
228 | } |
229 | } |
230 | // slower code path in case the mapping axis is not one of the coordinate system axes |
231 | else { |
232 | aiMatrix4x4 mTrafo; |
233 | aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo); |
234 | |
235 | // again the same, except we're applying a transformation now |
236 | for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { |
237 | const aiVector3D diff = ((mTrafo*mesh->mVertices[pnt])-center).Normalize(); |
238 | out[pnt] = aiVector3D((std::atan2(diff.y, diff.x) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F, |
239 | (std::asin(diff.z) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); |
240 | } |
241 | } |
242 | |
243 | |
244 | // Now find and remove UV seams. A seam occurs if a face has a tcoord |
245 | // close to zero on the one side, and a tcoord close to one on the |
246 | // other side. |
247 | RemoveUVSeams(mesh,out); |
248 | } |
249 | |
250 | // ------------------------------------------------------------------------------------------------ |
251 | void ComputeUVMappingProcess::ComputeCylinderMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out) |
252 | { |
253 | aiVector3D center, min, max; |
254 | |
255 | // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ... |
256 | // currently the mapping axis will always be one of x,y,z, except if the |
257 | // PretransformVertices step is used (it transforms the meshes into worldspace, |
258 | // thus changing the mapping axis) |
259 | if (axis * base_axis_x >= angle_epsilon) { |
260 | FindMeshCenter(mesh, center, min, max); |
261 | const ai_real diff = max.x - min.x; |
262 | |
263 | // If the main axis is 'z', the z coordinate of a point 'p' is mapped |
264 | // directly to the texture V axis. The other axis is derived from |
265 | // the angle between ( p.x - c.x, p.y - c.y ) and (1,0), where |
266 | // 'c' is the center point of the mesh. |
267 | for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { |
268 | const aiVector3D& pos = mesh->mVertices[pnt]; |
269 | aiVector3D& uv = out[pnt]; |
270 | |
271 | uv.y = (pos.x - min.x) / diff; |
272 | uv.x = (std::atan2( pos.z - center.z, pos.y - center.y) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI; |
273 | } |
274 | } |
275 | else if (axis * base_axis_y >= angle_epsilon) { |
276 | FindMeshCenter(mesh, center, min, max); |
277 | const ai_real diff = max.y - min.y; |
278 | |
279 | // just the same ... |
280 | for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { |
281 | const aiVector3D& pos = mesh->mVertices[pnt]; |
282 | aiVector3D& uv = out[pnt]; |
283 | |
284 | uv.y = (pos.y - min.y) / diff; |
285 | uv.x = (std::atan2( pos.x - center.x, pos.z - center.z) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI; |
286 | } |
287 | } |
288 | else if (axis * base_axis_z >= angle_epsilon) { |
289 | FindMeshCenter(mesh, center, min, max); |
290 | const ai_real diff = max.z - min.z; |
291 | |
292 | // just the same ... |
293 | for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { |
294 | const aiVector3D& pos = mesh->mVertices[pnt]; |
295 | aiVector3D& uv = out[pnt]; |
296 | |
297 | uv.y = (pos.z - min.z) / diff; |
298 | uv.x = (std::atan2( pos.y - center.y, pos.x - center.x) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI; |
299 | } |
300 | } |
301 | // slower code path in case the mapping axis is not one of the coordinate system axes |
302 | else { |
303 | aiMatrix4x4 mTrafo; |
304 | aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo); |
305 | FindMeshCenterTransformed(mesh, center, min, max,mTrafo); |
306 | const ai_real diff = max.y - min.y; |
307 | |
308 | // again the same, except we're applying a transformation now |
309 | for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt){ |
310 | const aiVector3D pos = mTrafo* mesh->mVertices[pnt]; |
311 | aiVector3D& uv = out[pnt]; |
312 | |
313 | uv.y = (pos.y - min.y) / diff; |
314 | uv.x = (std::atan2( pos.x - center.x, pos.z - center.z) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI; |
315 | } |
316 | } |
317 | |
318 | // Now find and remove UV seams. A seam occurs if a face has a tcoord |
319 | // close to zero on the one side, and a tcoord close to one on the |
320 | // other side. |
321 | RemoveUVSeams(mesh,out); |
322 | } |
323 | |
324 | // ------------------------------------------------------------------------------------------------ |
325 | void ComputeUVMappingProcess::ComputePlaneMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out) |
326 | { |
327 | ai_real diffu,diffv; |
328 | aiVector3D center, min, max; |
329 | |
330 | // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ... |
331 | // currently the mapping axis will always be one of x,y,z, except if the |
332 | // PretransformVertices step is used (it transforms the meshes into worldspace, |
333 | // thus changing the mapping axis) |
334 | if (axis * base_axis_x >= angle_epsilon) { |
335 | FindMeshCenter(mesh, center, min, max); |
336 | diffu = max.z - min.z; |
337 | diffv = max.y - min.y; |
338 | |
339 | for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { |
340 | const aiVector3D& pos = mesh->mVertices[pnt]; |
341 | out[pnt].Set((pos.z - min.z) / diffu,(pos.y - min.y) / diffv,0.0); |
342 | } |
343 | } |
344 | else if (axis * base_axis_y >= angle_epsilon) { |
345 | FindMeshCenter(mesh, center, min, max); |
346 | diffu = max.x - min.x; |
347 | diffv = max.z - min.z; |
348 | |
349 | for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { |
350 | const aiVector3D& pos = mesh->mVertices[pnt]; |
351 | out[pnt].Set((pos.x - min.x) / diffu,(pos.z - min.z) / diffv,0.0); |
352 | } |
353 | } |
354 | else if (axis * base_axis_z >= angle_epsilon) { |
355 | FindMeshCenter(mesh, center, min, max); |
356 | diffu = max.y - min.y; |
357 | diffv = max.z - min.z; |
358 | |
359 | for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { |
360 | const aiVector3D& pos = mesh->mVertices[pnt]; |
361 | out[pnt].Set((pos.y - min.y) / diffu,(pos.x - min.x) / diffv,0.0); |
362 | } |
363 | } |
364 | // slower code path in case the mapping axis is not one of the coordinate system axes |
365 | else |
366 | { |
367 | aiMatrix4x4 mTrafo; |
368 | aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo); |
369 | FindMeshCenterTransformed(mesh, center, min, max,mTrafo); |
370 | diffu = max.x - min.x; |
371 | diffv = max.z - min.z; |
372 | |
373 | // again the same, except we're applying a transformation now |
374 | for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { |
375 | const aiVector3D pos = mTrafo * mesh->mVertices[pnt]; |
376 | out[pnt].Set((pos.x - min.x) / diffu,(pos.z - min.z) / diffv,0.0); |
377 | } |
378 | } |
379 | |
380 | // shouldn't be necessary to remove UV seams ... |
381 | } |
382 | |
383 | // ------------------------------------------------------------------------------------------------ |
384 | void ComputeUVMappingProcess::ComputeBoxMapping( aiMesh*, aiVector3D* ) |
385 | { |
386 | DefaultLogger::get()->error("Mapping type currently not implemented" ); |
387 | } |
388 | |
389 | // ------------------------------------------------------------------------------------------------ |
390 | void ComputeUVMappingProcess::Execute( aiScene* pScene) |
391 | { |
392 | DefaultLogger::get()->debug("GenUVCoordsProcess begin" ); |
393 | char buffer[1024]; |
394 | |
395 | if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) |
396 | throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here" ); |
397 | |
398 | std::list<MappingInfo> mappingStack; |
399 | |
400 | /* Iterate through all materials and search for non-UV mapped textures |
401 | */ |
402 | for (unsigned int i = 0; i < pScene->mNumMaterials;++i) |
403 | { |
404 | mappingStack.clear(); |
405 | aiMaterial* mat = pScene->mMaterials[i]; |
406 | for (unsigned int a = 0; a < mat->mNumProperties;++a) |
407 | { |
408 | aiMaterialProperty* prop = mat->mProperties[a]; |
409 | if (!::strcmp( prop->mKey.data, "$tex.mapping" )) |
410 | { |
411 | aiTextureMapping& mapping = *((aiTextureMapping*)prop->mData); |
412 | if (aiTextureMapping_UV != mapping) |
413 | { |
414 | if (!DefaultLogger::isNullLogger()) |
415 | { |
416 | ai_snprintf(buffer, 1024, "Found non-UV mapped texture (%s,%u). Mapping type: %s" , |
417 | TextureTypeToString((aiTextureType)prop->mSemantic),prop->mIndex, |
418 | MappingTypeToString(mapping)); |
419 | |
420 | DefaultLogger::get()->info(buffer); |
421 | } |
422 | |
423 | if (aiTextureMapping_OTHER == mapping) |
424 | continue; |
425 | |
426 | MappingInfo info (mapping); |
427 | |
428 | // Get further properties - currently only the major axis |
429 | for (unsigned int a2 = 0; a2 < mat->mNumProperties;++a2) |
430 | { |
431 | aiMaterialProperty* prop2 = mat->mProperties[a2]; |
432 | if (prop2->mSemantic != prop->mSemantic || prop2->mIndex != prop->mIndex) |
433 | continue; |
434 | |
435 | if ( !::strcmp( prop2->mKey.data, "$tex.mapaxis" )) { |
436 | info.axis = *((aiVector3D*)prop2->mData); |
437 | break; |
438 | } |
439 | } |
440 | |
441 | unsigned int idx( 99999999 ); |
442 | |
443 | // Check whether we have this mapping mode already |
444 | std::list<MappingInfo>::iterator it = std::find (mappingStack.begin(),mappingStack.end(), info); |
445 | if (mappingStack.end() != it) |
446 | { |
447 | idx = (*it).uv; |
448 | } |
449 | else |
450 | { |
451 | /* We have found a non-UV mapped texture. Now |
452 | * we need to find all meshes using this material |
453 | * that we can compute UV channels for them. |
454 | */ |
455 | for (unsigned int m = 0; m < pScene->mNumMeshes;++m) |
456 | { |
457 | aiMesh* mesh = pScene->mMeshes[m]; |
458 | unsigned int outIdx = 0; |
459 | if ( mesh->mMaterialIndex != i || ( outIdx = FindEmptyUVChannel(mesh) ) == UINT_MAX || |
460 | !mesh->mNumVertices) |
461 | { |
462 | continue; |
463 | } |
464 | |
465 | // Allocate output storage |
466 | aiVector3D* p = mesh->mTextureCoords[outIdx] = new aiVector3D[mesh->mNumVertices]; |
467 | |
468 | switch (mapping) |
469 | { |
470 | case aiTextureMapping_SPHERE: |
471 | ComputeSphereMapping(mesh,info.axis,p); |
472 | break; |
473 | case aiTextureMapping_CYLINDER: |
474 | ComputeCylinderMapping(mesh,info.axis,p); |
475 | break; |
476 | case aiTextureMapping_PLANE: |
477 | ComputePlaneMapping(mesh,info.axis,p); |
478 | break; |
479 | case aiTextureMapping_BOX: |
480 | ComputeBoxMapping(mesh,p); |
481 | break; |
482 | default: |
483 | ai_assert(false); |
484 | } |
485 | if (m && idx != outIdx) |
486 | { |
487 | DefaultLogger::get()->warn("UV index mismatch. Not all meshes assigned to " |
488 | "this material have equal numbers of UV channels. The UV index stored in " |
489 | "the material structure does therefore not apply for all meshes. " ); |
490 | } |
491 | idx = outIdx; |
492 | } |
493 | info.uv = idx; |
494 | mappingStack.push_back(info); |
495 | } |
496 | |
497 | // Update the material property list |
498 | mapping = aiTextureMapping_UV; |
499 | ((aiMaterial*)mat)->AddProperty(&idx,1,AI_MATKEY_UVWSRC(prop->mSemantic,prop->mIndex)); |
500 | } |
501 | } |
502 | } |
503 | } |
504 | DefaultLogger::get()->debug("GenUVCoordsProcess finished" ); |
505 | } |
506 | |