/******************************************************************************/
/*
  Project   - MudBun
  Publisher - Long Bunny Labs
              http://LongBunnyLabs.com
  Author    - Ming-Lun "Allen" Chou
              http://AllenChou.net
*/
/******************************************************************************/

#ifndef MUDBUN_SDF_PRIMITIVES
#define MUDBUN_SDF_PRIMITIVES

#include "../Math/Vector.cginc"
#include "../Noise/CachedNoise3D.cginc"
#include "../Noise/ClassicNoise3D.cginc"
#include "../Noise/TriangleNoise3D.cginc"

// https://iquilezles.org/www/articles/distfunctions/distfunctions.htm

// r: radius
float sdf_sphere(float3 p, float r)
{
  return length(p) - r;
}

// h: extent
float sdf_ellipsoid(float3 p, float3 h)
{
  float k0 = max(kEpsilon, length(p / h));
  float k1 = max(kEpsilon, length(p / (h * h)));
  return 0.6f * (k0 * (k0 - 1.0f) / k1);
}

// c: center
// h: extent
// r: round
float sdf_box(float3 p, float3 h, float r = 0.0f)
{
  h = abs(h);
  float3 d = abs(p) - h;
  return length(max(d, 0.0f)) + min(max_comp(d), 0.0f) - r;
}

// a: point A
// b: point B
// r: radius
float sdf_capsule(float3 p, float3 a, float3 b, float r)
{
  float3 ab = b - a;
  float3 ap = p - a;
  p -= a + saturate(dot(ap, ab) / dot(ab, ab)) * ab;
  return length(p) - r;
}

// h:  height
// r1: radius 1
// r2: radius 2
// r:  round
float sdf_capped_cone(float3 p, float h, float r1, float r2, float r = 0.0f)
{
  float2 q = float2(length(p.xz), p.y);
  float2 k1 = float2(r2, h);
  float2 k2 = float2(r2 - r1, 2.0f * h);
  float2 ca = float2(q.x - min(q.x, (q.y < 0.0f) ? r1 : r2), abs(q.y) - h);
  float2 cb = q - k1 + k2 * clamp(dot(k1 - q, k2) / dot(k2, k2), 0.0f, 1.0f);
  float s = (cb.x < 0.0f && ca.y < 0.0f) ? -1.0f : 1.0f;
  return s * sqrt(min(dot(ca, ca), dot(cb, cb))) - r;
}

// h:  height
// r:  radius
// rr: extra radius
float sdf_cylinder(float3 p, float h, float r, float rr = 0.0f)
{
  float2 d = abs(float2(length(p.xz), p.y)) - float2(r, h);
  return min(max(d.x, d.y), 0.0f) + length(max(d, 0.0f)) - rr;
}

float sdf_torus(float3 p, float h, float r1, float r2)
{
  float3 q = float3(max(abs(p.x) - h, 0.0f), p.y, p.z);
  return length(float2(length(q.xz) - r1, q.y)) - r2;
}

float sdf_solid_angle(float3 p, float2 c, float r, float rr = 0.0f)
{
  // c is the sin/cos of the angle
  float2 q = float2(length(p.xz), p.y);
  float l = length(q) - r;
  float m = length(q - c * clamp(dot(q, c), 0.0f, r));
  return max(l, m*sign(c.y * q.x - c.x * q.y)) - rr;
}

float sdf_noise(int type, float3 p, float3 boundsMin, float3 boundsMax, float3 offset, float3 size, float threshold, int numOctaves, float octaveOffsetFactor, float3 period = 100.0f)
{
  float n = 0.0f;
  float f = 1.0f;
  switch (type)
  {
    case kSdfNoiseTypePerlin:
      n = 0.8f * (saturate(mbn_pnoise(p / size, offset, numOctaves, octaveOffsetFactor, period)) - 0.5f) + 0.5f;
      f = 0.9f;
      break;

    case kSdfNoiseTypeCachedPerlin:
      n = 0.9f * mbn_cached_noise(p / size, offset, numOctaves, octaveOffsetFactor);
      f = 0.8f;
      break;

    case kSdfNoiseTypeTriangle:
      n = mbn_triangle_noise(p / size, offset, numOctaves, octaveOffsetFactor);
      f = 0.4f;
      break;
  }

  float d = threshold - n;

  // noise is not an actual SDF
  // we need to scale the result to make it behave like one
  // making the result slightly smaller than it should be would prevent false positive voxel node culling
  d *= f * min(min(size.x, size.y), size.z);

  return d;
}

float sdf_round_cone(float3 p, float3 a, float3 b, float r1, float r2)
{
  // sampling independent computations (only depend on shape)
  float3 ba = b - a;
  float l2 = dot(ba, ba);
  float rr = r1 - r2;
  float a2 = l2 - rr * rr;
  float il2 = 1.0f / l2;

  // sampling dependent computations
  float3 pa = p - a;
  float y = dot(pa, ba);
  float z = y - l2;
  float3 g = pa * l2 - ba * y;
  float x2 = dot(g, g);
  float y2 = y * y * l2;
  float z2 = z * z * l2;

  // single square root!
  float k = sign(rr) * rr * rr * x2;
  if (sign(z) * a2 * z2 > k) 
    return sqrt(x2 + z2) * il2 - r2;

  if (sign(y) * a2 * y2 < k) 
    return sqrt(x2 + y2) * il2 - r1;

  return (sqrt(x2*a2*il2) + y * rr)*il2 - r1;
}

// https://www.shadertoy.com/view/MsXGWr
float2 sdf_segment(float3 p, float3 a, float3 b)
{
  float3 pa = p - a, ba = b - a;
  float h = saturate(dot(pa, ba) / dot(ba, ba));
  return float2(length(pa - ba * h), h);
}

// https://www.shadertoy.com/view/ldj3Wh
float2 sdf_bezier(float3 pos, float3 A, float3 B, float3 C)
{
  float3 a = B - A;
  float3 b = A - 2.0f * B + C;
  float3 c = a * 2.0f;
  float3 d = A - pos;

  float kk = 1.0f / dot(b, b);
  float kx = kk * dot(a, b);
  float ky = kk * (2.0f * dot(a, a) + dot(d, b)) / 3.0f;
  float kz = kk * dot(d,a);

  float2 res;

  float p = ky - kx * kx;
  float p3 = p * p * p;
  float q = kx * (2.0f * kx * kx - 3.0f * ky) + kz;
  float h = q * q + 4.0f * p3;

  if(h >= 0.0f) 
  { 
      h = sqrt(h);
      float2 x = (float2(h, -h) - q) / 2.0f;
      float2 uv = sign(x) * pow(abs(x), 0.33333333f);
      float t = clamp(uv.x + uv.y - kx, 0.0f, 1.0f);

      // 1 root
      float3 g = d + (c + b * t) * t;
      res = float2(dot(g, g), t);
  }
  else
  {
      float z = sqrt(-p);
      float v = acos(q / (p * z * 2.0f)) / 3.0f;
      float m = cos(v);
      float n = sin(v) * 1.732050808f;
      float3 t = clamp(float3(m + m,-n - m, n - m) * z - kx, 0.0f, 1.0f);
      
      // 3 roots, but only need two
      float3 g = d + (c + b * t.x) * t.x;
      float dis = dot(g, g);
      res = float2(dis, t.x);

      g = d + (c + b * t.y) * t.y;
      dis = dot(g, g);
      if(dis < res.x)
        res = float2(dis, t.y);
  }
  
  res.x = sqrt(res.x);
  return res;
}

#endif
