/*
 * Collision.cpp
 *
 * Copyright (C) 2009 Matthias Schiffer <matthias@gamezock.de>
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "Collision.h"
#include <cmath>
#include <limits>

namespace Zoom {

bool Collision::test(const Triangle &t, const MathUtil::Ray &ray, float *distance) {
  vmml::vec3f edge1 = t.getVertex(1) - t.getVertex(0);
  vmml::vec3f edge2 = t.getVertex(2) - t.getVertex(0);

  vmml::vec3f pvec = ray.getDirection().cross(edge2);

  float det = vmml::dot(edge1, pvec);
  if(det < MathUtil::EPSILON)
    return false;

  vmml::vec3f tvec = ray.getVertex() - t.getVertex(0);
  float u = vmml::dot(pvec, tvec);

  if(u < 0 || u > det)
    return false;

  vmml::vec3f qvec = tvec.cross(edge1);
  float v = vmml::dot(ray.getDirection(), qvec);
  if(v < 0 || u+v > det)
    return false;

  if(distance)
    *distance = vmml::dot(edge2, qvec) / det;

  return true;
}

bool Collision::testEdge(const vmml::vec3f &v1, const vmml::vec3f &v2, const vmml::vec3f &m, float r, const vmml::vec3f &move, float *distance) {
  vmml::vec3f edge = v2 - v1;

  vmml::vec3f avec = vmml::dot(edge, move)*edge / edge.squared_length() - move;
  vmml::vec3f cvec = v1 + vmml::dot(edge, m-v1)*edge / edge.squared_length() - m;

  float a = avec.squared_length();
  float b_2 = vmml::dot(avec, cvec);
  float c = cvec.squared_length() - r*r;

  float rootSq = b_2*b_2 - a*c;
  if(rootSq < 0)
    return false;

  float root = std::sqrt(rootSq);

  *distance = -(b_2 + root)/a;

  float edgeFact = edge.dot(m + move*(*distance) - v1);
  if(edgeFact < 0 || edgeFact > edge.squared_length())
    return false;

  if(*distance > -MathUtil::EPSILON && *distance < 1)
    return true;
  else
    return false;
}

bool Collision::testVertex(const vmml::vec3f &v, const vmml::vec3f &m, float r, const vmml::vec3f &move, float *distance) {
  float a = move.squared_length();
  float b_2 = vmml::dot(m-v, move);
  float c = (m-v).squared_length() - r*r;

  float rootSq = b_2*b_2 - a*c;
  if(rootSq < 0)
    return false;

  float root = std::sqrt(rootSq);

  *distance = -(b_2 + root)/a;

  if(*distance > -MathUtil::EPSILON && *distance < 1)
    return true;
  else
    return false;
}

bool Collision::test(const Triangle &t, const vmml::vec3f &m, float r, const vmml::vec3f &move, float *distance) {
  float d;

  if(test(t, MathUtil::Ray(m - r*t.computeNormal(), move), &d) && d > -MathUtil::EPSILON) {
    if(distance)
      *distance = d;

    if(d < 1) {
      return true;
    }
    else {
      return false;
    }
  }

  bool collision = false;
  float minDistance;

  for(int i = 0; i < 3; ++i) {
    if(testEdge(t.getVertex(i), t.getVertex((i+1)%3), m, r, move, &d)) {
      if(!collision || d < minDistance) {
        collision = true;
        minDistance = d;
      }
    }
  }

  if(collision) {
    if(distance)
      *distance = minDistance;

    return true;
  }

  collision = false;
  for(int i = 0; i < 3; ++i) {
    if(testVertex(t.getVertex(i), m, r, move, &d)) {
      if(!collision || d < minDistance) {
        collision = true;
        minDistance = d;
      }
    }
  }

  if(collision) {
    if(distance)
      *distance = minDistance;

    return true;
  }
  else {
    return false;
  }
}

}
