/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkUnstructuredGridPreIntegration.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/

/*
 * Copyright 2004 Sandia Corporation.
 * Under the terms of Contract DE-AC04-94AL85000, there is a non-exclusive
 * license for use of this work by or on behalf of the
 * U.S. Government. Redistribution and use in source and binary forms, with
 * or without modification, are permitted provided that this Notice and any
 * statement of authorship are reproduced on all copies.
 */

#include "vtkUnstructuredGridPreIntegration.h"

#include "vtkAbstractVolumeMapper.h"
#include "vtkDoubleArray.h"
#include "vtkObjectFactory.h"
#include "vtkUnstructuredGrid.h"
#include "vtkUnstructuredGridPartialPreIntegration.h"
#include "vtkVolume.h"
#include "vtkVolumeProperty.h"

#include <algorithm>
#include <cmath>

//------------------------------------------------------------------------------

vtkStandardNewMacro(vtkUnstructuredGridPreIntegration);

vtkCxxSetObjectMacro(
  vtkUnstructuredGridPreIntegration, Integrator, vtkUnstructuredGridVolumeRayIntegrator);

//------------------------------------------------------------------------------

vtkUnstructuredGridPreIntegration::vtkUnstructuredGridPreIntegration()
{
  this->Integrator = vtkUnstructuredGridPartialPreIntegration::New();
  this->Property = nullptr;

  this->NumComponents = 0;
  this->IntegrationTable = nullptr;
  this->IntegrationTableScalarShift = nullptr;
  this->IntegrationTableScalarScale = nullptr;

  this->IntegrationTableScalarResolution = 128;
  this->IntegrationTableLengthResolution = 256;

  this->IncrementalPreIntegration = 1;
  this->IntegrationTableLengthScale = 0;
}

vtkUnstructuredGridPreIntegration::~vtkUnstructuredGridPreIntegration()
{
  this->SetIntegrator(nullptr);

  if (this->IntegrationTable)
  {
    for (int i = 0; i < this->NumComponents; i++)
    {
      delete[] this->IntegrationTable[i];
    }
    delete[] this->IntegrationTable;
  }
  delete[] this->IntegrationTableScalarShift;
  delete[] this->IntegrationTableScalarScale;
}

void vtkUnstructuredGridPreIntegration::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  os << indent << "Integrator: " << this->Integrator << endl;

  os << indent << "IntegrationTableScalarResolution: " << this->IntegrationTableScalarResolution
     << endl;
  os << indent << "IntegrationTableLengthResolution: " << this->IntegrationTableLengthResolution
     << endl;

  os << indent << "IncrementalPreIntegration: " << this->IncrementalPreIntegration << endl;
}

//------------------------------------------------------------------------------

double vtkUnstructuredGridPreIntegration::GetIntegrationTableScalarShift(int component)
{
  return this->IntegrationTableScalarShift[component];
}

double vtkUnstructuredGridPreIntegration::GetIntegrationTableScalarScale(int component)
{
  return this->IntegrationTableScalarScale[component];
}

double vtkUnstructuredGridPreIntegration::GetIntegrationTableLengthScale()
{
  return this->IntegrationTableLengthScale;
}

float* vtkUnstructuredGridPreIntegration::GetPreIntegrationTable(int component)
{
  return this->IntegrationTable[component];
}

//------------------------------------------------------------------------------

void vtkUnstructuredGridPreIntegration::BuildPreIntegrationTables(vtkDataArray* scalars)
{
  // Delete old tables.
  if (this->IntegrationTable)
  {
    for (int i = 0; i < this->NumComponents; i++)
    {
      delete[] this->IntegrationTable[i];
    }
    delete[] this->IntegrationTable;
  }
  delete[] this->IntegrationTableScalarShift;
  delete[] this->IntegrationTableScalarScale;

  this->NumComponents = scalars->GetNumberOfComponents();

  // Establish temporary inputs to integrator.
  vtkVolume* tmpVolume = vtkVolume::New();
  vtkVolumeProperty* tmpProperty = vtkVolumeProperty::New();
  vtkDataArray* tmpScalars = vtkDataArray::CreateDataArray(scalars->GetDataType());

  tmpVolume->SetMapper(this->Volume->GetMapper());
  tmpVolume->SetProperty(tmpProperty);

  tmpProperty->IndependentComponentsOn();
  tmpProperty->SetInterpolationType(this->Property->GetInterpolationType());

  tmpScalars->SetNumberOfComponents(1);
  tmpScalars->SetNumberOfTuples(2);

  vtkDoubleArray* tmpIntersectionLengths = vtkDoubleArray::New();
  vtkDataArray* tmpNearIntersections = vtkDataArray::CreateDataArray(scalars->GetDataType());
  vtkDataArray* tmpFarIntersections = vtkDataArray::CreateDataArray(scalars->GetDataType());

  tmpIntersectionLengths->SetNumberOfComponents(1);
  tmpIntersectionLengths->SetNumberOfTuples(1);
  tmpNearIntersections->SetNumberOfComponents(1);
  tmpNearIntersections->SetNumberOfTuples(1);
  tmpFarIntersections->SetNumberOfComponents(1);
  tmpFarIntersections->SetNumberOfTuples(1);

  this->IntegrationTable = new float*[this->NumComponents];
  this->IntegrationTableScalarShift = new double[this->NumComponents];
  this->IntegrationTableScalarScale = new double[this->NumComponents];
  // Note that the scale set up such that a length of (this->MaxLength +
  // epsilon + 0.5) will scale to this->IntegrationTableLengthResolution-1.
  // Similar scaling is performed for the other dimensions of the
  // pre-integration table.
  this->IntegrationTableLengthScale =
    (this->IntegrationTableLengthResolution - 1) / this->MaxLength;

  // We only do computations at one length.
  float d_length = (float)(1.0 / this->IntegrationTableLengthScale);

  for (int component = 0; component < this->NumComponents; component++)
  {
    int d_idx, sb_idx, sf_idx;

    // Allocate table.
    try
    {
      this->IntegrationTable[component] = new float[4 * this->IntegrationTableScalarResolution *
        this->IntegrationTableScalarResolution * this->IntegrationTableLengthResolution];
    }
    catch (...)
    {
      this->IntegrationTable[component] = nullptr;
    }

    if (this->IntegrationTable[component] == nullptr)
    {
      // Could not allocate memory for table.
      if ((this->IntegrationTableScalarResolution > 32) ||
        (this->IntegrationTableLengthResolution > 64))
      {
        vtkWarningMacro("Could not allocate integration table.\n"
                        "Reducing the table size and trying again.");
        for (int i = 0; i < component; i++)
        {
          delete[] this->IntegrationTable[i];
        }
        delete[] this->IntegrationTable;
        this->IntegrationTable = nullptr;

        this->IntegrationTableScalarResolution = 32;
        this->IntegrationTableLengthResolution = 64;
        this->BuildPreIntegrationTables(scalars);
      }
      else
      {
        vtkErrorMacro("Could not allocate integration table.");
      }
      break;
    }

    // Determine scale and shift.
    double* range = scalars->GetRange(component);
    if (range[0] == range[1])
    {
      // Unusual case where the scalars are all the same.
      this->IntegrationTableScalarScale[component] = 1.0;
    }
    else
    {
      this->IntegrationTableScalarScale[component] =
        (this->IntegrationTableScalarResolution - 1) / (range[1] - range[0]);
    }
    this->IntegrationTableScalarShift[component] =
      -range[0] * this->IntegrationTableScalarScale[component];

    // Set values for d=0 (they are all zero).
    float* c = this->IntegrationTable[component];
    for (sb_idx = 0; sb_idx < this->IntegrationTableScalarResolution; sb_idx++)
    {
      for (sf_idx = 0; sf_idx < this->IntegrationTableScalarResolution; sf_idx++)
      {
        c[0] = c[1] = c[2] = c[3] = 0.0f;
        c += 4;
      }
    }

    // Initialize integrator.
    if (this->Property->GetColorChannels(component) == 3)
    {
      tmpProperty->SetColor(this->Property->GetRGBTransferFunction(component));
    }
    else
    {
      tmpProperty->SetColor(this->Property->GetGrayTransferFunction(component));
    }
    tmpProperty->SetScalarOpacity(this->Property->GetScalarOpacity(component));
    tmpProperty->SetScalarOpacityUnitDistance(
      this->Property->GetScalarOpacityUnitDistance(component));
    tmpProperty->SetShade(this->Property->GetShade(component));
    tmpProperty->SetAmbient(this->Property->GetAmbient(component));
    tmpProperty->SetDiffuse(this->Property->GetDiffuse(component));
    tmpProperty->SetSpecular(this->Property->GetSpecular(component));
    tmpProperty->SetSpecularPower(this->Property->GetSpecularPower(component));
    tmpScalars->SetTuple1(0, range[0]);
    tmpScalars->SetTuple1(1, range[1]);
    this->Integrator->Initialize(tmpVolume, tmpScalars);

    // Set values for next smallest d (the base values).
    tmpIntersectionLengths->SetTuple1(0, d_length);
    for (sb_idx = 0; sb_idx < this->IntegrationTableScalarResolution; sb_idx++)
    {
      double sb = (sb_idx - this->IntegrationTableScalarShift[component]) /
        (this->IntegrationTableScalarScale[component]);
      tmpFarIntersections->SetTuple1(0, sb);
      for (sf_idx = 0; sf_idx < this->IntegrationTableScalarResolution; sf_idx++)
      {
        double sf = (sf_idx - this->IntegrationTableScalarShift[component]) /
          (this->IntegrationTableScalarScale[component]);
        tmpNearIntersections->SetTuple1(0, sf);
        c[0] = c[1] = c[2] = c[3] = 0;
        this->Integrator->Integrate(
          tmpIntersectionLengths, tmpNearIntersections, tmpFarIntersections, c);
        c += 4;
      }
    }

    // Set rest of values using other values in table.
    if (this->IncrementalPreIntegration)
    {
      for (d_idx = 2; d_idx < this->IntegrationTableLengthResolution; d_idx++)
      {
        for (sb_idx = 0; sb_idx < this->IntegrationTableScalarResolution; sb_idx++)
        {
          for (sf_idx = 0; sf_idx < this->IntegrationTableScalarResolution; sf_idx++)
          {
            // We are going to perform incremental pre-integration.  To do
            // this, we compute the integration of a ray from sf to sb of
            // length d by combining two entries in the table.  The first
            // entry will be from sf to sm of length delta d (the smallest
            // non-zero length stored in the table).  The second entry will
            // be from sm to sb of length d - delta d.  See Weiler, et
            // al. "Hardware-Based Ray Casting for Tetrahedral Meshes" for
            // more details.
            int sm_idx = ((d_idx - 1) * sf_idx + sb_idx + d_idx / 2) / d_idx;

            float* colorf = this->GetIndexedTableEntry(sf_idx, sm_idx, 1, component);
            float* colorb = this->GetIndexedTableEntry(sm_idx, sb_idx, d_idx - 1, component);

            c[0] = colorf[0] + colorb[0] * (1.0f - colorf[3]);
            c[1] = colorf[1] + colorb[1] * (1.0f - colorf[3]);
            c[2] = colorf[2] + colorb[2] * (1.0f - colorf[3]);
            c[3] = colorf[3] + colorb[3] * (1.0f - colorf[3]);
            c += 4;
          }
        }
      }
    }
    else
    {
      for (d_idx = 2; d_idx < this->IntegrationTableLengthResolution; d_idx++)
      {
        for (sb_idx = 0; sb_idx < this->IntegrationTableScalarResolution; sb_idx++)
        {
          for (sf_idx = 0; sf_idx < this->IntegrationTableScalarResolution; sf_idx++)
          {
            // Compute the integration table the old-fashioned slow way.
            float length = d_idx * d_length;
            float sb = (float)((sb_idx - this->IntegrationTableScalarShift[component]) /
              (this->IntegrationTableScalarScale[component]));
            float sf = (float)((sf_idx - this->IntegrationTableScalarShift[component]) /
              (this->IntegrationTableScalarScale[component]));
            tmpIntersectionLengths->SetTuple1(0, length);
            tmpFarIntersections->SetTuple1(0, sb);
            tmpNearIntersections->SetTuple1(0, sf);
            c[0] = c[1] = c[2] = c[3] = 0.0f;
            this->Integrator->Integrate(
              tmpIntersectionLengths, tmpNearIntersections, tmpFarIntersections, c);
            c += 4;
          }
        }
      }
    }
  }

  // Get rid of temporary data.
  tmpVolume->Delete();
  tmpProperty->Delete();
  tmpScalars->Delete();

  tmpIntersectionLengths->Delete();
  tmpNearIntersections->Delete();
  tmpFarIntersections->Delete();
}

