OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes commits / cdee735

voronoi: the fourth .inc, and the node -- four of five
1118 lines, the largest of the shared sources, and the last one with a second implementation behind it: the GLSL used to be a hand-rolled 3x3x3 F1 with a hash of its own, two sockets against Blender's nine, one feature, one metric, no octaves. supports_gpu() is true again with blender_voronoi.inc behind it. .inc vs the old header 24960/24960 bit-exact, 4800 dim x feature x metric CPU vs GPU 4992/4992 (2496 of them Voronoi) op parity 716 -> 722/722 golden 12/12 blender 31/31 voronoi parity TRANSFORMED, NOT RETYPED. 1118 lines of working, gated arithmetic is exactly the wrong thing to copy by hand, so the .inc is produced by a mechanical rewrite of the header and the equivalence run is what says the rewrite was faithful. That run earned its keep three times over. ⛔ THE STRUCT-DEFAULT STRIPPER ATE LOCAL VARIABLES. GLSL structs cannot carry default member initialisers, so BvParams and BvOutput had to lose theirs -- but the regex matched any one-tab-indented declaration and so deleted the initialiser from every LOCAL of that shape too, including `float minDistance = FLT_MAX;`. That turns the nearest-point search into a search against garbage. It compiled cleanly in both languages and produced plausible cells. 4836/24960. ⛔ THE ACCUMULATOR STARTED FROM GARBAGE. `VoronoiOutput output;` relied on the same member initialisers, and the fractal wrapper accumulates into it. Zeroed explicitly now. I had written the warning about this for BvParams in the file's own header and still missed it for BvOutput. ⛔ A BARE sqrt()/pow() IN THE .inc RESOLVED TO THE DOUBLE VERSION in C++, so the CPU computed in double where the GPU computes in float. 208 of 24960 differed by ~1e-7 -- small enough to look like ordinary FMA noise and it was not. Float overloads in the shim; all 208 came back exact. Also: `output` is a RESERVED WORD in GLSL and an ordinary local in C++, the one place these two languages disagree about a NAME rather than arithmetic. THE EMITTER HAD ITS OWN BUG, and the op-parity gate is what found it. max_distance is not the same on every path: N_SPHERE_RADIUS leaves the struct default, DISTANCE_TO_EDGE sets it to 0.5 + 0.5 * randomness, and f1/f2/smooth compute the per-dimension cell distance. I wrote 1.0 for the middle one because it looked like "the default". One failing case, Voronoi.d3.f3, cpu 0.0586412 against gpu 0.0542431 -- small, plausible, and not findable by reading. Gate cases are split the way the two instruments differ: every dimension x feature x metric x normalize goes in the data-driven gate (2496 cases, 672 MiB), and six cases in the op gate for what only it can see -- that the emitter builds the parameter block, picks the per-dimension function and routes five outputs. Two of those six are the edge and radius features, precisely because those are where max_distance is decided. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
cdee7352c1b220a3ee61c941c6d9e3c4546972df
by plankatron <fleeky@gmail.com> · 2026-09-06T00:29:51.000Z

Changed files (8) +1654 -1046

