Skip to content

Commit

Permalink
Make our custom noises inherit Godot Noise.
Browse files Browse the repository at this point in the history
  • Loading branch information
Zylann committed Dec 18, 2024
1 parent d4dffef commit af88b2f
Show file tree
Hide file tree
Showing 12 changed files with 383 additions and 72 deletions.
1 change: 1 addition & 0 deletions common.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def get_sources(env, is_editor_build, include_tests):
"util/godot/classes/mesh.cpp",
"util/godot/classes/multimesh.cpp",
"util/godot/classes/node.cpp",
"util/godot/classes/noise.cpp",
"util/godot/classes/object.cpp",
"util/godot/classes/project_settings.cpp",
"util/godot/classes/rendering_device.cpp",
Expand Down
1 change: 1 addition & 0 deletions doc/source/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Semver is not yet in place, so each version can have breaking changes, although

Primarily developped with Godot 4.3.

- Noises (`ZN_FastNoiseLite`, `FastNoise2`, `SpotNoise`) now inherit from `Noise` in Godot 4.4 so they can be used in more places.
- `VoxelBlockyModel`: Added option to turn off "LOD skirts" when used with `VoxelLodTerrain`, which may be useful with transparent models
- `VoxelBlockyModelCube`: Added support for mesh rotation like `VoxelBlockyMesh` (prior to that, rotation buttons in the editor only swapped tiles around)
- `VoxelInstanceGenerator`: Added `OnePerTriangle` emission mode
Expand Down
53 changes: 40 additions & 13 deletions generators/graph/nodes/noise.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ void register_noise_nodes(Span<NodeType> types) {
const Runtime::Buffer &y = ctx.get_input(1);
Runtime::Buffer &out = ctx.get_output(0);
const Params p = ctx.get_params<Params>();
// TODO Fastpath for our custom noises, so separate nodes are no longer necessary?
// There is overhead in using the generic Godot interface, it goes through virtual functions, maybe even
// GDExtension API bridge. It is always better if inlining is possible (or having an API that can take
// series)
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = p.noise->get_noise_2d(x.data[i], y.data[i]);
}
Expand All @@ -149,23 +153,45 @@ void register_noise_nodes(Span<NodeType> types) {
ctx.make_error(String(ZN_TTR("{0} instance is null")).format(varray(Noise::get_class_static())));
return;
}
Ref<FastNoiseLite> fnl = noise;
if (fnl.is_null()) {
ctx.make_error(String(ZN_TTR("Shader generation with {0} is not supported."))
.format(varray(noise->get_class())));

Ref<FastNoiseLite> godot_fnl = noise;
if (godot_fnl.is_valid()) {
if (godot_fnl->is_domain_warp_enabled()) {
// TODO Support domain warp shader generation with Godot's implementation of FastNoiseLite
ctx.make_error(
String(ZN_TTR("Shader generation from {0} with domain warp is not supported. Use {1}."))
.format(varray(noise->get_class(), ZN_FastNoiseLiteGradient::get_class_static()))
);
return;
}
ctx.require_lib_code("vg_fnl", g_fast_noise_lite_shader);
add_fast_noise_lite_state_config(ctx, **godot_fnl);
ctx.add_format(
"{} = fnlGetNoise2D(state, {}, {});\n",
ctx.get_output_name(0),
ctx.get_input_name(0),
ctx.get_input_name(1)
);
return;
}
if (fnl->is_domain_warp_enabled()) {
// TODO Support domain warp shader generation with Godot's implementation of FastNoiseLite
ctx.make_error(
String(ZN_TTR("Shader generation from {0} with domain warp is not supported. Use {1}."))
.format(varray(noise->get_class(), ZN_FastNoiseLiteGradient::get_class_static())));

Ref<ZN_FastNoiseLite> zn_fnl = noise;
if (zn_fnl.is_valid()) {
ctx.require_lib_code("vg_fnl", g_fast_noise_lite_shader);
add_fast_noise_lite_state_config(ctx, **zn_fnl);
ctx.add_format(
"{} = fnlGetNoise2D(state, {}, {});\n",
ctx.get_output_name(0),
ctx.get_input_name(0),
ctx.get_input_name(1)
);
return;
}
ctx.require_lib_code("vg_fnl", g_fast_noise_lite_shader);
add_fast_noise_lite_state_config(ctx, **fnl);
ctx.add_format("{} = fnlGetNoise2D(state, {}, {});\n", ctx.get_output_name(0), ctx.get_input_name(0),
ctx.get_input_name(1));

ctx.make_error(
String(ZN_TTR("Shader generation with {0} is not supported.")).format(varray(noise->get_class()))
);
return;
};
}
{
Expand Down Expand Up @@ -204,6 +230,7 @@ void register_noise_nodes(Span<NodeType> types) {
const Runtime::Buffer &z = ctx.get_input(2);
Runtime::Buffer &out = ctx.get_output(0);
const Params p = ctx.get_params<Params>();
// TODO Fastpath for our custom noises, so separate nodes are no longer necessary?
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = p.noise->get_noise_3d(x.data[i], y.data[i], z.data[i]);
}
Expand Down
2 changes: 2 additions & 0 deletions register_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ void initialize_voxel_module(ModuleInitializationLevel p_level) {
open_log_file();
#endif

ClassDB::register_class<ZN_Noise>();

// TODO Enhancement: can I prevent users from instancing `VoxelEngine`?
// This class is used as a singleton so it's not really abstract.
// Should I use `register_abstract_class` anyways?
Expand Down
1 change: 1 addition & 0 deletions terrain/instancing/voxel_instance_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,7 @@ void VoxelInstanceGenerator::generate_transforms(
// Legacy noise (noise graph is more versatile, but this remains for compatibility)
if (noise.is_valid()) {
noise_cache.resize(vertex_cache.size());
// TODO Fastpath for our custom noises

switch (_noise_dimension) {
case DIMENSION_2D: {
Expand Down
41 changes: 41 additions & 0 deletions util/godot/classes/noise.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,52 @@
#ifndef ZN_GODOT_NOISE_H
#define ZN_GODOT_NOISE_H

#include "../core/version.h"

#if defined(ZN_GODOT)
#include <modules/noise/noise.h>
#elif defined(ZN_GODOT_EXTENSION)
#include <godot_cpp/classes/noise.hpp>
using namespace godot;
#endif

namespace zylann {

#if defined(ZN_GODOT) || (GODOT_VERSION_MAJOR == 4 && GODOT_VERSION_MINOR >= 4)
class ZN_Noise : public Noise {
GDCLASS(ZN_Noise, Noise)
#else
// Noise is abstract in the extension API and cannot be inherited
class ZN_Noise : public Resource {
GDCLASS(ZN_Noise, Resource)
#endif
public:
#if defined(ZN_GODOT)
real_t get_noise_1d(real_t p_x) const override;
real_t get_noise_2dv(Vector2 p_v) const override;
real_t get_noise_2d(real_t p_x, real_t p_y) const override;
real_t get_noise_3dv(Vector3 p_v) const override;
real_t get_noise_3d(real_t p_x, real_t p_y, real_t p_z) const override;
#elif defined(ZN_GODOT_EXTENSION) && (GODOT_VERSION_MAJOR == 4 && GODOT_VERSION_MINOR >= 4)
real_t _get_noise_1d(real_t p_x) const override;
real_t _get_noise_2d(Vector2 p_v) const override;
real_t _get_noise_3d(Vector3 p_v) const override;
#endif

protected:
virtual real_t _zn_get_noise_1d(real_t p_x) const;
virtual real_t _zn_get_noise_2d(Vector2 p_v) const;
virtual real_t _zn_get_noise_3d(Vector3 p_v) const;

// #if defined(ZN_GODOT_EXTENSION) && (GODOT_VERSION_MAJOR == 4 && GODOT_VERSION_MINOR <= 3)
// void real_t _b_get_noise_2dc(real_t p_x, real_t p_y) const;
// void real_t _b_get_noise_3dc(real_t p_x, real_t p_y, real_t p_z) const;
// #endif

private:
static void _bind_methods();
};

} // namespace zylann

#endif // ZN_GODOT_NOISE_H
168 changes: 168 additions & 0 deletions util/noise/fast_noise_2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,173 @@ String FastNoise2::_b_get_simd_level_name(SIMDLevel level) {
return get_simd_level_name(level);
}

real_t FastNoise2::_zn_get_noise_1d(real_t p_x) const {
return get_noise_2d_single(Vector2(p_x, 0.0));
}

real_t FastNoise2::_zn_get_noise_2d(Vector2 p_v) const {
return get_noise_2d_single(p_v);
}

real_t FastNoise2::_zn_get_noise_3d(Vector3 p_v) const {
return get_noise_3d_single(p_v);
}

float get_sum(Span<const float> values) {
float sum = 0.f;
for (const float v : values) {
sum += v;
}
return sum;
}

void normalize(Span<float> values) {
const float sum = get_sum(values);
if (sum > 0.0001f) {
const float inv_sum = 1.f / sum;
for (float &v : values) {
v *= inv_sum;
}
}
}

PackedByteArray f32_snorm_to_u8_packed_byte_array(Span<const float> src) {
PackedByteArray bytes;
bytes.resize(src.size());
Span<uint8_t> bytes_s(bytes.ptrw(), bytes.size());
for (size_t i = 0; i < src.size(); ++i) {
const float n = src[i];
bytes_s[i] = math::clamp(static_cast<int>((n * 0.5f + 0.5f) * 255.f), 0, 255);
}
return bytes;
}

#if defined(ZN_GODOT)
Ref<Image> FastNoise2::get_image(
#elif defined(ZN_GODOT_EXTENSION)
Ref<Image> FastNoise2::_get_image(
#endif
int p_width,
int p_height,
bool p_invert,
bool p_in_3d_space,
bool p_normalize
) const {
ZN_ASSERT_RETURN_V(p_width >= 0, Ref<Image>());
ZN_ASSERT_RETURN_V(p_height >= 0, Ref<Image>());

StdVector<float> noise_buffer;
noise_buffer.resize(p_width * p_height);

if (p_in_3d_space) {
get_noise_3d_grid(Vector3(), Vector3i(p_width, p_height, 1), to_span(noise_buffer));
} else {
get_noise_2d_grid(Vector2(), Vector2i(p_width, p_height), to_span(noise_buffer));
}

if (p_invert) {
for (float &v : noise_buffer) {
v = 1.f - v;
}
}

if (p_normalize) {
normalize(to_span(noise_buffer));
}

const PackedByteArray image_bytes = f32_snorm_to_u8_packed_byte_array(to_span(noise_buffer));

const Ref<Image> image = Image::create_from_data(p_width, p_height, false, Image::FORMAT_L8, image_bytes);

return image;
}

#if defined(ZN_GODOT)
TypedArray<Image> FastNoise2::get_image_3d(
#elif defined(ZN_GODOT_EXTENSION)
TypedArray<Image> FastNoise2::_get_image_3d(
#endif
int p_width,
int p_height,
int p_depth,
bool p_invert,
bool p_normalize
) const {
ZN_ASSERT_RETURN_V(p_width >= 0, TypedArray<Image>());
ZN_ASSERT_RETURN_V(p_height >= 0, TypedArray<Image>());
ZN_ASSERT_RETURN_V(p_depth >= 0, TypedArray<Image>());

StdVector<float> noise_buffer;
noise_buffer.resize(p_width * p_height * p_depth);

get_noise_3d_grid(Vector3(), Vector3i(p_width, p_height, p_depth), to_span(noise_buffer));

if (p_invert) {
for (float &v : noise_buffer) {
v = 1.f - v;
}
}

if (p_normalize) {
normalize(to_span(noise_buffer));
}

TypedArray<Image> images;
images.resize(p_height);

const unsigned int deck_size = p_width * p_height;

for (int z = 0; z < p_depth; ++z) {
const Span<const float> deck = to_span(noise_buffer).sub(z * deck_size, deck_size);
const PackedByteArray image_bytes = f32_snorm_to_u8_packed_byte_array(deck);
const Ref<Image> image = Image::create_from_data(p_width, p_height, false, Image::FORMAT_L8, image_bytes);
images[z] = image;
}

return images;
}

#if defined(ZN_GODOT)
Ref<Image> FastNoise2::get_seamless_image(
#elif defined(ZN_GODOT_EXTENSION)
Ref<Image> FastNoise2::_get_seamless_image(
#endif
int p_width,
int p_height,
bool p_invert,
bool p_in_3d_space,
real_t p_blend_skirt,
bool p_normalize
) const {
ZN_ASSERT_RETURN_V(p_width >= 0, Ref<Image>());
ZN_ASSERT_RETURN_V(p_height >= 0, Ref<Image>());

StdVector<float> noise_buffer;
noise_buffer.resize(p_width * p_height);

if (p_in_3d_space) {
ZN_PRINT_ERROR("Not supported");
} else {
get_noise_2d_grid_tileable(Vector2i(p_width, p_height), to_span(noise_buffer));
}

if (p_invert) {
for (float &v : noise_buffer) {
v = 1.f - v;
}
}

if (p_normalize) {
normalize(to_span(noise_buffer));
}

const PackedByteArray image_bytes = f32_snorm_to_u8_packed_byte_array(to_span(noise_buffer));

const Ref<Image> image = Image::create_from_data(p_width, p_height, false, Image::FORMAT_L8, image_bytes);

return image;
}

void FastNoise2::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_noise_type", "type"), &FastNoise2::set_noise_type);
ClassDB::bind_method(D_METHOD("get_noise_type"), &FastNoise2::get_noise_type);
Expand Down Expand Up @@ -638,6 +805,7 @@ void FastNoise2::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_noise_2d_single", "pos"), &FastNoise2::get_noise_2d_single);
ClassDB::bind_method(D_METHOD("get_noise_3d_single", "pos"), &FastNoise2::get_noise_3d_single);

// TODO Rename `get_image_3d_simd`, or have Godot expose get_image* functions as virtual
ClassDB::bind_method(D_METHOD("generate_image", "image", "tileable"), &FastNoise2::generate_image);

ClassDB::bind_method(D_METHOD("get_simd_level_name", "level"), &FastNoise2::_b_get_simd_level_name);
Expand Down
Loading

0 comments on commit af88b2f

Please sign in to comment.