/*----------------------------------------------------------------------------*
 | This file is part of DEU (Doom Editing Utilities), created by the DEU team:|
 | Raphael Quinet, Brendon Wyber, Ted Vessenes and others.  See README.1ST or |
 | the "about" dialog box for full credits.                                   |
 |                                                                            |
 | DEU is an open project: if you think that you can contribute, please join  |
 | the DEU team.  You will be credited for any code (or ideas) included in    |
 | the next version of the program.                                           |
 |                                                                            |
 | If you want to make any modifications and re-distribute them on your own,  |
 | you must follow the conditions of the DEU license.  Read the file LICENSE  |
 | in this directory or README.1ST in the top directory.  If do not have a    |
 | copy of these files, you can request them from any member of the DEU team, |
 | or by mail: Raphael Quinet, Rue des Martyrs 9, B-4550 Nandrin (Belgium).   |
 |                                                                            |
 | This program comes with absolutely no warranty.  Use it at your own risks! |
 *----------------------------------------------------------------------------*

 W_OBJECT.C - Insert, delete, copy, move, scale, rotate and create objects

*/

/* the includes */
#include "deu.h"
#include <math.h>
#include "d_misc.h"
#include "d_config.h"
#include "w_levels.h"
#include "w_select.h"
#include "w_object.h"


/*
   Get the number of objets of a given type minus one.
*/

Int16 GetMaxObjectNum(LevelPtr level, int objtype)
{
  switch (objtype)
    {
    case OBJ_THINGS:
      return level->num_things - 1;
    case OBJ_LINEDEFS:
      return level->num_linedefs - 1;
    case OBJ_SIDEDEFS:
      return level->num_sidedefs - 1;
    case OBJ_VERTEXES:
      return level->num_vertexes - 1;
    case OBJ_SECTORS:
      return level->num_sectors - 1;
    }
  return -1;
}



/*
   Delete a single object.
*/

void DeleteObject(LevelPtr level, int objtype, Int16 objnum)
{
   SelPtr list;

   list = NULL;
   SelectObject(&list, objnum);
   DeleteObjects(level, objtype, &list);
}



/*
   Delete a group of objects (*recursive*).
*/

void DeleteObjects(LevelPtr level, int objtype, SelPtr *listp)
{
  Int16  n, objnum;
  SelPtr cur;
  
  level->made_changes = TRUE;
  switch (objtype)
    {
    case OBJ_THINGS:
      while (*listp)
        {
          objnum = (*listp)->objnum;
          /* delete the Thing */
          level->num_things--;
          if (level->num_things > 0)
            {
              for (n = objnum; n < level->num_things; n++)
                level->things[n] = level->things[n + 1];
              level->things = (TPtr) ResizeFarMemory(level->things, level->num_things * sizeof(struct Thing));
            }
          else
            {
              FreeFarMemory(level->things);
              level->things = NULL;
            }
          for (cur = (*listp)->next; cur; cur = cur->next)
            if (cur->objnum > objnum)
              cur->objnum--;
          UnSelectObject(listp, objnum);
        }
      break;
    case OBJ_VERTEXES:
      while (*listp)
        {
          objnum = (*listp)->objnum;
          /* delete the LineDefs bound to this Vertex and change the references */
          for (n = 0; n < level->num_linedefs; n++)
            {
              if (level->linedefs[n].start == objnum || level->linedefs[n].end == objnum)
                DeleteObject(level, OBJ_LINEDEFS, n--);
              else
                {
                  if (level->linedefs[n].start >= objnum)
                    level->linedefs[n].start--;
                  if (level->linedefs[n].end >= objnum)
                    level->linedefs[n].end--;
                }
            }
          /* delete the Vertex */
          level->num_vertexes--;
          if (level->num_vertexes > 0)
            {
              for (n = objnum; n < level->num_vertexes; n++)
                level->vertexes[n] = level->vertexes[n + 1];
              level->vertexes = (VPtr) ResizeFarMemory(level->vertexes, level->num_vertexes * sizeof(struct Vertex));
            }
          else
            {
              FreeFarMemory(level->vertexes);
              level->vertexes = NULL;
            }
          for (cur = (*listp)->next; cur; cur = cur->next)
            if (cur->objnum > objnum)
              cur->objnum--;
          UnSelectObject(listp, objnum);
        }
      break;
    case OBJ_LINEDEFS:
      while (*listp)
        {
          objnum = (*listp)->objnum;
          /* delete the two SideDefs bound to this LineDef */
          if (level->linedefs[objnum].sidedef1 >= 0)
            DeleteObject(level, OBJ_SIDEDEFS, level->linedefs[objnum].sidedef1);
          if (level->linedefs[objnum].sidedef2 >= 0)
            DeleteObject(level, OBJ_SIDEDEFS, level->linedefs[objnum].sidedef2);
          /* delete the LineDef */
          level->num_linedefs--;
          if (level->num_linedefs > 0)
            {
              for (n = objnum; n < level->num_linedefs; n++)
                level->linedefs[n] = level->linedefs[n + 1];
              level->linedefs = (LDPtr) ResizeFarMemory(level->linedefs, level->num_linedefs * sizeof(struct LineDef));
            }
          else
            {
              FreeFarMemory(level->linedefs);
              level->linedefs = NULL;
            }
          for (cur = (*listp)->next; cur; cur = cur->next)
            if (cur->objnum > objnum)
              cur->objnum--;
          UnSelectObject(listp, objnum);
        }
      break;
    case OBJ_SIDEDEFS:
      while (*listp)
        {
          objnum = (*listp)->objnum;
          /* change the LineDefs references */
          for (n = 0; n < level->num_linedefs; n++)
            {
              if (level->linedefs[n].sidedef1 == objnum)
                level->linedefs[n].sidedef1 = -1;
              else if (level->linedefs[n].sidedef1 >= objnum)
                level->linedefs[n].sidedef1--;
              if (level->linedefs[n].sidedef2 == objnum)
                level->linedefs[n].sidedef2 = -1;
              else if (level->linedefs[n].sidedef2 >= objnum)
                level->linedefs[n].sidedef2--;
            }
          /* delete the SideDef */
          level->num_sidedefs--;
          if (level->num_sidedefs > 0)
            {
              for (n = objnum; n < level->num_sidedefs; n++)
                level->sidedefs[n] = level->sidedefs[n + 1];
              level->sidedefs = (SDPtr) ResizeFarMemory(level->sidedefs, level->num_sidedefs * sizeof(struct SideDef));
            }
          else
            {
              FreeFarMemory(level->sidedefs);
              level->sidedefs = NULL;
            }
          for (cur = (*listp)->next; cur; cur = cur->next)
            if (cur->objnum > objnum)
              cur->objnum--;
          UnSelectObject(listp, objnum);
        }
      level->made_map_changes = TRUE;
      break;
    case OBJ_SECTORS:
      while (*listp)
        {
          objnum = (*listp)->objnum;
          /* delete the SideDefs bound to this Sector and change the references */
          for (n = 0; n < level->num_sidedefs; n++)
            if (level->sidedefs[n].sector == objnum)
              DeleteObject(level, OBJ_SIDEDEFS, n--);
            else if (level->sidedefs[n].sector >= objnum)
              level->sidedefs[n].sector--;
          /* delete the Sector */
          level->num_sectors--;
          if (level->num_sectors > 0)
            {
              for (n = objnum; n < level->num_sectors; n++)
                level->sectors[n] = level->sectors[n + 1];
              level->sectors = (SPtr) ResizeFarMemory(level->sectors, level->num_sectors * sizeof(struct Sector));
            }
          else
            {
              FreeFarMemory(level->sectors);
              level->sectors = NULL;
            }
          for (cur = (*listp)->next; cur; cur = cur->next)
            if (cur->objnum > objnum)
              cur->objnum--;
          UnSelectObject(listp, objnum);
        }
      break;
    default:
      Beep();
    }
}


