/*
 * BSPTree.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 "BSPTree.h"

namespace Zoom {


void BSPTree::partition(const TriangleRecord &t, std::list<TriangleRecord> *front, std::list<TriangleRecord> *back) const {
  for(int i = 0; i < 3; ++i) {
    if(plane.contains(t.getTriangle().getVertex(i))) {
      const vmml::vec3f *v[3] = {&t.getTriangle().getVertex(i), &t.getTriangle().getVertex((i+1)%3), &t.getTriangle().getVertex((i+2)%3)};

      vmml::vec3f is = plane.intersection(MathUtil::Ray(*v[1], *v[2]-*v[1]));

      if(plane.isInFront(*v[1])) {
        front->push_back(TriangleRecord(Triangle(*v[0], *v[1], is, t.getTriangle().getColor()), t.getData()));
        back->push_back(TriangleRecord(Triangle(*v[0], is, *v[2], t.getTriangle().getColor()), t.getData()));
      }
      else {
        back->push_back(TriangleRecord(Triangle(*v[0], *v[1], is, t.getTriangle().getColor()), t.getData()));
        front->push_back(TriangleRecord(Triangle(*v[0], is, *v[2], t.getTriangle().getColor()), t.getData()));
      }

      return;
    }
  }

  for(int i = 0; i < 3; ++i) {
    const vmml::vec3f *v[3] = {&t.getTriangle().getVertex(i), &t.getTriangle().getVertex((i+1)%3), &t.getTriangle().getVertex((i+2)%3)};

    if((plane.isInFront(*v[0]) && plane.isBehind(*v[1]) && plane.isBehind(*v[2]))
        || (plane.isBehind(*v[0]) && plane.isInFront(*v[1]) && plane.isInFront(*v[2]))) {
      vmml::vec3f is1 = plane.intersection(MathUtil::Ray(*v[0], *v[1]-*v[0]));
      vmml::vec3f is2 = plane.intersection(MathUtil::Ray(*v[0], *v[2]-*v[0]));

      if(plane.isInFront(*v[0])) {
        front->push_back(TriangleRecord(Triangle(*v[0], is1, is2, t.getTriangle().getColor()), t.getData()));
        back->push_back(TriangleRecord(Triangle(is1, *v[1], is2, t.getTriangle().getColor()), t.getData()));
        back->push_back(TriangleRecord(Triangle(*v[1], *v[2], is2, t.getTriangle().getColor()), t.getData()));
      }
      else {
        back->push_back(TriangleRecord(Triangle(*v[0], is1, is2, t.getTriangle().getColor()), t.getData()));
        front->push_back(TriangleRecord(Triangle(is1, *v[1], is2, t.getTriangle().getColor()), t.getData()));
        front->push_back(TriangleRecord(Triangle(*v[1], *v[2], is2, t.getTriangle().getColor()), t.getData()));
      }

      return;
    }
  }
}


BSPTree::BSPTree(const std::list<TriangleRecord> &triangles) : frontTree(0), backTree(0) {
  const Triangle *planeT = findNearestTriangle(triangles, findCenter(triangles));

  if(!planeT)
    return;

  plane = MathUtil::Plane(*planeT);

  std::list<TriangleRecord> front, back;

  for(std::list<TriangleRecord>::const_iterator t = triangles.begin(); t != triangles.end(); ++t) {
    if(t->getTriangle().isDegenerate())
      continue;

    if(plane.contains(t->getTriangle())) {
      this->triangles.push_back(*t);
      continue;
    }
    else if(plane.isInFront(t->getTriangle())) {
      front.push_back(*t);
      continue;
    }
    else if(plane.isBehind(t->getTriangle())) {
      back.push_back(*t);
      continue;
    }

    partition(*t, &front, &back);
  }

  if(!front.empty())
    frontTree = new BSPTree(front);

  if(!back.empty())
    backTree = new BSPTree(back);
}

BSPTree& BSPTree::operator=(const BSPTree &tree) {
  if(frontTree) {
    delete frontTree;
    frontTree = 0;
  }

  if(backTree) {
    delete backTree;
    backTree = 0;
  }

  plane = tree.plane;
  triangles = tree.triangles;

  if(tree.frontTree)
    frontTree = new BSPTree(*tree.frontTree);

  if(tree.backTree)
    backTree = new BSPTree(*tree.backTree);

  return *this;
}

vmml::vec3f BSPTree::findCenter(const std::list<TriangleRecord> &triangles) {
  vmml::vec3f v;

  for(std::list<TriangleRecord>::const_iterator t = triangles.begin(); t != triangles.end(); ++t) {
    v += t->getTriangle().getCenter();
  }

  return v/triangles.size();
}

const Triangle* BSPTree::findNearestTriangle(const std::list<TriangleRecord> &triangles, const vmml::vec3f &v) {
  const Triangle *current = 0;
  float distanceSq;

  for(std::list<TriangleRecord>::const_iterator t = triangles.begin(); t != triangles.end(); ++t) {
    if(t->getTriangle().isDegenerate())
      continue;

    float d = t->getTriangle().getCenter().squared_distance(v);

    if(!current || d < distanceSq) {
      current = &t->getTriangle();
      distanceSq = d;
    }
  }

  return current;
}

}