//------------------------------------------------------------------------------

void vtkUnstructuredGridPreIntegration::Initialize(vtkVolume* volume, vtkDataArray* scalars)
{
  vtkVolumeProperty* property = volume->GetProperty();

  if ((property == this->Property) && (this->IntegrationTableBuilt > property->GetMTime()) &&
    (this->IntegrationTableBuilt > this->MTime))
  {
    // Nothing changed from the last time Initialize was run.
    return;
  }

  this->Property = property;
  this->Volume = volume;
  this->IntegrationTableBuilt.Modified();

  if (!property->GetIndependentComponents())
  {
    vtkErrorMacro("Cannot store dependent components in pre-integration table.");
    return;
  }

  // Determine the maximum possible length of a ray segment.
  vtkDataSet* input = volume->GetMapper()->GetDataSetInput();
  vtkIdType numcells = input->GetNumberOfCells();
  this->MaxLength = 0;
  for (vtkIdType i = 0; i < numcells; i++)
  {
    double cellbounds[6];
    input->GetCellBounds(i, cellbounds);
#define SQR(x) ((x) * (x))
    double diagonal_length = sqrt(SQR(cellbounds[1] - cellbounds[0]) +
      SQR(cellbounds[3] - cellbounds[2]) + SQR(cellbounds[5] - cellbounds[4]));
#undef SQR
    if (diagonal_length > this->MaxLength)
    {
      this->MaxLength = diagonal_length;
    }
  }

  this->BuildPreIntegrationTables(scalars);
}

