/*
 *   surf - visualizing algebraic curves and algebraic surfaces
 *   Copyright (C) 1996-1997 Friedrich-Alexander-Universitaet
 *                           Erlangen-Nuernberg
 *                 1997-2000 Johannes Gutenberg-Universitaet Mainz
 *   Authors: Stephan Endrass, Hans Huelf, Ruediger Oertel,
 *            Kai Schneider, Ralf Schmitt, Johannes Beigel
 *
 *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */




#include <stdio.h>
#include <math.h>

#include "DrawfuncData.h"
#include "MultiVariatePolynom.h"
#include "Hornergroup.h"
#include "Clip.h"
#include "gui_config.h"
#include "def.h"
#include "float_buffer.h"
#include "RgbBuffer.h"

// #define DEBUG
#include "debug.h"
#include "stop.h"


#define TOLERANZ_LOESCHE_LAY2      0.1
#define TOLERANZ_ZEICHNEN_NEU_LAY2 0.09


// ----------------------------------------------------------------------------
// --------- constructor -- bring it all together -----------------------------
// ----------------------------------------------------------------------------

DrawfuncData::DrawfuncData( RgbBuffer *rgbbuff, float_buffer *zbuff,
			    RationalHornerXY* PLANE,
			    HornergroupXYZ* SURF,
			    HornergroupXY* CURVE,
			    Clip *CL,
			    Position *POS,
			    double PW )
	:  Plane(PLANE),
	   Surface(SURF),
	   Curve(CURVE),
	  
	   clip(CL),
	   position(POS),                      
	   PointWidth(PW),                     // curve width
	   PointDiv(4*PW),                      
	   MaxDist(pow(PW+1,2.0)/(4.0*PW)),
	   WinSizeFactor((double)MIN(main_width_data,main_height_data)/20.0),
	   intensity(rgbbuff),
	   zbuffer(zbuff)
{
	Delta[VARIABLE_X] = GetDelta( VARIABLE_X );// coordinate distances
	Delta[VARIABLE_Y] = GetDelta( VARIABLE_Y );
}


DrawfuncData::~DrawfuncData()
{
	delete Curve;
	delete Surface;
	delete Plane;
}

// ----------------------------------------------------------------------------
// -------------- draw cutlines working in y direction ------------------------
// ----------------------------------------------------------------------------

void DrawfuncData::PrintCurve( int direction )
{
	BEGIN("DrawfuncData::PrintCurve");
	assert(direction == 0 || direction == 1);

	double Root[100];                 // array to store roots
	double Estimate[100];             // array to store estimates
	int    NumberOfEstimates = 0;     // counter
	int NumberOfRoots = 0;
	double Coord[2];
	double minimal = 0.0;   	    // coordinate range for current line/col clip
	double maximal = 0.0;
	int min = 0;
	int max = 0;
	GetBorders( direction, min, max );

	// go through all lines resp. columns
	for( int Pixel = min; Pixel < max && !stop ; Pixel++ ) {

		// transform pixel y to y coordinate
		Coord[direction] = ToUser( direction, Pixel );

		// get minimum and maximum x coordinate from clipping
		if( clip->ClipXY( direction, Coord[direction], minimal, maximal ) ) {
			// set value in function
			Curve->SetVar( direction, Coord[direction] );
	  
			// find all roots of function in min...max
			// with estimates at estimate(0)...estimate(NumberOfEstimates-1)
			if( ( NumberOfRoots = Curve->Zero( direction,
							   maximal,
							   minimal,
							   Root,
							   Estimate,
							   NumberOfEstimates ) ) ) {
				// reset number of estimates
				NumberOfEstimates = 0;
				// go through all roots found
				for( int RootNumber = 0;
				     RootNumber < NumberOfRoots;
				     RootNumber++ ) {
					Coord[1-direction] = Root[RootNumber];
					PaintPoint( Coord[0], Coord[1] );
					// set estimate at old root
					Estimate[NumberOfEstimates] = Root[RootNumber]
						+ Delta[direction] * Curve->EstimateDelta( direction, Root[RootNumber] );
					
					// only use estimate if smaller than previous
					if( NumberOfEstimates == 0 || Estimate[NumberOfEstimates] < Estimate[NumberOfEstimates-1] )
						NumberOfEstimates++;
				}
			}
		}
	}
}