/*
   Insert a new object.
*/

void InsertObject(LevelPtr level, int objtype, Int16 copyfrom, Int16 xpos, Int16 ypos)
{
  Int16 last;

  level->made_changes = TRUE;
  switch (objtype)
    {
    case OBJ_THINGS:
      last = level->num_things++;
      if (last > 0)
        level->things = (TPtr) ResizeFarMemory(level->things, (UInt32) level->num_things * sizeof(struct Thing));
      else
        level->things = (TPtr) GetFarMemory(sizeof(struct Thing));
      level->things[last].xpos = xpos;
      level->things[last].ypos = ypos;
      if (copyfrom >= 0)
        {
          level->things[last].type  = level->things[copyfrom].type;
          level->things[last].angle = level->things[copyfrom].angle;
          level->things[last].when  = level->things[copyfrom].when;
        }
      else
        {
          level->things[last].type  = 1;
          level->things[last].angle = 0;
          level->things[last].when  = 0x07;
        }
      break;
    case OBJ_VERTEXES:
      last = level->num_vertexes++;
      if (last > 0)
        level->vertexes = (VPtr) ResizeFarMemory(level->vertexes, (UInt32) level->num_vertexes * sizeof(struct Vertex));
      else
        level->vertexes = (VPtr) GetFarMemory(sizeof(struct Vertex));
      /* kluge: the Nodes builder will put -2 in copyfrom */
      if (copyfrom == -2)
        {
          /* do not snap this vertex to a 8x8 grid */
          level->vertexes[last].x = xpos;
          level->vertexes[last].y = ypos;
        }
      else
        {
          level->vertexes[last].x = xpos & ~7;
          level->vertexes[last].y = ypos & ~7;
          if (level->vertexes[last].x < level->map_minX)
            level->map_minX = level->vertexes[last].x;
          if (level->vertexes[last].x > level->map_maxX)
            level->map_maxX = level->vertexes[last].x;
          if (level->vertexes[last].y < level->map_minY)
            level->map_minY = level->vertexes[last].y;
          if (level->vertexes[last].y > level->map_maxY)
            level->map_maxY = level->vertexes[last].y;
          level->made_map_changes = TRUE;
        }
      break;
    case OBJ_LINEDEFS:
      last = level->num_linedefs++;
      if (last > 0)
        level->linedefs = (LDPtr) ResizeFarMemory(level->linedefs, (UInt32) level->num_linedefs * sizeof(struct LineDef));
      else
        level->linedefs = (LDPtr) GetFarMemory(sizeof(struct LineDef));
      if (copyfrom >= 0)
        {
          level->linedefs[last].start = level->linedefs[copyfrom].start;
          level->linedefs[last].end = level->linedefs[copyfrom].end;
          level->linedefs[last].flags = level->linedefs[copyfrom].flags;
          level->linedefs[last].type = level->linedefs[copyfrom].type;
          level->linedefs[last].tag = level->linedefs[copyfrom].tag;
        }
      else
        {
          level->linedefs[last].start = 0;
          level->linedefs[last].end = level->num_vertexes - 1;
          level->linedefs[last].flags = 1;
          level->linedefs[last].type = 0;
          level->linedefs[last].tag = 0;
        }
      level->linedefs[last].sidedef1 = -1;
      level->linedefs[last].sidedef2 = -1;
      break;
    case OBJ_SIDEDEFS:
      /* SideDefs are added from the LineDefs menu, so "copyfrom" should always be -1.  But I test it anyway. */
      last = level->num_sidedefs++;
      if (last > 0)
        level->sidedefs = (SDPtr) ResizeFarMemory(level->sidedefs, (UInt32) level->num_sidedefs * sizeof(struct SideDef));
      else
        level->sidedefs = (SDPtr) GetFarMemory(sizeof(struct SideDef));
      if (copyfrom >= 0)
        {
          level->sidedefs[last].xoff = level->sidedefs[copyfrom].xoff;
          level->sidedefs[last].yoff = level->sidedefs[copyfrom].yoff;
          strncpy(level->sidedefs[last].tex1, level->sidedefs[copyfrom].tex1, 8);
          strncpy(level->sidedefs[last].tex2, level->sidedefs[copyfrom].tex2, 8);
          strncpy(level->sidedefs[last].tex3, level->sidedefs[copyfrom].tex3, 8);
          level->sidedefs[last].sector = level->sidedefs[copyfrom].sector;
        }
      else
        {
          level->sidedefs[last].xoff = 0;
          level->sidedefs[last].yoff = 0;
          strcpy(level->sidedefs[last].tex1, "-");
          strcpy(level->sidedefs[last].tex2, "-");
          strcpy(level->sidedefs[last].tex3, Config.wallTexture);
          level->sidedefs[last].sector = level->num_sectors - 1;
        }
      level->made_map_changes = TRUE;
      break;
    case OBJ_SECTORS:
      last = level->num_sectors++;
      if (last > 0)
        level->sectors = (SPtr) ResizeFarMemory(level->sectors, (UInt32) level->num_sectors * sizeof(struct Sector));
      else
        level->sectors = (SPtr) GetFarMemory(sizeof(struct Sector));
      if (copyfrom >= 0)
        {
          level->sectors[last].floorh = level->sectors[copyfrom].floorh;
          level->sectors[last].ceilh = level->sectors[copyfrom].ceilh;
          strncpy(level->sectors[last].floort, level->sectors[copyfrom].floort, 8);
          strncpy(level->sectors[last].ceilt, level->sectors[copyfrom].ceilt, 8);
          level->sectors[last].light = level->sectors[copyfrom].light;
          level->sectors[last].type = level->sectors[copyfrom].type;
          level->sectors[last].tag = level->sectors[copyfrom].tag;
        }
      else
        {
          level->sectors[last].floorh = Config.floorHeight;
          level->sectors[last].ceilh = Config.ceilingHeight;
          strncpy(level->sectors[last].floort, Config.floorTexture, 8);
          strncpy(level->sectors[last].ceilt, Config.ceilingTexture, 8);
          level->sectors[last].light = Config.lightLevel;
          level->sectors[last].type = 0;
          level->sectors[last].tag = 0;
        }
      break;
    default:
      Beep();
    }
}


