// dome.cxx -- model sky with an upside down "bowl"
//
// Written by Curtis Olson, started December 1997.
// SSG-ified by Curtis Olson, February 2000.
//
// Copyright (C) 1997-2000  Curtis L. Olson  - curt@flightgear.org
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA  02111-1307, USA.
//
// $Id$


#ifdef HAVE_CONFIG_H
#  include <simgear_config.h>
#endif

#ifdef HAVE_WINDOWS_H
#  include <windows.h>
#endif

#include <math.h>

#include <GL/glut.h>
#include <simgear/xgl/xgl.h>

#include <plib/sg.h>

#include <simgear/debug/logstream.hxx>

#include "dome.hxx"


#ifdef __MWERKS__
#  pragma global_optimizer off
#endif


// in meters of course
static const float center_elev = 0.3125;

static const float upper_radius = 0.6250;
static const float upper_elev = 0.2500;

static const float middle_radius = 0.8750;
static const float middle_elev = 0.1000;

static const float lower_radius = 0.8750;
static const float lower_elev = 0.0000;

static const float bottom_radius = 0.6250;
static const float bottom_elev = -0.0250;


// Set up dome rendering callbacks
static int sgSkyDomePreDraw( ssgEntity *e ) {
    /* cout << endl << "Dome Pre Draw" << endl << "----------------" 
	 << endl << endl; */

    ssgLeaf *f = (ssgLeaf *)e;
    if ( f -> hasState () ) f->getState()->apply() ;

    glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_FOG_BIT );
    // cout << "push error = " << glGetError() << endl;

    glDisable( GL_DEPTH_TEST );
    glDisable( GL_FOG );

    return true;
}

static int sgSkyDomePostDraw( ssgEntity *e ) {
    /* cout << endl << "Dome Post Draw" << endl << "----------------" 
	 << endl << endl; */

    glPopAttrib();
    // cout << "pop error = " << glGetError() << endl;
    
    return true;
}


// Constructor
SGSkyDome::SGSkyDome( void ) {
}


// Destructor
SGSkyDome::~SGSkyDome( void ) {
}


