/*
  Maze editor: draft editor class
  Copyright (C) 1998 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 <limits.h>
#include <float.h>
#include "sysdef.h"
#include "me_draft.h"
#include "me_res.h"
#include "me_text.h"
#include "csengine/polygon.h"
#include "csobject/nameobj.h"
#include "csengine/thingtpl.h"

// mzDraftEditor class palette
static int palette_mzDraftEditor[] =
{
  cs_Color_Gray_L,              // 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
};

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

int  mzDraftEditor::ConnVerticeOrd = 0;
bool mzDraftEditor::doCreatePolygon = false;
int  mzDraftEditor::PolygonVertices = 0;
bool mzDraftEditor::doCreateBody = false;
int  mzDraftEditor::BodyCreationPhase = 0;

mzDraftWindow::mzDraftWindow (csComponent *iParent, char *iTitle) :
  csWindow (iParent, iTitle, CSWS_BUTSYSMENU | CSWS_TITLEBAR
    | CSWS_BUTCLOSE | CSWS_BUTHIDE | CSWS_BUTMAXIMIZE)
{
  CHK (tbTop = new csButton (this, cscmdMzDraftViewTop, CSBS_SHIFT, csbfsVeryThinRect));
  tbTop->SetText ("T");
  CHK (tbBottom = new csButton (this, cscmdMzDraftViewBottom, CSBS_SHIFT, csbfsVeryThinRect));
  tbBottom->SetText ("B");
  CHK (tbLeft = new csButton (this, cscmdMzDraftViewLeft, CSBS_SHIFT, csbfsVeryThinRect));
  tbLeft->SetText ("L");
  CHK (tbRight = new csButton (this, cscmdMzDraftViewRight, CSBS_SHIFT, csbfsVeryThinRect));
  tbRight->SetText ("R");
  CHK (tbFront = new csButton (this, cscmdMzDraftViewFront, CSBS_SHIFT, csbfsVeryThinRect));
  tbFront->SetText ("F");
  CHK (tbBack = new csButton (this, cscmdMzDraftViewBack, CSBS_SHIFT, csbfsVeryThinRect));
  tbBack->SetText ("K");
  CHK (tbFit = new csButton (this, cscmdMzDraftViewFit, CSBS_SHIFT, csbfsVeryThinRect));
  tbFit->SetText ("Fit");
  CHK (tbOrg = new csButton (this, cscmdMzDraftViewOrigin, CSBS_SHIFT, csbfsVeryThinRect));
  tbOrg->SetText ("Org");
}

bool mzDraftWindow::SetRect (int xmin, int ymin, int xmax, int ymax)
{
  bool ret = csWindow::SetRect (xmin, ymin, xmax, ymax);

  csComponent *t = GetChild (CSWID_TITLEBAR);
  int h = t->bound.Height ();

  tbTop->SetRect (t->bound.xmin, t->bound.ymin, t->bound.xmin + h, t->bound.ymin + h);
  t->bound.xmin += h;
  tbBottom->SetRect (t->bound.xmin, t->bound.ymin, t->bound.xmin + h, t->bound.ymin + h);
  t->bound.xmin += h;
  tbLeft->SetRect (t->bound.xmin, t->bound.ymin, t->bound.xmin + h, t->bound.ymin + h);
  t->bound.xmin += h;
  tbRight->SetRect (t->bound.xmin, t->bound.ymin, t->bound.xmin + h, t->bound.ymin + h);
  t->bound.xmin += h;
  tbFront->SetRect (t->bound.xmin, t->bound.ymin, t->bound.xmin + h, t->bound.ymin + h);
  t->bound.xmin += h;
  tbBack->SetRect (t->bound.xmin, t->bound.ymin, t->bound.xmin + h, t->bound.ymin + h);
  t->bound.xmin += h;

  int w = h * 7 / 4;
  tbFit->SetRect (t->bound.xmax - w, t->bound.ymin, t->bound.xmax, t->bound.ymin + h);
  t->bound.xmax -= w;
  tbOrg->SetRect (t->bound.xmax - w, t->bound.ymin, t->bound.xmax, t->bound.ymin + h);
  t->bound.xmax -= w;

  return ret;
}

void mzDraftWindow::FixSize (int &newW, int &newH)
{
  csWindow::FixSize (newW, newH);
  int minw = tbBack->bound.xmax + (bound.Width () - tbOrg->bound.xmin);
  if (newW < minw)
    newW = minw;
}

bool mzDraftWindow::HandleEvent (csEvent &Event)
{
  switch (Event.Type)
  {
    case csevCommand:
      switch (Event.Command.Code)
      {
        case cscmdButtonRightClick:
        {
          UInt cc = ((csButton *)Event.Command.Info)->GetCommandCode ();
          if ((cc == cscmdMzDraftViewFit)
           || (cc == cscmdMzDraftViewOrigin))
          {
            csEvent ev (0, csevBroadcast, cc, Event.Command.Info);
            parent->HandleEvent (ev);
          }
          break;
        }
        case cscmdMzDraftViewTop:
        case cscmdMzDraftViewBottom:
        case cscmdMzDraftViewLeft:
        case cscmdMzDraftViewRight:
        case cscmdMzDraftViewFront:
        case cscmdMzDraftViewBack:
        case cscmdMzDraftViewFit:
        case cscmdMzDraftViewOrigin:
          Event.Command.Info = GetChild (CSWID_CLIENT)->SendCommand
            (Event.Command.Code, Event.Command.Info);
          return true;
      } /* endswitch */
      break;
  } /* endswitch */
  return csWindow::HandleEvent (Event);
}

mzDraftEditor::mzDraftEditor (csComponent *iParent) : csComponent (iParent)
{
  SetPalette (palette_mzDraftEditor, sizeof (palette_mzDraftEditor) / sizeof (int));
  state |= CSS_SELECTABLE;
  phi = 0;
  theta = 0;
  scale = DEFAULT_SCALE;
  pos.Set (0, 0, 0);
  UpdateMatrix ();
  menu = NULL;
  dragRect = false;
  ModifyMode = 0;
  if (parent)
    parent->SendCommand (cscmdWindowSetClient, (void *)this);
}

void mzDraftEditor::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 ();

  if (parent)
  {
    char *vt = TEXT_VIEW_ABSTRACT;
    float unit = -scale / double ((bound.Width () + bound.Height ()) / 2);
    csVector3 v1 = w2c * csVector3 (0, 0, unit);
    csVector3 v2 = w2c * csVector3 (0, unit, 0);
         if ((fabs (v1.x) < SMALL_EPSILON)
          && (fabs (v1.y + 1) < SMALL_EPSILON)
          && (fabs (v1.z) < SMALL_EPSILON)
          && (fabs (v2.x) < SMALL_EPSILON)
          && (fabs (v2.y) < SMALL_EPSILON)
          && (fabs (v2.z - 1) < SMALL_EPSILON))
      vt = TEXT_VIEW_TOP;
    else if ((fabs (v1.x) < SMALL_EPSILON)
          && (fabs (v1.y - 1) < SMALL_EPSILON)
          && (fabs (v1.z) < SMALL_EPSILON)
          && (fabs (v2.x) < SMALL_EPSILON)
          && (fabs (v2.y) < SMALL_EPSILON)
          && (fabs (v2.z + 1) < SMALL_EPSILON))
      vt = TEXT_VIEW_BOTTOM;
    else if ((fabs (v1.x) < SMALL_EPSILON)
          && (fabs (v1.y) < SMALL_EPSILON)
          && (fabs (v1.z + 1) < SMALL_EPSILON))
      vt = TEXT_VIEW_BACK;
    else if ((fabs (v1.x) < SMALL_EPSILON)
          && (fabs (v1.y) < SMALL_EPSILON)
          && (fabs (v1.z - 1) < SMALL_EPSILON))
      vt = TEXT_VIEW_FRONT;
    else if ((fabs (v1.x + 1) < SMALL_EPSILON)
          && (fabs (v1.y) < SMALL_EPSILON)
          && (fabs (v1.z) < SMALL_EPSILON))
      vt = TEXT_VIEW_RIGHT;
    else if ((fabs (v1.x - 1) < SMALL_EPSILON)
          && (fabs (v1.y) < SMALL_EPSILON)
          && (fabs (v1.z) < SMALL_EPSILON))
      vt = TEXT_VIEW_LEFT;

    char oldtitle [64], newtitle [64];
    parent->GetText (oldtitle, sizeof (oldtitle));
    char *sq = strchr (oldtitle, ']');
    if (sq)
      *(sq + 1) = 0;

    sprintf (newtitle, "%s [%s]", oldtitle, vt);
    parent->SetText (newtitle);
  } /* endif */
}

void mzDraftEditor::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 = 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;
}

bool mzDraftEditor::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 ();
  return ret;
}

void mzDraftEditor::Draw ()
{
  Box (0, 0, bound.Width (), bound.Height (), CSPAL_DRAFT_BACKGROUND);
  //System->Printf (MSG_DEBUG_0, "Box dimension %d %d \n", bound.Width (), bound.Height ());

  // Draw coordinate axes and grid if required
  DrawGadgets ();

  int num_models = GLOBAL_MODELS.Length ();
  if (F_DRAFT_SHOWINACTIVEROOMS)
    for (int i = 0; i < num_models; i++)
      if (i != GLOBAL_MODELINDEX)
      {
        mz3DModel *m = (mz3DModel *)GLOBAL_MODELS [i];
	// currently we show all the stuff in this level
        // if (m->Type == csmtRoom || m->Type == csmtSector || m->Type == csmtThing)
          m->Draw (this, false, false, false);
      } /* endif */
  if (GLOBAL_MODEL)
    GLOBAL_MODEL->Draw (this, true, ModifyMode != 0, GLOBAL_KEY_STATE (shift));

  if (doCreatePolygon)
    ShowPolygon (oldMouseX, oldMouseY, dragMouseX, dragMouseY);
  if (doCreateBody)
    ShowBody (oldMouseX, oldMouseY, dragMouseX, dragMouseY);

  if (dragRect)
  {
    int x1 = dragMouseX, y1 = dragMouseY, x2 = oldMouseX, y2 = oldMouseY;
    if (x1 > x2) { int temp = x2; x2 = x1; x1 = temp; }
    if (y1 > y2) { int temp = y2; y2 = y1; y1 = temp; }
    Rect3D (x1, y1, x2, y2, CSPAL_DRAFT_SELRECT, CSPAL_DRAFT_SELRECT);
  } /* endif */

  csComponent::Draw ();
}

bool mzDraftEditor::clip3Dline (csVector3 &v1, csVector3 &v2)
{
  for (int i = 0; i < 4; i++)
  {
    float vis1 = v1.x * clip_eq [i].A + v1.y * clip_eq [i].B
               + v1.z * clip_eq [i].C + clip_eq [i].D;
    float vis2 = v2.x * clip_eq [i].A + v2.y * clip_eq [i].B
               + v2.z * clip_eq [i].C + clip_eq [i].D;
    if ((vis1 <= 0) && (vis2 <= 0))
      return false;                     // line is invisible
    if ((vis1 >= 0) && (vis2 >= 0))
      continue;                         // not clipped by this plane

    double denom = (clip_eq [i].A * (v2.x - v1.x)
                  + clip_eq [i].B * (v2.y - v1.y)
                  + clip_eq [i].C * (v2.z - v1.z));
    // Denominator cannot be zero since we already know plane intersects segment
    double t = - (clip_eq [i].A * v1.x + clip_eq [i].B * v1.y
                + clip_eq [i].C * v1.z + clip_eq [i].D) / denom;
    // also t cannot bigger than 1 or less than 0 because of the same thing
    float ix = v1.x + t * (v2.x - v1.x);
    float iy = v1.y + t * (v2.y - v1.y);
    float iz = v1.z + t * (v2.z - v1.z);
    if (vis1 > 0)
      v2.Set (ix, iy, iz);
    else
      v1.Set (ix, iy, iz);
  } /* endfor */
  return true;
}

void mzDraftEditor::SnapToGrid (csVector3 &v)
{
  if (F_DRAFT_GRID && F_DRAFT_SNAPTOGRID)
  {
    v.x = rint (v.x / GLOBAL_DRAFT_GRIDSPACING) * GLOBAL_DRAFT_GRIDSPACING;
    v.y = rint (v.y / GLOBAL_DRAFT_GRIDSPACING) * GLOBAL_DRAFT_GRIDSPACING;
    v.z = rint (v.z / GLOBAL_DRAFT_GRIDSPACING) * GLOBAL_DRAFT_GRIDSPACING;
  } /* endif */
}

void mzDraftEditor::GridInterpolationSteps (mz3DVertex &v1, mz3DVertex &v2,
  mz3DVertex &step, int &steps)
{
  mz3DVertex delta = v2 - v1;
  float f;
  if (fabs (delta.x) > fabs (delta.y))
    if (fabs (delta.x) > fabs (delta.z))
      f = GLOBAL_DRAFT_GRIDSPACING / delta.x;
    else
      f = GLOBAL_DRAFT_GRIDSPACING / delta.z;
  else
    if (fabs (delta.y) > fabs (delta.z))
      f = GLOBAL_DRAFT_GRIDSPACING / delta.y;
    else
      f = GLOBAL_DRAFT_GRIDSPACING / delta.z;
  f = fabs (f);
  step.Set (f * delta.x, f * delta.y, f * delta.z);
  steps = 1 + (int)ceil (fabs (1 / f));
}

void mzDraftEditor::DrawGadgets ()
{
  // Draw grid if needed
  if (F_DRAFT_GRID)
  {
    mz3DVertex vl = clip_corner [0];
    mz3DVertex vr = clip_corner [1];

    mz3DVertex dl, dr;
    int sl, sr;
    GridInterpolationSteps (vl, clip_corner [3], dl, sl);
    GridInterpolationSteps (vr, clip_corner [2], dr, sr);

    if (sl < sr)
      sl = sr;

    if ((sl >= bound.Height () / 5) || (sl < 0))
      sl = 0;

    while (sl)
    {
      mz3DVertex hv (vl), dh;
      int hs;
      GridInterpolationSteps (vl, vr, dh, hs);
      while (hs)
      {
        mz3DVertex v;
        v.x = floor (hv.x / GLOBAL_DRAFT_GRIDSPACING) * GLOBAL_DRAFT_GRIDSPACING;
        v.y = floor (hv.y / GLOBAL_DRAFT_GRIDSPACING) * GLOBAL_DRAFT_GRIDSPACING;
        v.z = floor (hv.z / GLOBAL_DRAFT_GRIDSPACING) * GLOBAL_DRAFT_GRIDSPACING;
        int wx, wy;
        world2win (v, wx, wy);
        Pixel (wx, wy, CSPAL_DRAFT_GRID);
        hv += dh;
        hs--;
      } /* endwhile */
      vl += dl;
      vr += dr;
      sl--;
    } /* endwhile */
  } /* endif */

  // Draw coordinate axis if needed
  if (F_DRAFT_AXIS)
  {
    csVector3 aXn, aXp, aYn, aYp, aZn, aZp;
    aXn.Set (xmin, 0, 0); aXp.Set (xmax, 0, 0);
    aYn.Set (0, ymin, 0); aYp.Set (0, ymax, 0);
    aZn.Set (0, 0, zmin); aZp.Set (0, 0, zmax);

    int x1=0, y1=0, x2=0, y2=0;
    csVector2 arrow1, arrow2;

#define LINE3D(p1,p2)                                                   \
    if (clip3Dline (p1, p2))                                            \
    {                                                                   \
      world2win (p1, x1, y1); world2win (p2, x2, y2);                   \
      Line (x1, y1, x2, y2, CSPAL_DRAFT_AXIS);                          \
    } /* endif */

    float norm;
#define ARROW(axis)                                                             \
    arrow1.Set (x1 - x2, y1 - y2);                                              \
    norm = arrow1.Norm ();                                                      \
    if (norm > 6)                                                               \
    {                                                                           \
      arrow1 /= (norm / 6);                                                     \
      arrow2 = arrow1; arrow2.Rotate (+ M_PI_2 / 3);                            \
      Line (x2, y2, x2 + (int)arrow2.x, y2 + (int)arrow2.y, CSPAL_DRAFT_AXIS);  \
      arrow2 = arrow1; arrow2.Rotate (- M_PI_2 / 3);                            \
      Line (x2, y2, x2 + (int)arrow2.x, y2 + (int)arrow2.y, CSPAL_DRAFT_AXIS);  \
      arrow2 = arrow1 * 2; arrow2.Rotate (- M_PI_2 / 2);                        \
      int tw = TextWidth (axis) / 2, th = TextHeight () / 2;                    \
      Text (x2 + (int)arrow2.x - tw, y2 + (int)arrow2.y - th,                   \
        CSPAL_DRAFT_AXISNAME, -1, axis);                                        \
    } /* endif */

    x1 = -99999; LINE3D (aXn, aXp); if (x1 != -99999) { ARROW ("X"); }
    x1 = -99999; LINE3D (aYn, aYp); if (x1 != -99999) { ARROW ("Y"); }
    x1 = -99999; LINE3D (aZn, aZp); if (x1 != -99999) { ARROW ("Z"); }

#undef LINE3D
#undef ARROW
  } /* endif */

  if (F_DRAFT_MODIFYAXIS
   && (GLOBAL_MODIFYAXIS.Type == axUserDefined))
  {
    int x, y;
    world2win (GLOBAL_MODIFYAXIS.Origin, x, y);
    Line (x - 4, y - 4, x - 2, y - 2, CSPAL_DRAFT_MODIFYAXIS);
    Line (x + 4, y - 4, x + 2, y - 2, CSPAL_DRAFT_MODIFYAXIS);
    Line (x + 4, y + 4, x + 2, y + 2, CSPAL_DRAFT_MODIFYAXIS);
    Line (x - 4, y + 4, x - 2, y + 2, CSPAL_DRAFT_MODIFYAXIS);
    Pixel (x, y, CSPAL_DRAFT_MODIFYAXIS);
  } /* endif */
}

void mzDraftEditor::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 mzDraftEditor::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 mzDraftEditor::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 mzDraftEditor::world2win (csVector3 &v, csVector3 &dest)
{
  dest = w2c * (v - pos);
  dest.x = bound.Width () / 2 + dest.x;
  dest.y = bound.Height () / 2 - dest.y;
}

void mzDraftEditor::world2winM (csVector3 &v, int &x, int &y)
{
  csVector3 tmp (v);
  Modify (tmp);
  world2win (tmp, x, y);
}

void mzDraftEditor::PrepareModificationMatrix ()
{
  ModifyDeform.Identity ();
  ModifyMotion.Set (0, 0, 0);
  int dx = dragMouseX - oldMouseX;
  int dy = oldMouseY - dragMouseY;
  switch (ModifyMode)
  {
    case cscmdMzMove:
      if (GLOBAL_KEY_STATE (ctrl)) dx = 0;
      if (GLOBAL_KEY_STATE (alt))  dy = 0;
      ModifyMotion = c2w * csVector3 (dx, dy, 0);
      GLOBAL_SETDESC3 (TEXT_PROCESS_MOTION, ModifyMotion.x, ModifyMotion.y,
        ModifyMotion.z);
      break;
    case cscmdMzScale2D:
    {
      float scale = 1 + ((float)dx) / 200;
      GLOBAL_SETDESC1 (TEXT_PROCESS_SCALE, scale * 100);
      float scaleX = scale, scaleY = scale;
      if (GLOBAL_KEY_STATE (ctrl)) scaleX = 1;
      if (GLOBAL_KEY_STATE (alt))  scaleY = 1;
      ModifyDeform = w2c * csMatrix3 (scaleX, 0,     0,
                                    0,     scaleY, 0,
                                    0,     0,      1);
      ModifyDeform *= c2w;
      break;
    }
    case cscmdMzScale3D:
    {
      float scale = 1 + ((float)dx) / 200;
      GLOBAL_SETDESC1 (TEXT_PROCESS_SCALE, scale * 100);
      float scaleX = scale, scaleY = scale;
      if (GLOBAL_KEY_STATE (ctrl)) scaleX = 1;
      if (GLOBAL_KEY_STATE (alt))  scaleY = 1;
      ModifyDeform = w2c * csMatrix3 (scaleX, 0,      0,
                                    0,      scaleY, 0,
                                    0,      0,      scale);
      ModifyDeform *= c2w;
      break;
    }
    case cscmdMzRotate:
    {
      double angle = (dx * M_PI) / 180;
      GLOBAL_SETDESC1 (TEXT_PROCESS_ROTATE, dx);
      float ca = cos (angle);
      float sa = sin (angle);
      if (GLOBAL_KEY_STATE (ctrl))
        ModifyDeform = w2c * csMatrix3 (ca,  0,   -sa,
                                      0,   1,   0,
                                      sa,  0,   ca);
      else if (GLOBAL_KEY_STATE (alt))
        ModifyDeform = w2c * csMatrix3 (1,   0,   0,
                                      0,   ca,  sa,
                                      0,   -sa, ca);
      else
        ModifyDeform = w2c * csMatrix3 (ca,  sa,  0,
                                      -sa, ca,  0,
                                      0,   0,   1);
      ModifyDeform *= c2w;
      break;
    }
  } /* endswitch */

  csVector3 min, max;
  if (GLOBAL_MODIFYAXIS.Type != axUserDefined)
    GLOBAL_MODEL->GetBoundingBox (this, min, max, GLOBAL_MODIFYAXIS.OnlySelection);

  switch (GLOBAL_MODIFYAXIS.Type)
  {
    case axUserDefined:
      ModifyCenter = GLOBAL_MODIFYAXIS.Origin;
      break;
    case axTopLeft:
      switch (GLOBAL_MODIFYAXIS.Pos)
      {
        case axViewPlane: win2world (min.x, min.y, 0, ModifyCenter); break;
        case axNear:      win2world (min.x, min.y, min.z, ModifyCenter); break;
        case axCentered:  win2world (min.x, min.y, (min.z + max.z) / 2, ModifyCenter); break;
        case axFar:       win2world (min.x, min.y, max.z, ModifyCenter); break;
      } /* endswitch */
      break;
    case axTopCenter:
      switch (GLOBAL_MODIFYAXIS.Pos)
      {
        case axViewPlane: win2world ((min.x + max.x) / 2, min.y, 0, ModifyCenter); break;
        case axNear:      win2world ((min.x + max.x) / 2, min.y, min.z, ModifyCenter); break;
        case axCentered:  win2world ((min.x + max.x) / 2, min.y, (min.z + max.z) / 2, ModifyCenter); break;
        case axFar:       win2world ((min.x + max.x) / 2, min.y, max.z, ModifyCenter); break;
      } /* endswitch */
      break;
    case axTopRight:
      switch (GLOBAL_MODIFYAXIS.Pos)
      {
        case axViewPlane: win2world (max.x, min.y, 0, ModifyCenter); break;
        case axNear:      win2world (max.x, min.y, min.z, ModifyCenter); break;
        case axCentered:  win2world (max.x, min.y, (min.z + max.z) / 2, ModifyCenter); break;
        case axFar:       win2world (max.x, min.y, max.z, ModifyCenter); break;
      } /* endswitch */
      break;
    case axLeft:
      switch (GLOBAL_MODIFYAXIS.Pos)
      {
        case axViewPlane: win2world (min.x, (min.y + max.y) / 2, 0, ModifyCenter); break;
        case axNear:      win2world (min.x, (min.y + max.y) / 2, min.z, ModifyCenter); break;
        case axCentered:  win2world (min.x, (min.y + max.y) / 2, (min.z + max.z) / 2, ModifyCenter); break;
        case axFar:       win2world (min.x, (min.y + max.y) / 2, max.z, ModifyCenter); break;
      } /* endswitch */
      break;
    case axCenter:
      switch (GLOBAL_MODIFYAXIS.Pos)
      {
        case axViewPlane: win2world ((min.x + max.x) / 2, (min.y + max.y) / 2, 0, ModifyCenter); break;
        case axNear:      win2world ((min.x + max.x) / 2, (min.y + max.y) / 2, min.z, ModifyCenter); break;
        case axCentered:  win2world ((min.x + max.x) / 2, (min.y + max.y) / 2, (min.z + max.z) / 2, ModifyCenter); break;
        case axFar:       win2world ((min.x + max.x) / 2, (min.y + max.y) / 2, max.z, ModifyCenter); break;
      } /* endswitch */
      break;
    case axRight:
      switch (GLOBAL_MODIFYAXIS.Pos)
      {
        case axViewPlane: win2world (max.x, (min.y + max.y) / 2, 0, ModifyCenter); break;
        case axNear:      win2world (max.x, (min.y + max.y) / 2, min.z, ModifyCenter); break;
        case axCentered:  win2world (max.x, (min.y + max.y) / 2, (min.z + max.z) / 2, ModifyCenter); break;
        case axFar:       win2world (max.x, (min.y + max.y) / 2, max.z, ModifyCenter); break;
      } /* endswitch */
      break;
    case axBottomLeft:
      switch (GLOBAL_MODIFYAXIS.Pos)
      {
        case axViewPlane: win2world (min.x, max.y, 0, ModifyCenter); break;
        case axNear:      win2world (min.x, max.y, min.z, ModifyCenter); break;
        case axCentered:  win2world (min.x, max.y, (min.z + max.z) / 2, ModifyCenter); break;
        case axFar:       win2world (min.x, max.y, max.z, ModifyCenter); break;
      } /* endswitch */
      break;
    case axBottomCenter:
      switch (GLOBAL_MODIFYAXIS.Pos)
      {
        case axViewPlane: win2world ((min.x + max.x) / 2, max.y, 0, ModifyCenter); break;
        case axNear:      win2world ((min.x + max.x) / 2, max.y, min.z, ModifyCenter); break;
        case axCentered:  win2world ((min.x + max.x) / 2, max.y, (min.z + max.z) / 2, ModifyCenter); break;
        case axFar:       win2world ((min.x + max.x) / 2, max.y, max.z, ModifyCenter); break;
      } /* endswitch */
      break;
    case axBottomRight:
      switch (GLOBAL_MODIFYAXIS.Pos)
      {
        case axViewPlane: win2world (max.x, max.y, 0, ModifyCenter); break;
        case axNear:      win2world (max.x, max.y, min.z, ModifyCenter); break;
        case axCentered:  win2world (max.x, max.y, (min.z + max.z) / 2, ModifyCenter); break;
        case axFar:       win2world (max.x, max.y, max.z, ModifyCenter); break;
      } /* endswitch */
      break;
  } /* endswitch */
}

void mzDraftEditor::Modify (csVector3 &v)
{
  v = ModifyDeform * (v - ModifyCenter) + ModifyCenter + ModifyMotion;
  SnapToGrid(v);
}

void mzDraftEditor::ZoomInOut (int x, int y, float delta)
{
  float newscale = scale + delta;

  if (newscale < MIN_SCALE)
    newscale = MIN_SCALE;
  if (newscale > MAX_SCALE)
    newscale = MAX_SCALE;
  if (newscale != scale)
  {
    mz3DVertex oldpos;
    win2world (x, y, 0, oldpos);
    scale = newscale;
    UpdateMatrix ();
    mz3DVertex newpos;
    win2world (x, y, 0, newpos);
    pos += (oldpos - newpos);
    UpdateMatrix ();
    Invalidate ();
  } /* endif */
}

int mzDraftEditor::PickVertex (int x, int y)
{
  if (!GLOBAL_MODEL)
    return -1;

  CHK (menu = new csMenu (app, csmfs3D, 0));

  int num_vert = GLOBAL_MODEL->Vertices ();
  int near_vert = 0;
  unsigned int choice = 0;
  for (int i = 0; i < num_vert; i++)
  {
    csVector3 p = w2c * (GLOBAL_MODEL->Vertex (i) - pos);
    int px = bound.Width () / 2 + (int)p.x;
    int py = bound.Height () / 2 - (int)p.y;

    if (fSquare (x - px) + fSquare (y - py) <= GLOBAL_DRAFT_PICKDIST)
    {
      char temp [64];
      sprintf (temp, "%d: %.2f, %.2f, %.2f", i,
        GLOBAL_MODEL->Vertex (i).x,
        GLOBAL_MODEL->Vertex (i).y,
        GLOBAL_MODEL->Vertex (i).z);
      choice = 0x90000000 + i;
      CHK ((void)new csMenuItem (menu, temp, choice));
      near_vert++;
    } /* endif */
  } /* endfor */
  if (near_vert > 1)
  {
    if (near_vert > 20)
    {
      MessageBox (app, TEXT_ERROR, TEXT_TOOMANYNEARVERT, CSMBS_ERROR | CSMBS_ABORT);
      choice = cscmdCancel;
    }
    else
    {
      LocalToGlobal (x, y);
      menu->SetPos (x, y);
      menu->PlaceItems ();
      app->CaptureMouse (menu);
      ((MazeEditor *)app)->EnsureFullyVisible (menu);
      choice = app->Execute (menu);
    } /* endif */;
  } /* endif */
  CHK (delete menu);
  menu = NULL;
  if ((choice >= 0x90000000) && (choice < 0x90000000 + num_vert))
    return choice - 0x90000000;
  else
    return -1;
}

int mzDraftEditor::InsertVertexAt (int x, int y)
{
  int num;
  CHK (mz3DVertex *v = new mz3DVertex);
  win2world (x, y, 0, *v);
  SnapToGrid(*v);
  if (GLOBAL_MODEL->InsertVertex (v, &num))
  {
    // Broadcase "model changed" notification
    GLOBAL_DRAFTCHANGED;
    GLOBAL_MODELINFO;
    return num;
  }
  else
  {
    MessageBox (app, TEXT_ERROR, TEXT_VERTEXALREADY, CSMBS_ERROR | CSMBS_ABORT);
    return -1;
  }
}

void mzDraftEditor::ZoomToRect (csVector3 &o, csVector3 &ha, csVector3 &va, bool iUpdateAngles)
{
  csVector3 v1 = ha - o;
  csVector3 v2 = va - o;
  if (iUpdateAngles)
  {
    // Calculate first the orientation of "camera"
    // For this, compute the cross product of two vectors and find
    // resulting vector's phi and theta
    csVector3 norm = v1 % v2;

    // if length of normal is zero, quit
    if (norm.Norm () == 0)
      return;

    float nphi = atan2 (norm.x, norm.z);
    float ntheta = -atan2 (norm.y, sqrt (fSquare (norm.z) + fSquare (norm.x)));
    SetViewAngles (nphi * (180.0 / M_PI), ntheta * (180.0 / M_PI));
  } /* endif */

  // Now compute the scale
  float s = 4 + (bound.Width () + bound.Height ()) / 2;
  float scale1 = v1.Norm () * s / float (bound.Width ());
  float scale2 = v2.Norm () * s / float (bound.Height ());
  if (scale1 > scale2)
    scale = scale1;
  else
    scale = scale2;
  if (scale < MIN_SCALE)
    scale = MIN_SCALE;
  if (scale > MAX_SCALE)
    scale = MAX_SCALE;

  // Calculate camera position
  pos = o + ((v1 + v2) / 2);

  UpdateMatrix ();
  Invalidate ();
}

void mzDraftEditor::ConnectPolyVertex (int x, int y)
{
  // The static array that is used to collect vertices
  static int poly [MAX_POLYGON_VERTICES];

  bool addpoly = false;
  bool endconn = false;
  bool redraw = false;
  bool already = false;
  int i, vertex;

  if ((x == INT_MAX) && (y == INT_MAX))
  {
    already = true;
    vertex = poly [0];
  }
  else
  {
    // If mouse is outside window bounds, exit
    if (!bound.ContainsRel (x, y))
      return;

    vertex = PickVertex (x, y);
    if (vertex < 0)
      return;

    for (i = 0; i < ConnVerticeOrd; i++)
      if (poly [i] == vertex)
      {
        already = true;
        break;
      } /* endif */
  } /* endif */

  if (already && ConnVerticeOrd < 3)
  {
    MessageBox (app, TEXT_ERROR, TEXT_NOTENOUGHVERT, CSMBS_ERROR | CSMBS_ABORT);
    return;
  } /* endif */

  switch (GLOBAL_EDITMODE)
  {
    case cscmdMzCreatePolyConnect:
      if (already)
      {
        endconn = true;
        addpoly = true;
      }
      else
      {
        poly [ConnVerticeOrd++] = vertex;
        endconn = ConnVerticeOrd > MAX_POLYGON_VERTICES;
        redraw = !GLOBAL_MODEL->Vertex (vertex).selected;
        GLOBAL_MODEL->Vertex (vertex).selected = true;
      } /* endif */
      break;
    case cscmdMzCreatePolyTriFan:
    case cscmdMzCreatePolyTriStrip:
      addpoly = (already || (ConnVerticeOrd > 1));
      if (!already)
      {
        poly [ConnVerticeOrd++] = vertex;
        redraw = !GLOBAL_MODEL->Vertex (vertex).selected;
        GLOBAL_MODEL->Vertex (vertex).selected = true;
      } /* endif */
      break;
    case cscmdMzCreatePolyQuadStrip:
      addpoly = (already || (ConnVerticeOrd > 2));
      if (!already)
      {
        poly [ConnVerticeOrd++] = vertex;
        redraw = !GLOBAL_MODEL->Vertex (vertex).selected;
        GLOBAL_MODEL->Vertex (vertex).selected = true;
      } /* endif */
      break;
  } /* endswitch */

  if (addpoly)
  {
    CHK (mz3DPolygon *p = new mz3DPolygon (GLOBAL_MODEL));
    for (i = 0; i < ConnVerticeOrd; i++)
      (*p) [i] = poly [i];
    char *e = p->ErrorText (p->Check ());
    if (e)
    {
      endconn = true;
      char buff [256];
      strcpy (buff, "Error detected:\n");
      strcat (buff, e);
      MessageBox (app, TEXT_ERROR, buff, CSMBS_ERROR | CSMBS_ABORT);
      delete p;
    }
    else if (GLOBAL_MODEL->InsertPolygon (p, &i))
    {
      redraw = true;
      GLOBAL_MODELINFO;
      switch (GLOBAL_EDITMODE)
      {
        case cscmdMzCreatePolyTriFan:
          poly [1] = poly [2];
          ConnVerticeOrd = 2;
          break;
        case cscmdMzCreatePolyTriStrip:
          poly [0] = poly [2];
          ConnVerticeOrd = 2;
          break;
        case cscmdMzCreatePolyQuadStrip:
          poly [0] = poly [ConnVerticeOrd - 1];
          poly [1] = poly [ConnVerticeOrd - 2];
          ConnVerticeOrd = 2;
          break;
      } /* endswitch */
    }
    else
    {
      endconn = true;
      MessageBox (app, TEXT_ERROR, TEXT_POLYALREADY, CSMBS_ERROR | CSMBS_ABORT);
      delete p;
    } /* endif */
  } /* endif */

  if (redraw)
    GLOBAL_DRAFTCHANGED;

  GLOBAL_SETDESC1 (TEXT_PROCESS_CREATEPOLY, ConnVerticeOrd);

  if (endconn)
    FinishConnectPoly ();
}

void mzDraftEditor::FinishConnectPoly ()
{
  if (ConnVerticeOrd)
  {
    ConnVerticeOrd = 0;
    // restore help line
    GLOBAL_SETMODE (GLOBAL_EDITMODE);
  } /* endif */
}