/*
   Copy a group of objects to a new position.
*/

void DuplicateObjects(LevelPtr level, int objtype, SelPtr list)
{
  Int16      n, m;
  SelPtr     cur;
  SelPtr     list1, list2;
  SelPtr     ref1, ref2;
  
  if (list == NULL)
    return;
  /* copy the object(s) */
  switch (objtype)
    {
    case OBJ_THINGS:
      for (cur = list; cur; cur = cur->next)
        {
          InsertObject(level, OBJ_THINGS, cur->objnum, level->things[cur->objnum].xpos, level->things[cur->objnum].ypos);
          cur->objnum = level->num_things - 1;
        }
      level->made_changes = TRUE;
      break;
      
    case OBJ_VERTEXES:
      for (cur = list; cur; cur = cur->next)
        {
          InsertObject(level, OBJ_VERTEXES, cur->objnum, level->vertexes[cur->objnum].x, level->vertexes[cur->objnum].y);
          cur->objnum = level->num_vertexes - 1;
        }
      level->made_changes = TRUE;
      level->made_map_changes = TRUE;
      break;
      
    case OBJ_LINEDEFS:
      list1 = NULL;
      list2 = NULL;
      /* create the LineDefs */
      for (cur = list; cur; cur = cur->next)
        {
          InsertObject(level, OBJ_LINEDEFS, cur->objnum, 0, 0);
          cur->objnum = level->num_linedefs - 1;
          if (!IsSelected(list1, level->linedefs[cur->objnum].start))
            {
              SelectObject(&list1, level->linedefs[cur->objnum].start);
              SelectObject(&list2, level->linedefs[cur->objnum].start);
            }
          if (!IsSelected(list1, level->linedefs[cur->objnum].end))
            {
              SelectObject(&list1, level->linedefs[cur->objnum].end);
              SelectObject(&list2, level->linedefs[cur->objnum].end);
            }
        }
      /* create the Vertices */
      DuplicateObjects(level, OBJ_VERTEXES, list2);
      /* update the references to the vertices */
      for (ref1 = list1, ref2 = list2; ref1 && ref2; ref1 = ref1->next, ref2 = ref2->next)
        {
          for (cur = list; cur; cur = cur->next)
            {
              if (ref1->objnum == level->linedefs[cur->objnum].start)
                level->linedefs[cur->objnum].start = ref2->objnum;
              if (ref1->objnum == level->linedefs[cur->objnum].end)
                level->linedefs[cur->objnum].end = ref2->objnum;
            }
        }
      ForgetSelection(&list1);
      ForgetSelection(&list2);
      break;

    case OBJ_SECTORS:
      list1 = NULL;
      list2 = NULL;
      /* create the LineDefs (and Vertices) */
      for (cur = list; cur; cur = cur->next)
        {
          for (n = 0; n < level->num_linedefs; n++)
            if ((((m = level->linedefs[n].sidedef1) >= 0 && level->sidedefs[m].sector == cur->objnum)
                 || ((m = level->linedefs[n].sidedef2) >= 0 && level->sidedefs[m].sector == cur->objnum))
                && ! IsSelected(list1, n))
              {
                SelectObject(&list1, n);
                SelectObject(&list2, n);
              }
        }
      DuplicateObjects(level, OBJ_LINEDEFS, list2);
      /* create the SideDefs */
      for (ref1 = list1, ref2 = list2; ref1 && ref2; ref1 = ref1->next, ref2 = ref2->next)
        {
          if ((n = level->linedefs[ref1->objnum].sidedef1) >= 0)
            {
              InsertObject(level, OBJ_SIDEDEFS, n, 0, 0);
              n = level->num_sidedefs - 1;
              level->linedefs[ref2->objnum].sidedef1 = n;
            }
          if ((m = level->linedefs[ref1->objnum].sidedef2) >= 0)
            {
              InsertObject(level, OBJ_SIDEDEFS, m, 0, 0);
              m = level->num_sidedefs - 1;
              level->linedefs[ref2->objnum].sidedef2 = m;
            }
          ref1->objnum = n;
          ref2->objnum = m;
        }
      /* create the Sectors */
      for (cur = list; cur; cur = cur->next)
        {
          InsertObject(level, OBJ_SECTORS, cur->objnum, 0, 0);
          for (ref1 = list1, ref2 = list2; ref1 && ref2; ref1 = ref1->next, ref2 = ref2->next)
            {
              if (ref1->objnum >= 0 && level->sidedefs[ref1->objnum].sector == cur->objnum)
                level->sidedefs[ref1->objnum].sector = level->num_sectors - 1;
              if (ref2->objnum >= 0 && level->sidedefs[ref2->objnum].sector == cur->objnum)
                level->sidedefs[ref2->objnum].sector = level->num_sectors - 1;
            }
          cur->objnum = level->num_sectors - 1;
        }
      ForgetSelection(&list1);
      ForgetSelection(&list2);
      break;
    }
}


