ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
PointCloudBuffers.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 // 4068: Filament has some clang-specific vectorizing pragma's that MSVC flags
9 // 4146: Filament's utils/algorithm.h utils::details::ctz() tries to negate
10 // an unsigned int.
11 // 4293: Filament's utils/algorithm.h utils::details::clz() does strange
12 // things with MSVC. Somehow sizeof(unsigned int) > 4, but its size is
13 // 32 so that x >> 32 gives a warning. (Or maybe the compiler can't
14 // determine the if statement does not run.)
15 #ifdef _MSC_VER
16 #pragma warning(push)
17 #pragma warning(disable : 4068 4146 4293)
18 #endif // _MSC_VER
19 
20 #include <filament/IndexBuffer.h>
21 #include <filament/VertexBuffer.h>
22 #include <geometry/SurfaceOrientation.h>
23 
24 #ifdef _MSC_VER
25 #pragma warning(pop)
26 #endif // _MSC_VER
27 
28 #include <ecvBBox.h>
29 #include <ecvPointCloud.h>
30 
31 #include "t/geometry/PointCloud.h"
35 
36 using namespace filament;
37 
38 namespace cloudViewer {
39 namespace visualization {
40 namespace rendering {
41 
42 namespace {
43 struct ColoredVertex {
44  math::float3 position = {0.f, 0.f, 0.f};
45  // Default to mid-gray which provides good separation from the two most
46  // common background colors: white and black. Otherwise, point clouds
47  // without per-vertex colors may appear is if they are not rendering because
48  // they blend with the background.
49  math::float4 color = {0.5f, 0.5f, 0.5f, 1.f};
50  math::quatf tangent = {0.f, 0.f, 0.f, 1.f};
51  math::float2 uv = {0.f, 0.f};
52 
53  static std::uint32_t GetPositionOffset() {
54  return offsetof(ColoredVertex, position);
55  }
56  static std::uint32_t GetColorOffset() {
57  return offsetof(ColoredVertex, color);
58  }
59  static std::uint32_t GetTangentOffset() {
60  return offsetof(ColoredVertex, tangent);
61  }
62  static std::uint32_t GetUVOffset() { return offsetof(ColoredVertex, uv); }
63  void SetVertexPosition(const Eigen::Vector3d& pos) {
64  auto float_pos = pos.cast<float>();
65  position.x = float_pos(0);
66  position.y = float_pos(1);
67  position.z = float_pos(2);
68  }
69 
70  float sRGBToLinear(float color) {
71  return color <= 0.04045f ? color / 12.92f
72  : pow((color + 0.055f) / 1.055f, 2.4f);
73  }
74 
75  void SetVertexColor(const Eigen::Vector3d& c, bool adjust_for_srgb) {
76  auto float_color = c.cast<float>();
77  if (adjust_for_srgb) {
78  color.x = sRGBToLinear(float_color(0));
79  color.y = sRGBToLinear(float_color(1));
80  color.z = sRGBToLinear(float_color(2));
81  } else {
82  color.x = float_color(0);
83  color.y = float_color(1);
84  color.z = float_color(2);
85  }
86  }
87 };
88 } // namespace
89 
90 IndexBufferHandle GeometryBuffersBuilder::CreateIndexBuffer(
91  size_t max_index, size_t n_subsamples /*= SIZE_MAX*/) {
93  auto& engine = EngineInstance::GetInstance();
94  auto& resource_mgr = EngineInstance::GetResourceManager();
95 
96  size_t n_indices = std::min(max_index, n_subsamples);
97  // Use double for precision, since float can only accurately represent
98  // integers up to 2^24 = 16 million, and we have buffers with more points
99  // than that.
100  double step = double(max_index) / double(n_indices);
101 
102  const size_t n_bytes = n_indices * sizeof(IndexType);
103  auto* uint_indices = static_cast<IndexType*>(malloc(n_bytes));
104  if (step <= 1.0) {
105  // std::iota is about 2X faster than a loop on my machine, anyway.
106  // Since this is the common case, and is used for every entity,
107  // special-case this to make it fast.
108  std::iota(uint_indices, uint_indices + n_indices, 0);
109  } else if (std::floor(step) == step) {
110  for (size_t i = 0; i < n_indices; ++i) {
111  uint_indices[i] = IndexType(step * i);
112  }
113  } else {
114  size_t idx = 0;
115  uint_indices[idx++] = 0;
116  double dist = 1.0;
117  size_t i;
118  for (i = 1; i < max_index; ++i) {
119  if (dist >= step) {
120  uint_indices[idx++] = IndexType(i);
121  dist -= step;
122  if (idx > n_indices) { // paranoia, should not happen
123  break;
124  }
125  }
126  dist += 1.0;
127  }
128  // Very occasionally floating point error leads to one fewer points
129  // being added.
130  if (i < max_index - 1) {
131  n_indices = i + 1;
132  }
133  }
134 
135  auto ib_handle =
136  resource_mgr.CreateIndexBuffer(n_indices, sizeof(IndexType));
137  if (!ib_handle) {
138  free(uint_indices);
139  return IndexBufferHandle();
140  }
141 
142  auto ibuf = resource_mgr.GetIndexBuffer(ib_handle).lock();
143 
144  // Moving `uintIndices` to IndexBuffer, which will clean them up later
145  // with DeallocateBuffer
146  IndexBuffer::BufferDescriptor indices_descriptor(uint_indices, n_bytes);
147  indices_descriptor.setCallback(GeometryBuffersBuilder::DeallocateBuffer);
148  ibuf->setBuffer(engine, std::move(indices_descriptor));
149  return ib_handle;
150 }
151 
152 PointCloudBuffersBuilder::PointCloudBuffersBuilder(const ccPointCloud& geometry)
153  : geometry_(geometry) {}
154 
155 RenderableManager::PrimitiveType PointCloudBuffersBuilder::GetPrimitiveType()
156  const {
157  return RenderableManager::PrimitiveType::POINTS;
158 }
159 
161  auto& engine = EngineInstance::GetInstance();
162  auto& resource_mgr = EngineInstance::GetResourceManager();
163 
164  const size_t n_vertices = geometry_.size();
165 
166  // We use CUSTOM0 for tangents along with TANGENTS attribute
167  // because Filament would optimize out anything about normals and lightning
168  // from unlit materials. But our shader for normals visualizing is unlit, so
169  // we need to use this workaround.
170  VertexBuffer* vbuf = VertexBuffer::Builder()
171  .bufferCount(1)
172  .vertexCount(std::uint32_t(n_vertices))
173  .attribute(VertexAttribute::POSITION, 0,
174  VertexBuffer::AttributeType::FLOAT3,
175  ColoredVertex::GetPositionOffset(),
176  sizeof(ColoredVertex))
177  .normalized(VertexAttribute::COLOR)
178  .attribute(VertexAttribute::COLOR, 0,
179  VertexBuffer::AttributeType::FLOAT4,
180  ColoredVertex::GetColorOffset(),
181  sizeof(ColoredVertex))
182  .normalized(VertexAttribute::TANGENTS)
183  .attribute(VertexAttribute::TANGENTS, 0,
184  VertexBuffer::AttributeType::FLOAT4,
185  ColoredVertex::GetTangentOffset(),
186  sizeof(ColoredVertex))
187  .attribute(VertexAttribute::CUSTOM0, 0,
188  VertexBuffer::AttributeType::FLOAT4,
189  ColoredVertex::GetTangentOffset(),
190  sizeof(ColoredVertex))
191  .attribute(VertexAttribute::UV0, 0,
192  VertexBuffer::AttributeType::FLOAT2,
193  ColoredVertex::GetUVOffset(),
194  sizeof(ColoredVertex))
195  .build(engine);
196 
197  VertexBufferHandle vb_handle;
198  if (vbuf) {
199  vb_handle = resource_mgr.AddVertexBuffer(vbuf);
200  } else {
201  return {};
202  }
203 
204  math::quatf* float4v_tagents = nullptr;
205  if (geometry_.hasNormals()) {
206  // Converting vertex normals to float base
207  std::vector<Eigen::Vector3f> normals;
208  normals.resize(n_vertices);
209  for (size_t i = 0; i < n_vertices; ++i) {
210  normals[i] = geometry_.getEigenNormal(i).cast<float>();
211  }
212 
213  // Converting normals to Filament type - quaternions
214  const size_t tangents_byte_count = n_vertices * 4 * sizeof(float);
215  float4v_tagents =
216  static_cast<math::quatf*>(malloc(tangents_byte_count));
217  auto orientation = filament::geometry::SurfaceOrientation::Builder()
218  .vertexCount(n_vertices)
219  .normals(reinterpret_cast<math::float3*>(
220  normals.data()))
221  .build();
222  orientation->getQuats(float4v_tagents, n_vertices);
223  }
224 
225  const size_t vertices_byte_count = n_vertices * sizeof(ColoredVertex);
226  auto* vertices = static_cast<ColoredVertex*>(malloc(vertices_byte_count));
227  const ColoredVertex kDefault;
228  for (size_t i = 0; i < geometry_.size(); ++i) {
229  ColoredVertex& element = vertices[i];
230  element.SetVertexPosition(geometry_.getEigenPoint(i));
231  if (geometry_.hasColors()) {
232  element.SetVertexColor(geometry_.getEigenColor(i),
234  } else {
235  element.color = kDefault.color;
236  }
237 
238  if (float4v_tagents) {
239  element.tangent = float4v_tagents[i];
240  } else {
241  element.tangent = kDefault.tangent;
242  }
243  element.uv = kDefault.uv;
244  }
245 
246  free(float4v_tagents);
247 
248  // Moving `vertices` to IndexBuffer, which will clean them up later
249  // with DeallocateBuffer
250  VertexBuffer::BufferDescriptor vb_descriptor(vertices, vertices_byte_count);
251  vb_descriptor.setCallback(GeometryBuffersBuilder::DeallocateBuffer);
252  vbuf->setBufferAt(engine, 0, std::move(vb_descriptor));
253 
254  auto ib_handle = CreateIndexBuffer(n_vertices);
255 
256  IndexBufferHandle downsampled_handle;
257  if (n_vertices >= downsample_threshold_) {
258  downsampled_handle =
260  }
261 
262  return std::make_tuple(vb_handle, ib_handle, downsampled_handle);
263 }
264 
266  const auto geometry_aabb = geometry_.GetAxisAlignedBoundingBox();
267 
268  const filament::math::float3 min(geometry_aabb.minCorner().x,
269  geometry_aabb.minCorner().y,
270  geometry_aabb.minCorner().z);
271  const filament::math::float3 max(geometry_aabb.maxCorner().x,
272  geometry_aabb.maxCorner().y,
273  geometry_aabb.maxCorner().z);
274 
275  Box aabb;
276  aabb.set(min, max);
277 
278  return aabb;
279 }
280 
282  const t::geometry::PointCloud& geometry)
283  : geometry_(geometry) {
284  // Make sure geometry is on CPU
285  auto pts = geometry.GetPointPositions();
286  if (pts.IsCUDA()) {
288  "GPU resident point clouds are not currently supported for "
289  "visualization. Copying data to CPU.");
290  geometry_ = geometry.To(core::Device("CPU:0"));
291  }
292 
293  // Now make sure data types are Float32
294  if (pts.GetDtype() != core::Float32) {
296  "Tensor point cloud points must have DType of Float32 not {}. "
297  "Converting.",
298  pts.GetDtype().ToString());
300  }
301  if (geometry_.HasPointNormals() &&
305  "Tensor point cloud normals must have DType of Float32 not {}. "
306  "Converting.",
307  normals.GetDtype().ToString());
309  }
310  if (geometry_.HasPointColors() &&
314  "Tensor point cloud colors must have DType of Float32 not {}. "
315  "Converting.",
316  colors.GetDtype().ToString());
318  if (colors.GetDtype() == core::UInt8) {
320  }
321  }
322 }
323 
324 RenderableManager::PrimitiveType TPointCloudBuffersBuilder::GetPrimitiveType()
325  const {
326  return RenderableManager::PrimitiveType::POINTS;
327 }
328 
330  auto& engine = EngineInstance::GetInstance();
331  auto& resource_mgr = EngineInstance::GetResourceManager();
332 
333  const auto& points = geometry_.GetPointPositions();
334  const size_t n_vertices = points.GetLength();
335 
336  // We use CUSTOM0 for tangents along with TANGENTS attribute
337  // because Filament would optimize out anything about normals and lightning
338  // from unlit materials. But our shader for normals visualizing is unlit, so
339  // we need to use this workaround.
340  VertexBuffer* vbuf = VertexBuffer::Builder()
341  .bufferCount(4)
342  .vertexCount(uint32_t(n_vertices))
343  .attribute(VertexAttribute::POSITION, 0,
344  VertexBuffer::AttributeType::FLOAT3)
345  .normalized(VertexAttribute::COLOR)
346  .attribute(VertexAttribute::COLOR, 1,
347  VertexBuffer::AttributeType::FLOAT3)
348  .normalized(VertexAttribute::TANGENTS)
349  .attribute(VertexAttribute::TANGENTS, 2,
350  VertexBuffer::AttributeType::FLOAT4)
351  .attribute(VertexAttribute::CUSTOM0, 2,
352  VertexBuffer::AttributeType::FLOAT4)
353  .attribute(VertexAttribute::UV0, 3,
354  VertexBuffer::AttributeType::FLOAT2)
355  .build(engine);
356 
357  VertexBufferHandle vb_handle;
358  if (vbuf) {
359  vb_handle = resource_mgr.AddVertexBuffer(vbuf);
360  } else {
361  return {};
362  }
363 
364  const size_t vertex_array_size = n_vertices * 3 * sizeof(float);
365  float* vertex_array = static_cast<float*>(malloc(vertex_array_size));
366  memcpy(vertex_array, points.GetDataPtr(), vertex_array_size);
367  VertexBuffer::BufferDescriptor pts_descriptor(
368  vertex_array, vertex_array_size,
370  vbuf->setBufferAt(engine, 0, std::move(pts_descriptor));
371 
372  const size_t color_array_size = n_vertices * 3 * sizeof(float);
373  if (geometry_.HasPointColors()) {
374  float* color_array = static_cast<float*>(malloc(color_array_size));
375  memcpy(color_array, geometry_.GetPointColors().GetDataPtr(),
376  color_array_size);
377  VertexBuffer::BufferDescriptor color_descriptor(
378  color_array, color_array_size,
380  vbuf->setBufferAt(engine, 1, std::move(color_descriptor));
381  } else {
382  float* color_array = static_cast<float*>(malloc(color_array_size));
383  for (size_t i = 0; i < n_vertices * 3; ++i) {
384  color_array[i] = 1.f;
385  }
386  VertexBuffer::BufferDescriptor color_descriptor(
387  color_array, color_array_size,
389  vbuf->setBufferAt(engine, 1, std::move(color_descriptor));
390  }
391 
392  const size_t normal_array_size = n_vertices * 4 * sizeof(float);
393  if (geometry_.HasPointNormals()) {
394  const auto& normals = geometry_.GetPointNormals();
395 
396  // Converting normals to Filament type - quaternions
397  auto float4v_tangents =
398  static_cast<math::quatf*>(malloc(normal_array_size));
399  auto orientation =
400  filament::geometry::SurfaceOrientation::Builder()
401  .vertexCount(n_vertices)
402  .normals(reinterpret_cast<const math::float3*>(
403  normals.GetDataPtr()))
404  .build();
405  orientation->getQuats(float4v_tangents, n_vertices);
406  VertexBuffer::BufferDescriptor normals_descriptor(
407  float4v_tangents, normal_array_size,
409  vbuf->setBufferAt(engine, 2, std::move(normals_descriptor));
410  delete orientation;
411  } else {
412  float* normal_array = static_cast<float*>(malloc(normal_array_size));
413  float* normal_ptr = normal_array;
414  for (size_t i = 0; i < n_vertices; ++i) {
415  *normal_ptr++ = 0.f;
416  *normal_ptr++ = 0.f;
417  *normal_ptr++ = 0.f;
418  *normal_ptr++ = 1.f;
419  }
420  VertexBuffer::BufferDescriptor normals_descriptor(
421  normal_array, normal_array_size,
423  vbuf->setBufferAt(engine, 2, std::move(normals_descriptor));
424  }
425 
426  const size_t uv_array_size = n_vertices * 2 * sizeof(float);
427  float* uv_array = static_cast<float*>(malloc(uv_array_size));
428  if (geometry_.HasPointAttr("uv")) {
429  const float* uv_src =
430  geometry_.GetPointAttr("uv").GetDataPtr<const float>();
431  memcpy(uv_array, uv_src, uv_array_size);
432  } else if (geometry_.HasPointAttr("__visualization_scalar")) {
433  // Update in FilamentScene::UpdateGeometry(), too.
434  memset(uv_array, 0, uv_array_size);
435  auto vis_scalars =
436  geometry_.GetPointAttr("__visualization_scalar").Contiguous();
437  const float* src = vis_scalars.GetDataPtr<const float>();
438  const size_t n = 2 * n_vertices;
439  for (size_t i = 0; i < n; i += 2) {
440  uv_array[i] = *src++;
441  }
442  } else {
443  memset(uv_array, 0, uv_array_size);
444  }
445  VertexBuffer::BufferDescriptor uv_descriptor(
446  uv_array, uv_array_size, GeometryBuffersBuilder::DeallocateBuffer);
447  vbuf->setBufferAt(engine, 3, std::move(uv_descriptor));
448 
449  auto ib_handle = CreateIndexBuffer(n_vertices);
450 
451  IndexBufferHandle downsampled_handle;
452  if (n_vertices >= downsample_threshold_) {
453  downsampled_handle =
455  }
456 
457  return std::make_tuple(vb_handle, ib_handle, downsampled_handle);
458 }
459 
461  auto min_bounds = geometry_.GetMinBound();
462  auto max_bounds = geometry_.GetMaxBound();
463  auto* min_bounds_float = min_bounds.GetDataPtr<float>();
464  auto* max_bounds_float = max_bounds.GetDataPtr<float>();
465 
466  filament::math::float3 min(min_bounds_float[0], min_bounds_float[1],
467  min_bounds_float[2]);
468  filament::math::float3 max(max_bounds_float[0], max_bounds_float[1],
469  max_bounds_float[2]);
470 
471  Box aabb;
472  aabb.set(min, max);
473  if (aabb.isEmpty()) {
474  min.x -= 0.1f;
475  min.y -= 0.1f;
476  min.z -= 0.1f;
477  max.x += 0.1f;
478  max.y += 0.1f;
479  max.z += 0.1f;
480  aabb.set(min, max);
481  }
482  return aabb;
483 }
484 
485 } // namespace rendering
486 } // namespace visualization
487 } // namespace cloudViewer
int points
math::float2 uv
math::quatf tangent
math::float4 color
math::float3 position
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
Eigen::Vector3d getEigenNormal(size_t index) const
bool hasNormals() const override
Returns whether normals are enabled or not.
virtual ccBBox GetAxisAlignedBoundingBox() const override
Returns an axis-aligned bounding box of the geometry.
bool hasColors() const override
Returns whether colors are enabled or not.
Eigen::Vector3d getEigenColor(size_t index) const
unsigned size() const override
Definition: PointCloudTpl.h:38
Eigen::Vector3d getEigenPoint(size_t index) const
Dtype GetDtype() const
Definition: Tensor.h:1164
Tensor To(Dtype dtype, bool copy=false) const
Definition: Tensor.cpp:739
A point cloud contains a list of 3D points.
Definition: PointCloud.h:82
core::Tensor & GetPointNormals()
Get the value of the "normals" attribute. Convenience function.
Definition: PointCloud.h:130
bool HasPointAttr(const std::string &key) const
Definition: PointCloud.h:207
core::Tensor GetMaxBound() const
Returns the max bound for point coordinates.
Definition: PointCloud.cpp:104
core::Tensor GetMinBound() const
Returns the min bound for point coordinates.
Definition: PointCloud.cpp:100
core::Tensor & GetPointPositions()
Get the value of the "positions" attribute. Convenience function.
Definition: PointCloud.h:124
PointCloud To(const core::Device &device, bool copy=false) const
Definition: PointCloud.cpp:112
const TensorMap & GetPointAttr() const
Getter for point_attr_ TensorMap. Used in Pybind.
Definition: PointCloud.h:111
core::Tensor & GetPointColors()
Get the value of the "colors" attribute. Convenience function.
Definition: PointCloud.h:127
static FilamentResourceManager & GetResourceManager()
static IndexBufferHandle CreateIndexBuffer(size_t max_index, size_t n_subsamples=SIZE_MAX)
static void DeallocateBuffer(void *buffer, size_t size, void *user_ptr)
std::tuple< VertexBufferHandle, IndexBufferHandle, IndexBufferHandle > Buffers
filament::RenderableManager::PrimitiveType GetPrimitiveType() const override
filament::RenderableManager::PrimitiveType GetPrimitiveType() const override
TPointCloudBuffersBuilder(const t::geometry::PointCloud &geometry)
double colors[3]
double normals[3]
#define LogWarning(...)
Definition: Logging.h:72
int min(int a, int b)
Definition: cutil_math.h:53
int max(int a, int b)
Definition: cutil_math.h:48
static double dist(double x1, double y1, double x2, double y2)
Definition: lsd.c:207
ccGuiPythonInstance * GetInstance() noexcept
Definition: Runtime.cpp:72
const Dtype UInt8
Definition: Dtype.cpp:48
const Dtype Float32
Definition: Dtype.cpp:42
MiniVec< float, N > floor(const MiniVec< float, N > &a)
Definition: MiniVec.h:75
REHandle< EntityType::IndexBuffer > IndexBufferHandle
Generic file read and write utility for python interface.
#define offsetof(STRUCTURE, FIELD)
Definition: sqlite3.c:14241