/*======================================================================================*/
/* 3DPanel										*/
/*											*/
/* - Camera functions									*/
/*											*/
/* AUTHOR:	Gabor Nagy								*/
/* DATE:	1996-Nov-05 23:48:33							*/
/*											*/
/* 3DPanel(TM) and 3DLib(TM) Copyright (C) 1995 By Gabor Nagy. All rights reserved.	*/
/*======================================================================================*/
#include <float.h>
#include <math.h>
#include <stdio.h>

#include <E3D/E3D.h>

#include <E3D/Math.h>

#include <E3D/Matrix.h>

#ifndef _E3D3DWindow_h
#include <E3D/3DWindow.h>
#endif

#include <E3D/StatusPanel.h>


E3dMatrix	E3dp_CameraMatrix, E3dp_LeftCameraMatrix, E3dp_RightCameraMatrix,	// For the control-models
		E3dp_InterestMatrix;

/*======================================*/
/* Set zoom/field of view		*/
/*======================================*/
void E3dp_3DWindowSetZoom(E3dWindow* L3DWindow, double LZoom)
{
 E3dCamera*		LCamera;


 switch(L3DWindow->ViewMode)
 {
  case E3dVM_PERSPECTIVE:
   LCamera=&(L3DWindow->PerspectiveCamera);
   LCamera->FieldOfView=LZoom;
  break;

  case E3dVM_TOP:
   LCamera=&(L3DWindow->TopCamera);
   LCamera->XZoom=LCamera->YZoom=LZoom;
  break;

  case E3dVM_FRONT:
   LCamera=&(L3DWindow->FrontCamera);
   LCamera->XZoom=LCamera->YZoom=LZoom;
  break;

  case E3dVM_RIGHT:
   LCamera=&(L3DWindow->RightCamera);
   LCamera->XZoom=LCamera->YZoom=LZoom;
  break;

  case E3dVM_SCHEMATICS:
   LCamera=&(L3DWindow->SchematicsCamera);
   LCamera->XZoom=LCamera->YZoom=LZoom;
  break;
 }
}


/*======================================*/
/* Reset cameras on a modelwindow	*/
/*======================================*/
void E3dp_3DWindowResetCameras(E3dWindow* L3DWindow)
{
 E3dCamera*	LCamera;
 double		LZNear=E3dp_Prefs.DefZNear, LZFar=E3dp_Prefs.DefZFar;


 LCamera=&(L3DWindow->PerspectiveCamera);
 E3d_CameraSet(LCamera, E3dp_Prefs.DefCameraPos.X, E3dp_Prefs.DefCameraPos.Y, E3dp_Prefs.DefCameraPos.Z, E3dp_Prefs.DefFieldOfView, E3dp_Prefs.DefZoom, LZNear, LZFar, E3dp_Prefs.DefEyeDistance);
 E3d_CallCallbacks(LCamera, LCamera->Callbacks, LCamera->NumOfCallbacks, NULL);

 LCamera=&(L3DWindow->TopCamera);
 E3d_CameraSet(LCamera, 0.0, LZFar*0.5, 0.0, E3dp_Prefs.DefFieldOfView, E3dp_Prefs.DefZoom, 0.1, LZFar, E3dp_Prefs.DefEyeDistance);
 LCamera->UpVector.X=0.0;
 LCamera->UpVector.Y=0.0;
 LCamera->UpVector.Z=-1.0;
 E3d_CallCallbacks(LCamera, LCamera->Callbacks, LCamera->NumOfCallbacks, NULL);

 LCamera=&(L3DWindow->FrontCamera);
 E3d_CameraSet(LCamera, 0.0, 0.0, LZFar*0.5, E3dp_Prefs.DefFieldOfView, E3dp_Prefs.DefZoom, LZNear, LZFar, E3dp_Prefs.DefEyeDistance);
 E3d_CallCallbacks(LCamera, LCamera->Callbacks, LCamera->NumOfCallbacks, NULL);

 LCamera=&(L3DWindow->RightCamera);
 E3d_CameraSet(LCamera, LZFar*0.5, 0.0, 0.0, E3dp_Prefs.DefFieldOfView, E3dp_Prefs.DefZoom, LZNear, LZFar, E3dp_Prefs.DefEyeDistance);
 E3d_CallCallbacks(LCamera, LCamera->Callbacks, LCamera->NumOfCallbacks, NULL);

 LCamera=&(L3DWindow->SchematicsCamera);
 E3d_CameraSet(LCamera, 0.0, 0.0, LZFar*0.5, E3dp_Prefs.DefFieldOfView, E3dp_Prefs.DefZoom, LZNear, LZFar, E3dp_Prefs.DefEyeDistance);
 E3d_CallCallbacks(LCamera, LCamera->Callbacks, LCamera->NumOfCallbacks, NULL);

 LCamera=&(L3DWindow->AnimationCamera);
 E3d_CameraSet(LCamera, 0.0, 0.0, LZFar*0.5, E3dp_Prefs.DefFieldOfView, E3dp_Prefs.DefZoom, LZNear, LZFar, E3dp_Prefs.DefEyeDistance);
 E3d_CallCallbacks(LCamera, LCamera->Callbacks, LCamera->NumOfCallbacks, NULL);
}


