/* -----------------------------------------------------------------------
   See COPYRIGHT.TXT and LICENSE.TXT for copyright and license information
   ----------------------------------------------------------------------- */
#include "plmbase_config.h"
#include <stdio.h>

#include "logfile.h"
#include "plm_math.h"
#include "ray_trace.h"
#include "volume.h"
#include "volume_limit.h"

// TODO: All the #defines in base/ray_trace.h need to
//       be rethought/goaway.

Point_location
Volume_limit::test_boundary (int d, double x)
{
    if (x < this->lower_limit[d]) {
        return POINTLOC_LEFT;
    } else if (x > this->upper_limit[d]) {
        return POINTLOC_RIGHT;
    } else {
        return POINTLOC_INSIDE;
    }
}

/* Return 1 if ray intersects volume */
int
Volume_limit::clip_ray (
    double *ip1,                /* OUTPUT: Intersection point 1 */
    double *ip2,                /* OUTPUT: Intersection point 2 */
    const double *p1,           /* INPUT:  Starting point of ray */
    const double *ray           /* INPUT:  Direction of ray */
)
{
    Point_location ploc[3];
    double p2[3];
    double alpha[3][2];
    double alpha_in, alpha_out;
    int d;

    /* Make a second point in direction of ray */
    vec3_add3 (p2, p1, ray);
    
    /* Compute point location */
    for (d = 0; d < 3; d++) {
        ploc[d] = this->test_boundary (d, p1[d]);
    }

    /* Compute alphas */
    for (d = 0; d < 3; d++) {
        /* If ray is parallel to grid, location must be inside */
        if (fabs(ray[d]) < DRR_LEN_TOLERANCE) {
            if (ploc[d] != POINTLOC_INSIDE) {
                return 0;
            }
            alpha[d][0] = - DBL_MAX;
            alpha[d][1] = + DBL_MAX;
            continue;
        }

        /* General configuration */
        alpha[d][0] = (this->lower_limit[d] - p1[d]) / ray[d];
        alpha[d][1] = (this->upper_limit[d] - p1[d]) / ray[d];

        /* Sort alpha */
        if (alpha[d][0] > alpha[d][1]) {
            double temp = alpha[d][1];
            alpha[d][1] = alpha[d][0];
            alpha[d][0] = temp;
        }
    }

    /* Check if alpha values overlap in all three dimensions.
       alpha_in is the minimum alpha, where the ray enters the volume.
       alpha_out is where it exits the volume. */
    alpha_in = alpha[0][0];
    alpha_out = alpha[0][1];
    for (d = 1; d < 3; d++) {
        if (alpha_in < alpha[d][0]) alpha_in = alpha[d][0];
        if (alpha_out > alpha[d][1]) alpha_out = alpha[d][1];
    }
#if defined (DRR_VERBOSE)
    printf ("alpha[*][0] = %g %g %g\n", alpha[0][0], alpha[1][0], alpha[2][0]);
    printf ("alpha[*][1] = %g %g %g\n", alpha[0][1], alpha[1][1], alpha[2][1]);
    printf ("alpha in/out = %g %g\n", alpha_in, alpha_out);
#endif

    /* If exit is before entrance, the segment does not intersect the volume */
    if (alpha_out - alpha_in < DRR_LEN_TOLERANCE) {
        return 0;
    }

    /* Compute the volume intersection points */
    for (d = 0; d < 3; d++) {
        ip1[d] = p1[d] + alpha_in * ray[d];
        ip2[d] = p1[d] + alpha_out * ray[d];
    }

    return 1;
}

