// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkLandmarkTransform.h"

#include "vtkMath.h"
#include "vtkMatrix4x4.h"
#include "vtkObjectFactory.h"
#include "vtkPoints.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkLandmarkTransform);

//------------------------------------------------------------------------------
vtkLandmarkTransform::vtkLandmarkTransform()
{
  this->Mode = VTK_LANDMARK_SIMILARITY;
  this->SourceLandmarks = nullptr;
  this->TargetLandmarks = nullptr;
}

//------------------------------------------------------------------------------
vtkLandmarkTransform::~vtkLandmarkTransform()
{
  if (this->SourceLandmarks)
  {
    this->SourceLandmarks->Delete();
  }
  if (this->TargetLandmarks)
  {
    this->TargetLandmarks->Delete();
  }
}

//------------------------------------------------------------------------------
void vtkLandmarkTransform::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << "Mode: " << this->GetModeAsString() << "\n";
  os << "SourceLandmarks: " << this->SourceLandmarks << "\n";
  if (this->SourceLandmarks)
  {
    this->SourceLandmarks->PrintSelf(os, indent.GetNextIndent());
  }
  os << "TargetLandmarks: " << this->TargetLandmarks << "\n";
  if (this->TargetLandmarks)
  {
    this->TargetLandmarks->PrintSelf(os, indent.GetNextIndent());
  }
}

//------------------------------------------------------------------------------
// Update the 4x4 matrix. Updates are only done as necessary.

void vtkLandmarkTransform::InternalUpdate()
{
  vtkIdType i;
  int j;

  if (this->SourceLandmarks == nullptr || this->TargetLandmarks == nullptr)
  {
    this->Matrix->Identity();
    return;
  }

  // --- compute the necessary transform to match the two sets of landmarks ---

  /*
    The solution is based on
    Berthold K. P. Horn (1987),
    "Closed-form solution of absolute orientation using unit quaternions,"
    Journal of the Optical Society of America A, 4:629-642
  */

  // Original python implementation by David G. Gobbi

  const vtkIdType N_PTS = this->SourceLandmarks->GetNumberOfPoints();
  if (N_PTS != this->TargetLandmarks->GetNumberOfPoints())
  {
    vtkErrorMacro("Update: Source and Target Landmarks contain a different number of points");
    return;
  }

  // -- if no points, stop here

  if (N_PTS == 0)
  {
    this->Matrix->Identity();
    return;
  }

  // -- find the centroid of each set --

  double source_centroid[3] = { 0, 0, 0 };
  double target_centroid[3] = { 0, 0, 0 };
  double p[3];
  for (i = 0; i < N_PTS; i++)
  {
    this->SourceLandmarks->GetPoint(i, p);
    source_centroid[0] += p[0];
    source_centroid[1] += p[1];
    source_centroid[2] += p[2];
    this->TargetLandmarks->GetPoint(i, p);
    target_centroid[0] += p[0];
    target_centroid[1] += p[1];
    target_centroid[2] += p[2];
  }
  source_centroid[0] /= N_PTS;
  source_centroid[1] /= N_PTS;
  source_centroid[2] /= N_PTS;
  target_centroid[0] /= N_PTS;
  target_centroid[1] /= N_PTS;
  target_centroid[2] /= N_PTS;

  // -- if only one point, stop right here

  if (N_PTS == 1)
  {
    this->Matrix->Identity();
    this->Matrix->Element[0][3] = target_centroid[0] - source_centroid[0];
    this->Matrix->Element[1][3] = target_centroid[1] - source_centroid[1];
    this->Matrix->Element[2][3] = target_centroid[2] - source_centroid[2];
    return;
  }

  // -- build the 3x3 matrix M --

  double M[3][3];
  double AAT[3][3];
  for (i = 0; i < 3; i++)
  {
    AAT[i][0] = M[i][0] = 0.0F; // fill M with zeros
    AAT[i][1] = M[i][1] = 0.0F;
    AAT[i][2] = M[i][2] = 0.0F;
  }
  vtkIdType pt;
  double a[3], b[3];
  double sa = 0.0F, sb = 0.0F;
  for (pt = 0; pt < N_PTS; pt++)
  {
    // get the origin-centred point (a) in the source set
    this->SourceLandmarks->GetPoint(pt, a);
    a[0] -= source_centroid[0];
    a[1] -= source_centroid[1];
    a[2] -= source_centroid[2];
    // get the origin-centred point (b) in the target set
    this->TargetLandmarks->GetPoint(pt, b);
    b[0] -= target_centroid[0];
    b[1] -= target_centroid[1];
    b[2] -= target_centroid[2];
    // accumulate the products a*T(b) into the matrix M
    for (i = 0; i < 3; i++)
    {
      M[i][0] += a[i] * b[0];
      M[i][1] += a[i] * b[1];
      M[i][2] += a[i] * b[2];

      // for the affine transform, compute ((a.a^t)^-1 . a.b^t)^t.
      // a.b^t is already in M.  here we put a.a^t in AAT.
      if (this->Mode == VTK_LANDMARK_AFFINE)
      {
        AAT[i][0] += a[i] * a[0];
        AAT[i][1] += a[i] * a[1];
        AAT[i][2] += a[i] * a[2];
      }
    }
    // accumulate scale factors (if desired)
    sa += a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
    sb += b[0] * b[0] + b[1] * b[1] + b[2] * b[2];
  }

  // if source or destination is degenerate then only report
  // translation
  if (sa == 0.0 || sb == 0.0)
  {
    this->Matrix->Identity();
    this->Matrix->Element[0][3] = target_centroid[0] - source_centroid[0];
    this->Matrix->Element[1][3] = target_centroid[1] - source_centroid[1];
    this->Matrix->Element[2][3] = target_centroid[2] - source_centroid[2];
    return;
  }

  if (this->Mode == VTK_LANDMARK_AFFINE)
  {
    // AAT = (a.a^t)^-1
    vtkMath::Invert3x3(AAT, AAT);

    // M = (a.a^t)^-1 . a.b^t
    vtkMath::Multiply3x3(AAT, M, M);

    // this->Matrix = M^t
    for (i = 0; i < 3; ++i)
    {
      for (j = 0; j < 3; ++j)
      {
        this->Matrix->Element[i][j] = M[j][i];
      }
    }
  }
  else
  {
    // compute required scaling factor (if desired)
    double scale = sqrt(sb / sa);

    // -- build the 4x4 matrix N --

    double Ndata[4][4];
    double* N[4];
    for (i = 0; i < 4; i++)
    {
      N[i] = Ndata[i];
      N[i][0] = 0.0F; // fill N with zeros
      N[i][1] = 0.0F;
      N[i][2] = 0.0F;
      N[i][3] = 0.0F;
    }
    // on-diagonal elements
    N[0][0] = M[0][0] + M[1][1] + M[2][2];
    N[1][1] = M[0][0] - M[1][1] - M[2][2];
    N[2][2] = -M[0][0] + M[1][1] - M[2][2];
    N[3][3] = -M[0][0] - M[1][1] + M[2][2];
    // off-diagonal elements
    N[0][1] = N[1][0] = M[1][2] - M[2][1];
    N[0][2] = N[2][0] = M[2][0] - M[0][2];
    N[0][3] = N[3][0] = M[0][1] - M[1][0];

    N[1][2] = N[2][1] = M[0][1] + M[1][0];
    N[1][3] = N[3][1] = M[2][0] + M[0][2];
    N[2][3] = N[3][2] = M[1][2] + M[2][1];

    // -- eigen-decompose N (is symmetric) --

    double eigenvectorData[4][4];
    double *eigenvectors[4], eigenvalues[4];

    eigenvectors[0] = eigenvectorData[0];
    eigenvectors[1] = eigenvectorData[1];
    eigenvectors[2] = eigenvectorData[2];
    eigenvectors[3] = eigenvectorData[3];

    vtkMath::JacobiN(N, 4, eigenvalues, eigenvectors);

    // the eigenvector with the largest eigenvalue is the quaternion we want
    // (they are sorted in decreasing order for us by JacobiN)
    double w, x, y, z;

    // first: if points are collinear, choose the quaternion that
    // results in the smallest rotation.
    if (eigenvalues[0] == eigenvalues[1] || N_PTS == 2)
    {
      double s0[3], t0[3], s1[3], t1[3];
      this->SourceLandmarks->GetPoint(0, s0);
      this->TargetLandmarks->GetPoint(0, t0);
      this->SourceLandmarks->GetPoint(1, s1);
      this->TargetLandmarks->GetPoint(1, t1);

      double ds[3], dt[3];
      double rs = 0, rt = 0;
      for (i = 0; i < 3; i++)
      {
        ds[i] = s1[i] - s0[i]; // vector between points
        rs += ds[i] * ds[i];
        dt[i] = t1[i] - t0[i];
        rt += dt[i] * dt[i];
      }

      // normalize the two vectors
      rs = sqrt(rs);
      ds[0] /= rs;
      ds[1] /= rs;
      ds[2] /= rs;
      rt = sqrt(rt);
      dt[0] /= rt;
      dt[1] /= rt;
      dt[2] /= rt;

      // take dot & cross product
      w = ds[0] * dt[0] + ds[1] * dt[1] + ds[2] * dt[2];
      x = ds[1] * dt[2] - ds[2] * dt[1];
      y = ds[2] * dt[0] - ds[0] * dt[2];
      z = ds[0] * dt[1] - ds[1] * dt[0];

      double r = sqrt(x * x + y * y + z * z);
      double theta = atan2(r, w);

      // construct quaternion
      w = cos(theta / 2);
      if (r != 0)
      {
        r = sin(theta / 2) / r;
        x = x * r;
        y = y * r;
        z = z * r;
      }
      else // rotation by 180 degrees: special case
      {
        // rotate around a vector perpendicular to ds
        vtkMath::Perpendiculars(ds, dt, nullptr, 0);
        r = sin(theta / 2);
        x = dt[0] * r;
        y = dt[1] * r;
        z = dt[2] * r;
      }
    }
    else // points are not collinear
    {
      w = eigenvectors[0][0];
      x = eigenvectors[1][0];
      y = eigenvectors[2][0];
      z = eigenvectors[3][0];
    }

    // convert quaternion to a rotation matrix

    double ww = w * w;
    double wx = w * x;
    double wy = w * y;
    double wz = w * z;

    double xx = x * x;
    double yy = y * y;
    double zz = z * z;

    double xy = x * y;
    double xz = x * z;
    double yz = y * z;

    this->Matrix->Element[0][0] = ww + xx - yy - zz;
    this->Matrix->Element[1][0] = 2.0 * (wz + xy);
    this->Matrix->Element[2][0] = 2.0 * (-wy + xz);

    this->Matrix->Element[0][1] = 2.0 * (-wz + xy);
    this->Matrix->Element[1][1] = ww - xx + yy - zz;
    this->Matrix->Element[2][1] = 2.0 * (wx + yz);

    this->Matrix->Element[0][2] = 2.0 * (wy + xz);
    this->Matrix->Element[1][2] = 2.0 * (-wx + yz);
    this->Matrix->Element[2][2] = ww - xx - yy + zz;

    if (this->Mode != VTK_LANDMARK_RIGIDBODY)
    { // add in the scale factor (if desired)
      for (i = 0; i < 3; i++)
      {
        this->Matrix->Element[i][0] *= scale;
        this->Matrix->Element[i][1] *= scale;
        this->Matrix->Element[i][2] *= scale;
      }
    }
  }

  // the translation is given by the difference in the transformed source
  // centroid and the target centroid
  double sx, sy, sz;

  sx = this->Matrix->Element[0][0] * source_centroid[0] +
    this->Matrix->Element[0][1] * source_centroid[1] +
    this->Matrix->Element[0][2] * source_centroid[2];
  sy = this->Matrix->Element[1][0] * source_centroid[0] +
    this->Matrix->Element[1][1] * source_centroid[1] +
    this->Matrix->Element[1][2] * source_centroid[2];
  sz = this->Matrix->Element[2][0] * source_centroid[0] +
    this->Matrix->Element[2][1] * source_centroid[1] +
    this->Matrix->Element[2][2] * source_centroid[2];

  this->Matrix->Element[0][3] = target_centroid[0] - sx;
  this->Matrix->Element[1][3] = target_centroid[1] - sy;
  this->Matrix->Element[2][3] = target_centroid[2] - sz;

  // fill the bottom row of the 4x4 matrix
  this->Matrix->Element[3][0] = 0.0;
  this->Matrix->Element[3][1] = 0.0;
  this->Matrix->Element[3][2] = 0.0;
  this->Matrix->Element[3][3] = 1.0;

  this->Matrix->Modified();
}

//------------------------------------------------------------------------------
vtkMTimeType vtkLandmarkTransform::GetMTime()
{
  vtkMTimeType result = this->vtkLinearTransform::GetMTime();
  vtkMTimeType mtime;

  if (this->SourceLandmarks)
  {
    mtime = this->SourceLandmarks->GetMTime();
    result = std::max(mtime, result);
  }
  if (this->TargetLandmarks)
  {
    mtime = this->TargetLandmarks->GetMTime();
    result = std::max(mtime, result);
  }
  return result;
}
//------------------------------------------------------------------------------
void vtkLandmarkTransform::SetSourceLandmarks(vtkPoints* source)
{
  if (this->SourceLandmarks == source)
  {
    return;
  }

  if (this->SourceLandmarks)
  {
    this->SourceLandmarks->Delete();
  }

  source->Register(this);
  this->SourceLandmarks = source;

  this->Modified();
}

//------------------------------------------------------------------------------
void vtkLandmarkTransform::SetTargetLandmarks(vtkPoints* target)
{
  if (this->TargetLandmarks == target)
  {
    return;
  }

  if (this->TargetLandmarks)
  {
    this->TargetLandmarks->Delete();
  }

  target->Register(this);
  this->TargetLandmarks = target;
  this->Modified();
}

//------------------------------------------------------------------------------
void vtkLandmarkTransform::Inverse()
{
  vtkPoints* tmp1 = this->SourceLandmarks;
  vtkPoints* tmp2 = this->TargetLandmarks;
  this->TargetLandmarks = tmp1;
  this->SourceLandmarks = tmp2;
  this->Modified();
}

//------------------------------------------------------------------------------
vtkAbstractTransform* vtkLandmarkTransform::MakeTransform()
{
  return vtkLandmarkTransform::New();
}

//------------------------------------------------------------------------------
void vtkLandmarkTransform::InternalDeepCopy(vtkAbstractTransform* transform)
{
  vtkLandmarkTransform* t = (vtkLandmarkTransform*)transform;

  this->SetMode(t->Mode);
  this->SetSourceLandmarks(t->SourceLandmarks);
  this->SetTargetLandmarks(t->TargetLandmarks);

  this->Modified();
}
VTK_ABI_NAMESPACE_END
