text: /*============================================================================
  
  The Medical Imaging Interaction Toolkit (MITK)
  
  Copyright (c) German Cancer Research Center (DKFZ)
  All rights reserved.
  
  Use of this source code is governed by a 3-clause BSD license that can be
  found in the LICENSE file.
  
  ============================================================================*/
  
  #include "mitkPlanarCross.h"
  #include "mitkPlaneGeometry.h"
  #include "mitkProperties.h"
  
  mitk::PlanarCross::PlanarCross()
    : FEATURE_ID_LONGESTDIAMETER(this->AddFeature("Longest Axis", "mm")),
      FEATURE_ID_SHORTAXISDIAMETER(this->AddFeature("Short Axis", "mm"))
  {
    // Cross has two control points at the beginning
    this->ResetNumberOfControlPoints(2);
  
    // Create property for SingleLineMode (default: false)
    this->SetProperty("SingleLineMode", mitk::BoolProperty::New(false));
  
    // Create helper polyline object (for drawing the orthogonal orientation line)
    this->SetNumberOfHelperPolyLines(1);
    m_HelperPolyLinesToBePainted->InsertElement(0, false);
  }
  
  void mitk::PlanarCross::SetSingleLineMode(bool singleLineMode)
  {
    this->SetProperty("SingleLineMode", mitk::BoolProperty::New(singleLineMode));
    this->Modified();
  }
  
  bool mitk::PlanarCross::GetSingleLineMode() const
  {
    const mitk::BoolProperty *singleLineMode =
      dynamic_cast<mitk::BoolProperty *>(this->GetProperty("SingleLineMode").GetPointer());
  
    if (singleLineMode != nullptr)
    {
      return singleLineMode->GetValue();
    }
    return false;
  }
  
  bool mitk::PlanarCross::ResetOnPointSelectNeeded() const
  {
    return this->GetSingleLineMode() == false || (m_SelectedControlPoint >= 0 && m_SelectedControlPoint <= 3);
  }
  
  bool mitk::PlanarCross::ResetOnPointSelect()
  {
    if (this->GetSingleLineMode())
    {
      // In single line mode --> nothing to reset
      return false;
    }
  
    switch (m_SelectedControlPoint)
    {
      default:
        // Nothing selected --> nothing to reset
        return false;
      case 0:
        // Control point 0 selected: exchange points 0 and 1
        {
          const Point2D tmpPoint = this->GetControlPoint(0);
          this->SetControlPoint(0, this->GetControlPoint(1));
          this->SetControlPoint(1, tmpPoint);
        }
        // FALLTHRU!
      case 1:
        // Control point 0 or 1 selected: reset number of control points to two
        this->ResetNumberOfControlPoints(2);
        this->SelectControlPoint(1);
        return true;
      case 2:
        // Control point 2 selected: replace point 0 with point 3 and point 1 with point 2
        this->SetControlPoint(0, this->GetControlPoint(3));
        this->SetControlPoint(1, this->GetControlPoint(2));
  
        // Adjust selected control point, reset number of control points to two
        this->ResetNumberOfControlPoints(2);
        this->SelectControlPoint(1);
        return true;
      case 3:
        // Control point 3 selected: replace point 0 with point 2 and point 1 with point 3
  
        this->SetControlPoint(0, this->GetControlPoint(2));
        this->SetControlPoint(1, this->GetControlPoint(3));
  
        // Adjust selected control point, reset number of control points to two
        this->ResetNumberOfControlPoints(2);
        this->SelectControlPoint(1);
        return true;
    }
  }
  
  unsigned int mitk::PlanarCross::GetNumberOfFeatures() const
  {
    if (this->GetSingleLineMode() || (this->GetNumberOfControlPoints() < 4))
    {
      return 1;
    }
    else
    {
      return 2;
    }
  }
  
  mitk::Point2D mitk::PlanarCross::ApplyControlPointConstraints(unsigned int index, const Point2D &point)
  {
    // Apply spatial constraints from superclass and from this class until the resulting constrained
    // point converges. Although not an optimal implementation, this iterative approach
    // helps to respect both constraints from the superclass and from this class. Without this,
    // situations may occur where control points are constrained by the superclass, but again
    // moved out of the superclass bounds by the subclass, or vice versa.
  
    unsigned int count = 0; // ensures stop of approach if point does not converge in reasonable time
    Point2D confinedPoint = point;
    Point2D superclassConfinedPoint;
    do
    {
      superclassConfinedPoint = Superclass::ApplyControlPointConstraints(index, confinedPoint);
      confinedPoint = this->InternalApplyControlPointConstraints(index, superclassConfinedPoint);
      ++count;
    } while ((confinedPoint.EuclideanDistanceTo(superclassConfinedPoint) > mitk::eps) && (count < 32));
  
    return confinedPoint;
  }
  
  mitk::Point2D mitk::PlanarCross::InternalApplyControlPointConstraints(unsigned int index, const Point2D &point)
  {
    // Apply constraints depending on current interaction state
    switch (index)
    {
      case 2:
      {
        // Check if 3rd control point is outside of the range (2D area) defined by the first
        // line (via the first two control points); if it is outside, clip it to the bounds
        const Point2D p1 = this->GetControlPoint(0);
        const Point2D p2 = this->GetControlPoint(1);
  
        Vector2D n1 = p2 - p1;
        n1.Normalize();
  
        const Vector2D v1 = point - p1;
        const double dotProduct = n1 * v1;
        const Point2D crossPoint = p1 + n1 * dotProduct;
        ;
        const Vector2D crossVector = point - crossPoint;
  
        if (dotProduct < 0.0)
        {
          // Out-of-bounds on the left: clip point to left boundary
          return (p1 + crossVector);
        }
        else if (dotProduct > p2.EuclideanDistanceTo(p1))
        {
          // Out-of-bounds on the right: clip point to right boundary
          return (p2 + crossVector);
        }
        else
        {
          // Pass back original point
          return point;
        }
      }
  
      case 3:
      {
        // Constrain 4th control point so that with the 3rd control point it forms
        // a line orthogonal to the first line (constraint 1); the 4th control point
        // must lie on the opposite side of the line defined by the first two control
        // points than the 3rd control point (constraint 2)
        const Point2D p1 = this->GetControlPoint(0);
        const Point2D p2 = this->GetControlPoint(1);
        const Point2D p3 = this->GetControlPoint(2);
  
        // Calculate distance of original point from orthogonal line the corrected
        // point should lie on to project the point onto this line
        Vector2D n1 = p2 - p1;
        n1.Normalize();
  
        const Vector2D v1 = point - p3;
        const double dotProduct1 = n1 * v1;
  
        const Point2D pointOnLine = point - n1 * dotProduct1;
  
        // Project new point onto line [p1, p2]
        const Vector2D v2 = pointOnLine - p1;
        double dotProduct2 = n1 * v2;
  
        const Point2D crossingPoint = p1 + n1 * dotProduct2;
  
        // Determine whether the projected point on the line, or the crossing point should be
        // used (according to the second constraint in the comment above)
        if ((pointOnLine.SquaredEuclideanDistanceTo(p3) > crossingPoint.SquaredEuclideanDistanceTo(p3)) &&
            (pointOnLine.SquaredEuclideanDistanceTo(p3) > pointOnLine.SquaredEuclideanDistanceTo(crossingPoint)))
        {
          return pointOnLine;
        }
        else
        {
          return crossingPoint;
        }
      }
  
      default:
        return point;
    }
  }
  
  void mitk::PlanarCross::GeneratePolyLine()
  {
    this->SetNumberOfPolyLines(1);
    this->ClearPolyLines();
  
    if (this->GetNumberOfControlPoints() > 2)
      this->SetNumberOfPolyLines(2);
  
    for (unsigned int i = 0; i < this->GetNumberOfControlPoints(); ++i)
    {
      if (i < 2)
        this->AppendPointToPolyLine(0, this->GetControlPoint(i));
  
      if (i > 1)
        this->AppendPointToPolyLine(1, this->GetControlPoint(i));
    }
  }
  
  void mitk::PlanarCross::GenerateHelperPolyLine(double /*mmPerDisplayUnit*/, unsigned int /*displayHeight*/)
  {
    // Generate helper polyline (orientation line orthogonal to first line)
    // if the third control point is currently being set
    if (this->GetNumberOfControlPoints() != 3)
    {
      m_HelperPolyLinesToBePainted->SetElement(0, false);
      return;
    }
  
    m_HelperPolyLinesToBePainted->SetElement(0, true);
  
    this->ClearHelperPolyLines();
  
    // Calculate cross point of first line (p1 to p2) and orthogonal line through
    // the third control point (p3)
    const Point2D p1 = this->GetControlPoint(0);
    const Point2D p2 = this->GetControlPoint(1);
    const Point2D p3 = this->GetControlPoint(2);
  
    Vector2D n1 = p2 - p1;
    n1.Normalize();
  
    const Vector2D v1 = p3 - p1;
    const Point2D crossPoint = p1 + n1 * (n1 * v1);
  
    const Vector2D v2 = crossPoint - p3;
    if (v2.GetNorm() < 1.0)
    {
      // If third point is on the first line, draw orthogonal "infinite" line
      // through cross point on line
      Vector2D v0;
      v0[0] = n1[1];
      v0[1] = -n1[0];
      this->AppendPointToHelperPolyLine(0, Point2D(p3 - v0 * 10000.0));
      this->AppendPointToHelperPolyLine(0, Point2D(p3 + v0 * 10000.0));
    }
    else
    {
      // Else, draw orthogonal line starting from third point and crossing the
      // first line, open-ended only on the other side
      this->AppendPointToHelperPolyLine(0, p3);
      this->AppendPointToHelperPolyLine(0, Point2D(p3 + v2 * 10000.0));
    }
  }
  
  void mitk::PlanarCross::EvaluateFeaturesInternal()
  {
    // Calculate length of first line
    const Point3D &p0 = this->GetWorldControlPoint(0);
    const Point3D &p1 = this->GetWorldControlPoint(1);
    double l1 = p0.EuclideanDistanceTo(p1);
  
    // Calculate length of second line
    double l2 = 0.0;
    if (!this->GetSingleLineMode() && (this->GetNumberOfControlPoints() > 3))
    {
      const Point3D &p2 = this->GetWorldControlPoint(2);
      const Point3D &p3 = this->GetWorldControlPoint(3);
      l2 = p2.EuclideanDistanceTo(p3);
    }
  
    double longestDiameter;
    double shortAxisDiameter;
    if (l1 > l2)
    {
      longestDiameter = l1;
      shortAxisDiameter = l2;
    }
    else
    {
      longestDiameter = l2;
      shortAxisDiameter = l1;
    }
  
    this->SetQuantity(FEATURE_ID_LONGESTDIAMETER, longestDiameter);
    this->SetQuantity(FEATURE_ID_SHORTAXISDIAMETER, shortAxisDiameter);
  }
  
  void mitk::PlanarCross::PrintSelf(std::ostream &os, itk::Indent indent) const
  {
    Superclass::PrintSelf(os, indent);
  }
  
  bool mitk::PlanarCross::Equals(const mitk::PlanarFigure &other) const
  {
    const auto *otherCross = dynamic_cast<const mitk::PlanarCross *>(&other);
    if (otherCross)
    {
      return Superclass::Equals(other);
    }
    else
    {
      return false;
    }
  }
  