Program Listing for File SparseTreesPointCloud.h

Return to documentation for file (include/mola_metric_maps/SparseTreesPointCloud.h)

/*               _
 _ __ ___   ___ | | __ _
| '_ ` _ \ / _ \| |/ _` | Modular Optimization framework for
| | | | | | (_) | | (_| | Localization and mApping (MOLA)
|_| |_| |_|\___/|_|\__,_| https://github.com/MOLAorg/mola

 Copyright (C) 2018-2026 Jose Luis Blanco, University of Almeria,
                         and individual contributors.
 SPDX-License-Identifier: GPL-3.0
 See LICENSE for full license information.
*/

#pragma once

#include <mola_metric_maps/FixedDenseGrid3D.h>
#include <mola_metric_maps/index3d_t.h>
#include <mrpt/core/round.h>
#include <mrpt/img/TColor.h>
#include <mrpt/img/color_maps.h>
#include <mrpt/maps/CMetricMap.h>
#include <mrpt/maps/CSimplePointsMap.h>
#include <mrpt/maps/NearestNeighborsCapable.h>
#include <mrpt/math/TBoundingBox.h>
#include <mrpt/math/TPoint3D.h>

#include <array>
#include <functional>
#include <map>
#include <optional>
#include <shared_mutex>

namespace mola
{
class SparseTreesPointCloud : public mrpt::maps::CMetricMap,
                              public mrpt::maps::NearestNeighborsCapable
{
  DEFINE_SERIALIZABLE(SparseTreesPointCloud, mola)
 public:
  constexpr static uint8_t GLOBAL_ID_SUBVOXEL_BITCOUNT = 20;

  using outer_index3d_t     = index3d_t<int32_t>;
  using inner_plain_index_t = uint32_t;
  using global_plain_index_t = uint64_t;

  inline outer_index3d_t coordToOuterIdx(const mrpt::math::TPoint3Df& pt) const
  {
    return outer_index3d_t(
        static_cast<int32_t>((pt.x + grid_size_half_) * grid_size_inv_),  //
        static_cast<int32_t>((pt.y + grid_size_half_) * grid_size_inv_),  //
        static_cast<int32_t>((pt.z + grid_size_half_) * grid_size_inv_));
  }

  inline mrpt::math::TPoint3Df outerIdxToCoord(const outer_index3d_t idx) const
  {
    return {
        (idx.cx - 0.5f) * grid_size_,  //
        (idx.cy - 0.5f) * grid_size_,  //
        (idx.cz - 0.5f) * grid_size_};
  }

  static inline global_plain_index_t g2plain(const outer_index3d_t& g, int subVoxelIndex = 0)
  {
    constexpr uint64_t SUBVOXEL_MASK = ((1 << GLOBAL_ID_SUBVOXEL_BITCOUNT) - 1);
    constexpr auto     OFF           = GLOBAL_ID_SUBVOXEL_BITCOUNT;
    constexpr int      FBITS         = (64 - OFF) / 3;
    constexpr uint64_t FMASK         = (1 << FBITS) - 1;

    return (static_cast<uint64_t>(subVoxelIndex) & SUBVOXEL_MASK) |
           (static_cast<uint64_t>(g.cx & FMASK) << (OFF + FBITS * 0)) |
           (static_cast<uint64_t>(g.cy & FMASK) << (OFF + FBITS * 1)) |
           (static_cast<uint64_t>(g.cz & FMASK) << (OFF + FBITS * 2));
  }

  SparseTreesPointCloud(float grid_size = 10.0f);

  ~SparseTreesPointCloud();

  void setGridProperties(float grid_size);

  struct GridData
  {
   public:
    GridData() = default;

    auto&       points() { return points_; }
    const auto& points() const { return points_; }

    void insertPoint(const mrpt::math::TPoint3Df& p) { points_.insertPoint(p); }

   private:
    mrpt::maps::CSimplePointsMap points_;
  };

  using grids_map_t = std::map<outer_index3d_t, GridData, index3d_hash<int32_t>>;

  // clear(): available in base class

  inline GridData* gridByOuterIdxs(const outer_index3d_t& oIdx, bool createIfNew)
  {
    // 1) Insert into grid map:
    GridData* grid = nullptr;
    cachedMtx_.lock_shared();

    for (int i = 0; i < CachedData::NUM_CACHED_IDXS; i++)
    {
      if (cached_.lastAccessGrid[i] && cached_.lastAccessIdx[i] == oIdx)
      {
        // Cache hit:
#ifdef USE_DEBUG_PROFILER
        mrpt::system::CTimeLoggerEntry tle(profiler, "insertPoint.cache_hit");
#endif
        grid = cached_.lastAccessGrid[i];
        break;
      }
    }
    cachedMtx_.unlock_shared();

    if (!grid)
    {
      // Cache miss:
#ifdef USE_DEBUG_PROFILER
      mrpt::system::CTimeLoggerEntry tle(profiler, "insertPoint.cache_misss");
#endif

      auto it = grids_.find(oIdx);
      if (it == grids_.end())
      {
        if (!createIfNew)
        {
          return nullptr;
        }

        grid = &grids_[oIdx];  // Create it
      }
      else
      {
        grid = &it->second;  // Use the found grid
      }
      // Add to cache:
      cachedMtx_.lock();

      cached_.lastAccessIdx[cached_.lastAccessNextWrite]  = oIdx;
      cached_.lastAccessGrid[cached_.lastAccessNextWrite] = grid;
      cached_.lastAccessNextWrite++;
      cached_.lastAccessNextWrite &= CachedData::NUM_CACHED_IDX_MASK;

      cachedMtx_.unlock();
    }

    return grid;
  }

  // const version:
  inline const GridData* gridByOuterIdxs(const outer_index3d_t& oIdx, bool createIfNew) const
  {  // reuse the non-const method:
    return const_cast<SparseTreesPointCloud*>(this)->gridByOuterIdxs(oIdx, createIfNew);
  }

  void insertPoint(const mrpt::math::TPoint3Df& pt)
  {
    auto* g = gridByOuterIdxs(coordToOuterIdx(pt), true);
    g->insertPoint(pt);

    // Also, update bbox:
    if (!cached_.boundingBox_.has_value())
    {
      cached_.boundingBox_.emplace(pt, pt);
    }
    else
    {
      cached_.boundingBox_->updateWithPoint(pt);
    }
  }

  const grids_map_t& grids() const { return grids_; }

  mrpt::math::TBoundingBoxf boundingBox() const override;

  void visitAllPoints(const std::function<void(const mrpt::math::TPoint3Df&)>& f) const;

  void visitAllGrids(const std::function<void(const outer_index3d_t&, const GridData&)>& f) const;

  bool saveToTextFile(const std::string& file) const;

  void eraseGridsFartherThan(const mrpt::math::TPoint3Df& pt, const float distanceMeters);

  void                 nn_prepare_for_2d_queries() const override;
  void                 nn_prepare_for_3d_queries() const override;
  [[nodiscard]] bool   nn_has_indices_or_ids() const override;
  [[nodiscard]] size_t nn_index_count() const override;
  [[nodiscard]] bool   nn_single_search(
        const mrpt::math::TPoint3Df& query, mrpt::math::TPoint3Df& result, float& out_dist_sqr,
        uint64_t& resultIndexOrID) const override;
  [[nodiscard]] bool nn_single_search(
      const mrpt::math::TPoint2Df& query, mrpt::math::TPoint2Df& result, float& out_dist_sqr,
      uint64_t& resultIndexOrID) const override;
  void nn_multiple_search(
      const mrpt::math::TPoint3Df& query, const size_t N,
      std::vector<mrpt::math::TPoint3Df>& results, std::vector<float>& out_dists_sqr,
      std::vector<uint64_t>& resultIndicesOrIDs) const override;
  void nn_multiple_search(
      const mrpt::math::TPoint2Df& query, const size_t N,
      std::vector<mrpt::math::TPoint2Df>& results, std::vector<float>& out_dists_sqr,
      std::vector<uint64_t>& resultIndicesOrIDs) const override;
  void nn_radius_search(
      const mrpt::math::TPoint3Df& query, const float search_radius_sqr,
      std::vector<mrpt::math::TPoint3Df>& results, std::vector<float>& out_dists_sqr,
      std::vector<uint64_t>& resultIndicesOrIDs, size_t maxPoints) const override;
  void nn_radius_search(
      const mrpt::math::TPoint2Df& query, const float search_radius_sqr,
      std::vector<mrpt::math::TPoint2Df>& results, std::vector<float>& out_dists_sqr,
      std::vector<uint64_t>& resultIndicesOrIDs, size_t maxPoints) const override;
  std::string asString() const override;

  void getVisualizationInto(mrpt::opengl::CSetOfObjects& outObj) const override;

  bool isEmpty() const override;

  void saveMetricMapRepresentationToFile(const std::string& filNamePrefix) const override;

  struct TInsertionOptions : public mrpt::config::CLoadableOptions
  {
    TInsertionOptions() = default;

    void loadFromConfigFile(
        const mrpt::config::CConfigFileBase& source,
        const std::string&                   section) override;  // See base docs
    void dumpToTextStream(std::ostream& out) const override;  // See base docs

    void writeToStream(mrpt::serialization::CArchive& out) const;
    void readFromStream(mrpt::serialization::CArchive& in);

    float minimum_points_clearance = 0.20f;

    float remove_submaps_farther_than = .0f;
  };
  TInsertionOptions insertionOptions;

  struct TLikelihoodOptions : public mrpt::config::CLoadableOptions
  {
    TLikelihoodOptions() = default;

    void loadFromConfigFile(
        const mrpt::config::CConfigFileBase& source,
        const std::string&                   section) override;  // See base docs
    void dumpToTextStream(std::ostream& out) const override;  // See base docs

    void writeToStream(mrpt::serialization::CArchive& out) const;
    void readFromStream(mrpt::serialization::CArchive& in);

    double sigma_dist = 0.5;

    double max_corr_distance = 1.0;

    uint32_t decimation = 10;
  };
  TLikelihoodOptions likelihoodOptions;

  struct TRenderOptions : public mrpt::config::CLoadableOptions
  {
    void loadFromConfigFile(
        const mrpt::config::CConfigFileBase& source,
        const std::string&                   section) override;  // See base docs
    void dumpToTextStream(std::ostream& out) const override;  // See base docs

    void writeToStream(mrpt::serialization::CArchive& out) const;
    void readFromStream(mrpt::serialization::CArchive& in);

    float point_size = 1.0f;

    bool show_inner_grid_boxes = false;

    mrpt::img::TColorf color{.0f, .0f, 1.0f};

    mrpt::img::TColormap colormap = mrpt::img::cmHOT;

    std::string recolorByPointField = "intensity";
  };
  TRenderOptions renderOptions;

 public:
  // Interface for use within a mrpt::maps::CMultiMetricMap:
  MAP_DEFINITION_START(SparseTreesPointCloud)
  float grid_size = 10.0f;

  mola::SparseTreesPointCloud::TInsertionOptions  insertionOpts;
  mola::SparseTreesPointCloud::TLikelihoodOptions likelihoodOpts;
  mola::SparseTreesPointCloud::TRenderOptions     renderOpts;
  MAP_DEFINITION_END(SparseTreesPointCloud)

 private:
  float grid_size_ = 10.0f;

  // Calculated from the above, in setVoxelProperties()
  float                 grid_size_inv_  = 1.0f / grid_size_;
  float                 grid_size_half_ = 0.5f * grid_size_;
  mrpt::math::TVector3D gridVector_;

  grids_map_t grids_;

  struct CachedData
  {
    CachedData() = default;

    void reset() { *this = CachedData(); }

    mutable std::optional<mrpt::math::TBoundingBoxf> boundingBox_;

    // 2 bits seems to be the optimum for typical cases:
    constexpr static int CBITS               = 2;
    constexpr static int NUM_CACHED_IDXS     = 1 << CBITS;
    constexpr static int NUM_CACHED_IDX_MASK = NUM_CACHED_IDXS - 1;

    int             lastAccessNextWrite = 0;
    outer_index3d_t lastAccessIdx[NUM_CACHED_IDXS];
    GridData*       lastAccessGrid[NUM_CACHED_IDXS] = {nullptr};
  };

  CachedData        cached_;
  std::shared_mutex cachedMtx_;

 protected:
  // See docs in base CMetricMap class:
  void internal_clear() override;

 private:
  // See docs in base CMetricMap class:
  bool internal_insertObservation(
      const mrpt::obs::CObservation&                   obs,
      const std::optional<const mrpt::poses::CPose3D>& robotPose = std::nullopt) override;
  // See docs in base class
  double internal_computeObservationLikelihood(
      const mrpt::obs::CObservation& obs, const mrpt::poses::CPose3D& takenFrom) const override;

  double internal_computeObservationLikelihoodPointCloud3D(
      const mrpt::poses::CPose3D& pc_in_map, const float* xs, const float* ys, const float* zs,
      const std::size_t num_pts) const;

  void internal_insertPointCloud3D(
      const mrpt::poses::CPose3D& pc_in_map, const float* xs, const float* ys, const float* zs,
      const std::size_t num_pts);

  // See docs in base class
  bool internal_canComputeObservationLikelihood(const mrpt::obs::CObservation& obs) const override;
};

}  // namespace mola