SConstruct +6 -0
……@@ -66,6 +66,12 @@ glsl_inc_gen = [
6666 "src/shared/blender_perlin.inc",
6767 "python3 tools/gen_glsl_inc.py $SOURCE $TARGET BLENDER_PERLIN_GLSL",
6868 ),
69 # Voronoi, fourth .inc. Also depends on the hash and does not include it.
70 env.Command(
71 "src/gpu/blender_voronoi_glsl.gen.h",
72 "src/shared/blender_voronoi.inc",
73 "python3 tools/gen_glsl_inc.py $SOURCE $TARGET BLENDER_VORONOI_GLSL",
74 ),
6975]
7076
7177sources = (
demo/glsl_op_gate_dump.gd +49 -0
……@@ -350,6 +350,55 @@ func _init() -> void:
350350 PackedStringArray()),
351351 })
352352
353 # --- Voronoi Texture --------------------------------------------------
354 # Few, for the same reason as Noise Texture: this gate inlines a function
355 # per case and a Voronoi case carries a 3x3x3 (or 5x5x5) cell search on top
356 # of an octave loop. The arithmetic is gated next door across every
357 # dimension x feature x metric (2496 cases, data-driven). What is checked
358 # HERE is the emitter: that it builds the parameter block, picks the
359 # per-dimension function, and routes five outputs.
360 #
361 # ⚠ Including DISTANCE_TO_EDGE and N_SPHERE_RADIUS, because those are the
362 # two features where the CPU leaves max_distance at the struct default --
363 # and a GLSL struct has no defaults, so the emitter has to set it.
364 for vcase in [[3, 0], [1, 0], [2, 0], [4, 0], [3, 3], [3, 4]]:
365 var g8 := ProcNodeGraph.new()
366 var vt := VoronoiTextureNode.new()
367 vt.set("voronoi_dimensions", vcase[0])
368 vt.set("feature", vcase[1])
369 vt.set("metric", 0)
370 vt.set("normalize", true)
371 var vt_idx := g8.add_node(vt)
372 var vvec := VectorNode.new()
373 vvec.set("value", Vector3(1.25, -2.5, 0.75))
374 g8.connect_sockets(g8.add_node(vvec), 0, vt_idx, 0)
375 var vw := ValueNode.new()
376 vw.set("value", 0.5)
377 g8.connect_sockets(g8.add_node(vw), 0, vt_idx, 1)
378 var vsc := ValueNode.new()
379 vsc.set("value", 2.0)
380 g8.connect_sockets(g8.add_node(vsc), 0, vt_idx, 2)
381 var vdet := ValueNode.new()
382 vdet.set("value", 1.5) # fractional, so the extra octave runs
383 g8.connect_sockets(g8.add_node(vdet), 0, vt_idx, 3)
384 var vrand := ValueNode.new()
385 vrand.set("value", 0.85)
386 g8.connect_sockets(g8.add_node(vrand), 0, vt_idx, 8)
387 # Output 0 is Distance for f1/edge; for N_SPHERE_RADIUS the interesting
388 # one is output 4, and reading 0 there would compare two zeroes.
389 var osock := 4 if vcase[1] == 4 else 0
390 var go8 := GroupOutputNode.new()
391 go8.set_interface([{"name": "Density", "type": 0}])
392 var go8_idx := g8.add_node(go8)
393 g8.connect_sockets(vt_idx, osock, go8_idx, 0)
394 cases.append({
395 "node": "Voronoi.d%d.f%d" % [vcase[0], vcase[1]], "op": vcase[1],
396 "inputs": [float(vcase[0]), 2.0, 1.5],
397 "cpu": float(g8.evaluate_socket(vt_idx, osock)),
398 "glsl": g8.compile_to_glsl_function("proc_density", false,
399 PackedStringArray()),
400 })
401
353402 # A DIRECTORY, not JSON: stage 2 is a standalone C++ program with no JSON
354403 # parser, and adding one to read our own test data would be more code than
355404 # the gate. One manifest line per case, one .glsl file per case.
src/nodes/noise_nodes.cpp +141 -0
……@@ -12,6 +12,7 @@
1212#include <cmath>
1313#include "../gpu/blender_hash_glsl.gen.h"
1414#include "../gpu/blender_perlin_glsl.gen.h"
15#include "../gpu/blender_voronoi_glsl.gen.h"
1516
1617namespace godot {
1718
……@@ -739,6 +740,146 @@ VoronoiTextureNode::VoronoiTextureNode() {
739740// The parameter setup is node_shader_tex_voronoi.cc's, including its clamps and
740741// its per-dimension max_distance -- which is what `normalize` divides by, so
741742// getting it from the wrong dimension scales the whole output.
743String VoronoiTextureNode::compile_glsl_prelude(int p_node_idx) const {
744 return String(BLENDER_HASH_GLSL) + String(BLENDER_VORONOI_GLSL);
745}
746
747String VoronoiTextureNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
748 // node_shader_tex_voronoi.cc's parameter setup, including its clamps and
749 // its per-dimension max_distance -- which is what `normalize` divides by,
750 // so taking it from the wrong dimension scales the whole output.
751 //
752 // feature, metric, dimensions and normalize are node PROPERTIES, so they
753 // are literals here and the branches fold at compile time, exactly as the
754 // CPU's switch does at run time.
755 const String pre = String("vt") + itos(p_node_idx);
756 const String P = pre + String("_p");
757 const String v = p_ctx.in(p_node_idx, 0);
758 const String w = p_ctx.in(p_node_idx, 1);
759 const bool is_1d = (voronoi_dimensions == 1);
760
761 String s = String("BvParams ") + P + String(";\n\t");
762 s += P + String(".feature = ") + itos(feature) + String(";\n\t");
763 s += P + String(".metric = ") + itos(metric) + String(";\n\t");
764 s += P + String(".normalize = ") + String(normalize ? "true" : "false") + String(";\n\t");
765 s += P + String(".scale = ") + p_ctx.in(p_node_idx, 2) + String(";\n\t");
766 s += P + String(".detail = ") + p_ctx.in(p_node_idx, 3) + String(";\n\t");
767 s += P + String(".roughness = ") + p_ctx.in(p_node_idx, 4) + String(";\n\t");
768 s += P + String(".lacunarity = ") + p_ctx.in(p_node_idx, 5) + String(";\n\t");
769 // Smoothness only exists for SMOOTH_F1, and it is halved and clamped.
770 s += P + String(".smoothness = ") +
771 (feature == bvoronoi::VORONOI_SMOOTH_F1
772 ? String("clamp((") + p_ctx.in(p_node_idx, 6) +
773 String(") / 2.0, 0.0, 0.5)")
774 : String("0.0")) +
775 String(";\n\t");
776 // Exponent only for Minkowski, and never in 1D.
777 s += P + String(".exponent = ") +
778 ((metric == bvoronoi::VORONOI_MINKOWSKI && !is_1d)
779 ? p_ctx.in(p_node_idx, 7)
780 : String("0.0")) +
781 String(";\n\t");
782 s += P + String(".randomness = clamp(") + p_ctx.in(p_node_idx, 8) +
783 String(", 0.0, 1.0);\n\t");
784
785 // ⚠ max_distance MUST BE SET ON EVERY PATH, and it is NOT the same value
786 // on each. A GLSL struct has no defaults, so anything left unset here is
787 // garbage that normalize divides by.
788 //
789 // N_SPHERE_RADIUS the CPU leaves VoronoiParams' default of 1.0, and
790 // n_sphere_radius never reads it
791 // DISTANCE_TO_EDGE the CPU sets it to r = 0.5 + 0.5 * randomness
792 // f1 / f2 / smooth the per-dimension cell distance, computed below
793 //
794 // The middle one is the trap: writing 1.0 there looks like "the default"
795 // and is wrong. It cost one failing case in the emitted-op gate --
796 // Voronoi.d3.f3, cpu 0.0586412 against gpu 0.0542431 -- which is exactly
797 // the kind of small, plausible difference nobody finds by reading.
798 const String R = pre + String("_r");
799 s += String("float ") + R + String(" = 0.5 + 0.5 * ") + P + String(".randomness;\n\t");
800 s += P + String(".max_distance = ") +
801 (feature == bvoronoi::VORONOI_DISTANCE_TO_EDGE ? R : String("1.0")) +
802 String(";\n\t");
803
804 // The coordinate, built from Vector and W the way the dimension says.
805 const String C = pre + String("_c");
806 switch (voronoi_dimensions) {
807 case 1:
808 s += String("float ") + C + String(" = (") + w + String(") * ") + P +
809 String(".scale;\n\t");
810 break;
811 case 2:
812 s += String("vec2 ") + C + String(" = vec2((") + v + String(").x, (") + v +
813 String(").y) * ") + P + String(".scale;\n\t");
814 break;
815 case 4:
816 s += String("vec4 ") + C + String(" = vec4(") + v + String(", ") + w +
817 String(") * ") + P + String(".scale;\n\t");
818 break;
819 default:
820 s += String("vec3 ") + C + String(" = (") + v + String(") * ") + P +
821 String(".scale;\n\t");
822 break;
823 }
824
825 const String sfx = itos(voronoi_dimensions);
826 const String D = p_ctx.declare(p_node_idx, 0); // Distance
827 const String COL = p_ctx.declare(p_node_idx, 1); // Color
828 const String POS = p_ctx.declare(p_node_idx, 2); // Position
829 const String WOUT = p_ctx.declare(p_node_idx, 3);
830 const String RAD = p_ctx.declare(p_node_idx, 4);
831
832 if (feature == bvoronoi::VORONOI_N_SPHERE_RADIUS) {
833 s += D + String(" = 0.0;\n\t");
834 s += COL + String(" = vec4(0.0, 0.0, 0.0, 1.0);\n\t");
835 s += POS + String(" = vec3(0.0);\n\t");
836 s += WOUT + String(" = 0.0;\n\t");
837 s += RAD + String(" = bv_voronoi_n_sphere_radius(") + P + String(", ") + C +
838 String(");");
839 return s;
840 }
841 if (feature == bvoronoi::VORONOI_DISTANCE_TO_EDGE) {
842 s += D + String(" = bv_fractal_voronoi_distance_to_edge_") + sfx + String("(") + P +
843 String(", ") + C + String(");\n\t");
844 s += COL + String(" = vec4(0.0, 0.0, 0.0, 1.0);\n\t");
845 s += POS + String(" = vec3(0.0);\n\t");
846 s += WOUT + String(" = 0.0;\n\t");
847 s += RAD + String(" = 0.0;");
848 return s;
849 }
850
851 // f1 / f2 / smooth_f1. max_distance is the distance across a cell for THIS
852 // dimension and metric, doubled for F2.
853 const String f2 = String(feature == bvoronoi::VORONOI_F2 ? " * 2.0" : " * 1.0");
854 s += P + String(".max_distance = ");
855 switch (voronoi_dimensions) {
856 case 1: s += R + f2; break;
857 case 2:
858 s += String("bv_voronoi_distance(vec2(0.0), vec2(") + R + String(", ") + R +
859 String("), ") + P + String(")") + f2;
860 break;
861 case 4:
862 s += String("bv_voronoi_distance(vec4(0.0), vec4(") + R + String(", ") + R +
863 String(", ") + R + String(", ") + R + String("), ") + P + String(")") + f2;
864 break;
865 default:
866 s += String("bv_voronoi_distance(vec3(0.0), vec3(") + R + String(", ") + R +
867 String(", ") + R + String("), ") + P + String(")") + f2;
868 break;
869 }
870 s += String(";\n\t");
871
872 const String O = pre + String("_o");
873 s += String("BvOutput ") + O + String(" = bv_fractal_voronoi_x_fx_") + sfx + String("(") +
874 P + String(", ") + C + String(", true);\n\t");
875 s += D + String(" = ") + O + String(".distance;\n\t");
876 s += COL + String(" = vec4(") + O + String(".color, 1.0);\n\t");
877 s += POS + String(" = ") + O + String(".position.xyz;\n\t");
878 s += WOUT + String(" = ") + O + String(".position.w;\n\t");
879 s += RAD + String(" = 0.0;");
880 return s;
881}
882
742883void VoronoiTextureNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
743884 SocketValue vec_v = p_ctx.read_input(p_node_idx, 0);
744885 SocketValue w_v = p_ctx.read_input(p_node_idx, 1);
src/nodes/noise_nodes.h +6 -3
……@@ -126,9 +126,12 @@ public:
126126 StringName get_node_type() const override { return "VoronoiTexture"; }
127127 String get_node_title() const override { return "Voronoi Texture"; }
128128 String get_node_category() const override { return "Texture"; }
129 // The GLSL was a THIRD arithmetic. Off until it is ported, so a GPU batch
130 // cannot silently evaluate a different function from the CPU path.
131 bool supports_gpu() const override { return false; }
129 // ON since 2026-09-05. The GLSL used to be a THIRD arithmetic -- a
130 // hand-rolled 3x3x3 F1 with a hash of its own. It now calls into
131 // blender_voronoi.inc, the same text execute() runs through the shim.
132 bool supports_gpu() const override { return true; }
133 String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
134 String compile_glsl_prelude(int p_node_idx) const override;
132135
133136 void set_voronoi_dimensions(int p_d) { voronoi_dimensions = p_d; }
134137 int get_voronoi_dimensions() const { return voronoi_dimensions; }
src/shared/blender_noise_cpp_shim.h +39 -0
……@@ -135,6 +135,35 @@ inline vec4 operator*(float s, vec4 a) { return vec4(s * a.x, s * a.y, s * a.z,
135135inline vec4 operator+(vec4 a, float s) { return vec4(a.x + s, a.y + s, a.z + s, a.w + s); }
136136inline vec4 operator-(vec4 a) { return vec4(-a.x, -a.y, -a.z, -a.w); }
137137
138// -- Division and compound assignment, and the rest of the vector builtins.
139// Voronoi needs these where perlin did not: it divides positions by a
140// scale, accumulates colour per octave, and measures real distances.
141inline vec2 operator/(vec2 a, float s) { return vec2(a.x / s, a.y / s); }
142inline vec3 operator/(vec3 a, float s) { return vec3(a.x / s, a.y / s, a.z / s); }
143inline vec4 operator/(vec4 a, float s) { return vec4(a.x / s, a.y / s, a.z / s, a.w / s); }
144inline vec2 &operator+=(vec2 &a, vec2 b) { a = a + b; return a; }
145inline vec3 &operator+=(vec3 &a, vec3 b) { a = a + b; return a; }
146inline vec4 &operator+=(vec4 &a, vec4 b) { a = a + b; return a; }
147inline vec2 &operator/=(vec2 &a, float s) { a = a / s; return a; }
148inline vec3 &operator/=(vec3 &a, float s) { a = a / s; return a; }
149inline vec4 &operator/=(vec4 &a, float s) { a = a / s; return a; }
150inline vec2 operator-(vec2 a, float s) { return vec2(a.x - s, a.y - s); }
151inline vec3 operator-(vec3 a, float s) { return vec3(a.x - s, a.y - s, a.z - s); }
152inline vec4 operator-(vec4 a, float s) { return vec4(a.x - s, a.y - s, a.z - s, a.w - s); }
153
154inline float dot(vec2 a, vec2 b) { return a.x * b.x + a.y * b.y; }
155inline float dot(vec3 a, vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
156inline float dot(vec4 a, vec4 b) { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; }
157inline float length(vec2 v) { return std::sqrt(dot(v, v)); }
158inline float length(vec3 v) { return std::sqrt(dot(v, v)); }
159inline float length(vec4 v) { return std::sqrt(dot(v, v)); }
160inline float distance(vec2 a, vec2 b) { return length(a - b); }
161inline float distance(vec3 a, vec3 b) { return length(a - b); }
162inline float distance(vec4 a, vec4 b) { return length(a - b); }
163inline vec2 normalize(vec2 v) { float l = length(v); return l > 0.0f ? v / l : vec2(); }
164inline vec3 normalize(vec3 v) { float l = length(v); return l > 0.0f ? v / l : vec3(); }
165inline vec4 normalize(vec4 v) { float l = length(v); return l > 0.0f ? v / l : vec4(); }
166
138167// -- Scalar builtins. float-only, because the .inc writes GLSL literals which
139168// are DOUBLE in C++, and letting one of these promote would compute a
140169// perlin octave in double on the CPU and in float on the GPU.
……@@ -144,12 +173,22 @@ inline float abs(float x) { return std::fabs(x); }
144173inline float min(float a, float b) { return a < b ? a : b; }
145174inline float max(float a, float b) { return a > b ? a : b; }
146175inline float clamp(float v, float lo, float hi) { return v < lo ? lo : (v > hi ? hi : v); }
176inline float ceil(float x) { return std::ceil(x); }
177// ⚠ FLOAT OVERLOADS, NOT std::. Without these a bare sqrt()/pow() in the .inc
178// resolves to the DOUBLE version from <math.h>, so the CPU computes an octave
179// in double where the GPU computes it in float. Measured: 208 of 24960
180// Voronoi comparisons differed by ~1e-7 for exactly this reason, and every
181// one of them came back to bit-exact once these existed.
182inline float sqrt(float x) { return std::sqrt(x); }
183inline float pow(float a, float b) { return std::pow(a, b); }
184inline float exp(float x) { return std::exp(x); }
147185
148186// The algorithms themselves. Same text the shader compiles, and in the same
149187// order the emitter concatenates them: perlin's lattice gradients ARE the
150188// hash, and GLSL has no #include to say so.
151189#include "blender_hash.inc"
152190#include "blender_perlin.inc"
191#include "blender_voronoi.inc"
153192
154193} // namespace bnoisegl
155194} // namespace godot
src/shared/blender_voronoi.h +80 -1036
……@@ -1,26 +1,23 @@
11#ifndef PROC_NODES_BLENDER_VORONOI_H
22#define PROC_NODES_BLENDER_VORONOI_H
33
4// BLENDER'S VORONOI, PORTED FROM BLENDER'S SOURCE.
4// BLENDER'S VORONOI — NOW AN ADAPTER, NOT AN IMPLEMENTATION.
55//
6// From `source/blender/blenlib/intern/noise.cc` (the "Voronoi Noise" section)
7// and the parameter setup in
8// `source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc`, in the tree at
9// /storage/projects/code/blender-source, commit 61a36e08, BLENDER_VERSION 502.
10// Blender in turn ported it from Cycles; original MIT, (c) 2013 Inigo Quilez.
6// The arithmetic is in src/shared/blender_voronoi.inc, compiled as GLSL and as
7// C++ from one text, exactly as the hash, perlin and erosion filter are. This
8// header is the godot-typed face: it converts Vector2/3/4 and the parameter
9// block at the call site and calls the one implementation. Every name the node
10// used is still here, so noise_nodes.cpp did not change.
1111//
12// Written out per dimension exactly as Blender writes it, rather than folded
13// into one templated routine, so the two files diff by eye. The differences
14// between dimensions are NOT mechanical -- 1D uses the Jenkins float hash while
15// 2D/3D/4D use the PCG integer hashes, 1D takes its distance straight from the
16// search loop while the others recompute it because the loop used the cheaper
17// bound, and each has its own max_distance. A "follows the pattern" port gets
18// those wrong.
12// ⛔ VORONOI USES THE PCG INTEGER HASHES, NOT THE JENKINS FLOAT HASH -- except
13// in 1D, which uses hash_float_to_float. Read off Blender's source rather than
14// assumed: one hash per file would be the right shape with unrelated numbers.
1915//
20// What was here before was a hand-rolled 3x3x3 F1 with a hash of its own: two
21// input sockets against Blender's nine, two outputs against five, 3D only, one
22// feature, one metric, no fractal octaves and no randomness. It produced
23// plausible cells, which is the worst way for a bridge to be wrong.
16// ⚠ THE PARAMETER BLOCK IS COPIED FIELD BY FIELD, ALL ELEVEN. GLSL structs
17// cannot carry default member initialisers, so BvParams has none and whatever
18// this function fails to set is garbage rather than a sensible default. The
19// defaults live HERE, on the godot-side struct the node fills in, which is
20// where they were before.
2421
2522#include "blender_noise.h"
2623
……@@ -28,9 +25,6 @@
2825#include <godot_cpp/variant/vector3.hpp>
2926#include <godot_cpp/variant/vector4.hpp>
3027
31#include <cfloat>
32#include <cmath>
33
3428namespace godot {
3529namespace bvoronoi {
3630
……@@ -72,1044 +66,94 @@ struct VoronoiOutput {
7266 Vector4 position;
7367};
7468
75// `mix` is Blender's: (1 - x) * v0 + x * v1, NOT v0 + x * (v1 - v0). They are
76// the same in algebra and not in floating point.
77static inline float bmix(float v0, float v1, float x) { return (1.0f - x) * v0 + x * v1; }
78static inline Vector2 bmix(const Vector2 &a, const Vector2 &b, float x) {
79 return a * (1.0f - x) + b * x;
80}
81static inline Vector3 bmix(const Vector3 &a, const Vector3 &b, float x) {
82 return a * (1.0f - x) + b * x;
83}
84static inline Vector4 bmix(const Vector4 &a, const Vector4 &b, float x) {
85 return a * (1.0f - x) + b * x;
86}
87
88// BLI's smoothstep, which clamps by BRANCHING on the edges rather than by
89// clamping t -- same values, and written the same way to keep the diff honest.
90static inline float bsmoothstep(float edge0, float edge1, float x) {
91 if (x < edge0) {
92 return 0.0f;
93 }
94 if (x >= edge1) {
95 return 1.0f;
96 }
97 float t = (x - edge0) / (edge1 - edge0);
98 return (3.0f - 2.0f * t) * (t * t);
99}
100
101static inline Vector2 bfloor(const Vector2 &v) { return Vector2(floorf(v.x), floorf(v.y)); }
102static inline Vector3 bfloor(const Vector3 &v) {
103 return Vector3(floorf(v.x), floorf(v.y), floorf(v.z));
104}
105static inline Vector4 bfloor(const Vector4 &v) {
106 return Vector4(floorf(v.x), floorf(v.y), floorf(v.z), floorf(v.w));
107}
69namespace detail {
70inline bnoisegl::vec2 gl(const Vector2 &v) { return bnoisegl::vec2(v.x, v.y); }
71inline bnoisegl::vec3 gl(const Vector3 &v) { return bnoisegl::vec3(v.x, v.y, v.z); }
72inline bnoisegl::vec4 gl(const Vector4 &v) { return bnoisegl::vec4(v.x, v.y, v.z, v.w); }
73inline Vector3 gd3(const bnoisegl::vec3 &v) { return Vector3(v.x, v.y, v.z); }
74inline Vector4 gd4(const bnoisegl::vec4 &v) { return Vector4(v.x, v.y, v.z, v.w); }
10875
109static inline IVec2 to_ivec(const Vector2 &v) { return IVec2{ (int32_t)v.x, (int32_t)v.y }; }
110static inline IVec3 to_ivec(const Vector3 &v) {
111 return IVec3{ (int32_t)v.x, (int32_t)v.y, (int32_t)v.z };
76// All eleven. A field left unset is not a default here, it is garbage.
77inline bnoisegl::BvParams gl(const VoronoiParams &p) {
78 bnoisegl::BvParams o;
79 o.scale = p.scale;
80 o.detail = p.detail;
81 o.roughness = p.roughness;
82 o.lacunarity = p.lacunarity;
83 o.smoothness = p.smoothness;
84 o.exponent = p.exponent;
85 o.randomness = p.randomness;
86 o.max_distance = p.max_distance;
87 o.normalize = p.normalize;
88 o.feature = p.feature;
89 o.metric = p.metric;
90 return o;
11291}
113static inline IVec4 to_ivec(const Vector4 &v) {
114 return IVec4{ (int32_t)v.x, (int32_t)v.y, (int32_t)v.z, (int32_t)v.w };
92inline VoronoiOutput gd(const bnoisegl::BvOutput &o) {
93 VoronoiOutput r;
94 r.distance = o.distance;
95 r.color = gd3(o.color);
96 r.position = gd4(o.position);
97 return r;
11598}
99} // namespace detail
116100
117static inline IVec2 iadd(const IVec2 &a, const IVec2 &b) { return IVec2{ a.x + b.x, a.y + b.y }; }
118static inline IVec3 iadd(const IVec3 &a, const IVec3 &b) {
119 return IVec3{ a.x + b.x, a.y + b.y, a.z + b.z };
101inline float voronoi_distance(float a, float b, const VoronoiParams &p) {
102 return bnoisegl::bv_voronoi_distance(a, b);
120103}
121static inline IVec4 iadd(const IVec4 &a, const IVec4 &b) {
122 return IVec4{ a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w };
104inline float voronoi_distance(const Vector2 &a, const Vector2 &b, const VoronoiParams &p) {
105 return bnoisegl::bv_voronoi_distance(detail::gl(a), detail::gl(b), detail::gl(p));
123106}
124
125static inline Vector2 to_vec(const IVec2 &v) { return Vector2((float)v.x, (float)v.y); }
126static inline Vector3 to_vec(const IVec3 &v) {
127 return Vector3((float)v.x, (float)v.y, (float)v.z);
107inline float voronoi_distance(const Vector3 &a, const Vector3 &b, const VoronoiParams &p) {
108 return bnoisegl::bv_voronoi_distance(detail::gl(a), detail::gl(b), detail::gl(p));
128109}
129static inline Vector4 to_vec(const IVec4 &v) {
130 return Vector4((float)v.x, (float)v.y, (float)v.z, (float)v.w);
110inline float voronoi_distance(const Vector4 &a, const Vector4 &b, const VoronoiParams &p) {
111 return bnoisegl::bv_voronoi_distance(detail::gl(a), detail::gl(b), detail::gl(p));
131112}
132113
133static inline float vdot(const Vector4 &a, const Vector4 &b) {
134 return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
114inline float voronoi_n_sphere_radius(const VoronoiParams &p, float c) {
115 return bnoisegl::bv_voronoi_n_sphere_radius(detail::gl(p), c);
135116}
136static inline float vlen_sq(const Vector4 &v) { return vdot(v, v); }
137static inline Vector4 vnorm(const Vector4 &v) {
138 float l = sqrtf(vlen_sq(v));
139 return l > 0.0f ? v / l : Vector4();
140}
141
142/* ***** Distances ***** */
143
144static inline float voronoi_distance(const float a, const float b) { return fabsf(b - a); }
145
146static inline float voronoi_distance(const Vector2 &a, const Vector2 &b,
147 const VoronoiParams &params) {
148 switch (params.metric) {
149 case VORONOI_EUCLIDEAN:
150 return a.distance_to(b);
151 case VORONOI_MANHATTAN:
152 return fabsf(a.x - b.x) + fabsf(a.y - b.y);
153 case VORONOI_CHEBYCHEV:
154 return MAX(fabsf(a.x - b.x), fabsf(a.y - b.y));
155 case VORONOI_MINKOWSKI:
156 return powf(powf(fabsf(a.x - b.x), params.exponent) +
157 powf(fabsf(a.y - b.y), params.exponent),
158 1.0f / params.exponent);
159 }
160 return 0.0f;
117inline float voronoi_n_sphere_radius(const VoronoiParams &p, const Vector2 &c) {
118 return bnoisegl::bv_voronoi_n_sphere_radius(detail::gl(p), detail::gl(c));
161119}
162
163static inline float voronoi_distance(const Vector3 &a, const Vector3 &b,
164 const VoronoiParams &params) {
165 switch (params.metric) {
166 case VORONOI_EUCLIDEAN:
167 return a.distance_to(b);
168 case VORONOI_MANHATTAN:
169 return fabsf(a.x - b.x) + fabsf(a.y - b.y) + fabsf(a.z - b.z);
170 case VORONOI_CHEBYCHEV:
171 return MAX(fabsf(a.x - b.x), MAX(fabsf(a.y - b.y), fabsf(a.z - b.z)));
172 case VORONOI_MINKOWSKI:
173 return powf(powf(fabsf(a.x - b.x), params.exponent) +
174 powf(fabsf(a.y - b.y), params.exponent) +
175 powf(fabsf(a.z - b.z), params.exponent),
176 1.0f / params.exponent);
177 }
178 return 0.0f;
120inline float voronoi_n_sphere_radius(const VoronoiParams &p, const Vector3 &c) {
121 return bnoisegl::bv_voronoi_n_sphere_radius(detail::gl(p), detail::gl(c));
179122}
180
181static inline float voronoi_distance(const Vector4 &a, const Vector4 &b,
182 const VoronoiParams &params) {
183 switch (params.metric) {
184 case VORONOI_EUCLIDEAN:
185 return sqrtf(vlen_sq(a - b));
186 case VORONOI_MANHATTAN:
187 return fabsf(a.x - b.x) + fabsf(a.y - b.y) + fabsf(a.z - b.z) + fabsf(a.w - b.w);
188 case VORONOI_CHEBYCHEV:
189 return MAX(MAX(fabsf(a.x - b.x), fabsf(a.y - b.y)),
190 MAX(fabsf(a.z - b.z), fabsf(a.w - b.w)));
191 case VORONOI_MINKOWSKI:
192 return powf(powf(fabsf(a.x - b.x), params.exponent) +
193 powf(fabsf(a.y - b.y), params.exponent) +
194 powf(fabsf(a.z - b.z), params.exponent) +
195 powf(fabsf(a.w - b.w), params.exponent),
196 1.0f / params.exponent);
197 }
198 return 0.0f;
123inline float voronoi_n_sphere_radius(const VoronoiParams &p, const Vector4 &c) {
124 return bnoisegl::bv_voronoi_n_sphere_radius(detail::gl(p), detail::gl(c));
199125}
200126
201// Cheaper comparison that does not change WHICH point is closest. Euclidean
202// compares squared, so f1/f2 recompute the real distance after the search --
203// which is why 3D's octave.distance is a second call and 1D's is not.
204template <typename T>
205static inline float voronoi_distance_bound(const T &a, const T &b, const VoronoiParams &params) {
206 if (params.metric == VORONOI_EUCLIDEAN) {
207 return (a - b).length_squared();
208 }
209 return voronoi_distance(a, b, params);
127inline float fractal_voronoi_distance_to_edge(const VoronoiParams &p, float c) {
128 return bnoisegl::bv_fractal_voronoi_distance_to_edge_1(detail::gl(p), c);
210129}
211template <>
212inline float voronoi_distance_bound<Vector4>(const Vector4 &a, const Vector4 &b,
213 const VoronoiParams &params) {
214 if (params.metric == VORONOI_EUCLIDEAN) {
215 return vlen_sq(a - b);
216 }
217 return voronoi_distance(a, b, params);
130inline float fractal_voronoi_distance_to_edge(const VoronoiParams &p, const Vector2 &c) {
131 return bnoisegl::bv_fractal_voronoi_distance_to_edge_2(detail::gl(p), detail::gl(c));
218132}
219
220/* **** 1D Voronoi **** */
221
222static inline Vector4 voronoi_position(const float coord) {
223 return Vector4(0.0f, 0.0f, 0.0f, coord);
133inline float fractal_voronoi_distance_to_edge(const VoronoiParams &p, const Vector3 &c) {
134 return bnoisegl::bv_fractal_voronoi_distance_to_edge_3(detail::gl(p), detail::gl(c));
224135}
225
226static inline VoronoiOutput voronoi_f1(const VoronoiParams &params, const float coord) {
227 float cellPosition = floorf(coord);
228 float localPosition = coord - cellPosition;
229
230 float minDistance = FLT_MAX;
231 float targetOffset = 0.0f;
232 float targetPosition = 0.0f;
233 for (int i = -1; i <= 1; i++) {
234 float cellOffset = (float)i;
235 float pointPosition = cellOffset +
236 hash_float_to_float(cellPosition + cellOffset) * params.randomness;
237 float distanceToPoint = voronoi_distance(pointPosition, localPosition);
238 if (distanceToPoint < minDistance) {
239 targetOffset = cellOffset;
240 minDistance = distanceToPoint;
241 targetPosition = pointPosition;
242 }
243 }
244
245 VoronoiOutput octave;
246 octave.distance = minDistance;
247 octave.color = hash_float_to_float3(cellPosition + targetOffset);
248 octave.position = voronoi_position(targetPosition + cellPosition);
249 return octave;
136inline float fractal_voronoi_distance_to_edge(const VoronoiParams &p, const Vector4 &c) {
137 return bnoisegl::bv_fractal_voronoi_distance_to_edge_4(detail::gl(p), detail::gl(c));
250138}
251139
252static inline VoronoiOutput voronoi_smooth_f1(const VoronoiParams &params, const float coord,
253 const bool calc_color) {
254 float cellPosition = floorf(coord);
255 float localPosition = coord - cellPosition;
256
257 float smoothDistance = 0.0f;
258 float smoothPosition = 0.0f;
259 Vector3 smoothColor;
260 float h = -1.0f;
261 for (int i = -2; i <= 2; i++) {
262 float cellOffset = (float)i;
263 float pointPosition = cellOffset +
264 hash_float_to_float(cellPosition + cellOffset) * params.randomness;
265 float distanceToPoint = voronoi_distance(pointPosition, localPosition);
266 h = h == -1.0f ? 1.0f
267 : bsmoothstep(0.0f, 1.0f,
268 0.5f + 0.5f * (smoothDistance - distanceToPoint) /
269 params.smoothness);
270 float correctionFactor = params.smoothness * h * (1.0f - h);
271 smoothDistance = bmix(smoothDistance, distanceToPoint, h) - correctionFactor;
272 correctionFactor /= 1.0f + 3.0f * params.smoothness;
273 Vector3 cellColor = hash_float_to_float3(cellPosition + cellOffset);
274 if (calc_color) {
275 smoothColor = bmix(smoothColor, cellColor, h) - Vector3(correctionFactor,
276 correctionFactor, correctionFactor);
277 }
278 smoothPosition = bmix(smoothPosition, pointPosition, h) - correctionFactor;
279 }
280
281 VoronoiOutput octave;
282 octave.distance = smoothDistance;
283 octave.color = smoothColor;
284 octave.position = voronoi_position(cellPosition + smoothPosition);
285 return octave;
140inline VoronoiOutput fractal_voronoi_x_fx(const VoronoiParams &p, float c, bool calc_color) {
141 return detail::gd(bnoisegl::bv_fractal_voronoi_x_fx_1(detail::gl(p), c, calc_color));
286142}
287
288static inline VoronoiOutput voronoi_f2(const VoronoiParams &params, const float coord) {
289 float cellPosition = floorf(coord);
290 float localPosition = coord - cellPosition;
291
292 float distanceF1 = FLT_MAX;
293 float distanceF2 = FLT_MAX;
294 float offsetF1 = 0.0f;
295 float positionF1 = 0.0f;
296 float offsetF2 = 0.0f;
297 float positionF2 = 0.0f;
298 for (int i = -1; i <= 1; i++) {
299 float cellOffset = (float)i;
300 float pointPosition = cellOffset +
301 hash_float_to_float(cellPosition + cellOffset) * params.randomness;
302 float distanceToPoint = voronoi_distance(pointPosition, localPosition);
303 if (distanceToPoint < distanceF1) {
304 distanceF2 = distanceF1;
305 distanceF1 = distanceToPoint;
306 offsetF2 = offsetF1;
307 offsetF1 = cellOffset;
308 positionF2 = positionF1;
309 positionF1 = pointPosition;
310 } else if (distanceToPoint < distanceF2) {
311 distanceF2 = distanceToPoint;
312 offsetF2 = cellOffset;
313 positionF2 = pointPosition;
314 }
315 }
316
317 VoronoiOutput octave;
318 octave.distance = distanceF2;
319 octave.color = hash_float_to_float3(cellPosition + offsetF2);
320 octave.position = voronoi_position(positionF2 + cellPosition);
321 return octave;
143inline VoronoiOutput fractal_voronoi_x_fx(const VoronoiParams &p, const Vector2 &c,
144 bool calc_color) {
145 return detail::gd(
146 bnoisegl::bv_fractal_voronoi_x_fx_2(detail::gl(p), detail::gl(c), calc_color));
322147}
323
324static inline float voronoi_distance_to_edge(const VoronoiParams &params, const float coord) {
325 float cellPosition = floorf(coord);
326 float localPosition = coord - cellPosition;
327
328 float midPointPosition = hash_float_to_float(cellPosition) * params.randomness;
329 float leftPointPosition = -1.0f +
330 hash_float_to_float(cellPosition - 1.0f) * params.randomness;
331 float rightPointPosition = 1.0f +
332 hash_float_to_float(cellPosition + 1.0f) * params.randomness;
333 float distanceToMidLeft = fabsf((midPointPosition + leftPointPosition) / 2.0f - localPosition);
334 float distanceToMidRight = fabsf((midPointPosition + rightPointPosition) / 2.0f -
335 localPosition);
336
337 return MIN(distanceToMidLeft, distanceToMidRight);
148inline VoronoiOutput fractal_voronoi_x_fx(const VoronoiParams &p, const Vector3 &c,
149 bool calc_color) {
150 return detail::gd(
151 bnoisegl::bv_fractal_voronoi_x_fx_3(detail::gl(p), detail::gl(c), calc_color));
338152}
339
340static inline float voronoi_n_sphere_radius(const VoronoiParams &params, const float coord) {
341 float cellPosition = floorf(coord);
342 float localPosition = coord - cellPosition;
343
344 float closestPoint = 0.0f;
345 float closestPointOffset = 0.0f;
346 float minDistance = FLT_MAX;
347 for (int i = -1; i <= 1; i++) {
348 float cellOffset = (float)i;
349 float pointPosition = cellOffset +
350 hash_float_to_float(cellPosition + cellOffset) * params.randomness;
351 float distanceToPoint = fabsf(pointPosition - localPosition);
352 if (distanceToPoint < minDistance) {
353 minDistance = distanceToPoint;
354 closestPoint = pointPosition;
355 closestPointOffset = cellOffset;
356 }
357 }
358
359 minDistance = FLT_MAX;
360 float closestPointToClosestPoint = 0.0f;
361 for (int i = -1; i <= 1; i++) {
362 if (i == 0) {
363 continue;
364 }
365 float cellOffset = (float)i + closestPointOffset;
366 float pointPosition = cellOffset +
367 hash_float_to_float(cellPosition + cellOffset) * params.randomness;
368 float distanceToPoint = fabsf(closestPoint - pointPosition);
369 if (distanceToPoint < minDistance) {
370 minDistance = distanceToPoint;
371 closestPointToClosestPoint = pointPosition;
372 }
373 }
374
375 return fabsf(closestPointToClosestPoint - closestPoint) / 2.0f;
376}
377
378/* **** 2D Voronoi **** */
379
380static inline Vector4 voronoi_position(const Vector2 &coord) {
381 return Vector4(coord.x, coord.y, 0.0f, 0.0f);
382}
383
384static inline VoronoiOutput voronoi_f1(const VoronoiParams &params, const Vector2 &coord) {
385 Vector2 cellPosition_f = bfloor(coord);
386 Vector2 localPosition = coord - cellPosition_f;
387 IVec2 cellPosition = to_ivec(cellPosition_f);
388
389 float minDistance = FLT_MAX;
390 IVec2 targetOffset;
391 Vector2 targetPosition;
392 for (int j = -1; j <= 1; j++) {
393 for (int i = -1; i <= 1; i++) {
394 IVec2 cellOffset{ i, j };
395 Vector2 pointPosition = to_vec(cellOffset) +
396 hash_int2_to_float2(iadd(cellPosition, cellOffset)) * params.randomness;
397 float distanceToPoint = voronoi_distance_bound(pointPosition, localPosition, params);
398 if (distanceToPoint < minDistance) {
399 targetOffset = cellOffset;
400 minDistance = distanceToPoint;
401 targetPosition = pointPosition;
402 }
403 }
404 }
405
406 VoronoiOutput octave;
407 octave.distance = voronoi_distance(targetPosition, localPosition, params);
408 octave.color = hash_int2_to_float3(iadd(cellPosition, targetOffset));
409 octave.position = voronoi_position(targetPosition + cellPosition_f);
410 return octave;
411}
412
413static inline VoronoiOutput voronoi_smooth_f1(const VoronoiParams &params, const Vector2 &coord,
414 const bool calc_color) {
415 Vector2 cellPosition_f = bfloor(coord);
416 Vector2 localPosition = coord - cellPosition_f;
417 IVec2 cellPosition = to_ivec(cellPosition_f);
418
419 float smoothDistance = 0.0f;
420 Vector3 smoothColor;
421 Vector2 smoothPosition;
422 float h = -1.0f;
423 for (int j = -2; j <= 2; j++) {
424 for (int i = -2; i <= 2; i++) {
425 IVec2 cellOffset{ i, j };
426 Vector2 pointPosition = to_vec(cellOffset) +
427 hash_int2_to_float2(iadd(cellPosition, cellOffset)) * params.randomness;
428 float distanceToPoint = voronoi_distance(pointPosition, localPosition, params);
429 h = h == -1.0f ? 1.0f
430 : bsmoothstep(0.0f, 1.0f,
431 0.5f + 0.5f * (smoothDistance - distanceToPoint) /
432 params.smoothness);
433 float correctionFactor = params.smoothness * h * (1.0f - h);
434 smoothDistance = bmix(smoothDistance, distanceToPoint, h) - correctionFactor;
435 correctionFactor /= 1.0f + 3.0f * params.smoothness;
436 if (calc_color) {
437 Vector3 cellColor = hash_int2_to_float3(iadd(cellPosition, cellOffset));
438 smoothColor = bmix(smoothColor, cellColor, h) - Vector3(correctionFactor,
439 correctionFactor, correctionFactor);
440 }
441 smoothPosition = bmix(smoothPosition, pointPosition, h) - Vector2(correctionFactor,
442 correctionFactor);
443 }
444 }
445
446 VoronoiOutput octave;
447 octave.distance = smoothDistance;
448 octave.color = smoothColor;
449 octave.position = voronoi_position(cellPosition_f + smoothPosition);
450 return octave;
451}
452
453static inline VoronoiOutput voronoi_f2(const VoronoiParams &params, const Vector2 &coord) {
454 Vector2 cellPosition_f = bfloor(coord);
455 Vector2 localPosition = coord - cellPosition_f;
456 IVec2 cellPosition = to_ivec(cellPosition_f);
457
458 float distanceF1 = FLT_MAX;
459 float distanceF2 = FLT_MAX;
460 IVec2 offsetF1;
461 Vector2 positionF1;
462 IVec2 offsetF2;
463 Vector2 positionF2;
464 for (int j = -1; j <= 1; j++) {
465 for (int i = -1; i <= 1; i++) {
466 IVec2 cellOffset{ i, j };
467 Vector2 pointPosition = to_vec(cellOffset) +
468 hash_int2_to_float2(iadd(cellPosition, cellOffset)) * params.randomness;
469 float distanceToPoint = voronoi_distance(pointPosition, localPosition, params);
470 if (distanceToPoint < distanceF1) {
471 distanceF2 = distanceF1;
472 distanceF1 = distanceToPoint;
473 offsetF2 = offsetF1;
474 offsetF1 = cellOffset;
475 positionF2 = positionF1;
476 positionF1 = pointPosition;
477 } else if (distanceToPoint < distanceF2) {
478 distanceF2 = distanceToPoint;
479 offsetF2 = cellOffset;
480 positionF2 = pointPosition;
481 }
482 }
483 }
484
485 VoronoiOutput octave;
486 octave.distance = distanceF2;
487 octave.color = hash_int2_to_float3(iadd(cellPosition, offsetF2));
488 octave.position = voronoi_position(positionF2 + cellPosition_f);
489 return octave;
490}
491
492static inline float voronoi_distance_to_edge(const VoronoiParams &params, const Vector2 &coord) {
493 Vector2 cellPosition_f = bfloor(coord);
494 Vector2 localPosition = coord - cellPosition_f;
495 IVec2 cellPosition = to_ivec(cellPosition_f);
496
497 Vector2 vectorToClosest;
498 float minDistance = FLT_MAX;
499 for (int j = -1; j <= 1; j++) {
500 for (int i = -1; i <= 1; i++) {
501 IVec2 cellOffset{ i, j };
502 Vector2 vectorToPoint = to_vec(cellOffset) +
503 hash_int2_to_float2(iadd(cellPosition, cellOffset)) * params.randomness -
504 localPosition;
505 float distanceToPoint = vectorToPoint.dot(vectorToPoint);
506 if (distanceToPoint < minDistance) {
507 minDistance = distanceToPoint;
508 vectorToClosest = vectorToPoint;
509 }
510 }
511 }
512
513 minDistance = FLT_MAX;
514 for (int j = -1; j <= 1; j++) {
515 for (int i = -1; i <= 1; i++) {
516 IVec2 cellOffset{ i, j };
517 Vector2 vectorToPoint = to_vec(cellOffset) +
518 hash_int2_to_float2(iadd(cellPosition, cellOffset)) * params.randomness -
519 localPosition;
520 Vector2 perpendicularToEdge = vectorToPoint - vectorToClosest;
521 if (perpendicularToEdge.dot(perpendicularToEdge) > 0.0001f) {
522 float distanceToEdge = ((vectorToClosest + vectorToPoint) / 2.0f)
523 .dot(perpendicularToEdge.normalized());
524 minDistance = MIN(minDistance, distanceToEdge);
525 }
526 }
527 }
528
529 return minDistance;
530}
531
532static inline float voronoi_n_sphere_radius(const VoronoiParams &params, const Vector2 &coord) {
533 Vector2 cellPosition_f = bfloor(coord);
534 Vector2 localPosition = coord - cellPosition_f;
535 IVec2 cellPosition = to_ivec(cellPosition_f);
536
537 Vector2 closestPoint;
538 IVec2 closestPointOffset;
539 float minDistanceSq = FLT_MAX;
540 for (int j = -1; j <= 1; j++) {
541 for (int i = -1; i <= 1; i++) {
542 IVec2 cellOffset{ i, j };
543 Vector2 pointPosition = to_vec(cellOffset) +
544 hash_int2_to_float2(iadd(cellPosition, cellOffset)) * params.randomness;
545 float distanceToPointSq = (pointPosition - localPosition).length_squared();
546 if (distanceToPointSq < minDistanceSq) {
547 minDistanceSq = distanceToPointSq;
548 closestPoint = pointPosition;
549 closestPointOffset = cellOffset;
550 }
551 }
552 }
553
554 minDistanceSq = FLT_MAX;
555 Vector2 closestPointToClosestPoint;
556 for (int j = -1; j <= 1; j++) {
557 for (int i = -1; i <= 1; i++) {
558 if (i == 0 && j == 0) {
559 continue;
560 }
561 IVec2 cellOffset = iadd(IVec2{ i, j }, closestPointOffset);
562 Vector2 pointPosition = to_vec(cellOffset) +
563 hash_int2_to_float2(iadd(cellPosition, cellOffset)) * params.randomness;
564 float distanceToPointSq = (closestPoint - pointPosition).length_squared();
565 if (distanceToPointSq < minDistanceSq) {
566 minDistanceSq = distanceToPointSq;
567 closestPointToClosestPoint = pointPosition;
568 }
569 }
570 }
571
572 return closestPointToClosestPoint.distance_to(closestPoint) / 2.0f;
573}
574
575/* **** 3D Voronoi **** */
576
577static inline Vector4 voronoi_position(const Vector3 &coord) {
578 return Vector4(coord.x, coord.y, coord.z, 0.0f);
579}
580
581static inline VoronoiOutput voronoi_f1(const VoronoiParams &params, const Vector3 &coord) {
582 Vector3 cellPosition_f = bfloor(coord);
583 Vector3 localPosition = coord - cellPosition_f;
584 IVec3 cellPosition = to_ivec(cellPosition_f);
585
586 float minDistance = FLT_MAX;
587 IVec3 targetOffset;
588 Vector3 targetPosition;
589 for (int k = -1; k <= 1; k++) {
590 for (int j = -1; j <= 1; j++) {
591 for (int i = -1; i <= 1; i++) {
592 IVec3 cellOffset{ i, j, k };
593 Vector3 pointPosition = to_vec(cellOffset) +
594 hash_int3_to_float3(iadd(cellPosition, cellOffset)) * params.randomness;
595 float distanceToPoint = voronoi_distance_bound(pointPosition, localPosition,
596 params);
597 if (distanceToPoint < minDistance) {
598 targetOffset = cellOffset;
599 minDistance = distanceToPoint;
600 targetPosition = pointPosition;
601 }
602 }
603 }
604 }
605
606 VoronoiOutput octave;
607 octave.distance = voronoi_distance(targetPosition, localPosition, params);
608 octave.color = hash_int3_to_float3(iadd(cellPosition, targetOffset));
609 octave.position = voronoi_position(targetPosition + cellPosition_f);
610 return octave;
611}
612
613static inline VoronoiOutput voronoi_smooth_f1(const VoronoiParams &params, const Vector3 &coord,
614 const bool calc_color) {
615 Vector3 cellPosition_f = bfloor(coord);
616 Vector3 localPosition = coord - cellPosition_f;
617 IVec3 cellPosition = to_ivec(cellPosition_f);
618
619 float smoothDistance = 0.0f;
620 Vector3 smoothColor;
621 Vector3 smoothPosition;
622 float h = -1.0f;
623 for (int k = -2; k <= 2; k++) {
624 for (int j = -2; j <= 2; j++) {
625 for (int i = -2; i <= 2; i++) {
626 IVec3 cellOffset{ i, j, k };
627 Vector3 pointPosition = to_vec(cellOffset) +
628 hash_int3_to_float3(iadd(cellPosition, cellOffset)) * params.randomness;
629 float distanceToPoint = voronoi_distance(pointPosition, localPosition, params);
630 h = h == -1.0f ? 1.0f
631 : bsmoothstep(0.0f, 1.0f,
632 0.5f + 0.5f * (smoothDistance - distanceToPoint) /
633 params.smoothness);
634 float correctionFactor = params.smoothness * h * (1.0f - h);
635 smoothDistance = bmix(smoothDistance, distanceToPoint, h) - correctionFactor;
636 correctionFactor /= 1.0f + 3.0f * params.smoothness;
637 if (calc_color) {
638 Vector3 cellColor = hash_int3_to_float3(iadd(cellPosition, cellOffset));
639 smoothColor = bmix(smoothColor, cellColor, h) - Vector3(correctionFactor,
640 correctionFactor, correctionFactor);
641 }
642 smoothPosition = bmix(smoothPosition, pointPosition, h) -
643 Vector3(correctionFactor, correctionFactor, correctionFactor);
644 }
645 }
646 }
647
648 VoronoiOutput octave;
649 octave.distance = smoothDistance;
650 octave.color = smoothColor;
651 octave.position = voronoi_position(cellPosition_f + smoothPosition);
652 return octave;
653}
654
655static inline VoronoiOutput voronoi_f2(const VoronoiParams &params, const Vector3 &coord) {
656 Vector3 cellPosition_f = bfloor(coord);
657 Vector3 localPosition = coord - cellPosition_f;
658 IVec3 cellPosition = to_ivec(cellPosition_f);
659
660 float distanceF1 = FLT_MAX;
661 float distanceF2 = FLT_MAX;
662 IVec3 offsetF1;
663 Vector3 positionF1;
664 IVec3 offsetF2;
665 Vector3 positionF2;
666 for (int k = -1; k <= 1; k++) {
667 for (int j = -1; j <= 1; j++) {
668 for (int i = -1; i <= 1; i++) {
669 IVec3 cellOffset{ i, j, k };
670 Vector3 pointPosition = to_vec(cellOffset) +
671 hash_int3_to_float3(iadd(cellPosition, cellOffset)) * params.randomness;
672 float distanceToPoint = voronoi_distance(pointPosition, localPosition, params);
673 if (distanceToPoint < distanceF1) {
674 distanceF2 = distanceF1;
675 distanceF1 = distanceToPoint;
676 offsetF2 = offsetF1;
677 offsetF1 = cellOffset;
678 positionF2 = positionF1;
679 positionF1 = pointPosition;
680 } else if (distanceToPoint < distanceF2) {
681 distanceF2 = distanceToPoint;
682 offsetF2 = cellOffset;
683 positionF2 = pointPosition;
684 }
685 }
686 }
687 }
688
689 VoronoiOutput octave;
690 octave.distance = distanceF2;
691 octave.color = hash_int3_to_float3(iadd(cellPosition, offsetF2));
692 octave.position = voronoi_position(positionF2 + cellPosition_f);
693 return octave;
694}
695
696static inline float voronoi_distance_to_edge(const VoronoiParams &params, const Vector3 &coord) {
697 Vector3 cellPosition_f = bfloor(coord);
698 Vector3 localPosition = coord - cellPosition_f;
699 IVec3 cellPosition = to_ivec(cellPosition_f);
700
701 Vector3 vectorToClosest;
702 float minDistance = FLT_MAX;
703 for (int k = -1; k <= 1; k++) {
704 for (int j = -1; j <= 1; j++) {
705 for (int i = -1; i <= 1; i++) {
706 IVec3 cellOffset{ i, j, k };
707 Vector3 vectorToPoint = to_vec(cellOffset) +
708 hash_int3_to_float3(iadd(cellPosition, cellOffset)) * params.randomness -
709 localPosition;
710 float distanceToPoint = vectorToPoint.dot(vectorToPoint);
711 if (distanceToPoint < minDistance) {
712 minDistance = distanceToPoint;
713 vectorToClosest = vectorToPoint;
714 }
715 }
716 }
717 }
718
719 minDistance = FLT_MAX;
720 for (int k = -1; k <= 1; k++) {
721 for (int j = -1; j <= 1; j++) {
722 for (int i = -1; i <= 1; i++) {
723 IVec3 cellOffset{ i, j, k };
724 Vector3 vectorToPoint = to_vec(cellOffset) +
725 hash_int3_to_float3(iadd(cellPosition, cellOffset)) * params.randomness -
726 localPosition;
727 Vector3 perpendicularToEdge = vectorToPoint - vectorToClosest;
728 if (perpendicularToEdge.dot(perpendicularToEdge) > 0.0001f) {
729 float distanceToEdge = ((vectorToClosest + vectorToPoint) / 2.0f)
730 .dot(perpendicularToEdge.normalized());
731 minDistance = MIN(minDistance, distanceToEdge);
732 }
733 }
734 }
735 }
736
737 return minDistance;
738}
739
740static inline float voronoi_n_sphere_radius(const VoronoiParams &params, const Vector3 &coord) {
741 Vector3 cellPosition_f = bfloor(coord);
742 Vector3 localPosition = coord - cellPosition_f;
743 IVec3 cellPosition = to_ivec(cellPosition_f);
744
745 Vector3 closestPoint;
746 IVec3 closestPointOffset;
747 float minDistanceSq = FLT_MAX;
748 for (int k = -1; k <= 1; k++) {
749 for (int j = -1; j <= 1; j++) {
750 for (int i = -1; i <= 1; i++) {
751 IVec3 cellOffset{ i, j, k };
752 Vector3 pointPosition = to_vec(cellOffset) +
753 hash_int3_to_float3(iadd(cellPosition, cellOffset)) * params.randomness;
754 float distanceToPointSq = (pointPosition - localPosition).length_squared();
755 if (distanceToPointSq < minDistanceSq) {
756 minDistanceSq = distanceToPointSq;
757 closestPoint = pointPosition;
758 closestPointOffset = cellOffset;
759 }
760 }
761 }
762 }
763
764 minDistanceSq = FLT_MAX;
765 Vector3 closestPointToClosestPoint;
766 for (int k = -1; k <= 1; k++) {
767 for (int j = -1; j <= 1; j++) {
768 for (int i = -1; i <= 1; i++) {
769 if (i == 0 && j == 0 && k == 0) {
770 continue;
771 }
772 IVec3 cellOffset = iadd(IVec3{ i, j, k }, closestPointOffset);
773 Vector3 pointPosition = to_vec(cellOffset) +
774 hash_int3_to_float3(iadd(cellPosition, cellOffset)) * params.randomness;
775 float distanceToPointSq = (closestPoint - pointPosition).length_squared();
776 if (distanceToPointSq < minDistanceSq) {
777 minDistanceSq = distanceToPointSq;
778 closestPointToClosestPoint = pointPosition;
779 }
780 }
781 }
782 }
783
784 return closestPointToClosestPoint.distance_to(closestPoint) / 2.0f;
785}
786
787/* **** 4D Voronoi **** */
788
789static inline Vector4 voronoi_position(const Vector4 &coord) { return coord; }
790
791static inline VoronoiOutput voronoi_f1(const VoronoiParams &params, const Vector4 &coord) {
792 Vector4 cellPosition_f = bfloor(coord);
793 Vector4 localPosition = coord - cellPosition_f;
794 IVec4 cellPosition = to_ivec(cellPosition_f);
795
796 float minDistance = FLT_MAX;
797 IVec4 targetOffset;
798 Vector4 targetPosition;
799 for (int u = -1; u <= 1; u++) {
800 for (int k = -1; k <= 1; k++) {
801 for (int j = -1; j <= 1; j++) {
802 for (int i = -1; i <= 1; i++) {
803 IVec4 cellOffset{ i, j, k, u };
804 Vector4 pointPosition = to_vec(cellOffset) +
805 hash_int4_to_float4(iadd(cellPosition, cellOffset)) *
806 params.randomness;
807 float distanceToPoint = voronoi_distance_bound(pointPosition, localPosition,
808 params);
809 if (distanceToPoint < minDistance) {
810 targetOffset = cellOffset;
811 minDistance = distanceToPoint;
812 targetPosition = pointPosition;
813 }
814 }
815 }
816 }
817 }
818
819 VoronoiOutput octave;
820 octave.distance = voronoi_distance(targetPosition, localPosition, params);
821 octave.color = hash_int4_to_float3(iadd(cellPosition, targetOffset));
822 octave.position = voronoi_position(targetPosition + cellPosition_f);
823 return octave;
824}
825
826static inline VoronoiOutput voronoi_smooth_f1(const VoronoiParams &params, const Vector4 &coord,
827 const bool calc_color) {
828 Vector4 cellPosition_f = bfloor(coord);
829 Vector4 localPosition = coord - cellPosition_f;
830 IVec4 cellPosition = to_ivec(cellPosition_f);
831
832 float smoothDistance = 0.0f;
833 Vector3 smoothColor;
834 Vector4 smoothPosition;
835 float h = -1.0f;
836 for (int u = -2; u <= 2; u++) {
837 for (int k = -2; k <= 2; k++) {
838 for (int j = -2; j <= 2; j++) {
839 for (int i = -2; i <= 2; i++) {
840 IVec4 cellOffset{ i, j, k, u };
841 Vector4 pointPosition = to_vec(cellOffset) +
842 hash_int4_to_float4(iadd(cellPosition, cellOffset)) *
843 params.randomness;
844 float distanceToPoint = voronoi_distance(pointPosition, localPosition, params);
845 h = h == -1.0f ? 1.0f
846 : bsmoothstep(0.0f, 1.0f,
847 0.5f + 0.5f * (smoothDistance - distanceToPoint) /
848 params.smoothness);
849 float correctionFactor = params.smoothness * h * (1.0f - h);
850 smoothDistance = bmix(smoothDistance, distanceToPoint, h) - correctionFactor;
851 correctionFactor /= 1.0f + 3.0f * params.smoothness;
852 if (calc_color) {
853 Vector3 cellColor = hash_int4_to_float3(iadd(cellPosition, cellOffset));
854 smoothColor = bmix(smoothColor, cellColor, h) - Vector3(correctionFactor,
855 correctionFactor, correctionFactor);
856 }
857 smoothPosition = bmix(smoothPosition, pointPosition, h) -
858 Vector4(correctionFactor, correctionFactor, correctionFactor,
859 correctionFactor);
860 }
861 }
862 }
863 }
864
865 VoronoiOutput octave;
866 octave.distance = smoothDistance;
867 octave.color = smoothColor;
868 octave.position = voronoi_position(cellPosition_f + smoothPosition);
869 return octave;
870}
871
872static inline VoronoiOutput voronoi_f2(const VoronoiParams &params, const Vector4 &coord) {
873 Vector4 cellPosition_f = bfloor(coord);
874 Vector4 localPosition = coord - cellPosition_f;
875 IVec4 cellPosition = to_ivec(cellPosition_f);
876
877 float distanceF1 = FLT_MAX;
878 float distanceF2 = FLT_MAX;
879 IVec4 offsetF1;
880 Vector4 positionF1;
881 IVec4 offsetF2;
882 Vector4 positionF2;
883 for (int u = -1; u <= 1; u++) {
884 for (int k = -1; k <= 1; k++) {
885 for (int j = -1; j <= 1; j++) {
886 for (int i = -1; i <= 1; i++) {
887 IVec4 cellOffset{ i, j, k, u };
888 Vector4 pointPosition = to_vec(cellOffset) +
889 hash_int4_to_float4(iadd(cellPosition, cellOffset)) *
890 params.randomness;
891 float distanceToPoint = voronoi_distance(pointPosition, localPosition, params);
892 if (distanceToPoint < distanceF1) {
893 distanceF2 = distanceF1;
894 distanceF1 = distanceToPoint;
895 offsetF2 = offsetF1;
896 offsetF1 = cellOffset;
897 positionF2 = positionF1;
898 positionF1 = pointPosition;
899 } else if (distanceToPoint < distanceF2) {
900 distanceF2 = distanceToPoint;
901 offsetF2 = cellOffset;
902 positionF2 = pointPosition;
903 }
904 }
905 }
906 }
907 }
908
909 VoronoiOutput octave;
910 octave.distance = distanceF2;
911 octave.color = hash_int4_to_float3(iadd(cellPosition, offsetF2));
912 octave.position = voronoi_position(positionF2 + cellPosition_f);
913 return octave;
914}
915
916static inline float voronoi_distance_to_edge(const VoronoiParams &params, const Vector4 &coord) {
917 Vector4 cellPosition_f = bfloor(coord);
918 Vector4 localPosition = coord - cellPosition_f;
919 IVec4 cellPosition = to_ivec(cellPosition_f);
920
921 Vector4 vectorToClosest;
922 float minDistance = FLT_MAX;
923 for (int u = -1; u <= 1; u++) {
924 for (int k = -1; k <= 1; k++) {
925 for (int j = -1; j <= 1; j++) {
926 for (int i = -1; i <= 1; i++) {
927 IVec4 cellOffset{ i, j, k, u };
928 Vector4 vectorToPoint = to_vec(cellOffset) +
929 hash_int4_to_float4(iadd(cellPosition, cellOffset)) *
930 params.randomness -
931 localPosition;
932 float distanceToPoint = vdot(vectorToPoint, vectorToPoint);
933 if (distanceToPoint < minDistance) {
934 minDistance = distanceToPoint;
935 vectorToClosest = vectorToPoint;
936 }
937 }
938 }
939 }
940 }
941
942 minDistance = FLT_MAX;
943 for (int u = -1; u <= 1; u++) {
944 for (int k = -1; k <= 1; k++) {
945 for (int j = -1; j <= 1; j++) {
946 for (int i = -1; i <= 1; i++) {
947 IVec4 cellOffset{ i, j, k, u };
948 Vector4 vectorToPoint = to_vec(cellOffset) +
949 hash_int4_to_float4(iadd(cellPosition, cellOffset)) *
950 params.randomness -
951 localPosition;
952 Vector4 perpendicularToEdge = vectorToPoint - vectorToClosest;
953 if (vdot(perpendicularToEdge, perpendicularToEdge) > 0.0001f) {
954 float distanceToEdge = vdot((vectorToClosest + vectorToPoint) / 2.0f,
955 vnorm(perpendicularToEdge));
956 minDistance = MIN(minDistance, distanceToEdge);
957 }
958 }
959 }
960 }
961 }
962
963 return minDistance;
964}
965
966static inline float voronoi_n_sphere_radius(const VoronoiParams &params, const Vector4 &coord) {
967 Vector4 cellPosition_f = bfloor(coord);
968 Vector4 localPosition = coord - cellPosition_f;
969 IVec4 cellPosition = to_ivec(cellPosition_f);
970
971 Vector4 closestPoint;
972 IVec4 closestPointOffset;
973 float minDistanceSq = FLT_MAX;
974 for (int u = -1; u <= 1; u++) {
975 for (int k = -1; k <= 1; k++) {
976 for (int j = -1; j <= 1; j++) {
977 for (int i = -1; i <= 1; i++) {
978 IVec4 cellOffset{ i, j, k, u };
979 Vector4 pointPosition = to_vec(cellOffset) +
980 hash_int4_to_float4(iadd(cellPosition, cellOffset)) *
981 params.randomness;
982 float distanceToPointSq = vlen_sq(pointPosition - localPosition);
983 if (distanceToPointSq < minDistanceSq) {
984 minDistanceSq = distanceToPointSq;
985 closestPoint = pointPosition;
986 closestPointOffset = cellOffset;
987 }
988 }
989 }
990 }
991 }
992
993 minDistanceSq = FLT_MAX;
994 Vector4 closestPointToClosestPoint;
995 for (int u = -1; u <= 1; u++) {
996 for (int k = -1; k <= 1; k++) {
997 for (int j = -1; j <= 1; j++) {
998 for (int i = -1; i <= 1; i++) {
999 if (i == 0 && j == 0 && k == 0 && u == 0) {
1000 continue;
1001 }
1002 IVec4 cellOffset = iadd(IVec4{ i, j, k, u }, closestPointOffset);
1003 Vector4 pointPosition = to_vec(cellOffset) +
1004 hash_int4_to_float4(iadd(cellPosition, cellOffset)) *
1005 params.randomness;
1006 float distanceToPointSq = vlen_sq(closestPoint - pointPosition);
1007 if (distanceToPointSq < minDistanceSq) {
1008 minDistanceSq = distanceToPointSq;
1009 closestPointToClosestPoint = pointPosition;
1010 }
1011 }
1012 }
1013 }
1014 }
1015
1016 return sqrtf(vlen_sq(closestPointToClosestPoint - closestPoint)) / 2.0f;
1017}
1018
1019/* **** Fractal Voronoi **** */
1020
1021// The fractalization is fBM's, except that some additions are replaced by lerps.
1022template <typename T>
1023static inline VoronoiOutput fractal_voronoi_x_fx(const VoronoiParams &params, const T coord,
1024 const bool calc_color) {
1025 float amplitude = 1.0f;
1026 float max_amplitude = 0.0f;
1027 float scale = 1.0f;
1028
1029 VoronoiOutput output;
1030 const bool zero_input = params.detail == 0.0f || params.roughness == 0.0f;
1031
1032 for (int i = 0; i <= (int)ceilf(params.detail); ++i) {
1033 VoronoiOutput octave = (params.feature == VORONOI_F2)
1034 ? voronoi_f2(params, coord * scale)
1035 : ((params.feature == VORONOI_SMOOTH_F1 && params.smoothness != 0.0f)
1036 ? voronoi_smooth_f1(params, coord * scale, calc_color)
1037 : voronoi_f1(params, coord * scale));
1038
1039 if (zero_input) {
1040 max_amplitude = 1.0f;
1041 output = octave;
1042 break;
1043 }
1044 if ((float)i <= params.detail) {
1045 max_amplitude += amplitude;
1046 output.distance += octave.distance * amplitude;
1047 output.color += octave.color * amplitude;
1048 output.position = bmix(output.position, octave.position / scale, amplitude);
1049 scale *= params.lacunarity;
1050 amplitude *= params.roughness;
1051 } else {
1052 float remainder = params.detail - floorf(params.detail);
1053 if (remainder != 0.0f) {
1054 max_amplitude = bmix(max_amplitude, max_amplitude + amplitude, remainder);
1055 output.distance = bmix(output.distance,
1056 output.distance + octave.distance * amplitude, remainder);
1057 output.color = bmix(output.color, output.color + octave.color * amplitude,
1058 remainder);
1059 output.position = bmix(output.position,
1060 bmix(output.position, octave.position / scale, amplitude), remainder);
1061 }
1062 }
1063 }
1064
1065 if (params.normalize) {
1066 output.distance /= max_amplitude * params.max_distance;
1067 output.color /= max_amplitude;
1068 }
1069
1070 output.position = (params.scale != 0.0f) ? output.position / params.scale : Vector4();
1071
1072 return output;
1073}
1074
1075template <typename T>
1076static inline float fractal_voronoi_distance_to_edge(const VoronoiParams &params, const T coord) {
1077 float amplitude = 1.0f;
1078 float max_amplitude = params.max_distance;
1079 float scale = 1.0f;
1080 float distance = 8.0f;
1081
1082 const bool zero_input = params.detail == 0.0f || params.roughness == 0.0f;
1083
1084 for (int i = 0; i <= (int)ceilf(params.detail); ++i) {
1085 const float octave_distance = voronoi_distance_to_edge(params, coord * scale);
1086
1087 if (zero_input) {
1088 distance = octave_distance;
1089 break;
1090 }
1091 if ((float)i <= params.detail) {
1092 max_amplitude = bmix(max_amplitude, params.max_distance / scale, amplitude);
1093 distance = bmix(distance, MIN(distance, octave_distance / scale), amplitude);
1094 scale *= params.lacunarity;
1095 amplitude *= params.roughness;
1096 } else {
1097 float remainder = params.detail - floorf(params.detail);
1098 if (remainder != 0.0f) {
1099 float lerp_amplitude = bmix(max_amplitude, params.max_distance / scale, amplitude);
1100 max_amplitude = bmix(max_amplitude, lerp_amplitude, remainder);
1101 float lerp_distance = bmix(distance, MIN(distance, octave_distance / scale),
1102 amplitude);
1103 distance = bmix(distance, MIN(distance, lerp_distance), remainder);
1104 }
1105 }
1106 }
1107
1108 if (params.normalize) {
1109 distance /= max_amplitude;
1110 }
1111
1112 return distance;
153inline VoronoiOutput fractal_voronoi_x_fx(const VoronoiParams &p, const Vector4 &c,
154 bool calc_color) {
155 return detail::gd(
156 bnoisegl::bv_fractal_voronoi_x_fx_4(detail::gl(p), detail::gl(c), calc_color));
1113157}
1114158
1115159} // namespace bvoronoi
src/shared/blender_voronoi.inc +1136 -0
……@@ -0,0 +1,1136 @@
1// ============================================================================
2// BLENDER'S VORONOI -- ONE TEXT, COMPILED AS BOTH GLSL AND C++.
3//
4// Ported from `source/blender/blenlib/intern/noise.cc` (the Voronoi section)
5// in the Blender tree at commit 61a36e08, BLENDER_VERSION 502, and kept
6// structurally identical to it so the two diff by eye when Blender moves.
7//
8// DEPENDS ON blender_hash.inc and does not include it -- GLSL has no
9// #include, so the emitter concatenates hash-then-voronoi and the C++ shim
10// includes them in that order.
11//
12// ⛔ VORONOI USES THE PCG INTEGER HASHES, NOT THE JENKINS FLOAT HASH -- except
13// in 1D, which uses hash_float_to_float. That is read off Blender's source,
14// not assumed: one hash per file would be the right shape with unrelated
15// numbers, which is the failure this whole layer exists to prevent.
16//
17// This file was TRANSFORMED from blender_voronoi.h, not retyped. 1118 lines of
18// working, gated arithmetic is exactly the wrong thing to copy by hand, and
19// the equivalence run against the pre-conversion header is what says the
20// transformation was faithful.
21//
22// ---------------------------------------------------------------------------
23// THE SUBSET
24//
25// NO TEMPLATES. `fractal_voronoi_x_fx` and `fractal_voronoi_distance_to_edge`
26// are template <typename T>; the body is written once and the preprocessor
27// stamps it out per dimension, as blender_perlin.inc does.
28// `voronoi_distance_bound` was a template with a Vector4 specialisation and
29// is three overloads here -- GLSL resolves on argument type just as well.
30//
31// NO DEFAULT MEMBER INITIALISERS. GLSL structs cannot have them, so
32// BvParams is filled by the caller. Every field, every time: a struct that
33// used to default `randomness = 1.0f` and now defaults to garbage is a
34// silent difference, which is why the adapter sets all eleven.
35//
36// NO godot METHODS. `(a - b).length_squared()` became bv_vlen_sq_v.
37// ============================================================================
38
39#ifndef PN_BLENDER_VORONOI
40#define PN_BLENDER_VORONOI
41
42#ifndef BN_FN
43#define BN_FN
44#endif
45
46const int BV_EUCLIDEAN = 0;
47const int BV_MANHATTAN = 1;
48const int BV_CHEBYCHEV = 2;
49const int BV_MINKOWSKI = 3;
50
51// Blender's SHD_VORONOI_* order, which is NOT the order the UI lists them in.
52const int BV_F1 = 0;
53const int BV_F2 = 1;
54const int BV_SMOOTH_F1 = 2;
55const int BV_DISTANCE_TO_EDGE = 3;
56const int BV_N_SPHERE_RADIUS = 4;
57
58struct BvParams {
59 float scale;
60 float detail;
61 float roughness;
62 float lacunarity;
63 float smoothness;
64 float exponent;
65 float randomness;
66 float max_distance;
67 bool normalize;
68 int feature;
69 int metric;
70};
71
72struct BvOutput {
73 float distance;
74 vec3 color;
75 vec4 position;
76};
77
78// `mix` is Blender's: (1 - x) * v0 + x * v1, NOT v0 + x * (v1 - v0). They are
79// the same in algebra and not in floating point.
80BN_FN float bv_bmix(float v0, float v1, float x) { return (1.0f - x) * v0 + x * v1; }
81BN_FN vec2 bv_bmix(vec2 a, vec2 b, float x) {
82 return a * (1.0f - x) + b * x;
83}
84BN_FN vec3 bv_bmix(vec3 a, vec3 b, float x) {
85 return a * (1.0f - x) + b * x;
86}
87BN_FN vec4 bv_bmix(vec4 a, vec4 b, float x) {
88 return a * (1.0f - x) + b * x;
89}
90
91// BLI's smoothstep, which clamps by BRANCHING on the edges rather than by
92// clamping t -- same values, and written the same way to keep the diff honest.
93BN_FN float bv_bsmoothstep(float edge0, float edge1, float x) {
94 if (x < edge0) {
95 return 0.0f;
96 }
97 if (x >= edge1) {
98 return 1.0f;
99 }
100 float t = (x - edge0) / (edge1 - edge0);
101 return (3.0f - 2.0f * t) * (t * t);
102}
103
104BN_FN vec2 bv_bfloor(vec2 v) { return vec2(floor(v.x), floor(v.y)); }
105BN_FN vec3 bv_bfloor(vec3 v) {
106 return vec3(floor(v.x), floor(v.y), floor(v.z));
107}
108BN_FN vec4 bv_bfloor(vec4 v) {
109 return vec4(floor(v.x), floor(v.y), floor(v.z), floor(v.w));
110}
111
112BN_FN ivec2 bv_to_ivec(vec2 v) { return ivec2( int(v.x), int(v.y) ); }
113BN_FN ivec3 bv_to_ivec(vec3 v) {
114 return ivec3( int(v.x), int(v.y), int(v.z) );
115}
116BN_FN ivec4 bv_to_ivec(vec4 v) {
117 return ivec4( int(v.x), int(v.y), int(v.z), int(v.w) );
118}
119
120BN_FN ivec2 bv_iadd(ivec2 a, ivec2 b) { return ivec2( a.x + b.x, a.y + b.y ); }
121BN_FN ivec3 bv_iadd(ivec3 a, ivec3 b) {
122 return ivec3( a.x + b.x, a.y + b.y, a.z + b.z );
123}
124BN_FN ivec4 bv_iadd(ivec4 a, ivec4 b) {
125 return ivec4( a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w );
126}
127
128BN_FN vec2 bv_to_vec(ivec2 v) { return vec2(float(v.x), float(v.y)); }
129BN_FN vec3 bv_to_vec(ivec3 v) {
130 return vec3(float(v.x), float(v.y), float(v.z));
131}
132BN_FN vec4 bv_to_vec(ivec4 v) {
133 return vec4(float(v.x), float(v.y), float(v.z), float(v.w));
134}
135
136BN_FN float bv_vdot(vec4 a, vec4 b) {
137 return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
138}
139BN_FN float bv_vlen_sq(vec4 v) { return bv_vdot(v, v); }
140BN_FN vec4 bv_vnorm(vec4 v) {
141 float l = sqrt(bv_vlen_sq(v));
142 return l > 0.0f ? v / l : vec4(0.0f);
143}
144
145/* ***** Distances ***** */
146
147BN_FN float bv_voronoi_distance(float a, float b) { return abs(b - a); }
148
149BN_FN float bv_voronoi_distance(vec2 a, vec2 b,
150 BvParams params) {
151 switch (params.metric) {
152 case BV_EUCLIDEAN:
153 return distance(a, b);
154 case BV_MANHATTAN:
155 return abs(a.x - b.x) + abs(a.y - b.y);
156 case BV_CHEBYCHEV:
157 return max(abs(a.x - b.x), abs(a.y - b.y));
158 case BV_MINKOWSKI:
159 return pow(pow(abs(a.x - b.x), params.exponent) +
160 pow(abs(a.y - b.y), params.exponent),
161 1.0f / params.exponent);
162 }
163 return 0.0f;
164}
165
166BN_FN float bv_voronoi_distance(vec3 a, vec3 b,
167 BvParams params) {
168 switch (params.metric) {
169 case BV_EUCLIDEAN:
170 return distance(a, b);
171 case BV_MANHATTAN:
172 return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z);
173 case BV_CHEBYCHEV:
174 return max(abs(a.x - b.x), max(abs(a.y - b.y), abs(a.z - b.z)));
175 case BV_MINKOWSKI:
176 return pow(pow(abs(a.x - b.x), params.exponent) +
177 pow(abs(a.y - b.y), params.exponent) +
178 pow(abs(a.z - b.z), params.exponent),
179 1.0f / params.exponent);
180 }
181 return 0.0f;
182}
183
184BN_FN float bv_voronoi_distance(vec4 a, vec4 b,
185 BvParams params) {
186 switch (params.metric) {
187 case BV_EUCLIDEAN:
188 return sqrt(bv_vlen_sq(a - b));
189 case BV_MANHATTAN:
190 return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z) + abs(a.w - b.w);
191 case BV_CHEBYCHEV:
192 return max(max(abs(a.x - b.x), abs(a.y - b.y)),
193 max(abs(a.z - b.z), abs(a.w - b.w)));
194 case BV_MINKOWSKI:
195 return pow(pow(abs(a.x - b.x), params.exponent) +
196 pow(abs(a.y - b.y), params.exponent) +
197 pow(abs(a.z - b.z), params.exponent) +
198 pow(abs(a.w - b.w), params.exponent),
199 1.0f / params.exponent);
200 }
201 return 0.0f;
202}
203
204// Cheaper comparison that does not change WHICH point is closest. Euclidean
205// compares squared, so f1/f2 recompute the real distance after the search --
206// which is why 3D's octave.distance is a second call and 1D's is not.
207// Cheaper comparison that does not change WHICH point is closest. Euclidean
208// compares squared, so f1/f2 recompute the real distance after the search --
209// which is why 3D's octave.distance is a second call and 1D's is not.
210//
211// A template with a Vector4 specialisation in the C++; here it is three
212// overloads, because GLSL resolves on the argument type just as well and the
213// only thing the specialisation changed was which length-squared to call.
214BN_FN float bv_vlen_sq_v(vec2 v) { return v.x * v.x + v.y * v.y; }
215BN_FN float bv_vlen_sq_v(vec3 v) { return v.x * v.x + v.y * v.y + v.z * v.z; }
216BN_FN float bv_vlen_sq_v(vec4 v) { return bv_vlen_sq(v); }
217
218BN_FN float bv_voronoi_distance_bound(vec2 a, vec2 b, BvParams params) {
219 if (params.metric == BV_EUCLIDEAN) { return bv_vlen_sq_v(a - b); }
220 return bv_voronoi_distance(a, b, params);
221}
222BN_FN float bv_voronoi_distance_bound(vec3 a, vec3 b, BvParams params) {
223 if (params.metric == BV_EUCLIDEAN) { return bv_vlen_sq_v(a - b); }
224 return bv_voronoi_distance(a, b, params);
225}
226BN_FN float bv_voronoi_distance_bound(vec4 a, vec4 b, BvParams params) {
227 if (params.metric == BV_EUCLIDEAN) { return bv_vlen_sq(a - b); }
228 return bv_voronoi_distance(a, b, params);
229}
230
231/* **** 1D Voronoi **** */
232
233BN_FN vec4 bv_voronoi_position(float coord) {
234 return vec4(0.0f, 0.0f, 0.0f, coord);
235}
236
237BN_FN BvOutput bv_voronoi_f1(BvParams params, float coord) {
238 float cellPosition = floor(coord);
239 float localPosition = coord - cellPosition;
240
241 float minDistance = 3.402823466e+38f;
242 float targetOffset = 0.0f;
243 float targetPosition = 0.0f;
244 for (int i = -1; i <= 1; i++) {
245 float cellOffset = float(i);
246 float pointPosition = cellOffset +
247 bn_hash_float_to_float(cellPosition + cellOffset) * params.randomness;
248 float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition);
249 if (distanceToPoint < minDistance) {
250 targetOffset = cellOffset;
251 minDistance = distanceToPoint;
252 targetPosition = pointPosition;
253 }
254 }
255
256 BvOutput octave;
257 octave.distance = minDistance;
258 octave.color = bn_hash_float_to_float3(cellPosition + targetOffset);
259 octave.position = bv_voronoi_position(targetPosition + cellPosition);
260 return octave;
261}
262
263BN_FN BvOutput bv_voronoi_smooth_f1(BvParams params, float coord,
264 bool calc_color) {
265 float cellPosition = floor(coord);
266 float localPosition = coord - cellPosition;
267
268 float smoothDistance = 0.0f;
269 float smoothPosition = 0.0f;
270 vec3 smoothColor;
271 float h = -1.0f;
272 for (int i = -2; i <= 2; i++) {
273 float cellOffset = float(i);
274 float pointPosition = cellOffset +
275 bn_hash_float_to_float(cellPosition + cellOffset) * params.randomness;
276 float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition);
277 h = h == -1.0f ? 1.0f
278 : bv_bsmoothstep(0.0f, 1.0f,
279 0.5f + 0.5f * (smoothDistance - distanceToPoint) /
280 params.smoothness);
281 float correctionFactor = params.smoothness * h * (1.0f - h);
282 smoothDistance = bv_bmix(smoothDistance, distanceToPoint, h) - correctionFactor;
283 correctionFactor /= 1.0f + 3.0f * params.smoothness;
284 vec3 cellColor = bn_hash_float_to_float3(cellPosition + cellOffset);
285 if (calc_color) {
286 smoothColor = bv_bmix(smoothColor, cellColor, h) - vec3(correctionFactor,
287 correctionFactor, correctionFactor);
288 }
289 smoothPosition = bv_bmix(smoothPosition, pointPosition, h) - correctionFactor;
290 }
291
292 BvOutput octave;
293 octave.distance = smoothDistance;
294 octave.color = smoothColor;
295 octave.position = bv_voronoi_position(cellPosition + smoothPosition);
296 return octave;
297}
298
299BN_FN BvOutput bv_voronoi_f2(BvParams params, float coord) {
300 float cellPosition = floor(coord);
301 float localPosition = coord - cellPosition;
302
303 float distanceF1 = 3.402823466e+38f;
304 float distanceF2 = 3.402823466e+38f;
305 float offsetF1 = 0.0f;
306 float positionF1 = 0.0f;
307 float offsetF2 = 0.0f;
308 float positionF2 = 0.0f;
309 for (int i = -1; i <= 1; i++) {
310 float cellOffset = float(i);
311 float pointPosition = cellOffset +
312 bn_hash_float_to_float(cellPosition + cellOffset) * params.randomness;
313 float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition);
314 if (distanceToPoint < distanceF1) {
315 distanceF2 = distanceF1;
316 distanceF1 = distanceToPoint;
317 offsetF2 = offsetF1;
318 offsetF1 = cellOffset;
319 positionF2 = positionF1;
320 positionF1 = pointPosition;
321 } else if (distanceToPoint < distanceF2) {
322 distanceF2 = distanceToPoint;
323 offsetF2 = cellOffset;
324 positionF2 = pointPosition;
325 }
326 }
327
328 BvOutput octave;
329 octave.distance = distanceF2;
330 octave.color = bn_hash_float_to_float3(cellPosition + offsetF2);
331 octave.position = bv_voronoi_position(positionF2 + cellPosition);
332 return octave;
333}
334
335BN_FN float bv_voronoi_distance_to_edge(BvParams params, float coord) {
336 float cellPosition = floor(coord);
337 float localPosition = coord - cellPosition;
338
339 float midPointPosition = bn_hash_float_to_float(cellPosition) * params.randomness;
340 float leftPointPosition = -1.0f +
341 bn_hash_float_to_float(cellPosition - 1.0f) * params.randomness;
342 float rightPointPosition = 1.0f +
343 bn_hash_float_to_float(cellPosition + 1.0f) * params.randomness;
344 float distanceToMidLeft = abs((midPointPosition + leftPointPosition) / 2.0f - localPosition);
345 float distanceToMidRight = abs((midPointPosition + rightPointPosition) / 2.0f -
346 localPosition);
347
348 return min(distanceToMidLeft, distanceToMidRight);
349}
350
351BN_FN float bv_voronoi_n_sphere_radius(BvParams params, float coord) {
352 float cellPosition = floor(coord);
353 float localPosition = coord - cellPosition;
354
355 float closestPoint = 0.0f;
356 float closestPointOffset = 0.0f;
357 float minDistance = 3.402823466e+38f;
358 for (int i = -1; i <= 1; i++) {
359 float cellOffset = float(i);
360 float pointPosition = cellOffset +
361 bn_hash_float_to_float(cellPosition + cellOffset) * params.randomness;
362 float distanceToPoint = abs(pointPosition - localPosition);
363 if (distanceToPoint < minDistance) {
364 minDistance = distanceToPoint;
365 closestPoint = pointPosition;
366 closestPointOffset = cellOffset;
367 }
368 }
369
370 minDistance = 3.402823466e+38f;
371 float closestPointToClosestPoint = 0.0f;
372 for (int i = -1; i <= 1; i++) {
373 if (i == 0) {
374 continue;
375 }
376 float cellOffset = float(i) + closestPointOffset;
377 float pointPosition = cellOffset +
378 bn_hash_float_to_float(cellPosition + cellOffset) * params.randomness;
379 float distanceToPoint = abs(closestPoint - pointPosition);
380 if (distanceToPoint < minDistance) {
381 minDistance = distanceToPoint;
382 closestPointToClosestPoint = pointPosition;
383 }
384 }
385
386 return abs(closestPointToClosestPoint - closestPoint) / 2.0f;
387}
388
389/* **** 2D Voronoi **** */
390
391BN_FN vec4 bv_voronoi_position(vec2 coord) {
392 return vec4(coord.x, coord.y, 0.0f, 0.0f);
393}
394
395BN_FN BvOutput bv_voronoi_f1(BvParams params, vec2 coord) {
396 vec2 cellPosition_f = bv_bfloor(coord);
397 vec2 localPosition = coord - cellPosition_f;
398 ivec2 cellPosition = bv_to_ivec(cellPosition_f);
399
400 float minDistance = 3.402823466e+38f;
401 ivec2 targetOffset;
402 vec2 targetPosition;
403 for (int j = -1; j <= 1; j++) {
404 for (int i = -1; i <= 1; i++) {
405 ivec2 cellOffset = ivec2( i, j );
406 vec2 pointPosition = bv_to_vec(cellOffset) +
407 bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness;
408 float distanceToPoint = bv_voronoi_distance_bound(pointPosition, localPosition, params);
409 if (distanceToPoint < minDistance) {
410 targetOffset = cellOffset;
411 minDistance = distanceToPoint;
412 targetPosition = pointPosition;
413 }
414 }
415 }
416
417 BvOutput octave;
418 octave.distance = bv_voronoi_distance(targetPosition, localPosition, params);
419 octave.color = bn_hash_int_to_float3(bv_iadd(cellPosition, targetOffset));
420 octave.position = bv_voronoi_position(targetPosition + cellPosition_f);
421 return octave;
422}
423
424BN_FN BvOutput bv_voronoi_smooth_f1(BvParams params, vec2 coord,
425 bool calc_color) {
426 vec2 cellPosition_f = bv_bfloor(coord);
427 vec2 localPosition = coord - cellPosition_f;
428 ivec2 cellPosition = bv_to_ivec(cellPosition_f);
429
430 float smoothDistance = 0.0f;
431 vec3 smoothColor;
432 vec2 smoothPosition;
433 float h = -1.0f;
434 for (int j = -2; j <= 2; j++) {
435 for (int i = -2; i <= 2; i++) {
436 ivec2 cellOffset = ivec2( i, j );
437 vec2 pointPosition = bv_to_vec(cellOffset) +
438 bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness;
439 float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition, params);
440 h = h == -1.0f ? 1.0f
441 : bv_bsmoothstep(0.0f, 1.0f,
442 0.5f + 0.5f * (smoothDistance - distanceToPoint) /
443 params.smoothness);
444 float correctionFactor = params.smoothness * h * (1.0f - h);
445 smoothDistance = bv_bmix(smoothDistance, distanceToPoint, h) - correctionFactor;
446 correctionFactor /= 1.0f + 3.0f * params.smoothness;
447 if (calc_color) {
448 vec3 cellColor = bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset));
449 smoothColor = bv_bmix(smoothColor, cellColor, h) - vec3(correctionFactor,
450 correctionFactor, correctionFactor);
451 }
452 smoothPosition = bv_bmix(smoothPosition, pointPosition, h) - vec2(correctionFactor,
453 correctionFactor);
454 }
455 }
456
457 BvOutput octave;
458 octave.distance = smoothDistance;
459 octave.color = smoothColor;
460 octave.position = bv_voronoi_position(cellPosition_f + smoothPosition);
461 return octave;
462}
463
464BN_FN BvOutput bv_voronoi_f2(BvParams params, vec2 coord) {
465 vec2 cellPosition_f = bv_bfloor(coord);
466 vec2 localPosition = coord - cellPosition_f;
467 ivec2 cellPosition = bv_to_ivec(cellPosition_f);
468
469 float distanceF1 = 3.402823466e+38f;
470 float distanceF2 = 3.402823466e+38f;
471 ivec2 offsetF1;
472 vec2 positionF1;
473 ivec2 offsetF2;
474 vec2 positionF2;
475 for (int j = -1; j <= 1; j++) {
476 for (int i = -1; i <= 1; i++) {
477 ivec2 cellOffset = ivec2( i, j );
478 vec2 pointPosition = bv_to_vec(cellOffset) +
479 bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness;
480 float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition, params);
481 if (distanceToPoint < distanceF1) {
482 distanceF2 = distanceF1;
483 distanceF1 = distanceToPoint;
484 offsetF2 = offsetF1;
485 offsetF1 = cellOffset;
486 positionF2 = positionF1;
487 positionF1 = pointPosition;
488 } else if (distanceToPoint < distanceF2) {
489 distanceF2 = distanceToPoint;
490 offsetF2 = cellOffset;
491 positionF2 = pointPosition;
492 }
493 }
494 }
495
496 BvOutput octave;
497 octave.distance = distanceF2;
498 octave.color = bn_hash_int_to_float3(bv_iadd(cellPosition, offsetF2));
499 octave.position = bv_voronoi_position(positionF2 + cellPosition_f);
500 return octave;
501}
502
503BN_FN float bv_voronoi_distance_to_edge(BvParams params, vec2 coord) {
504 vec2 cellPosition_f = bv_bfloor(coord);
505 vec2 localPosition = coord - cellPosition_f;
506 ivec2 cellPosition = bv_to_ivec(cellPosition_f);
507
508 vec2 vectorToClosest;
509 float minDistance = 3.402823466e+38f;
510 for (int j = -1; j <= 1; j++) {
511 for (int i = -1; i <= 1; i++) {
512 ivec2 cellOffset = ivec2( i, j );
513 vec2 vectorToPoint = bv_to_vec(cellOffset) +
514 bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness -
515 localPosition;
516 float distanceToPoint = dot(vectorToPoint, vectorToPoint);
517 if (distanceToPoint < minDistance) {
518 minDistance = distanceToPoint;
519 vectorToClosest = vectorToPoint;
520 }
521 }
522 }
523
524 minDistance = 3.402823466e+38f;
525 for (int j = -1; j <= 1; j++) {
526 for (int i = -1; i <= 1; i++) {
527 ivec2 cellOffset = ivec2( i, j );
528 vec2 vectorToPoint = bv_to_vec(cellOffset) +
529 bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness -
530 localPosition;
531 vec2 perpendicularToEdge = vectorToPoint - vectorToClosest;
532 if (dot(perpendicularToEdge, perpendicularToEdge) > 0.0001f) {
533 float distanceToEdge = dot((vectorToClosest + vectorToPoint) / 2.0f, normalize(perpendicularToEdge));
534 minDistance = min(minDistance, distanceToEdge);
535 }
536 }
537 }
538
539 return minDistance;
540}
541
542BN_FN float bv_voronoi_n_sphere_radius(BvParams params, vec2 coord) {
543 vec2 cellPosition_f = bv_bfloor(coord);
544 vec2 localPosition = coord - cellPosition_f;
545 ivec2 cellPosition = bv_to_ivec(cellPosition_f);
546
547 vec2 closestPoint;
548 ivec2 closestPointOffset;
549 float minDistanceSq = 3.402823466e+38f;
550 for (int j = -1; j <= 1; j++) {
551 for (int i = -1; i <= 1; i++) {
552 ivec2 cellOffset = ivec2( i, j );
553 vec2 pointPosition = bv_to_vec(cellOffset) +
554 bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness;
555 float distanceToPointSq = bv_vlen_sq_v(pointPosition - localPosition);
556 if (distanceToPointSq < minDistanceSq) {
557 minDistanceSq = distanceToPointSq;
558 closestPoint = pointPosition;
559 closestPointOffset = cellOffset;
560 }
561 }
562 }
563
564 minDistanceSq = 3.402823466e+38f;
565 vec2 closestPointToClosestPoint;
566 for (int j = -1; j <= 1; j++) {
567 for (int i = -1; i <= 1; i++) {
568 if (i == 0 && j == 0) {
569 continue;
570 }
571 ivec2 cellOffset = bv_iadd(ivec2( i, j ), closestPointOffset);
572 vec2 pointPosition = bv_to_vec(cellOffset) +
573 bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness;
574 float distanceToPointSq = bv_vlen_sq_v(closestPoint - pointPosition);
575 if (distanceToPointSq < minDistanceSq) {
576 minDistanceSq = distanceToPointSq;
577 closestPointToClosestPoint = pointPosition;
578 }
579 }
580 }
581
582 return distance(closestPointToClosestPoint, closestPoint) / 2.0f;
583}
584
585/* **** 3D Voronoi **** */
586
587BN_FN vec4 bv_voronoi_position(vec3 coord) {
588 return vec4(coord.x, coord.y, coord.z, 0.0f);
589}
590
591BN_FN BvOutput bv_voronoi_f1(BvParams params, vec3 coord) {
592 vec3 cellPosition_f = bv_bfloor(coord);
593 vec3 localPosition = coord - cellPosition_f;
594 ivec3 cellPosition = bv_to_ivec(cellPosition_f);
595
596 float minDistance = 3.402823466e+38f;
597 ivec3 targetOffset;
598 vec3 targetPosition;
599 for (int k = -1; k <= 1; k++) {
600 for (int j = -1; j <= 1; j++) {
601 for (int i = -1; i <= 1; i++) {
602 ivec3 cellOffset = ivec3( i, j, k );
603 vec3 pointPosition = bv_to_vec(cellOffset) +
604 bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness;
605 float distanceToPoint = bv_voronoi_distance_bound(pointPosition, localPosition,
606 params);
607 if (distanceToPoint < minDistance) {
608 targetOffset = cellOffset;
609 minDistance = distanceToPoint;
610 targetPosition = pointPosition;
611 }
612 }
613 }
614 }
615
616 BvOutput octave;
617 octave.distance = bv_voronoi_distance(targetPosition, localPosition, params);
618 octave.color = bn_hash_int_to_float3(bv_iadd(cellPosition, targetOffset));
619 octave.position = bv_voronoi_position(targetPosition + cellPosition_f);
620 return octave;
621}
622
623BN_FN BvOutput bv_voronoi_smooth_f1(BvParams params, vec3 coord,
624 bool calc_color) {
625 vec3 cellPosition_f = bv_bfloor(coord);
626 vec3 localPosition = coord - cellPosition_f;
627 ivec3 cellPosition = bv_to_ivec(cellPosition_f);
628
629 float smoothDistance = 0.0f;
630 vec3 smoothColor;
631 vec3 smoothPosition;
632 float h = -1.0f;
633 for (int k = -2; k <= 2; k++) {
634 for (int j = -2; j <= 2; j++) {
635 for (int i = -2; i <= 2; i++) {
636 ivec3 cellOffset = ivec3( i, j, k );
637 vec3 pointPosition = bv_to_vec(cellOffset) +
638 bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness;
639 float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition, params);
640 h = h == -1.0f ? 1.0f
641 : bv_bsmoothstep(0.0f, 1.0f,
642 0.5f + 0.5f * (smoothDistance - distanceToPoint) /
643 params.smoothness);
644 float correctionFactor = params.smoothness * h * (1.0f - h);
645 smoothDistance = bv_bmix(smoothDistance, distanceToPoint, h) - correctionFactor;
646 correctionFactor /= 1.0f + 3.0f * params.smoothness;
647 if (calc_color) {
648 vec3 cellColor = bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset));
649 smoothColor = bv_bmix(smoothColor, cellColor, h) - vec3(correctionFactor,
650 correctionFactor, correctionFactor);
651 }
652 smoothPosition = bv_bmix(smoothPosition, pointPosition, h) -
653 vec3(correctionFactor, correctionFactor, correctionFactor);
654 }
655 }
656 }
657
658 BvOutput octave;
659 octave.distance = smoothDistance;
660 octave.color = smoothColor;
661 octave.position = bv_voronoi_position(cellPosition_f + smoothPosition);
662 return octave;
663}
664
665BN_FN BvOutput bv_voronoi_f2(BvParams params, vec3 coord) {
666 vec3 cellPosition_f = bv_bfloor(coord);
667 vec3 localPosition = coord - cellPosition_f;
668 ivec3 cellPosition = bv_to_ivec(cellPosition_f);
669
670 float distanceF1 = 3.402823466e+38f;
671 float distanceF2 = 3.402823466e+38f;
672 ivec3 offsetF1;
673 vec3 positionF1;
674 ivec3 offsetF2;
675 vec3 positionF2;
676 for (int k = -1; k <= 1; k++) {
677 for (int j = -1; j <= 1; j++) {
678 for (int i = -1; i <= 1; i++) {
679 ivec3 cellOffset = ivec3( i, j, k );
680 vec3 pointPosition = bv_to_vec(cellOffset) +
681 bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness;
682 float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition, params);
683 if (distanceToPoint < distanceF1) {
684 distanceF2 = distanceF1;
685 distanceF1 = distanceToPoint;
686 offsetF2 = offsetF1;
687 offsetF1 = cellOffset;
688 positionF2 = positionF1;
689 positionF1 = pointPosition;
690 } else if (distanceToPoint < distanceF2) {
691 distanceF2 = distanceToPoint;
692 offsetF2 = cellOffset;
693 positionF2 = pointPosition;
694 }
695 }
696 }
697 }
698
699 BvOutput octave;
700 octave.distance = distanceF2;
701 octave.color = bn_hash_int_to_float3(bv_iadd(cellPosition, offsetF2));
702 octave.position = bv_voronoi_position(positionF2 + cellPosition_f);
703 return octave;
704}
705
706BN_FN float bv_voronoi_distance_to_edge(BvParams params, vec3 coord) {
707 vec3 cellPosition_f = bv_bfloor(coord);
708 vec3 localPosition = coord - cellPosition_f;
709 ivec3 cellPosition = bv_to_ivec(cellPosition_f);
710
711 vec3 vectorToClosest;
712 float minDistance = 3.402823466e+38f;
713 for (int k = -1; k <= 1; k++) {
714 for (int j = -1; j <= 1; j++) {
715 for (int i = -1; i <= 1; i++) {
716 ivec3 cellOffset = ivec3( i, j, k );
717 vec3 vectorToPoint = bv_to_vec(cellOffset) +
718 bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness -
719 localPosition;
720 float distanceToPoint = dot(vectorToPoint, vectorToPoint);
721 if (distanceToPoint < minDistance) {
722 minDistance = distanceToPoint;
723 vectorToClosest = vectorToPoint;
724 }
725 }
726 }
727 }
728
729 minDistance = 3.402823466e+38f;
730 for (int k = -1; k <= 1; k++) {
731 for (int j = -1; j <= 1; j++) {
732 for (int i = -1; i <= 1; i++) {
733 ivec3 cellOffset = ivec3( i, j, k );
734 vec3 vectorToPoint = bv_to_vec(cellOffset) +
735 bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness -
736 localPosition;
737 vec3 perpendicularToEdge = vectorToPoint - vectorToClosest;
738 if (dot(perpendicularToEdge, perpendicularToEdge) > 0.0001f) {
739 float distanceToEdge = dot((vectorToClosest + vectorToPoint) / 2.0f, normalize(perpendicularToEdge));
740 minDistance = min(minDistance, distanceToEdge);
741 }
742 }
743 }
744 }
745
746 return minDistance;
747}
748
749BN_FN float bv_voronoi_n_sphere_radius(BvParams params, vec3 coord) {
750 vec3 cellPosition_f = bv_bfloor(coord);
751 vec3 localPosition = coord - cellPosition_f;
752 ivec3 cellPosition = bv_to_ivec(cellPosition_f);
753
754 vec3 closestPoint;
755 ivec3 closestPointOffset;
756 float minDistanceSq = 3.402823466e+38f;
757 for (int k = -1; k <= 1; k++) {
758 for (int j = -1; j <= 1; j++) {
759 for (int i = -1; i <= 1; i++) {
760 ivec3 cellOffset = ivec3( i, j, k );
761 vec3 pointPosition = bv_to_vec(cellOffset) +
762 bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness;
763 float distanceToPointSq = bv_vlen_sq_v(pointPosition - localPosition);
764 if (distanceToPointSq < minDistanceSq) {
765 minDistanceSq = distanceToPointSq;
766 closestPoint = pointPosition;
767 closestPointOffset = cellOffset;
768 }
769 }
770 }
771 }
772
773 minDistanceSq = 3.402823466e+38f;
774 vec3 closestPointToClosestPoint;
775 for (int k = -1; k <= 1; k++) {
776 for (int j = -1; j <= 1; j++) {
777 for (int i = -1; i <= 1; i++) {
778 if (i == 0 && j == 0 && k == 0) {
779 continue;
780 }
781 ivec3 cellOffset = bv_iadd(ivec3( i, j, k ), closestPointOffset);
782 vec3 pointPosition = bv_to_vec(cellOffset) +
783 bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness;
784 float distanceToPointSq = bv_vlen_sq_v(closestPoint - pointPosition);
785 if (distanceToPointSq < minDistanceSq) {
786 minDistanceSq = distanceToPointSq;
787 closestPointToClosestPoint = pointPosition;
788 }
789 }
790 }
791 }
792
793 return distance(closestPointToClosestPoint, closestPoint) / 2.0f;
794}
795
796/* **** 4D Voronoi **** */
797
798BN_FN vec4 bv_voronoi_position(vec4 coord) { return coord; }
799
800BN_FN BvOutput bv_voronoi_f1(BvParams params, vec4 coord) {
801 vec4 cellPosition_f = bv_bfloor(coord);
802 vec4 localPosition = coord - cellPosition_f;
803 ivec4 cellPosition = bv_to_ivec(cellPosition_f);
804
805 float minDistance = 3.402823466e+38f;
806 ivec4 targetOffset;
807 vec4 targetPosition;
808 for (int u = -1; u <= 1; u++) {
809 for (int k = -1; k <= 1; k++) {
810 for (int j = -1; j <= 1; j++) {
811 for (int i = -1; i <= 1; i++) {
812 ivec4 cellOffset = ivec4( i, j, k, u );
813 vec4 pointPosition = bv_to_vec(cellOffset) +
814 bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
815 params.randomness;
816 float distanceToPoint = bv_voronoi_distance_bound(pointPosition, localPosition,
817 params);
818 if (distanceToPoint < minDistance) {
819 targetOffset = cellOffset;
820 minDistance = distanceToPoint;
821 targetPosition = pointPosition;
822 }
823 }
824 }
825 }
826 }
827
828 BvOutput octave;
829 octave.distance = bv_voronoi_distance(targetPosition, localPosition, params);
830 octave.color = bn_hash_int_to_float3(bv_iadd(cellPosition, targetOffset));
831 octave.position = bv_voronoi_position(targetPosition + cellPosition_f);
832 return octave;
833}
834
835BN_FN BvOutput bv_voronoi_smooth_f1(BvParams params, vec4 coord,
836 bool calc_color) {
837 vec4 cellPosition_f = bv_bfloor(coord);
838 vec4 localPosition = coord - cellPosition_f;
839 ivec4 cellPosition = bv_to_ivec(cellPosition_f);
840
841 float smoothDistance = 0.0f;
842 vec3 smoothColor;
843 vec4 smoothPosition;
844 float h = -1.0f;
845 for (int u = -2; u <= 2; u++) {
846 for (int k = -2; k <= 2; k++) {
847 for (int j = -2; j <= 2; j++) {
848 for (int i = -2; i <= 2; i++) {
849 ivec4 cellOffset = ivec4( i, j, k, u );
850 vec4 pointPosition = bv_to_vec(cellOffset) +
851 bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
852 params.randomness;
853 float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition, params);
854 h = h == -1.0f ? 1.0f
855 : bv_bsmoothstep(0.0f, 1.0f,
856 0.5f + 0.5f * (smoothDistance - distanceToPoint) /
857 params.smoothness);
858 float correctionFactor = params.smoothness * h * (1.0f - h);
859 smoothDistance = bv_bmix(smoothDistance, distanceToPoint, h) - correctionFactor;
860 correctionFactor /= 1.0f + 3.0f * params.smoothness;
861 if (calc_color) {
862 vec3 cellColor = bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset));
863 smoothColor = bv_bmix(smoothColor, cellColor, h) - vec3(correctionFactor,
864 correctionFactor, correctionFactor);
865 }
866 smoothPosition = bv_bmix(smoothPosition, pointPosition, h) -
867 vec4(correctionFactor, correctionFactor, correctionFactor,
868 correctionFactor);
869 }
870 }
871 }
872 }
873
874 BvOutput octave;
875 octave.distance = smoothDistance;
876 octave.color = smoothColor;
877 octave.position = bv_voronoi_position(cellPosition_f + smoothPosition);
878 return octave;
879}
880
881BN_FN BvOutput bv_voronoi_f2(BvParams params, vec4 coord) {
882 vec4 cellPosition_f = bv_bfloor(coord);
883 vec4 localPosition = coord - cellPosition_f;
884 ivec4 cellPosition = bv_to_ivec(cellPosition_f);
885
886 float distanceF1 = 3.402823466e+38f;
887 float distanceF2 = 3.402823466e+38f;
888 ivec4 offsetF1;
889 vec4 positionF1;
890 ivec4 offsetF2;
891 vec4 positionF2;
892 for (int u = -1; u <= 1; u++) {
893 for (int k = -1; k <= 1; k++) {
894 for (int j = -1; j <= 1; j++) {
895 for (int i = -1; i <= 1; i++) {
896 ivec4 cellOffset = ivec4( i, j, k, u );
897 vec4 pointPosition = bv_to_vec(cellOffset) +
898 bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
899 params.randomness;
900 float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition, params);
901 if (distanceToPoint < distanceF1) {
902 distanceF2 = distanceF1;
903 distanceF1 = distanceToPoint;
904 offsetF2 = offsetF1;
905 offsetF1 = cellOffset;
906 positionF2 = positionF1;
907 positionF1 = pointPosition;
908 } else if (distanceToPoint < distanceF2) {
909 distanceF2 = distanceToPoint;
910 offsetF2 = cellOffset;
911 positionF2 = pointPosition;
912 }
913 }
914 }
915 }
916 }
917
918 BvOutput octave;
919 octave.distance = distanceF2;
920 octave.color = bn_hash_int_to_float3(bv_iadd(cellPosition, offsetF2));
921 octave.position = bv_voronoi_position(positionF2 + cellPosition_f);
922 return octave;
923}
924
925BN_FN float bv_voronoi_distance_to_edge(BvParams params, vec4 coord) {
926 vec4 cellPosition_f = bv_bfloor(coord);
927 vec4 localPosition = coord - cellPosition_f;
928 ivec4 cellPosition = bv_to_ivec(cellPosition_f);
929
930 vec4 vectorToClosest;
931 float minDistance = 3.402823466e+38f;
932 for (int u = -1; u <= 1; u++) {
933 for (int k = -1; k <= 1; k++) {
934 for (int j = -1; j <= 1; j++) {
935 for (int i = -1; i <= 1; i++) {
936 ivec4 cellOffset = ivec4( i, j, k, u );
937 vec4 vectorToPoint = bv_to_vec(cellOffset) +
938 bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
939 params.randomness -
940 localPosition;
941 float distanceToPoint = bv_vdot(vectorToPoint, vectorToPoint);
942 if (distanceToPoint < minDistance) {
943 minDistance = distanceToPoint;
944 vectorToClosest = vectorToPoint;
945 }
946 }
947 }
948 }
949 }
950
951 minDistance = 3.402823466e+38f;
952 for (int u = -1; u <= 1; u++) {
953 for (int k = -1; k <= 1; k++) {
954 for (int j = -1; j <= 1; j++) {
955 for (int i = -1; i <= 1; i++) {
956 ivec4 cellOffset = ivec4( i, j, k, u );
957 vec4 vectorToPoint = bv_to_vec(cellOffset) +
958 bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
959 params.randomness -
960 localPosition;
961 vec4 perpendicularToEdge = vectorToPoint - vectorToClosest;
962 if (bv_vdot(perpendicularToEdge, perpendicularToEdge) > 0.0001f) {
963 float distanceToEdge = bv_vdot((vectorToClosest + vectorToPoint) / 2.0f,
964 bv_vnorm(perpendicularToEdge));
965 minDistance = min(minDistance, distanceToEdge);
966 }
967 }
968 }
969 }
970 }
971
972 return minDistance;
973}
974
975BN_FN float bv_voronoi_n_sphere_radius(BvParams params, vec4 coord) {
976 vec4 cellPosition_f = bv_bfloor(coord);
977 vec4 localPosition = coord - cellPosition_f;
978 ivec4 cellPosition = bv_to_ivec(cellPosition_f);
979
980 vec4 closestPoint;
981 ivec4 closestPointOffset;
982 float minDistanceSq = 3.402823466e+38f;
983 for (int u = -1; u <= 1; u++) {
984 for (int k = -1; k <= 1; k++) {
985 for (int j = -1; j <= 1; j++) {
986 for (int i = -1; i <= 1; i++) {
987 ivec4 cellOffset = ivec4( i, j, k, u );
988 vec4 pointPosition = bv_to_vec(cellOffset) +
989 bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
990 params.randomness;
991 float distanceToPointSq = bv_vlen_sq(pointPosition - localPosition);
992 if (distanceToPointSq < minDistanceSq) {
993 minDistanceSq = distanceToPointSq;
994 closestPoint = pointPosition;
995 closestPointOffset = cellOffset;
996 }
997 }
998 }
999 }
1000 }
1001
1002 minDistanceSq = 3.402823466e+38f;
1003 vec4 closestPointToClosestPoint;
1004 for (int u = -1; u <= 1; u++) {
1005 for (int k = -1; k <= 1; k++) {
1006 for (int j = -1; j <= 1; j++) {
1007 for (int i = -1; i <= 1; i++) {
1008 if (i == 0 && j == 0 && k == 0 && u == 0) {
1009 continue;
1010 }
1011 ivec4 cellOffset = bv_iadd(ivec4( i, j, k, u ), closestPointOffset);
1012 vec4 pointPosition = bv_to_vec(cellOffset) +
1013 bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
1014 params.randomness;
1015 float distanceToPointSq = bv_vlen_sq(closestPoint - pointPosition);
1016 if (distanceToPointSq < minDistanceSq) {
1017 minDistanceSq = distanceToPointSq;
1018 closestPointToClosestPoint = pointPosition;
1019 }
1020 }
1021 }
1022 }
1023 }
1024
1025 return sqrt(bv_vlen_sq(closestPointToClosestPoint - closestPoint)) / 2.0f;
1026}
1027
1028/* **** Fractal Voronoi **** */
1029
1030// The fractalization is fBM's, except that some additions are replaced by lerps.
1031
1032/* **** Fractal Voronoi **** */
1033
1034// The fractalization is fBM's, except that some additions are replaced by
1035// lerps. `template <typename T>` in the C++; one body, stamped per dimension.
1036#define BV_FRACTALS(T, S) \
1037BN_FN BvOutput bv_fractal_voronoi_x_fx_##S(BvParams params, const T coord, \
1038 bool calc_color) { \
1039 float amplitude = 1.0f; \
1040 float max_amplitude = 0.0f; \
1041 float scale = 1.0f; \
1042 \
1043 BvOutput outp; \
1044 outp.distance = 0.0f; \
1045 outp.color = vec3(0.0f); \
1046 outp.position = vec4(0.0f); \
1047 bool zero_input = params.detail == 0.0f || params.roughness == 0.0f; \
1048 \
1049 for (int i = 0; i <= int(ceil(params.detail)); ++i) { \
1050 BvOutput octave = (params.feature == BV_F2) \
1051 ? bv_voronoi_f2(params, coord * scale) \
1052 : ((params.feature == BV_SMOOTH_F1 && params.smoothness != 0.0f) \
1053 ? bv_voronoi_smooth_f1(params, coord * scale, calc_color) \
1054 : bv_voronoi_f1(params, coord * scale)); \
1055 \
1056 if (zero_input) { \
1057 max_amplitude = 1.0f; \
1058 outp = octave; \
1059 break; \
1060 } \
1061 if (float(i) <= params.detail) { \
1062 max_amplitude += amplitude; \
1063 outp.distance += octave.distance * amplitude; \
1064 outp.color += octave.color * amplitude; \
1065 outp.position = bv_bmix(outp.position, octave.position / scale, amplitude); \
1066 scale *= params.lacunarity; \
1067 amplitude *= params.roughness; \
1068 } else { \
1069 float remainder = params.detail - floor(params.detail); \
1070 if (remainder != 0.0f) { \
1071 max_amplitude = bv_bmix(max_amplitude, max_amplitude + amplitude, remainder); \
1072 outp.distance = bv_bmix(outp.distance, \
1073 outp.distance + octave.distance * amplitude, remainder); \
1074 outp.color = bv_bmix(outp.color, outp.color + octave.color * amplitude, \
1075 remainder); \
1076 outp.position = bv_bmix(outp.position, \
1077 bv_bmix(outp.position, octave.position / scale, amplitude), remainder); \
1078 } \
1079 } \
1080 } \
1081 \
1082 if (params.normalize) { \
1083 outp.distance /= max_amplitude * params.max_distance; \
1084 outp.color /= max_amplitude; \
1085 } \
1086 \
1087 outp.position = (params.scale != 0.0f) ? outp.position / params.scale : vec4(0.0f); \
1088 \
1089 return outp; \
1090} \
1091 \
1092BN_FN float bv_fractal_voronoi_distance_to_edge_##S(BvParams params, const T coord) { \
1093 float amplitude = 1.0f; \
1094 float max_amplitude = params.max_distance; \
1095 float scale = 1.0f; \
1096 float distance = 8.0f; \
1097 \
1098 bool zero_input = params.detail == 0.0f || params.roughness == 0.0f; \
1099 \
1100 for (int i = 0; i <= int(ceil(params.detail)); ++i) { \
1101 float octave_distance = bv_voronoi_distance_to_edge(params, coord * scale); \
1102 \
1103 if (zero_input) { \
1104 distance = octave_distance; \
1105 break; \
1106 } \
1107 if (float(i) <= params.detail) { \
1108 max_amplitude = bv_bmix(max_amplitude, params.max_distance / scale, amplitude); \
1109 distance = bv_bmix(distance, min(distance, octave_distance / scale), amplitude); \
1110 scale *= params.lacunarity; \
1111 amplitude *= params.roughness; \
1112 } else { \
1113 float remainder = params.detail - floor(params.detail); \
1114 if (remainder != 0.0f) { \
1115 float lerp_amplitude = bv_bmix(max_amplitude, params.max_distance / scale, amplitude); \
1116 max_amplitude = bv_bmix(max_amplitude, lerp_amplitude, remainder); \
1117 float lerp_distance = bv_bmix(distance, min(distance, octave_distance / scale), \
1118 amplitude); \
1119 distance = bv_bmix(distance, min(distance, lerp_distance), remainder); \
1120 } \
1121 } \
1122 } \
1123 \
1124 if (params.normalize) { \
1125 distance /= max_amplitude; \
1126 } \
1127 \
1128 return distance; \
1129}
1130
1131BV_FRACTALS(float, 1)
1132BV_FRACTALS(vec2, 2)
1133BV_FRACTALS(vec3, 3)
1134BV_FRACTALS(vec4, 4)
1135
1136#endif // PN_BLENDER_VORONOI
tests/test_shared_noise_parity.cpp +197 -7
……@@ -93,9 +93,11 @@ using namespace godot::bnoisegl;
9393static const char *SUBJECT_FILES[] = {
9494 "src/gpu/blender_hash_glsl.gen.h",
9595 "src/gpu/blender_perlin_glsl.gen.h",
96 "src/gpu/blender_voronoi_glsl.gen.h",
9697};
97static const char *SUBJECT_MARKS[] = { "BLENDER_HASH_GLSL", "BLENDER_PERLIN_GLSL" };
98static const int SUBJECT_N = 2;
98static const char *SUBJECT_MARKS[] = { "BLENDER_HASH_GLSL", "BLENDER_PERLIN_GLSL",
99 "BLENDER_VORONOI_GLSL" };
100static const int SUBJECT_N = 3;
99101
100102static const double TOL_REL = 1e-5;
101103static const double TOL_ABS = 1e-6;
……@@ -284,7 +286,9 @@ static void init_vulkan(Ctx &c) {
284286// actually run. Only the opcode and the dimension pick a code path.
285287// ============================================================================
286288
287static const uint32_t STRIDE = 16;
289// 24 rather than 16 since Voronoi arrived: its parameter block is eleven
290// fields and every one of them changes the answer.
291static const uint32_t STRIDE = 24;
288292
289293enum Op {
290294 OP_HASH1 = 1, OP_HASH2, OP_HASH3, OP_HASH4,
……@@ -305,6 +309,9 @@ enum Op {
305309 OP_PERLIN_SELECT, // dim, type, flags
306310 OP_PERLIN_DISTORTED, // dim, type, flags
307311 OP_PERLIN_FLOAT3, // dim, comp, type, flags
312 OP_VORONOI_XFX, // dim, comp (0=dist,1..3=color,4..7=position)
313 OP_VORONOI_EDGE, // dim
314 OP_VORONOI_RADIUS, // dim
308315 OP__COUNT
309316};
310317
……@@ -326,6 +333,8 @@ static const OpName OP_NAMES[] = {
326333 { OP_PERLIN_NOISE, "OP_PERLIN_NOISE" }, { OP_PERLIN_SIGNED, "OP_PERLIN_SIGNED" },
327334 { OP_PERLIN_SELECT, "OP_PERLIN_SELECT" }, { OP_PERLIN_DISTORTED, "OP_PERLIN_DISTORTED" },
328335 { OP_PERLIN_FLOAT3, "OP_PERLIN_FLOAT3" },
336 { OP_VORONOI_XFX, "OP_VORONOI_XFX" }, { OP_VORONOI_EDGE, "OP_VORONOI_EDGE" },
337 { OP_VORONOI_RADIUS, "OP_VORONOI_RADIUS" },
329338};
330339
331340struct Rec {
……@@ -341,7 +350,28 @@ static float float_of(uint32_t u) { float f; memcpy(&f, &u, sizeof(f)); return f
341350// Word slots, named once so both sides agree by construction.
342351enum { W_OP = 0, W_DIM = 1, W_TYPE = 2, W_FLAGS = 3, W_COMP = 4,
343352 W_A = 5, W_B = 6, W_C = 7, W_D = 8,
344 W_DETAIL = 9, W_ROUGH = 10, W_LAC = 11, W_OFF = 12, W_GAIN = 13, W_DIST = 14 };
353 W_DETAIL = 9, W_ROUGH = 10, W_LAC = 11, W_OFF = 12, W_GAIN = 13, W_DIST = 14,
354 // Voronoi's parameter block
355 W_SCALE = 15, W_SMOOTH = 16, W_EXPONENT = 17, W_RANDOMNESS = 18, W_MAXDIST = 19,
356 W_METRIC = 20 };
357
358// Filled from the record, all eleven fields -- a GLSL struct has no defaults
359// and a field left unset is garbage, not a sensible value.
360static BvParams params_from(const uint32_t *w) {
361 BvParams p;
362 p.scale = float_of(w[W_SCALE]);
363 p.detail = float_of(w[W_DETAIL]);
364 p.roughness = float_of(w[W_ROUGH]);
365 p.lacunarity = float_of(w[W_LAC]);
366 p.smoothness = float_of(w[W_SMOOTH]);
367 p.exponent = float_of(w[W_EXPONENT]);
368 p.randomness = float_of(w[W_RANDOMNESS]);
369 p.max_distance = float_of(w[W_MAXDIST]);
370 p.normalize = (w[W_FLAGS] & 1u) != 0u;
371 p.feature = (int)w[W_TYPE];
372 p.metric = (int)w[W_METRIC];
373 return p;
374}
345375
346376// --- the CPU half of the dispatcher -----------------------------------------
347377static uint32_t eval_cpu(const uint32_t *w) {
……@@ -437,6 +467,31 @@ static uint32_t eval_cpu(const uint32_t *w) {
437467 : bp_perlin_float3_fractal_distorted(v4, detail, rough, lac, off, gain, dist, type, nrm);
438468 return bits_of(comp == 0 ? r.x : (comp == 1 ? r.y : r.z));
439469 }
470 case OP_VORONOI_XFX: {
471 BvParams p = params_from(w);
472 BvOutput o = (dim == 1) ? bv_fractal_voronoi_x_fx_1(p, x, true)
473 : (dim == 2) ? bv_fractal_voronoi_x_fx_2(p, v2, true)
474 : (dim == 3) ? bv_fractal_voronoi_x_fx_3(p, v3, true)
475 : bv_fractal_voronoi_x_fx_4(p, v4, true);
476 if (comp == 0) return bits_of(o.distance);
477 if (comp <= 3) return bits_of(comp == 1 ? o.color.x : (comp == 2 ? o.color.y : o.color.z));
478 return bits_of(comp == 4 ? o.position.x : (comp == 5 ? o.position.y
479 : (comp == 6 ? o.position.z : o.position.w)));
480 }
481 case OP_VORONOI_EDGE: {
482 BvParams p = params_from(w);
483 return bits_of((dim == 1) ? bv_fractal_voronoi_distance_to_edge_1(p, x)
484 : (dim == 2) ? bv_fractal_voronoi_distance_to_edge_2(p, v2)
485 : (dim == 3) ? bv_fractal_voronoi_distance_to_edge_3(p, v3)
486 : bv_fractal_voronoi_distance_to_edge_4(p, v4));
487 }
488 case OP_VORONOI_RADIUS: {
489 BvParams p = params_from(w);
490 return bits_of((dim == 1) ? bv_voronoi_n_sphere_radius(p, x)
491 : (dim == 2) ? bv_voronoi_n_sphere_radius(p, v2)
492 : (dim == 3) ? bv_voronoi_n_sphere_radius(p, v3)
493 : bv_voronoi_n_sphere_radius(p, v4));
494 }
440495 default:
441496 fprintf(stderr, "FATAL: cpu dispatcher has no arm for op %u\n", op);
442497 exit(1);
……@@ -573,6 +628,52 @@ void main() {
573628 else { q = bp_perlin_float3_fractal_distorted(v4, detail, rough, lac, off, gain, dist, type, nrm); }
574629 r = floatBitsToUint(comp == 0u ? q.x : (comp == 1u ? q.y : q.z));
575630 }
631 else if (op == OP_VORONOI_XFX || op == OP_VORONOI_EDGE || op == OP_VORONOI_RADIUS) {
632 BvParams p;
633 p.scale = gF(b, 15u);
634 p.detail = detail;
635 p.roughness = rough;
636 p.lacunarity = lac;
637 p.smoothness = gF(b, 16u);
638 p.exponent = gF(b, 17u);
639 p.randomness = gF(b, 18u);
640 p.max_distance = gF(b, 19u);
641 p.normalize = nrm;
642 p.feature = type;
643 p.metric = int(gW(b, 20u));
644
645 if (op == OP_VORONOI_RADIUS) {
646 float f;
647 if (dim == 1u) { f = bv_voronoi_n_sphere_radius(p, x); }
648 else if (dim == 2u) { f = bv_voronoi_n_sphere_radius(p, v2); }
649 else if (dim == 3u) { f = bv_voronoi_n_sphere_radius(p, v3); }
650 else { f = bv_voronoi_n_sphere_radius(p, v4); }
651 r = floatBitsToUint(f);
652 } else if (op == OP_VORONOI_EDGE) {
653 float f;
654 if (dim == 1u) { f = bv_fractal_voronoi_distance_to_edge_1(p, x); }
655 else if (dim == 2u) { f = bv_fractal_voronoi_distance_to_edge_2(p, v2); }
656 else if (dim == 3u) { f = bv_fractal_voronoi_distance_to_edge_3(p, v3); }
657 else { f = bv_fractal_voronoi_distance_to_edge_4(p, v4); }
658 r = floatBitsToUint(f);
659 } else {
660 BvOutput o;
661 if (dim == 1u) { o = bv_fractal_voronoi_x_fx_1(p, x, true); }
662 else if (dim == 2u) { o = bv_fractal_voronoi_x_fx_2(p, v2, true); }
663 else if (dim == 3u) { o = bv_fractal_voronoi_x_fx_3(p, v3, true); }
664 else { o = bv_fractal_voronoi_x_fx_4(p, v4, true); }
665 float f;
666 if (comp == 0u) { f = o.distance; }
667 else if (comp == 1u) { f = o.color.x; }
668 else if (comp == 2u) { f = o.color.y; }
669 else if (comp == 3u) { f = o.color.z; }
670 else if (comp == 4u) { f = o.position.x; }
671 else if (comp == 5u) { f = o.position.y; }
672 else if (comp == 6u) { f = o.position.z; }
673 else { f = o.position.w; }
674 r = floatBitsToUint(f);
675 }
676 }
576677 out_v.v[idx] = r;
577678}
578679)DISPATCH";
……@@ -607,6 +708,11 @@ struct B {
607708 w[W_A] = bits_of(a); w[W_B] = bits_of(b);
608709 w[W_C] = bits_of(c); w[W_D] = bits_of(d); return *this;
609710 }
711 B &vor(float scale, float smooth, float expo, float rand01, float maxd, int metric) {
712 w[W_SCALE] = bits_of(scale); w[W_SMOOTH] = bits_of(smooth);
713 w[W_EXPONENT] = bits_of(expo); w[W_RANDOMNESS] = bits_of(rand01);
714 w[W_MAXDIST] = bits_of(maxd); w[W_METRIC] = (uint32_t)metric; return *this;
715 }
610716 B &frac(float detail, float rough, float lac, float off, float gain, float dist) {
611717 w[W_DETAIL] = bits_of(detail); w[W_ROUGH] = bits_of(rough);
612718 w[W_LAC] = bits_of(lac); w[W_OFF] = bits_of(off);
……@@ -755,6 +861,88 @@ static void build_cases() {
755861 }
756862}
757863
864static void build_voronoi_cases() {
865 // Every dimension x feature x metric, with normalize both ways. They are
866 // not variations on one function: the feature picks which of three
867 // entry points runs, the metric changes the distance inside the cell
868 // search, and the dimension changes both the lattice and which of the
869 // four stamped copies is called.
870 const float VC[][4] = {
871 { 0.0f, 0.0f, 0.0f, 0.0f },
872 { 1.25f, -2.5f, 0.75f, 3.5f },
873 { -13.75f, 7.5f, -0.25f, -6.5f },
874 };
875 const int NVC = (int)(sizeof(VC) / sizeof(VC[0]));
876
877 for (int i = 0; i < NVC; i++) {
878 const float x = VC[i][0], y = VC[i][1], z = VC[i][2], w = VC[i][3];
879 const vec2 c2(x, y); const vec3 c3(x, y, z); const vec4 c4(x, y, z, w);
880 for (uint32_t dim = 1; dim <= 4; dim++) {
881 for (int feat = 0; feat <= 4; feat++) {
882 for (int metric = 0; metric <= 3; metric++) {
883 for (int n = 0; n < 2; n++) {
884 const bool nb = (n != 0);
885 const float scale = 1.0f + 0.25f * (float)i;
886 const float detail = (i == 0) ? 0.0f : 2.5f; // fractional
887 const float rough = 0.5f;
888 const float lac = 2.0f;
889 const float smooth = (feat == 2) ? 0.25f : 0.0f;
890 const float expo = (metric == 3 && dim != 1) ? 2.5f : 0.0f;
891 const float rand01 = 0.85f;
892 const float r = 0.5f + 0.5f * rand01;
893 const float f2 = (feat == 1) ? 2.0f : 1.0f;
894
895 // max_distance: 1.0 for the edge and radius features,
896 // where the CPU leaves the struct default standing, and
897 // the per-dimension cell distance for the rest.
898 float maxd = 1.0f;
899 if (feat != 3 && feat != 4) {
900 BvParams tmp;
901 tmp.scale = scale; tmp.detail = detail; tmp.roughness = rough;
902 tmp.lacunarity = lac; tmp.smoothness = smooth; tmp.exponent = expo;
903 tmp.randomness = rand01; tmp.max_distance = 1.0f;
904 tmp.normalize = nb; tmp.feature = feat; tmp.metric = metric;
905 if (dim == 1) {
906 maxd = r * f2;
907 } else if (dim == 2) {
908 maxd = bv_voronoi_distance(vec2(0.0f), vec2(r, r), tmp) * f2;
909 } else if (dim == 3) {
910 maxd = bv_voronoi_distance(vec3(0.0f), vec3(r, r, r), tmp) * f2;
911 } else {
912 maxd = bv_voronoi_distance(vec4(0.0f), vec4(r, r, r, r), tmp) * f2;
913 }
914 }
915
916 char nm[80];
917 if (feat == 4) {
918 snprintf(nm, sizeof(nm), "bv_n_sphere_radius.d%u.m%d", dim, metric);
919 add(nm, false, B(OP_VORONOI_RADIUS).dim(dim).type(feat).norm(nb)
920 .f(x, y, z, w).frac(detail, rough, lac, 0.0f, 0.0f, 0.0f)
921 .vor(scale, smooth, expo, rand01, maxd, metric));
922 } else if (feat == 3) {
923 snprintf(nm, sizeof(nm), "bv_edge.d%u.m%d", dim, metric);
924 add(nm, false, B(OP_VORONOI_EDGE).dim(dim).type(feat).norm(nb)
925 .f(x, y, z, w).frac(detail, rough, lac, 0.0f, 0.0f, 0.0f)
926 .vor(scale, smooth, expo, rand01, maxd, metric));
927 } else {
928 // distance, all three colour components and all four
929 // of position -- the W component is a separate output
930 // on the node and comes from position.w.
931 for (uint32_t comp = 0; comp < 8; comp++) {
932 snprintf(nm, sizeof(nm), "bv_x_fx.d%u.f%d.m%d", dim, feat, metric);
933 add(nm, false, B(OP_VORONOI_XFX).dim(dim).type(feat).norm(nb)
934 .comp(comp).f(x, y, z, w)
935 .frac(detail, rough, lac, 0.0f, 0.0f, 0.0f)
936 .vor(scale, smooth, expo, rand01, maxd, metric));
937 }
938 }
939 }
940 }
941 }
942 }
943 }
944}
945
758946static long peak_rss_kb() {
759947 std::ifstream f("/proc/self/status");
760948 std::string line;
……@@ -790,13 +978,15 @@ int main(int argc, char **argv) {
790978 subject_desc += (i ? "\n " : "") + file;
791979 }
792980 if (lib.find("bn_hash_bit_final") == std::string::npos ||
793 lib.find("bp_perlin_signed") == std::string::npos) {
794 fprintf(stderr, "FATAL: subject text is missing bn_hash_bit_final or bp_perlin_signed"
795 " -- wrong subject?\n");
981 lib.find("bp_perlin_signed") == std::string::npos ||
982 lib.find("bv_voronoi_f1") == std::string::npos) {
983 fprintf(stderr, "FATAL: subject text is missing bn_hash_bit_final, bp_perlin_signed"
984 " or bv_voronoi_f1 -- wrong subject?\n");
796985 return 1;
797986 }
798987
799988 build_cases();
989 build_voronoi_cases();
800990 if (g_cases.empty()) { fprintf(stderr, "FATAL: no cases\n"); return 1; }
801991
802992 int n_exact = 0;