OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / shared / blender_voronoi.inc

1137 lines · inc ref:
// ============================================================================
// BLENDER'S VORONOI -- ONE TEXT, COMPILED AS BOTH GLSL AND C++.
//
// Ported from `source/blender/blenlib/intern/noise.cc` (the Voronoi section)
// in the Blender tree at commit 61a36e08, BLENDER_VERSION 502, and kept
// structurally identical to it so the two diff by eye when Blender moves.
//
// DEPENDS ON blender_hash.inc and does not include it -- GLSL has no
// #include, so the emitter concatenates hash-then-voronoi and the C++ shim
// includes them in that order.
//
// ⛔ VORONOI USES THE PCG INTEGER HASHES, NOT THE JENKINS FLOAT HASH -- except
// in 1D, which uses hash_float_to_float. That is read off Blender's source,
// not assumed: one hash per file would be the right shape with unrelated
// numbers, which is the failure this whole layer exists to prevent.
//
// This file was TRANSFORMED from blender_voronoi.h, not retyped. 1118 lines of
// working, gated arithmetic is exactly the wrong thing to copy by hand, and
// the equivalence run against the pre-conversion header is what says the
// transformation was faithful.
//
// ---------------------------------------------------------------------------
// THE SUBSET
//
//   NO TEMPLATES. `fractal_voronoi_x_fx` and `fractal_voronoi_distance_to_edge`
//   are template <typename T>; the body is written once and the preprocessor
//   stamps it out per dimension, as blender_perlin.inc does.
//   `voronoi_distance_bound` was a template with a Vector4 specialisation and
//   is three overloads here -- GLSL resolves on argument type just as well.
//
//   NO DEFAULT MEMBER INITIALISERS. GLSL structs cannot have them, so
//   BvParams is filled by the caller. Every field, every time: a struct that
//   used to default `randomness = 1.0f` and now defaults to garbage is a
//   silent difference, which is why the adapter sets all eleven.
//
//   NO godot METHODS. `(a - b).length_squared()` became bv_vlen_sq_v.
// ============================================================================

#ifndef PN_BLENDER_VORONOI
#define PN_BLENDER_VORONOI

#ifndef BN_FN
#define BN_FN
#endif

const int BV_EUCLIDEAN = 0;
const int BV_MANHATTAN = 1;
const int BV_CHEBYCHEV = 2;
const int BV_MINKOWSKI = 3;

// Blender's SHD_VORONOI_* order, which is NOT the order the UI lists them in.
const int BV_F1 = 0;
const int BV_F2 = 1;
const int BV_SMOOTH_F1 = 2;
const int BV_DISTANCE_TO_EDGE = 3;
const int BV_N_SPHERE_RADIUS = 4;

struct BvParams {
	float scale;
	float detail;
	float roughness;
	float lacunarity;
	float smoothness;
	float exponent;
	float randomness;
	float max_distance;
	bool normalize;
	int feature;
	int metric;
};

struct BvOutput {
	float distance;
	vec3 color;
	vec4 position;
};

// `mix` is Blender's: (1 - x) * v0 + x * v1, NOT v0 + x * (v1 - v0). They are
// the same in algebra and not in floating point.
BN_FN float bv_bmix(float v0, float v1, float x) { return (1.0f - x) * v0 + x * v1; }
BN_FN vec2 bv_bmix(vec2 a, vec2 b, float x) {
	return a * (1.0f - x) + b * x;
}
BN_FN vec3 bv_bmix(vec3 a, vec3 b, float x) {
	return a * (1.0f - x) + b * x;
}
BN_FN vec4 bv_bmix(vec4 a, vec4 b, float x) {
	return a * (1.0f - x) + b * x;
}

// BLI's smoothstep, which clamps by BRANCHING on the edges rather than by
// clamping t -- same values, and written the same way to keep the diff honest.
BN_FN float bv_bsmoothstep(float edge0, float edge1, float x) {
	if (x < edge0) {
		return 0.0f;
	}
	if (x >= edge1) {
		return 1.0f;
	}
	float t = (x - edge0) / (edge1 - edge0);
	return (3.0f - 2.0f * t) * (t * t);
}

BN_FN vec2 bv_bfloor(vec2 v) { return vec2(floor(v.x), floor(v.y)); }
BN_FN vec3 bv_bfloor(vec3 v) {
	return vec3(floor(v.x), floor(v.y), floor(v.z));
}
BN_FN vec4 bv_bfloor(vec4 v) {
	return vec4(floor(v.x), floor(v.y), floor(v.z), floor(v.w));
}

BN_FN ivec2 bv_to_ivec(vec2 v) { return ivec2( int(v.x), int(v.y) ); }
BN_FN ivec3 bv_to_ivec(vec3 v) {
	return ivec3( int(v.x), int(v.y), int(v.z) );
}
BN_FN ivec4 bv_to_ivec(vec4 v) {
	return ivec4( int(v.x), int(v.y), int(v.z), int(v.w) );
}

BN_FN ivec2 bv_iadd(ivec2 a, ivec2 b) { return ivec2( a.x + b.x, a.y + b.y ); }
BN_FN ivec3 bv_iadd(ivec3 a, ivec3 b) {
	return ivec3( a.x + b.x, a.y + b.y, a.z + b.z );
}
BN_FN ivec4 bv_iadd(ivec4 a, ivec4 b) {
	return ivec4( a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w );
}

BN_FN vec2 bv_to_vec(ivec2 v) { return vec2(float(v.x), float(v.y)); }
BN_FN vec3 bv_to_vec(ivec3 v) {
	return vec3(float(v.x), float(v.y), float(v.z));
}
BN_FN vec4 bv_to_vec(ivec4 v) {
	return vec4(float(v.x), float(v.y), float(v.z), float(v.w));
}

