// Copyright (c) 2023 Lachlan McDonald
// This work is licensed under the MIT License (MIT)
// https://github.com/lachlanmcdonald/magicavoxel-shaders
//
// This script utilises or modifies code from other projects or publications.
// Please see the attributions below for more information:
//
// 1. Copyright (c) 2020 ValgoBoi <https://github.com/ValgoBoi/clover-noise>
//    MIT License (MIT)
//    https://github.com/ValgoBoi/clover-noise/blob/master/LICENSE
//
// 2. Copyright (c) 2015 Michael Feldstein <https://github.com/msfeldstein/glsl-map>
//    MIT License (MIT)
//    https://github.com/msfeldstein/glsl-map/blob/master/LICENSE.md
//
// xs brush/treemap [Mode] [Direction] [Iterations] [Min Size] [Bias] [Edge] [Seed]
//
// xs_begin
// author : '@lmcdx.bsky.social'
// arg : { name = 'Mode'  var = 'm_mode'  range = '0 4'  value = '0'  step = '1'  precision = '0' }
// arg : { name = 'Direction'  var = 'm_direction'  range = '0 5'  value = '0'  step = '1'  precision = '0' }
// arg : { name = 'Iterations'  var = 'm_iters'  range = '2 32'  value = '8'  step = '1'  precision = '0' }
// arg : { name = 'Min Size'  var = 'm_min_size'  range = '0 100'  value = '10'  step = '1'  precision = '0' }
// arg : { name = 'Bias'  var = 'm_bias'  range = '0 100'  value = '50'  step = '1'  precision = '0' }
// arg : { name = 'Edge'  var = 'm_edge'  range = '0 100'  value = '0'  step = '1'  precision = '0' }
// arg : { name = 'Seed'  var = 'global_seed'  range = '1 100'  value = '1'  step = '1'  precision = '0' }
// xs_end

int mode = int(m_mode);
int direction = int(m_direction);
int iter_limit = int(m_iters);
float min_size = m_min_size / 100.0;
float bias = m_bias / 100.0;
float edge = m_edge / 100.0;

float hash(vec2 p) {
	return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x))));
}

struct Box {
	vec2 from;
	vec2 to;
	bool upper;
	int iter;
};

bool pointInBox(vec2 p, Box b) {
	return all(bvec4(greaterThanEqual(p, b.from), lessThanEqual(p, b.to)));
}

Box reshapeBox(Box k, bool horz, float f, vec2 p) {
	Box a = Box(k.from, k.to, false, k.iter + 1);
	Box b = Box(k.from, k.to, true, k.iter + 1);

	if (horz) {
		a.to.x = mix(a.from.x, a.to.x, f);
		b.from.x = mix(a.from.x, a.to.x, f);
	} else {
		a.to.y = mix(a.from.y, a.to.y, f);
		b.from.y = mix(a.from.y, a.to.y, f);
	}

	if (pointInBox(p, a)) {
		return a;
	} else {
		return b;
	}
}

float pal(float i) {
	float f = mix(0.0, float(i_num_color_sels), i);
	return color_sel(floor(f));
}

vec2 range(vec2 value, vec2 inMin, vec2 inMax, vec2 outMin, vec2 outMax) {
	return outMin + (outMax - outMin) * (value - inMin) / (inMax - inMin);
}

vec2 range(vec2 value, vec2 inMin, vec2 inMax) {
	return range(value, inMin, inMax, vec2(0.0), vec2(1.0));
}

float range(float value, float inMin, float inMax, float outMin, float outMax) {
  return outMin + (outMax - outMin) * (value - inMin) / (inMax - inMin);
}

float range(float value, float inMin, float inMax) {
	return range(value, inMin, inMax, 0.0, 1.0);
}

float map(vec3 v) {
	vec3 uv = floor(v) / i_volume_size;

	if (direction == 1) {
		uv.z = 1.0 - uv.z;
	} else if (direction == 2) {
		uv.yz = uv.zy;
	} else if (direction == 3) {
		uv.yz = uv.zy;
		uv.z = 1.0 - uv.z;
	} else if (direction == 4) {
		uv.xz = uv.zx;
	} else if (direction == 5) {
		uv.xz = uv.zx;
		uv.z = 1.0 - uv.z;
	}

	Box b = Box(vec2(0.0), vec2(1.0), true, 0);
	vec2 p = vec2(global_seed);

	// Iterate
	for (int iter = 0; iter < iter_limit; iter++) {
		float m = hash(p);
		bool horz = hash(vec2(m, iter)) <= bias;
		Box new_box = reshapeBox(b, horz, m, uv.xy);

		// Enforce min size
		vec2 dim = b.to - b.from;
		if (dim.x > min_size && dim.y > min_size) {
			b = new_box;
		}

		float w = pow(2.0, float(iter));
		p += vec2(b.upper ? w : 0.0, horz ? w : 0.0);
	}

	// Edge
	if (edge > 0.0) {
		vec2 local = range(uv.xy, b.from, b.to);
		if (any(lessThanEqual(local, vec2(edge))) || any(greaterThanEqual(local, 1.0 - vec2(edge)))) {
			return 0.0;
		}
	}

	float k = pow(2.0, float(iter_limit));
	float height = hash(p.xy / k);

	if (mode == 0) {
		float random_color = hash(p.yx / k);
		return height >= uv.z ? pal(random_color) : 0.0;
	} else if (mode == 1) {
		return height >= uv.z ? pal(height) : 0.0;
	} else if (mode == 2) {
		return height >= uv.z ? pal(1.0 - height) : 0.0;
	} else if (mode == 3) {
		float z = range(uv.z, 0.0, height);
		return height >= uv.z ? pal(z) : 0.0;
	} else if (mode == 4) {
		float z = range(uv.z, 0.0, height);
		return height >= uv.z ? pal(1.0 - z) : 0.0;
	}
}