Skip to content

File test_contour_utils.cpp

File List > contour > tests > test_contour_utils.cpp

Go to the documentation of this file

#include <gtest/gtest.h>

#include <cmath>
#include <set>

#include "contour/contour.hpp"
#include "utilities/coordinate.hpp"

// Test round_down function
TEST(ContourUtils, RoundDown) {
  // Test positive values
  EXPECT_DOUBLE_EQ(round_down(10.0, 5.0), 10.0);
  EXPECT_DOUBLE_EQ(round_down(12.0, 5.0), 10.0);
  EXPECT_DOUBLE_EQ(round_down(14.9, 5.0), 10.0);
  EXPECT_DOUBLE_EQ(round_down(15.0, 5.0), 15.0);
  EXPECT_DOUBLE_EQ(round_down(17.0, 5.0), 15.0);

  // Test negative values
  EXPECT_DOUBLE_EQ(round_down(-10.0, 5.0), -10.0);
  EXPECT_DOUBLE_EQ(round_down(-12.0, 5.0), -15.0);
  EXPECT_DOUBLE_EQ(round_down(-14.9, 5.0), -15.0);
  EXPECT_DOUBLE_EQ(round_down(-15.0, 5.0), -15.0);

  // Test with different intervals
  EXPECT_DOUBLE_EQ(round_down(10.0, 2.0), 10.0);
  EXPECT_DOUBLE_EQ(round_down(11.0, 2.0), 10.0);
  EXPECT_DOUBLE_EQ(round_down(12.0, 2.0), 12.0);

  // Test zero
  EXPECT_DOUBLE_EQ(round_down(0.0, 5.0), 0.0);
}

// Test crosses_contour function
TEST(ContourUtils, CrossesContour) {
  // Test cases that should cross
  EXPECT_TRUE(crosses_contour({5.0, 15.0}, 10.0));   // Crosses 10.0
  EXPECT_TRUE(crosses_contour({15.0, 5.0}, 10.0));   // Reversed
  EXPECT_TRUE(crosses_contour({8.0, 12.0}, 10.0));   // Crosses 10.0
  EXPECT_TRUE(crosses_contour({18.0, 22.0}, 10.0));  // Crosses 20.0

  // Test cases that should not cross
  EXPECT_FALSE(crosses_contour({5.0, 8.0}, 10.0));    // Both below 10.0
  EXPECT_FALSE(crosses_contour({12.0, 15.0}, 10.0));  // Both above 10.0, below 20.0
  EXPECT_FALSE(crosses_contour({10.0, 15.0}, 10.0));  // Starts exactly on contour - doesn't cross
  EXPECT_TRUE(crosses_contour({5.0, 10.0}, 10.0));    // Ends exactly on contour - crosses

  // Test negative values
  EXPECT_TRUE(crosses_contour({-15.0, -5.0}, 10.0));    // Crosses -10.0
  EXPECT_TRUE(crosses_contour({-5.0, -15.0}, 10.0));    // Reversed
  EXPECT_FALSE(crosses_contour({-15.0, -12.0}, 10.0));  // Both below -10.0, doesn't cross
  EXPECT_FALSE(crosses_contour({-8.0, -5.0}, 10.0));   // Both above -10.0, below 0.0, doesn't cross
  EXPECT_TRUE(crosses_contour({-5.0, 5.0}, 10.0));     // Crosses 0.0 (negative to positive)
  EXPECT_TRUE(crosses_contour({-12.0, -8.0}, 10.0));   // Crosses -10.0
  EXPECT_FALSE(crosses_contour({-10.0, -5.0}, 10.0));  // Starts exactly on contour - doesn't cross
  EXPECT_TRUE(crosses_contour({-15.0, -10.0}, 10.0));  // Ends exactly on contour - crosses

  // Test with different intervals
  EXPECT_TRUE(crosses_contour({4.0, 6.0}, 5.0));   // Crosses 5.0
  EXPECT_FALSE(crosses_contour({4.0, 4.5}, 5.0));  // Doesn't cross
}

