Added test for VoxelBlockSerializerInternal and RegionFile; added namespaces
parent
c2b0884126
commit
e46b1a023d
|
@ -153,7 +153,7 @@ void register_voxel_types() {
|
|||
#endif
|
||||
|
||||
#ifdef VOXEL_RUN_TESTS
|
||||
run_voxel_tests();
|
||||
zylann::voxel::tests::run_voxel_tests();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue