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
#ifdef VOXEL_RUN_TESTS
run_voxel_tests();
zylann::voxel::tests::run_voxel_tests();
#endif
}

View File

@ -5,22 +5,21 @@
#include <core/io/image.h>
#include <modules/opensimplex/open_simplex_noise.h>
namespace NoiseTests {
namespace zylann::voxel::noise_tests {
const int ITERATIONS = 1000000;
const int STEP_RESOLUTION_COUNT = 100;
const double STEP_MIN = 0.0001;
const double STEP_MAX = 0.01;
enum Tests {
enum Tests { //
TEST_MIN_MAX = 1,
TEST_DERIVATIVES = 2
};
// Sample a maximum change across the given step.
// The result is not normalized for performance.
template <typename F2, typename FloatT>
FloatT get_derivative(FloatT x, FloatT y, FloatT step, F2 noise_func_2d) {
template <typename F2, typename FloatT> FloatT get_derivative(FloatT x, FloatT y, FloatT step, F2 noise_func_2d) {
FloatT n0, n1, d;
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;
}
template <typename F2, typename F3, typename FloatT>
void test_min_max(F2 noise_func_2d, F3 noise_func_3d) {
template <typename F2, typename F3, typename FloatT> void test_min_max(F2 noise_func_2d, F3 noise_func_3d) {
FloatT min_value_2d = std::numeric_limits<FloatT>::max();
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
template <typename F2, typename F3, typename FloatT>
void test_derivatives_tpl(F2 noise_func_2d, F3 noise_func_3d) {
template <typename F2, typename F3, typename FloatT> void test_derivatives_tpl(F2 noise_func_2d, F3 noise_func_3d) {
const int iterations = ITERATIONS;
const int step_resolution_count = STEP_RESOLUTION_COUNT;
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)));
}
template <typename F3>
void test_derivatives_with_image(String fpath, double step, F3 noise_func_3d) {
template <typename F3> void test_derivatives_with_image(String fpath, double step, F3 noise_func_3d) {
const double x_min = 500.0;
const double y = 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);
}
template <typename F3>
void test_derivatives_with_image(String fname, int steps_resolution, F3 noise_func_3d) {
template <typename F3> void test_derivatives_with_image(String fname, int steps_resolution, F3 noise_func_3d) {
for (int i = 0; i < steps_resolution; ++i) {
const double step =
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>
void test_noise(String name, int tests, F2 noise_func_2d, F3 noise_func_3d) {
template <typename F2, typename F3> void test_noise(String name, int tests, F2 noise_func_2d, F3 noise_func_3d) {
print_line(String("--- {0}:").format(varray(name)));
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) {
test_noise(
name, tests,
[&fnl](double x, double y) { return fnl.GetNoise(x, y); },
name, tests, [&fnl](double x, double y) { return fnl.GetNoise(x, y); },
[&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);
const char *cell_distance_function_names[] = {
"Euclidean",
"EuclideanSq",
"Manhattan",
"Hybrid"
"Euclidean", //
"EuclideanSq", //
"Manhattan", //
"Hybrid" //
};
const char *cell_return_type_names[] = {
"CellValue",
"Distance",
"Distance2",
"Distance2Add",
"Distance2Sub",
"Distance2Mul",
"Distance2Div"
"CellValue", //
"Distance", //
"Distance2", //
"Distance2Add", //
"Distance2Sub", //
"Distance2Mul", //
"Distance2Div" //
};
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) {
fn.SetCellularDistanceFunction(
static_cast<fast_noise_lite::FastNoiseLite::CellularDistanceFunction>(cell_distance_function));
fn.SetCellularReturnType(
static_cast<fast_noise_lite::FastNoiseLite::CellularReturnType>(cell_return_type));
fn.SetCellularReturnType(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_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
// 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
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_set>
namespace zylann::voxel::tests {
void test_octree_update() {
const float lod_distance = 80;
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)));
}
}
} // namespace zylann::voxel::tests

View File

@ -1,7 +1,11 @@
#ifndef TEST_OCTREE_H
#define TEST_OCTREE_H
namespace zylann::voxel::tests {
void test_octree_update();
void test_octree_find_in_box();
} // namespace zylann::voxel::tests
#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/voxel_generator_graph.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/math/box3i.h"
#include "test_octree.h"
#include "testing.h"
#include <core/io/dir_access.h>
#include <core/string/print_string.h>
#include <core/templates/hash_map.h>
namespace zylann::voxel::tests {
void test_box3i_intersects() {
{
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));
}
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) \
@ -1040,6 +1130,10 @@ void run_voxel_tests() {
VOXEL_TEST(test_octree_find_in_box);
VOXEL_TEST(test_get_curve_monotonic_sections);
VOXEL_TEST(test_voxel_buffer_create);
VOXEL_TEST(test_block_serializer);
VOXEL_TEST(test_region_file);
print_line("------------ Voxel tests end -------------");
}
} // namespace zylann::voxel::tests

View File

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