/*
   Move a group of objects to a new position.
   The first call initializes the internal variables.  All subsequent calls
   to move the same group of objects must be made with list = NULL.
   This function returns TRUE if the objects have been moved since the last
   call.
*/

Bool MoveObjectsToCoords(LevelPtr level, int objtype, SelPtr list, Int16 newx, Int16 newy, Int16 grid)
{
  Int16         n, m;
  Int16         dx, dy;
  SelPtr        cur;
  static Int16  refx, refy; /* previous position */
  static SelPtr v_list; /* Vertices to be moved */
  static SelPtr th_list; /* Things to be moved */

  if (grid > 0)
    {
      newx = (newx + grid / 2) & ~(grid - 1);
      newy = (newy + grid / 2) & ~(grid - 1);
    }

  /* update the reference point and create the lists */
  if (list != NULL)
    {
      refx = newx;
      refy = newy;
      ForgetSelection(&v_list);
      ForgetSelection(&th_list);
      switch (objtype)
        {
        case OBJ_THINGS:
          for (cur = list; cur; cur = cur->next)
            {
              if (!IsSelected(th_list, cur->objnum))
                SelectObject(&th_list, cur->objnum);
            }
          break;
        case OBJ_VERTEXES:
          for (cur = list; cur; cur = cur->next)
            {
              if (!IsSelected(v_list, cur->objnum))
                SelectObject(&v_list, cur->objnum);
            }
          break;
        case OBJ_LINEDEFS:
          for (cur = list; cur; cur = cur->next)
            {
              if (!IsSelected(v_list, level->linedefs[cur->objnum].start))
                SelectObject(&v_list, level->linedefs[cur->objnum].start);
              if (!IsSelected(v_list, level->linedefs[cur->objnum].end))
                SelectObject(&v_list, level->linedefs[cur->objnum].end);
            }
          break;
        case OBJ_SECTORS:
          for (cur = list; cur; cur = cur->next)
            {
              for (n = 0; n < level->num_linedefs; n++)
                if (((m = level->linedefs[n].sidedef1) >= 0 && level->sidedefs[m].sector == cur->objnum)
                    || ((m = level->linedefs[n].sidedef2) >= 0 && level->sidedefs[m].sector == cur->objnum))
                  {
                    if (!IsSelected(v_list, level->linedefs[n].start))
                      SelectObject(&v_list, level->linedefs[n].start);
                    if (!IsSelected(v_list, level->linedefs[n].end))
                      SelectObject(&v_list, level->linedefs[n].end);
                  }
            }
          if (Config.moveThings && v_list != NULL)
            {
              Int16 minx, maxx, miny, maxy;

              minx = 32767;
              maxx = -32767;
              miny = 32767;
              maxy = -32767;
              for (cur = v_list; cur; cur = cur->next)
                {
                  if (level->vertexes[cur->objnum].x < minx)
                    minx = level->vertexes[cur->objnum].x;
                  if (level->vertexes[cur->objnum].x > maxx)
                    maxx = level->vertexes[cur->objnum].x;
                  if (level->vertexes[cur->objnum].y < miny)
                    miny = level->vertexes[cur->objnum].y;
                  if (level->vertexes[cur->objnum].y > maxy)
                    maxy = level->vertexes[cur->objnum].y;
                }
              for (n = 0; n < level->num_things; n++)
                {
                  /* speedup: ignore the Things that are outside the rectangle enclosing all sectors */
                  if (level->things[n].xpos < minx || level->things[n].xpos > maxx
                      || level->things[n].ypos < miny || level->things[n].ypos > maxy)
                    continue;
                  m = GetSectorForThing(level, n);
                  if (m >= 0 && IsSelected(list, m))
                    SelectObject(&th_list, n);
                }
            }
          break;
        }
      /* nothing to move now */
      return TRUE;
    }

  /* compute the displacement */
  dx = newx - refx;
  dy = newy - refy;
  /* nothing to do? */
  if (dx == 0 && dy == 0)
    return FALSE;

  /* move the Things in th_list */
  for (cur = th_list; cur; cur = cur->next)
    {
      level->things[cur->objnum].xpos += dx;
      level->things[cur->objnum].ypos += dy;
    }

  /* move the Vertices in v_list */
  for (cur = v_list; cur; cur = cur->next)
    {
      level->vertexes[cur->objnum].x += dx;
      level->vertexes[cur->objnum].y += dy;
    }

  refx = newx;
  refy = newy;
  if (v_list != NULL)
    level->made_map_changes = TRUE;
  level->made_changes = TRUE;
  return TRUE;
}


/*
   Get the coordinates (approx.) of an object.
*/

void GetObjectCoords(LevelPtr level, int objtype, Int16 objnum, Int16 *xpos, Int16 *ypos)
{
  Int16 n, v1, v2, sd1, sd2;
  Int32 accx, accy, num;

  switch (objtype)
    {
    case OBJ_THINGS:
      *xpos = level->things[objnum].xpos;
      *ypos = level->things[objnum].ypos;
      break;
    case OBJ_VERTEXES:
      *xpos = level->vertexes[objnum].x;
      *ypos = level->vertexes[objnum].y;
      break;
    case OBJ_SIDEDEFS:
      for (n = 0; n < level->num_linedefs; n++)
        if (level->linedefs[n].sidedef1 == objnum || level->linedefs[n].sidedef2 == objnum)
          {
            objnum = n;
            break;
          }
      if (n >= level->num_linedefs) /* "lost" SideDef */
        {
          *xpos = (level->map_minX + level->map_maxX) / 2;
          *ypos = (level->map_minY + level->map_maxY) / 2;
          break;
        }
    case OBJ_LINEDEFS:
      v1 = level->linedefs[objnum].start;
      v2 = level->linedefs[objnum].end;
      *xpos = (level->vertexes[v1].x + level->vertexes[v2].x) / 2;
      *ypos = (level->vertexes[v1].y + level->vertexes[v2].y) / 2;
      break;
    case OBJ_SECTORS:
      accx = 0L;
      accy = 0L;
      num = 0L;
      for (n = 0; n < level->num_linedefs; n++)
        {
          sd1 = level->linedefs[n].sidedef1;
          sd2 = level->linedefs[n].sidedef2;
          v1 = level->linedefs[n].start;
          v2 = level->linedefs[n].end;
          if ((sd1 >= 0 && level->sidedefs[sd1].sector == objnum) || (sd2 >= 0 && level->sidedefs[sd2].sector == objnum))
            {
              /* if the Sector is closed, all Vertices will be counted twice */
              accx += (Int32) level->vertexes[v1].x;
              accy += (Int32) level->vertexes[v1].y;
              num++;
              accx += (Int32) level->vertexes[v2].x;
              accy += (Int32) level->vertexes[v2].y;
              num++;
            }
        }
      if (num > 0)
        {
          *xpos = (Int16) ((accx + num / 2L) / num);
          *ypos = (Int16) ((accy + num / 2L) / num);
        }
      else
        {
          /* "lost" Sector */
          *xpos = (level->map_minX + level->map_maxX) / 2;
          *ypos = (level->map_minY + level->map_maxY) / 2;
        }
      break;
    }
}


