// Copyright 2009-2020 Intel Corporation
// SPDX-License-Identifier: Apache-2.0

#include "geometry.h"
#include "scene.h"

namespace embree
{
  const char* Geometry::gtype_names[Geometry::GTY_END] =
  {
    "flat_linear_curve",
    "round_linear_curve",
    "oriented_linear_curve",
    "",
    "flat_bezier_curve",
    "round_bezier_curve",
    "oriented_bezier_curve",
    "",
    "flat_bspline_curve",
    "round_bspline_curve",
    "oriented_bspline_curve",
    "",
    "flat_hermite_curve",
    "round_hermite_curve",
    "oriented_hermite_curve",
    "",
    "flat_catmull_rom_curve",
    "round_catmull_rom_curve",
    "oriented_catmull_rom_curve",
    "",    
    "triangles",
    "quads",
    "grid",
    "subdivs",
    "",
    "sphere",
    "disc",
    "oriented_disc",
    "",
    "usergeom",
    "instance_cheap",
    "instance_expensive",
  };
     
  Geometry::Geometry (Device* device, GType gtype, unsigned int numPrimitives, unsigned int numTimeSteps) 
    : device(device), userPtr(nullptr),
      numPrimitives(numPrimitives), numTimeSteps(unsigned(numTimeSteps)), fnumTimeSegments(float(numTimeSteps-1)), time_range(0.0f,1.0f),
      mask(-1),
      gtype(gtype),
      gsubtype(GTY_SUBTYPE_DEFAULT),
      quality(RTC_BUILD_QUALITY_MEDIUM),
      state((unsigned)State::MODIFIED),
      enabled(true),
      intersectionFilterN(nullptr), occlusionFilterN(nullptr), pointQueryFunc(nullptr)
  {
    device->refInc();
  }

  Geometry::~Geometry()
  {
    device->refDec();
  }

  void Geometry::setNumPrimitives(unsigned int numPrimitives_in)
  {      
    if (numPrimitives_in == numPrimitives) return;
    
    numPrimitives = numPrimitives_in;
    
    Geometry::update();
  }

  void Geometry::setNumTimeSteps (unsigned int numTimeSteps_in)
  {
    if (numTimeSteps_in == numTimeSteps) {
      return;
    }
    
    numTimeSteps = numTimeSteps_in;
    fnumTimeSegments = float(numTimeSteps_in-1);
    
    Geometry::update();
  }

  void Geometry::setTimeRange (const BBox1f range)
  {
    time_range = range;
    Geometry::update();
  }
  
  void Geometry::update()
  {
    ++modCounter_; // FIXME: required?
    state = (unsigned)State::MODIFIED;
  }
  
  void Geometry::commit() 
  {
    ++modCounter_;
    state = (unsigned)State::COMMITTED;
  }

  void Geometry::preCommit()
  {
    if (State::MODIFIED == (State)state)
      throw_RTCError(RTC_ERROR_INVALID_OPERATION,"geometry not committed");
  }

  void Geometry::postCommit()
  {
  }

  void Geometry::enable () 
  {
    if (isEnabled()) 
      return;

    enabled = true;
    ++modCounter_;
  }

  void Geometry::disable () 
  {
    if (isDisabled()) 
      return;
    
    enabled = false;
    ++modCounter_;
  }

  void Geometry::setUserData (void* ptr)
  {
    userPtr = ptr;
  }
  
  void Geometry::setIntersectionFilterFunctionN (RTCFilterFunctionN filter) 
  {
    if (!(getTypeMask() & (MTY_TRIANGLE_MESH | MTY_QUAD_MESH | MTY_CURVES | MTY_SUBDIV_MESH | MTY_USER_GEOMETRY | MTY_GRID_MESH)))
      throw_RTCError(RTC_ERROR_INVALID_OPERATION,"filter functions not supported for this geometry"); 

    intersectionFilterN = filter;
  }

  void Geometry::setOcclusionFilterFunctionN (RTCFilterFunctionN filter) 
  {
    if (!(getTypeMask() & (MTY_TRIANGLE_MESH | MTY_QUAD_MESH | MTY_CURVES | MTY_SUBDIV_MESH | MTY_USER_GEOMETRY | MTY_GRID_MESH)))
      throw_RTCError(RTC_ERROR_INVALID_OPERATION,"filter functions not supported for this geometry"); 

    occlusionFilterN = filter;
  }
  