//------------------------------------------------------------------------------

void vtkUnstructuredGridPreIntegration::Integrate(vtkDoubleArray* intersectionLengths,
  vtkDataArray* nearIntersections, vtkDataArray* farIntersections, float color[4])
{
  vtkIdType numIntersections = intersectionLengths->GetNumberOfTuples();

  for (vtkIdType i = 0; i < numIntersections; i++)
  {
    float* c = this->GetTableEntry(nearIntersections->GetComponent(i, 0),
      farIntersections->GetComponent(i, 0), intersectionLengths->GetComponent(i, 0), 0);
    float newcolor[4] = { c[0], c[1], c[2], c[3] };
    for (int component = 1; component < this->NumComponents; component++)
    {
      c = this->GetTableEntry(nearIntersections->GetComponent(i, component),
        farIntersections->GetComponent(i, component), intersectionLengths->GetComponent(i, 0),
        component);
      // The blending I'm using is a combination of porter and duff xors
      // and ins.
      float coef1 = 1.0f - 0.5f * c[3];
      float coef2 = 1.0f - 0.5f * newcolor[3];
      newcolor[0] = newcolor[0] * coef1 + c[0] * coef2;
      newcolor[1] = newcolor[1] * coef1 + c[1] * coef2;
      newcolor[2] = newcolor[2] * coef1 + c[2] * coef2;
      newcolor[3] = newcolor[3] * coef1 + c[3] * coef2;
    }

    float coef = 1.0f - color[3];
    color[0] += newcolor[0] * coef;
    color[1] += newcolor[1] * coef;
    color[2] += newcolor[2] * coef;
    color[3] += newcolor[3] * coef;
  }
}