// initialize the sky object and connect it into our scene graph
ssgBranch * SGSkyDome::build( double hscale, double vscale ) {
    sgVec4 color;

    float theta;
    int i;

    // set up the state
    dome_state = new ssgSimpleState();
    dome_state->setShadeModel( GL_SMOOTH );
    dome_state->disable( GL_LIGHTING );
    dome_state->disable( GL_CULL_FACE );
    dome_state->disable( GL_TEXTURE_2D );
    dome_state->enable( GL_COLOR_MATERIAL );
    dome_state->setColourMaterial( GL_AMBIENT_AND_DIFFUSE );
    dome_state->setMaterial( GL_EMISSION, 0, 0, 0, 1 );
    dome_state->setMaterial( GL_SPECULAR, 0, 0, 0, 1 );
    dome_state->disable( GL_BLEND );
    dome_state->disable( GL_ALPHA_TEST );

    // initialize arrays
    center_disk_vl = new ssgVertexArray( 14 );
    center_disk_cl = new ssgColourArray( 14 );
				       
    upper_ring_vl = new ssgVertexArray( 26 );
    upper_ring_cl = new ssgColourArray( 26 );

    middle_ring_vl = new ssgVertexArray( 26 );
    middle_ring_cl = new ssgColourArray( 26 );

    lower_ring_vl = new ssgVertexArray( 26 );
    lower_ring_cl = new ssgColourArray( 26 );

    // initially seed to all blue
    sgSetVec4( color, 0.0, 0.0, 1.0, 1.0 );

    // generate the raw vertex data
    sgVec3 center_vertex;
    sgVec3 upper_vertex[12];
    sgVec3 middle_vertex[12];
    sgVec3 lower_vertex[12];
    sgVec3 bottom_vertex[12];

    sgSetVec3( center_vertex, 0.0, 0.0, center_elev * vscale );

    for ( i = 0; i < 12; i++ ) {
	theta = (i * 30.0) * SGD_DEGREES_TO_RADIANS;

	sgSetVec3( upper_vertex[i],
		   cos(theta) * upper_radius * hscale,
		   sin(theta) * upper_radius * hscale,
		   upper_elev * vscale );
	
	sgSetVec3( middle_vertex[i],
		   cos((double)theta) * middle_radius * hscale,
		   sin((double)theta) * middle_radius * hscale,
		   middle_elev * vscale );

	sgSetVec3( lower_vertex[i],
		   cos((double)theta) * lower_radius * hscale,
		   sin((double)theta) * lower_radius * hscale,
		   lower_elev * vscale );

	sgSetVec3( bottom_vertex[i],
		   cos((double)theta) * bottom_radius * hscale,
		   sin((double)theta) * bottom_radius * hscale,
		   bottom_elev * vscale );
    }

    // generate the center disk vertex/color arrays
    center_disk_vl->add( center_vertex );
    center_disk_cl->add( color );
    for ( i = 11; i >= 0; i-- ) {
	center_disk_vl->add( upper_vertex[i] );
	center_disk_cl->add( color );
    }
    center_disk_vl->add( upper_vertex[11] );
    center_disk_cl->add( color );

    // generate the upper ring
    for ( i = 0; i < 12; i++ ) {
	upper_ring_vl->add( middle_vertex[i] );
	upper_ring_cl->add( color );

	upper_ring_vl->add( upper_vertex[i] );
	upper_ring_cl->add( color );
    }
    upper_ring_vl->add( middle_vertex[0] );
    upper_ring_cl->add( color );

    upper_ring_vl->add( upper_vertex[0] );
    upper_ring_cl->add( color );

    // generate middle ring
    for ( i = 0; i < 12; i++ ) {
	middle_ring_vl->add( lower_vertex[i] );
	middle_ring_cl->add( color );

	middle_ring_vl->add( middle_vertex[i] );
	middle_ring_cl->add( color );
    }
    middle_ring_vl->add( lower_vertex[0] );
    middle_ring_cl->add( color );

    middle_ring_vl->add( middle_vertex[0] );
    middle_ring_cl->add( color );

    // generate lower ring
    for ( i = 0; i < 12; i++ ) {
	lower_ring_vl->add( bottom_vertex[i] );
	lower_ring_cl->add( color );

	lower_ring_vl->add( lower_vertex[i] );
	lower_ring_cl->add( color );
    }
    lower_ring_vl->add( bottom_vertex[0] );
    lower_ring_cl->add( color );

    lower_ring_vl->add( lower_vertex[0] );
    lower_ring_cl->add( color );

    // force a repaint of the sky colors with ugly defaults
    sgVec4 fog_color;
    sgSetVec4( fog_color, 1.0, 1.0, 1.0, 1.0 );
    repaint( color, fog_color, 0.0, 5000.0 );

    // build the ssg scene graph sub tree for the sky and connected
    // into the provide scene graph branch
    ssgVtxTable *center_disk, *upper_ring, *middle_ring, *lower_ring;

    center_disk = new ssgVtxTable( GL_TRIANGLE_FAN, 
				   center_disk_vl, NULL, NULL, center_disk_cl );

    upper_ring = new ssgVtxTable( GL_TRIANGLE_STRIP, 
				  upper_ring_vl, NULL, NULL, upper_ring_cl );

    middle_ring = new ssgVtxTable( GL_TRIANGLE_STRIP, 
				   middle_ring_vl, NULL, NULL, middle_ring_cl );

    lower_ring = new ssgVtxTable( GL_TRIANGLE_STRIP, 
				  lower_ring_vl, NULL, NULL, lower_ring_cl );

    center_disk->setState( dome_state );
    upper_ring->setState( dome_state );
    middle_ring->setState( dome_state );
    lower_ring->setState( dome_state );

    dome_transform = new ssgTransform;
    dome_transform->addKid( center_disk );
    dome_transform->addKid( upper_ring );
    dome_transform->addKid( middle_ring );
    dome_transform->addKid( lower_ring );

    // not entirely satisfying.  We are depending here that the first
    // thing we add to a parent is the first drawn
    center_disk->setCallback( SSG_CALLBACK_PREDRAW, sgSkyDomePreDraw );
    center_disk->setCallback( SSG_CALLBACK_POSTDRAW, sgSkyDomePostDraw );

    upper_ring->setCallback( SSG_CALLBACK_PREDRAW, sgSkyDomePreDraw );
    upper_ring->setCallback( SSG_CALLBACK_POSTDRAW, sgSkyDomePostDraw );

    middle_ring->setCallback( SSG_CALLBACK_PREDRAW, sgSkyDomePreDraw );
    middle_ring->setCallback( SSG_CALLBACK_POSTDRAW, sgSkyDomePostDraw );

    lower_ring->setCallback( SSG_CALLBACK_PREDRAW, sgSkyDomePreDraw );
    lower_ring->setCallback( SSG_CALLBACK_POSTDRAW, sgSkyDomePostDraw );

    return dome_transform;
}