BN_FN float bv_vdot(vec4 a, vec4 b) {
	return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}
BN_FN float bv_vlen_sq(vec4 v) { return bv_vdot(v, v); }
BN_FN vec4 bv_vnorm(vec4 v) {
	float l = sqrt(bv_vlen_sq(v));
	return l > 0.0f ? v / l : vec4(0.0f);
}

/* ***** Distances ***** */

BN_FN float bv_voronoi_distance(float a, float b) { return abs(b - a); }

BN_FN float bv_voronoi_distance(vec2 a, vec2 b,
		BvParams params) {
	switch (params.metric) {
		case BV_EUCLIDEAN:
			return distance(a, b);
		case BV_MANHATTAN:
			return abs(a.x - b.x) + abs(a.y - b.y);
		case BV_CHEBYCHEV:
			return max(abs(a.x - b.x), abs(a.y - b.y));
		case BV_MINKOWSKI:
			return pow(pow(abs(a.x - b.x), params.exponent) +
							pow(abs(a.y - b.y), params.exponent),
					1.0f / params.exponent);
	}
	return 0.0f;
}

BN_FN float bv_voronoi_distance(vec3 a, vec3 b,
		BvParams params) {
	switch (params.metric) {
		case BV_EUCLIDEAN:
			return distance(a, b);
		case BV_MANHATTAN:
			return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z);
		case BV_CHEBYCHEV:
			return max(abs(a.x - b.x), max(abs(a.y - b.y), abs(a.z - b.z)));
		case BV_MINKOWSKI:
			return pow(pow(abs(a.x - b.x), params.exponent) +
							pow(abs(a.y - b.y), params.exponent) +
							pow(abs(a.z - b.z), params.exponent),
					1.0f / params.exponent);
	}
	return 0.0f;
}

BN_FN float bv_voronoi_distance(vec4 a, vec4 b,
		BvParams params) {
	switch (params.metric) {
		case BV_EUCLIDEAN:
			return sqrt(bv_vlen_sq(a - b));
		case BV_MANHATTAN:
			return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z) + abs(a.w - b.w);
		case BV_CHEBYCHEV:
			return max(max(abs(a.x - b.x), abs(a.y - b.y)),
					max(abs(a.z - b.z), abs(a.w - b.w)));
		case BV_MINKOWSKI:
			return pow(pow(abs(a.x - b.x), params.exponent) +
							pow(abs(a.y - b.y), params.exponent) +
							pow(abs(a.z - b.z), params.exponent) +
							pow(abs(a.w - b.w), params.exponent),
					1.0f / params.exponent);
	}
	return 0.0f;
}

// Cheaper comparison that does not change WHICH point is closest. Euclidean
// compares squared, so f1/f2 recompute the real distance after the search --
// which is why 3D's octave.distance is a second call and 1D's is not.
// Cheaper comparison that does not change WHICH point is closest. Euclidean
// compares squared, so f1/f2 recompute the real distance after the search --
// which is why 3D's octave.distance is a second call and 1D's is not.
//
// A template with a Vector4 specialisation in the C++; here it is three
// overloads, because GLSL resolves on the argument type just as well and the
// only thing the specialisation changed was which length-squared to call.
BN_FN float bv_vlen_sq_v(vec2 v) { return v.x * v.x + v.y * v.y; }
BN_FN float bv_vlen_sq_v(vec3 v) { return v.x * v.x + v.y * v.y + v.z * v.z; }
BN_FN float bv_vlen_sq_v(vec4 v) { return bv_vlen_sq(v); }

BN_FN float bv_voronoi_distance_bound(vec2 a, vec2 b, BvParams params) {
	if (params.metric == BV_EUCLIDEAN) { return bv_vlen_sq_v(a - b); }
	return bv_voronoi_distance(a, b, params);
}
BN_FN float bv_voronoi_distance_bound(vec3 a, vec3 b, BvParams params) {
	if (params.metric == BV_EUCLIDEAN) { return bv_vlen_sq_v(a - b); }
	return bv_voronoi_distance(a, b, params);
}
BN_FN float bv_voronoi_distance_bound(vec4 a, vec4 b, BvParams params) {
	if (params.metric == BV_EUCLIDEAN) { return bv_vlen_sq(a - b); }
	return bv_voronoi_distance(a, b, params);
}

/* **** 1D Voronoi **** */

BN_FN vec4 bv_voronoi_position(float coord) {
	return vec4(0.0f, 0.0f, 0.0f, coord);
}

BN_FN BvOutput bv_voronoi_f1(BvParams params, float coord) {
	float cellPosition = floor(coord);
	float localPosition = coord - cellPosition;

	float minDistance = 3.402823466e+38f;
	float targetOffset = 0.0f;
	float targetPosition = 0.0f;
	for (int i = -1; i <= 1; i++) {
		float cellOffset = float(i);
		float pointPosition = cellOffset +
				bn_hash_float_to_float(cellPosition + cellOffset) * params.randomness;
		float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition);
		if (distanceToPoint < minDistance) {
			targetOffset = cellOffset;
			minDistance = distanceToPoint;
			targetPosition = pointPosition;
		}
	}

	BvOutput octave;
	octave.distance = minDistance;
	octave.color = bn_hash_float_to_float3(cellPosition + targetOffset);
	octave.position = bv_voronoi_position(targetPosition + cellPosition);
	return octave;
}

