OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / shared / blender_noise.h

181 lines · c ref:
#ifndef PROC_NODES_BLENDER_NOISE_H
#define PROC_NODES_BLENDER_NOISE_H

// BLENDER'S NOISE HASH — NOW AN ADAPTER, NOT AN IMPLEMENTATION.
//
// The arithmetic that used to live here is in src/shared/blender_hash.inc,
// which is compiled BOTH as GLSL and as C++ — the same arrangement
// erosion_filter.inc has had since the erosion filter stopped existing twice.
// This header is the godot-typed face of it: it converts Vector2/3/4 to the
// shim's vec2/3/4 at the call site and calls the one implementation. Every
// name and signature that was here before is still here, so blender_perlin.h,
// blender_voronoi.h, noise_nodes.cpp and selection_nodes.cpp did not change.
//
// WHY THIS EXISTS. proc-nodes' Blender-facing texture nodes had their own
// arithmetic: White Noise was `fract(sin(dot(...)))` and Voronoi was a
// hand-rolled 3x3x3 F1 with a hash of its own. They are the right SHAPE and
// unrelated NUMBERS, which is the worst way for a bridge to be wrong -- a
// graph crosses, evaluates, produces plausible terrain, and is a different
// function from the one the author was looking at in Blender.
//
// WHY IT MOVED. That fix left the hash written TWICE more: this port, and a
// hand-written GLSL copy in GpuCompiler's noise library (deleted 2026-09-05,
// once the last two nodes came off it). They
// agreed -- measured, 22/22 exact, by tests/test_hash_parity.cpp -- but they
// agreed the way two transcriptions agree, which is to say nothing was
// stopping them from drifting the next time either was touched. The hash is
// the layer where that matters most: Voronoi's cell offsets are it, Perlin's
// lattice gradients are it, and Blender's White Noise Texture node is it and
// nothing else, so a disagreement cannot be seen from above -- it looks like
// the structure. This repo has already paid that bill once, in
// ProcNodes_Hash2D, at a correlation of -0.0043.
//
// THE HASH IS THE BOTTOM LAYER AND IT IS CHECKED ON ITS OWN, and because it
// is integer arithmetic the check is BIT-EXACT: GLSL requires unsigned
// overflow to wrap and C++ defines it as modulo 2^32, so there is no
// tolerance to hide in. tests/test_hash_parity.cpp compiles the .inc both
// ways and compares.
//
// Ported from `source/blender/blenlib/intern/noise.cc` in the Blender tree at
// /storage/projects/code/blender-source, commit 61a36e08, BLENDER_VERSION 502
// -- the same 5.2 the bridge is checked against.

#include "blender_noise_cpp_shim.h"

#include <godot_cpp/variant/vector2.hpp>
#include <godot_cpp/variant/vector3.hpp>
#include <godot_cpp/variant/vector4.hpp>

#include <cstdint>

namespace godot {
namespace bnoise {

// -- the conversion, and it is the ONLY thing this file does. Kept private:
//    the shim's types are an implementation detail of the dual compilation
//    and nothing outside should be holding one.
namespace detail {
inline bnoisegl::vec2 gl(const Vector2 &v) { return bnoisegl::vec2(v.x, v.y); }
inline bnoisegl::vec3 gl(const Vector3 &v) { return bnoisegl::vec3(v.x, v.y, v.z); }
inline bnoisegl::vec4 gl(const Vector4 &v) { return bnoisegl::vec4(v.x, v.y, v.z, v.w); }
inline Vector2 gd(const bnoisegl::vec2 &v) { return Vector2(v.x, v.y); }
inline Vector3 gd(const bnoisegl::vec3 &v) { return Vector3(v.x, v.y, v.z); }
inline Vector4 gd(const bnoisegl::vec4 &v) { return Vector4(v.x, v.y, v.z, v.w); }
} // namespace detail

// --- Jenkins Lookup3 ---

static inline uint32_t hash(uint32_t kx) { return bnoisegl::bn_hash(kx); }
static inline uint32_t hash(uint32_t kx, uint32_t ky) { return bnoisegl::bn_hash(kx, ky); }
static inline uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz) {
	return bnoisegl::bn_hash(kx, ky, kz);
}
static inline uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) {
	return bnoisegl::bn_hash(kx, ky, kz, kw);
}

static inline uint32_t float_as_uint(float f) { return bnoisegl::floatBitsToUint(f); }

static inline uint32_t hash_float(float k) { return bnoisegl::bn_hash_float(k); }
static inline uint32_t hash_float(const Vector2 &k) { return bnoisegl::bn_hash_float(detail::gl(k)); }
static inline uint32_t hash_float(const Vector3 &k) { return bnoisegl::bn_hash_float(detail::gl(k)); }
static inline uint32_t hash_float(const Vector4 &k) { return bnoisegl::bn_hash_float(detail::gl(k)); }