// Test get_contour_heights function
TEST(ContourUtils, GetContourHeights) {
  // Test basic case
  std::set<double> heights1 = get_contour_heights({5.0, 25.0}, 10.0);
  EXPECT_EQ(heights1.size(), 2);
  EXPECT_TRUE(heights1.find(10.0) != heights1.end());
  EXPECT_TRUE(heights1.find(20.0) != heights1.end());

  // Test reversed
  std::set<double> heights2 = get_contour_heights({25.0, 5.0}, 10.0);
  EXPECT_EQ(heights2.size(), 2);
  EXPECT_TRUE(heights2.find(10.0) != heights2.end());
  EXPECT_TRUE(heights2.find(20.0) != heights2.end());

  // Test single crossing
  std::set<double> heights3 = get_contour_heights({8.0, 12.0}, 10.0);
  EXPECT_EQ(heights3.size(), 1);
  EXPECT_TRUE(heights3.find(10.0) != heights3.end());

  // Test no crossing
  std::set<double> heights4 = get_contour_heights({5.0, 8.0}, 10.0);
  EXPECT_EQ(heights4.size(), 0);

  // Test multiple crossings
  std::set<double> heights5 = get_contour_heights({5.0, 45.0}, 10.0);
  EXPECT_EQ(heights5.size(), 4);
  EXPECT_TRUE(heights5.find(10.0) != heights5.end());
  EXPECT_TRUE(heights5.find(20.0) != heights5.end());
  EXPECT_TRUE(heights5.find(30.0) != heights5.end());
  EXPECT_TRUE(heights5.find(40.0) != heights5.end());

  // Test with different intervals
  std::set<double> heights6 = get_contour_heights({2.0, 8.0}, 2.0);
  // round_down(8.0, 2.0) = 8.0, then loop: 8.0, 6.0, 4.0 (3 values)
  EXPECT_EQ(heights6.size(), 3);
  EXPECT_TRUE(heights6.find(4.0) != heights6.end());
  EXPECT_TRUE(heights6.find(6.0) != heights6.end());
  EXPECT_TRUE(heights6.find(8.0) != heights6.end());
}

// Test interpolate_coordinates function
TEST(ContourUtils, InterpolateCoordinates) {
  Coordinate2D<double> a(0.0, 0.0);
  Coordinate2D<double> b(10.0, 10.0);

  // Test midpoint (target between a and b)
  Coordinate2D<double> mid = interpolate_coordinates(a, b, 0.0, 10.0, 5.0);
  EXPECT_NEAR(mid.x(), 5.0, 1e-10);
  EXPECT_NEAR(mid.y(), 5.0, 1e-10);

  // Test at a
  Coordinate2D<double> at_a = interpolate_coordinates(a, b, 0.0, 10.0, 0.0);
  EXPECT_NEAR(at_a.x(), 0.0, 1e-10);
  EXPECT_NEAR(at_a.y(), 0.0, 1e-10);

  // Test at b
  Coordinate2D<double> at_b = interpolate_coordinates(a, b, 0.0, 10.0, 10.0);
  EXPECT_NEAR(at_b.x(), 10.0, 1e-10);
  EXPECT_NEAR(at_b.y(), 10.0, 1e-10);

  // Test when a_val == b_val (should return midpoint)
  Coordinate2D<double> equal = interpolate_coordinates(a, b, 5.0, 5.0, 5.0);
  EXPECT_NEAR(equal.x(), 5.0, 1e-10);
  EXPECT_NEAR(equal.y(), 5.0, 1e-10);

  // Test different values
  Coordinate2D<double> c(0.0, 0.0);
  Coordinate2D<double> d(20.0, 0.0);
  Coordinate2D<double> result = interpolate_coordinates(c, d, 0.0, 20.0, 10.0);
  EXPECT_NEAR(result.x(), 10.0, 1e-10);
  EXPECT_NEAR(result.y(), 0.0, 1e-10);

  // Test interpolation closer to a
  Coordinate2D<double> close_a = interpolate_coordinates(a, b, 0.0, 10.0, 2.0);
  EXPECT_NEAR(close_a.x(), 2.0, 1e-10);
  EXPECT_NEAR(close_a.y(), 2.0, 1e-10);

  // Test interpolation closer to b
  Coordinate2D<double> close_b = interpolate_coordinates(a, b, 0.0, 10.0, 8.0);
  EXPECT_NEAR(close_b.x(), 8.0, 1e-10);
  EXPECT_NEAR(close_b.y(), 8.0, 1e-10);
}

// Test Contour class basic functionality
TEST(ContourUtils, ContourBasic) {
  std::vector<Coordinate2D<double>> points = {
      Coordinate2D<double>(0.0, 0.0), Coordinate2D<double>(1.0, 0.0),
      Coordinate2D<double>(1.0, 1.0), Coordinate2D<double>(0.0, 1.0)};

  Contour contour(10.0, std::move(points));
  EXPECT_DOUBLE_EQ(contour.height(), 10.0);
  EXPECT_EQ(contour.points().size(), 4);
  EXPECT_FALSE(contour.is_loop());  // Not a loop because first != last

  // Create a loop
  std::vector<Coordinate2D<double>> loop_points = {
      Coordinate2D<double>(0.0, 0.0), Coordinate2D<double>(1.0, 0.0),
      Coordinate2D<double>(1.0, 1.0), Coordinate2D<double>(0.0, 1.0),
      Coordinate2D<double>(0.0, 0.0)  // Close the loop
  };

  Contour loop_contour(10.0, std::move(loop_points));
  EXPECT_TRUE(loop_contour.is_loop());

  // Test empty contour
  std::vector<Coordinate2D<double>> empty_points;
  Contour empty_contour(5.0, std::move(empty_points));
  EXPECT_DOUBLE_EQ(empty_contour.height(), 5.0);
  EXPECT_EQ(empty_contour.points().size(), 0);
  EXPECT_FALSE(empty_contour.is_loop());
}