  void Geometry::setPointQueryFunction (RTCPointQueryFunction func) 
  {
    pointQueryFunc = func;
  }

  void Geometry::interpolateN(const RTCInterpolateNArguments* const args)
  {
    const void* valid_i = args->valid;
    const unsigned* primIDs = args->primIDs;
    const float* u = args->u;
    const float* v = args->v;
    unsigned int N = args->N;
    RTCBufferType bufferType = args->bufferType;
    unsigned int bufferSlot = args->bufferSlot;
    float* P = args->P;
    float* dPdu = args->dPdu;
    float* dPdv = args->dPdv;
    float* ddPdudu = args->ddPdudu;
    float* ddPdvdv = args->ddPdvdv;
    float* ddPdudv = args->ddPdudv;
    unsigned int valueCount = args->valueCount;

    if (valueCount > 256) throw_RTCError(RTC_ERROR_INVALID_OPERATION,"maximally 256 floating point values can be interpolated per vertex");
    const int* valid = (const int*) valid_i;
 
    __aligned(64) float P_tmp[256];
    __aligned(64) float dPdu_tmp[256];
    __aligned(64) float dPdv_tmp[256];
    __aligned(64) float ddPdudu_tmp[256];
    __aligned(64) float ddPdvdv_tmp[256];
    __aligned(64) float ddPdudv_tmp[256];

    float* Pt = P ? P_tmp : nullptr;
    float* dPdut = nullptr, *dPdvt = nullptr;
    if (dPdu) { dPdut = dPdu_tmp; dPdvt = dPdv_tmp; }
    float* ddPdudut = nullptr, *ddPdvdvt = nullptr, *ddPdudvt = nullptr;
    if (ddPdudu) { ddPdudut = ddPdudu_tmp; ddPdvdvt = ddPdvdv_tmp; ddPdudvt = ddPdudv_tmp; }
    
    for (unsigned int i=0; i<N; i++)
    {
      if (valid && !valid[i]) continue;

      RTCInterpolateArguments iargs;
      iargs.primID = primIDs[i];
      iargs.u = u[i];
      iargs.v = v[i];
      iargs.bufferType = bufferType;
      iargs.bufferSlot = bufferSlot;
      iargs.P = Pt;
      iargs.dPdu = dPdut;
      iargs.dPdv = dPdvt;
      iargs.ddPdudu = ddPdudut;
      iargs.ddPdvdv = ddPdvdvt;
      iargs.ddPdudv = ddPdudvt;
      iargs.valueCount = valueCount;
      interpolate(&iargs);
      
      if (likely(P)) {
        for (unsigned int j=0; j<valueCount; j++) 
          P[j*N+i] = Pt[j];
      }
      if (likely(dPdu)) 
      {
        for (unsigned int j=0; j<valueCount; j++) {
          dPdu[j*N+i] = dPdut[j];
          dPdv[j*N+i] = dPdvt[j];
        }
      }
      if (likely(ddPdudu)) 
      {
        for (unsigned int j=0; j<valueCount; j++) {
          ddPdudu[j*N+i] = ddPdudut[j];
          ddPdvdv[j*N+i] = ddPdvdvt[j];
          ddPdudv[j*N+i] = ddPdudvt[j];
        }
      }
    }
  }
    
  bool Geometry::pointQuery(PointQuery* query, PointQueryContext* context)
  {
    assert(context->primID < size());
   
    RTCPointQueryFunctionArguments args;
    args.query           = (RTCPointQuery*)context->query_ws;
    args.userPtr         = context->userPtr;
    args.primID          = context->primID;
    args.geomID          = context->geomID;
    args.context         = context->userContext;
    args.similarityScale = context->similarityScale;
    
    bool update = false;
    if(context->func)  update |= context->func(&args);
    if(pointQueryFunc) update |= pointQueryFunc(&args);

    if (update && context->userContext->instStackSize > 0)
    {
      // update point query
      if (context->query_type == POINT_QUERY_TYPE_AABB) {
        context->updateAABB();
      } else {
        assert(context->similarityScale > 0.f);
        query->radius = context->query_ws->radius * context->similarityScale;
      }
    }
    return update;
  }
}
