| … | … | @@ -66,6 +66,12 @@ glsl_inc_gen = [ |
| 66 | 66 | "src/shared/blender_perlin.inc", |
| 67 | 67 | "python3 tools/gen_glsl_inc.py $SOURCE $TARGET BLENDER_PERLIN_GLSL", |
| 68 | 68 | ), |
| 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 | ), | |
| 69 | 75 | ] |
| 70 | 76 | |
| 71 | 77 | sources = ( |
| … | … | @@ -350,6 +350,55 @@ func _init() -> void: |
| 350 | 350 | PackedStringArray()), |
| 351 | 351 | }) |
| 352 | 352 | |
| 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 | ||
| 353 | 402 | # A DIRECTORY, not JSON: stage 2 is a standalone C++ program with no JSON |
| 354 | 403 | # parser, and adding one to read our own test data would be more code than |
| 355 | 404 | # the gate. One manifest line per case, one .glsl file per case. |
| … | … | @@ -12,6 +12,7 @@ |
| 12 | 12 | #include <cmath> |
| 13 | 13 | #include "../gpu/blender_hash_glsl.gen.h" |
| 14 | 14 | #include "../gpu/blender_perlin_glsl.gen.h" |
| 15 | #include "../gpu/blender_voronoi_glsl.gen.h" | |
| 15 | 16 | |
| 16 | 17 | namespace godot { |
| 17 | 18 | |
| … | … | @@ -739,6 +740,146 @@ VoronoiTextureNode::VoronoiTextureNode() { |
| 739 | 740 | // The parameter setup is node_shader_tex_voronoi.cc's, including its clamps and |
| 740 | 741 | // its per-dimension max_distance -- which is what `normalize` divides by, so |
| 741 | 742 | // getting it from the wrong dimension scales the whole output. |
| 743 | String VoronoiTextureNode::compile_glsl_prelude(int p_node_idx) const { | |
| 744 | return String(BLENDER_HASH_GLSL) + String(BLENDER_VORONOI_GLSL); | |
| 745 | } | |
| 746 | ||
| 747 | String 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 | ||
| 742 | 883 | void VoronoiTextureNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 743 | 884 | SocketValue vec_v = p_ctx.read_input(p_node_idx, 0); |
| 744 | 885 | SocketValue w_v = p_ctx.read_input(p_node_idx, 1); |
| … | … | @@ -126,9 +126,12 @@ public: |
| 126 | 126 | StringName get_node_type() const override { return "VoronoiTexture"; } |
| 127 | 127 | String get_node_title() const override { return "Voronoi Texture"; } |
| 128 | 128 | 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; | |
| 132 | 135 | |
| 133 | 136 | void set_voronoi_dimensions(int p_d) { voronoi_dimensions = p_d; } |
| 134 | 137 | int get_voronoi_dimensions() const { return voronoi_dimensions; } |
| … | … | @@ -135,6 +135,35 @@ inline vec4 operator*(float s, vec4 a) { return vec4(s * a.x, s * a.y, s * a.z, |
| 135 | 135 | inline vec4 operator+(vec4 a, float s) { return vec4(a.x + s, a.y + s, a.z + s, a.w + s); } |
| 136 | 136 | inline vec4 operator-(vec4 a) { return vec4(-a.x, -a.y, -a.z, -a.w); } |
| 137 | 137 | |
| 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. | |
| 141 | inline vec2 operator/(vec2 a, float s) { return vec2(a.x / s, a.y / s); } | |
| 142 | inline vec3 operator/(vec3 a, float s) { return vec3(a.x / s, a.y / s, a.z / s); } | |
| 143 | inline vec4 operator/(vec4 a, float s) { return vec4(a.x / s, a.y / s, a.z / s, a.w / s); } | |
| 144 | inline vec2 &operator+=(vec2 &a, vec2 b) { a = a + b; return a; } | |
| 145 | inline vec3 &operator+=(vec3 &a, vec3 b) { a = a + b; return a; } | |
| 146 | inline vec4 &operator+=(vec4 &a, vec4 b) { a = a + b; return a; } | |
| 147 | inline vec2 &operator/=(vec2 &a, float s) { a = a / s; return a; } | |
| 148 | inline vec3 &operator/=(vec3 &a, float s) { a = a / s; return a; } | |
| 149 | inline vec4 &operator/=(vec4 &a, float s) { a = a / s; return a; } | |
| 150 | inline vec2 operator-(vec2 a, float s) { return vec2(a.x - s, a.y - s); } | |
| 151 | inline vec3 operator-(vec3 a, float s) { return vec3(a.x - s, a.y - s, a.z - s); } | |
| 152 | inline vec4 operator-(vec4 a, float s) { return vec4(a.x - s, a.y - s, a.z - s, a.w - s); } | |
| 153 | ||
| 154 | inline float dot(vec2 a, vec2 b) { return a.x * b.x + a.y * b.y; } | |
| 155 | inline float dot(vec3 a, vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; } | |
| 156 | inline float dot(vec4 a, vec4 b) { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; } | |
| 157 | inline float length(vec2 v) { return std::sqrt(dot(v, v)); } | |
| 158 | inline float length(vec3 v) { return std::sqrt(dot(v, v)); } | |
| 159 | inline float length(vec4 v) { return std::sqrt(dot(v, v)); } | |
| 160 | inline float distance(vec2 a, vec2 b) { return length(a - b); } | |
| 161 | inline float distance(vec3 a, vec3 b) { return length(a - b); } | |
| 162 | inline float distance(vec4 a, vec4 b) { return length(a - b); } | |
| 163 | inline vec2 normalize(vec2 v) { float l = length(v); return l > 0.0f ? v / l : vec2(); } | |
| 164 | inline vec3 normalize(vec3 v) { float l = length(v); return l > 0.0f ? v / l : vec3(); } | |
| 165 | inline vec4 normalize(vec4 v) { float l = length(v); return l > 0.0f ? v / l : vec4(); } | |
| 166 | ||
| 138 | 167 | // -- Scalar builtins. float-only, because the .inc writes GLSL literals which |
| 139 | 168 | // are DOUBLE in C++, and letting one of these promote would compute a |
| 140 | 169 | // 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); } |
| 144 | 173 | inline float min(float a, float b) { return a < b ? a : b; } |
| 145 | 174 | inline float max(float a, float b) { return a > b ? a : b; } |
| 146 | 175 | inline float clamp(float v, float lo, float hi) { return v < lo ? lo : (v > hi ? hi : v); } |
| 176 | inline 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. | |
| 182 | inline float sqrt(float x) { return std::sqrt(x); } | |
| 183 | inline float pow(float a, float b) { return std::pow(a, b); } | |
| 184 | inline float exp(float x) { return std::exp(x); } | |
| 147 | 185 | |
| 148 | 186 | // The algorithms themselves. Same text the shader compiles, and in the same |
| 149 | 187 | // order the emitter concatenates them: perlin's lattice gradients ARE the |
| 150 | 188 | // hash, and GLSL has no #include to say so. |
| 151 | 189 | #include "blender_hash.inc" |
| 152 | 190 | #include "blender_perlin.inc" |
| 191 | #include "blender_voronoi.inc" | |
| 153 | 192 | |
| 154 | 193 | } // namespace bnoisegl |
| 155 | 194 | } // namespace godot |
| … | … | @@ -1,26 +1,23 @@ |
| 1 | 1 | #ifndef PROC_NODES_BLENDER_VORONOI_H |
| 2 | 2 | #define PROC_NODES_BLENDER_VORONOI_H |
| 3 | 3 | |
| 4 | // BLENDER'S VORONOI, PORTED FROM BLENDER'S SOURCE. | |
| 4 | // BLENDER'S VORONOI — NOW AN ADAPTER, NOT AN IMPLEMENTATION. | |
| 5 | 5 | // |
| 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. | |
| 11 | 11 | // |
| 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. | |
| 19 | 15 | // |
| 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. | |
| 24 | 21 | |
| 25 | 22 | #include "blender_noise.h" |
| 26 | 23 | |
| … | … | @@ -28,9 +25,6 @@ |
| 28 | 25 | #include <godot_cpp/variant/vector3.hpp> |
| 29 | 26 | #include <godot_cpp/variant/vector4.hpp> |
| 30 | 27 | |
| 31 | #include <cfloat> | |
| 32 | #include <cmath> | |
| 33 | ||
| 34 | 28 | namespace godot { |
| 35 | 29 | namespace bvoronoi { |
| 36 | 30 | |
| … | … | @@ -72,1044 +66,94 @@ struct VoronoiOutput { |
| 72 | 66 | Vector4 position; |
| 73 | 67 | }; |
| 74 | 68 | |
| 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. | |
| 77 | static inline float bmix(float v0, float v1, float x) { return (1.0f - x) * v0 + x * v1; } | |
| 78 | static inline Vector2 bmix(const Vector2 &a, const Vector2 &b, float x) { | |
| 79 | return a * (1.0f - x) + b * x; | |
| 80 | } | |
| 81 | static inline Vector3 bmix(const Vector3 &a, const Vector3 &b, float x) { | |
| 82 | return a * (1.0f - x) + b * x; | |
| 83 | } | |
| 84 | static 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. | |
| 90 | static 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 | ||
| 101 | static inline Vector2 bfloor(const Vector2 &v) { return Vector2(floorf(v.x), floorf(v.y)); } | |
| 102 | static inline Vector3 bfloor(const Vector3 &v) { | |
| 103 | return Vector3(floorf(v.x), floorf(v.y), floorf(v.z)); | |
| 104 | } | |
| 105 | static inline Vector4 bfloor(const Vector4 &v) { | |
| 106 | return Vector4(floorf(v.x), floorf(v.y), floorf(v.z), floorf(v.w)); | |
| 107 | } | |
| 69 | namespace detail { | |
| 70 | inline bnoisegl::vec2 gl(const Vector2 &v) { return bnoisegl::vec2(v.x, v.y); } | |
| 71 | inline bnoisegl::vec3 gl(const Vector3 &v) { return bnoisegl::vec3(v.x, v.y, v.z); } | |
| 72 | inline bnoisegl::vec4 gl(const Vector4 &v) { return bnoisegl::vec4(v.x, v.y, v.z, v.w); } | |
| 73 | inline Vector3 gd3(const bnoisegl::vec3 &v) { return Vector3(v.x, v.y, v.z); } | |
| 74 | inline Vector4 gd4(const bnoisegl::vec4 &v) { return Vector4(v.x, v.y, v.z, v.w); } | |
| 108 | 75 | |
| 109 | static inline IVec2 to_ivec(const Vector2 &v) { return IVec2{ (int32_t)v.x, (int32_t)v.y }; } | |
| 110 | static 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. | |
| 77 | inline 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; | |
| 112 | 91 | } |
| 113 | static 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 }; | |
| 92 | inline 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; | |
| 115 | 98 | } |
| 99 | } // namespace detail | |
| 116 | 100 | |
| 117 | static inline IVec2 iadd(const IVec2 &a, const IVec2 &b) { return IVec2{ a.x + b.x, a.y + b.y }; } | |
| 118 | static inline IVec3 iadd(const IVec3 &a, const IVec3 &b) { | |
| 119 | return IVec3{ a.x + b.x, a.y + b.y, a.z + b.z }; | |
| 101 | inline float voronoi_distance(float a, float b, const VoronoiParams &p) { | |
| 102 | return bnoisegl::bv_voronoi_distance(a, b); | |
| 120 | 103 | } |
| 121 | static 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 }; | |
| 104 | inline 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)); | |
| 123 | 106 | } |
| 124 | ||
| 125 | static inline Vector2 to_vec(const IVec2 &v) { return Vector2((float)v.x, (float)v.y); } | |
| 126 | static inline Vector3 to_vec(const IVec3 &v) { | |
| 127 | return Vector3((float)v.x, (float)v.y, (float)v.z); | |
| 107 | inline 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)); | |
| 128 | 109 | } |
| 129 | static inline Vector4 to_vec(const IVec4 &v) { | |
| 130 | return Vector4((float)v.x, (float)v.y, (float)v.z, (float)v.w); | |
| 110 | inline 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)); | |
| 131 | 112 | } |
| 132 | 113 | |
| 133 | static 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; | |
| 114 | inline float voronoi_n_sphere_radius(const VoronoiParams &p, float c) { | |
| 115 | return bnoisegl::bv_voronoi_n_sphere_radius(detail::gl(p), c); | |
| 135 | 116 | } |
| 136 | static inline float vlen_sq(const Vector4 &v) { return vdot(v, v); } | |
| 137 | static 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 | ||
| 144 | static inline float voronoi_distance(const float a, const float b) { return fabsf(b - a); } | |
| 145 | ||
| 146 | static inline float voronoi_distance(const Vector2 &a, const Vector2 &b, | |
| 147 | const VoronoiParams ¶ms) { | |
| 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; | |
| 117 | inline 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)); | |
| 161 | 119 | } |
| 162 | ||
| 163 | static inline float voronoi_distance(const Vector3 &a, const Vector3 &b, | |
| 164 | const VoronoiParams ¶ms) { | |
| 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; | |
| 120 | inline 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)); | |
| 179 | 122 | } |
| 180 | ||
| 181 | static inline float voronoi_distance(const Vector4 &a, const Vector4 &b, | |
| 182 | const VoronoiParams ¶ms) { | |
| 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; | |
| 123 | inline 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)); | |
| 199 | 125 | } |
| 200 | 126 | |
| 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. | |
| 204 | template <typename T> | |
| 205 | static inline float voronoi_distance_bound(const T &a, const T &b, const VoronoiParams ¶ms) { | |
| 206 | if (params.metric == VORONOI_EUCLIDEAN) { | |
| 207 | return (a - b).length_squared(); | |
| 208 | } | |
| 209 | return voronoi_distance(a, b, params); | |
| 127 | inline 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); | |
| 210 | 129 | } |
| 211 | template <> | |
| 212 | inline float voronoi_distance_bound<Vector4>(const Vector4 &a, const Vector4 &b, | |
| 213 | const VoronoiParams ¶ms) { | |
| 214 | if (params.metric == VORONOI_EUCLIDEAN) { | |
| 215 | return vlen_sq(a - b); | |
| 216 | } | |
| 217 | return voronoi_distance(a, b, params); | |
| 130 | inline 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)); | |
| 218 | 132 | } |
| 219 | ||
| 220 | /* **** 1D Voronoi **** */ | |
| 221 | ||
| 222 | static inline Vector4 voronoi_position(const float coord) { | |
| 223 | return Vector4(0.0f, 0.0f, 0.0f, coord); | |
| 133 | inline 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)); | |
| 224 | 135 | } |
| 225 | ||
| 226 | static inline VoronoiOutput voronoi_f1(const VoronoiParams ¶ms, 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; | |
| 136 | inline 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)); | |
| 250 | 138 | } |
| 251 | 139 | |
| 252 | static inline VoronoiOutput voronoi_smooth_f1(const VoronoiParams ¶ms, 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; | |
| 140 | inline 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)); | |
| 286 | 142 | } |
| 287 | ||
| 288 | static inline VoronoiOutput voronoi_f2(const VoronoiParams ¶ms, 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; | |
| 143 | inline 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)); | |
| 322 | 147 | } |
| 323 | ||
| 324 | static inline float voronoi_distance_to_edge(const VoronoiParams ¶ms, 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); | |
| 148 | inline 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)); | |
| 338 | 152 | } |
| 339 | ||
| 340 | static inline float voronoi_n_sphere_radius(const VoronoiParams ¶ms, 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 | ||
| 380 | static inline Vector4 voronoi_position(const Vector2 &coord) { | |
| 381 | return Vector4(coord.x, coord.y, 0.0f, 0.0f); | |
| 382 | } | |
| 383 | ||
| 384 | static inline VoronoiOutput voronoi_f1(const VoronoiParams ¶ms, 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 | ||
| 413 | static inline VoronoiOutput voronoi_smooth_f1(const VoronoiParams ¶ms, 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 | ||
| 453 | static inline VoronoiOutput voronoi_f2(const VoronoiParams ¶ms, 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 | ||
| 492 | static inline float voronoi_distance_to_edge(const VoronoiParams ¶ms, 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 | ||
| 532 | static inline float voronoi_n_sphere_radius(const VoronoiParams ¶ms, 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 | ||
| 577 | static inline Vector4 voronoi_position(const Vector3 &coord) { | |
| 578 | return Vector4(coord.x, coord.y, coord.z, 0.0f); | |
| 579 | } | |
| 580 | ||
| 581 | static inline VoronoiOutput voronoi_f1(const VoronoiParams ¶ms, 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 | ||
| 613 | static inline VoronoiOutput voronoi_smooth_f1(const VoronoiParams ¶ms, 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 | ||
| 655 | static inline VoronoiOutput voronoi_f2(const VoronoiParams ¶ms, 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 | ||
| 696 | static inline float voronoi_distance_to_edge(const VoronoiParams ¶ms, 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 | ||
| 740 | static inline float voronoi_n_sphere_radius(const VoronoiParams ¶ms, 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 | ||
| 789 | static inline Vector4 voronoi_position(const Vector4 &coord) { return coord; } | |
| 790 | ||
| 791 | static inline VoronoiOutput voronoi_f1(const VoronoiParams ¶ms, 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 | ||
| 826 | static inline VoronoiOutput voronoi_smooth_f1(const VoronoiParams ¶ms, 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 | ||
| 872 | static inline VoronoiOutput voronoi_f2(const VoronoiParams ¶ms, 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 | ||
| 916 | static inline float voronoi_distance_to_edge(const VoronoiParams ¶ms, 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 | ||
| 966 | static inline float voronoi_n_sphere_radius(const VoronoiParams ¶ms, 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. | |
| 1022 | template <typename T> | |
| 1023 | static inline VoronoiOutput fractal_voronoi_x_fx(const VoronoiParams ¶ms, 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 | ||
| 1075 | template <typename T> | |
| 1076 | static inline float fractal_voronoi_distance_to_edge(const VoronoiParams ¶ms, 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; | |
| 153 | inline 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)); | |
| 1113 | 157 | } |
| 1114 | 158 | |
| 1115 | 159 | } // namespace bvoronoi |
| … | … | @@ -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 | ||
| 46 | const int BV_EUCLIDEAN = 0; | |
| 47 | const int BV_MANHATTAN = 1; | |
| 48 | const int BV_CHEBYCHEV = 2; | |
| 49 | const int BV_MINKOWSKI = 3; | |
| 50 | ||
| 51 | // Blender's SHD_VORONOI_* order, which is NOT the order the UI lists them in. | |
| 52 | const int BV_F1 = 0; | |
| 53 | const int BV_F2 = 1; | |
| 54 | const int BV_SMOOTH_F1 = 2; | |
| 55 | const int BV_DISTANCE_TO_EDGE = 3; | |
| 56 | const int BV_N_SPHERE_RADIUS = 4; | |
| 57 | ||
| 58 | struct 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 | ||
| 72 | struct 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. | |
| 80 | BN_FN float bv_bmix(float v0, float v1, float x) { return (1.0f - x) * v0 + x * v1; } | |
| 81 | BN_FN vec2 bv_bmix(vec2 a, vec2 b, float x) { | |
| 82 | return a * (1.0f - x) + b * x; | |
| 83 | } | |
| 84 | BN_FN vec3 bv_bmix(vec3 a, vec3 b, float x) { | |
| 85 | return a * (1.0f - x) + b * x; | |
| 86 | } | |
| 87 | BN_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. | |
| 93 | BN_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 | ||
| 104 | BN_FN vec2 bv_bfloor(vec2 v) { return vec2(floor(v.x), floor(v.y)); } | |
| 105 | BN_FN vec3 bv_bfloor(vec3 v) { | |
| 106 | return vec3(floor(v.x), floor(v.y), floor(v.z)); | |
| 107 | } | |
| 108 | BN_FN vec4 bv_bfloor(vec4 v) { | |
| 109 | return vec4(floor(v.x), floor(v.y), floor(v.z), floor(v.w)); | |
| 110 | } | |
| 111 | ||
| 112 | BN_FN ivec2 bv_to_ivec(vec2 v) { return ivec2( int(v.x), int(v.y) ); } | |
| 113 | BN_FN ivec3 bv_to_ivec(vec3 v) { | |
| 114 | return ivec3( int(v.x), int(v.y), int(v.z) ); | |
| 115 | } | |
| 116 | BN_FN ivec4 bv_to_ivec(vec4 v) { | |
| 117 | return ivec4( int(v.x), int(v.y), int(v.z), int(v.w) ); | |
| 118 | } | |
| 119 | ||
| 120 | BN_FN ivec2 bv_iadd(ivec2 a, ivec2 b) { return ivec2( a.x + b.x, a.y + b.y ); } | |
| 121 | BN_FN ivec3 bv_iadd(ivec3 a, ivec3 b) { | |
| 122 | return ivec3( a.x + b.x, a.y + b.y, a.z + b.z ); | |
| 123 | } | |
| 124 | BN_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 | ||
| 128 | BN_FN vec2 bv_to_vec(ivec2 v) { return vec2(float(v.x), float(v.y)); } | |
| 129 | BN_FN vec3 bv_to_vec(ivec3 v) { | |
| 130 | return vec3(float(v.x), float(v.y), float(v.z)); | |
| 131 | } | |
| 132 | BN_FN vec4 bv_to_vec(ivec4 v) { | |
| 133 | return vec4(float(v.x), float(v.y), float(v.z), float(v.w)); | |
| 134 | } | |
| 135 | ||
| 136 | BN_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 | } | |
| 139 | BN_FN float bv_vlen_sq(vec4 v) { return bv_vdot(v, v); } | |
| 140 | BN_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 | ||
| 147 | BN_FN float bv_voronoi_distance(float a, float b) { return abs(b - a); } | |
| 148 | ||
| 149 | BN_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 | ||
| 166 | BN_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 | ||
| 184 | BN_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. | |
| 214 | BN_FN float bv_vlen_sq_v(vec2 v) { return v.x * v.x + v.y * v.y; } | |
| 215 | BN_FN float bv_vlen_sq_v(vec3 v) { return v.x * v.x + v.y * v.y + v.z * v.z; } | |
| 216 | BN_FN float bv_vlen_sq_v(vec4 v) { return bv_vlen_sq(v); } | |
| 217 | ||
| 218 | BN_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 | } | |
| 222 | BN_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 | } | |
| 226 | BN_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 | ||
| 233 | BN_FN vec4 bv_voronoi_position(float coord) { | |
| 234 | return vec4(0.0f, 0.0f, 0.0f, coord); | |
| 235 | } | |
| 236 | ||
| 237 | BN_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 | ||
| 263 | BN_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 | ||
| 299 | BN_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 | ||
| 335 | BN_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 | ||
| 351 | BN_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 | ||
| 391 | BN_FN vec4 bv_voronoi_position(vec2 coord) { | |
| 392 | return vec4(coord.x, coord.y, 0.0f, 0.0f); | |
| 393 | } | |
| 394 | ||
| 395 | BN_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 | ||
| 424 | BN_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 | ||
| 464 | BN_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 | ||
| 503 | BN_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 | ||
| 542 | BN_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 | ||
| 587 | BN_FN vec4 bv_voronoi_position(vec3 coord) { | |
| 588 | return vec4(coord.x, coord.y, coord.z, 0.0f); | |
| 589 | } | |
| 590 | ||
| 591 | BN_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 | ||
| 623 | BN_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 | ||
| 665 | BN_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 | ||
| 706 | BN_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 | ||
| 749 | BN_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 | ||
| 798 | BN_FN vec4 bv_voronoi_position(vec4 coord) { return coord; } | |
| 799 | ||
| 800 | BN_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 | ||
| 835 | BN_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 | ||
| 881 | BN_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 | ||
| 925 | BN_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 | ||
| 975 | BN_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) \ | |
| 1037 | BN_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 | \ | |
| 1092 | BN_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 | ||
| 1131 | BV_FRACTALS(float, 1) | |
| 1132 | BV_FRACTALS(vec2, 2) | |
| 1133 | BV_FRACTALS(vec3, 3) | |
| 1134 | BV_FRACTALS(vec4, 4) | |
| 1135 | ||
| 1136 | #endif // PN_BLENDER_VORONOI |
| … | … | @@ -93,9 +93,11 @@ using namespace godot::bnoisegl; |
| 93 | 93 | static const char *SUBJECT_FILES[] = { |
| 94 | 94 | "src/gpu/blender_hash_glsl.gen.h", |
| 95 | 95 | "src/gpu/blender_perlin_glsl.gen.h", |
| 96 | "src/gpu/blender_voronoi_glsl.gen.h", | |
| 96 | 97 | }; |
| 97 | static const char *SUBJECT_MARKS[] = { "BLENDER_HASH_GLSL", "BLENDER_PERLIN_GLSL" }; | |
| 98 | static const int SUBJECT_N = 2; | |
| 98 | static const char *SUBJECT_MARKS[] = { "BLENDER_HASH_GLSL", "BLENDER_PERLIN_GLSL", | |
| 99 | "BLENDER_VORONOI_GLSL" }; | |
| 100 | static const int SUBJECT_N = 3; | |
| 99 | 101 | |
| 100 | 102 | static const double TOL_REL = 1e-5; |
| 101 | 103 | static const double TOL_ABS = 1e-6; |
| … | … | @@ -284,7 +286,9 @@ static void init_vulkan(Ctx &c) { |
| 284 | 286 | // actually run. Only the opcode and the dimension pick a code path. |
| 285 | 287 | // ============================================================================ |
| 286 | 288 | |
| 287 | static 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. | |
| 291 | static const uint32_t STRIDE = 24; | |
| 288 | 292 | |
| 289 | 293 | enum Op { |
| 290 | 294 | OP_HASH1 = 1, OP_HASH2, OP_HASH3, OP_HASH4, |
| … | … | @@ -305,6 +309,9 @@ enum Op { |
| 305 | 309 | OP_PERLIN_SELECT, // dim, type, flags |
| 306 | 310 | OP_PERLIN_DISTORTED, // dim, type, flags |
| 307 | 311 | 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 | |
| 308 | 315 | OP__COUNT |
| 309 | 316 | }; |
| 310 | 317 | |
| … | … | @@ -326,6 +333,8 @@ static const OpName OP_NAMES[] = { |
| 326 | 333 | { OP_PERLIN_NOISE, "OP_PERLIN_NOISE" }, { OP_PERLIN_SIGNED, "OP_PERLIN_SIGNED" }, |
| 327 | 334 | { OP_PERLIN_SELECT, "OP_PERLIN_SELECT" }, { OP_PERLIN_DISTORTED, "OP_PERLIN_DISTORTED" }, |
| 328 | 335 | { 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" }, | |
| 329 | 338 | }; |
| 330 | 339 | |
| 331 | 340 | struct Rec { |
| … | … | @@ -341,7 +350,28 @@ static float float_of(uint32_t u) { float f; memcpy(&f, &u, sizeof(f)); return f |
| 341 | 350 | // Word slots, named once so both sides agree by construction. |
| 342 | 351 | enum { W_OP = 0, W_DIM = 1, W_TYPE = 2, W_FLAGS = 3, W_COMP = 4, |
| 343 | 352 | 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. | |
| 360 | static 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 | } | |
| 345 | 375 | |
| 346 | 376 | // --- the CPU half of the dispatcher ----------------------------------------- |
| 347 | 377 | static uint32_t eval_cpu(const uint32_t *w) { |
| … | … | @@ -437,6 +467,31 @@ static uint32_t eval_cpu(const uint32_t *w) { |
| 437 | 467 | : bp_perlin_float3_fractal_distorted(v4, detail, rough, lac, off, gain, dist, type, nrm); |
| 438 | 468 | return bits_of(comp == 0 ? r.x : (comp == 1 ? r.y : r.z)); |
| 439 | 469 | } |
| 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 | } | |
| 440 | 495 | default: |
| 441 | 496 | fprintf(stderr, "FATAL: cpu dispatcher has no arm for op %u\n", op); |
| 442 | 497 | exit(1); |
| … | … | @@ -573,6 +628,52 @@ void main() { |
| 573 | 628 | else { q = bp_perlin_float3_fractal_distorted(v4, detail, rough, lac, off, gain, dist, type, nrm); } |
| 574 | 629 | r = floatBitsToUint(comp == 0u ? q.x : (comp == 1u ? q.y : q.z)); |
| 575 | 630 | } |
| 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 | } | |
| 576 | 677 | out_v.v[idx] = r; |
| 577 | 678 | } |
| 578 | 679 | )DISPATCH"; |
| … | … | @@ -607,6 +708,11 @@ struct B { |
| 607 | 708 | w[W_A] = bits_of(a); w[W_B] = bits_of(b); |
| 608 | 709 | w[W_C] = bits_of(c); w[W_D] = bits_of(d); return *this; |
| 609 | 710 | } |
| 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 | } | |
| 610 | 716 | B &frac(float detail, float rough, float lac, float off, float gain, float dist) { |
| 611 | 717 | w[W_DETAIL] = bits_of(detail); w[W_ROUGH] = bits_of(rough); |
| 612 | 718 | w[W_LAC] = bits_of(lac); w[W_OFF] = bits_of(off); |
| … | … | @@ -755,6 +861,88 @@ static void build_cases() { |
| 755 | 861 | } |
| 756 | 862 | } |
| 757 | 863 | |
| 864 | static 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 | ||
| 758 | 946 | static long peak_rss_kb() { |
| 759 | 947 | std::ifstream f("/proc/self/status"); |
| 760 | 948 | std::string line; |
| … | … | @@ -790,13 +978,15 @@ int main(int argc, char **argv) { |
| 790 | 978 | subject_desc += (i ? "\n " : "") + file; |
| 791 | 979 | } |
| 792 | 980 | 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"); | |
| 796 | 985 | return 1; |
| 797 | 986 | } |
| 798 | 987 | |
| 799 | 988 | build_cases(); |
| 989 | build_voronoi_cases(); | |
| 800 | 990 | if (g_cases.empty()) { fprintf(stderr, "FATAL: no cases\n"); return 1; } |
| 801 | 991 | |
| 802 | 992 | int n_exact = 0; |