static inline float uint_to_float_01(uint32_t k) { return bnoisegl::bn_uint_to_float_01(k); }

// Hashing INTEGERS into [0, 1] -- as against the `hash_float_to_float` family
// below, which hashes the BIT PATTERNS of floats. Random Value uses these:
// its inputs are an integer id and an integer seed, not coordinates.
static inline float hash_to_float(uint32_t kx) { return bnoisegl::bn_hash_to_float(kx); }
static inline float hash_to_float(uint32_t kx, uint32_t ky) {
	return bnoisegl::bn_hash_to_float(kx, ky);
}
static inline float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz) {
	return bnoisegl::bn_hash_to_float(kx, ky, kz);
}
static inline float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) {
	return bnoisegl::bn_hash_to_float(kx, ky, kz, kw);
}

static inline float hash_float_to_float(float k) { return bnoisegl::bn_hash_float_to_float(k); }
static inline float hash_float_to_float(const Vector2 &k) {
	return bnoisegl::bn_hash_float_to_float(detail::gl(k));
}
static inline float hash_float_to_float(const Vector3 &k) {
	return bnoisegl::bn_hash_float_to_float(detail::gl(k));
}
static inline float hash_float_to_float(const Vector4 &k) {
	return bnoisegl::bn_hash_float_to_float(detail::gl(k));
}

static inline Vector2 hash_float_to_float2(const Vector2 &k) {
	return detail::gd(bnoisegl::bn_hash_float_to_float2(detail::gl(k)));
}
static inline Vector2 hash_float_to_float2(const Vector3 &k) {
	return detail::gd(bnoisegl::bn_hash_float_to_float2(detail::gl(k)));
}
static inline Vector2 hash_float_to_float2(const Vector4 &k) {
	return detail::gd(bnoisegl::bn_hash_float_to_float2(detail::gl(k)));
}

static inline Vector3 hash_float_to_float3(float k) {
	return detail::gd(bnoisegl::bn_hash_float_to_float3(k));
}
static inline Vector3 hash_float_to_float3(const Vector2 &k) {
	return detail::gd(bnoisegl::bn_hash_float_to_float3(detail::gl(k)));
}
static inline Vector3 hash_float_to_float3(const Vector3 &k) {
	return detail::gd(bnoisegl::bn_hash_float_to_float3(detail::gl(k)));
}
static inline Vector3 hash_float_to_float3(const Vector4 &k) {
	return detail::gd(bnoisegl::bn_hash_float_to_float3(detail::gl(k)));
}

static inline Vector4 hash_float_to_float4(const Vector4 &k) {
	return detail::gd(bnoisegl::bn_hash_float_to_float4(detail::gl(k)));
}

// --- PCG integer hashes ---
//
// VORONOI USES THESE, NOT THE JENKINS FLOAT HASH ABOVE -- except in 1D, which
// uses hash_float_to_float. Reading that off Blender's source rather than
// assuming one hash per file is the difference between a port that matches and
// one that is the right shape with unrelated numbers.
//
// The types ARE the shim's, not copies of it: blender_voronoi.h writes
// `IVec3{ x, y, z }` and that has to keep meaning the thing the .inc hashes.

typedef bnoisegl::ivec2 IVec2;
typedef bnoisegl::ivec3 IVec3;
typedef bnoisegl::ivec4 IVec4;

static inline int32_t wrap_mul(int32_t a, int32_t b) { return bnoisegl::bn_wrap_mul(a, b); }
static inline int32_t wrap_add(int32_t a, int32_t b) { return bnoisegl::bn_wrap_add(a, b); }

static inline IVec2 hash_pcg2d_i(IVec2 v) { return bnoisegl::bn_hash_pcg2d_i(v); }
static inline IVec3 hash_pcg3d_i(IVec3 v) { return bnoisegl::bn_hash_pcg3d_i(v); }
static inline IVec4 hash_pcg4d_i(IVec4 v) { return bnoisegl::bn_hash_pcg4d_i(v); }

static inline float int_to_float_01(int32_t k) { return bnoisegl::bn_int_to_float_01(k); }

static inline Vector2 hash_int2_to_float2(IVec2 k) {
	return detail::gd(bnoisegl::bn_hash_int_to_float2(k));
}
static inline Vector3 hash_int3_to_float3(IVec3 k) {
	return detail::gd(bnoisegl::bn_hash_int_to_float3(k));
}
static inline Vector4 hash_int4_to_float4(IVec4 k) {
	return detail::gd(bnoisegl::bn_hash_int_to_float4(k));
}
static inline Vector3 hash_int2_to_float3(IVec2 k) {
	return detail::gd(bnoisegl::bn_hash_int_to_float3(k));
}
static inline Vector3 hash_int4_to_float3(IVec4 k) {
	return detail::gd(bnoisegl::bn_hash_int_to_float3(k));
}

} // namespace bnoise
} // namespace godot

#endif // PROC_NODES_BLENDER_NOISE_H