BN_FN BvOutput bv_voronoi_smooth_f1(BvParams params, float coord,
		bool calc_color) {
	float cellPosition = floor(coord);
	float localPosition = coord - cellPosition;

	float smoothDistance = 0.0f;
	float smoothPosition = 0.0f;
	vec3 smoothColor;
	float h = -1.0f;
	for (int i = -2; i <= 2; i++) {
		float cellOffset = float(i);
		float pointPosition = cellOffset +
				bn_hash_float_to_float(cellPosition + cellOffset) * params.randomness;
		float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition);
		h = h == -1.0f ? 1.0f
					   : bv_bsmoothstep(0.0f, 1.0f,
								 0.5f + 0.5f * (smoothDistance - distanceToPoint) /
												params.smoothness);
		float correctionFactor = params.smoothness * h * (1.0f - h);
		smoothDistance = bv_bmix(smoothDistance, distanceToPoint, h) - correctionFactor;
		correctionFactor /= 1.0f + 3.0f * params.smoothness;
		vec3 cellColor = bn_hash_float_to_float3(cellPosition + cellOffset);
		if (calc_color) {
			smoothColor = bv_bmix(smoothColor, cellColor, h) - vec3(correctionFactor,
											correctionFactor, correctionFactor);
		}
		smoothPosition = bv_bmix(smoothPosition, pointPosition, h) - correctionFactor;
	}

	BvOutput octave;
	octave.distance = smoothDistance;
	octave.color = smoothColor;
	octave.position = bv_voronoi_position(cellPosition + smoothPosition);
	return octave;
}

BN_FN BvOutput bv_voronoi_f2(BvParams params, float coord) {
	float cellPosition = floor(coord);
	float localPosition = coord - cellPosition;

	float distanceF1 = 3.402823466e+38f;
	float distanceF2 = 3.402823466e+38f;
	float offsetF1 = 0.0f;
	float positionF1 = 0.0f;
	float offsetF2 = 0.0f;
	float positionF2 = 0.0f;
	for (int i = -1; i <= 1; i++) {
		float cellOffset = float(i);
		float pointPosition = cellOffset +
				bn_hash_float_to_float(cellPosition + cellOffset) * params.randomness;
		float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition);
		if (distanceToPoint < distanceF1) {
			distanceF2 = distanceF1;
			distanceF1 = distanceToPoint;
			offsetF2 = offsetF1;
			offsetF1 = cellOffset;
			positionF2 = positionF1;
			positionF1 = pointPosition;
		} else if (distanceToPoint < distanceF2) {
			distanceF2 = distanceToPoint;
			offsetF2 = cellOffset;
			positionF2 = pointPosition;
		}
	}

	BvOutput octave;
	octave.distance = distanceF2;
	octave.color = bn_hash_float_to_float3(cellPosition + offsetF2);
	octave.position = bv_voronoi_position(positionF2 + cellPosition);
	return octave;
}

BN_FN float bv_voronoi_distance_to_edge(BvParams params, float coord) {
	float cellPosition = floor(coord);
	float localPosition = coord - cellPosition;

	float midPointPosition = bn_hash_float_to_float(cellPosition) * params.randomness;
	float leftPointPosition = -1.0f +
			bn_hash_float_to_float(cellPosition - 1.0f) * params.randomness;
	float rightPointPosition = 1.0f +
			bn_hash_float_to_float(cellPosition + 1.0f) * params.randomness;
	float distanceToMidLeft = abs((midPointPosition + leftPointPosition) / 2.0f - localPosition);
	float distanceToMidRight = abs((midPointPosition + rightPointPosition) / 2.0f -
			localPosition);

	return min(distanceToMidLeft, distanceToMidRight);
}

BN_FN float bv_voronoi_n_sphere_radius(BvParams params, float coord) {
	float cellPosition = floor(coord);
	float localPosition = coord - cellPosition;

	float closestPoint = 0.0f;
	float closestPointOffset = 0.0f;
	float minDistance = 3.402823466e+38f;
	for (int i = -1; i <= 1; i++) {
		float cellOffset = float(i);
		float pointPosition = cellOffset +
				bn_hash_float_to_float(cellPosition + cellOffset) * params.randomness;
		float distanceToPoint = abs(pointPosition - localPosition);
		if (distanceToPoint < minDistance) {
			minDistance = distanceToPoint;
			closestPoint = pointPosition;
			closestPointOffset = cellOffset;
		}
	}

	minDistance = 3.402823466e+38f;
	float closestPointToClosestPoint = 0.0f;
	for (int i = -1; i <= 1; i++) {
		if (i == 0) {
			continue;
		}
		float cellOffset = float(i) + closestPointOffset;
		float pointPosition = cellOffset +
				bn_hash_float_to_float(cellPosition + cellOffset) * params.randomness;
		float distanceToPoint = abs(closestPoint - pointPosition);
		if (distanceToPoint < minDistance) {
			minDistance = distanceToPoint;
			closestPointToClosestPoint = pointPosition;
		}
	}

	return abs(closestPointToClosestPoint - closestPoint) / 2.0f;
}

/* **** 2D Voronoi **** */

BN_FN vec4 bv_voronoi_position(vec2 coord) {
	return vec4(coord.x, coord.y, 0.0f, 0.0f);
}

BN_FN BvOutput bv_voronoi_f1(BvParams params, vec2 coord) {
	vec2 cellPosition_f = bv_bfloor(coord);
	vec2 localPosition = coord - cellPosition_f;
	ivec2 cellPosition = bv_to_ivec(cellPosition_f);

	float minDistance = 3.402823466e+38f;
	ivec2 targetOffset;
	vec2 targetPosition;
	for (int j = -1; j <= 1; j++) {
		for (int i = -1; i <= 1; i++) {
			ivec2 cellOffset = ivec2( i, j );
			vec2 pointPosition = bv_to_vec(cellOffset) +
					bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness;
			float distanceToPoint = bv_voronoi_distance_bound(pointPosition, localPosition, params);
			if (distanceToPoint < minDistance) {
				targetOffset = cellOffset;
				minDistance = distanceToPoint;
				targetPosition = pointPosition;
			}
		}
	}

	BvOutput octave;
	octave.distance = bv_voronoi_distance(targetPosition, localPosition, params);
	octave.color = bn_hash_int_to_float3(bv_iadd(cellPosition, targetOffset));
	octave.position = bv_voronoi_position(targetPosition + cellPosition_f);
	return octave;
}

