/***********************************************************************
 * perspective.c: Data concerning the point of view. It is defined by
 *                three coordinates: the location of the observer 
 *                ('from'), the point she is looking at ('center' or 
 *                'at') and a vector indicating which side is at the top
 *                of his view ('up'). 
 *                   It also contains any extra data necessary to make
 *                calculation of a point's projection faster.
 **********************************************************************/

/***********************************************************************
 *  This file is part of SpaceChart.
 *  Copyright (C) 2000 Miguel Coca <e970095@zipi.fi.upm.es>
 *
 *  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 <stdlib.h>
#include "../include/starmap.h"
#include "../include/perspective.h"

struct st_perspective
{
        coords_3d_t from, center, up;
        coords_3d_t a1, a2, a3, off;
        double dval;
};

/* Declaration of private functions. */
static void change_base( coords_3d_t* from, coords_3d_t* at, coords_3d_t* up,
                         coords_3d_t* point );
static void compute_aux( perspective_t *persp );

/* Public Functions */

perspective_t* perspective_new( coords_3d_t* from, coords_3d_t* center,
                                coords_3d_t* up )
{
        perspective_t* persp;

        if( ( persp = ( perspective_t* ) malloc( sizeof( perspective_t ) ) ) )
        {
                persp->from = *from;
                persp->center = *center;
                persp->up = *up;
                persp->dval = cos( THETA/2 ) / sin( THETA/2 );
                compute_aux( persp );
        }     

        return persp;
}

void perspective_get_from( perspective_t *persp, coords_3d_t* from )
{
        *from = persp->from;
}

void perspective_get_up( perspective_t *persp, coords_3d_t* up )
{
        *up = persp->up;
}

void perspective_get_center( perspective_t *persp, coords_3d_t* center )
{
        *center = persp->center;
}

/* Rotate the perspective v_ang radians in the vertical and h_ang radians
 * in the horizontal. */
void perspective_rotate( perspective_t *persp, double v_ang, double h_ang )
{
        coords_3d_t new_from, new_up;
        double r;

        r = distance_3d( &persp->from, &persp->center );
        
        v_ang = M_PI/2 - v_ang;

        new_from.x = r * cos(h_ang) * sin(v_ang);
        new_from.y = r * sin(h_ang) * sin(v_ang);
        new_from.z = r * cos(v_ang);
        
        new_up.x = (-1) *cos( h_ang ) * cos( v_ang );
        new_up.y = (-1) * sin( h_ang ) * cos( v_ang );
        new_up.z = sin( v_ang );

        change_base( &persp->from, &persp->center, &persp->up, &new_from );
        change_base( &persp->from, &persp->center, &persp->up, &new_up );
        new_from.x = new_from.x + persp->center.x;
        new_from.y = new_from.y + persp->center.y;
        new_from.z = new_from.z + persp->center.z;

        persp->from = new_from;
        persp->up = new_up;

        compute_aux(persp);
}

/* Set the coordinates given as the new center of the perspective system, 
 * keeping the current orientation of the view. */
void perspective_change_center(perspective_t *persp, coords_3d_t* new_center)
{
        coords_3d_t direction;
        double distance;
        
        distance = distance_3d( &persp->center, new_center );
        direction.x = new_center->x - persp->center.x;
        direction.y = new_center->y - persp->center.y;
        direction.z = new_center->z - persp->center.z;

        perspective_move_center( persp, &direction, distance );
}

/* Move the center distance parsecs in the given direction, keeping the
 * orientation of the view. */
void perspective_move_center( perspective_t *persp, coords_3d_t* direction,
                              double distance )
{
        double dir_mag;
        if( distance == 0.0)
                return;
        dir_mag = magnitude( direction );
        persp->from.x = persp->from.x + (direction->x/dir_mag)*distance;
        persp->from.y = persp->from.y + (direction->y/dir_mag)*distance;
        persp->from.z = persp->from.z + (direction->z/dir_mag)*distance;
        persp->center.x = persp->center.x + (direction->x/dir_mag)*distance;
        persp->center.y = persp->center.y + (direction->y/dir_mag)*distance;
        persp->center.z = persp->center.z + (direction->z/dir_mag)*distance;
        compute_aux(persp);
}

/* Calculate the projection of the given point in 2D. The results are given
 * in the [0..1] range. */
void perspective_compute( perspective_t* persp,
                          coords_3d_t* star_coords, 
                          double* x, double* y )
{
        coords_3d_t eye;

        eye.x = (star_coords->x * persp->a1.x ) + 
                (star_coords->y * persp->a1.y) + 
                (star_coords->z * persp->a1.z ) + persp->off.x;
        eye.y = (star_coords->x * persp->a2.x ) + 
                (star_coords->y * persp->a2.y) + 
                (star_coords->z * persp->a2.z ) + persp->off.y;
        eye.z = (star_coords->x * persp->a3.x ) + 
                (star_coords->y * persp->a3.y) + 
                (star_coords->z * persp->a3.z ) + persp->off.z;

        *x = (eye.x * persp->dval) / eye.z;
        *y = (eye.y * persp->dval) / eye.z;
}

void perspective_destroy( perspective_t* persp )
{
        free( persp );
}

/* Auxiliary Functions */

void change_base( coords_3d_t* from, coords_3d_t* center, coords_3d_t* up,
                  coords_3d_t* point )
{
        coords_3d_t new_point, radius;
        double r;
        
        r = distance_3d( from, center );
        radius.x = from->x - center->x;
        radius.y = from->y - center->y;
        radius.z = from->z - center->z;

        /* Operations to get the point in the standard coordinates system */
        new_point.x = (radius.x/r) * point->x 
                + ( (up->y*radius.z)/r - (up->z*radius.y)/r ) * point->y
                + up->x * point->z;
        new_point.y = (radius.y/r) * point->x
                + ( (up->z*radius.x)/r - (up->x*radius.z)/r ) * point->y
                + up->y * point->z; 
        new_point.z = (radius.z/r) * point->x
                + ( (up->x*radius.y)/r - (up->y*radius.x)/r ) * point->y
                + up->z * point->z;
        
        *point = new_point;
}

void compute_aux( perspective_t *persp )
{
        coords_3d_t a_prime, tmp;
        double a_prime_mag, tmp_mag;

        a_prime.x = persp->center.x - persp->from.x;
        a_prime.y = persp->center.y - persp->from.y;
        a_prime.z = persp->center.z - persp->from.z;
        a_prime_mag = magnitude( &a_prime );
        persp->a3.x = a_prime.x / a_prime_mag;
        persp->a3.y = a_prime.y / a_prime_mag;
        persp->a3.z = a_prime.z / a_prime_mag;
        tmp.x = ( a_prime.y * persp->up.z ) - ( persp->up.y * a_prime.z );
        tmp.y = ( a_prime.z * persp->up.x ) - ( persp->up.z * a_prime.x );
        tmp.z = ( a_prime.x * persp->up.y ) - ( persp->up.x * a_prime.y );
        tmp_mag = magnitude( &tmp );
        persp->a1.x = tmp.x / tmp_mag;
        persp->a1.y = tmp.y / tmp_mag;
        persp->a1.z = tmp.z / tmp_mag;
        tmp_mag = magnitude( &persp->up );
        persp->a2.x = persp->up.x / tmp_mag;
        persp->a2.y = persp->up.y / tmp_mag;
        persp->a2.z = persp->up.z / tmp_mag;
        persp->off.x = -(persp->a1.x*persp->from.x) - 
                (persp->a1.y*persp->from.y)-(persp->a1.z*persp->from.z);
        persp->off.y = -(persp->a2.x*persp->from.x) - 
                (persp->a2.y*persp->from.y)-(persp->a2.z*persp->from.z);
        persp->off.z = -(persp->a3.x*persp->from.x) - 
                (persp->a3.y*persp->from.y)-(persp->a3.z*persp->from.z);
}


syntax highlighted by Code2HTML, v. 0.9.1