/*======================================*/
/* Reset Cameras on all 3DWindows	*/
/*======================================*/
void E3dp_ResetCameraOn3DWindows()
{
 E3dWindow*	L3DWindow;
 unsigned int	LC, LN;
 

 LN=E3dp_NumOf3DWindows;
 for(LC=0;LC<LN;LC++)
 {
  L3DWindow=E3dp_3DWindows[LC];
  if((L3DWindow->WindowFlags)&E3dWF_CREATED) E3dp_3DWindowResetCameras(L3DWindow);
 }
 E3dp_Refresh3DWindows(E3dDF_ALL, E3dVM_ALL);
}


/*==============================================*/
/* Redraw 3DWindows other than the main one	*/
/*==============================================*/
void E3dp_RedrawAuxWindows(unsigned int LViewModes)
{
 E3dWindow*	L3DWindow;
 unsigned int	LC, LN=E3dp_NumOf3DWindows;


 for(LC=0;LC<LN;LC++)
 {
  L3DWindow=E3dp_3DWindows[LC];
  if(L3DWindow!=E3dp_Main3DWindow)
  {
   if(L3DWindow->ViewMode&LViewModes) E3dp_Redraw3DWindow(L3DWindow);
  }
 }
}


/*======================================*/
/* Camera control			*/
/*======================================*/
void E3dp_CameraControl(E3dWindow* L3DWindow, int LPtrX, int LPtrY, unsigned int LState)
{
 E3dCamera*	LCamera=NULL;
 E3dWindow*	LMWindow;
 E3d3DPosition	LTMove;
 E3dMatrix	LMatrix;
 double		LDiffFact=0.0, LDCamX, LDCamY, LTViewDistance, LPtrDiff, LRoll, LLat, LLong;
 int		LPtrXDiff, LPtrYDiff;
 unsigned int	LWC, LWN;
 EBool		LMainCameraChanged;


 LPtrXDiff=E3dp_PrevPtrX-LPtrX;
 LPtrYDiff=E3dp_PrevPtrY-LPtrY;

// Get appropriate Camera structure
//
 switch(L3DWindow->ViewMode)
 {
  case E3dVM_PERSPECTIVE:
   LCamera=&(L3DWindow->PerspectiveCamera);
  break;

  case E3dVM_TOP:
   LCamera=&(L3DWindow->TopCamera);
  break;

  case E3dVM_FRONT:
   LCamera=&(L3DWindow->FrontCamera);
  break;

  case E3dVM_RIGHT:
   LCamera=&(L3DWindow->RightCamera);
  break;

  case E3dVM_SCHEMATICS:
   LCamera=&(L3DWindow->SchematicsCamera);
  break;
 }


 if(L3DWindow->ViewMode==E3dVM_PERSPECTIVE)
 {
  switch(E3dp_PointerMode)
  {
   case E3dpPTR_ORBIT:						//  Orbit (tumble) the camera around the interest point
   case E3dpPTR_ORBIT_ODT:
   case E3dpPTR_ORBIT_OTD:
    LLat=LCamera->Latitude;
    LLong=LCamera->Longitude;
    if(LState&(Button1Mask|Button2Mask)) LLong+=((float)LPtrXDiff)*E3dp_Prefs.OrbitStepLongitude;
    if(LState&(Button1Mask|Button3Mask)) LLat-=((float)LPtrYDiff)*E3dp_Prefs.OrbitStepLatitude;
    if(LLat<-89.0) LLat=-89.0;if(LLat>89.0) LLat=89.0;
    E3d_OrbitCamera(LCamera, LLong, LLat);
    E3dp_PrintMessage(0, 0, "Orbiting camera: longitude: %3.2f  latitude: %3.2f", LCamera->Longitude, LCamera->Latitude);

// Refresh camera matrices from Eye/Interest/UpVector
//
    E3d_CameraRefreshMatrices(LCamera);

    E3d_CallCallbacks(LCamera, LCamera->Callbacks, LCamera->NumOfCallbacks, NULL);

// FIXME hack...
//
    if(L3DWindow->Settings.StereoMode!=E3dStereoSHUTTER) E3dp_Redraw3DWindow(L3DWindow);

    if((L3DWindow==E3dp_Main3DWindow)&&(E3dp_Prefs.ShowCamera))
    {
     LWN=E3dp_NumOf3DWindows;
     for(LWC=0;LWC<LWN;LWC++)
     {
      LMWindow=E3dp_3DWindows[LWC];
      if(LMWindow!=E3dp_Main3DWindow)
      {
       if(LMWindow->ViewMode&E3dVM_NORMAL3D) E3dp_Redraw3DWindow(LMWindow);
      }
     }
    }
   break;

   case E3dpPTR_DOLLY:						//  Move camera along the line of sight
   case E3dpPTR_DOLLY_ODT:
   case E3dpPTR_DOLLY_OTD:
    if(LState&Button1Mask) LDiffFact=E3dp_Prefs.DollyStep1;
    if(LState&Button2Mask) LDiffFact=E3dp_Prefs.DollyStep2;
    if(LState&Button3Mask) LDiffFact=E3dp_Prefs.DollyStep3;


// Exponential dolly
//
    if(E3dM_ABS(LPtrXDiff)>E3dM_ABS(LPtrYDiff)) LPtrDiff=(double)LPtrXDiff;
    else LPtrDiff=(double)LPtrYDiff;
    LTViewDistance=LCamera->ViewDistance*pow(.99, -LPtrDiff*LDiffFact*2.0);

//         LTViewDistance=LCamera+LDiffFact*(LPtrXDiff+LPtrYDiff);
    if(LTViewDistance<0.001) LTViewDistance=0.001;
    E3d_CameraSetViewDistance(LCamera, LTViewDistance);

    E3dp_PrintMessage(0, 0, "Dollying camera: view distance: %.4f Position: %f,%f,%f", LCamera->ViewDistance, LCamera->Position.X, LCamera->Position.Y, LCamera->Position.Z);

// Refresh camera matrices from Eye/Interest/UpVector
//
    E3d_CameraRefreshMatrices(LCamera);

// FIXME hack...
//
    if(L3DWindow->Settings.StereoMode!=E3dStereoSHUTTER)     E3dp_Redraw3DWindow(L3DWindow);

    E3d_CallCallbacks(LCamera, LCamera->Callbacks, LCamera->NumOfCallbacks, NULL);

    if((L3DWindow==E3dp_Main3DWindow)&&(E3dp_Prefs.ShowCamera))
    {
     E3dp_RedrawAuxWindows(E3dVM_NORMAL3D);
    }
   break;

   case E3dpPTR_BANK:						//  Bank camera (rotate it around the line of sight)
   case E3dpPTR_BANK_BZ:
    LRoll=LCamera->BankAngle;
    if(LState&(Button1Mask|Button2Mask|Button3Mask)) LRoll+=((float)LPtrXDiff)*E3dp_Prefs.RollStep+((float)LPtrYDiff)*E3dp_Prefs.RollStep;
    LCamera->BankAngle=LRoll;
    E3dp_Redraw3DWindow(L3DWindow);

    E3d_CameraCalcUpVector(LCamera);

// Refresh camera matrices from Eye/Interest/UpVector
//
    E3d_CameraRefreshMatrices(LCamera);

    E3dp_PrintMessage(0, 0, "Banking camera: bank angle: %.4f", LCamera->BankAngle);

    E3d_CallCallbacks(LCamera, LCamera->Callbacks, LCamera->NumOfCallbacks, NULL);

    if((L3DWindow==E3dp_Main3DWindow)&&(E3dp_Prefs.ShowCamera))
    {
     E3dp_RedrawAuxWindows(E3dVM_NORMAL3D);
    }
   break;
  }
 }


/*--------------------------------------*/
/* Camera tracking/scrolling/zooming	*/
/*--------------------------------------*/
 switch(E3dp_PointerMode)
 {
  case E3dpPTR_TRACK:
  case E3dpPTR_TRACK_ODT:
  case E3dpPTR_TRACK_OTD:
// Tracking/scrolling
//
   E3d_MatrixLoadIdentity(LMatrix);
   LMainCameraChanged=FALSE;
   switch(L3DWindow->ViewMode)
   {
    case E3dVM_PERSPECTIVE:
     E3d_CameraCalcOrbit(LCamera);
     E3d_MatrixRotate(LMatrix, 'y', LCamera->Longitude);
     E3d_MatrixRotate(LMatrix, 'x', -LCamera->Latitude);
     E3d_MatrixRotate(LMatrix, 'z', LCamera->BankAngle);

     LDiffFact=LCamera->ViewDistance*2.0*sin(LCamera->FieldOfView*0.5*E3dDEGREES_TO_RADIANS)/L3DWindow->YSize;
     if(LDiffFact<0.001) LDiffFact=0.001;

     LDCamX=LPtrXDiff*LDiffFact;
     LDCamY=-LPtrYDiff*LDiffFact;
     LTMove.X=LDCamX*LMatrix[M00]+LDCamY*LMatrix[M10];
     LTMove.Y=LDCamX*LMatrix[M01]+LDCamY*LMatrix[M11];
     LTMove.Z=LDCamX*LMatrix[M02]+LDCamY*LMatrix[M12];

     LCamera->Position.X+=LTMove.X;
     LCamera->Position.Y+=LTMove.Y;
     LCamera->Position.Z+=LTMove.Z;
     LCamera->InterestPoint.X+=LTMove.X;
     LCamera->InterestPoint.Y+=LTMove.Y;
     LCamera->InterestPoint.Z+=LTMove.Z;
     E3d_CameraUpdateViewDirection(LCamera);

// Refresh camera matrices from Eye/Interest/UpVector
//
     E3d_CameraRefreshMatrices(LCamera);

     if(L3DWindow==E3dp_Main3DWindow) LMainCameraChanged=TRUE;

     E3d_CallCallbacks(LCamera, LCamera->Callbacks, LCamera->NumOfCallbacks, NULL);
    break;

    case E3dVM_TOP:
     E3d_MatrixRotate(LMatrix, 'y', LCamera->Longitude);
     E3d_MatrixRotate(LMatrix, 'x', -LCamera->Latitude);
     E3d_MatrixRotate(LMatrix, 'z', LCamera->BankAngle);
     LDiffFact=LCamera->XZoom*2.0/((float)L3DWindow->XSize);
     LDCamX=LPtrXDiff*LDiffFact;
     LDCamY=-LPtrYDiff*LDiffFact;
     LTMove.X=LDCamX*LMatrix[M00]+LDCamY*LMatrix[M10];
     LTMove.Y=LDCamX*LMatrix[M01]+LDCamY*LMatrix[M11];
     LTMove.Z=LDCamX*LMatrix[M02]+LDCamY*LMatrix[M12];
     LCamera->Position.X+=LTMove.X;
     LCamera->Position.Y+=LTMove.Y;
     LCamera->Position.Z+=LTMove.Z;
     LCamera->InterestPoint.X+=LTMove.X;
     LCamera->InterestPoint.Y+=LTMove.Y;
     LCamera->InterestPoint.Z+=LTMove.Z;
    break;

    case E3dVM_FRONT:
     E3d_MatrixRotate(LMatrix, 'y', LCamera->Longitude);
     E3d_MatrixRotate(LMatrix, 'x', -LCamera->Latitude);
     E3d_MatrixRotate(LMatrix, 'z', LCamera->BankAngle);
     LDiffFact=LCamera->XZoom*2.0/((float)L3DWindow->XSize);
     LDCamX=LPtrXDiff*LDiffFact;
     LDCamY=-LPtrYDiff*LDiffFact;
     LTMove.X=LDCamX*LMatrix[M00]+LDCamY*LMatrix[M10];
     LTMove.Y=LDCamX*LMatrix[M01]+LDCamY*LMatrix[M11];
     LTMove.Z=LDCamX*LMatrix[M02]+LDCamY*LMatrix[M12];

     LCamera->Position.X+=LTMove.X;
     LCamera->Position.Y+=LTMove.Y;
     LCamera->Position.Z+=LTMove.Z;
     LCamera->InterestPoint.X+=LTMove.X;
     LCamera->InterestPoint.Y+=LTMove.Y;
     LCamera->InterestPoint.Z+=LTMove.Z;
    break;

    case E3dVM_RIGHT:
     E3d_MatrixRotate(LMatrix, 'y', LCamera->Longitude);
     E3d_MatrixRotate(LMatrix, 'x', -LCamera->Latitude);
     E3d_MatrixRotate(LMatrix, 'z', LCamera->BankAngle);
     LDiffFact=LCamera->XZoom*2.0/((float)L3DWindow->XSize);
     LDCamX=LPtrXDiff*LDiffFact;
     LDCamY=-LPtrYDiff*LDiffFact;
     LTMove.X=LDCamX*LMatrix[M00]+LDCamY*LMatrix[M10];
     LTMove.Y=LDCamX*LMatrix[M01]+LDCamY*LMatrix[M11];
     LTMove.Z=LDCamX*LMatrix[M02]+LDCamY*LMatrix[M12];
     LCamera->Position.X+=LTMove.X;
     LCamera->Position.Y+=LTMove.Y;
     LCamera->Position.Z+=LTMove.Z;
     LCamera->InterestPoint.X+=LTMove.X;
     LCamera->InterestPoint.Y+=LTMove.Y;
     LCamera->InterestPoint.Z+=LTMove.Z;
    break;

    case E3dVM_SCHEMATICS:
     E3d_MatrixRotate(LMatrix, 'y', LCamera->Longitude);
     E3d_MatrixRotate(LMatrix, 'x', -LCamera->Latitude);
     E3d_MatrixRotate(LMatrix, 'z', LCamera->BankAngle);
     LDiffFact=LCamera->XZoom*2.0/((float)L3DWindow->XSize);
     LDCamX=LPtrXDiff*LDiffFact;
     LDCamY=-LPtrYDiff*LDiffFact;
     LTMove.X=LDCamX*LMatrix[M00]+LDCamY*LMatrix[M10];
     LTMove.Y=LDCamX*LMatrix[M01]+LDCamY*LMatrix[M11];
     LTMove.Z=LDCamX*LMatrix[M02]+LDCamY*LMatrix[M12];

     LCamera->Position.X+=LTMove.X;
     LCamera->Position.Y+=LTMove.Y;
     LCamera->Position.Z+=LTMove.Z;
     LCamera->InterestPoint.X+=LTMove.X;
     LCamera->InterestPoint.Y+=LTMove.Y;
     LCamera->InterestPoint.Z+=LTMove.Z;
    break;
   }

// FIXME hack...
//
   if(L3DWindow->Settings.StereoMode!=E3dStereoSHUTTER) E3dp_Redraw3DWindow(L3DWindow);

   if(LMainCameraChanged&&(E3dp_Prefs.ShowCamera))
   {
    E3dp_RedrawAuxWindows(E3dVM_NORMAL3D);
   }
  break;

  case E3dpPTR_ZOOM:
  case E3dpPTR_ZOOM_BZ:
// Zooming
//
   if((LState&(Button1Mask|Button2Mask|Button3Mask))==Button2Mask)	// Only button2 is pressed ?
   {
    if(E3dM_ABS(LPtrXDiff)>E3dM_ABS(LPtrYDiff)) LPtrDiff=(double)LPtrXDiff;
    else LPtrDiff=(double)LPtrYDiff;
    switch(L3DWindow->ViewMode)
    {
     case E3dVM_PERSPECTIVE:
      LCamera->FieldOfView+=0.2*LPtrDiff;
      if(LCamera->FieldOfView<L3DWindow->FieldOfViewMin) LCamera->FieldOfView=L3DWindow->FieldOfViewMin;
      else if(LCamera->FieldOfView>L3DWindow->FieldOfViewMax) LCamera->FieldOfView=L3DWindow->FieldOfViewMax;

      E3d_CallCallbacks(LCamera, LCamera->Callbacks, LCamera->NumOfCallbacks, NULL);

      E3dp_PrintMessage(0, 2500, "Zooming camera. Field of view: %5.2f degrees", LCamera->FieldOfView);
     break;

     case E3dVM_TOP:
     case E3dVM_FRONT:
     case E3dVM_RIGHT:
      LCamera->XZoom*=pow(.99, -LPtrDiff);
      if(LCamera->XZoom<L3DWindow->ZoomMin) LCamera->XZoom=L3DWindow->ZoomMin;
      LCamera->YZoom=LCamera->XZoom;
     break;

     case E3dVM_SCHEMATICS:
      LCamera->XZoom*=pow(.99, -LPtrDiff);
      if(LCamera->XZoom<2.0) LCamera->XZoom=2.0;
      else if(LCamera->XZoom>400.0) LCamera->XZoom=400.0;
      LCamera->YZoom=LCamera->XZoom;
     break;
    }

// FIXME hack...
//
    if(L3DWindow->Settings.StereoMode!=E3dStereoSHUTTER)     E3dp_Redraw3DWindow(L3DWindow);
   }
  break;
 }
}