// ----------------------------------------------------------------------------
// (sk :) PAINT - POINT  
//  sk1:lösche HiddenLine flag, nicht mehr nötig, da nun möglich durch transparence
// ----------------------------------------------------------------------------

void DrawfuncData::PaintPoint( double CoordX, double CoordY )
{
	double CoordZ = -100000.0;
	double zMinimal = 0.0;
	double zMaximal = 0.0;
	double PixelX = ToPixel( VARIABLE_X, CoordX );
	double PixelY = ToPixel( VARIABLE_Y, CoordY );
	int PXRound = (int)( PixelX + 0.5 );
	int PYRound = (int)( PixelY + 0.5 );
	double ZDelta[2];

	// get z range and clip
	if( !clip->ClipXYZ( CoordX, CoordY, zMinimal, zMaximal ) )
		return;
  
	// surface clipping for mid pixel(s)
	// if drawing curve on a surface and hidden line enabled
	if( Surface ) {							/* sk1*/

		// get z of point and clip
		CoordZ = Plane->ZValue( CoordX, CoordY );
		
		// printf("z-Plane = %f at x %f, y %f\n",CoordZ, CoordX, CoordY );

		if( CoordZ < zMinimal )
			return;
		if( CoordZ > zMaximal )
			return;

		// see if surface or ball is in front
		Surface->SetRow( CoordY );
		Surface->SetVar( CoordX );

		// sk1: "if (surface in front)" : nicht sinnvoll, da alle NUS gefunden werden sollen 

		// estimate deltas according to surface derivatives
		ZDelta[VARIABLE_X] = Surface->EstimateDelta( VARIABLE_X, CoordZ ) * Delta[VARIABLE_X];
		ZDelta[VARIABLE_Y] = Surface->EstimateDelta( VARIABLE_Y, CoordZ ) * Delta[VARIABLE_Y];
		// no one is really interested in ZDelta...[RS]
		
		// get max surface z of closest pixel and compare with z buffer
		Surface->SetRow( ToUser( VARIABLE_Y, PYRound ) );
		Surface->SetVar( ToUser( VARIABLE_X, PXRound ) );
       
	}


	int px = (int)PixelX;
	int py = (int)PixelY;

	double MantX = PixelX - (double)px;
	double MantY = PixelY - (double)py;
  

  
	// starting with the mid row do all pixel rows
	// that are touched by the circular brush at position (PixelX,PixelY)
	// do mid row
	DoRow( px, py, CoordZ, 0, MantX, MantY, ZDelta );
	
	// do other rows
	int active = TRUE;
	int DistY = 1;
	
	while( active ) {
		active = ( DoRow( px, py, CoordZ, -DistY, MantX, MantY, ZDelta ) +
			   DoRow( px, py, CoordZ, +DistY, MantX, MantY, ZDelta ) );
		DistY++;
	}
}
// ----------------------------------------------------------------------------
// (sk :) DoRow     
//  sk1:lösche HiddenLine flag, nicht mehr nötig, da nun möglich durch transparence
// ----------------------------------------------------------------------------

int DrawfuncData::DoRow( int px, int py, double CoordZ, int DistY,
			  double MantX, double MantY, double *ZDelta )
{
	int ay = py + DistY;
	double DistanceY = MantY - (double)DistY;
	double uy = ToUser( VARIABLE_Y, ay );

  
	// if drawing on surface with hidden line enabled
	// set variable y in surface polynom
	if( Surface )							/* sk1*/
		Surface->SetRow( uy );

	// starting with the mid pixel do all pixels in this row
	// that are touched by the brush
	// do mid pixel
	int active = DoPixel( px, ay, uy, CoordZ, 0, MantX, DistanceY, ZDelta );

	// do rest of row
	int working = TRUE;
	int DistX = 1;

	while( working ) {
		// Ahh, changing "+" into "||" in the next statement will not work,
		// because then the second DoPixel won´t get executed 
		// Really nice trap :)
		working = ( DoPixel( px, ay, uy, CoordZ,
				     -DistX, MantX, DistanceY, ZDelta ) + 
			    DoPixel( px, ay, uy, CoordZ,
				     +DistX, MantX, DistanceY, ZDelta ) );	 
		DistX++;
	}
	// return true if any pixel in this row was modified
	return ( active || DistX > 2 );
}