/*
   Move (*x, *y) to a new position: rotate and scale around (0, 0).
*/

static void RotateAndScaleCoords(Int16 *x, Int16 *y, double angle, double scale)
{
  double r, theta;

  r = hypot((double) *x, (double) *y);
  theta = atan2((double) *y, (double) *x);
  *x = (Int16) (r * scale * cos(theta + angle) + 0.5);
  *y = (Int16) (r * scale * sin(theta + angle) + 0.5);
}


/*
   Rotate and scale a group of objects around their center of gravity.
*/

void RotateAndScaleObjects(LevelPtr level, int objtype, SelPtr obj, double angle, double scale)
{
  Int16  n, m;
  Int16  dx, dy;
  Int16  centerx, centery;
  Int32  accx, accy, num;
  SelPtr cur, vertices;

  if (obj == NULL)
    return;

  switch (objtype)
    {
    case OBJ_THINGS:
      accx = 0L;
      accy = 0L;
      num = 0L;
      for (cur = obj; cur; cur = cur->next)
        {
          accx += (Int32) level->things[cur->objnum].xpos;
          accy += (Int32) level->things[cur->objnum].ypos;
          num++;
        }
      centerx = (Int16) ((accx + num / 2L) / num);
      centery = (Int16) ((accy + num / 2L) / num);
      for (cur = obj; cur; cur = cur->next)
        {
          dx = level->things[cur->objnum].xpos - centerx;
          dy = level->things[cur->objnum].ypos - centery;
          RotateAndScaleCoords(&dx, &dy, angle, scale);
          level->things[cur->objnum].xpos = centerx + dx;
          level->things[cur->objnum].ypos = centery + dy;
        }
      level->made_changes = TRUE;
      break;
    case OBJ_VERTEXES:
      accx = 0L;
      accy = 0L;
      num = 0L;
      for (cur = obj; cur; cur = cur->next)
        {
          accx += (Int32) level->vertexes[cur->objnum].x;
          accy += (Int32) level->vertexes[cur->objnum].y;
          num++;
        }
      centerx = (Int16) ((accx + num / 2L) / num);
      centery = (Int16) ((accy + num / 2L) / num);
      for (cur = obj; cur; cur = cur->next)
        {
          dx = level->vertexes[cur->objnum].x - centerx;
          dy = level->vertexes[cur->objnum].y - centery;
          RotateAndScaleCoords(&dx, &dy, angle, scale);
          level->vertexes[cur->objnum].x = (centerx + dx + 4) & ~7;
          level->vertexes[cur->objnum].y = (centery + dy + 4) & ~7;
        }
      level->made_changes = TRUE;
      level->made_map_changes = TRUE;
      break;
    case OBJ_LINEDEFS:
      vertices = NULL;
      for (cur = obj; cur; cur = cur->next)
        {
          if (!IsSelected(vertices, level->linedefs[cur->objnum].start))
            SelectObject(&vertices, level->linedefs[cur->objnum].start);
          if (!IsSelected(vertices, level->linedefs[cur->objnum].end))
            SelectObject(&vertices, level->linedefs[cur->objnum].end);
        }
      RotateAndScaleObjects(level, OBJ_VERTEXES, vertices, angle, scale);
      ForgetSelection(&vertices);
      break;
    case OBJ_SECTORS:
      vertices = NULL;
      for (cur = obj; cur; cur = cur->next)
        {
          for (n = 0; n < level->num_linedefs; n++)
            if (((m = level->linedefs[n].sidedef1) >= 0 && level->sidedefs[m].sector == cur->objnum)
                || ((m = level->linedefs[n].sidedef2) >= 0 && level->sidedefs[m].sector == cur->objnum))
              {
                if (!IsSelected(vertices, level->linedefs[n].start))
                  SelectObject(&vertices, level->linedefs[n].start);
                if (!IsSelected(vertices, level->linedefs[n].end))
                  SelectObject(&vertices, level->linedefs[n].end);
              }
        }
      RotateAndScaleObjects(level, OBJ_VERTEXES, vertices, angle, scale);
      ForgetSelection(&vertices);
      break;
    }
}



/*
   Mirror a group of objects (vertical or horizontal mirror)
*/

