text: 
  //
  // This source file is part of appleseed.
  // Visit https://appleseedhq.net/ for additional information and resources.
  //
  // This software is released under the MIT license.
  //
  // Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited
  // Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization
  //
  // Permission is hereby granted, free of charge, to any person obtaining a copy
  // of this software and associated documentation files (the "Software"), to deal
  // in the Software without restriction, including without limitation the rights
  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  // copies of the Software, and to permit persons to whom the Software is
  // furnished to do so, subject to the following conditions:
  //
  // The above copyright notice and this permission notice shall be included in
  // all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  // THE SOFTWARE.
  //
  
  // Interface header.
  #include "material.h"
  
  // appleseed.renderer headers.
  #include "renderer/global/globallogger.h"
  #include "renderer/modeling/bsdf/bsdf.h"
  #include "renderer/modeling/bssrdf/bssrdf.h"
  #include "renderer/modeling/edf/edf.h"
  #include "renderer/modeling/input/inputarray.h"
  #include "renderer/modeling/input/source.h"
  #include "renderer/modeling/input/texturesource.h"
  #include "renderer/modeling/material/bumpmappingmodifier.h"
  #include "renderer/modeling/material/normalmappingmodifier.h"
  #include "renderer/modeling/project/project.h"
  #include "renderer/modeling/scene/scene.h"
  #include "renderer/modeling/scene/textureinstance.h"
  #include "renderer/modeling/surfaceshader/surfaceshader.h"
  #include "renderer/modeling/texture/texture.h"
  #include "renderer/modeling/volume/volume.h"
  #include "renderer/utility/messagecontext.h"
  #include "renderer/utility/paramarray.h"
  
  // appleseed.foundation headers.
  #include "foundation/image/colorspace.h"
  #include "foundation/string/string.h"
  #include "foundation/utility/api/apistring.h"
  #include "foundation/utility/makevector.h"
  
  // Standard headers.
  #include <string>
  
  using namespace foundation;
  
  namespace renderer
  {
  
  //
  // Material::RenderData class implementation.
  //
  
  Material::RenderData::RenderData()
  {
      clear();
  }
  
  void Material::RenderData::clear()
  {
      m_surface_shader = nullptr;
      m_bsdf = nullptr;
      m_bssrdf = nullptr;
      m_edf = nullptr;
      m_volume = nullptr;
      m_alpha_map = nullptr;
      m_shader_group = nullptr;
      m_basis_modifier = nullptr;
  }
  
  
  //
  // Material class implementation.
  //
  
  namespace
  {
      const UniqueID g_class_uid = new_guid();
  }
  
  UniqueID Material::get_class_uid()
  {
      return g_class_uid;
  }
  
  Material::Material(
      const char*             name,
      const ParamArray&       params)
    : ConnectableEntity(g_class_uid, params)
  {
      set_name(name);
  
      m_inputs.declare("surface_shader", InputFormat::Entity, "");
  }
  
  const char* Material::get_surface_shader_name() const
  {
      return get_non_empty(m_params, "surface_shader");
  }
  
  const char* Material::get_bsdf_name() const
  {
      return get_non_empty(m_params, "bsdf");
  }
  
  const char* Material::get_bssrdf_name() const
  {
      return get_non_empty(m_params, "bssrdf");
  }
  
  const char* Material::get_edf_name() const
  {
      return get_non_empty(m_params, "edf");
  }
  
  const char* Material::get_volume_name() const
  {
      return get_non_empty(m_params, "volume");
  }
  
  const SurfaceShader* Material::get_uncached_surface_shader() const
  {
      return static_cast<const SurfaceShader*>(m_inputs.get_entity("surface_shader"));
  }
  
  const BSDF* Material::get_uncached_bsdf() const
  {
      return static_cast<const BSDF*>(m_inputs.get_entity("bsdf"));
  }
  
  const BSSRDF* Material::get_uncached_bssrdf() const
  {
      return static_cast<const BSSRDF*>(m_inputs.get_entity("bssrdf"));
  }
  
  const EDF* Material::get_uncached_edf() const
  {
      return static_cast<const EDF*>(m_inputs.get_entity("edf"));
  }
  
  const Volume* Material::get_uncached_volume() const
  {
      return static_cast<const Volume*>(m_inputs.get_entity("volume"));
  }
  
  const Source* Material::get_uncached_alpha_map() const
  {
      return m_inputs.source("alpha_map");
  }
  
  const ShaderGroup* Material::get_uncached_osl_surface() const
  {
      return nullptr;
  }
  
  bool Material::has_alpha_map() const
  {
      return get_uncached_alpha_map() != nullptr;
  }
  
  bool Material::has_opaque_uniform_alpha_map() const
  {
      const Source* source = get_uncached_alpha_map();
  
      if (!source || !source->is_uniform())
          return false;
  
      float alpha;
      source->evaluate_uniform(alpha);
  
      return alpha == 1.0f;
  }
  
  bool Material::on_frame_begin(
      const Project&          project,
      const BaseGroup*        parent,
      OnFrameBeginRecorder&   recorder,
      IAbortSwitch*           abort_switch)
  {
      if (!ConnectableEntity::on_frame_begin(project, parent, recorder, abort_switch))
          return false;
  
      m_render_data.clear();
      m_render_data.m_surface_shader = get_uncached_surface_shader();
      if (m_render_data.m_surface_shader == nullptr)
          m_render_data.m_surface_shader = project.get_scene()->get_default_surface_shader();
      m_render_data.m_alpha_map = get_uncached_alpha_map();
      m_render_data.m_default_tangent_mode = get_default_tangent_mode();
  
      return true;
  }
  
  void Material::on_frame_end(
      const Project&          project,
      const BaseGroup*        parent)
  {
      delete m_render_data.m_basis_modifier;
      m_render_data.clear();
  
      ConnectableEntity::on_frame_end(project, parent);
  }
  
  bool Material::has_emission() const
  {
      return get_uncached_edf() != nullptr;
  }
  
  const char* Material::get_non_empty(const ParamArray& params, const char* name) const
  {
      if (!params.strings().exist(name))
          return nullptr;
  
      const char* value = params.strings().get(name);
  
      return is_empty_string(value) ? nullptr : value;
  }
  
  IBasisModifier* Material::create_basis_modifier(const MessageContext& context) const
  {
      // Retrieve the source bound to the displacement map input.
      const Source* displacement_source = m_inputs.source("displacement_map");
  
      // Nothing to do if there is no displacement source.
      if (displacement_source == nullptr)
          return nullptr;
  
      // Additional checks if a texture instance is used for displacement.
      const TextureSource* texture_map = dynamic_cast<const TextureSource*>(displacement_source);
      if (texture_map != nullptr)
      {
          // Print a warning if the displacement texture is not linear.
          const Texture& texture = texture_map->get_texture_instance().get_texture();
          if (texture.get_color_space() != ColorSpaceLinearRGB)
          {
              RENDERER_LOG_WARNING(
                  "%scolor space for displacement map \"%s\" "
                  "should be \"%s\" but is \"%s\" instead; expect artifacts and/or slowdowns.",
                  context.get(),
                  texture.get_path().c_str(),
                  color_space_name(ColorSpaceLinearRGB),
                  color_space_name(texture.get_color_space()));
          }
      }
  
      // Retrieve the displacement method.
      const std::string displacement_method =
          m_params.get_required<std::string>(
              "displacement_method",
              "bump",
              make_vector("bump", "normal"),
              context);
  
      // Create the basis modifier.
      if (displacement_method == "bump")
      {
          const float offset = m_params.get_optional<float>("bump_offset", 0.5f);
          const float amplitude = m_params.get_optional<float>("bump_amplitude", 1.0f);
          return new BumpMappingModifier(displacement_source, offset, amplitude);
      }
      else
      {
          const NormalMappingModifier::UpVector up_vector =
              m_params.get_optional<std::string>("normal_map_up", "z", make_vector("y", "z"), context) == "y"
                  ? NormalMappingModifier::UpVectorY
                  : NormalMappingModifier::UpVectorZ;
          return new NormalMappingModifier(displacement_source, up_vector);
      }
  }
  
  Material::RenderData::DefaultTangentMode Material::get_default_tangent_mode() const
  {
      const std::string default_tangent_mode =
          m_params.get_optional<std::string>(
              "default_tangent_mode",
              "uv",
              make_vector("uv", "local_x", "local_y", "local_z", "radial"));
      return
          default_tangent_mode == "local_x" ? RenderData::DefaultTangentMode::LocalX :
          default_tangent_mode == "local_y" ? RenderData::DefaultTangentMode::LocalY :
          default_tangent_mode == "local_z" ? RenderData::DefaultTangentMode::LocalZ :
          default_tangent_mode == "radial"  ? RenderData::DefaultTangentMode::Radial :
                                              RenderData::DefaultTangentMode::UV;
  }
  
  }   // namespace renderer
  