/*
 * Copyright 2011-2013 Blender Foundation
 *
 * 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

#include "kernel/sample/mapping.h"

CCL_NAMESPACE_BEGIN

/* Bump Node */

ccl_device_noinline void svm_node_set_bump(KernelGlobals kg,
                                           ccl_private ShaderData *sd,
                                           ccl_private float *stack,
                                           uint4 node)
{
#ifdef __RAY_DIFFERENTIALS__
  /* get normal input */
  uint normal_offset, scale_offset, invert, use_object_space;
  svm_unpack_node_uchar4(node.y, &normal_offset, &scale_offset, &invert, &use_object_space);

  float3 normal_in = stack_valid(normal_offset) ? stack_load_float3(stack, normal_offset) : sd->N;

  float3 dPdx = sd->dP.dx;
  float3 dPdy = sd->dP.dy;

  if (use_object_space) {
    object_inverse_normal_transform(kg, sd, &normal_in);
    object_inverse_dir_transform(kg, sd, &dPdx);
    object_inverse_dir_transform(kg, sd, &dPdy);
  }

  /* get surface tangents from normal */
  float3 Rx = cross(dPdy, normal_in);
  float3 Ry = cross(normal_in, dPdx);

  /* get bump values */
  uint c_offset, x_offset, y_offset, strength_offset;
  svm_unpack_node_uchar4(node.z, &c_offset, &x_offset, &y_offset, &strength_offset);

  float h_c = stack_load_float(stack, c_offset);
  float h_x = stack_load_float(stack, x_offset);
  float h_y = stack_load_float(stack, y_offset);

  /* compute surface gradient and determinant */
  float det = dot(dPdx, Rx);
  float3 surfgrad = (h_x - h_c) * Rx + (h_y - h_c) * Ry;

  float absdet = fabsf(det);

  float strength = stack_load_float(stack, strength_offset);
  float scale = stack_load_float(stack, scale_offset);

  if (invert)
    scale *= -1.0f;

  strength = max(strength, 0.0f);

  /* compute and output perturbed normal */
  float3 normal_out = safe_normalize(absdet * normal_in - scale * signf(det) * surfgrad);
  if (is_zero(normal_out)) {
    normal_out = normal_in;
  }
  else {
    normal_out = normalize(strength * normal_out + (1.0f - strength) * normal_in);
  }

  if (use_object_space) {
    object_normal_transform(kg, sd, &normal_out);
  }

  normal_out = ensure_valid_reflection(sd->Ng, sd->I, normal_out);

  stack_store_float3(stack, node.w, normal_out);
#endif
}

/* Displacement Node */

ccl_device void svm_node_set_displacement(KernelGlobals kg,
                                          ccl_private ShaderData *sd,
                                          ccl_private float *stack,
                                          uint fac_offset)
{
  float3 dP = stack_load_float3(stack, fac_offset);
  sd->P += dP;
}

ccl_device_noinline void svm_node_displacement(KernelGlobals kg,
                                               ccl_private ShaderData *sd,
                                               ccl_private float *stack,
                                               uint4 node)
{
  uint height_offset, midlevel_offset, scale_offset, normal_offset;
  svm_unpack_node_uchar4(node.y, &height_offset, &midlevel_offset, &scale_offset, &normal_offset);

  float height = stack_load_float(stack, height_offset);
  float midlevel = stack_load_float(stack, midlevel_offset);
  float scale = stack_load_float(stack, scale_offset);
  float3 normal = stack_valid(normal_offset) ? stack_load_float3(stack, normal_offset) : sd->N;
  uint space = node.w;

  float3 dP = normal;

  if (space == NODE_NORMAL_MAP_OBJECT) {
    /* Object space. */
    object_inverse_normal_transform(kg, sd, &dP);
    dP *= (height - midlevel) * scale;
    object_dir_transform(kg, sd, &dP);
  }
  else {
    /* World space. */
    dP *= (height - midlevel) * scale;
  }

  stack_store_float3(stack, node.z, dP);
}

ccl_device_noinline int svm_node_vector_displacement(
    KernelGlobals kg, ccl_private ShaderData *sd, ccl_private float *stack, uint4 node, int offset)
{
  uint4 data_node = read_node(kg, &offset);
  uint space = data_node.x;

  uint vector_offset, midlevel_offset, scale_offset, displacement_offset;
  svm_unpack_node_uchar4(
      node.y, &vector_offset, &midlevel_offset, &scale_offset, &displacement_offset);

  float3 vector = stack_load_float3(stack, vector_offset);
  float midlevel = stack_load_float(stack, midlevel_offset);
  float scale = stack_load_float(stack, scale_offset);
  float3 dP = (vector - make_float3(midlevel, midlevel, midlevel)) * scale;

  if (space == NODE_NORMAL_MAP_TANGENT) {
    /* Tangent space. */
    float3 normal = sd->N;
    object_inverse_normal_transform(kg, sd, &normal);

    const AttributeDescriptor attr = find_attribute(kg, sd, node.z);
    float3 tangent;
    if (attr.offset != ATTR_STD_NOT_FOUND) {
      tangent = primitive_surface_attribute_float3(kg, sd, attr, NULL, NULL);
    }
    else {
      tangent = normalize(sd->dPdu);
    }

    float3 bitangent = normalize(cross(normal, tangent));
    const AttributeDescriptor attr_sign = find_attribute(kg, sd, node.w);
    if (attr_sign.offset != ATTR_STD_NOT_FOUND) {
      float sign = primitive_surface_attribute_float(kg, sd, attr_sign, NULL, NULL);
      bitangent *= sign;
    }

    dP = tangent * dP.x + normal * dP.y + bitangent * dP.z;
  }

  if (space != NODE_NORMAL_MAP_WORLD) {
    /* Tangent or object space. */
    object_dir_transform(kg, sd, &dP);
  }

  stack_store_float3(stack, displacement_offset, dP);
  return offset;
}

CCL_NAMESPACE_END