// repaint the sky colors based on current value of sun_angle, sky,
// and fog colors.  This updates the color arrays for ssgVtxTable.
// sun angle in degrees relative to verticle
// 0 degrees = high noon
// 90 degrees = sun rise/set
// 180 degrees = darkest midnight
bool SGSkyDome::repaint( sgVec4 sky_color, sgVec4 fog_color, double sun_angle,
			 double vis )
{
    double diff;
    sgVec3 outer_param, outer_amt, outer_diff;
    sgVec3 middle_param, middle_amt, middle_diff;
    int i, j;

    // Check for sunrise/sunset condition
    if ( (sun_angle > 80.0) && (sun_angle < 100.0) ) {
	// 0.0 - 0.4
	sgSetVec3( outer_param,
		   (10.0 - fabs(90.0 - sun_angle)) / 20.0,
		   (10.0 - fabs(90.0 - sun_angle)) / 40.0,
		   -(10.0 - fabs(90.0 - sun_angle)) / 30.0 );

	sgSetVec3( middle_param,
		   (10.0 - fabs(90.0 - sun_angle)) / 40.0,
		   (10.0 - fabs(90.0 - sun_angle)) / 80.0,
		   0.0 );

	sgScaleVec3( outer_diff, outer_param, 1.0 / 6.0 );

	sgScaleVec3( middle_diff, middle_param, 1.0 / 6.0 );
    } else {
	sgSetVec3( outer_param, 0.0, 0.0, 0.0 );
	sgSetVec3( middle_param, 0.0, 0.0, 0.0 );

	sgSetVec3( outer_diff, 0.0, 0.0, 0.0 );
	sgSetVec3( middle_diff, 0.0, 0.0, 0.0 );
    }
    // printf("  outer_red_param = %.2f  outer_red_diff = %.2f\n", 
    //        outer_red_param, outer_red_diff);

    // calculate transition colors between sky and fog
    sgCopyVec3( outer_amt, outer_param );
    sgCopyVec3( middle_amt, middle_param );

    //
    // First, recalulate the basic colors
    //

    sgVec4 center_color;
    sgVec4 upper_color[12];
    sgVec4 middle_color[12];
    sgVec4 lower_color[12];
    sgVec4 bottom_color[12];

    double vis_factor;

    if ( vis < 3000.0 ) {
	vis_factor = (vis - 1000.0) / 2000.0;
	if ( vis_factor < 0.0 ) {
	    vis_factor = 0.0;
	}
    } else {
	vis_factor = 1.0;
    }

    for ( j = 0; j < 3; j++ ) {
	diff = sky_color[j] - fog_color[j];
	center_color[j] = sky_color[j] - diff * ( 1.0 - vis_factor );
    }
    center_color[3] = 1.0;

    for ( i = 0; i < 6; i++ ) {
	for ( j = 0; j < 3; j++ ) {
	    diff = sky_color[j] - fog_color[j];

	    // printf("sky = %.2f  fog = %.2f  diff = %.2f\n", 
	    //        l->sky_color[j], l->fog_color[j], diff);

	    upper_color[i][j] = sky_color[j] - diff * ( 1.0 - vis_factor * 0.7);
	    middle_color[i][j] = sky_color[j] - diff * ( 1.0 - vis_factor * 0.1)
		+ middle_amt[j];
	    lower_color[i][j] = fog_color[j] + outer_amt[j];

	    if ( upper_color[i][j] > 1.0 ) { upper_color[i][j] = 1.0; }
	    if ( upper_color[i][j] < 0.0 ) { upper_color[i][j] = 0.0; }
	    if ( middle_color[i][j] > 1.0 ) { middle_color[i][j] = 1.0; }
	    if ( middle_color[i][j] < 0.0 ) { middle_color[i][j] = 0.0; }
	    if ( lower_color[i][j] > 1.0 ) { lower_color[i][j] = 1.0; }
	    if ( lower_color[i][j] < 0.0 ) { lower_color[i][j] = 0.0; }
	}
	upper_color[i][3] = middle_color[i][3] = lower_color[i][3] = 1.0;

	for ( j = 0; j < 3; j++ ) {
	    outer_amt[j] -= outer_diff[j];
	    middle_amt[j] -= middle_diff[j];
	}

	/*
	printf("upper_color[%d] = %.2f %.2f %.2f %.2f\n", i, upper_color[i][0],
	       upper_color[i][1], upper_color[i][2], upper_color[i][3]);
	printf("middle_color[%d] = %.2f %.2f %.2f %.2f\n", i, 
	       middle_color[i][0], middle_color[i][1], middle_color[i][2], 
	       middle_color[i][3]);
	printf("lower_color[%d] = %.2f %.2f %.2f %.2f\n", i, 
	       lower_color[i][0], lower_color[i][1], lower_color[i][2], 
	       lower_color[i][3]);
	*/
    }

    sgSetVec3( outer_amt, 0.0, 0.0, 0.0 );
    sgSetVec3( middle_amt, 0.0, 0.0, 0.0 );

    for ( i = 6; i < 12; i++ ) {
	for ( j = 0; j < 3; j++ ) {
	    diff = sky_color[j] - fog_color[j];

	    // printf("sky = %.2f  fog = %.2f  diff = %.2f\n", 
	    //        sky_color[j], fog_color[j], diff);

	    upper_color[i][j] = sky_color[j] - diff * ( 1.0 - vis_factor * 0.7);
	    middle_color[i][j] = sky_color[j] - diff * ( 1.0 - vis_factor * 0.1)
		+ middle_amt[j];
	    lower_color[i][j] = fog_color[j] + outer_amt[j];

	    if ( upper_color[i][j] > 1.0 ) { upper_color[i][j] = 1.0; }
	    if ( upper_color[i][j] < 0.0 ) { upper_color[i][j] = 0.0; }
	    if ( middle_color[i][j] > 1.0 ) { middle_color[i][j] = 1.0; }
	    if ( middle_color[i][j] < 0.0 ) { middle_color[i][j] = 0.0; }
	    if ( lower_color[i][j] > 1.0 ) { lower_color[i][j] = 1.0; }
	    if ( lower_color[i][j] < 0.0 ) { lower_color[i][j] = 0.0; }
	}
	upper_color[i][3] = middle_color[i][3] = lower_color[i][3] = 1.0;

	for ( j = 0; j < 3; j++ ) {
	    outer_amt[j] += outer_diff[j];
	    middle_amt[j] += middle_diff[j];
	}

	/*
	printf("upper_color[%d] = %.2f %.2f %.2f %.2f\n", i, upper_color[i][0],
	       upper_color[i][1], upper_color[i][2], upper_color[i][3]);
	printf("middle_color[%d] = %.2f %.2f %.2f %.2f\n", i, 
	       middle_color[i][0], middle_color[i][1], middle_color[i][2], 
	       middle_color[i][3]);
	printf("lower_color[%d] = %.2f %.2f %.2f %.2f\n", i, 
	       lower_color[i][0], lower_color[i][1], lower_color[i][2], 
	       lower_color[i][3]);
 	*/
   }

    for ( i = 0; i < 12; i++ ) {
	sgCopyVec4( bottom_color[i], fog_color );
    }

    //
    // Second, assign the basic colors to the object color arrays
    //

    float *slot;
    int counter;

    // update the center disk color arrays
    counter = 0;
    slot = center_disk_cl->get( counter++ );
    // sgVec4 red;
    // sgSetVec4( red, 1.0, 0.0, 0.0, 1.0 );
    sgCopyVec4( slot, center_color );
    for ( i = 11; i >= 0; i-- ) {
	slot = center_disk_cl->get( counter++ );
	sgCopyVec4( slot, upper_color[i] );
    }
    slot = center_disk_cl->get( counter++ );
    sgCopyVec4( slot, upper_color[11] );

    // generate the upper ring
    counter = 0;
    for ( i = 0; i < 12; i++ ) {
	slot = upper_ring_cl->get( counter++ );
	sgCopyVec4( slot, middle_color[i] );

	slot = upper_ring_cl->get( counter++ );
	sgCopyVec4( slot, upper_color[i] );
    }
    slot = upper_ring_cl->get( counter++ );
    sgCopyVec4( slot, middle_color[0] );

    slot = upper_ring_cl->get( counter++ );
    sgCopyVec4( slot, upper_color[0] );

    // generate middle ring
    counter = 0;
    for ( i = 0; i < 12; i++ ) {
	slot = middle_ring_cl->get( counter++ );
	sgCopyVec4( slot, lower_color[i] );

	slot = middle_ring_cl->get( counter++ );
	sgCopyVec4( slot, middle_color[i] );
    }
    slot = middle_ring_cl->get( counter++ );
    sgCopyVec4( slot, lower_color[0] );

    slot = middle_ring_cl->get( counter++ );
    sgCopyVec4( slot, middle_color[0] );

    // generate lower ring
    counter = 0;
    for ( i = 0; i < 12; i++ ) {
	slot = lower_ring_cl->get( counter++ );
	sgCopyVec4( slot, bottom_color[i] );

	slot = lower_ring_cl->get( counter++ );
	sgCopyVec4( slot, lower_color[i] );
    }
    slot = lower_ring_cl->get( counter++ );
    sgCopyVec4( slot, bottom_color[0] );

    slot = lower_ring_cl->get( counter++ );
    sgCopyVec4( slot, lower_color[0] );

    return true;
}


