// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// 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 General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

#include "face.h"
#include "mesh.h"
#include "c3ga_util.h"
#include "surface_point.h"
#include "texture.h"
#include "../shared.src/mt19937-2.h"
#include "../shared.src/constants.h"

using namespace c3ga;
face::face() : m_u2(0.0f), m_v2(0.0f), m_w2(0.0f),
                     m_uv(0.0f), m_vw(0.0f), m_wu(0.0f),
                     m_u_w2(0.0f)
{
    for (int i = 0; i < 3; i++)
        m_vtx_idx[i] = 0;

    m_uv_dir[0][0] = m_uv_dir[0][1] =
        m_uv_dir[1][0] = m_uv_dir[1][1] = 0.0;
}

face::face(const int vtx_idx[3]) : m_u2(0.0f), m_v2(0.0f), m_w2(0.0f),
                                                m_uv(0.0f), m_vw(0.0f), m_wu(0.0f),
                                                m_u_w2(0.0f)
{
    for (int i = 0; i < 3; i++)
        m_vtx_idx[i] = vtx_idx[i];

    m_uv_dir[0][0] = m_uv_dir[0][1] =
        m_uv_dir[1][0] = m_uv_dir[1][1] = 0.0;
}

face::face(const face &f) : m_u2(f.m_u2), m_v2(f.m_v2), m_w2(f.m_w2),
                                        m_uv(f.m_uv), m_vw(f.m_vw), m_wu(f.m_wu),
                                        m_u_w2(f.m_u_w2) , m_pl(f.m_pl)
{
    for (int i = 0; i < 3; i++) {
            m_vtx_idx[i] = f.m_vtx_idx[i];
            m_edge_line[i] = f.m_edge_line[i];
        }

    m_uv_dir[0][0] = f.m_uv_dir[0][0];
    m_uv_dir[0][1] = f.m_uv_dir[0][1];
    m_uv_dir[1][0] = f.m_uv_dir[1][0];
    m_uv_dir[1][1] = f.m_uv_dir[1][1];
}


face::~face() {

}


face &face::operator=(const face &f) {
    if (this != &f) {
        for (int i = 0; i < 3; i++) {
            m_vtx_idx[i] = f.m_vtx_idx[i];
            m_edge_line[i] = f.m_edge_line[i];
        }

        m_u2 = f.m_u2,
        m_v2 = f.m_v2;
        m_w2 = f.m_w2;

        m_uv = f.m_uv;
        m_vw = f.m_vw;
        m_wu = f.m_wu;

        m_u_w2 = f.m_u_w2;

        m_uv_dir[0][0] = f.m_uv_dir[0][0];
        m_uv_dir[0][1] = f.m_uv_dir[0][1];
        m_uv_dir[1][0] = f.m_uv_dir[1][0];
        m_uv_dir[1][1] = f.m_uv_dir[1][1];

        m_pl = f.m_pl;
    }
    return *this;
}



void face::precompute_constants(const mesh &m) {
    // compute lines along the edges of the face
    for (int i = 0; i < 3; i++)
        m_edge_line[i] = _line(get_vtx(i, m).get_pt() ^ get_vtx((i + 1) % 3, m).get_pt() ^ ni);
        // don't make them unit, this will mess up the interpolation!
        //m_edge_line[i] = unit_r(get_vtx(i, m).get_pt() ^ get_vtx((i + 1) % 3, m).get_pt() ^ c3ga::ni);

    // compute the plane of the facew
    m_pl = _plane(unit_r(get_vtx(0, m).get_pt() ^ get_vtx(1, m).get_pt() ^ get_vtx(2, m).get_pt() ^ ni));

    /*
    Compute the direction (dual plane through the origin) of each of the 
    three m_edge_line variables are lines along the face.
    */
    vectorE3GA u = _vectorE3GA(nino << m_edge_line[0]);
    vectorE3GA v = _vectorE3GA(nino << m_edge_line[1]);
    vectorE3GA w = _vectorE3GA(nino << m_edge_line[2]);


    // compute interpolation constants
    m_u2 = _Float(u % u);
    m_v2 = _Float(v % v);
    m_w2 = _Float(w % w);

    m_uv = _Float(u % v);
    m_vw = _Float(v % w);
    m_wu = _Float(w % u);

    m_u_w2 = _Float((u^w) % (u^w));
}

bool face::uses_vtx_idx(int vtx_idx) const {
    return ((vtx_idx == get_vtx_idx(0)) ||
        (vtx_idx == get_vtx_idx(1)) ||
        (vtx_idx == get_vtx_idx(2)));
}

// todo: make better use of GA in this problem . . . !!!!



void face::compute_local_texture_basis(const mesh &m) {
    // Compute 'a' and 'b', Euclidean vectors in 
    // texture space along two of the edges of the polygon
    vectorE2GA a = _vectorE2GA(no << (m.get_vertex(m_vtx_idx[1]).get_tex_pt() - m.get_vertex(m_vtx_idx[0]).get_tex_pt()));
    vectorE2GA b = _vectorE2GA(no << (m.get_vertex(m_vtx_idx[2]).get_tex_pt() - m.get_vertex(m_vtx_idx[0]).get_tex_pt()));

    mv::Float a1 = _Float(a & e1);
    mv::Float a2 = _Float(a & e2);
    mv::Float b1 = _Float(b & e1);
    mv::Float b2 = _Float(b & e2);

    mv::Float alpha1, beta1, alpha2, beta2;

    if ((b2 * a1 - b1 * a2) == 0.0f) { // degenerate triangle; spans no area in texture space
        alpha1 = alpha2 = beta1 = beta2 = 0.0;
    }
    else {
        // invert matrix . . . 
        beta1 = a2 / (b1 * a2 - b2 * a1);
        alpha1 = (a2 == 0.0f) ? 1.0f / a1 : -beta1 * b2 / a2;
        beta2 = a1 / (b2 * a1 - b1 * a2);
        alpha2 = (a1 == 0.0f) ? 1.0f / a2 : -beta2 * b1 / a1;
    }

    m_uv_dir[0][0] = alpha1;
    m_uv_dir[0][1] = beta1;
    m_uv_dir[1][0] = alpha2;
    m_uv_dir[1][1] = beta2;
}


