/*
  Maze editor: 3D visualizer class
  Copyright (C) 1998,1999 by Jorrit Tyberghein
  Written by Andrew Zabolotny <bit@eltech.ru>

  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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "sysdef.h"
#include "me_3d.h"
#include "itxtmgr.h"
#include "csws/cswssys.h"

// mzDraftEditor class palette
static int palette_mzCameraView[] =
{
  cs_Color_Black,              // background
  cs_Color_Red_D,               // Unselected vertices
  cs_Color_Gray_D,              // Inactive vertices
  cs_Color_Red_L,               // Selected vertices
  cs_Color_Gray_D,              // Inactive polygon
  cs_Color_Cyan_M,              // Active polygon
  cs_Color_Red_L,               // Selected polygon
  cs_Color_Blue_D,              // Coordinate axis
  cs_Color_Blue_M,              // Coordinate axis names
  cs_Color_Black,               // Grid
  cs_Color_Red_L,               // Selection rectangle
  cs_Color_Green_D,             // Modification axis
  cs_Color_Brown_M,             // Modified lines
  0                             // Scratchpad
};

#define CSPAL_3DVIEW_BACKGROUND	0

// Minimum and maximum scale factors
#define MIN_SCALE       0.01
#define MAX_SCALE       1000
#define DEFAULT_SCALE   4

mz3DWindow::mz3DWindow (csComponent *iParent, char *iTitle) :
  csWindow (iParent, iTitle, 0)
{
}

bool mz3DWindow::SetRect (int xmin, int ymin, int xmax, int ymax)
{
  bool ret = csWindow::SetRect (xmin, ymin, xmax, ymax);
//  System->Printf (MSG_INITIALIZATION, "in mz3DWindow : Setting rectangle to %d %d %d %d! \n", xmin, ymin, xmax, ymax);
  return ret;
}

mzCameraView::mzCameraView (csComponent *iParent) : csComponent (iParent)
{
  SetPalette (palette_mzCameraView, sizeof (mzCameraView) / sizeof (int));
  state |= CSS_SELECTABLE;
  // testing
  phi = 0;
  theta = 0;
  scale = DEFAULT_SCALE;
  pos.Set (0, 0, -10.0);
  rview = NULL;
  cam = NULL;
  UpdateMatrix ();

  if (parent)
    parent->SendCommand (cscmdWindowSetClient, (void *)this);
}

void mzCameraView::UpdateMatrix ()
{
  double phi_r = (phi * M_PI) / 180;
  double theta_r = (theta * M_PI) / 180;
  double sp = sin (phi_r);
  double cp = cos (phi_r);
  double st = sin (theta_r);
  double ct = cos (theta_r);
  double s = 1.0;
//  double s = double ((bound.Width () + bound.Height ()) / 2) / scale;
  w2c.Set (s * cp,  s * sp * st, -s * sp * ct,
           0,       s * ct,       s * st,
           s * sp, -s * cp * st,  s * cp * ct);
  if (s)
    c2w = w2c.GetInverse ();
  else
    c2w = w2c;

  win2world (0,                  0,                   0, clip_corner [0]);
  win2world (bound.Width () - 1, 0,                   0, clip_corner [1]);
  win2world (bound.Width () - 1, bound.Height () - 1, 0, clip_corner [2]);
  win2world (0,                  bound.Height () - 1, 0, clip_corner [3]);

#define MAKE_EQ(n)                                                      \
  clip_eq [n].A = clip_corner [(n + 1) & 3].x - clip_corner [n].x;      \
  clip_eq [n].B = clip_corner [(n + 1) & 3].y - clip_corner [n].y;      \
  clip_eq [n].C = clip_corner [(n + 1) & 3].z - clip_corner [n].z;      \
  clip_eq [n].D = -(clip_eq [n].A * clip_corner [n].x                   \
                  + clip_eq [n].B * clip_corner [n].y                   \
                  + clip_eq [n].C * clip_corner [n].z);
  MAKE_EQ (0);
  MAKE_EQ (1);
  MAKE_EQ (2);
  MAKE_EQ (3);

  // Find the minimal and maximal coordinates of coordinate axes
  xmin = ymin = zmin = +999999;
  xmax = ymax = zmax = -999999;
  for (int i = 0; i < 4; i++)
  {
    // Compute the coordinate where OX axis intersects with clipping plane
    if (ABS (clip_eq [i].A) > SMALL_EPSILON)
    {
      float x = - (clip_eq [i].D / clip_eq [i].A);
      if (x < xmin) xmin = x;
      if (x > xmax) xmax = x;
    } /* endif */
    // Compute the coordinate where OY axis intersects with clipping plane
    if (ABS (clip_eq [i].B) > SMALL_EPSILON)
    {
      float y = - (clip_eq [i].D / clip_eq [i].B);
      if (y < ymin) ymin = y;
      if (y > ymax) ymax = y;
    } /* endif */
    // Compute the coordinate where OZ axis intersects with clipping plane
    if (ABS (clip_eq [i].C) > SMALL_EPSILON)
    {
      float z = - (clip_eq [i].D / clip_eq [i].C);
      if (z < zmin) zmin = z;
      if (z > zmax) zmax = z;
    } /* endif */
  } /* endfor */

  mz3DVertex org;
  win2world (bound.Width () / 2, bound.Height () / 2, 0, org);
  if (xmin < org.x - scale) xmin = org.x - scale;
  if (xmax > org.x + scale) xmax = org.x + scale;
  if (ymin < org.y - scale) ymin = org.y - scale;
  if (ymax > org.y + scale) ymax = org.y + scale;
  if (zmin < org.z - scale) zmin = org.z - scale;
  if (zmax > org.z + scale) zmax = org.z + scale;
  if (cam) {
    cam->SetC2W( c2w );
    cam->SetW2C( w2c );
    cam->SetPosition( pos );
    delete rview;
    bc = new csBoxClipper((float)xmin, (float)ymin, (float)xmax, (float)ymax);
    rview = new csRenderView(*cam, bc, System->piG3D, System->piG2D);
  }
}