BN_FN BvOutput bv_voronoi_smooth_f1(BvParams params, vec2 coord,
		bool calc_color) {
	vec2 cellPosition_f = bv_bfloor(coord);
	vec2 localPosition = coord - cellPosition_f;
	ivec2 cellPosition = bv_to_ivec(cellPosition_f);

	float smoothDistance = 0.0f;
	vec3 smoothColor;
	vec2 smoothPosition;
	float h = -1.0f;
	for (int j = -2; j <= 2; j++) {
		for (int i = -2; i <= 2; i++) {
			ivec2 cellOffset = ivec2( i, j );
			vec2 pointPosition = bv_to_vec(cellOffset) +
					bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness;
			float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition, params);
			h = h == -1.0f ? 1.0f
						   : bv_bsmoothstep(0.0f, 1.0f,
									 0.5f + 0.5f * (smoothDistance - distanceToPoint) /
													params.smoothness);
			float correctionFactor = params.smoothness * h * (1.0f - h);
			smoothDistance = bv_bmix(smoothDistance, distanceToPoint, h) - correctionFactor;
			correctionFactor /= 1.0f + 3.0f * params.smoothness;
			if (calc_color) {
				vec3 cellColor = bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset));
				smoothColor = bv_bmix(smoothColor, cellColor, h) - vec3(correctionFactor,
												correctionFactor, correctionFactor);
			}
			smoothPosition = bv_bmix(smoothPosition, pointPosition, h) - vec2(correctionFactor,
												  correctionFactor);
		}
	}

	BvOutput octave;
	octave.distance = smoothDistance;
	octave.color = smoothColor;
	octave.position = bv_voronoi_position(cellPosition_f + smoothPosition);
	return octave;
}

BN_FN BvOutput bv_voronoi_f2(BvParams params, vec2 coord) {
	vec2 cellPosition_f = bv_bfloor(coord);
	vec2 localPosition = coord - cellPosition_f;
	ivec2 cellPosition = bv_to_ivec(cellPosition_f);

	float distanceF1 = 3.402823466e+38f;
	float distanceF2 = 3.402823466e+38f;
	ivec2 offsetF1;
	vec2 positionF1;
	ivec2 offsetF2;
	vec2 positionF2;
	for (int j = -1; j <= 1; j++) {
		for (int i = -1; i <= 1; i++) {
			ivec2 cellOffset = ivec2( i, j );
			vec2 pointPosition = bv_to_vec(cellOffset) +
					bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness;
			float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition, params);
			if (distanceToPoint < distanceF1) {
				distanceF2 = distanceF1;
				distanceF1 = distanceToPoint;
				offsetF2 = offsetF1;
				offsetF1 = cellOffset;
				positionF2 = positionF1;
				positionF1 = pointPosition;
			} else if (distanceToPoint < distanceF2) {
				distanceF2 = distanceToPoint;
				offsetF2 = cellOffset;
				positionF2 = pointPosition;
			}
		}
	}

	BvOutput octave;
	octave.distance = distanceF2;
	octave.color = bn_hash_int_to_float3(bv_iadd(cellPosition, offsetF2));
	octave.position = bv_voronoi_position(positionF2 + cellPosition_f);
	return octave;
}

BN_FN float bv_voronoi_distance_to_edge(BvParams params, vec2 coord) {
	vec2 cellPosition_f = bv_bfloor(coord);
	vec2 localPosition = coord - cellPosition_f;
	ivec2 cellPosition = bv_to_ivec(cellPosition_f);

	vec2 vectorToClosest;
	float minDistance = 3.402823466e+38f;
	for (int j = -1; j <= 1; j++) {
		for (int i = -1; i <= 1; i++) {
			ivec2 cellOffset = ivec2( i, j );
			vec2 vectorToPoint = bv_to_vec(cellOffset) +
					bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness -
					localPosition;
			float distanceToPoint = dot(vectorToPoint, vectorToPoint);
			if (distanceToPoint < minDistance) {
				minDistance = distanceToPoint;
				vectorToClosest = vectorToPoint;
			}
		}
	}

	minDistance = 3.402823466e+38f;
	for (int j = -1; j <= 1; j++) {
		for (int i = -1; i <= 1; i++) {
			ivec2 cellOffset = ivec2( i, j );
			vec2 vectorToPoint = bv_to_vec(cellOffset) +
					bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness -
					localPosition;
			vec2 perpendicularToEdge = vectorToPoint - vectorToClosest;
			if (dot(perpendicularToEdge, perpendicularToEdge) > 0.0001f) {
				float distanceToEdge = dot((vectorToClosest + vectorToPoint) / 2.0f, normalize(perpendicularToEdge));
				minDistance = min(minDistance, distanceToEdge);
			}
		}
	}

	return minDistance;
}

