123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
- // SPDX-License-Identifier: MIT
- #include "UnitTestFramework.h"
- #include <Jolt/Geometry/ConvexHullBuilder.h>
- TEST_SUITE("ConvexHullBuilderTest")
- {
- constexpr float cTolerance = 1.0e-3f;
- using Face = ConvexHullBuilder::Face;
- using Positions = ConvexHullBuilder::Positions;
- TEST_CASE("TestDegenerate")
- {
- const char *error = nullptr;
- {
- // Too few points / coinciding points should be degenerate
- Positions positions { Vec3(1, 2, 3) };
- ConvexHullBuilder builder(positions);
- CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::TooFewPoints);
- positions.push_back(Vec3(1 + 0.5f * cTolerance, 2, 3));
- CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::TooFewPoints);
- positions.push_back(Vec3(1, 2 + 0.5f * cTolerance, 3));
- CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Degenerate);
- positions.push_back(Vec3(1, 2, 3 + 0.5f * cTolerance));
- CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Degenerate);
- }
- {
- // A line should be degenerate
- Positions positions;
- for (float v = 0.0f; v < 1.01f; v += 0.1f)
- positions.push_back(Vec3(v, 0, 0));
- ConvexHullBuilder builder(positions);
- CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Degenerate);
- }
- }
- TEST_CASE("Test2DHull")
- {
- const char *error = nullptr;
- {
- // A triangle
- Positions positions { Vec3(-1, 0, -1), Vec3(1, 0, -1), Vec3(-1, 0, 1) };
- ConvexHullBuilder builder(positions);
- CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Success);
- CHECK(builder.GetNumVerticesUsed() == 3);
- CHECK(builder.GetFaces().size() == 2);
- CHECK(builder.ContainsFace({ 0, 1, 2 }));
- CHECK(builder.ContainsFace({ 2, 1, 0 }));
- }
- {
- // A quad with many interior points
- Positions positions;
- for (int x = 0; x < 10; ++x)
- for (int z = 0; z < 10; ++z)
- positions.push_back(Vec3(0.1f * x, 0, 1.0f * 0.2f * z));
- ConvexHullBuilder builder(positions);
- CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Success);
- CHECK(builder.GetNumVerticesUsed() == 4);
- CHECK(builder.GetFaces().size() == 2);
- CHECK(builder.ContainsFace({ 0, 9, 99, 90 }));
- CHECK(builder.ContainsFace({ 90, 99, 9, 0 }));
- }
- {
- // Add disc with many interior points
- Positions positions;
- for (int r = 0; r < 10; ++r)
- for (int phi = 0; phi < 10; ++phi)
- {
- float f_r = 2.0f * r;
- float f_phi = 2.0f * JPH_PI * phi / 10;
- positions.push_back(Vec3(f_r * Cos(f_phi), f_r * Sin(f_phi), 0));
- }
- ConvexHullBuilder builder(positions);
- CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Success);
- CHECK(builder.GetNumVerticesUsed() == 10);
- CHECK(builder.GetFaces().size() == 2);
- CHECK(builder.ContainsFace({ 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }));
- CHECK(builder.ContainsFace({ 99, 98, 97, 96, 95, 94, 93, 92, 91, 90 }));
- }
- }
- TEST_CASE("Test3DHull")
- {
- const char *error = nullptr;
- {
- // A cube with lots of interior points
- Positions positions;
- for (int x = 0; x < 10; ++x)
- for (int y = 0; y < 10; ++y)
- for (int z = 0; z < 10; ++z)
- positions.push_back(Vec3(0.1f * x, 1.0f + 0.2f * y, 2.0f * 0.3f * z));
- ConvexHullBuilder builder(positions);
- CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Success);
- CHECK(builder.GetNumVerticesUsed() == 8);
- CHECK(builder.GetFaces().size() == 6);
- CHECK(builder.ContainsFace({ 0, 9, 99, 90 }));
- CHECK(builder.ContainsFace({ 0, 90, 990, 900 }));
- CHECK(builder.ContainsFace({ 900, 990, 999, 909 }));
- CHECK(builder.ContainsFace({ 9, 909, 999, 99 }));
- CHECK(builder.ContainsFace({ 90, 99, 999, 990 }));
- CHECK(builder.ContainsFace({ 0, 900, 909, 9 }));
- }
- {
- // Add sphere with many interior points
- Positions positions;
- for (int r = 0; r < 10; ++r)
- for (int phi = 0; phi < 10; ++phi)
- for (int theta = 0; theta < 10; ++theta)
- {
- float f_r = 2.0f * r;
- float f_phi = 2.0f * JPH_PI * phi / 10; // [0, 2 PI)
- float f_theta = JPH_PI * theta / 9; // [0, PI] (inclusive!)
- positions.push_back(f_r * Vec3::sUnitSpherical(f_theta, f_phi));
- }
- ConvexHullBuilder builder(positions);
- CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Success);
- CHECK(builder.GetNumVerticesUsed() == 82); // The two ends of the sphere have 10 points that have the same position
- // Too many faces, calculate the error instead
- Face *error_face;
- float max_error, coplanar_distance;
- int error_position_idx;
- builder.DetermineMaxError(error_face, max_error, error_position_idx, coplanar_distance);
- CHECK(max_error < max(coplanar_distance, cTolerance));
- }
- }
- TEST_CASE("TestRandomHull")
- {
- const char *error = nullptr;
- UnitTestRandom random(0x1ee7c0de);
- uniform_real_distribution<float> zero_one(0.0f, 1.0f);
- uniform_real_distribution<float> zero_two(0.0f, 2.0f);
- uniform_real_distribution<float> scale_start(0.1f, 0.5f);
- uniform_real_distribution<float> scale_range(0.1f, 2.0f);
- for (int iteration = 0; iteration < 100; ++iteration)
- {
- // Define vertex scale
- float start = scale_start(random);
- uniform_real_distribution<float> vertex_scale(start, start + scale_range(random));
- // Define shape scale to make shape less sphere like
- uniform_real_distribution<float> shape_scale(0.1f, 1.0f);
- Vec3 scale(shape_scale(random), shape_scale(random), shape_scale(random));
- // Add some random points
- Positions positions;
- for (int i = 0; i < 100; ++i)
- {
- // Add random point
- Vec3 p1 = vertex_scale(random) * Vec3::sRandom(random) * scale;
- positions.push_back(p1);
- // Point close to p1
- Vec3 p2 = p1 + cTolerance * zero_two(random) * Vec3::sRandom(random);
- positions.push_back(p2);
- // Point on a line to another point
- float fraction = zero_one(random);
- Vec3 p3 = fraction * p1 + (1.0f - fraction) * positions[random() % positions.size()];
- positions.push_back(p3);
- // Point close to p3
- Vec3 p4 = p3 + cTolerance * zero_two(random) * Vec3::sRandom(random);
- positions.push_back(p4);
- }
- // Build hull
- ConvexHullBuilder builder(positions);
- CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Success);
- // Calculate error
- Face *error_face;
- float max_error, coplanar_distance;
- int error_position_idx;
- builder.DetermineMaxError(error_face, max_error, error_position_idx, coplanar_distance);
- CHECK(max_error < max(coplanar_distance, 1.2f * cTolerance));
- }
- }
- }
|