// ======================================================================== //
// Copyright 2009-2017 Intel Corporation                                    //
//                                                                          //
// Licensed under the Apache License, Version 2.0 (the "License");          //
// you may not use this file except in compliance with the License.         //
// You may obtain a copy of the License at                                  //
//                                                                          //
//     http://www.apache.org/licenses/LICENSE-2.0                           //
//                                                                          //
// Unless required by applicable law or agreed to in writing, software      //
// distributed under the License is distributed on an "AS IS" BASIS,        //
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
// See the License for the specific language governing permissions and      //
// limitations under the License.                                           //
// ======================================================================== //

#pragma once

/*! \brief utility library containing sampling functions */

// convention is to return the sample (Vec3f) generated from given Vec2f 's'ample as last parameter
// sampling functions often come in pairs: sample and pdf (needed later for MIS)
// good reference is "Total Compendium" by Philip Dutre http://people.cs.kuleuven.be/~philip.dutre/GI/

#include "../math/vec.isph"


inline Vec3f cartesian(const float phi, const float sinTheta, const float cosTheta)
{
  float sinPhi, cosPhi;
  sincos(phi, &sinPhi, &cosPhi);
  return make_Vec3f(cosPhi * sinTheta,
                    sinPhi * sinTheta,
                    cosTheta);
}

inline Vec3f cartesian(const float phi, const float cosTheta)
{
  return cartesian(phi, cos2sin(cosTheta), cosTheta);
}


/// cosine-weighted sampling of hemisphere oriented along the +z-axis
////////////////////////////////////////////////////////////////////////////////

inline Vec3f cosineSampleHemisphere(const Vec2f s)
{
  const float phi = two_pi * s.x;
  const float cosTheta = sqrt(s.y);
  const float sinTheta = sqrt(1.0f - s.y);
  return cartesian(phi, sinTheta, cosTheta);
}

inline float cosineSampleHemispherePDF(const Vec3f &dir)
{
  return dir.z / M_PI;
}

inline float cosineSampleHemispherePDF(float cosTheta)
{
  return cosTheta / M_PI;
}


/// power cosine-weighted sampling of hemisphere oriented along the +z-axis
////////////////////////////////////////////////////////////////////////////////

inline Vec3f powerCosineSampleHemisphere(const float n, const Vec2f &s)
{
  const float phi = two_pi * s.x;
  const float cosTheta = pow(s.y, 1.0f / (n + 1.0f));
  return cartesian(phi, cosTheta);
}

inline float powerCosineSampleHemispherePDF(const float cosTheta, const float n) // TODO: order of arguments
{
  return (n + 1.0f) * (0.5f / M_PI) * pow(cosTheta, n);
}

inline float powerCosineSampleHemispherePDF(const Vec3f& dir, const float n) // TODO: order of arguments
{
  return (n + 1.0f) * (0.5f / M_PI) * pow(dir.z, n);
}

/// uniform sampling of cone of directions oriented along the +z-axis
////////////////////////////////////////////////////////////////////////////////

inline Vec3f uniformSampleCone(const float cosAngle, const Vec2f &s)
{
  const float phi = two_pi * s.x;
  const float cosTheta = 1.0f - s.y * (1.0f - cosAngle);
  return cartesian(phi, cosTheta);
}

inline float uniformSampleConePDF(const float cosAngle)
{
    return rcp(two_pi*(1.0f - cosAngle));
}

inline uniform float uniformSampleConePDF(const uniform float cosAngle)
{
    return rcp(two_pi*(1.0f - cosAngle));
}


/// uniform sampling of disk
////////////////////////////////////////////////////////////////////////////////

inline Vec3f uniformSampleDisk(const float radius, const Vec2f &s)
{
  const float r = sqrtf(s.x) * radius;
  const float phi = two_pi * s.y;
  float sinPhi, cosPhi;
  sincos(phi, &sinPhi, &cosPhi);
  return make_Vec3f(r * cosPhi, r * sinPhi, 0.f);
}

inline float uniformSampleDiskPDF(const float radius)
{
  return rcp(M_PI * sqr(radius));
}

inline uniform float uniformSampleDiskPDF(const uniform float radius)
{
  return rcp(M_PI * sqr(radius));
}


/// uniform sampling of triangle abc
////////////////////////////////////////////////////////////////////////////////

inline Vec3f uniformSampleTriangle(const Vec3f &a, const Vec3f &b, const Vec3f &c, const Vec2f &s)
{
  const float su = sqrtf(s.x);
  return c + (1.0f - su) * (a-c) + (s.y*su) * (b-c);
}

inline float uniformSampleTrianglePDF(const Vec3f &a, const Vec3f &b, const Vec3f &c)
{
  return 2.0f * rcp(abs(length(cross(a-c, b-c))));
}