BN_FN float bv_voronoi_n_sphere_radius(BvParams params, vec2 coord) {
	vec2 cellPosition_f = bv_bfloor(coord);
	vec2 localPosition = coord - cellPosition_f;
	ivec2 cellPosition = bv_to_ivec(cellPosition_f);

	vec2 closestPoint;
	ivec2 closestPointOffset;
	float minDistanceSq = 3.402823466e+38f;
	for (int j = -1; j <= 1; j++) {
		for (int i = -1; i <= 1; i++) {
			ivec2 cellOffset = ivec2( i, j );
			vec2 pointPosition = bv_to_vec(cellOffset) +
					bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness;
			float distanceToPointSq = bv_vlen_sq_v(pointPosition - localPosition);
			if (distanceToPointSq < minDistanceSq) {
				minDistanceSq = distanceToPointSq;
				closestPoint = pointPosition;
				closestPointOffset = cellOffset;
			}
		}
	}

	minDistanceSq = 3.402823466e+38f;
	vec2 closestPointToClosestPoint;
	for (int j = -1; j <= 1; j++) {
		for (int i = -1; i <= 1; i++) {
			if (i == 0 && j == 0) {
				continue;
			}
			ivec2 cellOffset = bv_iadd(ivec2( i, j ), closestPointOffset);
			vec2 pointPosition = bv_to_vec(cellOffset) +
					bn_hash_int_to_float2(bv_iadd(cellPosition, cellOffset)) * params.randomness;
			float distanceToPointSq = bv_vlen_sq_v(closestPoint - pointPosition);
			if (distanceToPointSq < minDistanceSq) {
				minDistanceSq = distanceToPointSq;
				closestPointToClosestPoint = pointPosition;
			}
		}
	}

	return distance(closestPointToClosestPoint, closestPoint) / 2.0f;
}

/* **** 3D Voronoi **** */

BN_FN vec4 bv_voronoi_position(vec3 coord) {
	return vec4(coord.x, coord.y, coord.z, 0.0f);
}

BN_FN BvOutput bv_voronoi_f1(BvParams params, vec3 coord) {
	vec3 cellPosition_f = bv_bfloor(coord);
	vec3 localPosition = coord - cellPosition_f;
	ivec3 cellPosition = bv_to_ivec(cellPosition_f);

	float minDistance = 3.402823466e+38f;
	ivec3 targetOffset;
	vec3 targetPosition;
	for (int k = -1; k <= 1; k++) {
		for (int j = -1; j <= 1; j++) {
			for (int i = -1; i <= 1; i++) {
				ivec3 cellOffset = ivec3( i, j, k );
				vec3 pointPosition = bv_to_vec(cellOffset) +
						bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness;
				float distanceToPoint = bv_voronoi_distance_bound(pointPosition, localPosition,
						params);
				if (distanceToPoint < minDistance) {
					targetOffset = cellOffset;
					minDistance = distanceToPoint;
					targetPosition = pointPosition;
				}
			}
		}
	}

	BvOutput octave;
	octave.distance = bv_voronoi_distance(targetPosition, localPosition, params);
	octave.color = bn_hash_int_to_float3(bv_iadd(cellPosition, targetOffset));
	octave.position = bv_voronoi_position(targetPosition + cellPosition_f);
	return octave;
}

BN_FN BvOutput bv_voronoi_smooth_f1(BvParams params, vec3 coord,
		bool calc_color) {
	vec3 cellPosition_f = bv_bfloor(coord);
	vec3 localPosition = coord - cellPosition_f;
	ivec3 cellPosition = bv_to_ivec(cellPosition_f);

	float smoothDistance = 0.0f;
	vec3 smoothColor;
	vec3 smoothPosition;
	float h = -1.0f;
	for (int k = -2; k <= 2; k++) {
		for (int j = -2; j <= 2; j++) {
			for (int i = -2; i <= 2; i++) {
				ivec3 cellOffset = ivec3( i, j, k );
				vec3 pointPosition = bv_to_vec(cellOffset) +
						bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness;
				float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition, params);
				h = h == -1.0f ? 1.0f
							   : bv_bsmoothstep(0.0f, 1.0f,
										 0.5f + 0.5f * (smoothDistance - distanceToPoint) /
														params.smoothness);
				float correctionFactor = params.smoothness * h * (1.0f - h);
				smoothDistance = bv_bmix(smoothDistance, distanceToPoint, h) - correctionFactor;
				correctionFactor /= 1.0f + 3.0f * params.smoothness;
				if (calc_color) {
					vec3 cellColor = bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset));
					smoothColor = bv_bmix(smoothColor, cellColor, h) - vec3(correctionFactor,
													correctionFactor, correctionFactor);
				}
				smoothPosition = bv_bmix(smoothPosition, pointPosition, h) -
						vec3(correctionFactor, correctionFactor, correctionFactor);
			}
		}
	}

	BvOutput octave;
	octave.distance = smoothDistance;
	octave.color = smoothColor;
	octave.position = bv_voronoi_position(cellPosition_f + smoothPosition);
	return octave;
}

BN_FN BvOutput bv_voronoi_f2(BvParams params, vec3 coord) {
	vec3 cellPosition_f = bv_bfloor(coord);
	vec3 localPosition = coord - cellPosition_f;
	ivec3 cellPosition = bv_to_ivec(cellPosition_f);

	float distanceF1 = 3.402823466e+38f;
	float distanceF2 = 3.402823466e+38f;
	ivec3 offsetF1;
	vec3 positionF1;
	ivec3 offsetF2;
	vec3 positionF2;
	for (int k = -1; k <= 1; k++) {
		for (int j = -1; j <= 1; j++) {
			for (int i = -1; i <= 1; i++) {
				ivec3 cellOffset = ivec3( i, j, k );
				vec3 pointPosition = bv_to_vec(cellOffset) +
						bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness;
				float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition, params);
				if (distanceToPoint < distanceF1) {
					distanceF2 = distanceF1;
					distanceF1 = distanceToPoint;
					offsetF2 = offsetF1;
					offsetF1 = cellOffset;
					positionF2 = positionF1;
					positionF1 = pointPosition;
				} else if (distanceToPoint < distanceF2) {
					distanceF2 = distanceToPoint;
					offsetF2 = cellOffset;
					positionF2 = pointPosition;
				}
			}
		}
	}

	BvOutput octave;
	octave.distance = distanceF2;
	octave.color = bn_hash_int_to_float3(bv_iadd(cellPosition, offsetF2));
	octave.position = bv_voronoi_position(positionF2 + cellPosition_f);
	return octave;
}