void mzCameraView::win2world (int x, int y, float z, csVector3 &v)
{
  csVector3 p ((float)(x - bound.Width () / 2), (float)(bound.Height () / 2 - y), z);
  v = c2w * p + pos;
}

void mzCameraView::win2world (float x, float y, float z, csVector3 &v)
{
  csVector3 p (x - bound.Width () / 2, bound.Height () / 2 - y, z);
  v = c2w * p + pos;
}

void mzCameraView::world2win (csVector3 &v, int &x, int &y)
{
  csVector3 p = w2c * (v - pos);
  x = bound.Width () / 2 + (int)p.x;
  y = bound.Height () / 2 - (int)p.y;
}

void mzCameraView::world2win (csVector3 &v, csVector3 &dest)
{
  dest = w2c * (v - pos);
  dest.x = bound.Width () / 2 + dest.x;
  dest.y = bound.Height () / 2 - dest.y;
}

bool mzCameraView::SetRect (int xmin, int ymin, int xmax, int ymax)
{
  int w = bound.Width ();
  int h = bound.Height ();
  bool ret = csComponent::SetRect (xmin, ymin, xmax, ymax);
  if ((w != bound.Width ())
   || (h != bound.Height ()))
    UpdateMatrix ();
  if (!cam) 
    cam =new csCamera();
  cam->SetC2W( c2w );
  cam->SetW2C( w2c );
  cam->SetPosition( pos );
  cam->aspect = System->FrameHeight;
//  System->Printf (MSG_INITIALIZATION, "Setting camera aspect to %f! \n", cam->aspect );
//  System->Printf (MSG_INITIALIZATION, "Setting rectangle to %d %d %d %d! \n", xmin, ymin, xmax, ymax);
  csCamera::aspect = cam->aspect;
  if (!rview) {
    bc = new csBoxClipper((float)xmin, (float)ymin, (float)xmax, (float)ymax);
    rview = new csRenderView(*cam, bc, System->piG3D, System->piG2D);
//    System->Printf (MSG_INITIALIZATION, "Creating new camera and view\n");
  } else {
    delete rview;
    bc = new csBoxClipper((float)xmin, (float)ymin, (float)xmax, (float)ymax);
    rview = new csRenderView(*cam, bc, System->piG3D, System->piG2D);
  }
  return ret;
}

void mzCameraView::SetViewAngles (float iPhi, float iTheta)
{
  phi = rint (fmod (iPhi, 360));
  if (phi < 0)
    phi = 360 + phi;
  theta = rint (fmod (iTheta, 360));
  if (theta >= 180)
    theta = theta - 360;
  if (theta <= -180)
    theta = 360 + theta;
  UpdateMatrix ();
}

void mzCameraView::Draw ()
{
  Box (0, 0, bound.Width (), bound.Height (), CSPAL_3DVIEW_BACKGROUND);

//System->Printf (MSG_INITIALIZATION, "Calling camera view draw \n");
//System->Printf (MSG_INITIALIZATION, "Box dimension %d %d \n", bound.Width (), bound.Height ());

//  if (GLOBAL_MODEL) {
//    for (int i=0; i<GLOBAL_MODEL->Sectors(); i++)
//      GLOBAL_MODEL->Sector( i ).Prepare();
//    GLOBAL_MODEL->Draw ( this );
//  }
  csComponent::Draw ();
}

bool mzCameraView::HandleEvent (csEvent &Event)
{
  // these variables are used when user cancels dragging
  static float old_phi, old_theta;
  static csVector3 old_pos;

  switch (Event.Type)
  {
    case csevMouseDown:
      if (Event.Mouse.Button == 1)
      {
        app->CaptureMouse (this);
        dragMouseX = oldMouseX = Event.Mouse.x;
        dragMouseY = oldMouseY = Event.Mouse.y;
        old_phi = phi;
        old_theta = theta;
      }
    break;
    case csevMouseUp:
      if (Event.Mouse.Button != 1)
        break;
      if (app->MouseOwner == this)
        app->CaptureMouse (NULL);
      Draw();
      break;
    case csevMouseMove:
          if (app->MouseOwner == this)
          {
            SetMouse (csmcMove);
            SetViewAngles (phi + (Event.Mouse.x - dragMouseX),
                           theta + (Event.Mouse.y - dragMouseY));
            dragMouseX = Event.Mouse.x;
            dragMouseY = Event.Mouse.y;
            Invalidate ();
          }
          else
            SetMouse (csmcArrow);
      break;
    case csevBroadcast:
      switch (Event.Command.Code)
      {
        case cscmdMzModelChangeNotify:
        case cscmdMzDraftChangeNotify:
          Invalidate ();
          return true;
//        case cscmdMzDraftViewFit:
//          FitModelInWindow ();
//          return true;
//        case cscmdMzDraftViewOrigin:
//          pos.Set (0, 0, 0);
//          UpdateMatrix ();
//          Invalidate ();
//          return true;
      } /* endswitch */
  } /* endswitch */
  return csComponent::HandleEvent (Event);
}
