1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2017, assimp team
6
7All rights reserved.
8
9Redistribution and use of this software in source and binary forms,
10with or without modification, are permitted provided that the
11following 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
27THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39----------------------------------------------------------------------
40*/
41
42/** @file MaterialSystem.cpp
43 * @brief Implementation of the material system of the library
44 */
45
46#include "Hash.h"
47#include "fast_atof.h"
48#include "ParsingUtils.h"
49#include "MaterialSystem.h"
50#include <assimp/types.h>
51#include <assimp/material.h>
52#include <assimp/DefaultLogger.hpp>
53#include "Macros.h"
54
55using namespace Assimp;
56
57// ------------------------------------------------------------------------------------------------
58// Get a specific property from a material
59aiReturn aiGetMaterialProperty(const aiMaterial* pMat,
60 const char* pKey,
61 unsigned int type,
62 unsigned int index,
63 const aiMaterialProperty** pPropOut)
64{
65 ai_assert (pMat != NULL);
66 ai_assert (pKey != NULL);
67 ai_assert (pPropOut != NULL);
68
69 /* Just search for a property with exactly this name ..
70 * could be improved by hashing, but it's possibly
71 * no worth the effort (we're bound to C structures,
72 * thus std::map or derivates are not applicable. */
73 for ( unsigned int i = 0; i < pMat->mNumProperties; ++i ) {
74 aiMaterialProperty* prop = pMat->mProperties[i];
75
76 if (prop /* just for safety ... */
77 && 0 == strcmp( prop->mKey.data, pKey )
78 && (UINT_MAX == type || prop->mSemantic == type) /* UINT_MAX is a wildcard, but this is undocumented :-) */
79 && (UINT_MAX == index || prop->mIndex == index))
80 {
81 *pPropOut = pMat->mProperties[i];
82 return AI_SUCCESS;
83 }
84 }
85 *pPropOut = NULL;
86 return AI_FAILURE;
87}
88
89// ------------------------------------------------------------------------------------------------
90// Get an array of floating-point values from the material.
91aiReturn aiGetMaterialFloatArray(const aiMaterial* pMat,
92 const char* pKey,
93 unsigned int type,
94 unsigned int index,
95 ai_real* pOut,
96 unsigned int* pMax)
97{
98 ai_assert (pOut != NULL);
99 ai_assert (pMat != NULL);
100
101 const aiMaterialProperty* prop;
102 aiGetMaterialProperty(pMat,pKey,type,index, (const aiMaterialProperty**) &prop);
103 if (!prop) {
104 return AI_FAILURE;
105 }
106
107 // data is given in floats, convert to ai_real
108 unsigned int iWrite = 0;
109 if( aiPTI_Float == prop->mType || aiPTI_Buffer == prop->mType) {
110 iWrite = prop->mDataLength / sizeof(float);
111 if (pMax) {
112 iWrite = std::min(*pMax,iWrite); ;
113 }
114 for (unsigned int a = 0; a < iWrite;++a) {
115 pOut[a] = static_cast<ai_real> ( reinterpret_cast<float*>(prop->mData)[a] );
116 }
117 if (pMax) {
118 *pMax = iWrite;
119 }
120 }
121 // data is given in doubles, convert to float
122 else if( aiPTI_Double == prop->mType) {
123 iWrite = prop->mDataLength / sizeof(double);
124 if (pMax) {
125 iWrite = std::min(*pMax,iWrite); ;
126 }
127 for (unsigned int a = 0; a < iWrite;++a) {
128 pOut[a] = static_cast<ai_real> ( reinterpret_cast<double*>(prop->mData)[a] );
129 }
130 if (pMax) {
131 *pMax = iWrite;
132 }
133 }
134 // data is given in ints, convert to float
135 else if( aiPTI_Integer == prop->mType) {
136 iWrite = prop->mDataLength / sizeof(int32_t);
137 if (pMax) {
138 iWrite = std::min(*pMax,iWrite); ;
139 }
140 for (unsigned int a = 0; a < iWrite;++a) {
141 pOut[a] = static_cast<ai_real> ( reinterpret_cast<int32_t*>(prop->mData)[a] );
142 }
143 if (pMax) {
144 *pMax = iWrite;
145 }
146 }
147 // a string ... read floats separated by spaces
148 else {
149 if (pMax) {
150 iWrite = *pMax;
151 }
152 // strings are zero-terminated with a 32 bit length prefix, so this is safe
153 const char *cur = prop->mData + 4;
154 ai_assert( prop->mDataLength >= 5 );
155 ai_assert( !prop->mData[ prop->mDataLength - 1 ] );
156 for ( unsigned int a = 0; ;++a) {
157 cur = fast_atoreal_move<ai_real>(cur,pOut[a]);
158 if ( a==iWrite-1 ) {
159 break;
160 }
161 if ( !IsSpace(*cur) ) {
162 DefaultLogger::get()->error("Material property" + std::string(pKey) +
163 " is a string; failed to parse a float array out of it.");
164 return AI_FAILURE;
165 }
166 }
167
168 if (pMax) {
169 *pMax = iWrite;
170 }
171 }
172 return AI_SUCCESS;
173}
174
175// ------------------------------------------------------------------------------------------------
176// Get an array if integers from the material
177aiReturn aiGetMaterialIntegerArray(const aiMaterial* pMat,
178 const char* pKey,
179 unsigned int type,
180 unsigned int index,
181 int* pOut,
182 unsigned int* pMax)
183{
184 ai_assert (pOut != NULL);
185 ai_assert (pMat != NULL);
186
187 const aiMaterialProperty* prop;
188 aiGetMaterialProperty(pMat,pKey,type,index,(const aiMaterialProperty**) &prop);
189 if (!prop) {
190 return AI_FAILURE;
191 }
192
193 // data is given in ints, simply copy it
194 unsigned int iWrite = 0;
195 if( aiPTI_Integer == prop->mType || aiPTI_Buffer == prop->mType) {
196 iWrite = prop->mDataLength / sizeof(int32_t);
197 if (pMax) {
198 iWrite = std::min(*pMax,iWrite); ;
199 }
200 for (unsigned int a = 0; a < iWrite;++a) {
201 pOut[a] = static_cast<int>(reinterpret_cast<int32_t*>(prop->mData)[a]);
202 }
203 if (pMax) {
204 *pMax = iWrite;
205 }
206 }
207 // data is given in floats convert to int
208 else if( aiPTI_Float == prop->mType) {
209 iWrite = prop->mDataLength / sizeof(float);
210 if (pMax) {
211 iWrite = std::min(*pMax,iWrite); ;
212 }
213 for (unsigned int a = 0; a < iWrite;++a) {
214 pOut[a] = static_cast<int>(reinterpret_cast<float*>(prop->mData)[a]);
215 }
216 if (pMax) {
217 *pMax = iWrite;
218 }
219 }
220 // it is a string ... no way to read something out of this
221 else {
222 if (pMax) {
223 iWrite = *pMax;
224 }
225 // strings are zero-terminated with a 32 bit length prefix, so this is safe
226 const char *cur = prop->mData+4;
227 ai_assert( prop->mDataLength >= 5 );
228 ai_assert( !prop->mData[ prop->mDataLength - 1 ] );
229 for (unsigned int a = 0; ;++a) {
230 pOut[a] = strtol10(cur,&cur);
231 if(a==iWrite-1) {
232 break;
233 }
234 if(!IsSpace(*cur)) {
235 DefaultLogger::get()->error("Material property" + std::string(pKey) +
236 " is a string; failed to parse an integer array out of it.");
237 return AI_FAILURE;
238 }
239 }
240
241 if (pMax) {
242 *pMax = iWrite;
243 }
244 }
245 return AI_SUCCESS;
246}
247
248// ------------------------------------------------------------------------------------------------
249// Get a color (3 or 4 floats) from the material
250aiReturn aiGetMaterialColor(const aiMaterial* pMat,
251 const char* pKey,
252 unsigned int type,
253 unsigned int index,
254 aiColor4D* pOut)
255{
256 unsigned int iMax = 4;
257 const aiReturn eRet = aiGetMaterialFloatArray(pMat,pKey,type,index,(ai_real*)pOut,&iMax);
258
259 // if no alpha channel is defined: set it to 1.0
260 if (3 == iMax) {
261 pOut->a = 1.0;
262 }
263
264 return eRet;
265}
266
267// ------------------------------------------------------------------------------------------------
268// Get a aiUVTransform (4 floats) from the material
269aiReturn aiGetMaterialUVTransform(const aiMaterial* pMat,
270 const char* pKey,
271 unsigned int type,
272 unsigned int index,
273 aiUVTransform* pOut)
274{
275 unsigned int iMax = 4;
276 return aiGetMaterialFloatArray(pMat,pKey,type,index,(ai_real*)pOut,&iMax);
277}
278
279// ------------------------------------------------------------------------------------------------
280// Get a string from the material
281aiReturn aiGetMaterialString(const aiMaterial* pMat,
282 const char* pKey,
283 unsigned int type,
284 unsigned int index,
285 aiString* pOut)
286{
287 ai_assert (pOut != NULL);
288
289 const aiMaterialProperty* prop;
290 aiGetMaterialProperty(pMat,pKey,type,index,(const aiMaterialProperty**)&prop);
291 if (!prop) {
292 return AI_FAILURE;
293 }
294
295 if( aiPTI_String == prop->mType) {
296 ai_assert(prop->mDataLength>=5);
297
298 // The string is stored as 32 but length prefix followed by zero-terminated UTF8 data
299 pOut->length = static_cast<unsigned int>(*reinterpret_cast<uint32_t*>(prop->mData));
300
301 ai_assert( pOut->length+1+4==prop->mDataLength );
302 ai_assert( !prop->mData[ prop->mDataLength - 1 ] );
303 memcpy(pOut->data,prop->mData+4,pOut->length+1);
304 }
305 else {
306 // TODO - implement lexical cast as well
307 DefaultLogger::get()->error("Material property" + std::string(pKey) +
308 " was found, but is no string" );
309 return AI_FAILURE;
310 }
311 return AI_SUCCESS;
312}
313
314// ------------------------------------------------------------------------------------------------
315// Get the number of textures on a particular texture stack
316ASSIMP_API unsigned int aiGetMaterialTextureCount(const C_STRUCT aiMaterial* pMat,
317 C_ENUM aiTextureType type)
318{
319 ai_assert (pMat != NULL);
320
321 // Textures are always stored with ascending indices (ValidateDS provides a check, so we don't need to do it again)
322 unsigned int max = 0;
323 for (unsigned int i = 0; i < pMat->mNumProperties;++i) {
324 aiMaterialProperty* prop = pMat->mProperties[i];
325
326 if ( prop /* just a sanity check ... */
327 && 0 == strcmp( prop->mKey.data, _AI_MATKEY_TEXTURE_BASE )
328 && prop->mSemantic == type) {
329
330 max = std::max(max,prop->mIndex+1);
331 }
332 }
333 return max;
334}
335
336// ------------------------------------------------------------------------------------------------
337aiReturn aiGetMaterialTexture(const C_STRUCT aiMaterial* mat,
338 aiTextureType type,
339 unsigned int index,
340 C_STRUCT aiString* path,
341 aiTextureMapping* _mapping /*= NULL*/,
342 unsigned int* uvindex /*= NULL*/,
343 ai_real* blend /*= NULL*/,
344 aiTextureOp* op /*= NULL*/,
345 aiTextureMapMode* mapmode /*= NULL*/,
346 unsigned int* flags /*= NULL*/
347 )
348{
349 ai_assert(NULL != mat && NULL != path);
350
351 // Get the path to the texture
352 if (AI_SUCCESS != aiGetMaterialString(mat,AI_MATKEY_TEXTURE(type,index),path)) {
353 return AI_FAILURE;
354 }
355 // Determine mapping type
356 aiTextureMapping mapping = aiTextureMapping_UV;
357 aiGetMaterialInteger(mat,AI_MATKEY_MAPPING(type,index),(int*)&mapping);
358 if (_mapping)
359 *_mapping = mapping;
360
361 // Get UV index
362 if (aiTextureMapping_UV == mapping && uvindex) {
363 aiGetMaterialInteger(mat,AI_MATKEY_UVWSRC(type,index),(int*)uvindex);
364 }
365 // Get blend factor
366 if (blend) {
367 aiGetMaterialFloat(mat,AI_MATKEY_TEXBLEND(type,index),blend);
368 }
369 // Get texture operation
370 if (op){
371 aiGetMaterialInteger(mat,AI_MATKEY_TEXOP(type,index),(int*)op);
372 }
373 // Get texture mapping modes
374 if (mapmode) {
375 aiGetMaterialInteger(mat,AI_MATKEY_MAPPINGMODE_U(type,index),(int*)&mapmode[0]);
376 aiGetMaterialInteger(mat,AI_MATKEY_MAPPINGMODE_V(type,index),(int*)&mapmode[1]);
377 }
378 // Get texture flags
379 if (flags){
380 aiGetMaterialInteger(mat,AI_MATKEY_TEXFLAGS(type,index),(int*)flags);
381 }
382 return AI_SUCCESS;
383}
384
385static const unsigned int DefaultNumAllocated = 5;
386
387// ------------------------------------------------------------------------------------------------
388// Construction. Actually the one and only way to get an aiMaterial instance
389aiMaterial::aiMaterial()
390: mProperties( NULL )
391, mNumProperties( 0 )
392, mNumAllocated( DefaultNumAllocated ) {
393 // Allocate 5 entries by default
394 mProperties = new aiMaterialProperty*[ DefaultNumAllocated ];
395}
396
397// ------------------------------------------------------------------------------------------------
398aiMaterial::~aiMaterial()
399{
400 Clear();
401
402 delete[] mProperties;
403}
404
405// ------------------------------------------------------------------------------------------------
406void aiMaterial::Clear()
407{
408 for (unsigned int i = 0; i < mNumProperties;++i) {
409 // delete this entry
410 delete mProperties[i];
411 AI_DEBUG_INVALIDATE_PTR(mProperties[i]);
412 }
413 mNumProperties = 0;
414
415 // The array remains allocated, we just invalidated its contents
416}
417
418// ------------------------------------------------------------------------------------------------
419aiReturn aiMaterial::RemoveProperty (const char* pKey,unsigned int type,
420 unsigned int index
421 )
422{
423 ai_assert(NULL != pKey);
424
425 for (unsigned int i = 0; i < mNumProperties;++i) {
426 aiMaterialProperty* prop = mProperties[i];
427
428 if (prop && !strcmp( prop->mKey.data, pKey ) &&
429 prop->mSemantic == type && prop->mIndex == index)
430 {
431 // Delete this entry
432 delete mProperties[i];
433
434 // collapse the array behind --.
435 --mNumProperties;
436 for (unsigned int a = i; a < mNumProperties;++a) {
437 mProperties[a] = mProperties[a+1];
438 }
439 return AI_SUCCESS;
440 }
441 }
442
443 return AI_FAILURE;
444}
445
446// ------------------------------------------------------------------------------------------------
447aiReturn aiMaterial::AddBinaryProperty (const void* pInput,
448 unsigned int pSizeInBytes,
449 const char* pKey,
450 unsigned int type,
451 unsigned int index,
452 aiPropertyTypeInfo pType
453 )
454{
455 ai_assert (pInput != NULL);
456 ai_assert (pKey != NULL);
457 ai_assert (0 != pSizeInBytes);
458
459 if ( 0 == pSizeInBytes ) {
460
461 }
462 // first search the list whether there is already an entry with this key
463 unsigned int iOutIndex = UINT_MAX;
464 for (unsigned int i = 0; i < mNumProperties;++i) {
465 aiMaterialProperty* prop = mProperties[i];
466
467 if (prop /* just for safety */ && !strcmp( prop->mKey.data, pKey ) &&
468 prop->mSemantic == type && prop->mIndex == index){
469
470 delete mProperties[i];
471 iOutIndex = i;
472 }
473 }
474
475 // Allocate a new material property
476 aiMaterialProperty* pcNew = new aiMaterialProperty();
477
478 // .. and fill it
479 pcNew->mType = pType;
480 pcNew->mSemantic = type;
481 pcNew->mIndex = index;
482
483 pcNew->mDataLength = pSizeInBytes;
484 pcNew->mData = new char[pSizeInBytes];
485 memcpy (pcNew->mData,pInput,pSizeInBytes);
486
487 pcNew->mKey.length = ::strlen(pKey);
488 ai_assert ( MAXLEN > pcNew->mKey.length);
489 strcpy( pcNew->mKey.data, pKey );
490
491 if (UINT_MAX != iOutIndex) {
492 mProperties[iOutIndex] = pcNew;
493 return AI_SUCCESS;
494 }
495
496 // resize the array ... double the storage allocated
497 if (mNumProperties == mNumAllocated) {
498 const unsigned int iOld = mNumAllocated;
499 mNumAllocated *= 2;
500
501 aiMaterialProperty** ppTemp;
502 try {
503 ppTemp = new aiMaterialProperty*[mNumAllocated];
504 } catch (std::bad_alloc&) {
505 delete pcNew;
506 return AI_OUTOFMEMORY;
507 }
508
509 // just copy all items over; then replace the old array
510 memcpy (ppTemp,mProperties,iOld * sizeof(void*));
511
512 delete[] mProperties;
513 mProperties = ppTemp;
514 }
515 // push back ...
516 mProperties[mNumProperties++] = pcNew;
517 return AI_SUCCESS;
518}
519
520// ------------------------------------------------------------------------------------------------
521aiReturn aiMaterial::AddProperty (const aiString* pInput,
522 const char* pKey,
523 unsigned int type,
524 unsigned int index)
525{
526 // We don't want to add the whole buffer .. write a 32 bit length
527 // prefix followed by the zero-terminated UTF8 string.
528 // (HACK) I don't want to break the ABI now, but we definitely
529 // ought to change aiString::mLength to uint32_t one day.
530 if (sizeof(size_t) == 8) {
531 aiString copy = *pInput;
532 uint32_t* s = reinterpret_cast<uint32_t*>(&copy.length);
533 s[1] = static_cast<uint32_t>(pInput->length);
534
535 return AddBinaryProperty(s+1,
536 static_cast<unsigned int>(pInput->length+1+4),
537 pKey,
538 type,
539 index,
540 aiPTI_String);
541 }
542 ai_assert(sizeof(size_t)==4);
543 return AddBinaryProperty(pInput,
544 static_cast<unsigned int>(pInput->length+1+4),
545 pKey,
546 type,
547 index,
548 aiPTI_String);
549}
550
551// ------------------------------------------------------------------------------------------------
552uint32_t Assimp::ComputeMaterialHash(const aiMaterial* mat, bool includeMatName /*= false*/)
553{
554 uint32_t hash = 1503; // magic start value, chosen to be my birthday :-)
555 for ( unsigned int i = 0; i < mat->mNumProperties; ++i ) {
556 aiMaterialProperty* prop;
557
558 // Exclude all properties whose first character is '?' from the hash
559 // See doc for aiMaterialProperty.
560 if ((prop = mat->mProperties[i]) && (includeMatName || prop->mKey.data[0] != '?')) {
561
562 hash = SuperFastHash(prop->mKey.data,(unsigned int)prop->mKey.length,hash);
563 hash = SuperFastHash(prop->mData,prop->mDataLength,hash);
564
565 // Combine the semantic and the index with the hash
566 hash = SuperFastHash((const char*)&prop->mSemantic,sizeof(unsigned int),hash);
567 hash = SuperFastHash((const char*)&prop->mIndex,sizeof(unsigned int),hash);
568 }
569 }
570 return hash;
571}
572
573// ------------------------------------------------------------------------------------------------
574void aiMaterial::CopyPropertyList(aiMaterial* pcDest,
575 const aiMaterial* pcSrc
576 )
577{
578 ai_assert(NULL != pcDest);
579 ai_assert(NULL != pcSrc);
580
581 unsigned int iOldNum = pcDest->mNumProperties;
582 pcDest->mNumAllocated += pcSrc->mNumAllocated;
583 pcDest->mNumProperties += pcSrc->mNumProperties;
584
585 aiMaterialProperty** pcOld = pcDest->mProperties;
586 pcDest->mProperties = new aiMaterialProperty*[pcDest->mNumAllocated];
587
588 if (iOldNum && pcOld) {
589 for (unsigned int i = 0; i < iOldNum;++i) {
590 pcDest->mProperties[i] = pcOld[i];
591 }
592 }
593
594 if ( pcOld ) {
595 delete[] pcOld;
596 }
597
598 for (unsigned int i = iOldNum; i< pcDest->mNumProperties;++i) {
599 aiMaterialProperty* propSrc = pcSrc->mProperties[i];
600
601 // search whether we have already a property with this name -> if yes, overwrite it
602 aiMaterialProperty* prop;
603 for ( unsigned int q = 0; q < iOldNum; ++q ) {
604 prop = pcDest->mProperties[q];
605 if (prop /* just for safety */ && prop->mKey == propSrc->mKey && prop->mSemantic == propSrc->mSemantic
606 && prop->mIndex == propSrc->mIndex) {
607 delete prop;
608
609 // collapse the whole array ...
610 memmove(&pcDest->mProperties[q],&pcDest->mProperties[q+1],i-q);
611 i--;
612 pcDest->mNumProperties--;
613 }
614 }
615
616 // Allocate the output property and copy the source property
617 prop = pcDest->mProperties[i] = new aiMaterialProperty();
618 prop->mKey = propSrc->mKey;
619 prop->mDataLength = propSrc->mDataLength;
620 prop->mType = propSrc->mType;
621 prop->mSemantic = propSrc->mSemantic;
622 prop->mIndex = propSrc->mIndex;
623
624 prop->mData = new char[propSrc->mDataLength];
625 memcpy(prop->mData,propSrc->mData,prop->mDataLength);
626 }
627}
628