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
43
44#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER
45
46#include "OgreImporter.h"
47#include "TinyFormatter.h"
48#include <assimp/material.h>
49#include <assimp/scene.h>
50#include <assimp/DefaultLogger.hpp>
51#include "fast_atof.h"
52
53#include <vector>
54#include <sstream>
55#include <memory>
56
57using namespace std;
58
59namespace Assimp
60{
61namespace Ogre
62{
63
64static const string partComment = "//";
65static const string partBlockStart = "{";
66static const string partBlockEnd = "}";
67
68void OgreImporter::ReadMaterials(const std::string &pFile, Assimp::IOSystem *pIOHandler, aiScene *pScene, Mesh *mesh)
69{
70 std::vector<aiMaterial*> materials;
71
72 // Create materials that can be found and parsed via the IOSystem.
73 for (size_t i=0, len=mesh->NumSubMeshes(); i<len; ++i)
74 {
75 SubMesh *submesh = mesh->GetSubMesh(i);
76 if (submesh && !submesh->materialRef.empty())
77 {
78 aiMaterial *material = ReadMaterial(pFile, pIOHandler, submesh->materialRef);
79 if (material)
80 {
81 submesh->materialIndex = static_cast<int>(materials.size());
82 materials.push_back(material);
83 }
84 }
85 }
86
87 AssignMaterials(pScene, materials);
88}
89
90void OgreImporter::ReadMaterials(const std::string &pFile, Assimp::IOSystem *pIOHandler, aiScene *pScene, MeshXml *mesh)
91{
92 std::vector<aiMaterial*> materials;
93
94 // Create materials that can be found and parsed via the IOSystem.
95 for (size_t i=0, len=mesh->NumSubMeshes(); i<len; ++i)
96 {
97 SubMeshXml *submesh = mesh->GetSubMesh( static_cast<uint16_t>(i));
98 if (submesh && !submesh->materialRef.empty())
99 {
100 aiMaterial *material = ReadMaterial(pFile, pIOHandler, submesh->materialRef);
101 if (material)
102 {
103 submesh->materialIndex = static_cast<int>(materials.size());
104 materials.push_back(material);
105 }
106 }
107 }
108
109 AssignMaterials(pScene, materials);
110}
111
112void OgreImporter::AssignMaterials(aiScene *pScene, std::vector<aiMaterial*> &materials)
113{
114 pScene->mNumMaterials = static_cast<unsigned int>(materials.size());
115 if (pScene->mNumMaterials > 0)
116 {
117 pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials];
118 for(size_t i=0;i<pScene->mNumMaterials; ++i) {
119 pScene->mMaterials[i] = materials[i];
120 }
121 }
122}
123
124aiMaterial* OgreImporter::ReadMaterial(const std::string &pFile, Assimp::IOSystem *pIOHandler, const std::string &materialName)
125{
126 if (materialName.empty()) {
127 return 0;
128 }
129
130 // Full reference and examples of Ogre Material Script
131 // can be found from http://www.ogre3d.org/docs/manual/manual_14.html
132
133 /*and here is another one:
134
135 import * from abstract_base_passes_depth.material
136 import * from abstract_base.material
137 import * from mat_shadow_caster.material
138 import * from mat_character_singlepass.material
139
140 material hero/hair/caster : mat_shadow_caster_skin_areject
141 {
142 set $diffuse_map "hero_hair_alpha_c.dds"
143 }
144
145 material hero/hair_alpha : mat_char_cns_singlepass_areject_4weights
146 {
147 set $diffuse_map "hero_hair_alpha_c.dds"
148 set $specular_map "hero_hair_alpha_s.dds"
149 set $normal_map "hero_hair_alpha_n.dds"
150 set $light_map "black_lightmap.dds"
151
152 set $shadow_caster_material "hero/hair/caster"
153 }
154 */
155
156 stringstream ss;
157
158 // Scope for scopre_ptr auto release
159 {
160 /* There are three .material options in priority order:
161 1) File with the material name (materialName)
162 2) File with the mesh files base name (pFile)
163 3) Optional user defined material library file (m_userDefinedMaterialLibFile) */
164 std::vector<string> potentialFiles;
165 potentialFiles.push_back(materialName + ".material");
166 potentialFiles.push_back(pFile.substr(0, pFile.rfind(".mesh")) + ".material");
167 if (!m_userDefinedMaterialLibFile.empty())
168 potentialFiles.push_back(m_userDefinedMaterialLibFile);
169
170 IOStream *materialFile = 0;
171 for(size_t i=0; i<potentialFiles.size(); ++i)
172 {
173 materialFile = pIOHandler->Open(potentialFiles[i]);
174 if (materialFile) {
175 break;
176 }
177 DefaultLogger::get()->debug(Formatter::format() << "Source file for material '" << materialName << "' " << potentialFiles[i] << " does not exist");
178 }
179 if (!materialFile)
180 {
181 DefaultLogger::get()->error(Formatter::format() << "Failed to find source file for material '" << materialName << "'");
182 return 0;
183 }
184
185 std::unique_ptr<IOStream> stream(materialFile);
186 if (stream->FileSize() == 0)
187 {
188 DefaultLogger::get()->warn(Formatter::format() << "Source file for material '" << materialName << "' is empty (size is 0 bytes)");
189 return 0;
190 }
191
192 // Read bytes
193 vector<char> data(stream->FileSize());
194 stream->Read(&data[0], stream->FileSize(), 1);
195
196 // Convert to UTF-8 and terminate the string for ss
197 BaseImporter::ConvertToUTF8(data);
198 data.push_back('\0');
199
200 ss << &data[0];
201 }
202
203 DefaultLogger::get()->debug("Reading material '" + materialName + "'");
204
205 aiMaterial *material = new aiMaterial();
206 m_textures.clear();
207
208 aiString ts(materialName);
209 material->AddProperty(&ts, AI_MATKEY_NAME);
210
211 // The stringstream will push words from a line until newline.
212 // It will also trim whitespace from line start and between words.
213 string linePart;
214 ss >> linePart;
215
216 const string partMaterial = "material";
217 const string partTechnique = "technique";
218
219 while(!ss.eof())
220 {
221 // Skip commented lines
222 if (linePart == partComment)
223 {
224 NextAfterNewLine(ss, linePart);
225 continue;
226 }
227 if (linePart != partMaterial)
228 {
229 ss >> linePart;
230 continue;
231 }
232
233 ss >> linePart;
234 if (linePart != materialName)
235 {
236 //DefaultLogger::get()->debug(Formatter::format() << "Found material '" << linePart << "' that does not match at index " << ss.tellg());
237 ss >> linePart;
238 continue;
239 }
240
241 NextAfterNewLine(ss, linePart);
242 if (linePart != partBlockStart)
243 {
244 DefaultLogger::get()->error(Formatter::format() << "Invalid material: block start missing near index " << ss.tellg());
245 return material;
246 }
247
248 DefaultLogger::get()->debug("material '" + materialName + "'");
249
250 while(linePart != partBlockEnd)
251 {
252 // Proceed to the first technique
253 ss >> linePart;
254
255 if (linePart == partTechnique)
256 {
257 string techniqueName = SkipLine(ss);
258 ReadTechnique(Trim(techniqueName), ss, material);
259 }
260
261 // Read information from a custom material
262 /** @todo This "set $x y" does not seem to be a official Ogre material system feature.
263 Materials can inherit other materials and override texture units by using the (unique)
264 parent texture unit name in your cloned material.
265 This is not yet supported and below code is probably some hack from the original
266 author of this Ogre importer. Should be removed? */
267 if (linePart=="set")
268 {
269 ss >> linePart;
270 if (linePart=="$specular")//todo load this values:
271 {
272 }
273 else if (linePart=="$diffuse")
274 {
275 }
276 else if (linePart=="$ambient")
277 {
278 }
279 else if (linePart=="$colormap")
280 {
281 ss >> linePart;
282 aiString ts(linePart);
283 material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0));
284 }
285 else if (linePart=="$normalmap")
286 {
287 ss >> linePart;
288 aiString ts(linePart);
289 material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0));
290 }
291 else if (linePart=="$shininess_strength")
292 {
293 ss >> linePart;
294 float Shininess = fast_atof(linePart.c_str());
295 material->AddProperty(&Shininess, 1, AI_MATKEY_SHININESS_STRENGTH);
296 }
297 else if (linePart=="$shininess_exponent")
298 {
299 ss >> linePart;
300 float Shininess = fast_atof(linePart.c_str());
301 material->AddProperty(&Shininess, 1, AI_MATKEY_SHININESS);
302 }
303 //Properties from Venetica:
304 else if (linePart=="$diffuse_map")
305 {
306 ss >> linePart;
307 if (linePart[0] == '"')// "file" -> file
308 linePart = linePart.substr(1, linePart.size()-2);
309 aiString ts(linePart);
310 material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0));
311 }
312 else if (linePart=="$specular_map")
313 {
314 ss >> linePart;
315 if (linePart[0] == '"')// "file" -> file
316 linePart = linePart.substr(1, linePart.size()-2);
317 aiString ts(linePart);
318 material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_SHININESS, 0));
319 }
320 else if (linePart=="$normal_map")
321 {
322 ss >> linePart;
323 if (linePart[0]=='"')// "file" -> file
324 linePart = linePart.substr(1, linePart.size()-2);
325 aiString ts(linePart);
326 material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0));
327 }
328 else if (linePart=="$light_map")
329 {
330 ss >> linePart;
331 if (linePart[0]=='"') {
332 linePart = linePart.substr(1, linePart.size() - 2);
333 }
334 aiString ts(linePart);
335 material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_LIGHTMAP, 0));
336 }
337 }
338 }
339 ss >> linePart;
340 }
341
342 return material;
343}
344
345bool OgreImporter::ReadTechnique(const std::string &techniqueName, stringstream &ss, aiMaterial *material)
346{
347 string linePart;
348 ss >> linePart;
349
350 if (linePart != partBlockStart)
351 {
352 DefaultLogger::get()->error(Formatter::format() << "Invalid material: Technique block start missing near index " << ss.tellg());
353 return false;
354 }
355
356 DefaultLogger::get()->debug(" technique '" + techniqueName + "'");
357
358 const string partPass = "pass";
359
360 while(linePart != partBlockEnd)
361 {
362 ss >> linePart;
363
364 // Skip commented lines
365 if (linePart == partComment)
366 {
367 SkipLine(ss);
368 continue;
369 }
370
371 /// @todo Techniques have other attributes than just passes.
372 if (linePart == partPass)
373 {
374 string passName = SkipLine(ss);
375 ReadPass(Trim(passName), ss, material);
376 }
377 }
378 return true;
379}
380
381bool OgreImporter::ReadPass(const std::string &passName, stringstream &ss, aiMaterial *material)
382{
383 string linePart;
384 ss >> linePart;
385
386 if (linePart != partBlockStart)
387 {
388 DefaultLogger::get()->error(Formatter::format() << "Invalid material: Pass block start missing near index " << ss.tellg());
389 return false;
390 }
391
392 DefaultLogger::get()->debug(" pass '" + passName + "'");
393
394 const string partAmbient = "ambient";
395 const string partDiffuse = "diffuse";
396 const string partSpecular = "specular";
397 const string partEmissive = "emissive";
398 const string partTextureUnit = "texture_unit";
399
400 while(linePart != partBlockEnd)
401 {
402 ss >> linePart;
403
404 // Skip commented lines
405 if (linePart == partComment)
406 {
407 SkipLine(ss);
408 continue;
409 }
410
411 // Colors
412 /// @todo Support alpha via aiColor4D.
413 if (linePart == partAmbient || linePart == partDiffuse || linePart == partSpecular || linePart == partEmissive)
414 {
415 float r, g, b;
416 ss >> r >> g >> b;
417 const aiColor3D color(r, g, b);
418
419 DefaultLogger::get()->debug(Formatter::format() << " " << linePart << " " << r << " " << g << " " << b);
420
421 if (linePart == partAmbient)
422 {
423 material->AddProperty(&color, 1, AI_MATKEY_COLOR_AMBIENT);
424 }
425 else if (linePart == partDiffuse)
426 {
427 material->AddProperty(&color, 1, AI_MATKEY_COLOR_DIFFUSE);
428 }
429 else if (linePart == partSpecular)
430 {
431 material->AddProperty(&color, 1, AI_MATKEY_COLOR_SPECULAR);
432 }
433 else if (linePart == partEmissive)
434 {
435 material->AddProperty(&color, 1, AI_MATKEY_COLOR_EMISSIVE);
436 }
437 }
438 else if (linePart == partTextureUnit)
439 {
440 string textureUnitName = SkipLine(ss);
441 ReadTextureUnit(Trim(textureUnitName), ss, material);
442 }
443 }
444 return true;
445}
446
447bool OgreImporter::ReadTextureUnit(const std::string &textureUnitName, stringstream &ss, aiMaterial *material)
448{
449 string linePart;
450 ss >> linePart;
451
452 if (linePart != partBlockStart)
453 {
454 DefaultLogger::get()->error(Formatter::format() << "Invalid material: Texture unit block start missing near index " << ss.tellg());
455 return false;
456 }
457
458 DefaultLogger::get()->debug(" texture_unit '" + textureUnitName + "'");
459
460 const string partTexture = "texture";
461 const string partTextCoordSet = "tex_coord_set";
462 const string partColorOp = "colour_op";
463
464 aiTextureType textureType = aiTextureType_NONE;
465 std::string textureRef;
466 int uvCoord = 0;
467
468 while(linePart != partBlockEnd)
469 {
470 ss >> linePart;
471
472 // Skip commented lines
473 if (linePart == partComment)
474 {
475 SkipLine(ss);
476 continue;
477 }
478
479 if (linePart == partTexture)
480 {
481 ss >> linePart;
482 textureRef = linePart;
483
484 // User defined Assimp config property to detect texture type from filename.
485 if (m_detectTextureTypeFromFilename)
486 {
487 size_t posSuffix = textureRef.find_last_of(".");
488 size_t posUnderscore = textureRef.find_last_of("_");
489
490 if (posSuffix != string::npos && posUnderscore != string::npos && posSuffix > posUnderscore)
491 {
492 string identifier = Ogre::ToLower(textureRef.substr(posUnderscore, posSuffix - posUnderscore));
493 DefaultLogger::get()->debug(Formatter::format() << "Detecting texture type from filename postfix '" << identifier << "'");
494
495 if (identifier == "_n" || identifier == "_nrm" || identifier == "_nrml" || identifier == "_normal" || identifier == "_normals" || identifier == "_normalmap")
496 {
497 textureType = aiTextureType_NORMALS;
498 }
499 else if (identifier == "_s" || identifier == "_spec" || identifier == "_specular" || identifier == "_specularmap")
500 {
501 textureType = aiTextureType_SPECULAR;
502 }
503 else if (identifier == "_l" || identifier == "_light" || identifier == "_lightmap" || identifier == "_occ" || identifier == "_occlusion")
504 {
505 textureType = aiTextureType_LIGHTMAP;
506 }
507 else if (identifier == "_disp" || identifier == "_displacement")
508 {
509 textureType = aiTextureType_DISPLACEMENT;
510 }
511 else
512 {
513 textureType = aiTextureType_DIFFUSE;
514 }
515 }
516 else
517 {
518 textureType = aiTextureType_DIFFUSE;
519 }
520 }
521 // Detect from texture unit name. This cannot be too broad as
522 // authors might give names like "LightSaber" or "NormalNinja".
523 else
524 {
525 string unitNameLower = Ogre::ToLower(textureUnitName);
526 if (unitNameLower.find("normalmap") != string::npos)
527 {
528 textureType = aiTextureType_NORMALS;
529 }
530 else if (unitNameLower.find("specularmap") != string::npos)
531 {
532 textureType = aiTextureType_SPECULAR;
533 }
534 else if (unitNameLower.find("lightmap") != string::npos)
535 {
536 textureType = aiTextureType_LIGHTMAP;
537 }
538 else if (unitNameLower.find("displacementmap") != string::npos)
539 {
540 textureType = aiTextureType_DISPLACEMENT;
541 }
542 else
543 {
544 textureType = aiTextureType_DIFFUSE;
545 }
546 }
547 }
548 else if (linePart == partTextCoordSet)
549 {
550 ss >> uvCoord;
551 }
552 /// @todo Implement
553 else if(linePart == partColorOp)
554 {
555 /*
556 ss >> linePart;
557 if("replace"==linePart)//I don't think, assimp has something for this...
558 {
559 }
560 else if("modulate"==linePart)
561 {
562 //TODO: set value
563 //material->AddProperty(aiTextureOp_Multiply)
564 }
565 */
566 }
567 }
568
569 if (textureRef.empty())
570 {
571 DefaultLogger::get()->warn("Texture reference is empty, ignoring texture_unit.");
572 return false;
573 }
574 if (textureType == aiTextureType_NONE)
575 {
576 DefaultLogger::get()->warn("Failed to detect texture type for '" + textureRef + "', ignoring texture_unit.");
577 return false;
578 }
579
580 unsigned int textureTypeIndex = m_textures[textureType];
581 m_textures[textureType]++;
582
583 DefaultLogger::get()->debug(Formatter::format() << " texture '" << textureRef << "' type " << textureType
584 << " index " << textureTypeIndex << " UV " << uvCoord);
585
586 aiString assimpTextureRef(textureRef);
587 material->AddProperty(&assimpTextureRef, AI_MATKEY_TEXTURE(textureType, textureTypeIndex));
588 material->AddProperty(&uvCoord, 1, AI_MATKEY_UVWSRC(textureType, textureTypeIndex));
589
590 return true;
591}
592
593} // Ogre
594} // Assimp
595
596#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER
597