BN_FN float bv_voronoi_distance_to_edge(BvParams params, vec3 coord) {
	vec3 cellPosition_f = bv_bfloor(coord);
	vec3 localPosition = coord - cellPosition_f;
	ivec3 cellPosition = bv_to_ivec(cellPosition_f);

	vec3 vectorToClosest;
	float minDistance = 3.402823466e+38f;
	for (int k = -1; k <= 1; k++) {
		for (int j = -1; j <= 1; j++) {
			for (int i = -1; i <= 1; i++) {
				ivec3 cellOffset = ivec3( i, j, k );
				vec3 vectorToPoint = bv_to_vec(cellOffset) +
						bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness -
						localPosition;
				float distanceToPoint = dot(vectorToPoint, vectorToPoint);
				if (distanceToPoint < minDistance) {
					minDistance = distanceToPoint;
					vectorToClosest = vectorToPoint;
				}
			}
		}
	}

	minDistance = 3.402823466e+38f;
	for (int k = -1; k <= 1; k++) {
		for (int j = -1; j <= 1; j++) {
			for (int i = -1; i <= 1; i++) {
				ivec3 cellOffset = ivec3( i, j, k );
				vec3 vectorToPoint = bv_to_vec(cellOffset) +
						bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness -
						localPosition;
				vec3 perpendicularToEdge = vectorToPoint - vectorToClosest;
				if (dot(perpendicularToEdge, perpendicularToEdge) > 0.0001f) {
					float distanceToEdge = dot((vectorToClosest + vectorToPoint) / 2.0f, normalize(perpendicularToEdge));
					minDistance = min(minDistance, distanceToEdge);
				}
			}
		}
	}

	return minDistance;
}

BN_FN float bv_voronoi_n_sphere_radius(BvParams params, vec3 coord) {
	vec3 cellPosition_f = bv_bfloor(coord);
	vec3 localPosition = coord - cellPosition_f;
	ivec3 cellPosition = bv_to_ivec(cellPosition_f);

	vec3 closestPoint;
	ivec3 closestPointOffset;
	float minDistanceSq = 3.402823466e+38f;
	for (int k = -1; k <= 1; k++) {
		for (int j = -1; j <= 1; j++) {
			for (int i = -1; i <= 1; i++) {
				ivec3 cellOffset = ivec3( i, j, k );
				vec3 pointPosition = bv_to_vec(cellOffset) +
						bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness;
				float distanceToPointSq = bv_vlen_sq_v(pointPosition - localPosition);
				if (distanceToPointSq < minDistanceSq) {
					minDistanceSq = distanceToPointSq;
					closestPoint = pointPosition;
					closestPointOffset = cellOffset;
				}
			}
		}
	}

	minDistanceSq = 3.402823466e+38f;
	vec3 closestPointToClosestPoint;
	for (int k = -1; k <= 1; k++) {
		for (int j = -1; j <= 1; j++) {
			for (int i = -1; i <= 1; i++) {
				if (i == 0 && j == 0 && k == 0) {
					continue;
				}
				ivec3 cellOffset = bv_iadd(ivec3( i, j, k ), closestPointOffset);
				vec3 pointPosition = bv_to_vec(cellOffset) +
						bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset)) * params.randomness;
				float distanceToPointSq = bv_vlen_sq_v(closestPoint - pointPosition);
				if (distanceToPointSq < minDistanceSq) {
					minDistanceSq = distanceToPointSq;
					closestPointToClosestPoint = pointPosition;
				}
			}
		}
	}

	return distance(closestPointToClosestPoint, closestPoint) / 2.0f;
}

/* **** 4D Voronoi **** */

BN_FN vec4 bv_voronoi_position(vec4 coord) { return coord; }

BN_FN BvOutput bv_voronoi_f1(BvParams params, vec4 coord) {
	vec4 cellPosition_f = bv_bfloor(coord);
	vec4 localPosition = coord - cellPosition_f;
	ivec4 cellPosition = bv_to_ivec(cellPosition_f);

	float minDistance = 3.402823466e+38f;
	ivec4 targetOffset;
	vec4 targetPosition;
	for (int u = -1; u <= 1; u++) {
		for (int k = -1; k <= 1; k++) {
			for (int j = -1; j <= 1; j++) {
				for (int i = -1; i <= 1; i++) {
					ivec4 cellOffset = ivec4( i, j, k, u );
					vec4 pointPosition = bv_to_vec(cellOffset) +
							bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
									params.randomness;
					float distanceToPoint = bv_voronoi_distance_bound(pointPosition, localPosition,
							params);
					if (distanceToPoint < minDistance) {
						targetOffset = cellOffset;
						minDistance = distanceToPoint;
						targetPosition = pointPosition;
					}
				}
			}
		}
	}

	BvOutput octave;
	octave.distance = bv_voronoi_distance(targetPosition, localPosition, params);
	octave.color = bn_hash_int_to_float3(bv_iadd(cellPosition, targetOffset));
	octave.position = bv_voronoi_position(targetPosition + cellPosition_f);
	return octave;
}

