Added test for VoxelBlockSerializerInternal and RegionFile; added namespaces

master
Marc Gilleron 2021-12-17 19:30:23 +00:00
parent c2b0884126
commit e46b1a023d
8 changed files with 242 additions and 33 deletions

View File

@ -153,7 +153,7 @@ void register_voxel_types() {
#endif #endif
#ifdef VOXEL_RUN_TESTS #ifdef VOXEL_RUN_TESTS
run_voxel_tests(); zylann::voxel::tests::run_voxel_tests();
#endif #endif
} }

View File

@ -5,22 +5,21 @@
#include <core/io/image.h> #include <core/io/image.h>
#include <modules/opensimplex/open_simplex_noise.h> #include <modules/opensimplex/open_simplex_noise.h>
namespace NoiseTests { namespace zylann::voxel::noise_tests {
const int ITERATIONS = 1000000; const int ITERATIONS = 1000000;
const int STEP_RESOLUTION_COUNT = 100; const int STEP_RESOLUTION_COUNT = 100;
const double STEP_MIN = 0.0001; const double STEP_MIN = 0.0001;
const double STEP_MAX = 0.01; const double STEP_MAX = 0.01;
enum Tests { enum Tests { //
TEST_MIN_MAX = 1, TEST_MIN_MAX = 1,
TEST_DERIVATIVES = 2 TEST_DERIVATIVES = 2
}; };
// Sample a maximum change across the given step. // Sample a maximum change across the given step.
// The result is not normalized for performance. // The result is not normalized for performance.
template <typename F2, typename FloatT> template <typename F2, typename FloatT> FloatT get_derivative(FloatT x, FloatT y, FloatT step, F2 noise_func_2d) {
FloatT get_derivative(FloatT x, FloatT y, FloatT step, F2 noise_func_2d) {
FloatT n0, n1, d; FloatT n0, n1, d;
FloatT max_derivative = 0.0; FloatT max_derivative = 0.0;
@ -69,8 +68,7 @@ FloatT get_derivative(FloatT x, FloatT y, FloatT z, FloatT step, F3 noise_func_3
return max_derivative; return max_derivative;
} }
template <typename F2, typename F3, typename FloatT> template <typename F2, typename F3, typename FloatT> void test_min_max(F2 noise_func_2d, F3 noise_func_3d) {
void test_min_max(F2 noise_func_2d, F3 noise_func_3d) {
FloatT min_value_2d = std::numeric_limits<FloatT>::max(); FloatT min_value_2d = std::numeric_limits<FloatT>::max();
FloatT max_value_2d = std::numeric_limits<FloatT>::min(); FloatT max_value_2d = std::numeric_limits<FloatT>::min();
@ -98,8 +96,7 @@ void test_min_max(F2 noise_func_2d, F3 noise_func_3d) {
} }
// Generic analysis for noise functions // Generic analysis for noise functions
template <typename F2, typename F3, typename FloatT> template <typename F2, typename F3, typename FloatT> void test_derivatives_tpl(F2 noise_func_2d, F3 noise_func_3d) {
void test_derivatives_tpl(F2 noise_func_2d, F3 noise_func_3d) {
const int iterations = ITERATIONS; const int iterations = ITERATIONS;
const int step_resolution_count = STEP_RESOLUTION_COUNT; const int step_resolution_count = STEP_RESOLUTION_COUNT;
const FloatT step_min = STEP_MIN; const FloatT step_min = STEP_MIN;
@ -169,8 +166,7 @@ void test_derivatives_tpl(F2 noise_func_2d, F3 noise_func_3d) {
print_line(String("Min max derivative: {0}").format(varray(min_max_derivative))); print_line(String("Min max derivative: {0}").format(varray(min_max_derivative)));
} }
template <typename F3> template <typename F3> void test_derivatives_with_image(String fpath, double step, F3 noise_func_3d) {
void test_derivatives_with_image(String fpath, double step, F3 noise_func_3d) {
const double x_min = 500.0; const double x_min = 500.0;
const double y = 500.0; const double y = 500.0;
const double z_min = 500.0; const double z_min = 500.0;
@ -204,8 +200,7 @@ void test_derivatives_with_image(String fpath, double step, F3 noise_func_3d) {
im->save_png(fpath); im->save_png(fpath);
} }
template <typename F3> template <typename F3> void test_derivatives_with_image(String fname, int steps_resolution, F3 noise_func_3d) {
void test_derivatives_with_image(String fname, int steps_resolution, F3 noise_func_3d) {
for (int i = 0; i < steps_resolution; ++i) { for (int i = 0; i < steps_resolution; ++i) {
const double step = const double step =
Math::lerp(STEP_MIN, STEP_MAX, static_cast<double>(i) / static_cast<double>(steps_resolution)); Math::lerp(STEP_MIN, STEP_MAX, static_cast<double>(i) / static_cast<double>(steps_resolution));
@ -214,8 +209,7 @@ void test_derivatives_with_image(String fname, int steps_resolution, F3 noise_fu
} }
} }
template <typename F2, typename F3> template <typename F2, typename F3> void test_noise(String name, int tests, F2 noise_func_2d, F3 noise_func_3d) {
void test_noise(String name, int tests, F2 noise_func_2d, F3 noise_func_3d) {
print_line(String("--- {0}:").format(varray(name))); print_line(String("--- {0}:").format(varray(name)));
if (tests & TEST_MIN_MAX) { if (tests & TEST_MIN_MAX) {
@ -229,8 +223,7 @@ void test_noise(String name, int tests, F2 noise_func_2d, F3 noise_func_3d) {
void test_fnl_noise(fast_noise_lite::FastNoiseLite &fnl, String name, int tests) { void test_fnl_noise(fast_noise_lite::FastNoiseLite &fnl, String name, int tests) {
test_noise( test_noise(
name, tests, name, tests, [&fnl](double x, double y) { return fnl.GetNoise(x, y); },
[&fnl](double x, double y) { return fnl.GetNoise(x, y); },
[&fnl](double x, double y, double z) { return fnl.GetNoise(x, y, z); }); [&fnl](double x, double y, double z) { return fnl.GetNoise(x, y, z); });
} }
@ -271,27 +264,26 @@ void test_noises() {
fn.SetNoiseType(fast_noise_lite::FastNoiseLite::NoiseType_Cellular); fn.SetNoiseType(fast_noise_lite::FastNoiseLite::NoiseType_Cellular);
const char *cell_distance_function_names[] = { const char *cell_distance_function_names[] = {
"Euclidean", "Euclidean", //
"EuclideanSq", "EuclideanSq", //
"Manhattan", "Manhattan", //
"Hybrid" "Hybrid" //
}; };
const char *cell_return_type_names[] = { const char *cell_return_type_names[] = {
"CellValue", "CellValue", //
"Distance", "Distance", //
"Distance2", "Distance2", //
"Distance2Add", "Distance2Add", //
"Distance2Sub", "Distance2Sub", //
"Distance2Mul", "Distance2Mul", //
"Distance2Div" "Distance2Div" //
}; };
for (int cell_distance_function = 0; cell_distance_function < 4; ++cell_distance_function) { for (int cell_distance_function = 0; cell_distance_function < 4; ++cell_distance_function) {
for (int cell_return_type = 0; cell_return_type < 7; ++cell_return_type) { for (int cell_return_type = 0; cell_return_type < 7; ++cell_return_type) {
fn.SetCellularDistanceFunction( fn.SetCellularDistanceFunction(
static_cast<fast_noise_lite::FastNoiseLite::CellularDistanceFunction>(cell_distance_function)); static_cast<fast_noise_lite::FastNoiseLite::CellularDistanceFunction>(cell_distance_function));
fn.SetCellularReturnType( fn.SetCellularReturnType(static_cast<fast_noise_lite::FastNoiseLite::CellularReturnType>(cell_return_type));
static_cast<fast_noise_lite::FastNoiseLite::CellularReturnType>(cell_return_type));
const char *cell_distance_function_name = cell_distance_function_names[cell_distance_function]; const char *cell_distance_function_name = cell_distance_function_names[cell_distance_function];
const char *cell_return_type_name = cell_return_type_names[cell_return_type]; const char *cell_return_type_name = cell_return_type_names[cell_return_type];
@ -326,11 +318,11 @@ void test_noises() {
} }
} }
} // namespace NoiseTests
// These are not actually unit tests, but rather analysis. They could be used with tests in the future, but // These are not actually unit tests, but rather analysis. They could be used with tests in the future, but
// it can be relatively hard for derivatives because empiric tests may bump on irregularities causing false-positives, // it can be relatively hard for derivatives because empiric tests may bump on irregularities causing false-positives,
// so for now derivative ranges are estimated manually from the results // so for now derivative ranges are estimated manually from the results
void run_noise_tests() { void run_noise_tests() {
NoiseTests::test_noises(); test_noises();
} }
} //namespace zylann::voxel::noise_tests

View File

@ -7,6 +7,8 @@
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
namespace zylann::voxel::tests {
void test_octree_update() { void test_octree_update() {
const float lod_distance = 80; const float lod_distance = 80;
const float view_distance = 1024; const float view_distance = 1024;
@ -222,3 +224,5 @@ void test_octree_find_in_box() {
.format(varray(lods, for_each_cell_time, single_query_time, checksum2))); .format(varray(lods, for_each_cell_time, single_query_time, checksum2)));
} }
} }
} // namespace zylann::voxel::tests

View File

@ -1,7 +1,11 @@
#ifndef TEST_OCTREE_H #ifndef TEST_OCTREE_H
#define TEST_OCTREE_H #define TEST_OCTREE_H
namespace zylann::voxel::tests {
void test_octree_update(); void test_octree_update();
void test_octree_find_in_box(); void test_octree_find_in_box();
} // namespace zylann::voxel::tests
#endif // TEST_OCTREE_H #endif // TEST_OCTREE_H

83
tests/testing.cpp Normal file
View File

@ -0,0 +1,83 @@
#include "testing.h"
#include <core/io/dir_access.h>
#include <core/io/file_access.h>
namespace zylann::testing {
constexpr char *DEFAULT_TEST_DATA_DIRECTORY = "zylann_testing_dir";
bool create_empty_file(String fpath) {
if (!FileAccess::exists(fpath)) {
FileAccessRef f = FileAccess::open(fpath, FileAccess::WRITE);
if (f) {
f->store_line("");
f->close();
} else {
ERR_PRINT("Failed to create file " + fpath);
return false;
}
}
return true;
}
bool remove_dir_if_exists(const char *p_dirpath) {
String dirpath = p_dirpath;
// If this is an empty string we could end up deleting the whole project, don't risk that
ERR_FAIL_COND_V(dirpath.is_empty(), false);
if (DirAccess::exists(dirpath)) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(da.f == nullptr, false);
String prev_dir = da->get_current_dir();
// Note, this does not change the working directory of the application.
// Very important to do that first, otherwise `erase_contents_recursive` would erase the whole project
const Error cd_err = da->change_dir(dirpath);
ERR_FAIL_COND_V(cd_err != OK, false);
// `remove` fails if the directory is not empty
const Error contents_remove_err = da->erase_contents_recursive();
ERR_FAIL_COND_V(contents_remove_err != OK, false);
// OS::get_singleton()->move_to_trash(dirpath); // ?
const Error cd_err2 = da->change_dir(prev_dir);
ERR_FAIL_COND_V(cd_err2 != OK, false);
const Error remove_err = da->remove(dirpath);
ERR_FAIL_COND_V(remove_err != OK, false);
}
return true;
}
bool create_clean_dir(const char *dirpath) {
ERR_FAIL_COND_V(dirpath == nullptr, false);
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(da.f == nullptr, false);
ERR_FAIL_COND_V(!remove_dir_if_exists(dirpath), false);
const Error err = da->make_dir(dirpath);
ERR_FAIL_COND_V(err != OK, false);
ERR_FAIL_COND_V(!create_empty_file(String(dirpath).plus_file(".gdignore")), false);
return true;
}
TestDirectory::TestDirectory() {
ERR_FAIL_COND(!create_clean_dir(DEFAULT_TEST_DATA_DIRECTORY));
_valid = true;
}
TestDirectory::~TestDirectory() {
ERR_FAIL_COND(!remove_dir_if_exists(DEFAULT_TEST_DATA_DIRECTORY));
}
String TestDirectory::get_path() const {
return DEFAULT_TEST_DATA_DIRECTORY;
}
} //namespace zylann::testing

27
tests/testing.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef TEST_UTIL_H
#define TEST_UTIL_H
#include "../util/span.h"
namespace zylann::testing {
// Utilities for testing
class TestDirectory {
public:
TestDirectory();
~TestDirectory();
bool is_valid() const {
return _valid;
}
String get_path() const;
private:
bool _valid = false;
};
} // namespace zylann::testing
#endif // TEST_UTIL_H

View File

@ -2,13 +2,19 @@
#include "../generators/graph/range_utility.h" #include "../generators/graph/range_utility.h"
#include "../generators/graph/voxel_generator_graph.h" #include "../generators/graph/voxel_generator_graph.h"
#include "../storage/voxel_data_map.h" #include "../storage/voxel_data_map.h"
#include "../streams/region/region_file.h"
#include "../streams/voxel_block_serializer.h"
#include "../util/island_finder.h" #include "../util/island_finder.h"
#include "../util/math/box3i.h" #include "../util/math/box3i.h"
#include "test_octree.h" #include "test_octree.h"
#include "testing.h"
#include <core/io/dir_access.h>
#include <core/string/print_string.h> #include <core/string/print_string.h>
#include <core/templates/hash_map.h> #include <core/templates/hash_map.h>
namespace zylann::voxel::tests {
void test_box3i_intersects() { void test_box3i_intersects() {
{ {
Box3i a(Vector3i(0, 0, 0), Vector3i(1, 1, 1)); Box3i a(Vector3i(0, 0, 0), Vector3i(1, 1, 1));
@ -1014,6 +1020,90 @@ void test_voxel_buffer_create() {
generated_voxels.create(Vector3i(1, 16, 18)); generated_voxels.create(Vector3i(1, 16, 18));
} }
void test_block_serializer() {
// Create an example buffer
const Vector3i block_size(8, 9, 10);
VoxelBufferInternal voxel_buffer;
voxel_buffer.create(block_size);
voxel_buffer.fill_area(42, Vector3i(1, 2, 3), Vector3i(5, 5, 5), 0);
voxel_buffer.fill_area(43, Vector3i(2, 3, 4), Vector3i(6, 6, 6), 0);
voxel_buffer.fill_area(44, Vector3i(1, 2, 3), Vector3i(5, 5, 5), 1);
// Serialize
VoxelBlockSerializerInternal serializer;
VoxelBlockSerializerInternal::SerializeResult result = serializer.serialize_and_compress(voxel_buffer);
ERR_FAIL_COND(!result.success);
std::vector<uint8_t> data = result.data;
// Deserialize
VoxelBufferInternal deserialized_voxel_buffer;
ERR_FAIL_COND(!serializer.decompress_and_deserialize(to_span_const(data), deserialized_voxel_buffer));
// Must be equal
ERR_FAIL_COND(!voxel_buffer.equals(deserialized_voxel_buffer));
}
void test_region_file() {
const int block_size_po2 = 4;
const int block_size = 1 << block_size_po2;
const char *region_file_name = "test_region_file.vxr";
zylann::testing::TestDirectory test_dir;
ERR_FAIL_COND(!test_dir.is_valid());
String region_file_path = test_dir.get_path().plus_file(region_file_name);
// Create a block of voxels
VoxelBufferInternal voxel_buffer;
voxel_buffer.create(Vector3iUtil::create(block_size));
voxel_buffer.fill_area(42, Vector3i(1, 2, 3), Vector3i(5, 5, 5), 0);
voxel_buffer.fill_area(43, Vector3i(2, 3, 4), Vector3i(6, 6, 6), 0);
{
VoxelRegionFile region_file;
// Configure region format
VoxelRegionFormat region_format = region_file.get_format();
region_format.block_size_po2 = block_size_po2;
for (unsigned int channel_index = 0; channel_index < VoxelBufferInternal::MAX_CHANNELS; ++channel_index) {
region_format.channel_depths[channel_index] = voxel_buffer.get_channel_depth(channel_index);
}
ERR_FAIL_COND(!region_file.set_format(region_format));
// Open file
const Error open_error = region_file.open(region_file_path, true);
ERR_FAIL_COND(open_error != OK);
// Save block
VoxelBlockSerializerInternal serializer;
const Error save_error = region_file.save_block(Vector3i(1, 2, 3), voxel_buffer, serializer);
ERR_FAIL_COND(save_error != OK);
// Read back
VoxelBufferInternal loaded_voxel_buffer;
const Error load_error = region_file.load_block(Vector3i(1, 2, 3), loaded_voxel_buffer, serializer);
ERR_FAIL_COND(load_error != OK);
// Must be equal
ERR_FAIL_COND(!voxel_buffer.equals(loaded_voxel_buffer));
}
// Load again but using a new region file object
{
VoxelRegionFile region_file;
// Open file
const Error open_error = region_file.open(region_file_path, false);
ERR_FAIL_COND(open_error != OK);
// Read back
VoxelBufferInternal loaded_voxel_buffer;
VoxelBlockSerializerInternal serializer;
const Error load_error = region_file.load_block(Vector3i(1, 2, 3), loaded_voxel_buffer, serializer);
ERR_FAIL_COND(load_error != OK);
// Must be equal
ERR_FAIL_COND(!voxel_buffer.equals(loaded_voxel_buffer));
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define VOXEL_TEST(fname) \ #define VOXEL_TEST(fname) \
@ -1040,6 +1130,10 @@ void run_voxel_tests() {
VOXEL_TEST(test_octree_find_in_box); VOXEL_TEST(test_octree_find_in_box);
VOXEL_TEST(test_get_curve_monotonic_sections); VOXEL_TEST(test_get_curve_monotonic_sections);
VOXEL_TEST(test_voxel_buffer_create); VOXEL_TEST(test_voxel_buffer_create);
VOXEL_TEST(test_block_serializer);
VOXEL_TEST(test_region_file);
print_line("------------ Voxel tests end -------------"); print_line("------------ Voxel tests end -------------");
} }
} // namespace zylann::voxel::tests

View File

@ -1,7 +1,12 @@
#ifndef VOXEL_TESTS_H #ifndef VOXEL_TESTS_H
#define VOXEL_TESTS_H #define VOXEL_TESTS_H
namespace zylann::voxel::tests {
void run_voxel_tests(); void run_voxel_tests();
} // namespace zylann::voxel::tests
namespace zylann::voxel::noise_tests {
void run_noise_tests(); void run_noise_tests();
} // namespace zylann::voxel::noise_tests
#endif // VOXEL_TESTS_H #endif // VOXEL_TESTS_H