ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvMaterial.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 "ecvMaterial.h"
9 
10 // CV_CORE_LIB
11 #include <CVTools.h>
12 
13 // Qt
14 #include <QMap>
15 #include <QOpenGLContext>
16 #include <QOpenGLTexture>
17 #include <QUuid>
18 
19 // Textures DB
20 static QMap<QString, QImage> s_textureDB;
21 // static QMap<QString, QSharedPointer<QOpenGLTexture>> s_openGLTextureDB;
22 
24  : m_name(name),
25  m_uniqueID(QUuid::createUuid().toString()),
26  m_illum(1),
27  m_diffuseFront(ecvColor::bright),
28  m_diffuseBack(ecvColor::bright),
29  m_ambient(ecvColor::night),
30  m_specular(ecvColor::night),
31  m_emission(ecvColor::night),
32  m_metallic(0.0f),
33  m_roughness(0.5f),
34  m_sheen(0.0f),
35  m_clearcoat(0.0f),
36  m_clearcoatRoughness(0.0f),
37  m_anisotropy(0.0f),
38  m_ambientOcclusion(0.0f) {
39  setShininess(50.0);
40 };
41 
43  : m_name(mtl.m_name),
44  m_textureFilename(mtl.m_textureFilename),
45  m_uniqueID(mtl.m_uniqueID),
46  m_illum(2),
47  m_diffuseFront(mtl.m_diffuseFront),
48  m_diffuseBack(mtl.m_diffuseBack),
49  m_ambient(mtl.m_ambient),
50  m_specular(mtl.m_specular),
51  m_emission(mtl.m_emission),
52  m_shininessFront(mtl.m_shininessFront),
53  m_shininessBack(mtl.m_shininessFront),
54  m_metallic(mtl.m_metallic),
55  m_roughness(mtl.m_roughness),
56  m_sheen(mtl.m_sheen),
57  m_clearcoat(mtl.m_clearcoat),
58  m_clearcoatRoughness(mtl.m_clearcoatRoughness),
59  m_anisotropy(mtl.m_anisotropy),
60  m_ambientOcclusion(mtl.m_ambientOcclusion),
61  m_textureFilenames(mtl.m_textureFilenames) {}
62 
66 }
67 
68 void ccMaterial::setShininess(float val) {
69  setShininessFront(val);
70  setShininessBack(0.8f * val);
71 }
72 
73 void ccMaterial::setTransparency(float val) {
74  m_diffuseFront.a = val;
75  m_diffuseBack.a = val;
76  m_ambient.a = val;
77  m_specular.a = val;
78  m_emission.a = val;
79 }
80 
81 void ccMaterial::applyGL(const QOpenGLContext* context,
82  bool lightEnabled,
83  bool skipDiffuse) const {
84  Q_UNUSED(context);
85  Q_UNUSED(lightEnabled);
86  Q_UNUSED(skipDiffuse);
87 
88  // get the set of OpenGL functions (version 2.1)
89  // QOpenGLFunctions_2_1* glFunc =
90  // context->versionFunctions<QOpenGLFunctions_2_1>(); assert(glFunc !=
91  // nullptr);
92 
93  // if (glFunc == nullptr)
94  // return;
95 
96  // if (lightEnabled)
97  //{
98  // if (!skipDiffuse)
99  // {
100  // glFunc->glMaterialfv(GL_FRONT, GL_DIFFUSE, m_diffuseFront.rgba);
101  // glFunc->glMaterialfv(GL_BACK, GL_DIFFUSE, m_diffuseBack.rgba);
102  // }
103  // glFunc->glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, m_ambient.rgba);
104  // glFunc->glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, m_specular.rgba);
105  // glFunc->glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, m_emission.rgba);
106  // glFunc->glMaterialf (GL_FRONT, GL_SHININESS, std::max(0.0f,
107  // std::min(m_shininessFront, 128.0f))); glFunc->glMaterialf (GL_BACK,
108  // GL_SHININESS, std::max(0.0f, std::min(m_shininessBack, 128.0f)));
109  // }
110  // else
111  //{
112  // glFunc->glColor4fv(m_diffuseFront.rgba);
113  // }
114 }
115 
116 bool ccMaterial::loadAndSetTexture(const QString& absoluteFilename) {
117  if (absoluteFilename.isEmpty()) {
118  CVLog::Warning(QString(
119  "[ccMaterial::loadAndSetTexture] filename can't be empty!"));
120  return false;
121  }
122 
123  // fix path separator bug
124  const QString& nativeFilename =
125  CVTools::ToNativeSeparators(absoluteFilename);
126 
127  if (s_textureDB.contains(nativeFilename)) {
128  // if the image is already in memory, we simply update the texture
129  // filename for this amterial
130  m_textureFilename = nativeFilename;
131  } else {
132  // otherwise, we try to load the corresponding file
133  QImage image(nativeFilename);
134  if (image.isNull()) {
135  CVLog::Warning(QString("[ccMaterial::loadAndSetTexture] Failed to "
136  "load image '%1'")
137  .arg(nativeFilename));
138  return false;
139  } else {
140  setTexture(image, nativeFilename, false);
141  }
142  }
143 
144  return true;
145 }
146 
147 void ccMaterial::setTexture(const QImage& image,
148  const QString& absoluteFilename /*=QString()*/,
149  bool mirrorImage /*=false*/) {
150  CVLog::PrintDebug(QString("[ccMaterial::setTexture] absoluteFilename = "
151  "'%1' / size = %2 x %3")
152  .arg(absoluteFilename)
153  .arg(image.width())
154  .arg(image.height()));
155 
156  // fix path separator bug
157  QString nativeFilename = CVTools::ToNativeSeparators(absoluteFilename);
158  if (nativeFilename.isEmpty()) {
159  // if the user hasn't provided any filename, we generate a fake one
160  nativeFilename = QString("tex_%1.jpg").arg(m_uniqueID);
161  assert(!s_textureDB.contains(nativeFilename));
162  } else {
163  // if the texture has already been loaded
164  if (s_textureDB.contains(nativeFilename)) {
165  // check that the size is compatible at least
166  if (s_textureDB[nativeFilename].size() != image.size()) {
167  CVLog::Warning(QString("[ccMaterial] A texture with the same "
168  "name (%1) "
169  "but with a different size has already "
170  "been loaded!")
171  .arg(nativeFilename));
172  }
173  m_textureFilename = nativeFilename;
174  return;
175  }
176  }
177 
178  m_textureFilename = absoluteFilename;
179 
180  // insert image into DB if necessary
181  s_textureDB[m_textureFilename] = mirrorImage ? image.mirrored() : image;
182 }
183 
184 const QImage& ccMaterial::getTexture() const {
186 }
187 
188 GLuint ccMaterial::getTextureID() const {
189  if (QOpenGLContext::currentContext()) {
190  const QImage& image = getTexture();
191  if (image.isNull()) {
192  return 0;
193  } else {
194  return 1;
195  }
196  } else {
197  return 0;
198  }
199 }
200 
202  return !m_textureFilename.isEmpty() &&
203  !s_textureDB[m_textureFilename].isNull();
204 }
205 
206 QImage ccMaterial::GetTexture(const QString& absoluteFilename) {
207  return s_textureDB[absoluteFilename];
208 }
209 
210 void ccMaterial::AddTexture(const QImage& image,
211  const QString& absoluteFilename) {
212  s_textureDB[absoluteFilename] = image;
213 }
214 
216 
218  if (m_textureFilename.isEmpty()) {
219  // nothing to do
220  return;
221  }
222 
223  CVLog::Print(QString("[ccMaterial::releaseTexture] Releasing texture '%1'")
224  .arg(m_textureFilename));
226  m_textureFilename.clear();
227 }
228 
229 // ========== Multi-Texture PBR Support Implementation ==========
230 
232  const QString& absoluteFilename) {
233  if (absoluteFilename.isEmpty()) {
234  CVLog::Warning(QString(
235  "[ccMaterial::loadAndSetTextureMap] filename can't be empty!"));
236  return false;
237  }
238 
239  const QString& nativeFilename =
240  CVTools::ToNativeSeparators(absoluteFilename);
241 
242  CVLog::PrintVerbose(QString("[ccMaterial::loadAndSetTextureMap] Loading "
243  "texture map (type=%1): %2")
244  .arg(static_cast<int>(type))
245  .arg(nativeFilename));
246 
247  // Load texture into global DB if not already present
248  if (!s_textureDB.contains(nativeFilename)) {
249  QImage image(nativeFilename);
250  if (image.isNull()) {
251  CVLog::Warning(QString("[ccMaterial::loadAndSetTextureMap] Failed "
252  "to load image '%1'")
253  .arg(nativeFilename));
254  return false;
255  }
256  s_textureDB[nativeFilename] = image;
257  }
258 
259  // Store filename for this texture type
260  // Support multiple textures of the same type (e.g., multiple map_Kd)
261  auto& textureList = m_textureFilenames[type];
262 
263  // Check if this exact texture file already exists for this type
264  bool alreadyExists = false;
265  for (const QString& existingTex : textureList) {
266  if (existingTex == nativeFilename) {
267  alreadyExists = true;
269  QString("[ccMaterial::loadAndSetTextureMap] Texture file "
270  "'%1' already exists for type %2, skipping "
271  "duplicate")
272  .arg(nativeFilename)
273  .arg(static_cast<int>(type)));
274  break;
275  }
276  }
277 
278  if (!alreadyExists) {
279  textureList.push_back(nativeFilename);
281  QString("[ccMaterial::loadAndSetTextureMap] Added texture '%1' "
282  "for type %2 (total: %3)")
283  .arg(nativeFilename)
284  .arg(static_cast<int>(type))
285  .arg(textureList.size()));
286  }
287 
288  // If this is the diffuse map, also set legacy m_textureFilename for
289  // backward compatibility (use the first one)
290  if (type == TextureMapType::DIFFUSE && !textureList.empty()) {
291  m_textureFilename = textureList[0];
292  }
293 
294  return true;
295 }
296 
298  auto it = m_textureFilenames.find(type);
299  if (it != m_textureFilenames.end() && !it->second.empty()) {
300  // Return the first texture of this type for backward compatibility
301  return it->second[0];
302  }
303  return QString();
304 }
305 
306 std::vector<QString> ccMaterial::getTextureFilenames(
307  TextureMapType type) const {
308  auto it = m_textureFilenames.find(type);
309  if (it != m_textureFilenames.end()) {
310  // Return all textures of this type
311  return it->second;
312  }
313  return std::vector<QString>();
314 }
315 
317  auto it = m_textureFilenames.find(type);
318  return it != m_textureFilenames.end() && !it->second.empty();
319 }
320 
321 std::vector<std::pair<ccMaterial::TextureMapType, QString>>
323  std::vector<std::pair<TextureMapType, QString>> result;
324  // Include all textures, including multiple textures of the same type
325  for (const auto& pair : m_textureFilenames) {
326  for (const QString& texPath : pair.second) {
327  result.push_back(std::make_pair(pair.first, texPath));
328  }
329  }
330  return result;
331 }
332 
333 bool ccMaterial::toFile(QFile& out, short dataVersion) const {
334  if (dataVersion < 20) {
335  assert(false);
336  return false;
337  }
338 
339  QDataStream outStream(&out);
340 
341  // material name (dataVersion >= 20)
342  outStream << m_name;
343  // texture filename (dataVersion >= 37) or texture image (dataVersion < 37)
344  outStream << m_textureFilename;
345  // material colors (dataVersion >= 20)
346  // we don't use QByteArray here as it has its own versions!
347  if (out.write((const char*)m_diffuseFront.rgba, sizeof(float) * 4) < 0)
348  return WriteError();
349  if (out.write((const char*)m_diffuseBack.rgba, sizeof(float) * 4) < 0)
350  return WriteError();
351  if (out.write((const char*)m_ambient.rgba, sizeof(float) * 4) < 0)
352  return WriteError();
353  if (out.write((const char*)m_specular.rgba, sizeof(float) * 4) < 0)
354  return WriteError();
355  if (out.write((const char*)m_emission.rgba, sizeof(float) * 4) < 0)
356  return WriteError();
357  // material shininess (dataVersion >= 20)
358  outStream << m_shininessFront;
359  outStream << m_shininessBack;
360 
361  return true;
362 }
363 
364 short ccMaterial::minimumFileVersion() const { return 20; }
365 
366 bool ccMaterial::fromFile(QFile& in,
367  short dataVersion,
368  int flags,
369  LoadedIDMap& oldToNewIDMap) {
370  Q_UNUSED(flags);
371 
372  QDataStream inStream(&in);
373 
374  // material name (dataVersion>=20)
375  inStream >> m_name;
376  if (dataVersion < 37) {
377  // texture (dataVersion>=20)
378  QImage texture;
379  inStream >> texture;
380  setTexture(texture, QString(), false);
381  } else {
382  // texture 'filename' (dataVersion>=37)
383  inStream >> m_textureFilename;
384  }
385  // material colors (dataVersion>=20)
386  if (in.read((char*)m_diffuseFront.rgba, sizeof(float) * 4) < 0)
387  return ReadError();
388  if (in.read((char*)m_diffuseBack.rgba, sizeof(float) * 4) < 0)
389  return ReadError();
390  if (in.read((char*)m_ambient.rgba, sizeof(float) * 4) < 0)
391  return ReadError();
392  if (in.read((char*)m_specular.rgba, sizeof(float) * 4) < 0)
393  return ReadError();
394  if (in.read((char*)m_emission.rgba, sizeof(float) * 4) < 0)
395  return ReadError();
396  // material shininess (dataVersion>=20)
397  inStream >> m_shininessFront;
398  inStream >> m_shininessBack;
399 
400  return true;
401 }
402 
403 bool ccMaterial::compare(const ccMaterial& mtl) const {
404  if (mtl.m_name != m_name || mtl.m_textureFilename != m_textureFilename ||
407  mtl.m_specular != m_specular || mtl.m_emission != m_emission ||
408  mtl.m_illum != m_illum || mtl.m_diffuseBack != m_diffuseBack ||
410  return false;
411  }
412 
413  return true;
414 }
std::shared_ptr< core::Tensor > image
int size
std::string name
char type
math::float4 color
core::Tensor result
Definition: VtkUtils.cpp:76
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 bool PrintVerbose(const char *format,...)
Prints out a verbose formatted message in console.
Definition: CVLog.cpp:103
static QString ToNativeSeparators(const QString &path)
Definition: CVTools.cpp:45
Mesh (triangle) material.
Definition: ecvMaterial.h:28
float m_shininessBack
Definition: ecvMaterial.h:260
TextureMapType
Texture map types for PBR materials.
Definition: ecvMaterial.h:176
bool compare(const ccMaterial &mtl) const
Compares this material with another one.
bool loadAndSetTextureMap(TextureMapType type, const QString &absoluteFilename)
Load and set a specific texture map type.
short minimumFileVersion() const override
Returns the minimum file version required to save this instance.
bool hasTexture() const
Returns whether the material has an associated texture or not.
GLuint getTextureID() const
Returns the texture ID (if any)
std::map< TextureMapType, std::vector< QString > > m_textureFilenames
Definition: ecvMaterial.h:273
ecvColor::Rgbaf m_diffuseBack
Definition: ecvMaterial.h:255
ecvColor::Rgbaf m_specular
Definition: ecvMaterial.h:257
void setShininess(float val)
Sets shininess (both front - 100% - and back - 80%)
Definition: ecvMaterial.cpp:68
bool toFile(QFile &out, short dataVersion) const override
float m_shininessFront
Definition: ecvMaterial.h:259
void setDiffuseFront(const ecvColor::Rgbaf &color)
Sets diffuse color (front)
Definition: ecvMaterial.h:53
static void ReleaseTextures()
Release all texture objects.
QString m_name
Definition: ecvMaterial.h:249
ecvColor::Rgbaf m_diffuseFront
Definition: ecvMaterial.h:254
void releaseTexture()
Release the texture.
void applyGL(const QOpenGLContext *context, bool lightEnabled, bool skipDiffuse) const
Apply parameters (OpenGL)
Definition: ecvMaterial.cpp:81
void setDiffuseBack(const ecvColor::Rgbaf &color)
Sets diffuse color (back)
Definition: ecvMaterial.h:57
std::vector< QString > getTextureFilenames(TextureMapType type) const
Get all texture filenames for a specific map type.
void setTexture(const QImage &image, const QString &absoluteFilename=QString(), bool mirrorImage=false)
Sets texture.
void setShininessFront(float val)
Sets shininess (front)
Definition: ecvMaterial.h:96
bool fromFile(QFile &in, short dataVersion, int flags, LoadedIDMap &oldToNewIDMap) override
Loads data from binary stream.
std::vector< std::pair< TextureMapType, QString > > getAllTextureFilenames() const
Get all texture map filenames.
static QImage GetTexture(const QString &absoluteFilename)
Returns the texture image associated to a given name.
ecvColor::Rgbaf m_emission
Definition: ecvMaterial.h:258
static void AddTexture(const QImage &image, const QString &absoluteFilename)
Adds a texture to the global texture DB.
void setShininessBack(float val)
Sets shininess (back)
Definition: ecvMaterial.h:98
const QImage & getTexture() const
Returns the texture (if any)
void setDiffuse(const ecvColor::Rgbaf &color)
Sets diffuse color (both front and back)
Definition: ecvMaterial.cpp:63
bool hasTextureMap(TextureMapType type) const
Check if a specific texture map type exists.
QString m_uniqueID
Definition: ecvMaterial.h:251
const QString & getTextureFilename() const
Returns the texture filename (if any)
Definition: ecvMaterial.h:44
QString m_textureFilename
Definition: ecvMaterial.h:250
bool loadAndSetTexture(const QString &absoluteFilename)
Loads texture from file (and set it if successful)
ccMaterial(const QString &name=QString("default"))
Default constructor.
Definition: ecvMaterial.cpp:23
ecvColor::Rgbaf m_ambient
Definition: ecvMaterial.h:256
void setTransparency(float val)
Sets transparency (all colors)
Definition: ecvMaterial.cpp:73
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.
static QMap< QString, QImage > s_textureDB
Definition: ecvMaterial.cpp:20
ImGuiContext * context
Definition: Window.cpp:76
CLOUDVIEWER_HOST_DEVICE Pair< First, Second > make_pair(const First &_first, const Second &_second)
Definition: SlabTraits.h:49
std::string toString(T x)
Definition: Common.h:80
Colors namespace.
Definition: ecvColorTypes.h:32
constexpr Rgbaf night(0.00f, 0.00f, 0.00f, 1.00F)
constexpr Rgbaf bright(1.00f, 1.00f, 1.00f, 1.00f)