// reposition the sky at the specified origin and orientation
// lon specifies a rotation about the Z axis
// lat specifies a rotation about the new Y axis
// spin specifies a rotation about the new Z axis (and orients the
// sunrise/set effects
bool SGSkyDome::reposition( sgVec3 p, double lon, double lat, double spin ) {
    sgMat4 T, LON, LAT, SPIN;
    sgVec3 axis;

    // Translate to view position
    // Point3D zero_elev = current_view.get_cur_zero_elev();
    // xglTranslatef( zero_elev.x(), zero_elev.y(), zero_elev.z() );
    sgMakeTransMat4( T, p );

    // printf("  Translated to %.2f %.2f %.2f\n", 
    //        zero_elev.x, zero_elev.y, zero_elev.z );

    // Rotate to proper orientation
    // printf("  lon = %.2f  lat = %.2f\n",
    //        lon * SGD_RADIANS_TO_DEGREES,
    //        lat * SGD_RADIANS_TO_DEGREES);
    // xglRotatef( lon * SGD_RADIANS_TO_DEGREES, 0.0, 0.0, 1.0 );
    sgSetVec3( axis, 0.0, 0.0, 1.0 );
    sgMakeRotMat4( LON, lon * SGD_RADIANS_TO_DEGREES, axis );

    // xglRotatef( 90.0 - f->get_Latitude() * SGD_RADIANS_TO_DEGREES,
    //             0.0, 1.0, 0.0 );
    sgSetVec3( axis, 0.0, 1.0, 0.0 );
    sgMakeRotMat4( LAT, 90.0 - lat * SGD_RADIANS_TO_DEGREES, axis );

    // xglRotatef( l->sun_rotation * SGD_RADIANS_TO_DEGREES, 0.0, 0.0, 1.0 );
    sgSetVec3( axis, 0.0, 0.0, 1.0 );
    sgMakeRotMat4( SPIN, spin * SGD_RADIANS_TO_DEGREES, axis );

    sgMat4 TRANSFORM;

    sgCopyMat4( TRANSFORM, T );
    sgPreMultMat4( TRANSFORM, LON );
    sgPreMultMat4( TRANSFORM, LAT );
    sgPreMultMat4( TRANSFORM, SPIN );

    sgCoord skypos;
    sgSetCoord( &skypos, TRANSFORM );

    dome_transform->setTransform( &skypos );

    return true;
}
