// soft min of a and b with smoothing factor k
float smin(float a, float b, float k) {
	float h = max(k - abs(a-b), 0) / k;
	return min(a, b) - h * h * h * k * (1 / 6.0);
}

// soft max of a and b with smoothing factor k
float smax(float a, float b, float k) {
	float h = max(k - abs(a - b), 0) / k;
	return max(a, b) + h * h * h * k * (1 / 6.0);
}

Material mixMat(Material a, Material b, float fac) {
	Material m;
	fac = clamp(fac * 1.1 - 0.05, 0, 1); // TODO make this configurable, also possibly use smoothstep
	m.col = lerp(a.col, b.col, fac);
	m.gloss = lerp(a.gloss, b.gloss, fac);
	return m;
}

Material mixMat(SurfacePoint a, SurfacePoint b) {
	float fac = clamp(a.dist/(a.dist + b.dist), 0, 1);
	return mixMat(a.mat, b.mat, fac);
}

Material nearestMat(SurfacePoint a, SurfacePoint b) {
	if (a.dist < b.dist) {
		return a.mat;
	} else {
		return b.mat;
	}
}

// --------------------------------
// base sdf operations
// --------------------------------

float qUnion(float a, float b) {
	return min(a, b);
}

float qUnion(float a, float b, float smooth) {
	return smin(a, b, smooth);
}

SurfacePoint qUnion(SurfacePoint a, SurfacePoint b) {
	SurfacePoint o;
	o.dist = min(a.dist, b.dist);
	o.mat = nearestMat(a, b);
	return o;
}

SurfacePoint qUnion(SurfacePoint a, SurfacePoint b, float smooth) {
	SurfacePoint o;
	o.dist = smin(a.dist, b.dist, smooth);
	o.mat = mixMat(a, b);
	return o;
}

float qSub(float a, float b) {
	return max(a, -b);
}

float qSub(float a, float b, float smooth) {
	return smax(a, -b, smooth);
}

SurfacePoint qSub(SurfacePoint a, SurfacePoint b) {
	SurfacePoint o;
	o.dist = max(a.dist, -b.dist);
	o.mat = a.mat;
	return o;
}

SurfacePoint qSub(SurfacePoint a, SurfacePoint b, float smooth) {
	SurfacePoint o;
	o.dist = smax(a.dist, -b.dist, smooth);
	o.mat = a.mat;
	return o;
}

float qIntersect(float a, float b) {
	return max(a, b);
}

float qIntersect(float a, float b, float smooth) {
	return smax(a, b, smooth);
}

SurfacePoint qIntersect(SurfacePoint a, SurfacePoint b) {
	SurfacePoint o;
	o.dist = max(a.dist, b.dist);
	o.mat = nearestMat(a, b);
	return o;
}

SurfacePoint qIntersect(SurfacePoint a, SurfacePoint b, float smooth) {
	SurfacePoint o;
	o.dist = smax(a.dist, b.dist, smooth);
	o.mat = mixMat(a, b);
	return o;
}

// --------------------------------
// single sdf operations
// --------------------------------

float qRound(float a, float radius) {
	return a - radius;
}

SurfacePoint qRound(SurfacePoint a, float radius) {
	a.dist -= radius;
	return a;
}

// --------------------------------
// spatial warping
// --------------------------------

float3 rotX(float3 p, float a) {
	return mul(float3x3(1, 0, 0, 0, cos(a), -sin(a), 0, sin(a), cos(a)), p);
}

float3 rotY(float3 p, float a) {
	return mul(float3x3(cos(a), 0, sin(a), 0, 1, 0, -sin(a), 0, cos(a)), p);
}

float3 rotZ(float3 p, float a) {
	return mul(float3x3(cos(a), -sin(a), 0, sin(a), cos(a), 0, 0, 0, 1), p);
}

// repeats space every r units, centered on the origin
float3 repXYZ(float3 p, float3 r) {
	float3 o = p;
	o = fmod(abs(p + r/2.0), r) - r/2.0;
	o *= sign(o);
	return o;
}

// repeats space every r units, centered on the origin, no sign
float3 repXYZUnsigned(float3 p, float3 r) {
	return fmod(abs(p + r / 2.0), r) - r / 2.0;
}

// repeats space
float3 repXZ(float3 p, float x, float z) {
	float3 o = p;
	o.x = fmod(abs(p.x) + x / 2.0, x) - x / 2.0;
	o.x *= sign(p.x);
	o.z = fmod(abs(p.z) + z / 2.0, z) - z / 2.0;
	o.z *= sign(p.z);
	return o;
}

float3 repX(float3 p, float x) {
	float3 o = p;
	o.x = fmod(abs(p.x) + x / 2.0, x) - x / 2.0;
	o.x *= sign(p.x);
	return o;
}