void MirrorObjects(LevelPtr level, int objtype, SelPtr obj, Bool vertmirror)
{
  Int16  n, m, tmp;
  Int16  centerx, centery;
  Int32  accx, accy, num;
  SelPtr cur, vertices;

  if (obj == NULL)
    return;

  switch (objtype)
    {
    case OBJ_THINGS:
      accx = 0L;
      accy = 0L;
      num = 0L;
      for (cur = obj; cur; cur = cur->next)
        {
          accx += (Int32) level->things[cur->objnum].xpos;
          accy += (Int32) level->things[cur->objnum].ypos;
          num++;
        }
      centerx = (Int16) ((accx + num / 2L) / num);
      centery = (Int16) ((accy + num / 2L) / num);
      if (vertmirror == TRUE)
        {
          for (cur = obj; cur; cur = cur->next)
            level->things[cur->objnum].xpos = centerx + (centerx - level->things[cur->objnum].xpos);
        }
      else
        {
          for (cur = obj; cur; cur = cur->next)
            level->things[cur->objnum].ypos = centery + (centery - level->things[cur->objnum].ypos);
        }
      level->made_changes = TRUE;
      break;
    case OBJ_VERTEXES:
      accx = 0L;
      accy = 0L;
      num = 0L;
      for (cur = obj; cur; cur = cur->next)
        {
          accx += (Int32) level->vertexes[cur->objnum].x;
          accy += (Int32) level->vertexes[cur->objnum].y;
          num++;
        }
      centerx = (Int16) ((accx + num / 2L) / num);
      centery = (Int16) ((accy + num / 2L) / num);
      if (vertmirror == TRUE)
        {
          for (cur = obj; cur; cur = cur->next)
            level->vertexes[cur->objnum].x = centerx + (centerx - level->vertexes[cur->objnum].x);
        }
      else
        {
          for (cur = obj; cur; cur = cur->next)
            level->vertexes[cur->objnum].y = centery + (centery - level->vertexes[cur->objnum].y);
        }
      level->made_changes = TRUE;
      level->made_map_changes = TRUE;
      break;
    case OBJ_LINEDEFS:
      vertices = NULL;
      for (cur = obj; cur; cur = cur->next)
        {
          if (!IsSelected(vertices, level->linedefs[cur->objnum].start))
            SelectObject(&vertices, level->linedefs[cur->objnum].start);
          if (!IsSelected(vertices, level->linedefs[cur->objnum].end))
            SelectObject(&vertices, level->linedefs[cur->objnum].end);
          /* swap endpoints */
          tmp = level->linedefs[cur->objnum].end;
          level->linedefs[cur->objnum].end = level->linedefs[cur->objnum].start;
          level->linedefs[cur->objnum].start = tmp;
        }
      MirrorObjects(level, OBJ_VERTEXES, vertices, vertmirror);
      ForgetSelection(&vertices);
      break;
    case OBJ_SECTORS:
      vertices = NULL;
      for (cur = obj; cur; cur = cur->next)
        {
          for (n = 0; n < level->num_linedefs; n++)
            if (((m = level->linedefs[n].sidedef1) >= 0 && level->sidedefs[m].sector == cur->objnum)
                || ((m = level->linedefs[n].sidedef2) >= 0 && level->sidedefs[m].sector == cur->objnum))
              {
                if (!IsSelected(vertices, level->linedefs[n].start))
                  SelectObject(&vertices, level->linedefs[n].start);
                if (!IsSelected(vertices, level->linedefs[n].end))
                  SelectObject(&vertices, level->linedefs[n].end);
                /* swap endpoints */
                tmp = level->linedefs[n].end;
                level->linedefs[n].end = level->linedefs[n].start;
                level->linedefs[n].start = tmp;
              }
        }
      MirrorObjects(level, OBJ_VERTEXES, vertices, vertmirror);
      ForgetSelection(&vertices);
      break;
    }
}



/*
   Renumber an object (and update all references to it).
*/

