ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
MeshTextureApplier.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 
9 
10 #include <CVLog.h>
11 #include <CVTools.h>
12 #include <Utils/cc2sm.h> // For cc2smReader::getVtkPolyDataWithTextures
13 #include <Utils/sm2cc.h>
14 #include <ecvGenericMesh.h>
15 #include <ecvHObjectCaster.h> // For ccHObjectCaster
16 #include <ecvMaterial.h>
17 #include <ecvMaterialSet.h>
18 #include <ecvPointCloud.h> // For POINT_VISIBLE
19 
21 
22 // PCL
23 #include <pcl/TextureMesh.h>
24 
25 #include <Eigen/Dense>
26 
27 // VTK
28 #include <vtkDataObject.h>
29 #include <vtkFloatArray.h>
30 #include <vtkLODActor.h>
31 #include <vtkMatrix4x4.h>
32 #include <vtkOpenGLRenderWindow.h>
33 #include <vtkPointData.h>
34 #include <vtkPolyData.h>
35 #include <vtkPolyDataMapper.h>
36 #include <vtkProperty.h>
37 #include <vtkRenderWindow.h>
38 #include <vtkRenderer.h>
39 #include <vtkSmartPointer.h>
40 #include <vtkTexture.h>
41 #include <vtkTextureUnitManager.h>
42 
43 namespace PclUtils {
44 namespace renders {
45 
47  vtkLODActor* actor,
49  mesh, // Non-const because getTriangleVertIndexes is non-const
50  vtkPolyData* polydata,
51  TextureRenderManager* render_manager,
52  vtkRenderer* renderer) {
53  if (!actor || !mesh || !render_manager) {
54  return false;
55  }
56 
57  const ccMaterialSet* materials = mesh->getMaterialSet();
58  if (!materials || materials->empty()) {
60  "[MeshTextureApplier::ApplyTexturesFromCCMesh] No materials "
61  "found in mesh");
62  return false;
63  }
64 
65  // Use cc2smReader::getVtkPolyDataWithTextures to ensure consistency
66  // This reuses the proven logic from getPclTextureMesh and addTextureMesh
67  // which ensures texture coordinates match point order perfectly
68  ccPointCloud* cloud =
70  if (!cloud) {
72  "[MeshTextureApplier::ApplyTexturesFromCCMesh] Failed to get "
73  "point cloud from mesh!");
74  return false;
75  }
76 
77  cc2smReader reader(cloud, true);
78  vtkSmartPointer<vtkPolyData> new_polydata;
79  vtkSmartPointer<vtkMatrix4x4> transformation;
80  std::vector<std::vector<Eigen::Vector2f>> tex_coordinates;
81 
82  if (!reader.getVtkPolyDataWithTextures(mesh, new_polydata, transformation,
83  tex_coordinates)) {
85  "[MeshTextureApplier::ApplyTexturesFromCCMesh] Failed to "
86  "convert mesh to VTK with textures!");
87  return false;
88  }
89 
90  // Replace polydata with the new one (which has correct texture coordinates)
91  if (polydata && new_polydata) {
92  polydata->ShallowCopy(new_polydata);
93  } else if (new_polydata) {
94  polydata = new_polydata.Get();
95  }
96 
97  // transformation is automatically managed by smart pointer, no manual
98  // cleanup needed
99 
100  CVLog::Print(
101  "[MeshTextureApplier::ApplyTexturesFromCCMesh] Successfully "
102  "converted mesh using getVtkPolyDataWithTextures with %zu texture "
103  "coordinate groups",
104  tex_coordinates.size());
105 
106  // Apply textures using material set
107  return ApplyTexturesFromMaterialSet(actor, materials, tex_coordinates,
108  polydata, render_manager, renderer);
109 }
110 
112  vtkLODActor* actor,
113  const ccMaterialSet* materials,
114  const std::vector<std::vector<Eigen::Vector2f>>& tex_coordinates,
115  vtkPolyData* polydata,
116  TextureRenderManager* render_manager,
117  vtkRenderer* renderer) {
118  if (!actor || !materials || !render_manager) {
119  return false;
120  }
121 
122  // Setup texture coordinate arrays in polydata
123  // CRITICAL: This must match the logic in addTextureMesh
124  // (PCLVis.cpp:1577-1598)
125  //
126  // In getPclTextureMesh, texture coordinates are grouped by material:
127  // - tex_coordinates[0] contains coordinates for material 0's triangles
128  // - tex_coordinates[1] contains coordinates for material 1's triangles
129  // - etc.
130  //
131  // Each material's texture coordinates are already in the correct order
132  // (matching the triangle order from getPclCloud2).
133  //
134  // For multi-texture rendering, we need:
135  // - TCoords0: coordinates for material 0 (only material 0's triangles have
136  // valid coords)
137  // - TCoords1: coordinates for material 1 (only material 1's triangles have
138  // valid coords)
139  // - etc.
140  //
141  // But VTK requires arrays with the same size as points, so we need to fill
142  // invalid regions with (-1, -1) or (0, 0) for triangles that don't belong
143  // to that material.
144  //
145  // However, since getPclCloud2 creates expanded points (each triangle vertex
146  // is separate), and tex_coordinates[mat_idx] already contains coordinates
147  // in the correct order, we can directly use them. But we need to create a
148  // full-size array and fill other regions appropriately.
149 
150  if (polydata) {
151  vtkIdType numPoints = polydata->GetNumberOfPoints();
152 
153  // Create texture coordinate arrays for each material
154  // Each array corresponds to one material's texture coordinates
155  // This matches the logic in addTextureMesh where tex_id corresponds to
156  // material index
157  size_t texture_index = 0;
158 
159  for (size_t mat_idx = 0;
160  mat_idx < materials->size() && mat_idx < tex_coordinates.size();
161  ++mat_idx) {
162  if (tex_coordinates[mat_idx].empty()) {
163  continue;
164  }
165 
166  auto material = materials->at(mat_idx);
167  if (!material) continue;
168 
169  // Get all DIFFUSE (map_Kd) textures for this material
170  using TexType = ccMaterial::TextureMapType;
171  std::vector<QString> diffuseTextures =
172  material->getTextureFilenames(TexType::DIFFUSE);
173 
174  // Create a texture coordinate array for EACH map_Kd texture in this
175  // material All textures from the same material share the same
176  // coordinates
177  for (size_t tex_in_mat = 0; tex_in_mat < diffuseTextures.size();
178  ++tex_in_mat) {
179  if (diffuseTextures[tex_in_mat].isEmpty()) {
180  continue;
181  }
182 
183  // Create texture coordinate array for this texture
184  // Use the material's texture coordinates directly
185  vtkSmartPointer<vtkFloatArray> coordinates =
187  coordinates->SetNumberOfComponents(2);
188  std::stringstream ss;
189  ss << "TCoords" << texture_index;
190  std::string coords_name = ss.str();
191  coordinates->SetName(coords_name.c_str());
192 
193  // Fill coordinates for this material
194  // tex_coordinates[mat_idx] already contains coordinates in the
195  // correct order matching the point order from getPclCloud2
196  for (const auto& tc : tex_coordinates[mat_idx]) {
197  coordinates->InsertNextTuple2(tc[0], tc[1]);
198  }
199 
200  // If the coordinate array size doesn't match numPoints, we need
201  // to pad This shouldn't happen if getPclCloud2 and
202  // getPclTextureMesh are consistent, but we handle it for safety
203  vtkIdType currentSize = coordinates->GetNumberOfTuples();
204  if (currentSize < numPoints) {
206  "[MeshTextureApplier::ApplyTexturesFromMaterialSet]"
207  " "
208  "Material %zu texture coordinate count (%ld) < "
209  "point count "
210  "(%ld), padding with default coordinates",
211  mat_idx, currentSize, numPoints);
212  for (vtkIdType i = currentSize; i < numPoints; ++i) {
213  coordinates->InsertNextTuple2(0.0f, 0.0f);
214  }
215  } else if (currentSize > numPoints) {
217  "[MeshTextureApplier::ApplyTexturesFromMaterialSet]"
218  " "
219  "Material %zu texture coordinate count (%ld) > "
220  "point count "
221  "(%ld), truncating",
222  mat_idx, currentSize, numPoints);
223  // Note: VTK arrays can't be easily truncated, so we'll just
224  // use what we have This indicates a data inconsistency that
225  // should be fixed upstream
226  }
227 
228  polydata->GetPointData()->AddArray(coordinates);
229 
230  // Set first texture coordinates as active TCoords (for PBR)
231  if (texture_index == 0) {
232  polydata->GetPointData()->SetTCoords(coordinates);
233  }
234 
235  texture_index++;
236  }
237  }
238 
240  "[MeshTextureApplier::ApplyTexturesFromMaterialSet] Created "
241  "%zu "
242  "texture coordinate arrays for %zu points",
243  texture_index, numPoints);
244  }
245 
246  // Use TextureRenderManager to apply materials
247  return render_manager->Apply(actor, materials, polydata, renderer);
248 }
249 
250 bool MeshTextureApplier::ApplyPBRTextures(vtkLODActor* actor,
251  const pcl::TextureMesh& mesh,
252  vtkPolyData* polydata,
253  TextureRenderManager* render_manager,
254  vtkRenderer* renderer) {
255  if (!actor || !render_manager) {
256  return false;
257  }
258 
259  CVLog::Print(
260  "[MeshTextureApplier::ApplyPBRTextures] DEPRECATED: This method "
261  "uses pcl::TexMaterial encoding. Consider using "
262  "ApplyTexturesFromCCMesh instead.");
263 
264  // Convert pcl::TexMaterial to ccMaterialSet for TextureRenderManager
265  ccMaterialSet* temp_material_set = new ccMaterialSet("TempMaterials");
266  for (const auto& pcl_mat : mesh.tex_materials) {
267  ccMaterial::Shared temp_material = ccMaterial::Shared(new ccMaterial);
268  pcl2cc::FromPCLMaterial(pcl_mat, temp_material);
269  temp_material_set->addMaterial(temp_material);
270  }
271 
272  // Convert texture coordinates
273  // pcl::TextureMesh uses Eigen::aligned_allocator, we need to convert
274  std::vector<std::vector<Eigen::Vector2f>> tex_coordinates;
275  tex_coordinates.reserve(mesh.tex_coordinates.size());
276  for (const auto& coords : mesh.tex_coordinates) {
277  std::vector<Eigen::Vector2f> coord_vec;
278  coord_vec.reserve(coords.size());
279  for (const auto& coord : coords) {
280  coord_vec.push_back(Eigen::Vector2f(coord[0], coord[1]));
281  }
282  tex_coordinates.push_back(coord_vec);
283  }
284 
285  // Use new method to apply textures
286  bool success = ApplyTexturesFromMaterialSet(actor, temp_material_set,
287  tex_coordinates, polydata,
288  render_manager, renderer);
289 
290  delete temp_material_set;
291  return success;
292 }
293 
295  vtkLODActor* actor,
296  const pcl::TextureMesh& mesh,
297  vtkPolyData* polydata,
298  vtkRenderWindow* render_window) {
299  if (!actor || !polydata || !render_window) {
300  return false;
301  }
302 
303  // This function is kept for backward compatibility but should use
304  // TextureRenderManager instead
306  "[MeshTextureApplier::ApplyTraditionalTextures] This function is "
307  "deprecated. Use TextureRenderManager instead.");
308 
309  // Get texture unit manager
310  vtkOpenGLRenderWindow* gl_window = vtkOpenGLRenderWindow::SafeDownCast(
311  static_cast<vtkObjectBase*>(render_window));
312  if (!gl_window) {
313  return false;
314  }
315  vtkTextureUnitManager* tex_manager = gl_window->GetTextureUnitManager();
316  if (!tex_manager) {
317  return false;
318  }
319 
320  // Check available texture units
321  int texture_units = tex_manager->GetNumberOfTextureUnits();
322  if (static_cast<size_t>(texture_units) < mesh.tex_materials.size()) {
324  "[MeshTextureApplier::ApplyTraditionalTextures] GPU texture "
325  "units %d < mesh textures %zu!",
326  texture_units, mesh.tex_materials.size());
327  }
328 
329  vtkPolyDataMapper* mapper =
330  vtkPolyDataMapper::SafeDownCast(actor->GetMapper());
331  if (!mapper) {
332  return false;
333  }
334 
335  // Apply textures (simplified version - full implementation should use
336  // MultiTextureRenderer)
337  size_t last_tex_id = std::min(mesh.tex_materials.size(),
338  static_cast<size_t>(texture_units));
339 
340  for (size_t tex_id = 0; tex_id < last_tex_id; ++tex_id) {
341 #if (VTK_MAJOR_VERSION == 8 && VTK_MINOR_VERSION >= 2) || VTK_MAJOR_VERSION > 8
342  const char* tu = mesh.tex_materials[tex_id].tex_name.c_str();
343 #else
344  int tu = vtkProperty::VTK_TEXTURE_UNIT_0 + tex_id;
345 #endif
346 
347  // Add texture coordinates array
348  vtkSmartPointer<vtkFloatArray> coordinates =
350  coordinates->SetNumberOfComponents(2);
351  std::stringstream ss;
352  if (mesh.tex_coordinates.size() == 1) {
353  ss << "TCoords";
354  } else {
355  ss << "TCoords" << tex_id;
356  }
357  std::string coords_name = ss.str();
358  coordinates->SetName(coords_name.c_str());
359 
360  // Fill coordinates
361  for (size_t t = 0; t < mesh.tex_coordinates.size(); ++t) {
362  if (t == tex_id) {
363  for (const auto& tc : mesh.tex_coordinates[t]) {
364  coordinates->InsertNextTuple2(tc[0], tc[1]);
365  }
366  } else {
367  for (size_t tc = 0; tc < mesh.tex_coordinates[t].size(); ++tc) {
368  coordinates->InsertNextTuple2(-1.0, -1.0);
369  }
370  }
371  }
372 
373  mapper->MapDataArrayToMultiTextureAttribute(
374  tu, coords_name.c_str(),
375  vtkDataObject::FIELD_ASSOCIATION_POINTS);
376  polydata->GetPointData()->AddArray(coordinates);
377  }
378 
379  return true;
380 }
381 
382 } // namespace renders
383 } // namespace PclUtils
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 Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
static bool ApplyTexturesFromCCMesh(vtkLODActor *actor, ccGenericMesh *mesh, vtkPolyData *polydata, TextureRenderManager *render_manager, vtkRenderer *renderer)
Apply textures from ccGenericMesh (preferred method)
static bool ApplyPBRTextures(vtkLODActor *actor, const pcl::TextureMesh &mesh, vtkPolyData *polydata, TextureRenderManager *render_manager, vtkRenderer *renderer)
Apply PBR textures from PCLTextureMesh (deprecated)
static bool ApplyTraditionalTextures(vtkLODActor *actor, const pcl::TextureMesh &mesh, vtkPolyData *polydata, vtkRenderWindow *render_window)
Apply textures from PCLTextureMesh to actor (traditional path)
static bool ApplyTexturesFromMaterialSet(vtkLODActor *actor, const ccMaterialSet *materials, const std::vector< std::vector< Eigen::Vector2f >> &tex_coordinates, vtkPolyData *polydata, TextureRenderManager *render_manager, vtkRenderer *renderer)
Apply textures from ccMaterialSet with texture coordinates.
bool Apply(vtkLODActor *actor, const ccMaterialSet *materials, vtkPolyData *polydata, vtkRenderer *renderer)
Apply rendering to actor.
CC to PCL cloud converter.
Definition: cc2sm.h:43
bool getVtkPolyDataWithTextures(ccGenericMesh *mesh, vtkSmartPointer< vtkPolyData > &polydata, vtkSmartPointer< vtkMatrix4x4 > &transformation, std::vector< std::vector< Eigen::Vector2f >> &tex_coordinates)
Convert ccGenericMesh to vtkPolyData with texture coordinates Reuses getPclTextureMesh logic to ensur...
Definition: cc2sm.cpp:1566
Generic mesh interface.
virtual const ccMaterialSet * getMaterialSet() const =0
virtual ccGenericPointCloud * getAssociatedCloud() const =0
Returns the vertices cloud.
static ccPointCloud * ToPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccPointCloud.
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
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
static void FromPCLMaterial(const PCLMaterial &inMaterial, ccMaterial::Shared &outMaterial)
Definition: sm2cc.cpp:311