ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvMaterialSet.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "ecvMaterialSet.h"
9 
10 #include "ecvViewportParameters.h"
11 
12 // Qt
13 #include <QFileInfo>
14 #include <QImage>
15 #include <QSet>
16 
17 // Qt5/Qt6 Compatibility
18 #include <QtCompat.h>
19 
20 // System
21 #include <set>
22 
23 ccMaterialSet::ccMaterialSet(const QString& name /*=QString()*/)
24  : std::vector<ccMaterial::CShared>(), CCShareable(), ccHObject(name) {
25  setFlagState(CC_LOCKED, true);
26 }
27 
28 int ccMaterialSet::findMaterialByName(QString mtlName) const {
29  CVLog::PrintDebug(QString("[ccMaterialSet::findMaterialByName] Query: ") +
30  mtlName);
31 
32  int i = 0;
33  for (ccMaterialSet::const_iterator it = begin(); it != end(); ++it, ++i) {
34  ccMaterial::CShared mtl = *it;
36  QString("\tmaterial #%1 name: %2").arg(i).arg(mtl->getName()));
37  if (mtl->getName() == mtlName) return i;
38  }
39 
40  return -1;
41 }
42 
43 int ccMaterialSet::findMaterialByUniqueID(QString uniqueID) const {
45  QString("[ccMaterialSet::findMaterialByUniqueID] Query: ") +
46  uniqueID);
47 
48  int i = 0;
49  for (ccMaterialSet::const_iterator it = begin(); it != end(); ++it, ++i) {
50  ccMaterial::CShared mtl = *it;
51  CVLog::PrintDebug(QString("\tmaterial #%1 ID: %2")
52  .arg(i)
53  .arg(mtl->getUniqueIdentifier()));
54  if (mtl->getUniqueIdentifier() == uniqueID) return i;
55  }
56 
57  return -1;
58 }
59 
61  bool allowDuplicateNames /*=false*/) {
62  if (!mtl) {
63  // invalid input material
64  return -1;
65  }
66 
67  // material already exists?
68  int previousIndex = findMaterialByName(mtl->getName());
69  // DGM: warning, the materials may have the same name, but they may be
70  // different in reality (other texture, etc.)!
71  if (previousIndex >= 0) {
72  const ccMaterial::CShared& previousMtl = (*this)[previousIndex];
73  if (!previousMtl->compare(*mtl)) {
74  // in fact the material is a bit different
75  previousIndex = -1;
76  if (!allowDuplicateNames) {
77  // generate a new name
78  static const unsigned MAX_ATTEMPTS = 100;
79  for (unsigned i = 1; i < MAX_ATTEMPTS; i++) {
80  QString newMtlName =
81  previousMtl->getName() + QString("_%1").arg(i);
82  if (findMaterialByName(newMtlName) < 0) {
83  // we duplicate the material and we change its name
84  ccMaterial::Shared newMtl(new ccMaterial(*mtl));
85  newMtl->setName(newMtlName);
86  mtl = newMtl;
87  break;
88  }
89  }
90  }
91  }
92  }
93  if (previousIndex >= 0 && !allowDuplicateNames) return previousIndex;
94 
95  try {
96  push_back(mtl);
97  } catch (const std::bad_alloc&) {
98  // not enough memory
99  return -1;
100  }
101 
102  return static_cast<int>(size()) - 1;
103 }
104 
105 // MTL PARSER INSPIRED FROM KIXOR.NET "objloader"
106 // (http://www.kixor.net/dev/objloader/)
108  const QString& filename,
109  ccMaterialSet& materials,
110  QStringList& errors) {
111  // open mtl file
112  QString fullPathFilename = path + QString('/') + filename;
113  QFile file(fullPathFilename);
114  if (!file.open(QFile::ReadOnly)) {
115  errors << QString("Error reading file: %1").arg(filename);
116  return false;
117  }
118 
119  // update path (if the input filename has already a relative path)
120  path = QFileInfo(fullPathFilename).absolutePath();
121 
122  QTextStream stream(&file);
123 
124  QString currentLine = stream.readLine();
125  unsigned currentLineIndex = 0;
126  ccMaterial::Shared currentMaterial(0);
127  while (!currentLine.isNull()) {
128  ++currentLineIndex;
129 
130  QStringList tokens = qtCompatSplitRegex(currentLine, "\\s+",
132 
133  // skip comments & empty lines
134  if (tokens.empty() ||
135  tokens.front().startsWith('/', Qt::CaseInsensitive) ||
136  tokens.front().startsWith('#', Qt::CaseInsensitive)) {
137  currentLine = stream.readLine();
138  continue;
139  }
140 
141  // start material
142  if (tokens.front() == "newmtl") {
143  // push the previous material (if any)
144  if (currentMaterial) {
145  materials.addMaterial(currentMaterial);
146  currentMaterial = ccMaterial::Shared(0);
147  }
148 
149  // get the name
150  QString materialName =
151  currentLine.mid(7).trimmed(); // we must take the whole
152  // line! (see OBJ filter)
153  if (materialName.isEmpty()) materialName = "undefined";
154  currentMaterial = ccMaterial::Shared(new ccMaterial(materialName));
155 
156  } else if (currentMaterial) // we already have a "current" material
157  {
158  // ambient
159  if (tokens.front() == "Ka") {
160  if (tokens.size() > 3) {
161  ecvColor::Rgbaf ambient(tokens[1].toFloat(),
162  tokens[2].toFloat(),
163  tokens[3].toFloat(), 1.0f);
164  currentMaterial->setAmbient(ambient);
165  }
166  }
167 
168  // diff
169  else if (tokens.front() == "Kd") {
170  if (tokens.size() > 3) {
171  ecvColor::Rgbaf diffuse(tokens[1].toFloat(),
172  tokens[2].toFloat(),
173  tokens[3].toFloat(), 1.0f);
174  currentMaterial->setDiffuse(diffuse);
175  }
176  }
177 
178  // specular
179  else if (tokens.front() == "Ks") {
180  if (tokens.size() > 3) {
181  ecvColor::Rgbaf specular(tokens[1].toFloat(),
182  tokens[2].toFloat(),
183  tokens[3].toFloat(), 1.0f);
184  currentMaterial->setSpecular(specular);
185  }
186  }
187 
188  // emission
189  else if (tokens.front() == "Ke") {
190  if (tokens.size() > 3) {
191  ecvColor::Rgbaf emission(tokens[1].toFloat(),
192  tokens[2].toFloat(),
193  tokens[3].toFloat(), 1.0f);
194  currentMaterial->setEmission(emission);
195  }
196  }
197 
198  // shiny
199  else if (tokens.front() == "Ns") {
200  if (tokens.size() > 1)
201  currentMaterial->setShininess(tokens[1].toFloat());
202  }
203  // transparency (inverse of)
204  else if (tokens.front() == "d") {
205  if (tokens.size() > 1) {
206  float d = tokens[1].toFloat();
207  if (d == 0) {
209  QString("Material %1 'alpha' (=d) value is 0 "
210  "(= fully transparent)")
211  .arg(currentMaterial->getName()));
212  }
213  currentMaterial->setTransparency(d);
214  }
215  }
216  // transparency
217  else if (tokens.front() == "Tr") {
218  if (tokens.size() > 1) {
219  float tr = tokens[1].toFloat();
220  if (tr == 1.0f) {
222  QString("Material %1 'transparency' (=Tr) "
223  "value is 1 (= fully transparent)")
224  .arg(currentMaterial->getName()));
225  }
226  currentMaterial->setTransparency(1.0f -
227  tokens[1].toFloat());
228  }
229  }
230  // reflection
231  else if (tokens.front() == "r") {
232  // ignored
233  // if (tokens.size() > 1)
234  // currentMaterial->reflect = tokens[1].toFloat();
235  }
236  // glossy
237  else if (tokens.front() == "sharpness") {
238  // ignored
239  // if (tokens.size() > 1)
240  // currentMaterial->glossy = tokens[1].toFloat();
241  }
242  // refract index
243  else if (tokens.front() == "Ni") {
244  // ignored
245  // if (tokens.size() > 1)
246  // currentMaterial->refract_index = tokens[1].toFloat();
247  }
248  // illumination type
249  else if (tokens.front() == "illum") {
250  if (tokens.size() > 1)
251  currentMaterial->setIllum(tokens[1].toInt());
252  }
253  // texture maps - support standard OBJ and PBR extensions
254  else if (tokens.front() == "map_Ka" || tokens.front() == "map_Kd" ||
255  tokens.front() == "map_Ks" || tokens.front() == "map_Ke" ||
256  tokens.front() == "map_d" || tokens.front() == "map_Ns" ||
257  tokens.front() == "map_Bump" ||
258  tokens.front() == "map_bump" || tokens.front() == "bump" ||
259  tokens.front() == "norm" || tokens.front() == "map_Pr" ||
260  tokens.front() == "map_Pm" || tokens.front() == "map_Ps" ||
261  tokens.front() == "map_Pc" ||
262  tokens.front() == "map_Pcr" ||
263  tokens.front() == "map_aniso" ||
264  tokens.front() == "map_disp" || tokens.front() == "disp" ||
265  tokens.front() == "refl") {
266  // Determine texture map type
268  QString mapCommand = tokens.front();
269 
270  if (mapCommand == "map_Kd") {
272  } else if (mapCommand == "map_Ka") {
274  } else if (mapCommand == "map_Ks") {
276  } else if (mapCommand == "map_Ke") {
278  } else if (mapCommand == "map_d") {
280  } else if (mapCommand == "map_Ns") {
281  // map_Ns is shininess/glossiness map
282  // Can be mapped to roughness (inverted) or used as-is
284  } else if (mapCommand == "map_Bump" ||
285  mapCommand == "map_bump" || mapCommand == "bump" ||
286  mapCommand == "norm") {
288  } else if (mapCommand == "map_Pr") {
290  } else if (mapCommand == "map_Pm") {
292  } else if (mapCommand == "map_Ps") {
294  } else if (mapCommand == "map_Pc") {
296  } else if (mapCommand == "map_Pcr") {
298  } else if (mapCommand == "map_aniso") {
300  } else if (mapCommand == "map_disp" || mapCommand == "disp") {
302  } else if (mapCommand == "refl") {
304  } else {
305  // Fallback to diffuse
307  }
308 
309  // Extract texture filename
310  // DGM: in case there's hidden or space characters at the
311  // beginning of the line...
312  int shift = currentLine.indexOf(mapCommand, 0);
313  QString textureFilename =
314  (shift + mapCommand.length() + 1 < currentLine.size()
315  ? currentLine
316  .mid(shift + mapCommand.length() + 1)
317  .trimmed()
318  : QString());
319 
320  // Filter out MTL texture options (e.g., -bm, -blendu, -blendv,
321  // -boost, -mm, -o, -s, -t, -texres, -clamp, -imfchan) These
322  // options start with '-' and may have numeric parameters
323  QStringList parts =
324  textureFilename.split(' ', QtCompat::SkipEmptyParts);
325  QString actualFilename;
326  for (const QString& part : parts) {
327  // Skip options that start with '-' and their numeric
328  // parameters
329  if (part.startsWith('-')) {
330  continue; // Skip option flag
331  }
332  // Check if this is a numeric parameter following an option
333  bool isNumber = false;
334  part.toDouble(&isNumber);
335  if (isNumber && actualFilename.isEmpty()) {
336  continue; // Skip numeric parameter
337  }
338  // This should be the actual filename
339  actualFilename = part;
340  break;
341  }
342 
343  // If we found a filename after filtering, use it
344  if (!actualFilename.isEmpty()) {
345  textureFilename = actualFilename;
346  }
347 
348  // remove any quotes around the filename (Photoscan 1.4 bug)
349  if (textureFilename.startsWith("\"")) {
350  textureFilename =
351  textureFilename.right(textureFilename.size() - 1);
352  }
353  if (textureFilename.endsWith("\"")) {
354  textureFilename =
355  textureFilename.left(textureFilename.size() - 1);
356  }
357 
358  // Normalize path separators (convert backslashes to forward
359  // slashes)
360  textureFilename = textureFilename.replace('\\', '/');
361 
362  QString fullTexName = path + QString('/') + textureFilename;
363 
364  // Load texture using new multi-texture API
365  if (!currentMaterial->loadAndSetTextureMap(mapType,
366  fullTexName)) {
367  errors << QString("Failed to load texture file: %1 (type: "
368  "%2)")
369  .arg(fullTexName)
370  .arg(mapCommand);
371  }
372  }
373  // PBR scalar parameters
374  else if (tokens.front() == "Pm") {
375  // Metallic factor
376  if (tokens.size() > 1)
377  currentMaterial->setMetallic(tokens[1].toFloat());
378  } else if (tokens.front() == "Pr") {
379  // Roughness factor
380  if (tokens.size() > 1)
381  currentMaterial->setRoughness(tokens[1].toFloat());
382  } else if (tokens.front() == "Ps") {
383  // Sheen factor
384  if (tokens.size() > 1)
385  currentMaterial->setSheen(tokens[1].toFloat());
386  } else if (tokens.front() == "Pc") {
387  // Clearcoat factor
388  if (tokens.size() > 1)
389  currentMaterial->setClearcoat(tokens[1].toFloat());
390  } else if (tokens.front() == "Pcr") {
391  // Clearcoat roughness
392  if (tokens.size() > 1)
393  currentMaterial->setClearcoatRoughness(tokens[1].toFloat());
394  } else if (tokens.front() == "aniso") {
395  // Anisotropy factor
396  if (tokens.size() > 1)
397  currentMaterial->setAnisotropy(tokens[1].toFloat());
398  } else if (tokens.front() == "Pa") {
399  // Ambient Occlusion factor
400  if (tokens.size() > 1)
401  currentMaterial->setAmbientOcclusion(tokens[1].toFloat());
402  } else {
403  errors << QString("Unknown command '%1' at line %2")
404  .arg(tokens.front())
405  .arg(currentLineIndex);
406  }
407  }
408 
409  currentLine = stream.readLine();
410  }
411 
412  file.close();
413 
414  // don't forget to push the last material!
415  if (currentMaterial) materials.addMaterial(currentMaterial);
416 
417  return true;
418 }
419 
421  const QString& baseFilename,
422  QStringList& errors) const {
423  // open mtl file
424  QString filename = path + QString('/') + baseFilename + QString(".mtl");
425  QFile file(filename);
426  if (!file.open(QFile::WriteOnly)) {
427  errors << QString("Error writing file: %1").arg(filename);
428  return false;
429  }
430  QTextStream stream(&file);
431 
432  stream << "# Generated by CLOUDVIEWER " << QtCompat::endl;
433 
434  // texture filenames already used
435  QMap<QString, QString> absFilenamesSaved;
436  QSet<QString> filenamesUsed;
437 
438  // Helper function to save texture and return relative filename
439  auto saveTextureFile = [&](const QString& absFilename,
440  size_t matIndex) -> QString {
441  if (absFilename.isEmpty()) return QString();
442 
443  // if the file has not already been saved
444  if (!absFilenamesSaved.contains(absFilename)) {
445  QFileInfo fileInfo(absFilename);
446  QString texName = fileInfo.fileName();
447  if (texName.isEmpty()) {
448  // Generate a name if filename is empty
449  texName = QString("tex_%1.jpg").arg(matIndex);
450  } else if (fileInfo.suffix().isEmpty()) {
451  texName += QString(".jpg");
452  }
453 
454  // make sure that the local filename is unique!
455  QString originalTexName = texName;
456  int counter = 0;
457  while (filenamesUsed.contains(texName)) {
458  QString baseName = fileInfo.completeBaseName();
459  QString suffix =
460  fileInfo.suffix().isEmpty() ? "jpg" : fileInfo.suffix();
461  texName = QString("%1_%2.%3")
462  .arg(baseName)
463  .arg(counter++)
464  .arg(suffix);
465  }
466  filenamesUsed.insert(texName);
467 
468  QString destFilename = path + QString('/') + texName;
469  QImage texture = ccMaterial::GetTexture(absFilename);
470  if (!texture.isNull() && texture.save(destFilename)) {
471  // Normalize path separators (use forward slashes for MTL
472  // format)
473  QString normalizedTexName = texName.replace('\\', '/');
474  absFilenamesSaved[absFilename] = normalizedTexName;
475  } else {
476  errors << QString("Failed to save the texture file '%1' to "
477  "'%2'!")
478  .arg(absFilename, destFilename);
479  return QString();
480  }
481  }
482 
483  if (absFilenamesSaved.contains(absFilename)) {
484  // Normalize path separators (use forward slashes for MTL format)
485  QString relativeFilename = absFilenamesSaved[absFilename];
486  return relativeFilename.replace('\\', '/');
487  }
488  return QString();
489  };
490 
491  size_t matIndex = 0;
492  for (ccMaterialSet::const_iterator it = begin(); it != end();
493  ++it, ++matIndex) {
494  ccMaterial::CShared mtl = *it;
495  stream << QtCompat::endl
496  << "newmtl " << mtl->getName() << QtCompat::endl;
497 
498  // Basic material colors
499  const ecvColor::Rgbaf& Ka = mtl->getAmbient();
500  const ecvColor::Rgbaf& Kd = mtl->getDiffuseFront();
501  const ecvColor::Rgbaf& Ks = mtl->getSpecular();
502  const ecvColor::Rgbaf& Ke = mtl->getEmission();
503 
504  stream << "Ka " << Ka.r << " " << Ka.g << " " << Ka.b << QtCompat::endl;
505  stream << "Kd " << Kd.r << " " << Kd.g << " " << Kd.b << QtCompat::endl;
506  stream << "Ks " << Ks.r << " " << Ks.g << " " << Ks.b << QtCompat::endl;
507 
508  // Emission color (if not black/default)
509  if (Ke.r > 0.0f || Ke.g > 0.0f || Ke.b > 0.0f) {
510  stream << "Ke " << Ke.r << " " << Ke.g << " " << Ke.b
511  << QtCompat::endl;
512  }
513 
514  // Transparency (use diffuse alpha, or ambient if diffuse is not set)
515  float transparency = Kd.a;
516  if (transparency < 1.0f) {
517  stream << "d " << transparency << QtCompat::endl;
518  }
519 
520  // Illumination model
521  stream << "illum " << mtl->getIllum() << QtCompat::endl;
522 
523  // Shininess
524  stream << "Ns " << mtl->getShininessFront() << QtCompat::endl;
525 
526  // PBR scalar parameters (only save if non-default values)
527  if (mtl->getMetallic() > 0.0f) {
528  stream << "Pm " << mtl->getMetallic() << QtCompat::endl;
529  }
530  if (mtl->getRoughness() != 0.5f) {
531  stream << "Pr " << mtl->getRoughness() << QtCompat::endl;
532  }
533  if (mtl->getSheen() > 0.0f) {
534  stream << "Ps " << mtl->getSheen() << QtCompat::endl;
535  }
536  if (mtl->getClearcoat() > 0.0f) {
537  stream << "Pc " << mtl->getClearcoat() << QtCompat::endl;
538  }
539  if (mtl->getClearcoatRoughness() > 0.0f) {
540  stream << "Pcr " << mtl->getClearcoatRoughness() << QtCompat::endl;
541  }
542  if (mtl->getAnisotropy() > 0.0f) {
543  stream << "aniso " << mtl->getAnisotropy() << QtCompat::endl;
544  }
545  if (mtl->getAmbientOcclusion() > 0.0f) {
546  stream << "Pa " << mtl->getAmbientOcclusion() << QtCompat::endl;
547  }
548 
549  // Save all texture maps using the multi-texture API
550  auto allTextures = mtl->getAllTextureFilenames();
551  for (const auto& texPair : allTextures) {
552  ccMaterial::TextureMapType mapType = texPair.first;
553  QString absFilename = texPair.second;
554 
555  if (absFilename.isEmpty()) continue;
556 
557  QString relativeFilename = saveTextureFile(absFilename, matIndex);
558  if (relativeFilename.isEmpty()) continue;
559 
560  // Map texture type to MTL command
561  QString mapCommand;
562  switch (mapType) {
564  mapCommand = "map_Kd";
565  break;
567  mapCommand = "map_Ka";
568  break;
570  mapCommand = "map_Ks";
571  break;
573  mapCommand = "map_Ke";
574  break;
576  mapCommand = "map_d";
577  break;
579  mapCommand = "map_Ns";
580  break;
582  mapCommand = "map_Bump";
583  break;
585  mapCommand = "map_Pr";
586  break;
588  mapCommand = "map_Pm";
589  break;
591  mapCommand = "map_Ps";
592  break;
594  mapCommand = "map_Pc";
595  break;
597  mapCommand = "map_Pcr";
598  break;
600  mapCommand = "map_aniso";
601  break;
603  mapCommand = "map_disp";
604  break;
606  mapCommand = "refl";
607  break;
608  default:
609  // Fallback to diffuse
610  mapCommand = "map_Kd";
611  break;
612  }
613 
614  stream << mapCommand << " " << relativeFilename << QtCompat::endl;
615  }
616 
617  // Legacy texture support (for backward compatibility)
618  // Only save if not already saved via multi-texture API
619  if (mtl->hasTexture() &&
620  !mtl->hasTextureMap(ccMaterial::TextureMapType::DIFFUSE)) {
621  QString absFilename = mtl->getTextureFilename();
622  QString relativeFilename = saveTextureFile(absFilename, matIndex);
623  if (!relativeFilename.isEmpty()) {
624  stream << "map_Kd " << relativeFilename << QtCompat::endl;
625  }
626  }
627  }
628 
629  file.close();
630 
631  return true;
632 }
633 
635  try {
636  for (ccMaterialSet::const_iterator it = source.begin();
637  it != source.end(); ++it) {
638  ccMaterial::CShared mtl = *it;
639  if (addMaterial(mtl) <= 0) {
641  QString("[ccMaterialSet::append] Material %1 couldn't "
642  "be added to material set and will be ignored")
643  .arg(mtl->getName()));
644  }
645  }
646  } catch (... /*const std::bad_alloc&*/) // out of memory
647  {
648  CVLog::Warning("[ccMaterialSet::append] Not enough memory");
649  return false;
650  }
651 
652  return true;
653 }
654 
656  ccMaterialSet* cloneSet = new ccMaterialSet(getName());
657  if (!cloneSet->append(*this)) {
658  CVLog::Warning("[ccMaterialSet::clone] Not enough memory");
659  cloneSet->release();
660  cloneSet = 0;
661  }
662 
663  return cloneSet;
664 }
665 
666 bool ccMaterialSet::toFile_MeOnly(QFile& out, short dataVersion) const {
667  assert(out.isOpen() && (out.openMode() & QIODevice::WriteOnly));
668  if (dataVersion < 37) {
669  assert(false);
670  return false;
671  }
672 
673  if (!ccHObject::toFile_MeOnly(out, dataVersion)) return false;
674 
675  // Materials count (dataVersion>=20)
676  uint32_t count = (uint32_t)size();
677  if (out.write((const char*)&count, 4) < 0) return WriteError();
678 
679  // texture filenames
680  std::set<QString> texFilenames;
681 
682  // Write each material
683  for (ccMaterialSet::const_iterator it = begin(); it != end(); ++it) {
684  ccMaterial::CShared mtl = *it;
685  mtl->toFile(out, dataVersion);
686 
687  // remember its texture as well (if any)
688  QString texFilename = mtl->getTextureFilename();
689  if (!texFilename.isEmpty()) texFilenames.insert(texFilename);
690  }
691 
692  // now save the number of textures (dataVersion>=37)
693  QDataStream outStream(&out);
694  outStream << static_cast<uint32_t>(texFilenames.size());
695  // and save the textures (dataVersion>=37)
696  {
697  for (std::set<QString>::const_iterator it = texFilenames.begin();
698  it != texFilenames.end(); ++it) {
699  outStream << *it; // name
700  outStream << ccMaterial::GetTexture(*it); // then image
701  }
702  }
703 
704  return true;
705 }
706 
708  if (empty()) {
709  return 37;
710  } else {
711  return std::max(static_cast<short>(37), at(0)->minimumFileVersion());
712  }
713 }
714 
716  short dataVersion,
717  int flags,
718  LoadedIDMap& oldToNewIDMap) {
719  if (!ccHObject::fromFile_MeOnly(in, dataVersion, flags, oldToNewIDMap))
720  return false;
721 
722  // Materials count (dataVersion>=20)
723  uint32_t count = 0;
724  ;
725  if (in.read((char*)&count, 4) < 0) return ReadError();
726  if (count == 0) return true;
727 
728  // Load each material
729  {
730  for (uint32_t i = 0; i < count; ++i) {
732  if (!mtl->fromFile(in, dataVersion, flags, oldToNewIDMap))
733  return false;
734  addMaterial(
735  mtl,
736  true); // if we load a file, we can't allow that materials
737  // are not in the same order as before!
738  }
739  }
740 
741  if (dataVersion >= 37) {
742  QDataStream inStream(&in);
743 
744  // now load the number of textures (dataVersion>=37)
745  uint32_t texCount = 0;
746  inStream >> texCount;
747  // and load the textures (dataVersion>=37)
748  {
749  for (uint32_t i = 0; i < texCount; ++i) {
750  QString filename;
751  inStream >> filename;
752  QImage image;
753  inStream >> image;
755  }
756  }
757  }
758 
759  return true;
760 }
@ CC_LOCKED
Definition: CVTypes.h:31
std::string filename
std::shared_ptr< core::Tensor > image
int size
std::string name
int count
QStringList qtCompatSplitRegex(const QString &str, const QString &pattern, Qt::SplitBehavior behavior=Qt::KeepEmptyParts)
Definition: QtCompat.h:308
virtual void release()
Decrease counter and deletes object when 0.
Definition: CVShareable.cpp:35
static bool WarningDebug(const char *format,...)
Same as Warning, but works only in Debug mode.
Definition: CVLog.cpp:167
static bool PrintDebug(const char *format,...)
Same as Print, but works only in Debug mode.
Definition: CVLog.cpp:153
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
virtual bool fromFile_MeOnly(QFile &in, short dataVersion, int flags, LoadedIDMap &oldToNewIDMap)
Loads own object data.
short minimumFileVersion() const override
Returns the minimum file version required to save this instance.
virtual bool toFile_MeOnly(QFile &out, short dataVersion) const
Save own object data.
Mesh (triangle) material.
bool fromFile_MeOnly(QFile &in, short dataVersion, int flags, LoadedIDMap &oldToNewIDMap) override
Loads own object data.
int findMaterialByName(QString mtlName) const
Finds material by name.
ccMaterialSet(const QString &name=QString())
Default constructor.
int addMaterial(ccMaterial::CShared mat, bool allowDuplicateNames=false)
Adds a material.
bool saveAsMTL(QString path, const QString &baseFilename, QStringList &errors) const
Saves to an MTL file (+ associated texture images)
static bool ParseMTL(QString path, const QString &filename, ccMaterialSet &materials, QStringList &errors)
MTL (material) file parser.
short minimumFileVersion_MeOnly() const override
ccMaterialSet * clone() const
Clones materials set.
bool toFile_MeOnly(QFile &out, short dataVersion) const override
Save own object data.
int findMaterialByUniqueID(QString uniqueID) const
Finds material by unique identifier.
bool append(const ccMaterialSet &source)
Appends materials from another set.
Mesh (triangle) material.
Definition: ecvMaterial.h:28
TextureMapType
Texture map types for PBR materials.
Definition: ecvMaterial.h:176
QSharedPointer< const ccMaterial > CShared
Const + Shared type.
Definition: ecvMaterial.h:31
static QImage GetTexture(const QString &absoluteFilename)
Returns the texture image associated to a given name.
static void AddTexture(const QImage &image, const QString &absoluteFilename)
Adds a texture to the global texture DB.
QSharedPointer< ccMaterial > Shared
Shared type.
Definition: ecvMaterial.h:33
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
virtual void setFlagState(CV_OBJECT_FLAG flag, bool state)
Sets flag state.
Definition: ecvObject.cpp:120
QMultiMap< unsigned, unsigned > LoadedIDMap
Map of loaded unique IDs (old ID --> new ID)
static bool ReadError()
Sends a custom error message (read error) and returns 'false'.
static bool WriteError()
Sends a custom error message (write error) and returns 'false'.
RGBA color structure.
constexpr QRegularExpression::PatternOption CaseInsensitive
Definition: QtCompat.h:174
constexpr Qt::SplitBehavior SkipEmptyParts
Definition: QtCompat.h:302
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
static const std::string path
Definition: PointCloud.cpp:59
Rgb at(size_t color_id)
Definition: Eigen.h:85