OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / shared / erosion_cpp_shim.h

134 lines · c ref:
#ifndef PROC_NODES_EROSION_CPP_SHIM_H
#define PROC_NODES_EROSION_CPP_SHIM_H

// =========================================================================
// erosion_cpp_shim.h — the C++ half of erosion_filter.inc's dual compilation.
//
// erosion_filter.inc is written in the subset GLSL and C++ agree on. In a
// shader it is injected verbatim and every builtin it names is a GLSL
// builtin. In C++ nothing named vec2, fract or dot exists, so this header
// supplies them — with the SAME componentwise semantics — and then the .inc
// compiles unchanged.
//
// THE ONE RULE FOR THIS FILE: every function here must match its GLSL
// builtin exactly, including on the edges. GLSL fract(x) is x - floor(x),
// which for negative x is NOT std::fmod and NOT truncation; getting that
// wrong would make the CPU and GPU disagree in precisely the way this whole
// exercise exists to prevent, while looking correct in a diff.
//
// Deliberately NOT included: <algorithm>. std::max/std::min would be
// candidates for the unqualified max()/min() calls inside the .inc and the
// overload resolution against the double-typed GLSL literals (max(0.0, x))
// is not obviously stable across compilers. The overloads below are the
// only ones in scope.
//
// This header is godot-free by design — the whole point is that the erosion
// filter can be evaluated by an oracle with no engine. Do not add
// godot_cpp includes here; convert at the call site instead.
// =========================================================================

#include <cmath>
#include <cstdint>