void RenumberObject(LevelPtr level, int objtype, Int16 oldnum, Int16 newnum)
{
  Int16  n;

  level->made_changes = TRUE;
  switch (objtype)
    {
    case OBJ_THINGS:
      if (newnum > oldnum)
        {
          struct Thing tmp;

          tmp = level->things[oldnum];
          for (n = oldnum; n < newnum; n++)
            level->things[n] = level->things[n + 1];
          level->things[newnum] = tmp;
        }
      else
        {
          struct Thing tmp;

          tmp = level->things[oldnum];
          for (n = oldnum; n > newnum; n--)
            level->things[n] = level->things[n - 1];
          level->things[newnum] = tmp;
        }
      break;
    case OBJ_VERTEXES:
      if (newnum > oldnum)
        {
          struct Vertex tmp;

          /* update the LineDef references */
          for (n = 0; n < level->num_linedefs; n++)
            {
              if (level->linedefs[n].start == oldnum)
                level->linedefs[n].start = newnum;
              else if (level->linedefs[n].start > oldnum
                       && level->linedefs[n].start <= newnum)
                level->linedefs[n].start--;
              if (level->linedefs[n].end == oldnum)
                level->linedefs[n].end = newnum;
              else if (level->linedefs[n].end > oldnum
                       && level->linedefs[n].end <= newnum)
                level->linedefs[n].end--;
            }
          tmp = level->vertexes[oldnum];
          for (n = oldnum; n < newnum; n++)
            level->vertexes[n] = level->vertexes[n + 1];
          level->vertexes[newnum] = tmp;
        }
      else
        {
          struct Vertex tmp;

          /* update the LineDef references */
          for (n = 0; n < level->num_linedefs; n++)
            {
              if (level->linedefs[n].start == oldnum)
                level->linedefs[n].start = newnum;
              else if (level->linedefs[n].start < oldnum
                       && level->linedefs[n].start >= newnum)
                level->linedefs[n].start++;
              if (level->linedefs[n].end == oldnum)
                level->linedefs[n].end = newnum;
              else if (level->linedefs[n].end < oldnum
                       && level->linedefs[n].end >= newnum)
                level->linedefs[n].end++;
            }
          tmp = level->vertexes[oldnum];
          for (n = oldnum; n > newnum; n--)
            level->vertexes[n] = level->vertexes[n - 1];
          level->vertexes[newnum] = tmp;
        }
      level->made_map_changes = TRUE;
      break;
    case OBJ_LINEDEFS:
      if (newnum > oldnum)
        {
          struct LineDef tmp;

          tmp = level->linedefs[oldnum];
          for (n = oldnum; n < newnum; n++)
            level->linedefs[n] = level->linedefs[n + 1];
          level->linedefs[newnum] = tmp;
        }
      else
        {
          struct LineDef tmp;

          tmp = level->linedefs[oldnum];
          for (n = oldnum; n > newnum; n--)
            level->linedefs[n] = level->linedefs[n - 1];
          level->linedefs[newnum] = tmp;
        }
      level->made_map_changes = TRUE;
      break;
    case OBJ_SIDEDEFS:
      if (newnum > oldnum)
        {
          struct SideDef tmp;

          /* update the LineDef references */
          for (n = 0; n < level->num_linedefs; n++)
            {
              if (level->linedefs[n].sidedef1 == oldnum)
                level->linedefs[n].sidedef1 = newnum;
              else if (level->linedefs[n].sidedef1 > oldnum
                       && level->linedefs[n].sidedef1 <= newnum)
                level->linedefs[n].sidedef1--;
              if (level->linedefs[n].sidedef2 == oldnum)
                level->linedefs[n].sidedef2 = newnum;
              else if (level->linedefs[n].sidedef2 > oldnum
                       && level->linedefs[n].sidedef2 <= newnum)
                level->linedefs[n].sidedef2--;
            }
          tmp = level->sidedefs[oldnum];
          for (n = oldnum; n < newnum; n++)
            level->sidedefs[n] = level->sidedefs[n + 1];
          level->sidedefs[newnum] = tmp;
        }
      else
        {
          struct SideDef tmp;

          /* update the LineDef references */
          for (n = 0; n < level->num_linedefs; n++)
            {
              if (level->linedefs[n].sidedef1 == oldnum)
                level->linedefs[n].sidedef1 = newnum;
              else if (level->linedefs[n].sidedef1 < oldnum
                       && level->linedefs[n].sidedef1 >= newnum)
                level->linedefs[n].sidedef1++;
              if (level->linedefs[n].sidedef2 == oldnum)
                level->linedefs[n].sidedef2 = newnum;
              else if (level->linedefs[n].sidedef2 < oldnum
                       && level->linedefs[n].sidedef2 >= newnum)
                level->linedefs[n].sidedef2++;
            }
          tmp = level->sidedefs[oldnum];
          for (n = oldnum; n > newnum; n--)
            level->sidedefs[n] = level->sidedefs[n - 1];
          level->sidedefs[newnum] = tmp;
        }
      break;
    case OBJ_SECTORS:
      if (newnum > oldnum)
        {
          struct Sector tmp;

          /* update the SideDef references */
          for (n = 0; n < level->num_sidedefs; n++)
            {
              if (level->sidedefs[n].sector == oldnum)
                level->sidedefs[n].sector = newnum;
              else if (level->sidedefs[n].sector > oldnum
                       && level->sidedefs[n].sector <= newnum)
                level->sidedefs[n].sector--;
            }
          tmp = level->sectors[oldnum];
          for (n = oldnum; n < newnum; n++)
            level->sectors[n] = level->sectors[n + 1];
          level->sectors[newnum] = tmp;
        }
      else
        {
          struct Sector tmp;

          /* update the SideDef references */
          for (n = 0; n < level->num_sidedefs; n++)
            {
              if (level->sidedefs[n].sector == oldnum)
                level->sidedefs[n].sector = newnum;
              else if (level->sidedefs[n].sector < oldnum
                       && level->sidedefs[n].sector >= newnum)
                level->sidedefs[n].sector++;
            }
          tmp = level->sectors[oldnum];
          for (n = oldnum; n > newnum; n--)
            level->sectors[n] = level->sectors[n - 1];
          level->sectors[newnum] = tmp;
        }
      break;
    default:
      Beep();
    }
}


/*
   Find the first free tag number.
*/

Int16 FindFreeTag(LevelPtr level)
{
  Int16 tag, n;
  Bool  ok;
  
  tag = 1;
  ok = FALSE;
  while (! ok)
    {
      ok = TRUE;
      for (n = 0; n < level->num_linedefs; n++)
        if (level->linedefs[n].tag == tag)
          {
            ok = FALSE;
            break;
          }
      if (ok)
        for (n = 0; n < level->num_sectors; n++)
          if (level->sectors[n].tag == tag)
            {
              ok = FALSE;
              break;
            }
      tag++;
    }
  return tag - 1;
}


/* end of file */
