33 for (ccMaterialSet::const_iterator it = begin(); it != end(); ++it, ++i) {
36 QString(
"\tmaterial #%1 name: %2").arg(i).arg(mtl->getName()));
37 if (mtl->getName() == mtlName)
return i;
45 QString(
"[ccMaterialSet::findMaterialByUniqueID] Query: ") +
49 for (ccMaterialSet::const_iterator it = begin(); it != end(); ++it, ++i) {
53 .arg(mtl->getUniqueIdentifier()));
54 if (mtl->getUniqueIdentifier() == uniqueID)
return i;
61 bool allowDuplicateNames ) {
71 if (previousIndex >= 0) {
73 if (!previousMtl->compare(*mtl)) {
76 if (!allowDuplicateNames) {
78 static const unsigned MAX_ATTEMPTS = 100;
79 for (
unsigned i = 1; i < MAX_ATTEMPTS; i++) {
81 previousMtl->getName() + QString(
"_%1").arg(i);
85 newMtl->setName(newMtlName);
93 if (previousIndex >= 0 && !allowDuplicateNames)
return previousIndex;
97 }
catch (
const std::bad_alloc&) {
102 return static_cast<int>(
size()) - 1;
110 QStringList& errors) {
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);
120 path = QFileInfo(fullPathFilename).absolutePath();
122 QTextStream stream(&file);
124 QString currentLine = stream.readLine();
125 unsigned currentLineIndex = 0;
127 while (!currentLine.isNull()) {
134 if (tokens.empty() ||
137 currentLine = stream.readLine();
142 if (tokens.front() ==
"newmtl") {
144 if (currentMaterial) {
150 QString materialName =
151 currentLine.mid(7).trimmed();
153 if (materialName.isEmpty()) materialName =
"undefined";
156 }
else if (currentMaterial)
159 if (tokens.front() ==
"Ka") {
160 if (tokens.size() > 3) {
163 tokens[3].toFloat(), 1.0f);
164 currentMaterial->setAmbient(ambient);
169 else if (tokens.front() ==
"Kd") {
170 if (tokens.size() > 3) {
173 tokens[3].toFloat(), 1.0f);
174 currentMaterial->setDiffuse(diffuse);
179 else if (tokens.front() ==
"Ks") {
180 if (tokens.size() > 3) {
183 tokens[3].toFloat(), 1.0f);
184 currentMaterial->setSpecular(specular);
189 else if (tokens.front() ==
"Ke") {
190 if (tokens.size() > 3) {
193 tokens[3].toFloat(), 1.0f);
194 currentMaterial->setEmission(emission);
199 else if (tokens.front() ==
"Ns") {
200 if (tokens.size() > 1)
201 currentMaterial->setShininess(tokens[1].toFloat());
204 else if (tokens.front() ==
"d") {
205 if (tokens.size() > 1) {
206 float d = tokens[1].toFloat();
209 QString(
"Material %1 'alpha' (=d) value is 0 "
210 "(= fully transparent)")
211 .arg(currentMaterial->getName()));
213 currentMaterial->setTransparency(d);
217 else if (tokens.front() ==
"Tr") {
218 if (tokens.size() > 1) {
219 float tr = tokens[1].toFloat();
222 QString(
"Material %1 'transparency' (=Tr) "
223 "value is 1 (= fully transparent)")
224 .arg(currentMaterial->getName()));
226 currentMaterial->setTransparency(1.0f -
227 tokens[1].toFloat());
231 else if (tokens.front() ==
"r") {
237 else if (tokens.front() ==
"sharpness") {
243 else if (tokens.front() ==
"Ni") {
249 else if (tokens.front() ==
"illum") {
250 if (tokens.size() > 1)
251 currentMaterial->setIllum(tokens[1].toInt());
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") {
268 QString mapCommand = tokens.front();
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") {
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") {
312 int shift = currentLine.indexOf(mapCommand, 0);
313 QString textureFilename =
314 (shift + mapCommand.length() + 1 < currentLine.size()
316 .mid(shift + mapCommand.length() + 1)
325 QString actualFilename;
326 for (
const QString& part : parts) {
329 if (part.startsWith(
'-')) {
333 bool isNumber =
false;
334 part.toDouble(&isNumber);
335 if (isNumber && actualFilename.isEmpty()) {
339 actualFilename = part;
344 if (!actualFilename.isEmpty()) {
345 textureFilename = actualFilename;
349 if (textureFilename.startsWith(
"\"")) {
351 textureFilename.right(textureFilename.size() - 1);
353 if (textureFilename.endsWith(
"\"")) {
355 textureFilename.left(textureFilename.size() - 1);
360 textureFilename = textureFilename.replace(
'\\',
'/');
362 QString fullTexName =
path + QString(
'/') + textureFilename;
365 if (!currentMaterial->loadAndSetTextureMap(mapType,
367 errors << QString(
"Failed to load texture file: %1 (type: "
374 else if (tokens.front() ==
"Pm") {
376 if (tokens.size() > 1)
377 currentMaterial->setMetallic(tokens[1].toFloat());
378 }
else if (tokens.front() ==
"Pr") {
380 if (tokens.size() > 1)
381 currentMaterial->setRoughness(tokens[1].toFloat());
382 }
else if (tokens.front() ==
"Ps") {
384 if (tokens.size() > 1)
385 currentMaterial->setSheen(tokens[1].toFloat());
386 }
else if (tokens.front() ==
"Pc") {
388 if (tokens.size() > 1)
389 currentMaterial->setClearcoat(tokens[1].toFloat());
390 }
else if (tokens.front() ==
"Pcr") {
392 if (tokens.size() > 1)
393 currentMaterial->setClearcoatRoughness(tokens[1].toFloat());
394 }
else if (tokens.front() ==
"aniso") {
396 if (tokens.size() > 1)
397 currentMaterial->setAnisotropy(tokens[1].toFloat());
398 }
else if (tokens.front() ==
"Pa") {
400 if (tokens.size() > 1)
401 currentMaterial->setAmbientOcclusion(tokens[1].toFloat());
403 errors << QString(
"Unknown command '%1' at line %2")
405 .arg(currentLineIndex);
409 currentLine = stream.readLine();
415 if (currentMaterial) materials.
addMaterial(currentMaterial);
421 const QString& baseFilename,
422 QStringList& errors)
const {
424 QString
filename =
path + QString(
'/') + baseFilename + QString(
".mtl");
426 if (!file.open(QFile::WriteOnly)) {
427 errors << QString(
"Error writing file: %1").arg(
filename);
430 QTextStream stream(&file);
435 QMap<QString, QString> absFilenamesSaved;
436 QSet<QString> filenamesUsed;
439 auto saveTextureFile = [&](
const QString& absFilename,
440 size_t matIndex) -> QString {
441 if (absFilename.isEmpty())
return QString();
444 if (!absFilenamesSaved.contains(absFilename)) {
445 QFileInfo fileInfo(absFilename);
446 QString texName = fileInfo.fileName();
447 if (texName.isEmpty()) {
449 texName = QString(
"tex_%1.jpg").arg(matIndex);
450 }
else if (fileInfo.suffix().isEmpty()) {
451 texName += QString(
".jpg");
455 QString originalTexName = texName;
457 while (filenamesUsed.contains(texName)) {
458 QString baseName = fileInfo.completeBaseName();
460 fileInfo.suffix().isEmpty() ?
"jpg" : fileInfo.suffix();
461 texName = QString(
"%1_%2.%3")
466 filenamesUsed.insert(texName);
468 QString destFilename =
path + QString(
'/') + texName;
470 if (!texture.isNull() && texture.save(destFilename)) {
473 QString normalizedTexName = texName.replace(
'\\',
'/');
474 absFilenamesSaved[absFilename] = normalizedTexName;
476 errors << QString(
"Failed to save the texture file '%1' to "
478 .arg(absFilename, destFilename);
483 if (absFilenamesSaved.contains(absFilename)) {
485 QString relativeFilename = absFilenamesSaved[absFilename];
486 return relativeFilename.replace(
'\\',
'/');
492 for (ccMaterialSet::const_iterator it = begin(); it != end();
509 if (Ke.
r > 0.0f || Ke.
g > 0.0f || Ke.
b > 0.0f) {
510 stream <<
"Ke " << Ke.
r <<
" " << Ke.
g <<
" " << Ke.
b
515 float transparency = Kd.
a;
516 if (transparency < 1.0f) {
527 if (mtl->getMetallic() > 0.0f) {
530 if (mtl->getRoughness() != 0.5f) {
533 if (mtl->getSheen() > 0.0f) {
536 if (mtl->getClearcoat() > 0.0f) {
539 if (mtl->getClearcoatRoughness() > 0.0f) {
540 stream <<
"Pcr " << mtl->getClearcoatRoughness() <<
QtCompat::endl;
542 if (mtl->getAnisotropy() > 0.0f) {
545 if (mtl->getAmbientOcclusion() > 0.0f) {
550 auto allTextures = mtl->getAllTextureFilenames();
551 for (
const auto& texPair : allTextures) {
553 QString absFilename = texPair.second;
555 if (absFilename.isEmpty())
continue;
557 QString relativeFilename = saveTextureFile(absFilename, matIndex);
558 if (relativeFilename.isEmpty())
continue;
564 mapCommand =
"map_Kd";
567 mapCommand =
"map_Ka";
570 mapCommand =
"map_Ks";
573 mapCommand =
"map_Ke";
576 mapCommand =
"map_d";
579 mapCommand =
"map_Ns";
582 mapCommand =
"map_Bump";
585 mapCommand =
"map_Pr";
588 mapCommand =
"map_Pm";
591 mapCommand =
"map_Ps";
594 mapCommand =
"map_Pc";
597 mapCommand =
"map_Pcr";
600 mapCommand =
"map_aniso";
603 mapCommand =
"map_disp";
610 mapCommand =
"map_Kd";
614 stream << mapCommand <<
" " << relativeFilename <<
QtCompat::endl;
619 if (mtl->hasTexture() &&
621 QString absFilename = mtl->getTextureFilename();
622 QString relativeFilename = saveTextureFile(absFilename, matIndex);
623 if (!relativeFilename.isEmpty()) {
636 for (ccMaterialSet::const_iterator it = source.begin();
637 it != source.end(); ++it) {
641 QString(
"[ccMaterialSet::append] Material %1 couldn't "
642 "be added to material set and will be ignored")
643 .arg(mtl->getName()));
657 if (!cloneSet->
append(*
this)) {
667 assert(out.isOpen() && (out.openMode() & QIODevice::WriteOnly));
668 if (dataVersion < 37) {
680 std::set<QString> texFilenames;
683 for (ccMaterialSet::const_iterator it = begin(); it != end(); ++it) {
685 mtl->toFile(out, dataVersion);
688 QString texFilename = mtl->getTextureFilename();
689 if (!texFilename.isEmpty()) texFilenames.insert(texFilename);
693 QDataStream outStream(&out);
694 outStream << static_cast<uint32_t>(texFilenames.size());
697 for (std::set<QString>::const_iterator it = texFilenames.begin();
698 it != texFilenames.end(); ++it) {
726 if (
count == 0)
return true;
730 for (uint32_t i = 0; i <
count; ++i) {
732 if (!mtl->fromFile(in, dataVersion, flags, oldToNewIDMap))
741 if (dataVersion >= 37) {
742 QDataStream inStream(&in);
745 uint32_t texCount = 0;
746 inStream >> texCount;
749 for (uint32_t i = 0; i < texCount; ++i) {
std::shared_ptr< core::Tensor > image
QStringList qtCompatSplitRegex(const QString &str, const QString &pattern, Qt::SplitBehavior behavior=Qt::KeepEmptyParts)
virtual void release()
Decrease counter and deletes object when 0.
static bool WarningDebug(const char *format,...)
Same as Warning, but works only in Debug mode.
static bool PrintDebug(const char *format,...)
Same as Print, but works only in Debug mode.
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Hierarchical CLOUDVIEWER Object.
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.
TextureMapType
Texture map types for PBR materials.
QSharedPointer< const ccMaterial > CShared
Const + Shared type.
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.
virtual QString getName() const
Returns object name.
virtual void setFlagState(CV_OBJECT_FLAG flag, bool state)
Sets flag state.
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'.
constexpr QRegularExpression::PatternOption CaseInsensitive
constexpr Qt::SplitBehavior SkipEmptyParts
QTextStream & endl(QTextStream &stream)
static const std::string path