namespace dcerosion {

// GLSL's uint. The integer lattice hash in erosion_filter.inc is written in
// 32-bit unsigned arithmetic precisely because it is the one arithmetic both
// languages define identically: GLSL requires unsigned overflow to wrap, and
// C++ defines unsigned overflow as modulo 2^32. Signed types would have been
// undefined behaviour on one side and implementation-defined on the other.
typedef uint32_t uint;

struct vec2 {
	float x, y;
	vec2() : x(0.0f), y(0.0f) {}
	explicit vec2(float s) : x(s), y(s) {}
	vec2(float a, float b) : x(a), y(b) {}
};

struct vec3 {
	float x, y, z;
	vec3() : x(0.0f), y(0.0f), z(0.0f) {}
	explicit vec3(float s) : x(s), y(s), z(s) {}
	vec3(float a, float b, float c) : x(a), y(b), z(c) {}
};

// vec4 carries phacelle_noise's (cos, sin, sideDir.x, sideDir.y) and the
// rounding/onset parameter bundles. No arithmetic operators are defined for it
// on purpose: the .inc only ever constructs one and reads its components, and
// an unused operator is somewhere for GLSL and C++ to differ unnoticed.
struct vec4 {
	float x, y, z, w;
	vec4() : x(0.0f), y(0.0f), z(0.0f), w(0.0f) {}
	explicit vec4(float s) : x(s), y(s), z(s), w(s) {}
	vec4(float a, float b, float c, float d) : x(a), y(b), z(c), w(d) {}
};

// -- vec2 arithmetic. GLSL vec*vec is COMPONENTWISE, not a dot product.
inline vec2 operator+(vec2 a, vec2 b) { return vec2(a.x + b.x, a.y + b.y); }
inline vec2 operator-(vec2 a, vec2 b) { return vec2(a.x - b.x, a.y - b.y); }
inline vec2 operator*(vec2 a, vec2 b) { return vec2(a.x * b.x, a.y * b.y); }
inline vec2 operator/(vec2 a, vec2 b) { return vec2(a.x / b.x, a.y / b.y); }
inline vec2 operator+(vec2 a, float s) { return vec2(a.x + s, a.y + s); }
inline vec2 operator-(vec2 a, float s) { return vec2(a.x - s, a.y - s); }
inline vec2 operator*(vec2 a, float s) { return vec2(a.x * s, a.y * s); }
inline vec2 operator/(vec2 a, float s) { return vec2(a.x / s, a.y / s); }
inline vec2 operator+(float s, vec2 a) { return vec2(s + a.x, s + a.y); }
inline vec2 operator-(float s, vec2 a) { return vec2(s - a.x, s - a.y); }
inline vec2 operator*(float s, vec2 a) { return vec2(s * a.x, s * a.y); }
inline vec2 operator-(vec2 a) { return vec2(-a.x, -a.y); }
inline vec2 &operator+=(vec2 &a, vec2 b) { a.x += b.x; a.y += b.y; return a; }
inline vec2 &operator-=(vec2 &a, vec2 b) { a.x -= b.x; a.y -= b.y; return a; }

// -- vec3 arithmetic.
inline vec3 operator+(vec3 a, vec3 b) { return vec3(a.x + b.x, a.y + b.y, a.z + b.z); }
inline vec3 operator-(vec3 a, vec3 b) { return vec3(a.x - b.x, a.y - b.y, a.z - b.z); }
inline vec3 operator*(vec3 a, vec3 b) { return vec3(a.x * b.x, a.y * b.y, a.z * b.z); }
inline vec3 operator*(vec3 a, float s) { return vec3(a.x * s, a.y * s, a.z * s); }
inline vec3 operator/(vec3 a, float s) { return vec3(a.x / s, a.y / s, a.z / s); }
inline vec3 operator*(float s, vec3 a) { return vec3(s * a.x, s * a.y, s * a.z); }
inline vec3 operator-(vec3 a) { return vec3(-a.x, -a.y, -a.z); }
inline vec3 &operator+=(vec3 &a, vec3 b) { a.x += b.x; a.y += b.y; a.z += b.z; return a; }
inline vec3 &operator-=(vec3 &a, vec3 b) { a.x -= b.x; a.y -= b.y; a.z -= b.z; return a; }

// -- Scalar builtins. float-only overloads: the .inc writes GLSL literals
//    (0.0, 1e-6) which are double in C++, so these accept the implicit
//    narrowing rather than leaving the call ambiguous.
inline float floorf_(float x) { return std::floor(x); }
inline float floor(float x) { return std::floor(x); }
inline float fract(float x) { return x - std::floor(x); }
inline float max(float a, float b) { return a > b ? a : b; }
inline float min(float a, float b) { return a < b ? a : b; }
inline float exp(float x) { return std::exp(x); }
inline float cos(float x) { return std::cos(x); }
inline float sin(float x) { return std::sin(x); }
inline float sqrt(float x) { return std::sqrt(x); }
inline float abs(float x) { return std::fabs(x); }
inline float pow(float a, float b) { return std::pow(a, b); }
inline float sign(float x) { return x > 0.0f ? 1.0f : (x < 0.0f ? -1.0f : 0.0f); }
inline float clamp(float v, float lo, float hi) { return v < lo ? lo : (v > hi ? hi : v); }
inline float mix(float a, float b, float t) { return a + (b - a) * t; }
inline float smoothstep(float e0, float e1, float x) {
	float t = clamp((x - e0) / (e1 - e0), 0.0f, 1.0f);
	return t * t * (3.0f - 2.0f * t);
}

// -- Vector builtins. Componentwise, matching GLSL.
inline vec2 floor(vec2 v) { return vec2(std::floor(v.x), std::floor(v.y)); }
inline vec2 fract(vec2 v) { return vec2(fract(v.x), fract(v.y)); }
inline vec2 abs(vec2 v) { return vec2(std::fabs(v.x), std::fabs(v.y)); }
inline vec2 sign(vec2 v) { return vec2(sign(v.x), sign(v.y)); }
inline float dot(vec2 a, vec2 b) { return a.x * b.x + a.y * b.y; }
inline float length(vec2 v) { return std::sqrt(dot(v, v)); }
inline vec2 normalize(vec2 v) { float l = length(v); return l > 0.0f ? v / l : vec2(0.0f); }
inline vec2 mix(vec2 a, vec2 b, float t) { return a + (b - a) * t; }
inline vec3 mix(vec3 a, vec3 b, float t) { return a + (b - a) * t; }

// The algorithm itself. Same text the shader compiles.
#include "erosion_filter.inc"


} // namespace dcerosion

#endif // PROC_NODES_EROSION_CPP_SHIM_H