BN_FN BvOutput bv_voronoi_smooth_f1(BvParams params, vec4 coord,
		bool calc_color) {
	vec4 cellPosition_f = bv_bfloor(coord);
	vec4 localPosition = coord - cellPosition_f;
	ivec4 cellPosition = bv_to_ivec(cellPosition_f);

	float smoothDistance = 0.0f;
	vec3 smoothColor;
	vec4 smoothPosition;
	float h = -1.0f;
	for (int u = -2; u <= 2; u++) {
		for (int k = -2; k <= 2; k++) {
			for (int j = -2; j <= 2; j++) {
				for (int i = -2; i <= 2; i++) {
					ivec4 cellOffset = ivec4( i, j, k, u );
					vec4 pointPosition = bv_to_vec(cellOffset) +
							bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
									params.randomness;
					float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition, params);
					h = h == -1.0f ? 1.0f
								   : bv_bsmoothstep(0.0f, 1.0f,
											 0.5f + 0.5f * (smoothDistance - distanceToPoint) /
															params.smoothness);
					float correctionFactor = params.smoothness * h * (1.0f - h);
					smoothDistance = bv_bmix(smoothDistance, distanceToPoint, h) - correctionFactor;
					correctionFactor /= 1.0f + 3.0f * params.smoothness;
					if (calc_color) {
						vec3 cellColor = bn_hash_int_to_float3(bv_iadd(cellPosition, cellOffset));
						smoothColor = bv_bmix(smoothColor, cellColor, h) - vec3(correctionFactor,
														correctionFactor, correctionFactor);
					}
					smoothPosition = bv_bmix(smoothPosition, pointPosition, h) -
							vec4(correctionFactor, correctionFactor, correctionFactor,
									correctionFactor);
				}
			}
		}
	}

	BvOutput octave;
	octave.distance = smoothDistance;
	octave.color = smoothColor;
	octave.position = bv_voronoi_position(cellPosition_f + smoothPosition);
	return octave;
}

BN_FN BvOutput bv_voronoi_f2(BvParams params, vec4 coord) {
	vec4 cellPosition_f = bv_bfloor(coord);
	vec4 localPosition = coord - cellPosition_f;
	ivec4 cellPosition = bv_to_ivec(cellPosition_f);

	float distanceF1 = 3.402823466e+38f;
	float distanceF2 = 3.402823466e+38f;
	ivec4 offsetF1;
	vec4 positionF1;
	ivec4 offsetF2;
	vec4 positionF2;
	for (int u = -1; u <= 1; u++) {
		for (int k = -1; k <= 1; k++) {
			for (int j = -1; j <= 1; j++) {
				for (int i = -1; i <= 1; i++) {
					ivec4 cellOffset = ivec4( i, j, k, u );
					vec4 pointPosition = bv_to_vec(cellOffset) +
							bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
									params.randomness;
					float distanceToPoint = bv_voronoi_distance(pointPosition, localPosition, params);
					if (distanceToPoint < distanceF1) {
						distanceF2 = distanceF1;
						distanceF1 = distanceToPoint;
						offsetF2 = offsetF1;
						offsetF1 = cellOffset;
						positionF2 = positionF1;
						positionF1 = pointPosition;
					} else if (distanceToPoint < distanceF2) {
						distanceF2 = distanceToPoint;
						offsetF2 = cellOffset;
						positionF2 = pointPosition;
					}
				}
			}
		}
	}

	BvOutput octave;
	octave.distance = distanceF2;
	octave.color = bn_hash_int_to_float3(bv_iadd(cellPosition, offsetF2));
	octave.position = bv_voronoi_position(positionF2 + cellPosition_f);
	return octave;
}

BN_FN float bv_voronoi_distance_to_edge(BvParams params, vec4 coord) {
	vec4 cellPosition_f = bv_bfloor(coord);
	vec4 localPosition = coord - cellPosition_f;
	ivec4 cellPosition = bv_to_ivec(cellPosition_f);

	vec4 vectorToClosest;
	float minDistance = 3.402823466e+38f;
	for (int u = -1; u <= 1; u++) {
		for (int k = -1; k <= 1; k++) {
			for (int j = -1; j <= 1; j++) {
				for (int i = -1; i <= 1; i++) {
					ivec4 cellOffset = ivec4( i, j, k, u );
					vec4 vectorToPoint = bv_to_vec(cellOffset) +
							bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
									params.randomness -
							localPosition;
					float distanceToPoint = bv_vdot(vectorToPoint, vectorToPoint);
					if (distanceToPoint < minDistance) {
						minDistance = distanceToPoint;
						vectorToClosest = vectorToPoint;
					}
				}
			}
		}
	}

	minDistance = 3.402823466e+38f;
	for (int u = -1; u <= 1; u++) {
		for (int k = -1; k <= 1; k++) {
			for (int j = -1; j <= 1; j++) {
				for (int i = -1; i <= 1; i++) {
					ivec4 cellOffset = ivec4( i, j, k, u );
					vec4 vectorToPoint = bv_to_vec(cellOffset) +
							bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
									params.randomness -
							localPosition;
					vec4 perpendicularToEdge = vectorToPoint - vectorToClosest;
					if (bv_vdot(perpendicularToEdge, perpendicularToEdge) > 0.0001f) {
						float distanceToEdge = bv_vdot((vectorToClosest + vectorToPoint) / 2.0f,
								bv_vnorm(perpendicularToEdge));
						minDistance = min(minDistance, distanceToEdge);
					}
				}
			}
		}
	}

	return minDistance;
}

