ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
IoUtils.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 <QImageReader>
9 #include <QRegularExpression>
10 #include <QVector3D>
11 
12 // CV_CORE_LIB
13 #include <CVTools.h>
14 
15 // CV_DB_LIB
16 #include "IoUtils.h"
17 #include "assimp/material.h"
18 #include "assimp/mesh.h"
19 #include "assimp/metadata.h"
20 #include "assimp/scene.h"
21 #include "ecvHObjectCaster.h"
22 #include "ecvMaterialSet.h"
23 #include "ecvMesh.h"
24 #include "ecvPointCloud.h"
25 
26 namespace {
27 QImage _getEmbeddedTexture(unsigned int inTextureIndex,
28  const aiScene *inScene) {
29  QImage image;
30 
31  if (inScene->mNumTextures == 0) {
33  QStringLiteral("[qMeshIO] Scene requests embedded texture, but "
34  "there are none"));
35  return image;
36  }
37 
38  auto texture = inScene->mTextures[inTextureIndex];
39 
40  // From assimp: "If mHeight is zero the texture is compressed"
41  bool isCompressed = (texture->mHeight == 0);
42 
43  if (!isCompressed) {
45  QStringLiteral("[qMeshIO] Uncompressed embedded textures not "
46  "yet implemented"));
47  return image;
48  }
49 
50  // From assimp: "mWidth specifies the size of the memory area pcData is
51  // pointing to, in bytes"
52  auto dataSize = static_cast<const int32_t>(texture->mWidth);
53 
54  const QByteArray imageDataByteArray(
55  reinterpret_cast<const char *>(texture->pcData), dataSize);
56 
57  return QImage::fromData(imageDataByteArray);
58 }
59 
60 QImage _getTextureFromFile(const QString &inPath,
61  const QString &inTexturePath) {
62  QString cPath = QStringLiteral("%1/%2").arg(inPath, inTexturePath);
63 
64  cPath = CVTools::ToNativeSeparators(cPath);
65 
66  if (!QFile::exists(cPath)) {
67  CVLog::Warning(QStringLiteral("[qMeshIO] Material not found: '%1'")
68  .arg(cPath));
69  return {};
70  }
71 
72  QImageReader reader(cPath);
73 
74  QImage image = reader.read();
75  if (image.isNull()) {
77  QString("[_getTextureFromFile] failed to read image %1, %2")
78  .arg(cPath)
79  .arg(reader.errorString()));
80  }
81  return image;
82 }
83 
84 inline ecvColor::Rgbaf _convertColour(const aiColor4D &inColour) {
85  return ecvColor::Rgbaf{inColour.r, inColour.g, inColour.b, inColour.a};
86 }
87 
88 // Map all the material properties we know about from assimp
89 void _assignMaterialProperties(aiMaterial *inAIMaterial,
90  ccMaterial::Shared &inCCMaterial) {
91  aiColor4D colour;
92 
93  if (inAIMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, colour) == AI_SUCCESS) {
94  inCCMaterial->setDiffuse(_convertColour(colour));
95  }
96 
97  if (inAIMaterial->Get(AI_MATKEY_COLOR_AMBIENT, colour) == AI_SUCCESS) {
98  inCCMaterial->setAmbient(_convertColour(colour));
99  }
100 
101  if (inAIMaterial->Get(AI_MATKEY_COLOR_SPECULAR, colour) == AI_SUCCESS) {
102  inCCMaterial->setSpecular(_convertColour(colour));
103  }
104 
105  if (inAIMaterial->Get(AI_MATKEY_COLOR_EMISSIVE, colour) == AI_SUCCESS) {
106  inCCMaterial->setEmission(_convertColour(colour));
107  }
108 
109  ai_real property;
110 
111  if (inAIMaterial->Get(AI_MATKEY_SHININESS, property) == AI_SUCCESS) {
112  inCCMaterial->setShininess(property);
113  }
114 
115  if (inAIMaterial->Get(AI_MATKEY_OPACITY, property) == AI_SUCCESS) {
116  inCCMaterial->setTransparency(property);
117  }
118 }
119 } // namespace
120 
121 namespace IoUtils {
123  const QString &inPath,
124  const aiScene *inScene) {
125  if (inScene->mNumMaterials == 0) {
126  return nullptr;
127  }
128 
129  unsigned int index = inMesh->mMaterialIndex;
130  const auto aiMaterial = inScene->mMaterials[index];
131 
132  const aiString cName = aiMaterial->GetName();
133 
134  auto newMaterial = ccMaterial::Shared(new ccMaterial(cName.C_Str()));
135 
136  CVLog::PrintDebug(QStringLiteral("[qMeshIO] Creating material '%1'")
137  .arg(newMaterial->getName()));
138 
139  // ========================================================================
140  // Load all PBR texture types
141  // ========================================================================
142 
143  // Helper lambda to load a texture for a specific type
144  auto loadTexture = [&](aiTextureType aiType,
146  const char *typeName) {
147  if (aiMaterial->GetTextureCount(aiType) > 0) {
148  aiString texturePath;
149 
150  if (aiMaterial->GetTexture(aiType, 0, &texturePath) == AI_SUCCESS) {
151  static QRegularExpression sRegExp("^\\*(?<index>[0-9]+)$");
152  auto match = sRegExp.match(texturePath.C_Str());
153 
154  QImage image;
155  QString path =
156  CVTools::ToNativeSeparators(QStringLiteral("%1/%2").arg(
157  inPath, texturePath.C_Str()));
158 
159  // Try different loading methods
160  if (match.hasMatch()) {
161  // Embedded texture by index
162  const QString cIndex = match.captured("index");
163  image = _getEmbeddedTexture(cIndex.toUInt(), inScene);
164  } else if (!QFile::exists(path) && inScene->HasTextures()) {
165  // Find embedded texture by name
166  unsigned int cIndex = 0;
167  for (unsigned int i = 0; i < inScene->mNumTextures; ++i) {
168  aiString textureName = inScene->mTextures[i]->mFilename;
169  if (textureName == texturePath) {
170  cIndex = i;
171  break;
172  }
173  }
174  image = _getEmbeddedTexture(cIndex, inScene);
175  } else {
176  // Load from file
177  image = _getTextureFromFile(inPath, texturePath.C_Str());
178  }
179 
180  if (!image.isNull()) {
181  // Use new multi-texture API
182  if (newMaterial->loadAndSetTextureMap(ccType, path)) {
184  QStringLiteral(
185  "[qMeshIO] Loaded %1 texture: %2")
186  .arg(typeName, path));
187  }
188 
189  // For diffuse, also set legacy texture for backward
190  // compatibility
191  if (ccType == ccMaterial::TextureMapType::DIFFUSE) {
192  newMaterial->setTexture(image, path, false);
193  }
194  } else {
196  QStringLiteral(
197  "[qMeshIO] Failed to load %1 texture: %2")
198  .arg(typeName, path));
199  }
200  }
201  }
202  };
203 
204  // Load all supported texture types
205  loadTexture(aiTextureType_DIFFUSE, ccMaterial::TextureMapType::DIFFUSE,
206  "Diffuse");
207  loadTexture(aiTextureType_AMBIENT, ccMaterial::TextureMapType::AMBIENT,
208  "Ambient/AO");
209  loadTexture(aiTextureType_SPECULAR, ccMaterial::TextureMapType::SPECULAR,
210  "Specular");
211  loadTexture(aiTextureType_NORMALS, ccMaterial::TextureMapType::NORMAL,
212  "Normal");
213  loadTexture(aiTextureType_HEIGHT, ccMaterial::TextureMapType::NORMAL,
214  "Height/Normal"); // Height maps can be used as normals
215  loadTexture(aiTextureType_EMISSIVE, ccMaterial::TextureMapType::EMISSIVE,
216  "Emissive");
217  loadTexture(aiTextureType_OPACITY, ccMaterial::TextureMapType::OPACITY,
218  "Opacity");
219  loadTexture(aiTextureType_DISPLACEMENT,
221  loadTexture(aiTextureType_REFLECTION,
223  loadTexture(aiTextureType_SHININESS, ccMaterial::TextureMapType::SHININESS,
224  "Shininess");
225  loadTexture(aiTextureType_METALNESS, ccMaterial::TextureMapType::METALLIC,
226  "Metallic");
227  loadTexture(aiTextureType_DIFFUSE_ROUGHNESS,
229 
230  _assignMaterialProperties(aiMaterial, newMaterial);
231 
232  ccMaterialSet *materialSet = new ccMaterialSet("Materials");
233 
234  materialSet->addMaterial(newMaterial);
235 
236  return materialSet;
237 }
238 
239 ccMesh *newCCMeshFromAIMesh(const aiMesh *inMesh) {
240  auto newPC = new ccPointCloud("Vertices");
241  auto newMesh = new ccMesh(newPC);
242 
243  QString name(inMesh->mName.C_Str());
244 
245  if (name.isEmpty()) {
246  name = QStringLiteral("Mesh");
247  }
248 
249  CVLog::Print(QStringLiteral("[qMeshIO] Mesh '%1' has %2 verts & %3 faces")
250  .arg(name,
251  QLocale::system().toString(inMesh->mNumVertices),
252  QLocale::system().toString(inMesh->mNumFaces)));
253 
254  if (!inMesh->HasPositions() || !inMesh->HasFaces()) {
256  QStringLiteral(
257  "[qMeshIO] Mesh '%1' does not have vertices or faces")
258  .arg(name));
259 
260  delete newPC;
261  delete newMesh;
262 
263  return nullptr;
264  }
265 
266  // reserve memory for points and mesh (because we need to do this before
267  // other memory allocations)
268  newPC->reserveThePointsTable(inMesh->mNumVertices);
269 
270  if (inMesh->HasFaces()) {
271  newMesh->reserve(inMesh->mNumFaces);
272  }
273 
274  // vertex colors
275  bool hasVertexColors = inMesh->HasVertexColors(0);
276  if (hasVertexColors) {
277  bool allocated = newPC->reserveTheRGBTable();
278  if (!allocated) {
280  QStringLiteral(
281  "[qMeshIO] Cannot allocate colors for mesh '%1'")
282  .arg(name));
283  }
284  }
285 
286  // normals
287  if (inMesh->HasNormals()) {
288  bool allocated = newPC->reserveTheNormsTable();
289 
290  if (!allocated) {
292  QStringLiteral(
293  "[qMeshIO] Cannot allocate normals for mesh '%1'")
294  .arg(name));
295  }
296  }
297 
298  // texture coordinates
299  bool hasTextureCoordinates = inMesh->HasTextureCoords(0);
300 
301  TextureCoordsContainer *texCoords = nullptr;
302 
303  if (hasTextureCoordinates) {
304  texCoords = new TextureCoordsContainer;
305 
306  texCoords->reserve(inMesh->mNumVertices);
307 
308  bool allocated = texCoords->isAllocated();
309 
310  allocated &= newMesh->reservePerTriangleTexCoordIndexes();
311  allocated &= newMesh->reservePerTriangleMtlIndexes();
312 
313  if (!allocated) {
314  delete texCoords;
315  hasTextureCoordinates = false;
316  CVLog::Warning(QStringLiteral("[qMeshIO] Cannot allocate texture "
317  "coordinates for mesh '%1'")
318  .arg(name));
319  } else {
320  newMesh->setTexCoordinatesTable(texCoords);
321  }
322  }
323 
324  // vertices
325  for (unsigned int i = 0; i < inMesh->mNumVertices; ++i) {
326  const aiVector3D &point = inMesh->mVertices[i];
327 
328  CCVector3 point2(static_cast<PointCoordinateType>(point.x),
329  static_cast<PointCoordinateType>(point.y),
330  static_cast<PointCoordinateType>(point.z));
331 
332  newPC->addPoint(point2);
333 
334  // colors
335  if (newPC->hasColors()) {
336  const aiColor4D &colors = inMesh->mColors[0][i];
337 
338  ecvColor::Rgb color(static_cast<ColorCompType>(colors.r * 255),
339  static_cast<ColorCompType>(colors.g * 255),
340  static_cast<ColorCompType>(colors.b * 255));
341 
342  newPC->addRGBColor(color);
343  }
344 
345  // normals
346  if (newPC->hasNormals()) {
347  const aiVector3D &normal = inMesh->mNormals[i];
348 
349  CCVector3 normal2(static_cast<PointCoordinateType>(normal.x),
350  static_cast<PointCoordinateType>(normal.y),
351  static_cast<PointCoordinateType>(normal.z));
352 
353  newPC->addNorm(normal2);
354  }
355 
356  // texture coordinates
357  if (hasTextureCoordinates) {
358  const aiVector3D &texCoord = inMesh->mTextureCoords[0][i];
359 
360  const TexCoords2D coord{texCoord.x, texCoord.y};
361 
362  texCoords->addElement(coord);
363  }
364  }
365 
366  newPC->setEnabled(false);
367 
368  // faces
369  if (inMesh->HasFaces()) {
370  newMesh->reserve(inMesh->mNumFaces);
371 
372  for (unsigned int i = 0; i < inMesh->mNumFaces; ++i) {
373  const aiFace &face = inMesh->mFaces[i];
374 
375  if (face.mNumIndices != 3) {
376  continue;
377  }
378 
379  newMesh->addTriangle(face.mIndices[0], face.mIndices[1],
380  face.mIndices[2]);
381 
382  // texture coordinates
383  if (hasTextureCoordinates) {
384  newMesh->addTriangleMtlIndex(0);
385 
386  newMesh->addTriangleTexCoordIndexes(
387  static_cast<int>(face.mIndices[0]),
388  static_cast<int>(face.mIndices[1]),
389  static_cast<int>(face.mIndices[2]));
390  }
391  }
392  }
393 
394  if (newMesh->size() == 0) {
396  QStringLiteral("[qMeshIO] Mesh '%1' does not have any faces")
397  .arg(name));
398 
399  delete newPC;
400  delete newMesh;
401 
402  return nullptr;
403  }
404 
405  newMesh->setName(name);
406  newMesh->setVisible(true);
407 
408  if (!newPC->hasNormals()) {
410  QStringLiteral("[qMeshIO] Mesh '%1' does not have normals - "
411  "will compute them per vertex automatically!")
412  .arg(name));
413 
414  newMesh->computeNormals(true);
415  }
416 
417  newMesh->showNormals(true);
418  newMesh->showColors(hasVertexColors);
419  newMesh->addChild(newPC);
420 
421  return newMesh;
422 }
423 
424 ccGLMatrix convertMatrix(const aiMatrix4x4 &inAssimpMatrix) {
425  const int cWidth = 4;
426  const int cHeight = 4;
427 
429 
430  for (unsigned int i = 0; i < cWidth; ++i) {
431  for (unsigned int j = 0; j < cHeight; ++j) {
432  data[j * cHeight + i] =
433  static_cast<PointCoordinateType>(inAssimpMatrix[i][j]);
434  }
435  }
436 
437  return ccGLMatrix(data);
438 }
439 
440 QVariant convertMetaValueToVariant(aiMetadata *inData,
441  unsigned int inValueIndex) {
442  QVariant metaValue;
443 
444  switch (inData->mValues[inValueIndex].mType) {
445  case AI_BOOL: {
446  bool value = false;
447 
448  inData->Get<bool>(inValueIndex, value);
449 
450  metaValue = value;
451  break;
452  }
453 
454  case AI_INT32: {
455  int32_t value = 0;
456 
457  inData->Get<int32_t>(inValueIndex, value);
458 
459  metaValue = value;
460  break;
461  }
462 
463  case AI_UINT64: {
464  uint64_t value = 0;
465 
466  inData->Get<uint64_t>(inValueIndex, value);
467 
468  metaValue = static_cast<qulonglong>(value);
469  break;
470  }
471 
472  case AI_FLOAT: {
473  float value = 0;
474 
475  inData->Get<float>(inValueIndex, value);
476 
477  metaValue = value;
478  break;
479  }
480 
481  case AI_DOUBLE: {
482  double value = 0;
483 
484  inData->Get<double>(inValueIndex, value);
485 
486  metaValue = value;
487  break;
488  }
489 
490  case AI_AISTRING: {
491  aiString value;
492 
493  inData->Get<aiString>(inValueIndex, value);
494 
495  metaValue = value.C_Str();
496  break;
497  }
498 
499  case AI_AIVECTOR3D: {
500  aiVector3D value;
501 
502  inData->Get<aiVector3D>(inValueIndex, value);
503 
504  metaValue = QVector3D(value.x, value.y, value.z);
505  break;
506  }
507 
508  case AI_META_MAX:
509  case FORCE_32BIT: {
510  // This is necessary to avoid a warning.
511  // Assimp doesn't use enum type specifiers.
512  // It uses this odd trick w/FORCE_32BIT to force the type of the
513  // enum.
514  break;
515  }
516  }
517 
518  return metaValue;
519 }
520 } // namespace IoUtils
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
std::shared_ptr< core::Tensor > image
double normal[3]
std::string name
math::float4 color
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
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
static QString ToNativeSeparators(const QString &path)
Definition: CVTools.cpp:45
Array of 2D texture coordinates.
bool isAllocated() const
Returns whether some memory has been allocated or not.
Definition: ecvArray.h:67
void addElement(const Type &value)
Definition: ecvArray.h:105
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
Mesh (triangle) material.
int addMaterial(ccMaterial::CShared mat, bool allowDuplicateNames=false)
Adds a material.
Mesh (triangle) material.
Definition: ecvMaterial.h:28
TextureMapType
Texture map types for PBR materials.
Definition: ecvMaterial.h:176
QSharedPointer< ccMaterial > Shared
Shared type.
Definition: ecvMaterial.h:33
Triangular mesh.
Definition: ecvMesh.h:35
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
RGB color structure.
Definition: ecvColorTypes.h:49
RGBA color structure.
double colors[3]
std::vector< unsigned int > face
unsigned char ColorCompType
Default color components type (R,G and B)
Definition: ecvColorTypes.h:29
static const unsigned OPENGL_MATRIX_SIZE
Model view matrix size (OpenGL)
ccMaterialSet * createMaterialSetForMesh(const aiMesh *inMesh, const QString &inPath, const aiScene *inScene)
Definition: IoUtils.cpp:122
ccMesh * newCCMeshFromAIMesh(const aiMesh *inMesh)
Definition: IoUtils.cpp:239
QVariant convertMetaValueToVariant(aiMetadata *inData, unsigned int inValueIndex)
Definition: IoUtils.cpp:440
ccGLMatrix convertMatrix(const aiMatrix4x4 &inAssimpMatrix)
Definition: IoUtils.cpp:424
static const std::string path
Definition: PointCloud.cpp:59
std::string toString(T x)
Definition: Common.h:80
2D texture coordinates
Definition: lsd.c:149
int y
Definition: lsd.c:149
int x
Definition: lsd.c:149