bool face::find_intersection(const dualPlane &ray_dual_plane, const dualLine &ray_dual_line, surface_point &spt, bool find_closest) const {
    // check if line intersects triangle:
    mv::Float side1 = _Float(no % (ray_dual_line << get_edge_line(0)));
    mv::Float side2 = _Float(no % (ray_dual_line << get_edge_line(1)));
    mv::Float side3 = _Float(no % (ray_dual_line << get_edge_line(2)));
    int sd;

    // check if face was intersected, and if so, from what side did the ray come:
    if ((side1 > 0.0) && (side2 > 0.0) && (side3 > 0.0)) {
        sd = 1; // front side
    }
    else if ((side1 < 0.0) && (side2 < 0.0) && (side3 < 0.0)) {
        sd = -1; // back side
    }
    else if ((side1 >= 0.0) && (side2 >= 0.0) && (side3 >= 0.0)) {
        sd = 1; // front side (barely touching)
    }
    else return false;

    // compute the intersection point & distance to ray start
    normalizedFlatPoint I = _normalizedFlatPoint((ray_dual_line << get_pl()) *
            (1.0f /  _float((ray_dual_line << get_pl()) % (noni))));
    mv::Float d = -_Float((ray_dual_plane << I) % no);
    if (d < RAY_INTERSECTION_MIN_DISTANCE) return false;

    // if no precise info required, we're done:
    if (!find_closest) {
        spt.set_valid(true);
        spt.set_distance_local(d);
        return true;
    }
    else {
        if ((!spt.get_valid()) ||
            (spt.get_valid() && (d < spt.get_distance_local()))) {
            spt.set_valid(true);
            spt.set_distance_local(d);
            spt.set_pt_local(I);
            spt.set_side(sd);
            return true;
        }
        else return false;
    }
}


void face::compute_interpolation_weights(const normalizedFlatPoint &pt, mv::Float iw[3], const vertex &vtx0) const {
    mv::Float u_d, v_d, w_d;

    // compute u, v, w as Euclidean vectors:
    vectorE3GA u = _vectorE3GA(noni << get_edge_line(0));
    vectorE3GA v = _vectorE3GA(noni << get_edge_line(1));
    vectorE3GA w = _vectorE3GA(noni << get_edge_line(2));

    // compute local position of point 'pt', as a Euclidean vector
    vectorE3GA d = _vectorE3GA(no<<(pt - (vtx0.get_pt() ^ ni)));

    // compute inner product of d with each edge direction
    u_d = _float(u & d);
    v_d = _float(v & d);
    w_d = _float(w & d);

    // compute the weights for the three vertices :
    iw[0] = -(m_v2 * u_d - m_uv * v_d) / m_u_w2 + 1.0f;
    iw[1] = -(m_w2 * v_d - m_vw * w_d) / m_u_w2;
    iw[2] = -(m_u2 * w_d - m_wu * u_d) / m_u_w2;
}


void face::compute_surface_point_details(surface_point &spt, const model &m) const {
    // get the three vertices of this face
    const vertex &vtx0(get_vtx(0, *m.get_mesh()));
    const vertex &vtx1(get_vtx(1, *m.get_mesh()));
    const vertex &vtx2(get_vtx(2, *m.get_mesh()));

    // compute the interpolation weights
    mv::Float iw[3]; // iw = interpolation weights
    compute_interpolation_weights(spt.get_pt_local(), iw, vtx0);

    // compute the interpolated texture point
    spt.set_tex_pt(_normalizedFlatPoint2D(iw[0] * vtx0.get_tex_pt() + iw[1] * vtx1.get_tex_pt() + iw[2] * vtx2.get_tex_pt()));

    // compute interpolated, normalized surface normal
    freeBivector att = _freeBivector(unit_e(iw[0] * vtx0.get_att() + iw[1] * vtx1.get_att() + iw[2] * vtx2.get_att()));

    // apply bumpmap if it is enabled:
    // todo: clean up GA usage in here . . .
    if (m.get_bump_map_texture().enabled()) {
        float bump[3];
        m.get_bump_map_texture().get_bump_map_value(spt.get_tex_pt(), bump);

        // construct attitudes for from the edge lines 0 & 2
        freeBivector att_edge_0 = _freeBivector(dual(get_edge_line(0)) ^ ni);
        freeBivector att_edge_2 = _freeBivector(dual(get_edge_line(2)) ^ ni);

        // transform bumpmap from the 2D 'bumpmap frame' to the local 3D frame
        freeBivector bump_att = _freeBivector(m.get_bumpyness() *
            ((m_uv_dir[0][1] * bump[0] + m_uv_dir[1][1] * bump[1]) * att_edge_2 -
            (m_uv_dir[0][0] * bump[0] + m_uv_dir[1][0] * bump[1]) * att_edge_0));

        // set surface attitude in model frame, with bump added
        spt.set_att_local(_freeBivector(unit_e(att + bump_att)));
    }
    else {
        // set surface attitude in model frame
        spt.set_att_local(att);
    }
}












































// leave this comment for online browsing anchors