BN_FN float bv_voronoi_n_sphere_radius(BvParams params, vec4 coord) {
	vec4 cellPosition_f = bv_bfloor(coord);
	vec4 localPosition = coord - cellPosition_f;
	ivec4 cellPosition = bv_to_ivec(cellPosition_f);

	vec4 closestPoint;
	ivec4 closestPointOffset;
	float minDistanceSq = 3.402823466e+38f;
	for (int u = -1; u <= 1; u++) {
		for (int k = -1; k <= 1; k++) {
			for (int j = -1; j <= 1; j++) {
				for (int i = -1; i <= 1; i++) {
					ivec4 cellOffset = ivec4( i, j, k, u );
					vec4 pointPosition = bv_to_vec(cellOffset) +
							bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
									params.randomness;
					float distanceToPointSq = bv_vlen_sq(pointPosition - localPosition);
					if (distanceToPointSq < minDistanceSq) {
						minDistanceSq = distanceToPointSq;
						closestPoint = pointPosition;
						closestPointOffset = cellOffset;
					}
				}
			}
		}
	}

	minDistanceSq = 3.402823466e+38f;
	vec4 closestPointToClosestPoint;
	for (int u = -1; u <= 1; u++) {
		for (int k = -1; k <= 1; k++) {
			for (int j = -1; j <= 1; j++) {
				for (int i = -1; i <= 1; i++) {
					if (i == 0 && j == 0 && k == 0 && u == 0) {
						continue;
					}
					ivec4 cellOffset = bv_iadd(ivec4( i, j, k, u ), closestPointOffset);
					vec4 pointPosition = bv_to_vec(cellOffset) +
							bn_hash_int_to_float4(bv_iadd(cellPosition, cellOffset)) *
									params.randomness;
					float distanceToPointSq = bv_vlen_sq(closestPoint - pointPosition);
					if (distanceToPointSq < minDistanceSq) {
						minDistanceSq = distanceToPointSq;
						closestPointToClosestPoint = pointPosition;
					}
				}
			}
		}
	}

	return sqrt(bv_vlen_sq(closestPointToClosestPoint - closestPoint)) / 2.0f;
}

/* **** Fractal Voronoi **** */

// The fractalization is fBM's, except that some additions are replaced by lerps.

/* **** Fractal Voronoi **** */

// The fractalization is fBM's, except that some additions are replaced by
// lerps. `template <typename T>` in the C++; one body, stamped per dimension.
#define BV_FRACTALS(T, S) \
BN_FN BvOutput bv_fractal_voronoi_x_fx_##S(BvParams params, const T coord, \
		bool calc_color) { \
	float amplitude = 1.0f; \
	float max_amplitude = 0.0f; \
	float scale = 1.0f; \
 \
	BvOutput outp; \
	outp.distance = 0.0f; \
	outp.color = vec3(0.0f); \
	outp.position = vec4(0.0f); \
	bool zero_input = params.detail == 0.0f || params.roughness == 0.0f; \
 \
	for (int i = 0; i <= int(ceil(params.detail)); ++i) { \
		BvOutput octave = (params.feature == BV_F2) \
				? bv_voronoi_f2(params, coord * scale) \
				: ((params.feature == BV_SMOOTH_F1 && params.smoothness != 0.0f) \
									? bv_voronoi_smooth_f1(params, coord * scale, calc_color) \
									: bv_voronoi_f1(params, coord * scale)); \
 \
		if (zero_input) { \
			max_amplitude = 1.0f; \
			outp = octave; \
			break; \
		} \
		if (float(i) <= params.detail) { \
			max_amplitude += amplitude; \
			outp.distance += octave.distance * amplitude; \
			outp.color += octave.color * amplitude; \
			outp.position = bv_bmix(outp.position, octave.position / scale, amplitude); \
			scale *= params.lacunarity; \
			amplitude *= params.roughness; \
		} else { \
			float remainder = params.detail - floor(params.detail); \
			if (remainder != 0.0f) { \
				max_amplitude = bv_bmix(max_amplitude, max_amplitude + amplitude, remainder); \
				outp.distance = bv_bmix(outp.distance, \
						outp.distance + octave.distance * amplitude, remainder); \
				outp.color = bv_bmix(outp.color, outp.color + octave.color * amplitude, \
						remainder); \
				outp.position = bv_bmix(outp.position, \
						bv_bmix(outp.position, octave.position / scale, amplitude), remainder); \
			} \
		} \
	} \
 \
	if (params.normalize) { \
		outp.distance /= max_amplitude * params.max_distance; \
		outp.color /= max_amplitude; \
	} \
 \
	outp.position = (params.scale != 0.0f) ? outp.position / params.scale : vec4(0.0f); \
 \
	return outp; \
} \
 \
BN_FN float bv_fractal_voronoi_distance_to_edge_##S(BvParams params, const T coord) { \
	float amplitude = 1.0f; \
	float max_amplitude = params.max_distance; \
	float scale = 1.0f; \
	float distance = 8.0f; \
 \
	bool zero_input = params.detail == 0.0f || params.roughness == 0.0f; \
 \
	for (int i = 0; i <= int(ceil(params.detail)); ++i) { \
		float octave_distance = bv_voronoi_distance_to_edge(params, coord * scale); \
 \
		if (zero_input) { \
			distance = octave_distance; \
			break; \
		} \
		if (float(i) <= params.detail) { \
			max_amplitude = bv_bmix(max_amplitude, params.max_distance / scale, amplitude); \
			distance = bv_bmix(distance, min(distance, octave_distance / scale), amplitude); \
			scale *= params.lacunarity; \
			amplitude *= params.roughness; \
		} else { \
			float remainder = params.detail - floor(params.detail); \
			if (remainder != 0.0f) { \
				float lerp_amplitude = bv_bmix(max_amplitude, params.max_distance / scale, amplitude); \
				max_amplitude = bv_bmix(max_amplitude, lerp_amplitude, remainder); \
				float lerp_distance = bv_bmix(distance, min(distance, octave_distance / scale), \
						amplitude); \
				distance = bv_bmix(distance, min(distance, lerp_distance), remainder); \
			} \
		} \
	} \
 \
	if (params.normalize) { \
		distance /= max_amplitude; \
	} \
 \
	return distance; \
}

BV_FRACTALS(float, 1)
BV_FRACTALS(vec2, 2)
BV_FRACTALS(vec3, 3)
BV_FRACTALS(vec4, 4)

#endif // PN_BLENDER_VORONOI