// ----------------------------------------------------------------------------
// (sk :) DoPixel 
//  sk1:lösche HiddenLine flag, nicht mehr nötig, da nun möglich durch transparence
// ----------------------------------------------------------------------------
  
int DrawfuncData::DoPixel( int px,
			   int ay,
			   double uy,
			   double CoordZ,
			   int DistX,
			   double MantX,
			   double DistanceY,
			   double *ZDelta )
{
	int ax = px + DistX;

	// calculate x distance of pixel from curve point
	double DistanceX = MantX - (double)DistX;
	double ux = ToUser( VARIABLE_X, ax );


	// sk :Suche NUS mit Surface->Zero in einem kugeligen Bereich um ...
	//     den Schnittpunkt ( von  Plane und Surface) auf der Surface . ...
	//     Damit wird perspektivische Verkürzung der Kurve auf der Fläche ...
	//     möglich, und zum einen werden vernünftige z-Werte berechnet ...
	//     und zum anderen wird die Kurve korrekt auf die Fläche gezeichnet ...
	//     Graphik(unten) zeigt die Kreisscheibe ...
	//
	//      A      ****
	//    Y I    **    **
	//      I   *  O     *   O = z.B DERZEITIG_ERREICHTER_PUNKT,  
	//      I   *        *       dieser liegt auf der x-y Ebene mit ...
	//      I   *        *       z = z-Wert vom Schnittpkt (Plane mit Surface)
	//      I    **    **  	( also : z = CoordZ )
	//      I      ****
	//     -x------------------> X
	
	// sk :angleichen des Kugelradius in z-Richtung auf Fenstergröße, da ...
	//     Kurvenbreite abhängig von Fensterausmaßen 
	double diameter=WinSizeFactor*PointWidth/10.0;
  
	// sk:ermittle entsprechenden Radius  
	double radius  = diameter/2.0;	
  
	// sk:ermittle distance vom Mittelpunkt des DERZEITIG_ERREICHTER_...
	//    PUNKTes (siehe oben) im Kreis   	 
	double distance=sqrt(DistanceX*DistanceX+DistanceY*DistanceY);
  
	if( radius < distance ) 
		return FALSE;

	// sk:rechne z-Wert vom derzeitigen Punkt zum Kugelrand    
	double D =sqrt( radius*radius - distance*distance ); // in Pixeln
	double DU = D/WinSizeFactor;
  
  
	// sk :setze Startwert genug weit weg
	double zEstimate= -100000.0;

	//sk:errechne zmin und zmax von dem Bereich in
	// dem die NUSen gesucht werden sollen  	
	double zMinimal = CoordZ-D/WinSizeFactor;		
	double zMaximal = CoordZ+D/WinSizeFactor;
	
	
	// get z range at pixel and clip
	double zMin,zMax;
	if( !clip->ClipXYZ( ux, uy, zMin, zMax ) ) 
		return FALSE;
	
	if( zMaximal > zMax ) 
		zMaximal = zMax;
	if( zMinimal < zMin ) 
		zMinimal = zMin; 
	double brightness=0.0;
	
	if( Surface ) {
		Surface->SetVar( ux );	

		// sk :Array to save all roots of plane with surface
		double SRoots[100];
		int    NUS=0;
      
		// sk: NUS = Anzahl, der gefundenen Nullstellen
		NUS = Surface->Zero( zMaximal,zMinimal,SRoots,NULL,0) ; 
      
		// sk: Wenn NUS gefunden 
		if ( NUS ) {
			// sk :Suche nach dieser NUS, die in dem Bereich dem Schnittpkt ...
			//     (Plane mit surface) am nächsten liegt

			double eps_min=10000.0;
			double eps;
			int i=0;

			while( i<NUS && (eps=fabs(SRoots[i] - CoordZ))<eps_min ) { 
				eps_min=eps;
				i++;
			}
         
			// sk :setze zEstimate gleich der gefundene NUS 
			zEstimate = SRoots[i-1];      

			// sk :wenn noch innerhalb relativen Radius
			if ( eps_min < DU  ) {  
				// clip pixel in z range
				if( zEstimate > zMaximal ) 
					return FALSE;
				if( zEstimate < zMinimal )
					return FALSE;

				// sk : setze brightness je nach dem wie
				// weit entfernt der Punkt vom Mittelpkt ist
				brightness=( eps_min*eps_min*WinSizeFactor*WinSizeFactor
					     +DistanceX*DistanceX 
					     +DistanceY*DistanceY  )/(radius*radius);

			} else { // end if NUS in "BrushBall"

				return FALSE;
			} 
		} else { // end if NUS

			// sk :zeichne auch trotzdem wenn überhaupt keine Fläche gefunden
			//  erhoffe mir dadurch Stellen zu zeichnen wenn wie z.B. bei der
			//  Cyclide möglich, der "Ring" der Cyclide genau auf der dem
			//  Plane liegt. 

			
// 			if ( ( fabs(DistanceX) < 2.0 ) && ( fabs(DistanceY) < 2.0 ) ) {
// 				zEstimate = CoordZ- DistanceX * ZDelta[VARIABLE_X]     
// 					- DistanceY * ZDelta[VARIABLE_Y];
			  
// 				brightness=MaxDist-( 
// 					DistanceX*DistanceX 
// 					+DistanceY*DistanceY  )/PointDiv;
// 			} else {
				return FALSE;
// 			}
		}
	} else {//end if surface
		// sk :calculate curve_intensity without surface
		brightness = ( DistanceX*DistanceX +
			       DistanceY*DistanceY )/(radius*radius);
	}
  
	// skip pixel if dark
	if( brightness <= 0.0 ) {
		return FALSE;
	}
	
	// sk :     wenn nur Kurve ohne Fläche gezeichnet wird ...
	//     oder wenn neuer z-wert gesetzt, weil er vor dem vorherigen liegt,
	//     dann setze Pixel 
	if( SetZbuffer (ax, ay, zEstimate) || !Surface ) {
		SetCurvePixel( ax,ay,1.0 - pow( brightness,curve_gamma_data ) );   
	}

	return TRUE;
}