/* Return 1 if line segment intersects volume */
int
Volume_limit::clip_segment (
    double *ip1,                /* OUTPUT: Intersection point 1 */
    double *ip2,                /* OUTPUT: Intersection point 2 */
    const double *p1,           /* INPUT:  Line segment point 1 */
    const double *p2            /* INPUT:  Line segment point 2 */
)
{
    Point_location ploc[3][2];
    double ray[3];
    double alpha_lo[3], alpha_hi[3];
    double alpha_in, alpha_out;
    int d;

    /* Compute the ray */
    vec3_sub3 (ray, p2, p1);

    for (d = 0; d < 3; d++) {
        ploc[d][0] = this->test_boundary (d, p1[d]);
        ploc[d][1] = this->test_boundary (d, p2[d]);
        /* Immediately reject segments which don't intersect the volume in 
           this dimension */
        if (ploc[d][0] == POINTLOC_LEFT && ploc[d][1] == POINTLOC_LEFT) {
            return 0;
        }
        if (ploc[d][0] == POINTLOC_RIGHT && ploc[d][1] == POINTLOC_RIGHT) {
            return 0;
        }
    }

    /* If we made it here, all three dimensions have some range of alpha
       where they intersects the volume.  However, these alphas might 
       not overlap.  We compute the alphas, then test overlapping 
       alphas to find the segment range within the volume.  */
    for (d = 0; d < 3; d++)
    {
        /* If ray is parallel to grid, location must be inside */
        if (fabs(ray[d]) < DRR_LEN_TOLERANCE) {
            alpha_lo[d] = - DBL_MAX;
            alpha_hi[d] = + DBL_MAX;
            continue;
        }

        alpha_lo[d] = (this->lower_limit[d] - p1[d]) / ray[d];
        alpha_hi[d] = (this->upper_limit[d] - p1[d]) / ray[d];

        /* Sort alphas */
        if (alpha_hi[d] < alpha_lo[d]) {
            double tmp = alpha_hi[d];
            alpha_hi[d] = alpha_lo[d];
            alpha_lo[d] = tmp;
        }

        /* Clip alphas to segment */
        if (alpha_lo[d] < 0.0) alpha_lo[d] = 0.0;
        if (alpha_lo[d] > 1.0) alpha_lo[d] = 1.0;
        if (alpha_hi[d] < 0.0) alpha_hi[d] = 0.0;
        if (alpha_hi[d] > 1.0) alpha_hi[d] = 1.0;
    }

    /* alpha_in is the alpha where the segment enters the boundary, and 
       alpha_out is where it exits the boundary.  */
    alpha_in = alpha_lo[0];
    alpha_out = alpha_hi[0];
    for (d = 1; d < 3; d++) {
        if (alpha_in < alpha_lo[d]) alpha_in = alpha_lo[d];
        if (alpha_out > alpha_hi[d]) alpha_out = alpha_hi[d];
    }
#if defined (DRR_VERBOSE)
    printf ("p1 = %g %g %g\n", p1[0], p1[1], p1[2]);
    printf ("p2 = %g %g %g\n", p2[0], p2[1], p2[2]);
    printf ("ray = %g %g %g\n", ray[0], ray[1], ray[2]);
    printf ("lower_lim = %g %g %g\n", this->lower_limit[0], 
        this->lower_limit[1], this->lower_limit[2]);
    printf ("upper_lim = %g %g %g\n", this->upper_limit[0], 
        this->upper_limit[1], this->upper_limit[2]);
    printf ("alpha_lo = %g %g %g\n", alpha_lo[0], alpha_lo[1], alpha_lo[2]);
    printf ("alpha_hi = %g %g %g\n", alpha_hi[0], alpha_hi[1], alpha_hi[2]);
    printf ("alpha in/out = %g %g\n", alpha_in, alpha_out);
#endif

    /* If exit is before entrance, the segment does not intersect the volume */
    if (alpha_out - alpha_in < DRR_LEN_TOLERANCE) {
        return 0;
    }

    /* Create the volume intersection points */
    for (d = 0; d < 3; d++) {
        ip1[d] = p1[d] + alpha_in * ray[d];
        ip2[d] = p1[d] + alpha_out * ray[d];
    }

    return 1;
}

void
Volume_limit::find_limit (const Volume *vol)
{
    int d;

    /* Compute volume boundary box */
    for (d = 0; d < 3; d++) {
        this->lower_limit[d] = vol->origin[d] - 0.5 * vol->spacing[d];
        this->upper_limit[d] = this->lower_limit[d]
            + vol->dim[d] * vol->spacing[d];
        if (this->lower_limit[d] <= this->upper_limit[d]) {
            this->dir[d] = 1;
        } else {
            double tmp;
            this->dir[d] = -1;
            /* Swap limits */
            tmp = this->lower_limit[d];
            this->lower_limit[d] = this->upper_limit[d];
            this->upper_limit[d] = tmp;
        }
        this->lower_limit[d] += DRR_BOUNDARY_TOLERANCE;
        this->upper_limit[d] -= DRR_BOUNDARY_TOLERANCE;
    }
}

void
Volume_limit::find_limit (const Volume::Pointer& vol)
{
    this->find_limit (vol.get());
}

void
Volume_limit::print ()
{
    lprintf ("Volume_limit:\n%g %g / %g %g / %g %g\n",
        lower_limit[0], upper_limit[0], 
        lower_limit[1], upper_limit[1], 
        lower_limit[2], upper_limit[2]);
}

