diff --git a/modules/noise/noise.cpp b/modules/noise/noise.cpp index 30bf12685937..5a901cb6e175 100644 --- a/modules/noise/noise.cpp +++ b/modules/noise/noise.cpp @@ -30,11 +30,13 @@ #include "noise.h" +#include + Ref Noise::get_seamless_image(int p_width, int p_height, bool p_invert, bool p_in_3d_space, real_t p_blend_skirt) const { ERR_FAIL_COND_V(p_width <= 0 || p_height <= 0, Ref()); - int skirt_width = p_width * p_blend_skirt; - int skirt_height = p_height * p_blend_skirt; + int skirt_width = MAX(1, p_width * p_blend_skirt); + int skirt_height = MAX(1, p_height * p_blend_skirt); int src_width = p_width + skirt_width; int src_height = p_height + skirt_height; @@ -67,8 +69,8 @@ Ref Noise::get_image(int p_width, int p_height, bool p_invert, bool p_in_ // Get all values and identify min/max values. Vector values; values.resize(p_width * p_height); - real_t min_val = 1000; - real_t max_val = -1000; + real_t min_val = FLT_MAX; + real_t max_val = -FLT_MAX; for (int y = 0, i = 0; y < p_height; y++) { for (int x = 0; x < p_width; x++, i++) { diff --git a/modules/noise/noise.h b/modules/noise/noise.h index 7f3672b1e287..8f8ecf29a5f7 100644 --- a/modules/noise/noise.h +++ b/modules/noise/noise.h @@ -107,8 +107,8 @@ class Noise : public Resource { int skirt_height = MAX(1, p_height * p_blend_skirt); int src_width = p_width + skirt_width; int src_height = p_height + skirt_height; - int half_width = p_width * .5; - int half_height = p_height * .5; + int half_width = p_width * 0.5; + int half_height = p_height * 0.5; int skirt_edge_x = half_width + skirt_width; int skirt_edge_y = half_height + skirt_height; @@ -146,7 +146,7 @@ class Noise : public Resource { // Blend the vertical skirt over the middle seam. for (int x = half_width; x < skirt_edge_x; x++) { - int alpha = 255 * (1 - Math::smoothstep(.1f, .9f, float(x - half_width) / float(skirt_width))); + int alpha = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(x - half_width) / float(skirt_width))); for (int y = 0; y < p_height; y++) { // Skip the center square if (y == half_height) { @@ -160,7 +160,7 @@ class Noise : public Resource { // Blend the horizontal skirt over the middle seam. for (int y = half_height; y < skirt_edge_y; y++) { - int alpha = 255 * (1 - Math::smoothstep(.1f, .9f, float(y - half_height) / float(skirt_height))); + int alpha = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(y - half_height) / float(skirt_height))); for (int x = 0; x < p_width; x++) { // Skip the center square if (x == half_width) { @@ -175,8 +175,8 @@ class Noise : public Resource { // Fill in the center square. Wr starts at the top left of Q4, which is the equivalent of the top left of s3, unless a modulo is used. for (int y = half_height; y < skirt_edge_y; y++) { for (int x = half_width; x < skirt_edge_x; x++) { - int xpos = 255 * (1 - Math::smoothstep(.1f, .9f, float(x - half_width) / float(skirt_width))); - int ypos = 255 * (1 - Math::smoothstep(.1f, .9f, float(y - half_height) / float(skirt_height))); + int xpos = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(x - half_width) / float(skirt_width))); + int ypos = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(y - half_height) / float(skirt_height))); // Blend s3(Q1) onto s5(Q2) for the top half. T top_blend = _alpha_blend(rd_src(x, y, img_buff::ALT_X), rd_src(x, y, img_buff::DEFAULT), xpos); diff --git a/modules/noise/noise_texture_2d.cpp b/modules/noise/noise_texture_2d.cpp index 38242bcf2f17..0eedb286bd33 100644 --- a/modules/noise/noise_texture_2d.cpp +++ b/modules/noise/noise_texture_2d.cpp @@ -88,7 +88,7 @@ void NoiseTexture2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "in_3d_space"), "set_in_3d_space", "is_in_3d_space"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_mipmaps"), "set_generate_mipmaps", "is_generating_mipmaps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "seamless"), "set_seamless", "get_seamless"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "seamless_blend_skirt", PROPERTY_HINT_RANGE, "0.05,1,0.001"), "set_seamless_blend_skirt", "get_seamless_blend_skirt"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "seamless_blend_skirt", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_seamless_blend_skirt", "get_seamless_blend_skirt"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "as_normal_map"), "set_as_normal_map", "is_normal_map"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bump_strength", PROPERTY_HINT_RANGE, "0,32,0.1,or_greater"), "set_bump_strength", "get_bump_strength"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_color_ramp", "get_color_ramp"); @@ -182,7 +182,7 @@ Ref NoiseTexture2D::_modulate_with_gradient(Ref p_image, Refget_pixel(col, row); - Color ramp_color = color_ramp->get_color_at_offset(pixel_color.get_luminance()); + Color ramp_color = p_gradient->get_color_at_offset(pixel_color.get_luminance()); new_image->set_pixel(col, row, ramp_color); } } @@ -296,7 +296,7 @@ bool NoiseTexture2D::get_seamless() { } void NoiseTexture2D::set_seamless_blend_skirt(real_t p_blend_skirt) { - ERR_FAIL_COND(p_blend_skirt < 0.05 || p_blend_skirt > 1); + ERR_FAIL_COND(p_blend_skirt < 0 || p_blend_skirt > 1); if (p_blend_skirt == seamless_blend_skirt) { return; diff --git a/modules/noise/tests/test_fastnoise_lite.h b/modules/noise/tests/test_fastnoise_lite.h new file mode 100644 index 000000000000..0a435c6a5c78 --- /dev/null +++ b/modules/noise/tests/test_fastnoise_lite.h @@ -0,0 +1,637 @@ +/**************************************************************************/ +/* test_fastnoise_lite.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_FASTNOISE_LITE_H +#define TEST_FASTNOISE_LITE_H + +#include "tests/test_macros.h" + +#include "modules/noise/fastnoise_lite.h" + +namespace TestFastNoiseLite { + +// Uitility functions for finding differences in noise generation + +bool all_equal_approx(const Vector &p_values_1, const Vector &p_values_2) { + ERR_FAIL_COND_V_MSG(p_values_1.size() != p_values_2.size(), false, "Arrays must be the same size. This is a error in the test code."); + + for (int i = 0; i < p_values_1.size(); i++) { + if (!Math::is_equal_approx(p_values_1[i], p_values_2[i])) { + return false; + } + } + return true; +} + +Vector> find_approx_equal_vec_pairs(std::initializer_list> inputs) { + Vector> p_array = Vector>(inputs); + + Vector> result; + for (int i = 0; i < p_array.size(); i++) { + for (int j = i + 1; j < p_array.size(); j++) { + if (all_equal_approx(p_array[i], p_array[j])) { + result.push_back(Pair(i, j)); + } + } + } + return result; +} + +#define CHECK_ARGS_APPROX_PAIRWISE_DISTINCT_VECS(...) \ + { \ + Vector> equal_pairs = find_approx_equal_vec_pairs({ __VA_ARGS__ }); \ + for (Pair p : equal_pairs) { \ + MESSAGE("Argument with index ", p.first, " is approximately equal to argument with index ", p.second); \ + } \ + CHECK_MESSAGE(equal_pairs.size() == 0, "All arguments should be pairwise distinct."); \ + } + +Vector get_noise_samples_1d(const FastNoiseLite &p_noise, size_t p_count = 32) { + Vector result; + result.resize(p_count); + for (size_t i = 0; i < p_count; i++) { + result.write[i] = p_noise.get_noise_1d(i); + } + return result; +} + +Vector get_noise_samples_2d(const FastNoiseLite &p_noise, size_t p_count = 32) { + Vector result; + result.resize(p_count); + for (size_t i = 0; i < p_count; i++) { + result.write[i] = p_noise.get_noise_2d(i, i); + } + return result; +} + +Vector get_noise_samples_3d(const FastNoiseLite &p_noise, size_t p_count = 32) { + Vector result; + result.resize(p_count); + for (size_t i = 0; i < p_count; i++) { + result.write[i] = p_noise.get_noise_3d(i, i, i); + } + return result; +} + +// The following test suite is rather for testing the wrapper code than the actual noise generation. + +TEST_CASE("[FastNoiseLite] Getter and setter") { + FastNoiseLite noise; + + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_SIMPLEX_SMOOTH); + CHECK(noise.get_noise_type() == FastNoiseLite::NoiseType::TYPE_SIMPLEX_SMOOTH); + + noise.set_seed(123); + CHECK(noise.get_seed() == 123); + + noise.set_frequency(0.123); + CHECK(noise.get_frequency() == doctest::Approx(0.123)); + + noise.set_offset(Vector3(1, 2, 3)); + CHECK(noise.get_offset() == Vector3(1, 2, 3)); + + noise.set_fractal_type(FastNoiseLite::FractalType::FRACTAL_PING_PONG); + CHECK(noise.get_fractal_type() == FastNoiseLite::FractalType::FRACTAL_PING_PONG); + + noise.set_fractal_octaves(2); + CHECK(noise.get_fractal_octaves() == 2); + + noise.set_fractal_lacunarity(1.123); + CHECK(noise.get_fractal_lacunarity() == doctest::Approx(1.123)); + + noise.set_fractal_gain(0.123); + CHECK(noise.get_fractal_gain() == doctest::Approx(0.123)); + + noise.set_fractal_weighted_strength(0.123); + CHECK(noise.get_fractal_weighted_strength() == doctest::Approx(0.123)); + + noise.set_fractal_ping_pong_strength(0.123); + CHECK(noise.get_fractal_ping_pong_strength() == doctest::Approx(0.123)); + + noise.set_cellular_distance_function(FastNoiseLite::CellularDistanceFunction::DISTANCE_MANHATTAN); + CHECK(noise.get_cellular_distance_function() == FastNoiseLite::CellularDistanceFunction::DISTANCE_MANHATTAN); + + noise.set_cellular_return_type(FastNoiseLite::CellularReturnType::RETURN_DISTANCE2_SUB); + CHECK(noise.get_cellular_return_type() == FastNoiseLite::CellularReturnType::RETURN_DISTANCE2_SUB); + + noise.set_cellular_jitter(0.123); + CHECK(noise.get_cellular_jitter() == doctest::Approx(0.123)); + + noise.set_domain_warp_enabled(true); + CHECK(noise.is_domain_warp_enabled() == true); + noise.set_domain_warp_enabled(false); + CHECK(noise.is_domain_warp_enabled() == false); + + noise.set_domain_warp_type(FastNoiseLite::DomainWarpType::DOMAIN_WARP_SIMPLEX_REDUCED); + CHECK(noise.get_domain_warp_type() == FastNoiseLite::DomainWarpType::DOMAIN_WARP_SIMPLEX_REDUCED); + + noise.set_domain_warp_amplitude(0.123); + CHECK(noise.get_domain_warp_amplitude() == doctest::Approx(0.123)); + + noise.set_domain_warp_frequency(0.123); + CHECK(noise.get_domain_warp_frequency() == doctest::Approx(0.123)); + + noise.set_domain_warp_fractal_type(FastNoiseLite::DomainWarpFractalType::DOMAIN_WARP_FRACTAL_INDEPENDENT); + CHECK(noise.get_domain_warp_fractal_type() == FastNoiseLite::DomainWarpFractalType::DOMAIN_WARP_FRACTAL_INDEPENDENT); + + noise.set_domain_warp_fractal_octaves(2); + CHECK(noise.get_domain_warp_fractal_octaves() == 2); + + noise.set_domain_warp_fractal_lacunarity(1.123); + CHECK(noise.get_domain_warp_fractal_lacunarity() == doctest::Approx(1.123)); + + noise.set_domain_warp_fractal_gain(0.123); + CHECK(noise.get_domain_warp_fractal_gain() == doctest::Approx(0.123)); +} + +TEST_CASE("[FastNoiseLite] Basic noise generation") { + FastNoiseLite noise; + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_SIMPLEX); + noise.set_fractal_type(FastNoiseLite::FractalType::FRACTAL_NONE); + noise.set_seed(123); + noise.set_offset(Vector3(10, 10, 10)); + + // 1D noise will be checked just in the cases where there's the possibility of + // finding a bug/regression in the wrapper function. + // (since it uses FastNoise's 2D noise generator with the Y coordinate set to 0). + + SUBCASE("Determinacy of noise generation (all noise types)") { + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_SIMPLEX); + CHECK(noise.get_noise_2d(0, 0) == doctest::Approx(noise.get_noise_2d(0, 0))); + CHECK(noise.get_noise_3d(0, 0, 0) == doctest::Approx(noise.get_noise_3d(0, 0, 0))); + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_SIMPLEX_SMOOTH); + CHECK(noise.get_noise_2d(0, 0) == doctest::Approx(noise.get_noise_2d(0, 0))); + CHECK(noise.get_noise_3d(0, 0, 0) == doctest::Approx(noise.get_noise_3d(0, 0, 0))); + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_CELLULAR); + CHECK(noise.get_noise_2d(0, 0) == doctest::Approx(noise.get_noise_2d(0, 0))); + CHECK(noise.get_noise_3d(0, 0, 0) == doctest::Approx(noise.get_noise_3d(0, 0, 0))); + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_PERLIN); + CHECK(noise.get_noise_2d(0, 0) == doctest::Approx(noise.get_noise_2d(0, 0))); + CHECK(noise.get_noise_3d(0, 0, 0) == doctest::Approx(noise.get_noise_3d(0, 0, 0))); + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_VALUE); + CHECK(noise.get_noise_2d(0, 0) == doctest::Approx(noise.get_noise_2d(0, 0))); + CHECK(noise.get_noise_3d(0, 0, 0) == doctest::Approx(noise.get_noise_3d(0, 0, 0))); + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_VALUE_CUBIC); + CHECK(noise.get_noise_2d(0, 0) == doctest::Approx(noise.get_noise_2d(0, 0))); + CHECK(noise.get_noise_3d(0, 0, 0) == doctest::Approx(noise.get_noise_3d(0, 0, 0))); + } + + SUBCASE("Different seeds should produce different noise") { + noise.set_seed(456); + Vector noise_seed_1_1d = get_noise_samples_1d(noise); + Vector noise_seed_1_2d = get_noise_samples_2d(noise); + Vector noise_seed_1_3d = get_noise_samples_3d(noise); + noise.set_seed(123); + Vector noise_seed_2_1d = get_noise_samples_1d(noise); + Vector noise_seed_2_2d = get_noise_samples_2d(noise); + Vector noise_seed_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(noise_seed_1_1d, noise_seed_2_1d)); + CHECK_FALSE(all_equal_approx(noise_seed_1_2d, noise_seed_2_2d)); + CHECK_FALSE(all_equal_approx(noise_seed_1_3d, noise_seed_2_3d)); + } + + SUBCASE("Different frequencies should produce different noise") { + noise.set_frequency(0.1); + Vector noise_frequency_1_1d = get_noise_samples_1d(noise); + Vector noise_frequency_1_2d = get_noise_samples_2d(noise); + Vector noise_frequency_1_3d = get_noise_samples_3d(noise); + noise.set_frequency(1.0); + Vector noise_frequency_2_1d = get_noise_samples_1d(noise); + Vector noise_frequency_2_2d = get_noise_samples_2d(noise); + Vector noise_frequency_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(noise_frequency_1_1d, noise_frequency_2_1d)); + CHECK_FALSE(all_equal_approx(noise_frequency_1_2d, noise_frequency_2_2d)); + CHECK_FALSE(all_equal_approx(noise_frequency_1_3d, noise_frequency_2_3d)); + } + + SUBCASE("Noise should be offset by the offset parameter") { + noise.set_offset(Vector3(1, 2, 3)); + Vector noise_offset_1_1d = get_noise_samples_1d(noise); + Vector noise_offset_1_2d = get_noise_samples_2d(noise); + Vector noise_offset_1_3d = get_noise_samples_3d(noise); + noise.set_offset(Vector3(4, 5, 6)); + Vector noise_offset_2_1d = get_noise_samples_1d(noise); + Vector noise_offset_2_2d = get_noise_samples_2d(noise); + Vector noise_offset_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(noise_offset_1_1d, noise_offset_2_1d)); + CHECK_FALSE(all_equal_approx(noise_offset_1_2d, noise_offset_2_2d)); + CHECK_FALSE(all_equal_approx(noise_offset_1_3d, noise_offset_2_3d)); + } + + SUBCASE("Different noise types should produce different noise") { + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_SIMPLEX); + Vector noise_type_simplex_2d = get_noise_samples_2d(noise); + Vector noise_type_simplex_3d = get_noise_samples_3d(noise); + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_SIMPLEX_SMOOTH); + Vector noise_type_simplex_smooth_2d = get_noise_samples_2d(noise); + Vector noise_type_simplex_smooth_3d = get_noise_samples_3d(noise); + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_CELLULAR); + Vector noise_type_cellular_2d = get_noise_samples_2d(noise); + Vector noise_type_cellular_3d = get_noise_samples_3d(noise); + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_PERLIN); + Vector noise_type_perlin_2d = get_noise_samples_2d(noise); + Vector noise_type_perlin_3d = get_noise_samples_3d(noise); + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_VALUE); + Vector noise_type_value_2d = get_noise_samples_2d(noise); + Vector noise_type_value_3d = get_noise_samples_3d(noise); + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_VALUE_CUBIC); + Vector noise_type_value_cubic_2d = get_noise_samples_2d(noise); + Vector noise_type_value_cubic_3d = get_noise_samples_3d(noise); + + CHECK_ARGS_APPROX_PAIRWISE_DISTINCT_VECS(noise_type_simplex_2d, + noise_type_simplex_smooth_2d, + noise_type_cellular_2d, + noise_type_perlin_2d, + noise_type_value_2d, + noise_type_value_cubic_2d); + + CHECK_ARGS_APPROX_PAIRWISE_DISTINCT_VECS(noise_type_simplex_3d, + noise_type_simplex_smooth_3d, + noise_type_cellular_3d, + noise_type_perlin_3d, + noise_type_value_3d, + noise_type_value_cubic_3d); + } +} + +TEST_CASE("[FastNoiseLite] Fractal noise") { + FastNoiseLite noise; + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_SIMPLEX); + noise.set_offset(Vector3(10, 10, 10)); + noise.set_frequency(0.01); + noise.set_fractal_type(FastNoiseLite::FractalType::FRACTAL_FBM); + noise.set_fractal_octaves(4); + noise.set_fractal_lacunarity(2.0); + noise.set_fractal_gain(0.5); + noise.set_fractal_weighted_strength(0.5); + noise.set_fractal_ping_pong_strength(2.0); + + SUBCASE("Different fractal types should produce different results") { + noise.set_fractal_type(FastNoiseLite::FractalType::FRACTAL_NONE); + Vector fractal_type_none_2d = get_noise_samples_2d(noise); + Vector fractal_type_none_3d = get_noise_samples_3d(noise); + noise.set_fractal_type(FastNoiseLite::FractalType::FRACTAL_FBM); + Vector fractal_type_fbm_2d = get_noise_samples_2d(noise); + Vector fractal_type_fbm_3d = get_noise_samples_3d(noise); + noise.set_fractal_type(FastNoiseLite::FractalType::FRACTAL_RIDGED); + Vector fractal_type_ridged_2d = get_noise_samples_2d(noise); + Vector fractal_type_ridged_3d = get_noise_samples_3d(noise); + noise.set_fractal_type(FastNoiseLite::FractalType::FRACTAL_PING_PONG); + Vector fractal_type_ping_pong_2d = get_noise_samples_2d(noise); + Vector fractal_type_ping_pong_3d = get_noise_samples_3d(noise); + + CHECK_ARGS_APPROX_PAIRWISE_DISTINCT_VECS(fractal_type_none_2d, + fractal_type_fbm_2d, + fractal_type_ridged_2d, + fractal_type_ping_pong_2d); + + CHECK_ARGS_APPROX_PAIRWISE_DISTINCT_VECS(fractal_type_none_3d, + fractal_type_fbm_3d, + fractal_type_ridged_3d, + fractal_type_ping_pong_3d); + } + + SUBCASE("Different octaves should produce different results") { + noise.set_fractal_octaves(1.0); + Vector fractal_octaves_1_2d = get_noise_samples_2d(noise); + Vector fractal_octaves_1_3d = get_noise_samples_3d(noise); + noise.set_fractal_octaves(8.0); + Vector fractal_octaves_2_2d = get_noise_samples_2d(noise); + Vector fractal_octaves_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(fractal_octaves_1_2d, fractal_octaves_2_2d)); + CHECK_FALSE(all_equal_approx(fractal_octaves_1_3d, fractal_octaves_2_3d)); + } + + SUBCASE("Different lacunarity should produce different results") { + noise.set_fractal_lacunarity(1.0); + Vector fractal_lacunarity_1_2d = get_noise_samples_2d(noise); + Vector fractal_lacunarity_1_3d = get_noise_samples_3d(noise); + noise.set_fractal_lacunarity(2.0); + Vector fractal_lacunarity_2_2d = get_noise_samples_2d(noise); + Vector fractal_lacunarity_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(fractal_lacunarity_1_2d, fractal_lacunarity_2_2d)); + CHECK_FALSE(all_equal_approx(fractal_lacunarity_1_3d, fractal_lacunarity_2_3d)); + } + + SUBCASE("Different gain should produce different results") { + noise.set_fractal_gain(0.5); + Vector fractal_gain_1_2d = get_noise_samples_2d(noise); + Vector fractal_gain_1_3d = get_noise_samples_3d(noise); + noise.set_fractal_gain(0.75); + Vector fractal_gain_2_2d = get_noise_samples_2d(noise); + Vector fractal_gain_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(fractal_gain_1_2d, fractal_gain_2_2d)); + CHECK_FALSE(all_equal_approx(fractal_gain_1_3d, fractal_gain_2_3d)); + } + + SUBCASE("Different weights should produce different results") { + noise.set_fractal_weighted_strength(0.5); + Vector fractal_weighted_strength_1_2d = get_noise_samples_2d(noise); + Vector fractal_weighted_strength_1_3d = get_noise_samples_3d(noise); + noise.set_fractal_weighted_strength(0.75); + Vector fractal_weighted_strength_2_2d = get_noise_samples_2d(noise); + Vector fractal_weighted_strength_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(fractal_weighted_strength_1_2d, fractal_weighted_strength_2_2d)); + CHECK_FALSE(all_equal_approx(fractal_weighted_strength_1_3d, fractal_weighted_strength_2_3d)); + } + + SUBCASE("Different ping pong strength should produce different results") { + noise.set_fractal_type(FastNoiseLite::FractalType::FRACTAL_PING_PONG); + noise.set_fractal_ping_pong_strength(0.5); + Vector fractal_ping_pong_strength_1_2d = get_noise_samples_2d(noise); + Vector fractal_ping_pong_strength_1_3d = get_noise_samples_3d(noise); + noise.set_fractal_ping_pong_strength(0.75); + Vector fractal_ping_pong_strength_2_2d = get_noise_samples_2d(noise); + Vector fractal_ping_pong_strength_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(fractal_ping_pong_strength_1_2d, fractal_ping_pong_strength_2_2d)); + CHECK_FALSE(all_equal_approx(fractal_ping_pong_strength_1_3d, fractal_ping_pong_strength_2_3d)); + } +} + +TEST_CASE("[FastNoiseLite] Cellular noise") { + FastNoiseLite noise; + noise.set_fractal_type(FastNoiseLite::FractalType::FRACTAL_NONE); + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_CELLULAR); + noise.set_cellular_distance_function(FastNoiseLite::CellularDistanceFunction::DISTANCE_EUCLIDEAN); + noise.set_cellular_return_type(FastNoiseLite::CellularReturnType::RETURN_DISTANCE); + noise.set_frequency(1.0); + + SUBCASE("Different distance functions should produce different results") { + noise.set_cellular_distance_function(FastNoiseLite::CellularDistanceFunction::DISTANCE_EUCLIDEAN); + Vector cellular_distance_function_euclidean_2d = get_noise_samples_2d(noise); + Vector cellular_distance_function_euclidean_3d = get_noise_samples_3d(noise); + noise.set_cellular_distance_function(FastNoiseLite::CellularDistanceFunction::DISTANCE_EUCLIDEAN_SQUARED); + Vector cellular_distance_function_euclidean_squared_2d = get_noise_samples_2d(noise); + Vector cellular_distance_function_euclidean_squared_3d = get_noise_samples_3d(noise); + noise.set_cellular_distance_function(FastNoiseLite::CellularDistanceFunction::DISTANCE_MANHATTAN); + Vector cellular_distance_function_manhattan_2d = get_noise_samples_2d(noise); + Vector cellular_distance_function_manhattan_3d = get_noise_samples_3d(noise); + noise.set_cellular_distance_function(FastNoiseLite::CellularDistanceFunction::DISTANCE_HYBRID); + Vector cellular_distance_function_hybrid_2d = get_noise_samples_2d(noise); + Vector cellular_distance_function_hybrid_3d = get_noise_samples_3d(noise); + + CHECK_ARGS_APPROX_PAIRWISE_DISTINCT_VECS(cellular_distance_function_euclidean_2d, + cellular_distance_function_euclidean_squared_2d, + cellular_distance_function_manhattan_2d, + cellular_distance_function_hybrid_2d); + + CHECK_ARGS_APPROX_PAIRWISE_DISTINCT_VECS(cellular_distance_function_euclidean_3d, + cellular_distance_function_euclidean_squared_3d, + cellular_distance_function_manhattan_3d, + cellular_distance_function_hybrid_3d); + } + + SUBCASE("Different return function types should produce different results") { + noise.set_cellular_return_type(FastNoiseLite::CellularReturnType::RETURN_CELL_VALUE); + Vector cellular_return_type_cell_value_2d = get_noise_samples_2d(noise); + Vector cellular_return_type_cell_value_3d = get_noise_samples_3d(noise); + noise.set_cellular_return_type(FastNoiseLite::CellularReturnType::RETURN_DISTANCE); + Vector cellular_return_type_distance_2d = get_noise_samples_2d(noise); + Vector cellular_return_type_distance_3d = get_noise_samples_3d(noise); + noise.set_cellular_return_type(FastNoiseLite::CellularReturnType::RETURN_DISTANCE2); + Vector cellular_return_type_distance2_2d = get_noise_samples_2d(noise); + Vector cellular_return_type_distance2_3d = get_noise_samples_3d(noise); + noise.set_cellular_return_type(FastNoiseLite::CellularReturnType::RETURN_DISTANCE2_ADD); + Vector cellular_return_type_distance2_add_2d = get_noise_samples_2d(noise); + Vector cellular_return_type_distance2_add_3d = get_noise_samples_3d(noise); + noise.set_cellular_return_type(FastNoiseLite::CellularReturnType::RETURN_DISTANCE2_SUB); + Vector cellular_return_type_distance2_sub_2d = get_noise_samples_2d(noise); + Vector cellular_return_type_distance2_sub_3d = get_noise_samples_3d(noise); + noise.set_cellular_return_type(FastNoiseLite::CellularReturnType::RETURN_DISTANCE2_MUL); + Vector cellular_return_type_distance2_mul_2d = get_noise_samples_2d(noise); + Vector cellular_return_type_distance2_mul_3d = get_noise_samples_3d(noise); + noise.set_cellular_return_type(FastNoiseLite::CellularReturnType::RETURN_DISTANCE2_DIV); + Vector cellular_return_type_distance2_div_2d = get_noise_samples_2d(noise); + Vector cellular_return_type_distance2_div_3d = get_noise_samples_3d(noise); + + CHECK_ARGS_APPROX_PAIRWISE_DISTINCT_VECS(cellular_return_type_cell_value_2d, + cellular_return_type_distance_2d, + cellular_return_type_distance2_2d, + cellular_return_type_distance2_add_2d, + cellular_return_type_distance2_sub_2d, + cellular_return_type_distance2_mul_2d, + cellular_return_type_distance2_div_2d); + + CHECK_ARGS_APPROX_PAIRWISE_DISTINCT_VECS(cellular_return_type_cell_value_3d, + cellular_return_type_distance_3d, + cellular_return_type_distance2_3d, + cellular_return_type_distance2_add_3d, + cellular_return_type_distance2_sub_3d, + cellular_return_type_distance2_mul_3d, + cellular_return_type_distance2_div_3d); + } + + SUBCASE("Different cellular jitter should produce different results") { + noise.set_cellular_jitter(0.0); + Vector cellular_jitter_1_2d = get_noise_samples_2d(noise); + Vector cellular_jitter_1_3d = get_noise_samples_3d(noise); + noise.set_cellular_jitter(0.5); + Vector cellular_jitter_2_2d = get_noise_samples_2d(noise); + Vector cellular_jitter_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(cellular_jitter_1_2d, cellular_jitter_2_2d)); + CHECK_FALSE(all_equal_approx(cellular_jitter_1_3d, cellular_jitter_2_3d)); + } +} + +TEST_CASE("[FastNoiseLite] Domain warp") { + FastNoiseLite noise; + noise.set_frequency(1.0); + noise.set_domain_warp_amplitude(200.0); + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_SIMPLEX); + noise.set_domain_warp_enabled(true); + + SUBCASE("Different domain warp types should produce different results") { + noise.set_domain_warp_type(FastNoiseLite::DomainWarpType::DOMAIN_WARP_SIMPLEX); + Vector domain_warp_type_simplex_2d = get_noise_samples_2d(noise); + Vector domain_warp_type_simplex_3d = get_noise_samples_3d(noise); + noise.set_domain_warp_type(FastNoiseLite::DomainWarpType::DOMAIN_WARP_SIMPLEX_REDUCED); + Vector domain_warp_type_simplex_reduced_2d = get_noise_samples_2d(noise); + Vector domain_warp_type_simplex_reduced_3d = get_noise_samples_3d(noise); + noise.set_domain_warp_type(FastNoiseLite::DomainWarpType::DOMAIN_WARP_BASIC_GRID); + Vector domain_warp_type_basic_grid_2d = get_noise_samples_2d(noise); + Vector domain_warp_type_basic_grid_3d = get_noise_samples_3d(noise); + + CHECK_ARGS_APPROX_PAIRWISE_DISTINCT_VECS(domain_warp_type_simplex_2d, + domain_warp_type_simplex_reduced_2d, + domain_warp_type_basic_grid_2d); + + CHECK_ARGS_APPROX_PAIRWISE_DISTINCT_VECS(domain_warp_type_simplex_3d, + domain_warp_type_simplex_reduced_3d, + domain_warp_type_basic_grid_3d); + } + + SUBCASE("Different domain warp amplitude should produce different results") { + noise.set_domain_warp_amplitude(0.0); + Vector domain_warp_amplitude_1_2d = get_noise_samples_2d(noise); + Vector domain_warp_amplitude_1_3d = get_noise_samples_3d(noise); + noise.set_domain_warp_amplitude(100.0); + Vector domain_warp_amplitude_2_2d = get_noise_samples_2d(noise); + Vector domain_warp_amplitude_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(domain_warp_amplitude_1_2d, domain_warp_amplitude_2_2d)); + CHECK_FALSE(all_equal_approx(domain_warp_amplitude_1_3d, domain_warp_amplitude_2_3d)); + } + + SUBCASE("Different domain warp frequency should produce different results") { + noise.set_domain_warp_frequency(0.1); + Vector domain_warp_frequency_1_2d = get_noise_samples_2d(noise); + Vector domain_warp_frequency_1_3d = get_noise_samples_3d(noise); + noise.set_domain_warp_frequency(2.0); + Vector domain_warp_frequency_2_2d = get_noise_samples_2d(noise); + Vector domain_warp_frequency_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(domain_warp_frequency_1_2d, domain_warp_frequency_2_2d)); + CHECK_FALSE(all_equal_approx(domain_warp_frequency_1_3d, domain_warp_frequency_2_3d)); + } + + SUBCASE("Different domain warp fractal type should produce different results") { + noise.set_domain_warp_fractal_type(FastNoiseLite::DomainWarpFractalType::DOMAIN_WARP_FRACTAL_NONE); + Vector domain_warp_fractal_type_none_2d = get_noise_samples_2d(noise); + Vector domain_warp_fractal_type_none_3d = get_noise_samples_3d(noise); + noise.set_domain_warp_fractal_type(FastNoiseLite::DomainWarpFractalType::DOMAIN_WARP_FRACTAL_PROGRESSIVE); + Vector domain_warp_fractal_type_progressive_2d = get_noise_samples_2d(noise); + Vector domain_warp_fractal_type_progressive_3d = get_noise_samples_3d(noise); + noise.set_domain_warp_fractal_type(FastNoiseLite::DomainWarpFractalType::DOMAIN_WARP_FRACTAL_INDEPENDENT); + Vector domain_warp_fractal_type_independent_2d = get_noise_samples_2d(noise); + Vector domain_warp_fractal_type_independent_3d = get_noise_samples_3d(noise); + + CHECK_ARGS_APPROX_PAIRWISE_DISTINCT_VECS(domain_warp_fractal_type_none_2d, + domain_warp_fractal_type_progressive_2d, + domain_warp_fractal_type_independent_2d); + + CHECK_ARGS_APPROX_PAIRWISE_DISTINCT_VECS(domain_warp_fractal_type_none_3d, + domain_warp_fractal_type_progressive_3d, + domain_warp_fractal_type_independent_3d); + } + + SUBCASE("Different domain warp fractal octaves should produce different results") { + noise.set_domain_warp_fractal_octaves(1); + Vector domain_warp_fractal_octaves_1_2d = get_noise_samples_2d(noise); + Vector domain_warp_fractal_octaves_1_3d = get_noise_samples_3d(noise); + noise.set_domain_warp_fractal_octaves(6); + Vector domain_warp_fractal_octaves_2_2d = get_noise_samples_2d(noise); + Vector domain_warp_fractal_octaves_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(domain_warp_fractal_octaves_1_2d, domain_warp_fractal_octaves_2_2d)); + CHECK_FALSE(all_equal_approx(domain_warp_fractal_octaves_1_3d, domain_warp_fractal_octaves_2_3d)); + } + + SUBCASE("Different domain warp fractal lacunarity should produce different results") { + noise.set_domain_warp_fractal_lacunarity(0.5); + Vector domain_warp_fractal_lacunarity_1_2d = get_noise_samples_2d(noise); + Vector domain_warp_fractal_lacunarity_1_3d = get_noise_samples_3d(noise); + noise.set_domain_warp_fractal_lacunarity(5.0); + Vector domain_warp_fractal_lacunarity_2_2d = get_noise_samples_2d(noise); + Vector domain_warp_fractal_lacunarity_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(domain_warp_fractal_lacunarity_1_2d, domain_warp_fractal_lacunarity_2_2d)); + CHECK_FALSE(all_equal_approx(domain_warp_fractal_lacunarity_1_3d, domain_warp_fractal_lacunarity_2_3d)); + } + + SUBCASE("Different domain warp fractal gain should produce different results") { + noise.set_domain_warp_fractal_gain(0.1); + Vector domain_warp_fractal_gain_1_2d = get_noise_samples_2d(noise); + Vector domain_warp_fractal_gain_1_3d = get_noise_samples_3d(noise); + noise.set_domain_warp_fractal_gain(0.9); + Vector domain_warp_fractal_gain_2_2d = get_noise_samples_2d(noise); + Vector domain_warp_fractal_gain_2_3d = get_noise_samples_3d(noise); + + CHECK_FALSE(all_equal_approx(domain_warp_fractal_gain_1_2d, domain_warp_fractal_gain_2_2d)); + CHECK_FALSE(all_equal_approx(domain_warp_fractal_gain_1_3d, domain_warp_fractal_gain_2_3d)); + } +} + +// Raw image data for the reference images used in the regression tests. +// Generated with the following code: +// for (int y = 0; y < img->get_data().size(); y++) { +// printf("0x%x,", img->get_data()[y]); +// } + +const Vector ref_img_1_data = { 0xff, 0xe6, 0xd2, 0xc2, 0xb7, 0xb4, 0xb4, 0xb7, 0xc2, 0xd2, 0xe6, 0xe6, 0xcb, 0xb4, 0xa1, 0x94, 0x90, 0x90, 0x94, 0xa1, 0xb4, 0xcb, 0xd2, 0xb4, 0x99, 0x82, 0x72, 0x6c, 0x6c, 0x72, 0x82, 0x99, 0xb4, 0xc2, 0xa1, 0x82, 0x65, 0x50, 0x48, 0x48, 0x50, 0x65, 0x82, 0xa1, 0xb7, 0x94, 0x72, 0x50, 0x32, 0x24, 0x24, 0x32, 0x50, 0x72, 0x94, 0xb4, 0x90, 0x6c, 0x48, 0x24, 0x0, 0x0, 0x24, 0x48, 0x6c, 0x90, 0xb4, 0x90, 0x6c, 0x48, 0x24, 0x0, 0x0, 0x24, 0x48, 0x6c, 0x90, 0xb7, 0x94, 0x72, 0x50, 0x32, 0x24, 0x24, 0x33, 0x50, 0x72, 0x94, 0xc2, 0xa1, 0x82, 0x65, 0x50, 0x48, 0x48, 0x50, 0x66, 0x82, 0xa1, 0xd2, 0xb4, 0x99, 0x82, 0x72, 0x6c, 0x6c, 0x72, 0x82, 0x99, 0xb4, 0xe6, 0xcb, 0xb4, 0xa1, 0x94, 0x90, 0x90, 0x94, 0xa1, 0xb4, 0xcc }; +const Vector ref_img_2_data = { 0xff, 0xe6, 0xd2, 0xc2, 0xb7, 0xb4, 0xb4, 0xb7, 0xc2, 0xd2, 0xe6, 0xe6, 0xcb, 0xb4, 0xa1, 0x94, 0x90, 0x90, 0x94, 0xa1, 0xb4, 0xcb, 0xd2, 0xb4, 0x99, 0x82, 0x72, 0x6c, 0x6c, 0x72, 0x82, 0x99, 0xb4, 0xc2, 0xa1, 0x82, 0x65, 0x50, 0x48, 0x48, 0x50, 0x65, 0x82, 0xa1, 0xb7, 0x94, 0x72, 0x50, 0x32, 0x24, 0x24, 0x32, 0x50, 0x72, 0x94, 0xb4, 0x90, 0x6c, 0x48, 0x24, 0x0, 0x0, 0x24, 0x48, 0x6c, 0x90, 0xb4, 0x90, 0x6c, 0x48, 0x24, 0x0, 0x0, 0x24, 0x48, 0x6c, 0x90, 0xb7, 0x94, 0x72, 0x50, 0x32, 0x24, 0x24, 0x33, 0x50, 0x72, 0x94, 0xc2, 0xa1, 0x82, 0x65, 0x50, 0x48, 0x48, 0x50, 0x66, 0x82, 0xa1, 0xd2, 0xb4, 0x99, 0x82, 0x72, 0x6c, 0x6c, 0x72, 0x82, 0x99, 0xb4, 0xe6, 0xcb, 0xb4, 0xa1, 0x94, 0x90, 0x90, 0x94, 0xa1, 0xb4, 0xcc }; +const Vector ref_img_3_data = { 0xff, 0xe6, 0xd2, 0xc2, 0xb7, 0xb4, 0xb4, 0xb7, 0xc2, 0xd2, 0xe6, 0xe6, 0xcb, 0xb4, 0xa1, 0x94, 0x90, 0x90, 0x94, 0xa1, 0xb4, 0xcb, 0xd2, 0xb4, 0x99, 0x82, 0x72, 0x6c, 0x6c, 0x72, 0x82, 0x99, 0xb4, 0xc2, 0xa1, 0x82, 0x65, 0x50, 0x48, 0x48, 0x50, 0x65, 0x82, 0xa1, 0xb7, 0x94, 0x72, 0x50, 0x32, 0x24, 0x24, 0x32, 0x50, 0x72, 0x94, 0xb4, 0x90, 0x6c, 0x48, 0x24, 0x0, 0x0, 0x24, 0x48, 0x6c, 0x90, 0xb4, 0x90, 0x6c, 0x48, 0x24, 0x0, 0x0, 0x24, 0x48, 0x6c, 0x90, 0xb7, 0x94, 0x72, 0x50, 0x32, 0x24, 0x24, 0x33, 0x50, 0x72, 0x94, 0xc2, 0xa1, 0x82, 0x65, 0x50, 0x48, 0x48, 0x50, 0x66, 0x82, 0xa1, 0xd2, 0xb4, 0x99, 0x82, 0x72, 0x6c, 0x6c, 0x72, 0x82, 0x99, 0xb4, 0xe6, 0xcb, 0xb4, 0xa1, 0x94, 0x90, 0x90, 0x94, 0xa1, 0xb4, 0xcc }; + +// Utiliy function to compare two images pixel by pixel (for easy debugging of regressions) +void compare_image_with_reference(const Ref &p_img, const Ref &p_reference_img) { + for (int y = 0; y < p_img->get_height(); y++) { + for (int x = 0; x < p_img->get_width(); x++) { + CHECK(p_img->get_pixel(x, y) == p_reference_img->get_pixel(x, y)); + } + } +} + +TEST_CASE("[FastNoiseLite] Generating seamless 2D images (11x11px) and compare to reference images") { + FastNoiseLite noise; + noise.set_noise_type(FastNoiseLite::NoiseType::TYPE_CELLULAR); + noise.set_fractal_type(FastNoiseLite::FractalType::FRACTAL_NONE); + noise.set_cellular_distance_function(FastNoiseLite::CellularDistanceFunction::DISTANCE_EUCLIDEAN); + noise.set_frequency(0.1); + noise.set_cellular_jitter(0.0); + + SUBCASE("Blend skirt 0.0") { + Ref img = noise.get_seamless_image(11, 11, false, false, 0.0); + + Ref ref_img_1 = memnew(Image); + ref_img_1->set_data(11, 11, false, Image::FORMAT_L8, ref_img_1_data); + + compare_image_with_reference(img, ref_img_1); + } + + SUBCASE("Blend skirt 0.1") { + Ref img = noise.get_seamless_image(11, 11, false, false, 0.1); + + Ref ref_img_2 = memnew(Image); + ref_img_2->set_data(11, 11, false, Image::FORMAT_L8, ref_img_2_data); + + compare_image_with_reference(img, ref_img_2); + } + + SUBCASE("Blend skirt 1.0") { + Ref img = noise.get_seamless_image(11, 11, false, false, 0.1); + + Ref ref_img_3 = memnew(Image); + ref_img_3->set_data(11, 11, false, Image::FORMAT_L8, ref_img_3_data); + + compare_image_with_reference(img, ref_img_3); + } +} + +} //namespace TestFastNoiseLite + +#endif // TEST_FASTNOISE_LITE_H diff --git a/modules/noise/tests/test_noise_texture_2d.h b/modules/noise/tests/test_noise_texture_2d.h new file mode 100644 index 000000000000..9e280b5d970b --- /dev/null +++ b/modules/noise/tests/test_noise_texture_2d.h @@ -0,0 +1,267 @@ +/**************************************************************************/ +/* test_noise_texture_2d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_NOISE_TEXTURE_2D_H +#define TEST_NOISE_TEXTURE_2D_H + +#include "tests/test_macros.h" + +#include "modules/noise/noise_texture_2d.h" + +namespace TestNoiseTexture2D { + +class NoiseTextureTester : public RefCounted { + GDCLASS(NoiseTextureTester, RefCounted); + + const NoiseTexture2D *const texture; + +public: + NoiseTextureTester(const NoiseTexture2D *const p_texture) : + texture{ p_texture } {}; + + Color compute_average_color(const Ref &p_noise_image) { + Color r_avg_color{}; + + for (int i = 0; i < p_noise_image->get_width(); ++i) { + for (int j = 0; j < p_noise_image->get_height(); ++j) { + const Color pixel = p_noise_image->get_pixel(i, j); + r_avg_color += pixel; + } + } + + int pixel_count = p_noise_image->get_width() * p_noise_image->get_height(); + r_avg_color /= pixel_count; + return r_avg_color; + } + + void check_mip_and_color_ramp() { + const Ref noise_image = texture->get_image(); + CHECK(noise_image.is_valid()); + CHECK(noise_image->get_width() == texture->get_width()); + CHECK(noise_image->get_height() == texture->get_height()); + + CHECK(noise_image->get_format() == Image::FORMAT_RGBA8); + CHECK(noise_image->has_mipmaps()); + + Color avg_color = compute_average_color(noise_image); + + // Check that the noise texture is modulated correctly by the color ramp (Gradient). + CHECK_FALSE_MESSAGE((avg_color.r + avg_color.g + avg_color.b) == doctest::Approx(0.0), "The noise texture should not be all black"); + CHECK_FALSE_MESSAGE((avg_color.r + avg_color.g + avg_color.b) == doctest::Approx(noise_image->get_width() * noise_image->get_height() * 3.0), "The noise texture should not be all white"); + CHECK_MESSAGE(avg_color.g == doctest::Approx(0.0), "The noise texture should not have any green when modulated correctly by the color ramp"); + } + + void check_normal_map() { + const Ref noise_image = texture->get_image(); + CHECK(noise_image.is_valid()); + CHECK(noise_image->get_width() == texture->get_width()); + CHECK(noise_image->get_height() == texture->get_height()); + + CHECK(noise_image->get_format() == Image::FORMAT_RGBA8); + CHECK_FALSE(noise_image->has_mipmaps()); + + Color avg_color = compute_average_color(noise_image); + + // Check for the characteristic color distribution (for tangent space) of a normal map. + CHECK(avg_color.r == doctest::Approx(0.5).epsilon(0.05)); + CHECK(avg_color.g == doctest::Approx(0.5).epsilon(0.05)); + CHECK(avg_color.b == doctest::Approx(1.0).epsilon(0.05)); + } + + void check_seamless_texture_grayscale() { + const Ref noise_image = texture->get_image(); + CHECK(noise_image.is_valid()); + CHECK(noise_image->get_width() == texture->get_width()); + CHECK(noise_image->get_height() == texture->get_height()); + + CHECK(noise_image->get_format() == Image::FORMAT_L8); + + Color avg_color = compute_average_color(noise_image); + + // Since it's a grayscale image and every channel except the alpha channel has the + // same values (conversion happens in Image::get_pixel) we only need to test one channel. + CHECK(avg_color.r == doctest::Approx(0.5).epsilon(0.05)); + } + + void check_seamless_texture_rgba() { + const Ref noise_image = texture->get_image(); + CHECK(noise_image.is_valid()); + CHECK(noise_image->get_width() == texture->get_width()); + CHECK(noise_image->get_height() == texture->get_height()); + + CHECK(noise_image->get_format() == Image::FORMAT_RGBA8); + + // Check that the noise texture is modulated correctly by the color ramp (Gradient). + Color avg_color = compute_average_color(noise_image); + + // We use a default (black to white) gradient, so the average of the red, green and blue channels should be the same. + CHECK(avg_color.r == doctest::Approx(0.5).epsilon(0.05)); + CHECK(avg_color.g == doctest::Approx(0.5).epsilon(0.05)); + CHECK(avg_color.b == doctest::Approx(0.5).epsilon(0.05)); + } +}; + +TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") { + Ref noise_texture = memnew(NoiseTexture2D); + + Ref noise = memnew(FastNoiseLite); + noise_texture->set_noise(noise); + CHECK(noise_texture->get_noise() == noise); + noise_texture->set_noise(nullptr); + CHECK(noise_texture->get_noise() == nullptr); + + noise_texture->set_width(8); + noise_texture->set_height(4); + CHECK(noise_texture->get_width() == 8); + CHECK(noise_texture->get_height() == 4); + + ERR_PRINT_OFF; + noise_texture->set_width(-1); + noise_texture->set_height(-1); + ERR_PRINT_ON; + CHECK(noise_texture->get_width() == 8); + CHECK(noise_texture->get_height() == 4); + + noise_texture->set_invert(true); + CHECK(noise_texture->get_invert() == true); + noise_texture->set_invert(false); + CHECK(noise_texture->get_invert() == false); + + noise_texture->set_in_3d_space(true); + CHECK(noise_texture->is_in_3d_space() == true); + noise_texture->set_in_3d_space(false); + CHECK(noise_texture->is_in_3d_space() == false); + + noise_texture->set_generate_mipmaps(true); + CHECK(noise_texture->is_generating_mipmaps() == true); + noise_texture->set_generate_mipmaps(false); + CHECK(noise_texture->is_generating_mipmaps() == false); + + noise_texture->set_seamless(true); + CHECK(noise_texture->get_seamless() == true); + noise_texture->set_seamless(false); + CHECK(noise_texture->get_seamless() == false); + + noise_texture->set_seamless_blend_skirt(0.45); + CHECK(noise_texture->get_seamless_blend_skirt() == doctest::Approx(0.45)); + + ERR_PRINT_OFF; + noise_texture->set_seamless_blend_skirt(-1.0); + noise_texture->set_seamless_blend_skirt(2.0); + CHECK(noise_texture->get_seamless_blend_skirt() == doctest::Approx(0.45)); + ERR_PRINT_ON; + + noise_texture->set_as_normal_map(true); + CHECK(noise_texture->is_normal_map() == true); + noise_texture->set_as_normal_map(false); + CHECK(noise_texture->is_normal_map() == false); + + noise_texture->set_bump_strength(0.168); + CHECK(noise_texture->get_bump_strength() == doctest::Approx(0.168)); + + Ref gradient = memnew(Gradient); + noise_texture->set_color_ramp(gradient); + CHECK(noise_texture->get_color_ramp() == gradient); + noise_texture->set_color_ramp(nullptr); + CHECK(noise_texture->get_color_ramp() == nullptr); +} + +TEST_CASE("[NoiseTexture2D][SceneTree] Generating a basic noise texture with mipmaps and color ramp modulation") { + Ref noise_texture = memnew(NoiseTexture2D); + + Ref noise = memnew(FastNoiseLite); + noise_texture->set_noise(noise); + + Ref gradient = memnew(Gradient); + Vector points; + points.push_back({ 0.0, Color(1, 0, 0) }); + points.push_back({ 1.0, Color(0, 0, 1) }); + gradient->set_points(points); + noise_texture->set_color_ramp(gradient); + noise_texture->set_width(16); + noise_texture->set_height(16); + noise_texture->set_generate_mipmaps(true); + + Ref tester = memnew(NoiseTextureTester(noise_texture.ptr())); + noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTextureTester::check_mip_and_color_ramp)); + MessageQueue::get_singleton()->flush(); +} + +TEST_CASE("[NoiseTexture2D][SceneTree] Generating a normal map without mipmaps") { + Ref noise_texture = memnew(NoiseTexture2D); + + Ref noise = memnew(FastNoiseLite); + noise->set_frequency(0.5); + noise_texture->set_noise(noise); + noise_texture->set_width(16); + noise_texture->set_height(16); + noise_texture->set_as_normal_map(true); + noise_texture->set_bump_strength(0.5); + noise_texture->set_generate_mipmaps(false); + + Ref tester = memnew(NoiseTextureTester(noise_texture.ptr())); + noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTextureTester::check_normal_map)); + MessageQueue::get_singleton()->flush(); +} + +TEST_CASE("[NoiseTexture2D][SceneTree] Generating a seamless noise texture") { + Ref noise_texture = memnew(NoiseTexture2D); + + Ref noise = memnew(FastNoiseLite); + noise->set_frequency(0.5); + noise_texture->set_noise(noise); + noise_texture->set_width(16); + noise_texture->set_height(16); + noise_texture->set_seamless(true); + + Ref tester = memnew(NoiseTextureTester(noise_texture.ptr())); + + SUBCASE("Grayscale(L8) 16x16, with seamless blend skirt of 0.05") { + noise_texture->set_seamless_blend_skirt(0.05); + noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTextureTester::check_seamless_texture_grayscale)); + MessageQueue::get_singleton()->flush(); + } + + SUBCASE("16x16 modulated with default (transparent)black and white gradient (RGBA8), with seamless blend skirt of 1.0") { + Ref gradient = memnew(Gradient); + Vector points; + points.push_back({ 0.0, Color(0, 0, 0, 0) }); + points.push_back({ 1.0, Color(1, 1, 1, 1) }); + gradient->set_points(points); + noise_texture->set_color_ramp(gradient); + noise_texture->set_seamless_blend_skirt(1.0); + noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTextureTester::check_seamless_texture_rgba)); + MessageQueue::get_singleton()->flush(); + } +} + +} //namespace TestNoiseTexture2D + +#endif // TEST_NOISE_TEXTURE_2D_H