// ----------------------------------------------------------------------------
// sk :Set Z-Buffer at pixel x,y
// ----------------------------------------------------------------------------
bool DrawfuncData::SetZbuffer( int x, int y, double z )
{  
	// get old value
	double zold=zbuffer->Get(x,y);
  
	// sk :     wenn hier noch keine Kurve gesetzt  ...
	//     oder wenn hier ein z-Wert einer curve schon im hintergrund, ...
	//     dann lösche Pixel im Layer2  
	if(!intensity->GetTag(x,y,CURVEBIT)|| (z>zold+TOLERANZ_LOESCHE_LAY2) ) {
		intensity->SetLayerTwo(x,y,0);
	}
          
	// sk :wenn z-Wert geringfügig (TOLERANZ_ZEICHEN_NEU_LAY2) ...  
	//     dahinter liegt , dann ...
	//     trotzdem setzen und damit zeichnen
	if( z+TOLERANZ_ZEICHNEN_NEU_LAY2 > zold ) {
		// sk :aber nur zbuffer setzen wenn neuer Wert echt größer ist
		if( z>zold ) {    
			zbuffer->Set(x,y,z);
		}
		return true;
	}
	return false;
}





// ----------------------------------------------------------------------------
// (sk :) set one pixel of the curve     ( Umstellung auf RGB-Modell )
// ----------------------------------------------------------------------------
void DrawfuncData::SetCurvePixel( int x, int y, double val )
{
	if( x > min[0] && x < max[0] && y > min[1] && y < max[1] ) {
		if( val > 1.0 ) 
			val = 1.0;
		
		if( val > 0.0 ) {
			intensity->SetLayerTwoIfHigher( x, y,(int)floor( val*255.0 ));
		}
	}
}
  
// ----------------------------------------------------------------------------
// --------------------- end of file DrawData.cc ------------------------------
// ----------------------------------------------------------------------------
