Skip to content

File test_octree.cpp

File List > gui > tests > test_octree.cpp

Go to the documentation of this file

#include <gtest/gtest.h>

#include "gui/point_octree.hpp"

TEST(PointOctree, EmptyOctreeHasZeroPoints) {
  PointOctree octree;
  EXPECT_EQ(octree.total_points(), 0);
}

TEST(PointOctree, InsertBatchStoresPoints) {
  const Extent3D bounds(-10, 10, -10, 10, -10, 10);
  PointOctree octree(bounds);

  OctreePointVector points(100);
  for (size_t i = 0; i < points.size(); ++i) {
    points[i].x = static_cast<float>(i % 10 - 5);
    points[i].y = static_cast<float>(i / 10 - 5);
    points[i].z = 0.0f;
  }

  octree.insert_batch(std::move(points));
  EXPECT_EQ(octree.total_points(), 100);
}

TEST(PointOctree, NodePointCountMatchesRange) {
  const Extent3D bounds(-10, 10, -10, 10, -10, 10);
  PointOctree octree(bounds);

  OctreePointVector points(50);
  for (size_t i = 0; i < points.size(); ++i) {
    points[i].x = static_cast<float>(i) * 0.1f;
    points[i].y = 0.0f;
    points[i].z = 0.0f;
  }

  octree.insert_batch(std::move(points));

  const auto* root = octree.root();
  ASSERT_NE(root, nullptr);

  size_t total_leaf_points = 0;
  std::function<void(const PointOctreeNode&)> count_leaves = [&](const PointOctreeNode& node) {
    if (!node.has_children()) {
      total_leaf_points += node.point_count();
      EXPECT_EQ(node.point_count(), node.end_index - node.begin_index);
    } else {
      for (const auto& child : node.children) {
        if (child) {
          count_leaves(*child);
        }
      }
    }
  };
  count_leaves(*root);
  EXPECT_EQ(total_leaf_points, 50);
}

TEST(PointOctree, NodeDrawChunkSizeRespectsQuality) {
  const size_t point_count = 1000;
  const double distance = 200.0;  // Far enough that quality 1.0 doesn't saturate

  // Quality 1.0 at distance 200 should give < full count
  const size_t chunk_low = PointOctree::node_draw_chunk_size(point_count, distance, 1.0);
  EXPECT_LT(chunk_low, point_count);
  EXPECT_GT(chunk_low, 0);

  // Higher quality gives more points
  const size_t chunk_high = PointOctree::node_draw_chunk_size(point_count, distance, 4.0);
  EXPECT_GT(chunk_high, chunk_low);

  // Very high quality saturates at point_count
  const size_t chunk_max = PointOctree::node_draw_chunk_size(point_count, distance, 1000.0);
  EXPECT_EQ(chunk_max, point_count);
}

TEST(PointOctree, NodeDrawChunkSizeRespectsDistance) {
  const size_t point_count = 1000;
  const double quality = 1.0;

  // Closer distance gives more points
  const size_t chunk_near = PointOctree::node_draw_chunk_size(point_count, 20.0, quality);
  const size_t chunk_far = PointOctree::node_draw_chunk_size(point_count, 200.0, quality);

  EXPECT_GT(chunk_near, chunk_far);
}

TEST(PointOctree, ShuffleDoesNotLosePoints) {
  const Extent3D bounds(-10, 10, -10, 10, -10, 10);
  PointOctree octree(bounds);

  OctreePointVector points(100);
  for (size_t i = 0; i < points.size(); ++i) {
    points[i].x = static_cast<float>(i % 10 - 5);
    points[i].y = static_cast<float>(i / 10 - 5);
    points[i].z = static_cast<float>(i);
    points[i].classification = static_cast<uint8_t>(i % 5);
  }

  octree.insert_batch(std::move(points));
  const size_t count_before = octree.total_points();

  octree.shuffle_leaves();
  EXPECT_EQ(octree.total_points(), count_before);

  // Verify all classifications still present (shuffled, not lost)
  std::array<int, 5> class_counts{};
  for (const auto& pt : octree.points()) {
    class_counts[pt.classification % 5]++;
  }
  for (int count : class_counts) {
    EXPECT_EQ(count, 20);
  }
}

TEST(PointOctree, CancellationPreventsBuild) {
  const Extent3D bounds(-100, 100, -100, 100, -100, 100);
  PointOctree octree(bounds);

  OctreePointVector points(100'000);
  for (size_t i = 0; i < points.size(); ++i) {
    points[i].x = static_cast<float>((i % 1000) - 500);
    points[i].y = static_cast<float>((i / 1000) - 50);
    points[i].z = 0.0f;
  }

  std::atomic<bool> cancel{true};
  octree.insert_batch(std::move(points), {}, &cancel);

  // Build should abort early, so not all points will be in leaves
  EXPECT_LE(octree.total_points(), 100'000);
}