void mzDraftEditor::SelectRect (csRect &r)
{
  bool redraw = false;
  r.Normalize ();
// System->Printf (MSG_DEBUG_0, "Executing Select rectangle\n");

  switch (GLOBAL_EDITMODE)
  {
    case cscmdMzVertexSelect:
    case cscmdMzVertexDeselect:
    case cscmdMzVertexInvert:
    {
      int i;
      for (i = GLOBAL_MODEL->Vertices () - 1; i >= 0; i--)
      {
        int x, y;
        world2win (GLOBAL_MODEL->Vertex (i), x, y);
        if (r.Contains (x, y))
        {
          switch (GLOBAL_EDITMODE)
          {
            case cscmdMzVertexSelect:
              GLOBAL_MODEL->Vertex (i).selected = true;
              break;
            case cscmdMzVertexDeselect:
              GLOBAL_MODEL->Vertex (i).selected = false;
              break;
            case cscmdMzVertexInvert:
              GLOBAL_MODEL->Vertex (i).selected = !GLOBAL_MODEL->Vertex (i).selected;
              break;
          } /* endswitch */
          redraw = true;
        } /* endif */
      } /* endfor */
      break;
    }
    case cscmdMzPolySelect:
    case cscmdMzPolyDeselect:
    case cscmdMzPolyInvert:
	case cscmdMzPolyChg:
		{
      int i, j;
      for (i = GLOBAL_MODEL->Polygons () - 1; i >= 0; i--)
      {
        mz3DPolygon *poly = &GLOBAL_MODEL->Polygon (i);
        int inside = 0;
        for (j = poly->Vertices () - 1; j >= 0; j--)
        {
          int x, y;
          world2win (poly->Vertex (j), x, y);
          if (r.Contains (x, y))
            inside++;
        } /* endfor */

        if (inside >= poly->Vertices ())
        {
          switch (GLOBAL_EDITMODE)
          {
            case cscmdMzPolySelect:
              GLOBAL_MODEL->Polygon (i).selected = true;
              break;
            case cscmdMzPolyDeselect:
              GLOBAL_MODEL->Polygon (i).selected = false;
              break;
            case cscmdMzPolyInvert:
              GLOBAL_MODEL->Polygon (i).selected = !GLOBAL_MODEL->Polygon (i).selected;
              break;
			case cscmdMzPolyChg:
				PolygonInfoDialog (app, GLOBAL_MODEL->Polygon (i));
          } /* endswitch */
          redraw = true;
        } /* endif */
      } /* endfor */
      break;
    }
	case cscmdMzPortalSelect:
		{
      int i, j, k, SelectModels = 0, SelectPolygons = 0, port_ind[10][2];

	  for (i=0; i<GLOBAL_MODELS.Length(); i++) {
		  mz3DModel *m = (mz3DModel *)GLOBAL_MODELS[i];
		  for (j = m->Polygons () - 1; j >= 0; j--)
		  {
			mz3DPolygon *poly = &m->Polygon (j);
			int inside = 0;
			for (k = poly->Vertices () - 1; k >= 0; k--)
			{
			int x, y;
				world2win (poly->Vertex (k), x, y);
				if (r.Contains (x, y))
					inside++;
			} /* endfor */

			if (inside >= poly->Vertices ())
			{
              //m->Polygon (j).selected = true;
			  SelectPolygons++;
			  port_ind[SelectModels][0] = i;
			  port_ind[SelectModels++][1] = j;
			} /* end if */
			redraw = true;
		  } /* end for j */
      } /* endfor i */

	  mz3DModel *m1 = 0, *m2 = 0;
	  char tmp[80];
	  if ((SelectPolygons == 2) && (SelectModels == 2)) {
		for (i=0; i<2; i++) {
		  m1 = (mz3DModel *)GLOBAL_MODELS[port_ind[i][0]];
		  m2 = (mz3DModel *)GLOBAL_MODELS[port_ind[1-i][0]];

		  // follow the original spec. of CS
		  sprintf(tmp, " %s  ", /*m2->Polygon(p_ind).Name,*/ m2->Name);
		  //System->Printf (MSG_DEBUG_0, "Name of portal expression %s", tmp);
		  m1->Polygon(port_ind[i][1]).portal = strnew(tmp);
		  //System->Printf (MSG_DEBUG_0, "Name of selected polygons %s\n", m1->Polygon (port_ind[i][1]).Name);
		}
		sprintf(tmp, "Portal between sector %s & %s established\n", m1->Name, m2->Name );
        MessageBox (app, TEXT_INFOTITLE, tmp, CSMBS_INFO | CSMBS_OK);

	  }
	  else
          MessageBox (app, TEXT_ERROR, TEXT_NOTVALIDPORTAL, CSMBS_ERROR | CSMBS_ABORT);

	  break;
	}

  } /* endswitch */

  if (redraw)
    GLOBAL_DRAFTCHANGED;
}

void mzDraftEditor::FitModelInWindow ()
{
  if (!GLOBAL_MODEL
   || !GLOBAL_MODEL->Vertices ())
  {
DefaultView:
    scale = DEFAULT_SCALE;
    pos.Set (0, 0, 0);
    UpdateMatrix ();
    Invalidate ();
    return;
  } /* endif */

  float xmin = FLT_MAX, ymin = FLT_MAX, xmax = -FLT_MAX, ymax = -FLT_MAX;
  float s = double ((bound.Width () + bound.Height ()) / 2) / scale;
  for (int i = GLOBAL_MODEL->Vertices () - 1; i >= 0; i--)
  {
    csVector3 p = (w2c * (GLOBAL_MODEL->Vertex (i) - pos)) / s;
    if (xmin > p.x)
      xmin = p.x;
    if (ymin > p.y)
      ymin = p.y;
    if (xmax < p.x)
      xmax = p.x;
    if (ymax < p.y)
      ymax = p.y;
  } /* endfor */

  if ((xmin != xmax)
   || (ymin != ymax))
  {
    s = 4 + (bound.Width () + bound.Height ()) / 2;
    float scale1 = (xmax - xmin) * s / float (bound.Width ());
    float scale2 = (ymax - ymin) * s / float (bound.Height ());
    if (scale1 > scale2)
      scale = scale1;
    else
      scale = scale2;
    if (scale < MIN_SCALE)
      scale = MIN_SCALE;
    if (scale > MAX_SCALE)
      scale = MAX_SCALE;
  }
  else
    goto DefaultView;

  CenterModelInWindow ();
}

void mzDraftEditor::CenterModelInWindow ()
{
  float xmin = +FLT_MAX, ymin = +FLT_MAX, zmin = +FLT_MAX;
  float xmax = -FLT_MAX, ymax = -FLT_MAX, zmax = -FLT_MAX;
  for (int i = GLOBAL_MODEL->Vertices () - 1; i >= 0; i--)
  {
    csVector3 *p = &GLOBAL_MODEL->Vertex (i);
    if (xmin > p->x)
      xmin = p->x;
    if (ymin > p->y)
      ymin = p->y;
    if (zmin > p->z)
      zmin = p->z;
    if (xmax < p->x)
      xmax = p->x;
    if (ymax < p->y)
      ymax = p->y;
    if (zmax < p->z)
      zmax = p->z;
  } /* endfor */
  pos.Set ((xmax + xmin) / 2, (ymax + ymin) / 2, (zmax + zmin) / 2);

  UpdateMatrix ();
  Invalidate ();
}

void mzDraftEditor::ShowPolygon (int xc, int yc, int xr, int yr)
{
  double DeltaAngle = 2 * M_PI / PolygonVertices;
  double Angle = 0;
  double StartAngle = atan2 (yr - yc, xr - xc);
  double Radius = sqrt (fSquare (xr - xc) + fSquare (yr - yc));
  csVector2 oldDot;
  while (Angle <= 2 * M_PI + SMALL_EPSILON)
  {
    csVector2 Dot (xc + Radius * cos (StartAngle + Angle),
                 yc + Radius * sin (StartAngle + Angle));
    if (Angle)
      Line ((int)rint (oldDot.x), (int)rint (oldDot.y),
            (int)rint (Dot.x), (int)rint (Dot.y), CSPAL_DRAFT_SELPOLY);
    oldDot = Dot;
    Angle += DeltaAngle;
  } /* endwhile */
}

void mzDraftEditor::CreatePolygon (int icx, int icy, int irx, int iry,
  float iz, bool iReverse, int *oVertIdx)
{
  double DeltaAngle = 2 * M_PI / PolygonVertices;
  double Angle = 0;
  double StartAngle = atan2 (iry - icy, irx - icx);
  double Radius = sqrt (fSquare (irx - icx) + fSquare (iry - icy));
  int Vertex = 0;
  csSector *curr_sector = NULL;
  csThing *curr_thing = NULL;
  csSprite3D *curr_sprite = NULL;

  mz3DPolygon *p = new mz3DPolygon (GLOBAL_MODEL);
  MazeEditor * mzParent = (MazeEditor *)app;
  // determine which collections should we add to
  switch (GLOBAL_MODEL->Type) {
  case csmtSector:
      curr_sector = (csSector *)mzParent->world->sectors[GLOBAL_MODEL->sector_ind];
      break;
  case csmtThing:
      //System->Printf(MSG_DEBUG_0, "index %d\n", GLOBAL_MODEL->thing_ind);
      //curr_thing = (csThing *)mzParent->world->thing_templates[GLOBAL_MODEL->thing_ind];
      break;
  case csmtSprite:
      curr_sprite = (csSprite3D *)mzParent->world->sprite_templates[GLOBAL_MODEL->sprite_ind];
      break;
  default:
      // error, return to calling function
      return;
  }
 /* csPolygon3D *poly3d, *n_poly3d;

  // create the csPolygon counterpart
  // in future it should be subclassed by mz3DPolygon
  CHK(poly3d = new csPolygon3D(GLOBAL_MODEL->coating) );
  csNameObject::AddName(*poly3d,p->Name);*/
  switch (GLOBAL_MODEL->Type) {
  case csmtSector:
    p->sector = curr_sector;
    GLOBAL_MODEL->InsertSector ( curr_sector, NULL );
    //poly3d->SetSector (curr_sector);
    //curr_sector->AddPolygon( poly3d );
	break;
  case csmtThing:
    //curr_thing->AddPolygon( poly3d );
    GLOBAL_MODEL->InsertThing ( curr_thing, NULL );
    break;
  default:
    break;
  };

  while (Angle < 2 * M_PI - SMALL_EPSILON)
  {
    csVector2 Dot (icx + Radius * cos (StartAngle + Angle),
                 icy + Radius * sin (StartAngle + Angle));
    mz3DVertex *v = new mz3DVertex ();
    win2world (Dot.x, Dot.y, iz, *v);
    SnapToGrid(*v);
    int VertexIndex;
    GLOBAL_MODEL->InsertVertex (v, &VertexIndex);
    switch (GLOBAL_MODEL->Type) {
    case csmtSector:
      curr_sector->AddVertex ( (*v).x, (*v).y, (*v).z ); break;
    case csmtThing:
      //curr_thing->AddVertex ( (*v).x, (*v).y, (*v).z ); break;
    case csmtSprite:
      break;
    default:
      break;
    }
    (*p) [Vertex] = VertexIndex;
    //poly3d->AddVertex (VertexIndex);
    //poly3d->SetTextureType (POLYTXT_GOURAUD);
    if (oVertIdx)
      oVertIdx [Vertex] = VertexIndex;
    Angle += DeltaAngle;
    Vertex++;
  } /* endwhile */
  if (iReverse)
    p->Reverse ();

  /*csGouraudShaded* gs = poly3d->GetGouraudInfo ();
  gs->Setup(Vertex);
  for (int VertexIndex = 0; VertexIndex < Vertex; VertexIndex++)
    gs->SetUV(VertexIndex, float(rand()%256) / 255, float(rand()%256) / 255);
*/
  // Split polygon if it has too many vertices
  while (p->Vertices () > MAX_POLYGON_VERTICES)
  {
    mz3DPolygon *np = new mz3DPolygon (GLOBAL_MODEL);
    // create the csPolygon counterpart
    // in future it should be subclassed by mz3DPolygon
  /*  CHK(n_poly3d = new csPolygon3D(GLOBAL_MODEL->coating) );
    csNameObject::AddName(*n_poly3d, np->Name);
    poly3d->SetSector (curr_sector);*/

    int splitvert = MAX_POLYGON_VERTICES - 1;
    if (p->Vertices () - splitvert < 2)
      splitvert = p->Vertices () - 2;
    (*np) [0] = (*p) [splitvert++];
    //n_poly3d->AddVertex ((*np) [0]);
    while (p->Vertices () > splitvert)
    {
      (*np) [np->Vertices ()] = (*p) [splitvert];
      //n_poly3d->AddVertex( (*np) [np->Vertices ()] );
      // should delete the vertex in poly3d, but no available
      // function to do this !! (TO-DO)
      p->DeleteVertex (splitvert);
    } /* endwhile */
    (*np) [np->Vertices ()] = (*p) [0];
    //n_poly3d->AddVertex( (*np) [np->Vertices ()] );
    p->selected = true;
    p->Texture = GLOBAL_MODEL->coating;
    GLOBAL_MODEL->InsertPolygon (np, NULL);
    p = np;
  } /* endwhile */

  p->selected = true;
  p->Texture = GLOBAL_MODEL->coating;
  p->sector = curr_sector;
  GLOBAL_MODEL->InsertPolygon (p, NULL);

  // add cs3Dpolygon to mz3DPolygon
  /*poly3d->Finish();
  switch (GLOBAL_MODEL->Type) {
  case csmtSector:
    poly3d->SetSector (curr_sector);
    curr_sector->AddPolygon( poly3d );	break;
  case csmtThing:
      //curr_thing->AddPolygon( poly3d );break;
  case csmtSprite:
      break;
  default:
      break;
  };
  p->csp = poly3d;*/

  GLOBAL_MODELINFO;
}

void mzDraftEditor::CreateBox (int xl, int yt, int xr, int yb, float depth)
{
  if ((xl == xr)
   || (yt == yb))
    return;

  csSector *curr_sector = NULL;
  csThing *curr_thing = NULL;
  csSprite3D *curr_sprite = NULL;
  int idx [8];
  mz3DVertex *v;
  MazeEditor * mzParent = (MazeEditor *)app;

  // determine which collections should we add to
  switch (GLOBAL_MODEL->Type) {
  case csmtSector:
      curr_sector = (csSector *)mzParent->world->sectors[GLOBAL_MODEL->sector_ind];
      break;
  case csmtThing:
     // curr_thing = (csThing *)mzParent->world->thing_templates[GLOBAL_MODEL->thing_ind];
      break;
  case csmtSprite:
      curr_sprite = (csSprite3D *)mzParent->world->sprite_templates[GLOBAL_MODEL->sprite_ind];
      break;
  default:
      // error, return to calling function
      return;
  }

#define ADD_VERTEX(X, Y, Z, n)                                          \
  CHK (v = new mz3DVertex ()); win2world (X, Y, Z, *v); SnapToGrid(*v); \
  switch (GLOBAL_MODEL->Type) {						\
  case csmtSector:							\
    curr_sector->AddVertex ( (*v).x, (*v).y, (*v).z ); break;		\
  case csmtThing:							\
  case csmtSprite:							\
      break;								\
  default:								\
      break;								\
  }									\
  GLOBAL_MODEL->InsertVertex (v, &idx [n]);

  //    curr_thing->AddVertex ( (*v).x, (*v).y, (*v).z ); break;

  ADD_VERTEX (xl, yt, 0, 0);
  ADD_VERTEX (xr, yt, 0, 1);
  ADD_VERTEX (xr, yb, 0, 2);
  ADD_VERTEX (xl, yb, 0, 3);
  ADD_VERTEX (xl, yt, -depth, 4);
  ADD_VERTEX (xr, yt, -depth, 5);
  ADD_VERTEX (xr, yb, -depth, 6);
  ADD_VERTEX (xl, yb, -depth, 7);
#undef ADD_VERTEX

#define ADD_POLYGON(v1, v2, v3, v4)                                     \
  CHK (p = new mz3DPolygon (GLOBAL_MODEL));                             \
  (*p) [0] = idx [v1]; (*p) [1] = idx [v2];                             \
  (*p) [2] = idx [v3]; (*p) [3] = idx [v4];                             \
  if (depth < 0) p->Reverse ();                                         \
  p->selected = true;                                                   \
  p->Texture = GLOBAL_MODEL->coating;					\
  switch (GLOBAL_MODEL->Type) {						\
  case csmtSector:							\
    p->sector = curr_sector;						\
    GLOBAL_MODEL->InsertSector ( curr_sector, NULL );			\
    break;								\
  case csmtThing:							\
    GLOBAL_MODEL->InsertThing ( curr_thing, NULL );			\
    break;								\
  default:								\
    break;								\
  };									\
  GLOBAL_MODEL->InsertPolygon (p, NULL);

#define ADD_POLY_CS(v1, v2, v3, v4)					\
  CHK(poly3d = new csPolygon3D(GLOBAL_MODEL->coating) );		\
  csNameObject::AddName(*poly3d,p->Name);				\
  poly3d->SetParent (curr_sector);					\
  poly3d->AddVertex (idx[v1]); poly3d->AddVertex (idx[v2]);		\
  poly3d->AddVertex (idx[v3]); poly3d->AddVertex (idx[v4]);		\
  poly3d->SetTextureType (POLYTXT_GOURAUD);				\
  gs = poly3d->GetGouraudInfo ();					\
  gs->Setup(4);								\
  gs->SetUV(0, 0.0, 0.0);   						\
  gs->SetUV(1, 0.0, 1.0);   						\
  gs->SetUV(2, 1.0, 1.0);   						\
  gs->SetUV(3, 1.0, 0.0);   						\
  poly3d->Finish();		      					\
  switch (GLOBAL_MODEL->Type) {						\
  case csmtSector:							\
    poly3d->SetSector (curr_sector);					\
    curr_sector->AddPolygon( poly3d );	break;				\
  case csmtThing:							\
  case csmtSprite:							\
      break;								\
  default:								\
      break;								\
  };									\
  gs->EnableGouraud (true);						\
  gs->SetColor(0, 1.0, 0.0, 0.0);					\
  gs->SetColor(1, 0.0, 1.0, 0.0);					\
  gs->SetColor(2, 0.0, 0.0, 1.0);					\
  gs->SetColor(3, 1.0, 1.0, 1.0); 					\
  poly3d->SetFlatColor(1.0, 1.0, 1.0);					\
  poly3d->SetTextureSpace (tx_matrix, tx_vector);			\
  p->csp = poly3d;

  mz3DPolygon *p;
#if 0
  csPolygon3D *poly3d;
  csMatrix3 tx_matrix;
  csVector3 tx_vector;
  csGouraudShaded* gs;
#endif

  ADD_POLYGON (0, 1, 2, 3);
  //ADD_POLY_CS (0, 1, 2, 3);
  if (depth != 0)
  {
    ADD_POLYGON (7,6,5,4);
    //ADD_POLY_CS (7,6,5,4);
    ADD_POLYGON (0,3,7,4);
    //ADD_POLY_CS (0,3,7,4);
    ADD_POLYGON (1,0,4,5);
    //ADD_POLY_CS (1,0,4,5);
    ADD_POLYGON (2,1,5,6);
    //ADD_POLY_CS (2,1,5,6);
    ADD_POLYGON (3,2,6,7);
    //ADD_POLY_CS (3,2,6,7);
  } /* endif */
#undef ADD_POLYGON
#undef ADD_POLY_CS

  GLOBAL_MODELINFO;
}

void mzDraftEditor::CreatePrism (int xc, int yc, int xr, int yr, float depth)
{
  MazeEditor * mzParent = (MazeEditor *)app;
  csSector *curr_sector = NULL;
  csSprite3D *curr_sprite = NULL;
  //csPolygon3D *poly3d;


  if ((xc == xr)
   && (yc == yr))
    return;

  // determine which collections should we add to
  switch (GLOBAL_MODEL->Type) {
  case csmtSector:
      curr_sector = (csSector *)mzParent->world->sectors[GLOBAL_MODEL->sector_ind];
      break;
  case csmtThing:
   //   curr_thing = (csThing *)mzParent->world->thing_templates[GLOBAL_MODEL->thing_ind];
      break;
  case csmtSprite:
      curr_sprite = (csSprite3D *)mzParent->world->sprite_templates[GLOBAL_MODEL->sprite_ind];
      break;
  default:
      // error, return to calling function
      return;
  }

  bool reverse = (depth < 0);
  CHK (int *tv = new int [PolygonVertices]);
  CHK (int *bv = new int [PolygonVertices]);
  CreatePolygon (xc, yc, xr, yr, 0, reverse, tv);
  if (depth != 0)
  {
    CreatePolygon (xc, yc, xr, yr, -depth, !reverse, bv);
    for (int idx = 0; idx < PolygonVertices; idx++)
    {
      CHK (mz3DPolygon *p = new mz3DPolygon (GLOBAL_MODEL));
      (*p) [0] = bv [idx];
      (*p) [1] = bv [idx == 0 ? PolygonVertices - 1 : idx - 1];
      (*p) [2] = tv [idx == 0 ? PolygonVertices - 1 : idx - 1];
      (*p) [3] = tv [idx];
     /* if (reverse) */ p->Reverse ();
      p->selected = true;
      p->Texture = GLOBAL_MODEL->coating;
      p->sector = curr_sector;
      GLOBAL_MODEL->InsertPolygon (p, NULL);

      // Create the csPolygon3D counterpart
    /*  CHK(poly3d = new csPolygon3D(GLOBAL_MODEL->coating) );
      csNameObject::AddName(*poly3d,p->Name);
      poly3d->SetParent (curr_sector);
      poly3d->SetSector (curr_sector);
      poly3d->AddVertex ((*p) [3] ); poly3d->AddVertex ((*p) [2] );
      poly3d->AddVertex ((*p) [1]); poly3d->AddVertex ((*p) [0]);
      poly3d->SetTextureType (POLYTXT_GOURAUD);
      csGouraudShaded* gs = poly3d->GetGouraudInfo ();
      gs->Setup(4);
      gs->SetUV(0, 0.0, 0.0);
      gs->SetUV(1, 0.0, 1.0);
      gs->SetUV(2, 1.0, 1.0);
      gs->SetUV(3, 1.0, 0.0);
      poly3d->Finish();
      switch (GLOBAL_MODEL->Type) {
      case csmtSector:
	poly3d->SetSector (curr_sector);
	curr_sector->AddPolygon( poly3d );
	break;
      case csmtThing:
//	curr_thing->AddPolygon( poly3d )
        break;
      case csmtSprite:
	  break;
      default:
          break;
      };
      p->csp = poly3d;
*/
    } /* endfor */
  } /* endif */
  CHK (delete [] bv);
  CHK (delete [] tv);
  GLOBAL_MODELINFO;
}

void mzDraftEditor::CreatePyramid (int xc, int yc, int xr, int yr, float depth)
{

  MazeEditor * mzParent = (MazeEditor *)app;
//  csPolygon3D *poly3d;
  csSector *curr_sector = NULL;
  csSprite3D *curr_sprite = NULL;

  if ((xc == xr)
   && (yc == yr))
    return;

  // determine which collections should we add to
  switch (GLOBAL_MODEL->Type) {
  case csmtSector:
      curr_sector = (csSector *)mzParent->world->sectors[GLOBAL_MODEL->sector_ind];
      break;
  case csmtThing:
  //    curr_thing = (csThing *)mzParent->world->thing_templates[GLOBAL_MODEL->thing_ind];
      break;
  case csmtSprite:
      curr_sprite = (csSprite3D *)mzParent->world->sprite_templates[GLOBAL_MODEL->sprite_ind];
      break;
  default:
      // error, return to calling function
      return;
  }

  bool reverse = (depth < 0);
  CHK (int *tv = new int [PolygonVertices]);
  CreatePolygon (xc, yc, xr, yr, 0, reverse, tv);
  if (depth != 0)
  {
    CHK (mz3DVertex *v = new mz3DVertex ()); win2world (xc, yc, -depth, *v);
    SnapToGrid(*v);
    int apex;
    GLOBAL_MODEL->InsertVertex (v, &apex);
    switch (GLOBAL_MODEL->Type) {
    case csmtSector:
      curr_sector->AddVertex ( (*v).x, (*v).y, (*v).z );
      break;
    case csmtThing:
  //  curr_thing->AddVertex ( (*v).x, (*v).y, (*v).z );
      break;
    case csmtSprite:
      break;
    default:
      break;
    }
    for (int idx = 0; idx < PolygonVertices; idx++)
    {
      CHK (mz3DPolygon *p = new mz3DPolygon (GLOBAL_MODEL));
      (*p) [0] = apex;
      (*p) [1] = tv [idx == 0 ? PolygonVertices - 1 : idx - 1];
      (*p) [2] = tv [idx];
      /*if (reverse) */ p->Reverse ();
      p->selected = true;
      p->Texture = GLOBAL_MODEL->coating;
      p->sector = curr_sector;
      GLOBAL_MODEL->InsertPolygon ( p, NULL);

  /*    CHK(poly3d = new csPolygon3D(GLOBAL_MODEL->coating) );
      csNameObject::AddName(*poly3d,p->Name);
      poly3d->SetParent (curr_sector);
      poly3d->SetSector (curr_sector);
      poly3d->AddVertex ((*p) [2] );
      poly3d->AddVertex ((*p) [1] );
      poly3d->AddVertex ((*p) [0] );
      poly3d->SetTextureType (POLYTXT_GOURAUD);
      csGouraudShaded* gs = poly3d->GetGouraudInfo ();
      gs->Setup(3);
      gs->SetUV(0, 0.0, 0.0);
      gs->SetUV(1, 0.0, 1.0);
      gs->SetUV(2, 1.0, 1.0);
      poly3d->Finish();
      switch (GLOBAL_MODEL->Type) {
      case csmtSector:
	poly3d->SetSector (curr_sector);
	curr_sector->AddPolygon( poly3d );
	break;
      case csmtThing:
//	curr_thing->AddPolygon( poly3d );
        break;
      case csmtSprite:
	break;
      default:
        break;
      };
      p->csp = poly3d;
*/
    } /* endfor */
  } /* endif */
  CHK (delete [] tv);
  GLOBAL_MODELINFO;
}

void mzDraftEditor::CreateTorus (int xc, int yc, int xr, int yr,
  float iInnerRadius, float iOuterRadius)
{
  if (iInnerRadius == iOuterRadius)
    return;

  CHK (int *prevsect = new int [GLOBAL_TORUSSECTDETAIL]);
  CHK (int *thissect = new int [GLOBAL_TORUSSECTDETAIL]);
  CHK (int *firstsect = new int [GLOBAL_TORUSSECTDETAIL]);
  double DeltaAngle = 2 * M_PI / PolygonVertices;
  double Angle = 0;
  double StartAngle = atan2 (yr - yc, xr - xc);
  double DeltaSectAngle = 2 * M_PI / GLOBAL_TORUSSECTDETAIL;
  double MiddleRadius = (iInnerRadius + iOuterRadius) / 2;
  double SectRadius = (iOuterRadius - iInnerRadius) / 2;
  while (Angle <= 2 * M_PI + SMALL_EPSILON)
  {
    if (Angle < 2 * M_PI - SMALL_EPSILON)
    {
      double SectAngle = 0;
      int SectVertex = 0;
      while (SectVertex < GLOBAL_TORUSSECTDETAIL)
      {
        CHK (mz3DVertex *v = new mz3DVertex ());
        // Compute the point on torus cross-section as if it were crossed by XOZ
        v->Set (MiddleRadius + cos (SectAngle) * SectRadius,
                0, sin (SectAngle) * SectRadius);
        // Now rotate the vertex around OZ by SectAngle
        double sa = sin (StartAngle + Angle);
        double ca = cos (StartAngle + Angle);
        float v_x = v->x;
        v->x = xc + v_x * ca + v->y * sa;
        v->y = yc - v_x * sa + v->y * ca;
        // And finally, transfer from window space to world space
        v->x -= bound.Width () / 2;
        v->y = bound.Height () / 2 - v->y;
        *v = c2w * (*v) + pos;
        // Insert vertex into model
        GLOBAL_MODEL->InsertVertex (v, &thissect [SectVertex]);
        SectAngle += DeltaSectAngle;
        SectVertex++;
      } /* endwhile */
    }
    else
      memcpy (thissect, firstsect, GLOBAL_TORUSSECTDETAIL * sizeof (int));

    if (Angle)
      // Create faces between current cross-section and previous cross-section
      for (int idx = 0; idx < GLOBAL_TORUSSECTDETAIL; idx++)
      {
        CHK (mz3DPolygon *p = new mz3DPolygon (GLOBAL_MODEL));
        (*p) [0] = thissect [idx];
        (*p) [1] = thissect [idx == 0 ? GLOBAL_TORUSSECTDETAIL - 1 : idx - 1];
        (*p) [2] = prevsect [idx == 0 ? GLOBAL_TORUSSECTDETAIL - 1 : idx - 1];
        (*p) [3] = prevsect [idx];
        p->selected = true;
	p->Texture = GLOBAL_MODEL->coating;
        GLOBAL_MODEL->InsertPolygon (p, NULL);
      } /* endfor */
    else
      memcpy (firstsect, thissect, GLOBAL_TORUSSECTDETAIL * sizeof (int));
    Angle += DeltaAngle;
    memcpy (prevsect, thissect, GLOBAL_TORUSSECTDETAIL * sizeof (int));
  } /* endwhile */
  CHK (delete [] firstsect);
  CHK (delete [] thissect);
  CHK (delete [] prevsect);
  GLOBAL_MODELINFO;
}

void mzDraftEditor::CreateSphere (int xc, int yc, int xr, int yr)
{
  if ((xc == xr)
   && (yc == yr))
    return;

  CHK (int *prevsect = new int [PolygonVertices]);
  CHK (int *thissect = new int [PolygonVertices]);
  double DeltaAngle = 2 * M_PI / PolygonVertices;
  double AngleY = 0;
  double StartAngleZ = atan2 (yr - yc, xr - xc);
  double RadiusY = sqrt (fSquare (xr - xc) + fSquare (yr - yc));
  double Displacement;

  if ((PolygonVertices / 4) & 1)
    Displacement = DeltaAngle / 2;
  else
    Displacement = 0;

  while (AngleY <= M_PI + SMALL_EPSILON)
  {
    if (AngleY >= M_PI - SMALL_EPSILON)
      AngleY = M_PI;
    double RadiusZ = RadiusY * sin (AngleY);
    double PlaneZ = -RadiusY * cos (AngleY);
    double AngleZ = 0;
    int SectVertex = 0;
    while (SectVertex < PolygonVertices)
    {
      CHK (mz3DVertex *v = new mz3DVertex ());
      // Compute the next point on sphere
      v->Set (xc + cos (StartAngleZ + AngleZ + Displacement) * RadiusZ,
              yc + sin (StartAngleZ + AngleZ + Displacement) * RadiusZ,
              PlaneZ);
      // Now transfer it from window space to world space
      v->x -= bound.Width () / 2;
      v->y = bound.Height () / 2 - v->y;
      *v = c2w * (*v) + pos;
      // Insert vertex into model
      GLOBAL_MODEL->InsertVertex (v, &thissect [SectVertex]);
      AngleZ += DeltaAngle;
      SectVertex++;
    } /* endwhile */

    if (AngleY)
      // Connect current cross-section with previous by using triangles
      for (int idx = 0; idx < PolygonVertices; idx++)
      {
        CHK (mz3DPolygon *p = new mz3DPolygon (GLOBAL_MODEL));
        if (Displacement)
        {
          (*p) [0] = prevsect [idx];
          (*p) [1] = thissect [idx];
          (*p) [2] = thissect [idx == 0 ? PolygonVertices - 1 : idx - 1];
        }
        else
        {
          (*p) [0] = prevsect [idx];
          (*p) [1] = thissect [idx  == PolygonVertices - 1 ? 0 : idx + 1];
          (*p) [2] = thissect [idx];
        } /* endif */
        p->selected = true;
        p->Texture = GLOBAL_MODEL->coating;
        GLOBAL_MODEL->InsertPolygon (p, NULL);

        CHK (p = new mz3DPolygon (GLOBAL_MODEL));
        if (Displacement)
        {
          (*p) [0] = prevsect [idx];
          (*p) [1] = prevsect [idx  == PolygonVertices - 1 ? 0 : idx + 1];
          (*p) [2] = thissect [idx];
        }
        else
        {
          (*p) [0] = prevsect [idx];
          (*p) [1] = prevsect [idx  == PolygonVertices - 1 ? 0 : idx + 1];
          (*p) [2] = thissect [idx  == PolygonVertices - 1 ? 0 : idx + 1];
        } /* endif */
        p->selected = true;
	p->Texture = GLOBAL_MODEL->coating;
        GLOBAL_MODEL->InsertPolygon (p, NULL);
      } /* endfor */
    memcpy (prevsect, thissect, PolygonVertices * sizeof (int));
    AngleY += DeltaAngle;
    if (Displacement == 0)
      Displacement = DeltaAngle / 2;
    else
      Displacement = 0;
  } /* endwhile */
  CHK (delete [] thissect);
  CHK (delete [] prevsect);
  GLOBAL_MODELINFO;
}

