/*
  * (non-Javadoc)
  *
  * @see java.lang.Object#hashCode()
  */
 @Override
 public int hashCode() {
   final int prime = 31;
   int result = 1;
   result = prime * result + getOuterType().hashCode();
   result = prime * result + ((mDistance == null) ? 0 : mDistance.hashCode());
   result = prime * result + ((mHitRay == null) ? 0 : mHitRay.hashCode());
   result = prime * result + ((mIntersection == null) ? 0 : mIntersection.hashCode());
   return result;
 }
    /**
     * Methode prueft, ob der gesetzte Schnittpunkt Startpunkt des getroffenen Strahls ist. Sofern
     * dies der Fall ist, wird das Start-Flag gesetzt.
     */
    private void isPointStart() {

      // teste, ob der Schnittpunkt dem Startpunkt entspricht
      MyVector3f rayStart = mHitRay.getStart();
      if (rayStart.equals(mIntersection)) {
        mIsStart = true;
        return;
      }

      MyVectormath mathHelper = MyVectormath.getInstance();

      // teste nun, ob die Distanz zwischen den Punkten innerhalb eines
      // Toleranzbereichs liegt
      // sofern dies der Fall ist, werden die Punkte als "gleich"
      // angesehen
      float distance = mathHelper.calculatePointPointDistance(rayStart, mIntersection);
      if (mathHelper.isWithinTolerance(distance, 0.0f, 0.01f)) {
        mIsStart = true;
        return;
      }

      mIsStart = false;
    }
 /*
  * (non-Javadoc)
  *
  * @see java.lang.Object#equals(java.lang.Object)
  */
 @Override
 public boolean equals(Object obj) {
   if (this == obj) return true;
   if (obj == null) return false;
   if (getClass() != obj.getClass()) return false;
   Hit other = (Hit) obj;
   if (!getOuterType().equals(other.getOuterType())) return false;
   if (mDistance == null) {
     if (other.mDistance != null) return false;
   } else if (!mDistance.equals(other.mDistance)) return false;
   if (mHitRay == null) {
     if (other.mHitRay != null) return false;
   } else if (!mHitRay.equals(other.mHitRay)) return false;
   if (mIntersection == null) {
     if (other.mIntersection != null) return false;
   } else if (!mIntersection.equals(other.mIntersection)) return false;
   return true;
 }
  /**
   * Methode durchlaeuft alle Strahlen im uebergebenen Vektor und testet auf Schnitte mit dem
   * uebergebenen Strahl. Hierbei wird immer der Strahl zurueckgegeben, dessen Schnitt mit dem
   * uebergebenen Strahl dem Ausgangspunkt am naechsten liegt (darum kann man auch nicht abbrechen,
   * sobald man den ersten Schnitt gefunden hat)
   *
   * @param current Strahl, fuer den im Strahlenvektor nach Schnitten gesucht wird
   * @param bucket Eimer, fuer den der gemergte Grundriss berechnet wird und der alle Strahlen fuer
   *     diese Rechnung enthaelt
   * @param lastIntersected Im letzten Durchlauf geschnittener Strahl. So wird ein Ping-Pong-Effekt
   *     zwischen Iterationen vermieden
   * @param lastIntersection Vektor, der den letzten berechneten Schnittpunkt beschreibt
   * @return Hit-Datenstruktur, die den getroffenen Strahl sowie den Schnittpunkt enthaelt
   */
  private Hit findIntersectingRay(
      final Ray current,
      final FootprintBucket bucket,
      final Ray lastIntersected,
      final MyVector3f lastIntersection) {

    Ray currentTestRay = null;
    float currentIntersectionDistance = Float.MAX_VALUE,
        lastIntersectionDistance = -Float.MAX_VALUE;
    MyVector3f currentIntersection = null;

    List<Hit> hits = new ArrayList<Hit>();
    Hit currentHit = null;

    LOGGER.debug("Aktueller Teststrahl: " + current);
    LOGGER.debug("Letzter Schnittpunkt: " + lastIntersection);

    Iterator<Ray> allRayIter = bucket.getAllRays().iterator();
    while (allRayIter.hasNext()) {
      currentTestRay = allRayIter.next();

      // sich selber nicht testen
      if (currentTestRay.equals(current)) continue;

      // den zuletzt geschnittenen Strahl ebenfalls ueberspringen
      if (currentTestRay.equals(lastIntersected)) continue;

      // sonst Schnittpunkt berechnen
      currentIntersection =
          mMathHelper.calculateRay2RayIntersectionApproximation(currentTestRay, current);

      // Entfernung des letzten Schnittpunkts zum Startpunkt bestimmen
      if (lastIntersection != null)
        lastIntersectionDistance =
            mMathHelper.calculatePointPointDistance(lastIntersection, current.getStart());

      if (currentIntersection != null) {

        // das Verfahren wird einen Strahl finden, der den Startpunkt
        // des aktuellen Strahls als Endpunkt besitzt, diesen
        // ueberspringen
        if (currentIntersection.equals(current.getStart())) continue;

        // wenn Vertices auf Strahlen liegen, kann der Fall auftreten,
        // dass der Schnittpunkt des letzten Durchlaufs erneut gefunden
        // wird
        if (currentIntersection.equals(lastIntersection)) continue;

        // logger.trace("CurrentIntersection: " + currentIntersection);

        // befindet sich der Strahl auf den Liniensegmenten beider
        // Teststrahlen?
        if (mMathHelper.isPointOnLineSegment(currentIntersection, currentTestRay)
            && mMathHelper.isPointOnLineSegment(currentIntersection, current)) {

          // sortiere solche Hits aus, bei denen der Schnittpunkt der
          // Endpunkt der getroffenen Kante ist
          double rayParameter =
              mMathHelper.calculateParameterOnRayForPoint(currentIntersection, currentTestRay);
          if (mMathHelper.isWithinTolerance(rayParameter, 1.0d, 0.001d)) {
            continue;
          }

          // Entfernung bestimmen
          currentIntersectionDistance =
              mMathHelper.calculatePointPointDistance(currentIntersection, current.getStart());

          // wenn der neue Schnittpunkt naeher am Startpunkt liegt,als
          // der im vorheringen Startpunkt berechnete, breche ab!
          // Dieser Schnittpunkt liegt auf dem Eingabestrahl und muss
          // nicht mit dem Startpunkt identisch sein, darum muss
          // jeder neue Schnittpunkt, der auf dem Strahl ermittelt
          // wird, zwingend weiter vom Start des Eingabestrahls
          // entfernt liegen
          if (lastIntersectionDistance > currentIntersectionDistance) {
            continue;
          }

          // erzeuge eine Treffer-Instanz
          currentHit = new Hit(currentTestRay, currentIntersectionDistance, currentIntersection);
          if (!hits.contains(currentHit)) {
            hits.add(currentHit);
            // logger.info("Kandidat: " + currentHit);
          }

          // suche nach Strahlen, die eine Verlaengerung des
          // Ausgangsstrahls sind und somit durch Schnittpunkttests
          // nicht gefunden werden koennen
          List<Ray> additionalRays =
              searchForRaysContainingGivenPoint(currentIntersection, current, bucket.getAllRays());

          // wenn Strahlen gefunden wurden, adde sie zum Hit-Vetor
          if (additionalRays.size() > 0) {
            Ray additionalRay = null;
            Iterator<Ray> additionalRayIter = additionalRays.iterator();
            while (additionalRayIter.hasNext()) {
              additionalRay = additionalRayIter.next();
              currentHit = new Hit(additionalRay, currentIntersectionDistance, currentIntersection);
              if (!hits.contains(currentHit)) {
                hits.add(currentHit);
                // logger.info("Kandidat: " + currentHit);
              }
            }
          }
        }
      }
    }

    LOGGER.debug("Insgesamt wurden " + hits.size() + " potentielle Kandidaten bestimmt.");

    // wenn kein Strahl gefunden wurde, werfe eine Exception
    assert hits.size() > 0
        : "Fehler: Es konnte kein Strahl gefunden werden, der sich mit dem Eingabestrahl schneidet! Eingabe: "
            + current;

    /*
     * logger.info("Potentielle Treffer: "); for(int i = 0; i < hits.size();
     * i++) logger.info(hits.get(i));
     */
    // es wurde nur ein Strahl getroffen
    if (hits.size() == 1) {
      return hits.get(0);
    }

    currentHit = chooseResultRay(hits, current, bucket);
    if (currentHit == null) return hits.get(0);
    else return currentHit;
  }