/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
 *
 * SPDX-License-Identifier: Apache-2.0 */

#include "node_fresnel.h"
#include "stdcycles.h"

shader node_principled_bsdf(string distribution = "multi_ggx",
                            string subsurface_method = "random_walk",
                            color BaseColor = color(0.8, 0.8, 0.8),
                            float SubsurfaceWeight = 0.0,
                            float SubsurfaceScale = 0.1,
                            vector SubsurfaceRadius = vector(1.0, 1.0, 1.0),
                            float SubsurfaceIOR = 1.4,
                            float SubsurfaceAnisotropy = 0.0,
                            float Metallic = 0.0,
                            float SpecularIORLevel = 0.5,
                            color SpecularTint = color(1.0),
                            float Roughness = 0.5,
                            float Anisotropic = 0.0,
                            float AnisotropicRotation = 0.0,
                            float SheenWeight = 0.0,
                            float SheenRoughness = 0.5,
                            color SheenTint = 0.5,
                            float CoatWeight = 0.0,
                            float CoatRoughness = 0.03,
                            float CoatIOR = 1.5,
                            color CoatTint = color(1.0, 1.0, 1.0),
                            float IOR = 1.45,
                            float TransmissionWeight = 0.0,
                            color EmissionColor = 1.0,
                            float EmissionStrength = 0.0,
                            float Alpha = 1.0,
                            normal Normal = N,
                            normal CoatNormal = N,
                            normal Tangent = normalize(dPdu),
                            output closure color BSDF = 0)
{
  float CLOSURE_WEIGHT_CUTOFF = 1e-5;

  /* Clamping to match SVM */
  float metallic = clamp(Metallic, 0.0, 1.0);
  float transmission = clamp(TransmissionWeight, 0.0, 1.0);
  float subsurface_weight = clamp(SubsurfaceWeight, 0.0, 1.0);
  color specular_tint = max(SpecularTint, color(0.0));
  float coat_weight = max(CoatWeight, 0.0);
  color coat_tint = max(CoatTint, color(0.0));
  float sheen_weight = max(SheenWeight, 0.0);
  color base_color = max(BaseColor, color(0.0));
  color clamped_base_color = min(base_color, color(1.0));

  float r2 = clamp(Roughness, 0.0, 1.0);
  r2 = r2 * r2;

  float alpha_x = r2, alpha_y = r2;

  /* Handle anisotropy. */
  vector T = Tangent;
  if (Anisotropic > 0.0) {
    float aspect = sqrt(1.0 - clamp(Anisotropic, 0.0, 1.0) * 0.9);
    alpha_x /= aspect;
    alpha_y *= aspect;
    if (AnisotropicRotation != 0.0)
      T = rotate(T, AnisotropicRotation * M_2PI, point(0.0, 0.0, 0.0), Normal);
  }

  if (metallic < 1.0 && TransmissionWeight < 1.0) {
    float eta = max(IOR, 1e-5);
    float f0 = F0_from_ior(eta);
    if (SpecularIORLevel != 0.5) {
      f0 *= 2.0 * max(SpecularIORLevel, 0.0);
      eta = ior_from_F0(f0);
      if (IOR < 1.0) {
        eta = 1.0 / eta;
      }
    }

    BSDF = base_color * diffuse(Normal);
    if (subsurface_weight > CLOSURE_WEIGHT_CUTOFF) {
      vector radius = max(SubsurfaceScale * SubsurfaceRadius, vector(0.0));
      float subsurface_ior = (subsurface_method == "random_walk_skin") ? SubsurfaceIOR : eta;
      closure color SubsurfBSDF = bssrdf(subsurface_method,
                                         Normal,
                                         radius,
                                         clamped_base_color,
                                         "roughness",
                                         r2,
                                         "ior",
                                         subsurface_ior,
                                         "anisotropy",
                                         SubsurfaceAnisotropy);
      BSDF = mix(BSDF, clamped_base_color * SubsurfBSDF, subsurface_weight);
    }

    if (eta != 1.0) {
      /* Apply specular tint */
      color F0 = f0 * specular_tint;
      color F90 = color(1.0);

      BSDF = layer(
          generalized_schlick_bsdf(
              Normal, T, color(1.0), color(0.0), alpha_x, alpha_y, F0, F90, -eta, distribution),
          BSDF);
    }
  }

  closure color TransmissionBSDF = 0;
  if (metallic < 1.0 && TransmissionWeight > CLOSURE_WEIGHT_CUTOFF) {
    float eta = max(IOR, 1e-5);
    eta = backfacing() ? 1.0 / eta : eta;

    color F0 = F0_from_ior(eta) * specular_tint;
    color F90 = color(1.0);

    TransmissionBSDF = generalized_schlick_bsdf(Normal,
                                                vector(0.0),
                                                color(1.0),
                                                sqrt(clamped_base_color),
                                                r2,
                                                r2,
                                                F0,
                                                F90,
                                                -eta,
                                                distribution),
    BSDF = mix(BSDF, TransmissionBSDF, transmission);
  }

  closure color MetallicBSDF = 0;
  if (metallic > CLOSURE_WEIGHT_CUTOFF) {
    color F0 = clamped_base_color;
    color F82 = min(specular_tint, color(1.0));
    MetallicBSDF = microfacet_f82_tint(distribution, Normal, T, alpha_x, alpha_y, F0, F82);
    BSDF = mix(BSDF, MetallicBSDF, metallic);
  }

  if (EmissionStrength != 0.0 && EmissionColor != color(0.0)) {
    BSDF += EmissionStrength * EmissionColor * emission();
  }

  if (coat_weight > CLOSURE_WEIGHT_CUTOFF) {
    float coat_ior = max(CoatIOR, 1.0);
    if (CoatTint != color(1.0)) {
      float coat_neta = 1.0 / coat_ior;
      float cosNI = dot(I, CoatNormal);
      float cosNT = sqrt(1.0 - coat_neta * coat_neta * (1 - cosNI * cosNI));
      BSDF *= mix(color(1.0), pow(CoatTint, 1.0 / cosNT), clamp(coat_weight, 0.0, 1.0));
    }
    float coat_r2 = clamp(CoatRoughness, 0.0, 1.0);
    coat_r2 = coat_r2 * coat_r2;

    closure color CoatBSDF = dielectric_bsdf(
        CoatNormal, vector(0.0), color(1.0), color(0.0), coat_r2, coat_r2, coat_ior, "ggx");
    BSDF = layer(coat_weight * CoatBSDF, BSDF);
  }

  if (SheenWeight > CLOSURE_WEIGHT_CUTOFF) {
    normal sheen_normal = normalize(mix(Normal, CoatNormal, clamp(coat_weight, 0.0, 1.0)));
    closure color SheenBSDF = sheen(sheen_normal, clamp(SheenRoughness, 0.0, 1.0));
    BSDF = layer(sheen_weight * max(SheenTint, color(0.0)) * SheenBSDF, BSDF);
  }

  BSDF = mix(transparent(), BSDF, clamp(Alpha, 0.0, 1.0));
}