void mzDraftEditor::ShowBody (int xc, int yc, int xr, int yr)
{
  static int x1, y1, x2, y2, x3, y3, x4, y4;
  static mzDraftEditor *View;
  static char *depthtext;

  switch (GLOBAL_EDITMODE)
  {
    case cscmdMzCreateBox:
      switch (BodyCreationPhase)
      {
        case 0:
          View = this;
          if (xc < xr) { x1 = xc; x2 = xr; } else { x1 = xr; x2 = xc; }
          if (yc < yr) { y1 = yc; y2 = yr; } else { y1 = yr; y2 = yc; }
          Rect3D (x1, y1, x2, y2, CSPAL_DRAFT_SELRECT, CSPAL_DRAFT_SELRECT);
          break;
        case 1:
          GLOBAL_SETDESC1 (depthtext = TEXT_PROCESS_CREATEBOXP2, 0);
          BodyCreationPhase = 3;
          break;
        case 4:
        {
DefineDepth:
          x3 = xc; y3 = yc; x4 = xr; y4 = yr;
          Line (x3, y3, x4, y4, CSPAL_DRAFT_SELRECT);
          csVector3 v1, v2;
          win2world (x3, y3, 0, v1);
          win2world (x4, y4, 0, v2);
	  SnapToGrid(v2);
          GLOBAL_SETDESC1 (depthtext, (v2 - v1).Norm ());
          break;
        }
        case 5:
          {
            // create box
            float depth = sqrt (fSquare (x4 - x3) +
                                fSquare (y4 - y3));
            View->CreateBox (x1, y1, x2, y2, depth);
          }
FinishOperation:
          if (app->MouseOwner == this)
            app->CaptureMouse (NULL);
          doCreateBody = false;
          GLOBAL_SETMODE (GLOBAL_EDITMODE);
          GLOBAL_DRAFTCHANGED_DELAYED;
          break;
      } /* endswitch */
      break;
    case cscmdMzCreatePrism:
      switch (BodyCreationPhase)
      {
        case 0:
          ShowPolygon (xc, yc, xr, yr);
          break;
        case 1:
          View = this;
          x1 = xc; y1 = yc; x2 = xr; y2 = yr;
          GLOBAL_SETDESC1 (depthtext = TEXT_PROCESS_CREATEPRISMP2, 0);
          BodyCreationPhase = 3;
          break;
        case 4:
          goto DefineDepth;
        case 5:
          {
            // create prism
            float depth = sqrt (fSquare (x4 - x3) +
                                fSquare (y4 - y3));
            View->CreatePrism (x1, y1, x2, y2, depth);
          }
          goto FinishOperation;
      } /* endswitch */
      break;
    case cscmdMzCreatePyramid:
      switch (BodyCreationPhase)
      {
        case 0:
          ShowPolygon (xc, yc, xr, yr);
          break;
        case 1:
          View = this;
          x1 = xc; y1 = yc; x2 = xr; y2 = yr;
          GLOBAL_SETDESC1 (depthtext = TEXT_PROCESS_CREATEPYRAMIDP2, 0);
          BodyCreationPhase = 3;
          break;
        case 4:
          goto DefineDepth;
        case 5:
          {
            // create prism
            float depth = sqrt (fSquare (x4 - x3) +
                                fSquare (y4 - y3));
            View->CreatePyramid (x1, y1, x2, y2, depth);
          }
          goto FinishOperation;
      } /* endswitch */
      break;
    case cscmdMzCreateTorus:
      switch (BodyCreationPhase)
      {
        case 0:
          depthtext = TEXT_PROCESS_CREATETORUS;
          goto PolyCircle;
        case 1:
          View = this;
          x1 = xc; y1 = yc; x2 = xr; y2 = yr;
          GLOBAL_SETDESC1 (depthtext = TEXT_PROCESS_CREATETORUSP2, 0);
          BodyCreationPhase = 3;
        case 3:
        case 4:
        {
          if ((BodyCreationPhase == 3)
           && (this != View))
            break;
          xc = x1; yc = y1;
          ShowPolygon (x1, y1, x2, y2);
          int oldPV = PolygonVertices;
          PolygonVertices = 64;                 // approximate circle
          ShowPolygon (x1, y1, x2, y2);
          PolygonVertices = oldPV;
          if (BodyCreationPhase == 3)
            break;
          else
            goto PolyCircle;
        }
        case 5:
          {
            // create torus
            float radius1 = sqrt (fSquare (xr - x1) + fSquare (yr - y1));
            float radius2 = sqrt (fSquare (x2 - x1) + fSquare (y2 - y1));
            if (radius1 > radius2)
            { float tmp = radius1; radius1 = radius2; radius2 = tmp; }
            View->CreateTorus (x1, y1, x2, y2, radius1, radius2);
          }
          goto FinishOperation;
      } /* endswitch */
      break;
    case cscmdMzCreateSphere:
      switch (BodyCreationPhase)
      {
        case 0:
        {
PolyCircle:
          ShowPolygon (xc, yc, xr, yr);
          int oldPV = PolygonVertices;
          PolygonVertices = 64;                 // approximate circle
          ShowPolygon (xc, yc, xr, yr);
          PolygonVertices = oldPV;
          csVector3 v1, v2;
          win2world (xc, yc, 0, v1);
          win2world (xr, yr, 0, v2);
          GLOBAL_SETDESC1 (depthtext, (v2 - v1).Norm ());
          break;
        }
        case 1:
          View = this;
          CreateSphere (xc, yc, xr, yr);
          goto FinishOperation;
      } /* endswitch */
      break;
  } /* endswitch */
}

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

  // If mode switches from one of CreatePoly, drop vertice index to 0
  if (ConnVerticeOrd
   && (GLOBAL_EDITMODE != cscmdMzCreatePolyConnect)
   && (GLOBAL_EDITMODE != cscmdMzCreatePolyTriFan)
   && (GLOBAL_EDITMODE != cscmdMzCreatePolyTriStrip)
   && (GLOBAL_EDITMODE != cscmdMzCreatePolyQuadStrip))
    ConnVerticeOrd = 0;

  // If mode switches from one of CreateBody, disable body creation mode
  if (doCreateBody
   && (GLOBAL_EDITMODE != cscmdMzCreateBox)
   && (GLOBAL_EDITMODE != cscmdMzCreatePrism)
   && (GLOBAL_EDITMODE != cscmdMzCreatePyramid)
   && (GLOBAL_EDITMODE != cscmdMzCreateTorus)
   && (GLOBAL_EDITMODE != cscmdMzCreateSphere))
  {
    doCreateBody = false;
    Invalidate ();
  }

  switch (Event.Type)
  {
    case csevMouseDown:
    case csevMouseDoubleClick:
      Select ();
      if (!GLOBAL_MODEL
       && (GLOBAL_EDITMODE != cscmdMzCoordRotate)
       && (GLOBAL_EDITMODE != cscmdMzZoomToRect)
       && (GLOBAL_EDITMODE != cscmdMzZoomInOut)
       && (GLOBAL_EDITMODE != cscmdMzSlideView)
       && (GLOBAL_EDITMODE != cscmdMzSetupAxis))
        MessageBox (app, TEXT_ERROR, TEXT_NOMODEL, CSMBS_ERROR | CSMBS_ABORT);
      else
        switch (GLOBAL_EDITMODE)
        {
          case cscmdMzCreateVertex:
            if (Event.Mouse.Button == 1)
              InsertVertexAt (Event.Mouse.x, Event.Mouse.y);
            break;
          case cscmdMzDeleteVertex:
            if (Event.Mouse.Button == 1)
              if (GLOBAL_MODEL->DeleteVertex (PickVertex (Event.Mouse.x, Event.Mouse.y)))
              {
                GLOBAL_DRAFTCHANGED;
                GLOBAL_MODELINFO;
              } /* endif */
            break;
          case cscmdMzCreateTriangle:
          case cscmdMzCreateSquare:
          case cscmdMzCreateHexagon:
          case cscmdMzCreateOctagon:
          case cscmdMzCreateNgon:
            if (Event.Mouse.Button == 1)
            {
              app->CaptureMouse (this);
              dragMouseX = oldMouseX = Event.Mouse.x;
              dragMouseY = oldMouseY = Event.Mouse.y;
              doCreatePolygon = true;
              Invalidate ();
              switch (GLOBAL_EDITMODE)
              {
                case cscmdMzCreateTriangle:
                  PolygonVertices = 3;
                  GLOBAL_SETDESC (TEXT_PROCESS_CREATETRIANGLE);
                  break;
                case cscmdMzCreateSquare:
                  PolygonVertices = 4;
                  GLOBAL_SETDESC (TEXT_PROCESS_CREATESQUARE);
                  break;
                case cscmdMzCreateHexagon:
                  PolygonVertices = 6;
                  GLOBAL_SETDESC (TEXT_PROCESS_CREATEHEXAGON);
                  break;
                case cscmdMzCreateOctagon:
                  PolygonVertices = 8;
                  GLOBAL_SETDESC (TEXT_PROCESS_CREATEOCTAGON);
                  break;
                case cscmdMzCreateNgon:
                  PolygonVertices = GLOBAL_NGONSIDES;
                  GLOBAL_SETDESC1 (TEXT_PROCESS_CREATENGON, PolygonVertices);
                  break;
              } /* endswitch */
            }
            else if (Event.Mouse.Button == 2)
              goto AbortOperation;
            break;
          case cscmdMzCreateBox:
          case cscmdMzCreatePrism:
          case cscmdMzCreatePyramid:
          case cscmdMzCreateTorus:
          case cscmdMzCreateSphere:
            if (Event.Mouse.Button == 1)
              if (!doCreateBody)
              {
                app->CaptureMouse (this);
                dragMouseX = oldMouseX = Event.Mouse.x;
                dragMouseY = oldMouseY = Event.Mouse.y;
                doCreateBody = true;
                BodyCreationPhase = 0;
                switch (GLOBAL_EDITMODE)
                {
                  case cscmdMzCreateBox:
                    GLOBAL_SETDESC (TEXT_PROCESS_CREATEBOX);
                    break;
                  case cscmdMzCreatePrism:
                    PolygonVertices = GLOBAL_PRISMSIDES;
                    GLOBAL_SETDESC (TEXT_PROCESS_CREATEPRISM);
                    break;
                  case cscmdMzCreatePyramid:
                    PolygonVertices = GLOBAL_PYRAMIDSIDES;
                    GLOBAL_SETDESC (TEXT_PROCESS_CREATEPYRAMID);
                    break;
                  case cscmdMzCreateTorus:
                    PolygonVertices = GLOBAL_TORUSRADIALDETAIL;
                    GLOBAL_SETDESC1 (TEXT_PROCESS_CREATETORUS, 0);
                    break;
                  case cscmdMzCreateSphere:
                    PolygonVertices = GLOBAL_SPHEREDETAIL;
                    GLOBAL_SETDESC1 (TEXT_PROCESS_CREATESPHERE, 0);
                    break;
                } /* endswitch */
              }
              else
              {
                app->CaptureMouse (this);
                dragMouseX = oldMouseX = Event.Mouse.x;
                dragMouseY = oldMouseY = Event.Mouse.y;
                BodyCreationPhase = (BodyCreationPhase & 0xfffffffc) + 4;
                Invalidate ();
              } /* endif */
            else if (Event.Mouse.Button == 2)
              goto AbortOperation;
            break;
          case cscmdMzCreateLight:
			  if (Event.Mouse.Button == 1) {
// read the planar coordinates first
			CHK (mz3DVertex *v = new mz3DVertex);
			win2world (Event.Mouse.x, Event.Mouse.y, 0, *v);
			mz3DLight *lp = new mz3DLight(GLOBAL_MODEL);
			lp->Radius = 20.0;
			lp->Color.red = 1.0;
			lp->Color.green = 1.0;
			lp->Color.blue = 1.0;
			if (LightParmDialog (app, lp->Radius, lp->Color) == cscmdOK)
			{
				app->Dismiss (cscmdMzCreateLight);
			} /* endif */

				lp->Position = *v;
			int i;
			GLOBAL_MODEL->InsertLight(lp, &i);
			  }
            break;
          case cscmdMzCreatePolyConnect:
          case cscmdMzCreatePolyTriFan:
          case cscmdMzCreatePolyTriStrip:
          case cscmdMzCreatePolyQuadStrip:
            if (Event.Mouse.Button == 1)
              ConnectPolyVertex (Event.Mouse.x, Event.Mouse.y);
            else if (Event.Mouse.Button == 2)
              FinishConnectPoly ();
            break;
          case cscmdMzVertexSelect:
          case cscmdMzVertexDeselect:
          case cscmdMzVertexInvert:
          case cscmdMzPolySelect:
          case cscmdMzPolyDeselect:
          case cscmdMzPolyInvert:
		  case cscmdMzPolyChg:
		  case cscmdMzPortalSelect:
            if (Event.Mouse.Button == 1)
            {
              app->CaptureMouse (this);
              dragMouseX = oldMouseX = Event.Mouse.x;
              dragMouseY = oldMouseY = Event.Mouse.y;
              dragRect = true;
            }
            else if (Event.Mouse.Button == 2)
              goto AbortOperation;
            break;
          case cscmdMzMove:
          case cscmdMzScale2D:
          case cscmdMzScale3D:
          case cscmdMzRotate:
            if (Event.Mouse.Button == 1)
            {
              if (GLOBAL_MODEL->HasSelection ())
              {
                if (ModifyMode)
                  break;
                app->CaptureMouse (this);
                dragMouseX = oldMouseX = Event.Mouse.x;
                dragMouseY = oldMouseY = Event.Mouse.y;
                ModifyMode = GLOBAL_EDITMODE;
                PrepareModificationMatrix ();
                Invalidate ();
              }
              else
                MessageBox (app, TEXT_ERROR, TEXT_NOSELECTION, CSMBS_ERROR | CSMBS_ABORT);
            }
            else if (Event.Mouse.Button == 2)
              goto AbortOperation;
            break;
          case cscmdMzSetupAxis:
            if (Event.Mouse.Button == 1)
            {
              win2world (Event.Mouse.x, Event.Mouse.y, 0, GLOBAL_MODIFYAXIS.Origin);
              GLOBAL_DRAFTCHANGED;
            }
            else if (Event.Mouse.Button == 2)
              goto AbortOperation;
            break;
          case cscmdMzZoomInOut:
            if (Event.Mouse.Button == 1)
              ZoomInOut (Event.Mouse.x, Event.Mouse.y, -scale/4);
            else if (Event.Mouse.Button == 2)
              ZoomInOut (Event.Mouse.x, Event.Mouse.y, +scale/4);
            break;
          case cscmdMzCoordRotate:
          case cscmdMzZoomToRect:
          case cscmdMzSlideView:
            if (Event.Mouse.Button == 1)
            {
              app->CaptureMouse (this);
              dragMouseX = oldMouseX = Event.Mouse.x;
              dragMouseY = oldMouseY = Event.Mouse.y;
              dragRect = (GLOBAL_EDITMODE == cscmdMzZoomToRect);
              switch (GLOBAL_EDITMODE)
              {
                case cscmdMzCoordRotate:
                  old_phi = phi;
                  old_theta = theta;
                  break;
                case cscmdMzSlideView:
                  old_pos = pos;
                  break;
              } /* endswitch */
            }
            else if (Event.Mouse.Button == 2)
              goto AbortOperation;
            break;
        } /* endswitch */
      return true;
    case csevMouseUp:
      if (Event.Mouse.Button != 1)
        break;
      if (app->MouseOwner == this)
        app->CaptureMouse (NULL);
      switch (GLOBAL_EDITMODE)
      {
        case cscmdMzVertexSelect:
        case cscmdMzVertexDeselect:
        case cscmdMzVertexInvert:
        case cscmdMzPolySelect:
        case cscmdMzPolyDeselect:
        case cscmdMzPolyInvert:
		case cscmdMzPolyChg:
		case cscmdMzPortalSelect:
          if (dragRect)
          {
            csRect r (oldMouseX, oldMouseY, dragMouseX, dragMouseY);
            SelectRect (r);
            dragRect = false;
            Invalidate ();
          } /* endif */
          break;
        case cscmdMzZoomToRect:
          if (dragRect)
          {
            int x1 = dragMouseX, y1 = dragMouseY, x2 = oldMouseX, y2 = oldMouseY;
            if (x1 > x2) { int temp = x2; x2 = x1; x1 = temp; }
            if (y1 < y2) { int temp = y2; y2 = y1; y1 = temp; }
            mz3DVertex o,ha,va;
            win2world (x1, y1, 0, o);
            win2world (x2, y1, 0, ha);
            win2world (x1, y2, 0, va);
            ZoomToRect (o, ha, va);
            dragRect = false;
          } /* endif */
          break;
        case cscmdMzMove:
        case cscmdMzScale2D:
        case cscmdMzScale3D:
        case cscmdMzRotate:
          if (ModifyMode)
          {
            GLOBAL_MODEL->Modify (this, GLOBAL_KEY_STATE (shift));
            ModifyMode = 0;
            GLOBAL_SETMODE (GLOBAL_EDITMODE);
            GLOBAL_DRAFTCHANGED;
          } /* endif */
          break;
        case cscmdMzCreateTriangle:
        case cscmdMzCreateSquare:
        case cscmdMzCreateHexagon:
        case cscmdMzCreateOctagon:
        case cscmdMzCreateNgon:
          if (doCreatePolygon)
          {
            CreatePolygon (oldMouseX, oldMouseY, dragMouseX, dragMouseY, 0,
              false, NULL);
            doCreatePolygon = false;
            GLOBAL_DRAFTCHANGED;
          } /* endif */
          break;
        case cscmdMzCreateBox:
        case cscmdMzCreatePrism:
        case cscmdMzCreatePyramid:
        case cscmdMzCreateTorus:
        case cscmdMzCreateSphere:
          if (doCreateBody)
          {
            BodyCreationPhase++;
            Invalidate ();
          } /* endif */
          break;
      } /* endswitch */
      // fallback to MouseMove to set cursor shape
    case csevMouseMove:
      switch (GLOBAL_EDITMODE)
      {
        case cscmdMzCreateTriangle:
        case cscmdMzCreateSquare:
        case cscmdMzCreateHexagon:
        case cscmdMzCreateOctagon:
        case cscmdMzCreateNgon:
          if (doCreatePolygon)
          {
            dragMouseX = Event.Mouse.x;
            dragMouseY = Event.Mouse.y;
            Invalidate ();
          } /* endif */
          SetMouse (csmcPen);
          break;
        case cscmdMzCreateVertex:
        case cscmdMzDeleteVertex:
        case cscmdMzCreateLight:
        case cscmdMzCreatePolyConnect:
        case cscmdMzCreatePolyTriFan:
        case cscmdMzCreatePolyTriStrip:
        case cscmdMzCreatePolyQuadStrip:
          SetMouse (csmcPen);
          break;
        case cscmdMzCreateBox:
        case cscmdMzCreatePrism:
        case cscmdMzCreatePyramid:
        case cscmdMzCreateTorus:
        case cscmdMzCreateSphere:
          if (doCreateBody && ((BodyCreationPhase & 3) != 3))
          {
            dragMouseX = Event.Mouse.x;
            dragMouseY = Event.Mouse.y;
            Invalidate ();
          } /* endif */
          SetMouse (csmcPen);
          break;
        case cscmdMzVertexSelect:
        case cscmdMzVertexDeselect:
        case cscmdMzVertexInvert:
        case cscmdMzPolySelect:
        case cscmdMzPolyDeselect:
        case cscmdMzPolyInvert:
		case cscmdMzPolyChg:
		case cscmdMzPortalSelect:
          SetMouse (csmcCross);
          break;
        case cscmdMzMove:
        case cscmdMzScale2D:
        case cscmdMzScale3D:
        case cscmdMzRotate:
          if (app->MouseOwner == this)
          {
            SetMouse (csmcNone);
            dragMouseX = Event.Mouse.x;
            dragMouseY = Event.Mouse.y;
            PrepareModificationMatrix ();
            Invalidate ();
          }
          else
            SetMouse (csmcArrow);
          break;
        case cscmdMzCoordRotate:
          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 cscmdMzZoomInOut:
          SetMouse (csmcLens);
          break;
        case cscmdMzZoomToRect:
          SetMouse (csmcCross);
          break;
        case cscmdMzSlideView:
          if (app->MouseOwner == this)
          {
            SetMouse (csmcMove);
            if ((dragMouseX != Event.Mouse.x)
             || (dragMouseY != Event.Mouse.y))
            {
              mz3DVertex oldpos, newpos;
              win2world (dragMouseX, dragMouseY, 0, oldpos);
              win2world (Event.Mouse.x, Event.Mouse.y, 0, newpos);
              pos += (oldpos - newpos);
              dragMouseX = Event.Mouse.x;
              dragMouseY = Event.Mouse.y;
              UpdateMatrix ();
              Invalidate ();
            } /* endif */
          }
          else
            SetMouse (csmcArrow);
          break;
      } /* endswitch */
#if 1
      if (dragRect)
      {
        dragMouseX = Event.Mouse.x; dragMouseY = Event.Mouse.y;
        Invalidate ();
      } /* endif */
#else
      {
        int x1 = dragMouseX, y1 = dragMouseY, x2 = oldMouseX, y2 = oldMouseY;
        if (x1 > x2) { int temp = x2; x2 = x1; x1 = temp; }
        if (y1 > y2) { int temp = y2; y2 = y1; y1 = temp; }
        dragMouseX = Event.Mouse.x; dragMouseY = Event.Mouse.y;
        if (dragMouseX < x1) x1 = dragMouseX;
        if (dragMouseX > x2) x2 = dragMouseX;
        if (dragMouseY < y1) y1 = dragMouseY;
        if (dragMouseY > y2) y2 = dragMouseY;
        Invalidate (x1, y1, x2, y2);
      } /* endif */
#endif
      {
        mz3DVertex v;
        win2world (Event.Mouse.x, Event.Mouse.y, 0, v);
        GLOBAL_SETINFO (MZID_INFO_PTR_X, v.x);
        GLOBAL_SETINFO (MZID_INFO_PTR_Y, v.y);
        GLOBAL_SETINFO (MZID_INFO_PTR_Z, v.z);
        GLOBAL_SETINFO (MZID_INFO_VIEW_PHI, int (rint (phi)));
        GLOBAL_SETINFO (MZID_INFO_VIEW_THETA, int (rint (theta)));
        char temp [32];
        if (scale > 1)
          if (fabs (scale - floor (scale)) >= 0.05)
            sprintf (temp, "%.1f:1", scale);
          else
            sprintf (temp, "%.0f:1", scale);
        else
          if (fabs (scale - floor (scale)) >= 0.05)
            sprintf (temp, "1:%.1f", 1/scale);
          else
            sprintf (temp, "1:%.0f", 1/scale);
        GLOBAL_SETINFO (MZID_INFO_VIEW_SCALE, temp);
        GLOBAL_SETINFO (MZID_INFO_VIEW_X, pos.x);
        GLOBAL_SETINFO (MZID_INFO_VIEW_Y, pos.y);
        GLOBAL_SETINFO (MZID_INFO_VIEW_Z, pos.z);
      }
      return true;
    case csevKeyDown:
      switch (Event.Key.Code)
      {
        case CSKEY_ESC:
          if ((Event.Key.ShiftKeys & CSMASK_ALLSHIFTS) != 0)
            break;
AbortOperation:
          if ((app->MouseOwner == this)
           || doCreateBody)
          {
            if (app->MouseOwner == this)
              app->CaptureMouse (NULL);
            switch (GLOBAL_EDITMODE)
            {
              case cscmdMzCreateTriangle:
              case cscmdMzCreateSquare:
              case cscmdMzCreateHexagon:
              case cscmdMzCreateOctagon:
              case cscmdMzCreateNgon:
                doCreatePolygon = false;
                break;
              case cscmdMzCreateBox:
              case cscmdMzCreatePrism:
              case cscmdMzCreatePyramid:
              case cscmdMzCreateTorus:
              case cscmdMzCreateSphere:
                doCreateBody = false;
                break;
              case cscmdMzVertexSelect:
              case cscmdMzVertexDeselect:
              case cscmdMzVertexInvert:
              case cscmdMzPolySelect:
              case cscmdMzPolyDeselect:
              case cscmdMzPolyInvert:
			  case cscmdMzPolyChg:
			  case cscmdMzZoomToRect:
			  case cscmdMzPortalSelect:
                dragRect = false;
                break;
              case cscmdMzMove:
              case cscmdMzScale2D:
              case cscmdMzScale3D:
              case cscmdMzRotate:
                ModifyMode = 0;
                break;
              case cscmdMzCoordRotate:
                SetViewAngles (old_phi, old_theta);
                break;
              case cscmdMzSlideView:
                pos = old_pos;
                break;
            } /* endswitch */
            GLOBAL_SETMODE (GLOBAL_EDITMODE);
            UpdateMatrix ();
            Invalidate ();
          }
          else if (ConnVerticeOrd)
            FinishConnectPoly ();
          return true;
        case CSKEY_ENTER:
          if ((Event.Key.ShiftKeys & CSMASK_ALLSHIFTS) == 0)
            break;
          if (ConnVerticeOrd)
            ConnectPolyVertex (INT_MAX, INT_MAX);
          return true;
        case CSKEY_SHIFT:
        case CSKEY_CTRL:
        case CSKEY_ALT:
          if (ModifyMode)
          {
            PrepareModificationMatrix ();
            Invalidate ();
          }
          return true;
      } /* endswitch */
      break;
    case csevKeyUp:
      switch (Event.Key.Code)
      {
        case CSKEY_SHIFT:
        case CSKEY_CTRL:
        case CSKEY_ALT:
          if (ModifyMode)
          {
            PrepareModificationMatrix ();
            Invalidate ();
          }
          return true;
      } /* endswitch */
      break;
    case csevCommand:
      switch (Event.Command.Code)
      {
        // see also SetViewAngles
        case cscmdMzDraftViewTop:
          SetViewAngles (0, +90);
          Invalidate ();
          return true;
        case cscmdMzDraftViewBottom:
          SetViewAngles (0, -90);
          Invalidate ();
          return true;
        case cscmdMzDraftViewLeft:
          SetViewAngles (90, 0);
          Invalidate ();
          return true;
        case cscmdMzDraftViewRight:
          SetViewAngles (270, 0);
          Invalidate ();
          return true;
        case cscmdMzDraftViewFront:
          SetViewAngles (180, 0);
          Invalidate ();
          return true;
        case cscmdMzDraftViewBack:
          SetViewAngles (0, 0);
          Invalidate ();
          return true;
        case cscmdMzDraftViewFit:
          FitModelInWindow ();
          return true;
        case cscmdMzDraftViewOrigin:
          pos.Set (0, 0, 0);
          UpdateMatrix ();
          Invalidate ();
          return true;
      } /* endswitch */
      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);
}
