/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.

--- HISTORICAL COMMENTS FOLLOW ---

 * $Logfile: /DescentIII/Main/editor/editor_lighting.cpp $
 * $Revision: 1.1.1.1 $
 * $Date: 2003-08-26 03:57:38 $
 * $Author: kevinb $
 * $NoKeywords: $
 */

#include <algorithm>
#include <windows.h>
#include "3d.h"
#include "gametexture.h"
#include "erooms.h"
#include "editor_lighting.h"
#include "descent.h"
#include "room.h"
#include "lightmap.h"
#include "polymodel.h"
#include <string.h>
#include <stdlib.h>
#include "terrain.h"
#include "radiosity.h"
#include "lighting.h"
#include "findintersection.h"
#include "lightmap_info.h"
#include "object_lighting.h"
#include "d3edit.h"
#include "ddio.h"
#include "bsp.h"
#include "Application.h"
#include "AppDatabase.h"
#include "program.h"
#include "loadlevel.h"
#include "special_face.h"
#include "boa.h"
#include "mem.h"

struct spec_vertex {
  float x, y;
};

int AllowCombining = 1;
float GlobalMultiplier = 1.0;

rad_surface *Light_surfaces = NULL;

vector ScratchCenters[MAX_LIGHTMAP_INFOS];
vector ScratchRVecs[MAX_LIGHTMAP_INFOS];
vector ScratchUVecs[MAX_LIGHTMAP_INFOS];

float Room_multiplier[MAX_ROOMS + MAX_PALETTE_ROOMS];
float Room_ambience_r[MAX_ROOMS + MAX_PALETTE_ROOMS], Room_ambience_g[MAX_ROOMS + MAX_PALETTE_ROOMS],
    Room_ambience_b[MAX_ROOMS + MAX_PALETTE_ROOMS];

uint8_t *TerrainLightSpeedup[MAX_SATELLITES];

int LightSpacing = LIGHTMAP_SPACING;
int BestFit = 0;
int Square_surfaces = 0;
int Lightmaps_for_rad = 0;

// Ambient values for terrain
float Ambient_red = 0.0f, Ambient_green = 0.0f, Ambient_blue = 0.0f;

void DoTerrainDynamicTable();

uint8_t *Lightmap_mask = NULL;
static uint8_t *Lmi_spoken_for;
int Squeeze_lightmap_handle = -1;

int FindEmptyMaskSpot(int w, int h, int *dest_x, int *dest_y) {
  int cur_x = 0, cur_y = 0;
  int i, t;

  for (cur_y = 0; cur_y < 128; cur_y++) {
    if (cur_y + h > 128)
      return 0;
    for (cur_x = 0; cur_x < 128; cur_x++) {
      if (cur_x + w > 128)
        continue;

      int hit_mask = 0;
      for (i = 0; i < h && !hit_mask; i++) {
        for (t = 0; t < w && !hit_mask; t++) {
          if (Lightmap_mask[((cur_y + i) * 128) + cur_x + t])
            hit_mask = 1;
        }
      }
      if (hit_mask == 0) {
        // Hurray! We found an empty spot
        *dest_x = cur_x;
        *dest_y = cur_y;
        return 1;
      }
    }
  }

  return 0;
}

void CopySqueezeBodyAndEdges(uint16_t *dest_data, uint16_t *src_data, int w, int h, int dest_x, int dest_y) {
  int i, t;

  ASSERT(w + dest_x <= 126);
  ASSERT(h + dest_y <= 126);
  ASSERT(dest_x >= 0 && dest_y >= 0);

  // First copy the main body
  for (i = 0; i < h; i++) {
    for (t = 0; t < w; t++) {
      dest_data[((dest_y + 1 + i) * 128) + (dest_x + 1 + t)] = src_data[(w * i) + t];
      Lightmap_mask[((dest_y + i + 1) * 128) + dest_x + 1 + t] = 1;
    }
  }

  // Now copy the edges
  // Left edge
  for (i = 0; i < h; i++) {
    dest_data[((dest_y + 1 + i) * 128) + (dest_x + 0)] = src_data[(w * i) + 0];
    Lightmap_mask[((dest_y + i + 1) * 128) + dest_x + 0] = 1;
  }
  // Right edge
  for (i = 0; i < h; i++) {
    dest_data[((dest_y + 1 + i) * 128) + (dest_x + w + 1)] = src_data[(w * i) + (w - 1)];
    Lightmap_mask[((dest_y + i + 1) * 128) + dest_x + w + 1] = 1;
  }
  // Top edge
  for (i = 0; i < w; i++) {
    dest_data[(dest_y * 128) + (dest_x + i + 1)] = src_data[i];
    Lightmap_mask[(dest_y * 128) + (dest_x + i + 1)] = 1;
  }
  // Bottom edge
  for (i = 0; i < w; i++) {
    dest_data[((dest_y + 1 + h) * 128) + (dest_x + i + 1)] = src_data[(w * (h - 1)) + i];
    Lightmap_mask[((dest_y + 1 + h) * 128) + (dest_x + i + 1)] = 1;
  }

  // Now copy the corners
  // Upper left
  dest_data[(dest_y * 128) + (dest_x)] = src_data[0];
  Lightmap_mask[(dest_y * 128) + (dest_x)] = 1;
  // Upper right
  dest_data[(dest_y * 128) + (dest_x + w + 1)] = src_data[w - 1];
  Lightmap_mask[(dest_y * 128) + (dest_x + w + 1)] = 1;
  // Lower left
  dest_data[((dest_y + h + 1) * 128) + (dest_x)] = src_data[w * (h - 1)];
  Lightmap_mask[((dest_y + h + 1) * 128) + (dest_x)] = 1;
  // Lower right
  dest_data[((dest_y + h + 1) * 128) + (dest_x + w + 1)] = src_data[(w * (h - 1)) + (w - 1)];
  Lightmap_mask[((dest_y + h + 1) * 128) + (dest_x + w + 1)] = 1;
}

void CopySqueezeDataForRooms(int roomnum, int facenum, uint16_t *dest_data, int dest_x, int dest_y) {
  room *rp = &Rooms[roomnum];
  lightmap_info *lmi_ptr = &LightmapInfo[rp->faces[facenum].lmi_handle];
  uint16_t *src_data = (uint16_t *)lm_data(lmi_ptr->lm_handle);

  int w = lmi_ptr->width;
  int h = lmi_ptr->height;

  // Copy over the actual lightmap data
  CopySqueezeBodyAndEdges(dest_data, src_data, w, h, dest_x, dest_y);

  // Now alter all face uvs that have this lightmap info
  float dest_u = (float)(dest_x + 1) / 128.0;
  float dest_v = (float)(dest_y + 1) / 128.0;

  float u_scalar = (float)w / 128.0;
  float v_scalar = (float)h / 128.0;

  for (int t = roomnum; t <= Highest_room_index; t++) {
    room *this_rp = &Rooms[t];
    if (!this_rp->used)
      continue;

    for (int j = 0; j < this_rp->num_faces; j++) {

      if (!(this_rp->faces[j].flags & FF_LIGHTMAP))
        continue;

      if (this_rp->faces[j].lmi_handle == BAD_LMI_INDEX)
        continue;

      if (Lmi_spoken_for[this_rp->faces[j].lmi_handle])
        continue;
      if (this_rp->faces[j].lmi_handle == rp->faces[facenum].lmi_handle) {
        lmi_ptr->x1 = dest_x + 1;
        lmi_ptr->y1 = dest_y + 1;

        // We have to alter our uvs for this face to reflect our change
        face *fp = &this_rp->faces[j];
        for (int k = 0; k < fp->num_verts; k++) {
          fp->face_uvls[k].u2 *= u_scalar;
          fp->face_uvls[k].u2 += dest_u;

          fp->face_uvls[k].v2 *= v_scalar;
          fp->face_uvls[k].v2 += dest_v;
        }
      }
    }
  }

  ASSERT(rp->faces[facenum].lmi_handle != BAD_LMI_INDEX);

  Lmi_spoken_for[rp->faces[facenum].lmi_handle] = 1;

  // Free our old lightmap
  GameLightmaps[lmi_ptr->lm_handle].used = 1;
  lm_FreeLightmap(lmi_ptr->lm_handle);

  lmi_ptr->lm_handle = Squeeze_lightmap_handle;
  GameLightmaps[Squeeze_lightmap_handle].used++;
}

void CopySqueezeDataForObject(object *obj, int subnum, int facenum, uint16_t *dest_data, int dest_x, int dest_y) {
  lightmap_object_face *fp = &obj->lm_object.lightmap_faces[subnum][facenum];
  lightmap_info *lmi_ptr = &LightmapInfo[fp->lmi_handle];
  uint16_t *src_data = (uint16_t *)lm_data(lmi_ptr->lm_handle);
  int t, k;

  int w = lmi_ptr->width;
  int h = lmi_ptr->height;

  // Copy over the actual lightmap data
  CopySqueezeBodyAndEdges(dest_data, src_data, w, h, dest_x, dest_y);

  // Now alter all face uvs that have this lightmap info
  float dest_u = (float)(dest_x + 1) / 128.0;
  float dest_v = (float)(dest_y + 1) / 128.0;

  float u_scalar = (float)w / 128.0;
  float v_scalar = (float)h / 128.0;

  for (int objnum = obj - Objects; objnum != -1; objnum = Objects[objnum].next) {
    object *this_obj = &Objects[objnum];

    if (this_obj->lighting_render_type != LRT_LIGHTMAPS)
      continue;
    if (!this_obj->lm_object.used)
      continue;

    for (t = 0; t < this_obj->lm_object.num_models; t++) {
      if (IsNonRenderableSubmodel(&Poly_models[this_obj->rtype.pobj_info.model_num], t))
        continue;

      for (k = 0; k < this_obj->lm_object.num_faces[t]; k++) {
        lightmap_object_face *this_fp = &this_obj->lm_object.lightmap_faces[t][k];
        int lmi_handle = this_fp->lmi_handle;
        if (lmi_handle == BAD_LMI_INDEX)
          continue;
        if (Lmi_spoken_for[lmi_handle])
          continue;

        if (lmi_handle == fp->lmi_handle) {
          lmi_ptr->x1 = dest_x + 1;
          lmi_ptr->y1 = dest_y + 1;

          // We have to alter our uvs for this face to reflect our change
          for (int j = 0; j < this_fp->num_verts; j++) {
            this_fp->u2[j] *= u_scalar;
            this_fp->u2[j] += dest_u;

            this_fp->v2[j] *= v_scalar;
            this_fp->v2[j] += dest_v;
          }
        }
      }
    }
  }

  ASSERT(fp->lmi_handle != BAD_LMI_INDEX);
  Lmi_spoken_for[fp->lmi_handle] = 1;

  // Free our old lightmap
  GameLightmaps[lmi_ptr->lm_handle].used = 1;
  lm_FreeLightmap(lmi_ptr->lm_handle);

  lmi_ptr->lm_handle = Squeeze_lightmap_handle;
  GameLightmaps[Squeeze_lightmap_handle].used++;
}

// Simply clears flags for combine portals
void ClearCombinePortals(int terrain) {
  for (int i = 0; i <= Highest_room_index; i++) {
    room *rp = &Rooms[i];

    if (!rp->used)
      continue;

    if (terrain && !(rp->flags & RF_EXTERNAL))
      continue;

    if (!terrain && (rp->flags & RF_EXTERNAL))
      continue;

    for (int t = 0; t < rp->num_portals; t++) {
      portal *portal_a = &rp->portals[t];
      portal_a->flags &= ~PF_COMBINED;
    }
  }
}

// For rendering...combines all portals if they are on the same plane
void CheckCombinePortals(int terrain) {
  ClearCombinePortals(terrain);

  int combine_count = 0;
  mprintf(0, "Combining portals...");

  for (int i = 0; i <= Highest_room_index; i++) {
    room *rp = &Rooms[i];

    if (!rp->used)
      continue;

    if (terrain && !(rp->flags & RF_EXTERNAL))
      continue;

    if (!terrain && (rp->flags & RF_EXTERNAL))
      continue;

    for (int t = 0; t < rp->num_portals; t++) {
      portal *portal_a = &rp->portals[t];
      face *face_a = &rp->faces[portal_a->portal_face];

      if (portal_a->flags & PF_COMBINED)
        continue;

      // Don't combine if face is breakable
      if (GameTextures[face_a->tmap].flags & TF_BREAKABLE)
        continue;

      float dist_a = vm_DotProduct(&rp->verts[face_a->face_verts[0]], &face_a->normal);

      for (int k = 0; k < rp->num_portals; k++) {
        portal *portal_b = &rp->portals[k];
        face *face_b = &rp->faces[portal_b->portal_face];

        if (portal_b->flags & PF_COMBINED)
          continue;

        // Don't combine if one portal is render-faces and the other is not
        if ((portal_a->flags & PF_RENDER_FACES) != (portal_b->flags & PF_RENDER_FACES))
          continue;

        // Don't combine if face is breakable
        if (GameTextures[face_b->tmap].flags & TF_BREAKABLE)
          continue;

        if (t == k)
          continue;

        // Check to see if the portals connect to the same room
        if (portal_a->croom != portal_b->croom)
          continue;

        /*// Check to see if they share a normal
        float dp=vm_DotProduct (&face_a->normal,&face_b->normal);

        if (dp < .95f)
                continue;


        // Check to see if the distances are same
        float dist_b=vm_DotProduct (&rp->verts[face_b->face_verts[0]],&face_b->normal);

        if (fabs(dist_b-dist_a)>.5)
                continue;

        int match=0;
        // Test to see if any points at all can touch
        for (int x=0; x<face_a->num_verts && !match; x++ )
        {
                for (int y=0; y<face_b->num_verts && !match; y++ )
                {
                        if (PointsAreSame(&rp->verts[face_a->face_verts[x]],&rp->verts[face_b->face_verts[y]]))
                                match=1;
                }
        }

        if (match==0)
                continue;

        //Check to see if the portals connect to the same room
        if (portal_a->croom != portal_b->croom)
                continue;

        // Hurray! These portals can be combined*/
        portal_a->flags |= PF_COMBINED;
        portal_b->flags |= PF_COMBINED;

        portal_a->combine_master = t;
        portal_b->combine_master = t;
        combine_count++;
      }
    }
  }

  mprintf(0, "%d portals combined.\n", combine_count);
}

// Squeezes all the lightmaps down into as few 128x128s as possible
void SqueezeLightmaps(int external, int target_roomnum) {
  int i, t, k;
  mprintf(0, "Squeezing %s lightmaps, please wait...\n", external ? "external" : "internal");

  Lmi_spoken_for = (uint8_t *)mem_malloc(MAX_LIGHTMAP_INFOS);
  Lightmap_mask = (uint8_t *)mem_malloc(128 * 128);
  Squeeze_lightmap_handle = -1;

  ASSERT(Lightmap_mask);
  ASSERT(Lmi_spoken_for);
  memset(Lmi_spoken_for, 0, MAX_LIGHTMAP_INFOS);
  memset(Lightmap_mask, 0, 128 * 128);

  // Go through all the rooms and sqeeze them one by one
  for (i = 0; i <= Highest_room_index; i++) {
    room *rp = &Rooms[i];
    if (!rp->used)
      continue;
    if (rp->flags & RF_NO_LIGHT)
      continue;

    if (external) {
      if (!(rp->flags & RF_EXTERNAL))
        continue;
    } else {
      if ((rp->flags & RF_EXTERNAL))
        continue;

      if (target_roomnum != -1 && target_roomnum != i)
        continue;
    }

    // Go through each face
    for (t = 0; t < rp->num_faces; t++) {
      face *fp = &rp->faces[t];
      int lmi_handle = fp->lmi_handle;

      if (!(fp->flags & FF_LIGHTMAP))
        continue;

      if (lmi_handle == BAD_LMI_INDEX)
        continue;
      if (Lmi_spoken_for[lmi_handle])
        continue;

      lightmap_info *lmi = &LightmapInfo[lmi_handle];

      int dest_x, dest_y;
      int src_w = lmi->width;
      int src_h = lmi->height;

      if (Squeeze_lightmap_handle == -1) {
        memset(Lightmap_mask, 0, 128 * 128);
        Squeeze_lightmap_handle = lm_AllocLightmap(128, 128);
        uint16_t *fill_data = (uint16_t *)lm_data(Squeeze_lightmap_handle);
        memset(fill_data, 0, 128 * 128 * 2);
      }

      if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) {
        // Cool, found a spot
        CopySqueezeDataForRooms(i, t, lm_data(Squeeze_lightmap_handle), dest_x, dest_y);
      } else {
        // Can't find an empty slot, so search through all the other remaining faces
        for (k = t; k < rp->num_faces; k++) {
          face *fp = &rp->faces[k];
          int lmi_handle = fp->lmi_handle;

          if (!(fp->flags & FF_LIGHTMAP))
            continue;
          if (lmi_handle == BAD_LMI_INDEX)
            continue;
          if (Lmi_spoken_for[lmi_handle])
            continue;

          lightmap_info *lmi = &LightmapInfo[lmi_handle];

          int src_w = lmi->width;
          int src_h = lmi->height;

          if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) {
            CopySqueezeDataForRooms(i, k, lm_data(Squeeze_lightmap_handle), dest_x, dest_y);
          }
        }

        // Now, allocate a new lightmap and start over
        ASSERT(Squeeze_lightmap_handle != -1);
        ASSERT(GameLightmaps[Squeeze_lightmap_handle].used != 1);
        GameLightmaps[Squeeze_lightmap_handle].used--;

        memset(Lightmap_mask, 0, 128 * 128);
        Squeeze_lightmap_handle = lm_AllocLightmap(128, 128);
        uint16_t *fill_data = (uint16_t *)lm_data(Squeeze_lightmap_handle);
        memset(fill_data, 0, 128 * 128 * 2);

        ASSERT(Lmi_spoken_for[lmi_handle] == 0);

        if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) {
          CopySqueezeDataForRooms(i, t, lm_data(Squeeze_lightmap_handle), dest_x, dest_y);
        } else {
          Int3(); // Get Jason, how did this happen????
        }
      }
    }

    // Now search through all the objects in this room
    for (k = rp->objects; k != -1; k = Objects[k].next) {
      object *obj = &Objects[k];
      if (obj->lighting_render_type != LRT_LIGHTMAPS)
        continue;
      if (!obj->lm_object.used)
        continue;

      for (t = 0; t < obj->lm_object.num_models; t++) {
        if (IsNonRenderableSubmodel(&Poly_models[obj->rtype.pobj_info.model_num], t))
          continue;

        for (int j = 0; j < obj->lm_object.num_faces[t]; j++) {
          lightmap_object_face *fp = &obj->lm_object.lightmap_faces[t][j];
          int lmi_handle = fp->lmi_handle;
          if (lmi_handle == BAD_LMI_INDEX)
            continue;
          if (Lmi_spoken_for[lmi_handle])
            continue;

          lightmap_info *lmi = &LightmapInfo[lmi_handle];

          int dest_x, dest_y;
          int src_w = lmi->width;
          int src_h = lmi->height;

          if (Squeeze_lightmap_handle == -1) {
            memset(Lightmap_mask, 0, 128 * 128);
            Squeeze_lightmap_handle = lm_AllocLightmap(128, 128);
            uint16_t *fill_data = (uint16_t *)lm_data(Squeeze_lightmap_handle);
            memset(fill_data, 0, 128 * 128 * 2);
          }

          if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) {
            // Cool, found a spot
            CopySqueezeDataForObject(obj, t, j, lm_data(Squeeze_lightmap_handle), dest_x, dest_y);
          } else {
            // Can't find an empty slot, so search through all the other remaining faces
            for (int a = rp->objects; a != -1; a = Objects[a].next) {
              object *obj = &Objects[a];
              if (obj->lighting_render_type != LRT_LIGHTMAPS)
                continue;
              if (!obj->lm_object.used)
                continue;

              for (int b = 0; b < obj->lm_object.num_models; b++) {
                if (IsNonRenderableSubmodel(&Poly_models[obj->rtype.pobj_info.model_num], b))
                  continue;

                for (int c = 0; c < obj->lm_object.num_faces[b]; c++) {
                  lightmap_object_face *fp = &obj->lm_object.lightmap_faces[b][c];
                  int lmi_handle = fp->lmi_handle;
                  if (lmi_handle == BAD_LMI_INDEX)
                    continue;
                  if (Lmi_spoken_for[lmi_handle])
                    continue;

                  lightmap_info *lmi = &LightmapInfo[lmi_handle];

                  int src_w = lmi->width;
                  int src_h = lmi->height;

                  if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) {
                    CopySqueezeDataForObject(obj, b, c, lm_data(Squeeze_lightmap_handle), dest_x, dest_y);
                  }
                }
              }
            }

            // Now, allocate a new lightmap and start over
            ASSERT(Squeeze_lightmap_handle != -1);
            ASSERT(GameLightmaps[Squeeze_lightmap_handle].used != 1);
            GameLightmaps[Squeeze_lightmap_handle].used--;

            memset(Lightmap_mask, 0, 128 * 128);
            Squeeze_lightmap_handle = lm_AllocLightmap(128, 128);
            uint16_t *fill_data = (uint16_t *)lm_data(Squeeze_lightmap_handle);
            memset(fill_data, 0, 128 * 128 * 2);

            ASSERT(Lmi_spoken_for[lmi_handle] == 0);

            if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) {
              CopySqueezeDataForObject(obj, t, j, lm_data(Squeeze_lightmap_handle), dest_x, dest_y);
            } else {
              Int3(); // Get Jason, how did this happen????
            }
          }
        }
      }
    }
  }

  if (Squeeze_lightmap_handle != -1) {
    ASSERT(GameLightmaps[Squeeze_lightmap_handle].used != 1);
    GameLightmaps[Squeeze_lightmap_handle].used--;
  }

  // Squeeze all terrain object lightmaps now
  if (external) {

    Squeeze_lightmap_handle = -1;
    for (i = 0; i <= Highest_object_index; i++) {
      object *obj = &Objects[i];

      if (obj->type == OBJ_ROOM)
        continue;
      if (obj->lighting_render_type != LRT_LIGHTMAPS)
        continue;
      if (!obj->lm_object.used)
        continue;
      if (!OBJECT_OUTSIDE(obj))
        continue;

      for (t = 0; t < obj->lm_object.num_models; t++) {
        if (IsNonRenderableSubmodel(&Poly_models[obj->rtype.pobj_info.model_num], t))
          continue;
        for (k = 0; k < obj->lm_object.num_faces[t]; k++) {
          lightmap_object_face *fp = &obj->lm_object.lightmap_faces[t][k];
          int lmi_handle = fp->lmi_handle;
          if (lmi_handle == BAD_LMI_INDEX)
            continue;
          if (Lmi_spoken_for[lmi_handle])
            continue;

          lightmap_info *lmi = &LightmapInfo[lmi_handle];

          int dest_x, dest_y;
          int src_w = lmi->width;
          int src_h = lmi->height;

          if (Squeeze_lightmap_handle == -1) {
            memset(Lightmap_mask, 0, 128 * 128);
            Squeeze_lightmap_handle = lm_AllocLightmap(128, 128);
            uint16_t *fill_data = (uint16_t *)lm_data(Squeeze_lightmap_handle);
            memset(fill_data, 0, 128 * 128 * 2);
          }

          if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) {
            // Cool, found a spot
            CopySqueezeDataForObject(obj, t, k, lm_data(Squeeze_lightmap_handle), dest_x, dest_y);
          } else {
            // Search through the remaining objects on the terrain
            for (int a = i; a <= Highest_object_index; a++) {
              object *obj = &Objects[a];

              if (!obj->lm_object.used)
                continue;
              if (!OBJECT_OUTSIDE(obj))
                continue;

              for (int b = 0; b < obj->lm_object.num_models; b++) {
                if (IsNonRenderableSubmodel(&Poly_models[obj->rtype.pobj_info.model_num], b))
                  continue;
                for (int c = 0; c < obj->lm_object.num_faces[b]; c++) {
                  lightmap_object_face *fp = &obj->lm_object.lightmap_faces[b][c];
                  int lmi_handle = fp->lmi_handle;
                  if (lmi_handle == BAD_LMI_INDEX)
                    continue;
                  if (Lmi_spoken_for[lmi_handle])
                    continue;

                  lightmap_info *lmi = &LightmapInfo[lmi_handle];

                  int src_w = lmi->width;
                  int src_h = lmi->height;

                  if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) {
                    CopySqueezeDataForObject(obj, b, c, lm_data(Squeeze_lightmap_handle), dest_x, dest_y);
                  }
                }
              }
            }

            // Now, allocate a new lightmap and start over
            ASSERT(Squeeze_lightmap_handle != -1);
            ASSERT(GameLightmaps[Squeeze_lightmap_handle].used != 1);
            GameLightmaps[Squeeze_lightmap_handle].used--;

            memset(Lightmap_mask, 0, 128 * 128);
            Squeeze_lightmap_handle = lm_AllocLightmap(128, 128);
            uint16_t *fill_data = (uint16_t *)lm_data(Squeeze_lightmap_handle);
            memset(fill_data, 0, 128 * 128 * 2);

            ASSERT(Lmi_spoken_for[lmi_handle] == 0);

            if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) {
              CopySqueezeDataForObject(obj, t, k, lm_data(Squeeze_lightmap_handle), dest_x, dest_y);
            } else {
              Int3(); // Get Jason, how did this happen????
            }
          }
        }
      }
    }

    if (Squeeze_lightmap_handle != -1) {
      ASSERT(GameLightmaps[Squeeze_lightmap_handle].used != 1);
      GameLightmaps[Squeeze_lightmap_handle].used--;
    }
  }

  mem_free(Lightmap_mask);
  mem_free(Lmi_spoken_for);
  mprintf(0, "Done squeezing lightmaps.\n");
}

void ComputeSurfaceRes(rad_surface *surf, room *rp, int facenum) {
  int i;
  float left = 1.1f, right = -1, top = 1.1f, bottom = -1;
  face *fp = &rp->faces[facenum];
  int lw = lmi_w(fp->lmi_handle);
  int lh = lmi_h(fp->lmi_handle);

  for (i = 0; i < fp->num_verts; i++) {
    if (fp->face_uvls[i].u2 < left)
      left = fp->face_uvls[i].u2;
    if (fp->face_uvls[i].u2 > right)
      right = fp->face_uvls[i].u2;
    if (fp->face_uvls[i].v2 < top)
      top = fp->face_uvls[i].v2;
    if (fp->face_uvls[i].v2 > bottom)
      bottom = fp->face_uvls[i].v2;
  }

  float left_result = (left * lw) + .0001;
  float right_result = (right * lw) + .0001;
  float top_result = (top * lh) + .0001;
  float bottom_result = (bottom * lh) + .0001;

  surf->x1 = floor(left_result);
  surf->x2 = floor(right_result);

  surf->y1 = floor(top_result);
  surf->y2 = floor(bottom_result);

  surf->xresolution = (surf->x2 - surf->x1);
  surf->yresolution = (surf->y2 - surf->y1);

  // Adjust for a accuracy errors
  if (((right_result) - (float)surf->x2) > .005)
    surf->xresolution++;

  if (((bottom_result) - (float)surf->y2) > .005)
    surf->yresolution++;

  if (((top_result) - (float)surf->y1) > .99)
    surf->y1++;

  if (((left_result) - (float)surf->x1) > .99)
    surf->x1++;

  ASSERT((surf->x1 + surf->xresolution) <= lw);
  ASSERT((surf->y1 + surf->yresolution) <= lh);
}

// Take the computed volume spectra of a room and save it in the room struct
void AssignVolumeSpectraToRoom(int roomnum) {
  ASSERT(Rooms[roomnum].used);
  ASSERT(!(Rooms[roomnum].flags & RF_EXTERNAL));
  ASSERT(!(Rooms[roomnum].flags & RF_NO_LIGHT));

  room *rp = &Rooms[roomnum];

  int i, t, j;
  int w = rp->volume_width;
  int h = rp->volume_height;
  int d = rp->volume_depth;

  for (i = 0; i < d; i++) {
    for (t = 0; t < h; t++) {
      for (j = 0; j < w; j++) {
        spectra *this_spectra = &Volume_elements[roomnum][(i * w * h) + (t * w) + j].color;

        if (this_spectra->r < 0) {
          Int3(); // Shouldn't hit this
          rp->volume_lights[(i * w * h) + (t * w) + j] = INVISIBLE_VOLUME_ELEMENT;
        } else {
          float rmax = GetMaxColor(this_spectra);

          if (rmax > 1.0 && rmax > 0.0) {
            this_spectra->r /= rmax;
            this_spectra->g /= rmax;
            this_spectra->b /= rmax;
          }

          int r = (this_spectra->r * 7);
          r <<= 5;
          int g = (this_spectra->g * 7);
          g <<= 2;
          int b = (this_spectra->b * 3);

          uint8_t volume_color = r | g | b;

          if (!UseVolumeLights)
            rp->volume_lights[(i * w * h) + (t * w) + j] = 255;
          else
            rp->volume_lights[(i * w * h) + (t * w) + j] = volume_color;
        }
      }
    }
  }
}

// Returns 0 if there are bad faces in this level
int CheckForBadFaces(int roomnum) {
  int i, t;

  for (i = 0; i <= Highest_room_index; i++) {
    if (roomnum != -1 && i != roomnum)
      continue;

    room *rp = &Rooms[i];
    if (!rp->used)
      continue;

    for (t = 0; t < rp->num_faces; t++) {
      if (rp->faces[t].num_verts < 3)
        return 0; // Bad face detected!
    }
  }

  return 1;
}

// Calculates radiosity and sets lightmaps for indoor faces only
void DoRadiosityForRooms() {
  int i, t, j;
  int facecount = 0;
  int surface_index = 0;
  int max_index;
  int save_after_bsp = 0;

  if (!CheckForBadFaces(-1)) {
    OutrageMessageBox("You have bad faces in your level.  Please do a Verify Level.");
    return;
  }

  MakeBOAVisTable(1);

  if (UseBSP) {
    // if ((MessageBox(NULL,"Do you wish to save the level after BSP construction?","Question",MB_YESNO))==IDYES)
    // save_after_bsp=1;
  }

  BuildBSPTree();

  if (save_after_bsp) {
    char filename[_MAX_PATH];
    ddio_MakePath(filename, Base_directory, "BSPSave.D3L", NULL);

    // Save the level to
    SaveLevel(filename);
  }

  mprintf(0, "Setting up...\n");

  Lightmaps_for_rad = 0;

  if (UseVolumeLights)
    Do_volume_lighting = 1;

  ClearAllVolumeLights();
  ClearAllRoomLightmaps(0);
  ClearAllObjectLightmaps(0);
  ComputeAllRoomLightmapUVs(0);

  // Figure out memory for volume lights
  int vw, vh, vd;
  for (int roomnum = 0; roomnum < MAX_ROOMS; roomnum++) {
    Volume_elements[roomnum] = NULL;

    if (Rooms[roomnum].used && !(Rooms[roomnum].flags & RF_EXTERNAL) && !(Rooms[roomnum].flags & RF_NO_LIGHT)) {
      int num_bytes = GetVolumeSizeOfRoom(&Rooms[roomnum], &vw, &vh, &vd);

      Rooms[roomnum].volume_width = vw;
      Rooms[roomnum].volume_height = vh;
      Rooms[roomnum].volume_depth = vd;

      Rooms[roomnum].volume_lights = (uint8_t *)mem_malloc(vw * vh * vd);
      ASSERT(Rooms[roomnum].volume_lights);

      Volume_elements[roomnum] = (volume_element *)mem_malloc((vw * vh * vd) * sizeof(volume_element));
      ASSERT(Volume_elements[roomnum]);

      // Now go through and find all the valid spectra points
      float cur_z = Rooms[roomnum].min_xyz.z + .1;

      for (i = 0; i < vd; i++, cur_z += VOLUME_SPACING) {
        float cur_y = Rooms[roomnum].min_xyz.y + .1;
        for (t = 0; t < vh; t++, cur_y += VOLUME_SPACING) {
          float cur_x = Rooms[roomnum].min_xyz.x + .1;
          for (j = 0; j < vw; j++, cur_x += VOLUME_SPACING) {
            vector dest_vec;
            dest_vec.x = cur_x;
            dest_vec.y = cur_y;
            dest_vec.z = cur_z;

            Volume_elements[roomnum][(i * vw * vh) + (t * vw) + j].pos = dest_vec;

            if (FindPointRoom(&dest_vec) != roomnum) {
              Volume_elements[roomnum][(i * vw * vh) + (t * vw) + j].color.r = 0;
              Volume_elements[roomnum][(i * vw * vh) + (t * vw) + j].flags = VEF_REVERSE_SHOOT;
            } else {
              Volume_elements[roomnum][(i * vw * vh) + (t * vw) + j].color.r = 0;
              Volume_elements[roomnum][(i * vw * vh) + (t * vw) + j].flags = 0;
            }
          }
        }
      }
    }
  }

  // First count how many faces we'll need
  for (i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used && !(Rooms[i].flags & RF_EXTERNAL) && !(Rooms[i].flags & RF_NO_LIGHT)) {
      facecount += Rooms[i].num_faces;
    }
  }

  // Setup specular lighting
  Calculate_specular_lighting = 1;
  SetupSpecularLighting(0);

  // Do objects
  facecount += GetTotalObjectFaces(0);
  // Do satellites
  facecount += Terrain_sky.num_satellites;

  // Allocate enough memory to hold all surfaces

  Light_surfaces = (rad_surface *)mem_malloc(facecount * sizeof(rad_surface));
  ASSERT(Light_surfaces != NULL);

  // Set initial surface properties
  max_index = surface_index = 0;

  for (i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used) {
      if ((Rooms[i].flags & RF_EXTERNAL) || (Rooms[i].flags & RF_NO_LIGHT))
        continue;

      for (t = 0; t < Rooms[i].num_faces; t++, surface_index++, max_index++) {

        ComputeSurfaceRes(&Light_surfaces[surface_index], &Rooms[i], t);

        if (Rooms[i].faces[t].num_verts) {
          Light_surfaces[surface_index].verts = (vector *)mem_malloc(Rooms[i].faces[t].num_verts * sizeof(vector));
          ASSERT(Light_surfaces[surface_index].verts != NULL);
        } else {
          Light_surfaces[surface_index].verts = NULL;
          mprintf(0, "Room=%d Face %d has no verts!\n", i, t);
        }

        if (Light_surfaces[surface_index].xresolution * Light_surfaces[surface_index].yresolution) {
          Light_surfaces[surface_index].elements =
              (rad_element *)mem_malloc(Light_surfaces[surface_index].xresolution *
                                        Light_surfaces[surface_index].yresolution * sizeof(rad_element));
          ASSERT(Light_surfaces[surface_index].elements != NULL);
        } else {
          Light_surfaces[surface_index].elements = NULL;
          mprintf(0, "Room=%d Face %d is slivered!\n", i, t);
        }

        Light_surfaces[surface_index].flags = 0;

        if (Rooms[i].faces[t].portal_num != -1 &&
            (((Rooms[i].portals[Rooms[i].faces[t].portal_num].flags & PF_RENDER_FACES) == 0) ||
             ((Rooms[i].portals[Rooms[i].faces[t].portal_num].flags & PF_RENDER_FACES) &&
              (GameTextures[Rooms[i].faces[t].tmap].flags & TF_TMAP2))))

        {
          Light_surfaces[surface_index].surface_type = ST_PORTAL;
          Light_surfaces[surface_index].emittance.r = 0;
          Light_surfaces[surface_index].emittance.g = 0;
          Light_surfaces[surface_index].emittance.b = 0;
        } else {
          if (Rooms[i].faces[t].light_multiple > 200)
            Rooms[i].faces[t].light_multiple = 4;

          float mul = ((float)Rooms[i].faces[t].light_multiple) / 4.0;
          mul *= GlobalMultiplier * Room_multiplier[i];

          Light_surfaces[surface_index].emittance.r = (float)GameTextures[Rooms[i].faces[t].tmap].r * mul;
          Light_surfaces[surface_index].emittance.g = (float)GameTextures[Rooms[i].faces[t].tmap].g * mul;
          Light_surfaces[surface_index].emittance.b = (float)GameTextures[Rooms[i].faces[t].tmap].b * mul;
          Light_surfaces[surface_index].surface_type = ST_ROOM;

          if ((GetMaxColor(&Light_surfaces[surface_index].emittance)) > .005)
            Light_surfaces[surface_index].flags |= SF_LIGHTSOURCE;
        }

        Light_surfaces[surface_index].normal = LightmapInfo[Rooms[i].faces[t].lmi_handle].normal;
        Light_surfaces[surface_index].roomnum = i;
        Light_surfaces[surface_index].facenum = t;

        if (Rooms[i].flags & RF_TOUCHES_TERRAIN)
          Light_surfaces[surface_index].flags |= SF_TOUCHES_TERRAIN;

        for (int k = 0; k < Rooms[i].num_portals; k++) {
          if (Rooms[i].portals[k].croom == -1 || (Rooms[Rooms[i].portals[k].croom].flags & RF_EXTERNAL))
            Light_surfaces[surface_index].flags |= SF_TOUCHES_TERRAIN;
        }

        Light_surfaces[surface_index].reflectivity = GameTextures[Rooms[i].faces[t].tmap].reflectivity;

        // Set the vertices for each element
        BuildElementListForRoomFace(i, t, &Light_surfaces[surface_index]);

        int xres = Light_surfaces[surface_index].xresolution;
        int yres = Light_surfaces[surface_index].yresolution;
      }
    }
  }

  // Setup satellites
  for (i = 0; i < Terrain_sky.num_satellites; i++, surface_index++) {
    Light_surfaces[surface_index].verts = (vector *)mem_malloc(sizeof(vector) * 3);
    ASSERT(Light_surfaces[surface_index].verts != NULL);

    Light_surfaces[surface_index].elements = (rad_element *)mem_malloc(sizeof(rad_element));
    ASSERT(Light_surfaces[surface_index].elements != NULL);

    Light_surfaces[surface_index].elements[0].verts = (vector *)mem_malloc(sizeof(vector) * 3);
    ASSERT(Light_surfaces[surface_index].elements[0].verts);

    Light_surfaces[surface_index].surface_type = ST_SATELLITE;
    Light_surfaces[surface_index].emittance.r = Terrain_sky.satellite_r[i];
    Light_surfaces[surface_index].emittance.g = Terrain_sky.satellite_g[i];
    Light_surfaces[surface_index].emittance.b = Terrain_sky.satellite_b[i];

    Light_surfaces[surface_index].roomnum = i;
    Light_surfaces[surface_index].facenum = 0;

    Light_surfaces[surface_index].reflectivity = 0;
    Light_surfaces[surface_index].xresolution = 1;
    Light_surfaces[surface_index].yresolution = 1;

    vm_MakeZero(&Light_surfaces[surface_index].normal);

    Light_surfaces[surface_index].verts[0] = Terrain_sky.satellite_vectors[i];
    Light_surfaces[surface_index].verts[1] = Terrain_sky.satellite_vectors[i];
    Light_surfaces[surface_index].verts[2] = Terrain_sky.satellite_vectors[i];
    Light_surfaces[surface_index].num_verts = 3;

    Light_surfaces[surface_index].elements[0].verts[0] = Terrain_sky.satellite_vectors[i];
    Light_surfaces[surface_index].elements[0].verts[1] = Terrain_sky.satellite_vectors[i];
    Light_surfaces[surface_index].elements[0].verts[2] = Terrain_sky.satellite_vectors[i];
    Light_surfaces[surface_index].elements[0].num_verts = 3;

    Light_surfaces[surface_index].elements[0].flags = 0;
  }

  // Setup Objects
  ComputeSurfacesForObjects(surface_index, 0);

  mprintf(0, "This radiosity run is using %d lightmaps.\n", Lightmaps_for_rad);
  mprintf(0, "Solving radiosity equation (press tilde key to stop)...\n");
  if (D3EditState.hemicube_radiosity)
    DoRadiosityRun(SM_HEMICUBE, Light_surfaces, facecount);
  else
    DoRadiosityRun(SM_RAYCAST, Light_surfaces, facecount);
  mprintf(0, "Done solving radiosity - cleaning up...\n");

  surface_index = 0;

  // Assign lightap properties
  for (i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used) {
      if ((Rooms[i].flags & RF_EXTERNAL) || (Rooms[i].flags & RF_NO_LIGHT))
        continue;

      for (t = 0; t < Rooms[i].num_faces; t++, surface_index++) {
        AssignRoomSurfaceToLightmap(i, t, &Light_surfaces[surface_index]);
      }

      AssignVolumeSpectraToRoom(i);
      mem_free(Volume_elements[i]);
    }
  }

  surface_index += Terrain_sky.num_satellites;

  AssignLightmapsToObjectSurfaces(surface_index, 0);

  BlurLightmapInfos(LMI_ROOM);
  BlurLightmapInfos(LMI_ROOM_OBJECT);

  ShadeLightmapInfoEdges(LMI_ROOM);
  ShadeLightmapInfoEdges(LMI_ROOM_OBJECT);

  // Free our memory

  for (i = 0; i < facecount; i++) {
    if (Light_surfaces[i].verts)
      mem_free(Light_surfaces[i].verts);
    for (t = 0; t < Light_surfaces[i].xresolution * Light_surfaces[i].yresolution; t++)
      if (Light_surfaces[i].elements[t].num_verts > 0)
        mem_free(Light_surfaces[i].elements[t].verts);

    if (Light_surfaces[i].elements)
      mem_free(Light_surfaces[i].elements);
    Light_surfaces[i].elements = NULL;
  }
  mem_free(Light_surfaces);
  Light_surfaces = NULL;
  Do_volume_lighting = 0;

  // Free specular lighting stuff
  CleanupSpecularLighting(0);
  Calculate_specular_lighting = 0;

  // Figure out combined portals
  CheckCombinePortals(0);

  // Finally, squeeze the lightmaps
  SqueezeLightmaps(0, -1);

  char filename[_MAX_PATH + 1];
  ddio_MakePath(filename, Base_directory, "LightSave.D3L", NULL);

  // Save the level to disk
  SaveLevel(filename);

  OutrageMessageBox("Mine radiosity complete!");
}

// Calculates radiosity and sets lightmaps for indoor faces only
void DoRadiosityForCurrentRoom(room *rp) {
  int t;
  int facecount = 0;
  int surface_index = 0;
  int max_index;

  if (!CheckForBadFaces(rp - Rooms)) {
    OutrageMessageBox("You have bad faces in your level.  Please do a Verify Level.");
    return;
  }

  mprintf(0, "Setting up...\n");

  ASSERT(rp != NULL);
  ASSERT(rp->used);

  if (rp->flags & RF_EXTERNAL) {
    OutrageMessageBox("You cannot run single room radiosity on external rooms!");
    return;
  }

  if (rp->flags & RF_NO_LIGHT) {
    OutrageMessageBox("You cannot run single room radiosity on a room marked not to light!");
    return;
  }

  // Build bsp tree
  BuildSingleBSPTree(rp - Rooms);

  ClearRoomLightmaps(rp - Rooms);
  for (t = 0; t <= Highest_object_index; t++) {
    if (Objects[t].type != OBJ_NONE && (Objects[t].roomnum == rp - Rooms))
      ClearObjectLightmaps(&Objects[t]);
  }

  // Build lightmap uvs
  for (t = 0; t < rp->num_faces; t++) {
    vector verts[MAX_VERTS_PER_FACE * 5];
    int room_list[2], face_list[2];
    for (int k = 0; k < rp->faces[t].num_verts; k++)
      verts[k] = rp->verts[rp->faces[t].face_verts[k]];

    room_list[0] = rp - Rooms;
    face_list[0] = t;

    BuildLightmapUVs(room_list, face_list, 1, verts, rp->faces[t].num_verts, 0);
  }

  // First count how many faces we'll need
  facecount += rp->num_faces;

  // Do objects
  facecount += GetTotalObjectFacesForSingleRoom(rp - Rooms);

  // Allocate enough memory to hold all surfaces

  Light_surfaces = (rad_surface *)mem_malloc(facecount * sizeof(rad_surface));
  ASSERT(Light_surfaces != NULL);

  // Set initial surface properties
  max_index = surface_index = 0;

  for (t = 0; t < rp->num_faces; t++, surface_index++, max_index++) {
    ComputeSurfaceRes(&Light_surfaces[surface_index], rp, t);

    if (rp->faces[t].num_verts) {
      Light_surfaces[surface_index].verts = (vector *)mem_malloc(rp->faces[t].num_verts * sizeof(vector));
      ASSERT(Light_surfaces[surface_index].verts != NULL);
    } else {
      Light_surfaces[surface_index].verts = NULL;
      mprintf(0, "Room=%d Face %d has no verts!\n", rp - Rooms, t);
    }

    if (Light_surfaces[surface_index].xresolution * Light_surfaces[surface_index].yresolution) {
      Light_surfaces[surface_index].elements = (rad_element *)mem_malloc(
          Light_surfaces[surface_index].xresolution * Light_surfaces[surface_index].yresolution * sizeof(rad_element));
      ASSERT(Light_surfaces[surface_index].elements != NULL);
    } else {
      Light_surfaces[surface_index].elements = NULL;
      mprintf(0, "Room=%d Face %d is slivered!\n", rp - Rooms, t);
    }

    if (rp->faces[t].portal_num != -1 && (((rp->portals[rp->faces[t].portal_num].flags & PF_RENDER_FACES) == 0) ||
                                          ((rp->portals[rp->faces[t].portal_num].flags & PF_RENDER_FACES) &&
                                           (GameTextures[rp->faces[t].tmap].flags & TF_TMAP2)))) {
      Light_surfaces[surface_index].surface_type = ST_PORTAL;
      Light_surfaces[surface_index].emittance.r = 0;
      Light_surfaces[surface_index].emittance.g = 0;
      Light_surfaces[surface_index].emittance.b = 0;
    } else {
      float mul = ((float)rp->faces[t].light_multiple) / 4.0;
      mul *= GlobalMultiplier * Room_multiplier[rp - Rooms];
      Light_surfaces[surface_index].emittance.r = (float)GameTextures[rp->faces[t].tmap].r * mul;
      Light_surfaces[surface_index].emittance.g = (float)GameTextures[rp->faces[t].tmap].g * mul;
      Light_surfaces[surface_index].emittance.b = (float)GameTextures[rp->faces[t].tmap].b * mul;
      Light_surfaces[surface_index].surface_type = ST_ROOM;
    }

    Light_surfaces[surface_index].normal = rp->faces[t].normal;
    Light_surfaces[surface_index].roomnum = ROOMNUM(rp);
    Light_surfaces[surface_index].facenum = t;

    Light_surfaces[surface_index].reflectivity = GameTextures[rp->faces[t].tmap].reflectivity;

    // Set the vertices for each element
    BuildElementListForRoomFace(rp - Rooms, t, &Light_surfaces[surface_index]);

    int xres = Light_surfaces[surface_index].xresolution;
    int yres = Light_surfaces[surface_index].yresolution;
  }

  // Setup Objects
  ComputeSurfacesForObjectsForSingleRoom(surface_index, rp - Rooms);

  mprintf(0, "Solving radiosity equation (press tilde key to stop)...\n");
  if (D3EditState.hemicube_radiosity)
    DoRadiosityRun(SM_HEMICUBE, Light_surfaces, facecount);
  else
    DoRadiosityRun(SM_RAYCAST, Light_surfaces, facecount);
  mprintf(0, "Done solving radiosity - cleaning up...\n");

  surface_index = 0;

  // Assign lightap properties
  for (t = 0; t < rp->num_faces; t++, surface_index++) {
    AssignRoomSurfaceToLightmap(rp - Rooms, t, &Light_surfaces[surface_index]);
  }

  AssignLightmapsToObjectSurfacesForSingleRoom(surface_index, rp - Rooms);

  // BlurLightmapInfos (LMI_ROOM);
  // BlurLightmapInfos (LMI_ROOM_OBJECT);

  // ShadeLightmapInfoEdges (LMI_ROOM);
  // ShadeLightmapInfoEdges (LMI_ROOM_OBJECT);

  // Free our memory

  for (int i = 0; i < facecount; i++) {
    mem_free(Light_surfaces[i].verts);
    for (t = 0; t < Light_surfaces[i].xresolution * Light_surfaces[i].yresolution; t++)
      if (Light_surfaces[i].elements[t].num_verts > 0)
        mem_free(Light_surfaces[i].elements[t].verts);

    mem_free(Light_surfaces[i].elements);
    Light_surfaces[i].elements = NULL;
  }
  mem_free(Light_surfaces);
  Light_surfaces = NULL;

  // Finally, squeeze the lightmaps
  SqueezeLightmaps(0, rp - Rooms);

  OutrageMessageBox("Room radiosity complete!");
}

// Allocates and sets a lightmap based on the surface elements given
void AssignRoomSurfaceToLightmap(int roomnum, int facenum, rad_surface *sp) {
  face *fp = &Rooms[roomnum].faces[facenum];
  room *rp = &Rooms[roomnum];

  int i, t, lmi_handle;
  int xres, yres;
  int lw, lh;
  int x1 = sp->x1;
  int y1 = sp->y1;
  int use_lightmap = 1;

  xres = sp->xresolution;
  yres = sp->yresolution;

  ASSERT(fp->lmi_handle != BAD_LMI_INDEX);
  lmi_handle = fp->lmi_handle;

  lw = lmi_w(lmi_handle);
  lh = lmi_h(lmi_handle);

  ASSERT((xres + x1) <= lw);
  ASSERT((yres + y1) <= lh);

  ASSERT(lw >= 2);
  ASSERT(lh >= 2);

  uint16_t *dest_data = lm_data(LightmapInfo[lmi_handle].lm_handle);

  // Set face pointer
  if (GameTextures[fp->tmap].flags & TF_ALPHA)
    use_lightmap = 0;
  if (GameTextures[fp->tmap].flags & (TF_FORCE_LIGHTMAP | TF_SATURATE_LIGHTMAP))
    use_lightmap = 1;

  if (use_lightmap)
    fp->flags |= FF_LIGHTMAP;

  for (i = 0; i < yres; i++) {
    for (t = 0; t < xres; t++) {
      if (!(sp->elements[i * xres + t].flags & EF_IGNORE)) {
        ddgr_color color = GR_16_TO_COLOR(dest_data[(i + y1) * lw + (t + x1)]);
        int red = GR_COLOR_RED(color);
        int green = GR_COLOR_GREEN(color);
        int blue = GR_COLOR_BLUE(color);

        float fr, fg, fb;

        if (!(dest_data[(i + y1) * lw + (t + x1)] & OPAQUE_FLAG)) {
          red = green = blue = 0;
        }

        fr = std::min(1.0f, sp->elements[i * xres + t].exitance.r + Ambient_red + Room_ambience_r[roomnum]);
        fg = std::min(1.0f, sp->elements[i * xres + t].exitance.g + Ambient_green + Room_ambience_g[roomnum]);
        fb = std::min(1.0f, sp->elements[i * xres + t].exitance.b + Ambient_blue + Room_ambience_b[roomnum]);

        fr = (fr * 255) + .5;
        fg = (fg * 255) + .5;
        fb = (fb * 255) + .5;

        red += (int)fr;
        green += (int)fg;
        blue += (int)fb;

        if (dest_data[(i + y1) * lw + (t + x1)] & OPAQUE_FLAG) {

          red /= 2;
          green /= 2;
          blue /= 2;
        }

        red = std::min(red, 255);
        green = std::min(green, 255);
        blue = std::min(blue, 255);

        uint16_t texel = OPAQUE_FLAG | GR_RGB16(red, green, blue);

        dest_data[(i + y1) * lw + (t + x1)] = texel;
      }
    }
  }
}

// Important - vertnum is the index into the face_verts[] array in the face structure,
// not an index into the verts[] array of the room structure
void BuildElementListForRoomFace(int roomnum, int facenum, rad_surface *surf) {
  matrix face_matrix, trans_matrix;
  vector fvec;
  vector avg_vert;
  vector verts[MAX_VERTS_PER_FACE * 5];
  vector rot_vert;
  vector vert;
  int i, t;
  int xres, yres;
  int lmi_handle;
  int x1 = surf->x1;
  int y1 = surf->y1;

  xres = surf->xresolution;
  yres = surf->yresolution;

  ASSERT(Rooms[roomnum].used);
  ASSERT(Rooms[roomnum].faces[facenum].num_verts >= 3);
  ASSERT(Rooms[roomnum].faces[facenum].lmi_handle != BAD_LMI_INDEX);

  lmi_handle = Rooms[roomnum].faces[facenum].lmi_handle;
  avg_vert = ScratchCenters[lmi_handle];

  // Make the orientation matrix
  // Reverse the normal because we're looking "at" the face, not from it
  fvec = -LightmapInfo[lmi_handle].normal;

  vm_VectorToMatrix(&face_matrix, &fvec, NULL, NULL);
  // Make the transformation matrix

  angvec avec;
  vm_ExtractAnglesFromMatrix(&avec, &face_matrix);
  vm_AnglesToMatrix(&trans_matrix, avec.p, avec.h, avec.b);

  // Rotate all the points
  for (i = 0; i < Rooms[roomnum].faces[facenum].num_verts; i++) {
    vert = Rooms[roomnum].verts[Rooms[roomnum].faces[facenum].face_verts[i]];

    vert -= avg_vert;
    vm_MatrixMulVector(&rot_vert, &vert, &trans_matrix);

    verts[i] = rot_vert;
  }

  // Find a base vector
  vector base_vector;
  vector xdiff, ydiff;

  vm_MakeZero(&xdiff);
  vm_MakeZero(&ydiff);

  // Rotate our upper left point into our 2d space
  vert = LightmapInfo[lmi_handle].upper_left - avg_vert;
  vm_MatrixMulVector(&base_vector, &vert, &trans_matrix);

  xdiff.x = LightmapInfo[lmi_handle].xspacing;
  ydiff.y = LightmapInfo[lmi_handle].yspacing;

  vm_TransposeMatrix(&trans_matrix);

  for (i = 0; i < yres; i++) {
    for (t = 0; t < xres; t++) {
      int element_index = i * xres + t;
      vector clip_verts[4];

      rad_element *ep = &surf->elements[element_index];

      // Allocate some free space
      clip_verts[0] = base_vector + (xdiff * (t + x1)) - (ydiff * (i + y1));
      clip_verts[1] = base_vector + (xdiff * (t + x1 + 1)) - (ydiff * (i + y1));
      clip_verts[2] = base_vector + (xdiff * (t + x1 + 1)) - (ydiff * (i + y1 + 1));
      clip_verts[3] = base_vector + (xdiff * (t + x1)) - (ydiff * (i + y1 + 1));
      ClipSurfaceElement(verts, ep, clip_verts, Rooms[roomnum].faces[facenum].num_verts);

      for (int k = 0; k < ep->num_verts; k++) {
        vm_MatrixMulVector(&rot_vert, &ep->verts[k], &trans_matrix);
        ep->verts[k] = rot_vert + avg_vert;
      }
    }
  }

  if (Square_surfaces) {
    surf->verts[0] = base_vector;
    surf->verts[1] = base_vector + (xdiff * xres);
    surf->verts[2] = base_vector + (xdiff * xres) - (ydiff * yres);
    surf->verts[3] = base_vector - (ydiff * yres);

    for (int k = 0; k < 4; k++) {
      vm_MatrixMulVector(&rot_vert, &surf->verts[k], &trans_matrix);
      surf->verts[k] = rot_vert + avg_vert;
    }
    surf->num_verts = 4;
  } else {

    surf->num_verts = Rooms[roomnum].faces[facenum].num_verts;
    for (int k = 0; k < surf->num_verts; k++) {
      surf->verts[k] = Rooms[roomnum].verts[Rooms[roomnum].faces[facenum].face_verts[k]];
    }
  }
}

vector *rad_ClipTop, *rad_ClipBottom, *rad_ClipLeft, *rad_ClipRight;
rad_point GlobalTempRadPoint;

void SetRadClipLines(vector *tp, vector *rp, vector *bp, vector *lp) {
  rad_ClipTop = tp;
  rad_ClipRight = rp;
  rad_ClipBottom = bp;
  rad_ClipLeft = lp;
}

uint8_t CodeRadPoint(rad_point *rp) {
  uint8_t code = 0;
  if (rp->pos.x < rad_ClipLeft->x)
    code |= CC_OFF_LEFT;
  if (rp->pos.x > rad_ClipRight->x)
    code |= CC_OFF_RIGHT;
  if (rp->pos.y < rad_ClipBottom->y)
    code |= CC_OFF_BOT;
  if (rp->pos.y > rad_ClipTop->y)
    code |= CC_OFF_TOP;
  rp->code = code;

  return code;
}

void ClipSurfaceElement(vector *surf_verts, rad_element *ep, vector *clip_verts, int nv) {
  rad_point src_verts[50];
  rad_point dest_verts[50];
  rad_point *slist, *dlist;
  uint8_t and = 0xff;
  uint8_t or = 0;
  int i;

  ASSERT(nv < 50);

  ep->flags = 0;

  SetRadClipLines(&clip_verts[0], &clip_verts[1], &clip_verts[2], &clip_verts[3]);

  for (i = 0; i < nv; i++) {
    src_verts[i].pos = surf_verts[i];
    src_verts[i].code = CodeRadPoint(&src_verts[i]);
  }

  for (i = 0; i < nv; i++) {
    and &= src_verts[i].code;
    or |= src_verts[i].code;
  }

  if (and) {
    // This element is not even in the face, ignore it
    ep->flags |= EF_IGNORE;
    ep->num_verts = 0;
    return;
  }

  int pnv = nv, nnv;
  slist = src_verts;
  dlist = dest_verts;

  nnv = ClipRadPointList(&slist, &dlist, &pnv, or);

  ep->num_verts = nnv;

  if (ep->num_verts == 0)
    ep->flags |= EF_IGNORE;
  else {
    ep->verts = (vector *)mem_malloc(sizeof(vector) * nnv);
    ASSERT(ep->verts);

    for (i = 0; i < nnv; i++) {
      ep->verts[i] = dlist[i].pos;
    }
  }
}

// Takes a points and clips all its viewpoints so they fit into the view frustum
// Puts the new clipped list in dest
// Returns number of points in clipped polygon

int ClipRadPointList(rad_point **src, rad_point **dest, int *nv, int code) {
  int plane, num = 0;
  rad_point **save_src = src, *t;
  rad_point **save_dest = dest;

  for (plane = 1; plane < 16; plane <<= 1) {
    if (plane & code) // Is this point off this plane?
    {

      *nv = ClipRadToPlane(plane, *src, *dest, *nv);
      if (*nv == 0)
        return 0;
      ASSERT(*nv >= 3);

      t = *dest;
      *dest = *src;
      *src = t;
      num++;
    }
  }

  t = *dest;
  *dest = *src;
  *src = t;

  return (*nv);
}

// Clips to a specific plane, putting resulting points in dest and returning number of points in dest
int ClipRadToPlane(int plane, rad_point *src, rad_point *dest, int nv) {
  int i, num = 0, limit = nv - 1, ppoint, npoint;

  ASSERT(nv >= 3);

  for (i = 0; i < nv; i++) {
    if (i == limit)
      npoint = 0;
    else
      npoint = i + 1;

    if (i == 0)
      ppoint = limit;
    else
      ppoint = i - 1;

    // mprintf(0,"checking point %d ",i);
    if (src[i].code & plane) // off this plane?
    {

      if (!(src[ppoint].code & plane)) // prev point on?
      {
        // mprintf(0,"pVertex %d off %d plane.\n",i,plane);
        ClipRadEdge(plane, &src[ppoint], &src[i]);

        memcpy(&dest[num], &GlobalTempRadPoint, sizeof(rad_point));
        num++;
      }

      if (!(src[npoint].code & plane)) // next point on?
      {

        // mprintf(0,"nVertex %d off %d plane.\n",i,plane);
        ClipRadEdge(plane, &src[npoint], &src[i]);

        memcpy(&dest[num], &GlobalTempRadPoint, sizeof(rad_point));
        num++;
      }

    } else // This point is on
    {

      // mprintf(0,"is on\n");
      memcpy(&dest[num], &src[i], sizeof(rad_point));
      num++;
    }
  }
  return (num);
}

// Takes two points and a plane, and clips.
void ClipRadEdge(int plane_flag, rad_point *on_pnt, rad_point *off_pnt) {
  if (plane_flag & CC_OFF_TOP) {
    float percent_on = 1.0 - ((off_pnt->pos.y - rad_ClipTop->y) / (off_pnt->pos.y - on_pnt->pos.y));
    GlobalTempRadPoint.pos = on_pnt->pos + ((off_pnt->pos - on_pnt->pos) * percent_on);
  }

  if (plane_flag & CC_OFF_RIGHT) {
    float percent_on = 1.0 - ((off_pnt->pos.x - rad_ClipRight->x) / (off_pnt->pos.x - on_pnt->pos.x));
    GlobalTempRadPoint.pos = on_pnt->pos + ((off_pnt->pos - on_pnt->pos) * percent_on);
  }

  if (plane_flag & CC_OFF_LEFT) {
    float percent_on = 1.0 - ((off_pnt->pos.x - rad_ClipLeft->x) / (off_pnt->pos.x - on_pnt->pos.x));
    GlobalTempRadPoint.pos = on_pnt->pos + ((off_pnt->pos - on_pnt->pos) * percent_on);
  }

  if (plane_flag & CC_OFF_BOT) {
    float percent_on = 1.0 - ((off_pnt->pos.y - rad_ClipBottom->y) / (off_pnt->pos.y - on_pnt->pos.y));
    GlobalTempRadPoint.pos = on_pnt->pos + ((off_pnt->pos - on_pnt->pos) * percent_on);
  }

  CodeRadPoint(&GlobalTempRadPoint);
}

// Adds color spectrums together
void AddSpectra(spectra *dest, spectra *a, spectra *b) {
  dest->r = a->r + b->r;
  dest->g = a->g + b->g;
  dest->b = a->b + b->b;
}

// Returns 1 if a src vector can hit dest vector unobstructed, else 0
int ShootRayForTerrainLight(vector *src, vector *dest, int cellnum) {
  fvi_info hit_info;
  fvi_query fq;
  vector temp_dest = *dest;

  if (temp_dest.y >= MAX_TERRAIN_HEIGHT * 3) {
    float mag;
    float ydiff;
    vector ray = temp_dest - *src;

    mag = vm_GetMagnitude(&ray);
    ray /= mag;

    ydiff = ((MAX_TERRAIN_HEIGHT * 3) - src->y) / ray.y;

    temp_dest = *src + (ray * ydiff);
  }

  // shoot a ray from the light position to the current vertex
  fq.p0 = src;
  fq.p1 = &temp_dest;
  fq.startroom = MAKE_ROOMNUM(cellnum);

  fq.rad = 0.0f;
  fq.flags = FQ_CHECK_OBJS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS | FQ_OBJ_BACKFACE | FQ_NO_RELINK |
             FQ_IGNORE_RENDER_THROUGH_PORTALS;
  fq.thisobjnum = -1;
  fq.ignore_obj_list = NULL;

  int fate = fvi_FindIntersection(&fq, &hit_info);

  if (fate == HIT_OUT_OF_TERRAIN_BOUNDS || fate == HIT_NONE)
    return 1;

  return 0;
}

#define AREA_X (TERRAIN_WIDTH - 1)
#define AREA_Z (TERRAIN_DEPTH - 1)
// #define AREA_X	128
// #define AREA_Z	128

// Puts a 3d grid of points on the terrain and sets a bit indicating if a light source
// can reach that point.  This is used for the dynamic lighting of objects on the terrain
void DoTerrainDynamicTable() {
  int i, t, k, j, maxrays;
  int raynum = 0;
  int key;

  maxrays = AREA_X * AREA_Z * 8 * Terrain_sky.num_satellites;

  mprintf(0, "Calculating dynamic light table for %d points...\n", maxrays);
  mprintf(0, "Press tilde key to abort!\n");

  memset(Terrain_dynamic_table, 0, (TERRAIN_DEPTH * TERRAIN_WIDTH));

  for (i = 0; i < AREA_Z; i++) {
    //	ddio_KeyFrame();
    while ((key = ddio_KeyInKey()) != 0) {
      if (key == KEY_LAPOSTRO) {
        for (; i < AREA_Z; i++) {
          for (t = 0; t < AREA_X; t++) {
            int tseg = i * TERRAIN_WIDTH + t;
            Terrain_dynamic_table[tseg] = 255;
          }
        }

        return;
      }
    }

    for (t = 0; t < AREA_X; t++) {
      int tseg = i * TERRAIN_WIDTH + t;
      vector gp;

      gp.x = (t * TERRAIN_SIZE);
      gp.z = (i * TERRAIN_SIZE);

      gp.y = Terrain_seg[tseg].y;

      for (k = 0; k < 8; k++) {
        vector pos;

        pos.x = gp.x;
        pos.z = gp.z;
        pos.y = k * (MAX_TERRAIN_HEIGHT / 8);

        for (j = 0; j < Terrain_sky.num_satellites; j++) {
          raynum++;
          mprintf_at(2, 4, 0, "Ray=%d      ", raynum);

          if (gp.y > pos.y)
            continue;

          int answer;
          answer = ShootRayForTerrainLight(&pos, &Terrain_sky.satellite_vectors[j], tseg);
          if (!answer)
            continue;

          Terrain_dynamic_table[tseg] |= (1 << k);
          j = Terrain_sky.num_satellites;
        }
      }
    }
  }
}

void ComputeTerrainSpeedTable() {
  int i, t, j, raynum = 0;

  mprintf(0, "Precomputing terrain speed table...(%d rays)\n", AREA_X * AREA_Z);
  for (i = 0; i < AREA_Z; i++) {
    for (t = 0; t < AREA_X; t++) {
      int tseg = i * TERRAIN_WIDTH + t;
      vector pos;

      pos.x = (t * TERRAIN_SIZE);
      pos.z = (i * TERRAIN_SIZE);
      pos.y = (Terrain_seg[tseg].y) + .001;

      raynum++;
      if ((raynum % 1000) == 0)
        mprintf_at(2, 4, 0, "Ray=%d      ", raynum);

      for (j = 0; j < Terrain_sky.num_satellites; j++)
        TerrainLightSpeedup[j][tseg] = ShootRayForTerrainLight(&pos, &Terrain_sky.satellite_vectors[j], tseg);
    }
  }
}

float GetMaxColor(spectra *sp);

// Calculates radiosity for the outdoor surfaces
void DoRadiosityForTerrain() {
  int i, t, raynum = 0;
  int extra_surfaces = 0, total_surfaces = 0;
  int surf_index = 0;
  spectra *terrain_sums[2];
  int room_surf_start, obj_surf_start;

  int do_dynamic = 0;
  int lit = 0;

  // First make sure there is even a light source
  for (i = 0; i < Terrain_sky.num_satellites; i++) {
    int tmap = Terrain_sky.satellite_texture[i];
    if (Terrain_sky.satellite_r[i] > 0 || Terrain_sky.satellite_g[i] > 0 || Terrain_sky.satellite_b[i] > 0)
      lit = 1;
  }

  if (!lit) {
    OutrageMessageBox("No moons/suns cast light so there is no point in running terrain radiosity!");
    return;
  }

  for (i = 0; i < Terrain_sky.num_satellites; i++) {
    TerrainLightSpeedup[i] = (uint8_t *)mem_malloc(TERRAIN_WIDTH * TERRAIN_DEPTH);
    ASSERT(TerrainLightSpeedup[i]);
  }

  if ((MessageBox(NULL,
                  "Do you wish to calculate dynamic terrain lighting when radiosity is completed? (Note: Dynamic "
                  "lighting takes a long time)",
                  "Question", MB_YESNO)) == IDYES)
    do_dynamic = 1;

  if (!Ignore_terrain)
    ComputeTerrainSpeedTable();

  extra_surfaces += Terrain_sky.num_satellites;
  extra_surfaces += GetTotalObjectFaces(1);

  // Get our external rooms ready
  ClearAllRoomLightmaps(1);
  ComputeAllRoomLightmapUVs(1);

  // count how many faces we'll need
  for (i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used && (Rooms[i].flags & RF_EXTERNAL))
      extra_surfaces += Rooms[i].num_faces;
  }

  if (!Ignore_terrain)
    total_surfaces = ((AREA_X) * (AREA_Z) * 2);
  total_surfaces += extra_surfaces;

  // Get our object lightmaps ready
  ClearAllObjectLightmaps(1);

  // Allocate memory
  terrain_sums[0] = (spectra *)mem_malloc(TERRAIN_WIDTH * TERRAIN_DEPTH * sizeof(spectra));
  terrain_sums[1] = (spectra *)mem_malloc(TERRAIN_WIDTH * TERRAIN_DEPTH * sizeof(spectra));
  ASSERT(terrain_sums[0] && terrain_sums[1]);

  Light_surfaces = (rad_surface *)mem_malloc(total_surfaces * sizeof(rad_surface));
  ASSERT(Light_surfaces != NULL);

  // Setup radiosity surfaces
  if (!Ignore_terrain) {
    for (i = 0; i < (AREA_X) * (AREA_Z); i++, surf_index += 2) {
      vector a, b, c;
      int x, z;

      x = i % AREA_X;
      z = i / AREA_Z;

      int seg = z * TERRAIN_WIDTH + x;

      // Do common stuff (for both triangles)
      for (x = 0; x < 2; x++) {
        Light_surfaces[i * 2 + x].surface_type = ST_TERRAIN;
        Light_surfaces[i * 2 + x].emittance.r =
            (float)GameTextures[Terrain_tex_seg[Terrain_seg[seg].texseg_index].tex_index].r;
        Light_surfaces[i * 2 + x].emittance.g =
            (float)GameTextures[Terrain_tex_seg[Terrain_seg[seg].texseg_index].tex_index].g;
        Light_surfaces[i * 2 + x].emittance.b =
            (float)GameTextures[Terrain_tex_seg[Terrain_seg[seg].texseg_index].tex_index].b;

        Light_surfaces[i * 2 + x].roomnum = MAKE_ROOMNUM(seg);
        Light_surfaces[i * 2 + x].facenum = x;

        Light_surfaces[i * 2 + x].reflectivity =
            GameTextures[Terrain_tex_seg[Terrain_seg[seg].texseg_index].tex_index].reflectivity;
        Light_surfaces[i * 2 + x].xresolution = 1;
        Light_surfaces[i * 2 + x].yresolution = 1;
      }

      // Do upper left triangle
      Light_surfaces[i * 2].elements = (rad_element *)mem_malloc(sizeof(rad_element));
      ASSERT(Light_surfaces[i * 2].elements != NULL);

      Light_surfaces[i * 2].elements[0].verts = (vector *)mem_malloc(sizeof(vector) * 3);
      ASSERT(Light_surfaces[i * 2].elements[0].verts);

      Light_surfaces[i * 2].verts = (vector *)mem_malloc(sizeof(vector) * 3);
      ASSERT(Light_surfaces[i * 2].verts != NULL);

      Light_surfaces[i * 2].normal = TerrainNormals[MAX_TERRAIN_LOD - 1][seg].normal1;

      z = i / (AREA_X);
      x = i % (AREA_X);

      a.x = x * TERRAIN_SIZE;
      a.y = Terrain_seg[(z * TERRAIN_WIDTH) + (x)].y;
      a.z = z * TERRAIN_SIZE;

      b.x = x * TERRAIN_SIZE;
      b.y = Terrain_seg[((z + 1) * TERRAIN_WIDTH) + (x)].y;
      b.z = (z + 1) * TERRAIN_SIZE;

      c.x = (x + 1) * TERRAIN_SIZE;
      c.y = Terrain_seg[((z + 1) * TERRAIN_WIDTH) + (x + 1)].y;
      c.z = (z + 1) * TERRAIN_SIZE;

      Light_surfaces[i * 2].verts[0] = a;
      Light_surfaces[i * 2].verts[1] = b;
      Light_surfaces[i * 2].verts[2] = c;
      Light_surfaces[i * 2].num_verts = 3;

      Light_surfaces[i * 2].elements[0].verts[0] = a;
      Light_surfaces[i * 2].elements[0].verts[1] = b;
      Light_surfaces[i * 2].elements[0].verts[2] = c;
      Light_surfaces[i * 2].elements[0].num_verts = 3;

      Light_surfaces[i * 2].elements[0].flags = 0;

      // Now do lower right

      Light_surfaces[i * 2 + 1].elements = (rad_element *)mem_malloc(sizeof(rad_element));
      ASSERT(Light_surfaces[i * 2 + 1].elements != NULL);

      Light_surfaces[i * 2 + 1].elements[0].verts = (vector *)mem_malloc(sizeof(vector) * 3);
      ASSERT(Light_surfaces[i * 2 + 1].elements[0].verts);

      Light_surfaces[i * 2 + 1].verts = (vector *)mem_malloc(sizeof(vector) * 3);
      ASSERT(Light_surfaces[i * 2 + 1].verts != NULL);

      Light_surfaces[i * 2 + 1].normal = TerrainNormals[MAX_TERRAIN_LOD - 1][seg].normal2;

      z = i / (AREA_X);
      x = i % (AREA_X);

      a.x = x * TERRAIN_SIZE;
      a.y = Terrain_seg[(z * TERRAIN_WIDTH) + (x)].y;
      a.z = z * TERRAIN_SIZE;

      b.x = (x + 1) * TERRAIN_SIZE;
      b.y = Terrain_seg[((z + 1) * TERRAIN_WIDTH) + (x + 1)].y;
      b.z = (z + 1) * TERRAIN_SIZE;

      c.x = (x + 1) * TERRAIN_SIZE;
      c.y = Terrain_seg[(z * TERRAIN_WIDTH) + (x + 1)].y;
      c.z = z * TERRAIN_SIZE;

      Light_surfaces[i * 2 + 1].verts[0] = a;
      Light_surfaces[i * 2 + 1].verts[1] = b;
      Light_surfaces[i * 2 + 1].verts[2] = c;
      Light_surfaces[i * 2 + 1].num_verts = 3;

      Light_surfaces[i * 2 + 1].elements[0].verts[0] = a;
      Light_surfaces[i * 2 + 1].elements[0].verts[1] = b;
      Light_surfaces[i * 2 + 1].elements[0].verts[2] = c;
      Light_surfaces[i * 2 + 1].elements[0].num_verts = 3;

      Light_surfaces[i * 2 + 1].elements[0].flags = 0;
    }
  }

  // Setup satellites
  for (i = 0; i < Terrain_sky.num_satellites; i++, surf_index++) {
    Light_surfaces[surf_index].verts = (vector *)mem_malloc(sizeof(vector) * 3);
    ASSERT(Light_surfaces[surf_index].verts != NULL);

    Light_surfaces[surf_index].elements = (rad_element *)mem_malloc(sizeof(rad_element));
    ASSERT(Light_surfaces[surf_index].elements != NULL);

    Light_surfaces[surf_index].elements[0].verts = (vector *)mem_malloc(sizeof(vector) * 3);
    ASSERT(Light_surfaces[surf_index].elements[0].verts);

    Light_surfaces[surf_index].surface_type = ST_SATELLITE;
    Light_surfaces[surf_index].emittance.r = Terrain_sky.satellite_r[i];
    Light_surfaces[surf_index].emittance.g = Terrain_sky.satellite_g[i];
    Light_surfaces[surf_index].emittance.b = Terrain_sky.satellite_b[i];

    Light_surfaces[surf_index].roomnum = i;
    Light_surfaces[surf_index].facenum = 0;

    Light_surfaces[surf_index].reflectivity = 0;
    Light_surfaces[surf_index].xresolution = 1;
    Light_surfaces[surf_index].yresolution = 1;

    vm_MakeZero(&Light_surfaces[surf_index].normal);

    Light_surfaces[surf_index].verts[0] = Terrain_sky.satellite_vectors[i];
    Light_surfaces[surf_index].verts[1] = Terrain_sky.satellite_vectors[i];
    Light_surfaces[surf_index].verts[2] = Terrain_sky.satellite_vectors[i];
    Light_surfaces[surf_index].num_verts = 3;

    Light_surfaces[surf_index].elements[0].verts[0] = Terrain_sky.satellite_vectors[i];
    Light_surfaces[surf_index].elements[0].verts[1] = Terrain_sky.satellite_vectors[i];
    Light_surfaces[surf_index].elements[0].verts[2] = Terrain_sky.satellite_vectors[i];
    Light_surfaces[surf_index].elements[0].num_verts = 3;

    Light_surfaces[surf_index].elements[0].flags = 0;
  }

  // Setup external rooms

  room_surf_start = surf_index;
  for (i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used) {
      if (!(Rooms[i].flags & RF_EXTERNAL))
        continue;

      for (t = 0; t < Rooms[i].num_faces; t++, surf_index++) {

        ComputeSurfaceRes(&Light_surfaces[surf_index], &Rooms[i], t);

        Light_surfaces[surf_index].verts = (vector *)mem_malloc(Rooms[i].faces[t].num_verts * sizeof(vector));
        ASSERT(Light_surfaces[surf_index].verts != NULL);

        Light_surfaces[surf_index].elements = (rad_element *)mem_malloc(
            Light_surfaces[surf_index].xresolution * Light_surfaces[surf_index].yresolution * sizeof(rad_element));
        ASSERT(Light_surfaces[surf_index].elements != NULL);

        if (Rooms[i].faces[t].portal_num != -1 &&
            !(Rooms[i].portals[Rooms[i].faces[t].portal_num].flags & PF_RENDER_FACES))

        {
          Light_surfaces[surf_index].surface_type = ST_PORTAL;
          Light_surfaces[surf_index].emittance.r = 0;
          Light_surfaces[surf_index].emittance.g = 0;
          Light_surfaces[surf_index].emittance.b = 0;
        } else {
          float mul = ((float)Rooms[i].faces[t].light_multiple) / 4.0;
          mul *= GlobalMultiplier * Room_multiplier[i];

          Light_surfaces[surf_index].emittance.r = (float)GameTextures[Rooms[i].faces[t].tmap].r * mul;
          Light_surfaces[surf_index].emittance.g = (float)GameTextures[Rooms[i].faces[t].tmap].g * mul;
          Light_surfaces[surf_index].emittance.b = (float)GameTextures[Rooms[i].faces[t].tmap].b * mul;
          Light_surfaces[surf_index].surface_type = ST_EXTERNAL_ROOM;

          // if ((GetMaxColor (&Light_surfaces[surf_index].emittance))>.005)
          //	Light_surfaces[surf_index].flags|=SF_LIGHTSOURCE;
        }

        Light_surfaces[surf_index].normal = LightmapInfo[Rooms[i].faces[t].lmi_handle].normal;
        Light_surfaces[surf_index].roomnum = i;
        Light_surfaces[surf_index].facenum = t;

        Light_surfaces[surf_index].reflectivity = GameTextures[Rooms[i].faces[t].tmap].reflectivity;

        // Set the vertices for each element
        BuildElementListForRoomFace(i, t, &Light_surfaces[surf_index]);

        int xres = Light_surfaces[surf_index].xresolution;
        int yres = Light_surfaces[surf_index].yresolution;
      }
    }
  }

  // Setup objects
  obj_surf_start = surf_index;
  ComputeSurfacesForObjects(surf_index, 1);

  mprintf(0, "Solving radiosity equation (press tilde key to stop)...\n");

  if (D3EditState.hemicube_radiosity)
    DoRadiosityRun(SM_SWITCH_AFTER_SATELLITES, Light_surfaces, total_surfaces);
  else
    DoRadiosityRun(SM_RAYCAST, Light_surfaces, total_surfaces);

  // Figure out lighting by averaging the two triangles per terrain cell

  if (!Ignore_terrain) {
    for (i = 0; i < AREA_X * AREA_Z; i++) {
      // Add in ambient terrain light
      terrain_sums[0][i].r = std::min(1.0f, Light_surfaces[i * 2].elements[0].exitance.r + Ambient_red);
      terrain_sums[0][i].g = std::min(1.0f, Light_surfaces[i * 2].elements[0].exitance.g + Ambient_green);
      terrain_sums[0][i].b = std::min(1.0f, Light_surfaces[i * 2].elements[0].exitance.b + Ambient_blue);

      terrain_sums[1][i].r = std::min(1.0f, Light_surfaces[i * 2 + 1].elements[0].exitance.r + Ambient_red);
      terrain_sums[1][i].g = std::min(1.0f, Light_surfaces[i * 2 + 1].elements[0].exitance.g + Ambient_green);
      terrain_sums[1][i].b = std::min(1.0f, Light_surfaces[i * 2 + 1].elements[0].exitance.b + Ambient_blue);
    }

    for (i = 0; i < (AREA_X) * (AREA_Z); i++) {
      int x, z;
      spectra total;
      int num_items = 0;
      int seg;

      memset(&total, 0, sizeof(spectra));

      x = i % (AREA_X);
      z = i / (AREA_Z);

      seg = z * TERRAIN_WIDTH + x;

      if (x != 0) {
        AddSpectra(&total, &total, &terrain_sums[1][z * (AREA_X) + (x - 1)]);
        num_items++;
      }

      AddSpectra(&total, &total, &terrain_sums[0][z * (AREA_X) + x]);
      AddSpectra(&total, &total, &terrain_sums[1][z * (AREA_X) + x]);

      num_items += 2;

      if (x != 0) {
        if (z != 0) {
          AddSpectra(&total, &total, &terrain_sums[0][(z - 1) * (AREA_X) + (x - 1)]);
          AddSpectra(&total, &total, &terrain_sums[1][(z - 1) * (AREA_X) + (x - 1)]);
          num_items += 2;
        }
      }

      if (z != 0) {

        AddSpectra(&total, &total, &terrain_sums[0][(z - 1) * (AREA_X) + x]);
        num_items++;
      }

      total.r /= num_items;
      total.g /= num_items;
      total.b /= num_items;

      if (total.r > 1.0)
        total.r = 1.0;
      if (total.g > 1.0)
        total.g = 1.0;
      if (total.b > 1.0)
        total.b = 1.0;

      Terrain_seg[seg].l = Float_to_ubyte(GetMaxColor(&total));

      Terrain_seg[seg].r = Float_to_ubyte(total.r);
      Terrain_seg[seg].g = Float_to_ubyte(total.g);
      Terrain_seg[seg].b = Float_to_ubyte(total.b);
    }
  }

  // Assign lightmaps to external rooms
  for (i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used) {
      if (!(Rooms[i].flags & RF_EXTERNAL))
        continue;

      for (t = 0; t < Rooms[i].num_faces; t++, room_surf_start++) {
        AssignRoomSurfaceToLightmap(i, t, &Light_surfaces[room_surf_start]);
      }
    }
  }

  // Now assign lightmaps to objects
  AssignLightmapsToObjectSurfaces(obj_surf_start, 1);

  // Shade room/object lightmaps

  BlurLightmapInfos(LMI_EXTERNAL_ROOM);
  BlurLightmapInfos(LMI_TERRAIN_OBJECT);

  ShadeLightmapInfoEdges(LMI_EXTERNAL_ROOM);
  ShadeLightmapInfoEdges(LMI_TERRAIN_OBJECT);

  // Free memory

  for (i = 0; i < total_surfaces; i++) {
    mem_free(Light_surfaces[i].verts);
    for (int t = 0; t < Light_surfaces[i].xresolution * Light_surfaces[i].yresolution; t++)
      if (Light_surfaces[i].elements[t].num_verts > 0)
        mem_free(Light_surfaces[i].elements[t].verts);
    mem_free(Light_surfaces[i].elements);
  }

  mem_free(Light_surfaces);
  mem_free(terrain_sums[0]);
  mem_free(terrain_sums[1]);

  for (i = 0; i < Terrain_sky.num_satellites; i++)
    mem_free(TerrainLightSpeedup[i]);

  UpdateTerrainLightmaps();
  SqueezeLightmaps(1, -1);
  // Figure out combined portals
  CheckCombinePortals(1);

  if (do_dynamic)
    DoTerrainDynamicTable();

  OutrageMessageBox(
      "Terrain radiosity complete!\nIt is recommended you save the level so you won't have to rerun this operation.");
}
/*
void DoRadiosityForTerrain ()
{
        int i,t,j,maxrays;
        int raynum=0;
        spectra *exitance;
        int lit=0;
        int do_dynamic=0;

        if ((MessageBox(NULL,"Do you wish to calculate dynamic terrain lighting when radiosity is completed? (Note:
Dynamic lighting takes a long time)","Question",MB_YESNO))==IDYES) do_dynamic=1;

        // First make sure there is even a light source
        for (i=0;i<Terrain_sky.num_satellites;i++)
        {
                int tmap=Terrain_sky.satellite_texture[i];
                if (GameTextures[tmap].r>0 || GameTextures[tmap].g || GameTextures[tmap].b)
                        lit=1;
        }

        if (!lit)
        {
                OutrageMessageBox ("No moons/suns cast light so there is no point in running terrain radiosity!");
                return;
        }

        maxrays=(TERRAIN_DEPTH-1)*(TERRAIN_WIDTH-1)*Terrain_sky.num_satellites;

        mprintf(0,"Calculating terrain radiosity for %d points! (be patient!)\n",maxrays);

        // Malloc some memory
        exitance=(spectra *)mem_malloc (TERRAIN_DEPTH*TERRAIN_WIDTH*sizeof(spectra));
        ASSERT (exitance!=NULL);

        for (i=0;i<TERRAIN_DEPTH*TERRAIN_WIDTH;i++)
        {
                exitance[i].r=0;
                exitance[i].g=0;
                exitance[i].b=0;
        }

        // Now shoot a ray from every triangle on the terrain to every moon/sun in the
        // sky, tallying the results
        for (i=0;i<TERRAIN_DEPTH-1;i++)
        {
                for (t=0;t<TERRAIN_WIDTH-1;t++)
                {
                        int tseg=i*TERRAIN_WIDTH+t;
                        vector pos1,pos2;

                        pos1.x=(t*TERRAIN_SIZE)+(TERRAIN_SIZE/3);
                        pos1.z=(i*TERRAIN_SIZE)+(TERRAIN_SIZE * .66);
                        pos1.y=GetTerrainGroundPoint (&pos1);
                        pos1+=((TerrainNormals[MAX_TERRAIN_LOD-1][tseg].normal1)/16);

                        pos2.x=(t*TERRAIN_SIZE)+(TERRAIN_SIZE * .66);
                        pos2.z=(i*TERRAIN_SIZE)+(TERRAIN_SIZE/3);
                        pos2.y=GetTerrainGroundPoint (&pos2);
                        pos2+=((TerrainNormals[MAX_TERRAIN_LOD-1][tseg].normal2)/16);

                        for (j=0;j<Terrain_sky.num_satellites;j++)
                        {
                                raynum++;
                                mprintf_at(2,4,0,"Ray=%d      ",raynum);

                                int answer1,answer2;
                                answer1=ShootRayForTerrainLight (&pos1,&Terrain_sky.satellite_vectors[j],tseg);
                                answer2=ShootRayForTerrainLight (&pos2,&Terrain_sky.satellite_vectors[j],tseg);

                                if (answer1==0 && answer2==0)
                                        continue;

                                float r=exitance[tseg].r;
                                float g=exitance[tseg].g;
                                float b=exitance[tseg].b;
                                int tmap=Terrain_sky.satellite_texture[j];

                                float light1,light2,light_avg;
                                vector tnorm1=TerrainNormals[MAX_TERRAIN_LOD-1][tseg].normal1;
                                vector tnorm2=TerrainNormals[MAX_TERRAIN_LOD-1][tseg].normal2;

                                vector normal;

                                vm_GetNormalizedDir (&normal,&Terrain_sky.satellite_vectors[j],&pos1);
                                light1=answer1*(vm_DotProduct (&normal,&tnorm1));
                                if (light1<0)
                                        light1=0;

                                vm_GetNormalizedDir (&normal,&Terrain_sky.satellite_vectors[j],&pos2);
                                light2=answer2*(vm_DotProduct (&normal,&tnorm2));
                                if (light2<0)
                                        light2=0;

                                light_avg=(light1+light2)/2;

                                r+=(light_avg*GameTextures[tmap].r);
                                g+=(light_avg*GameTextures[tmap].g);
                                b+=(light_avg*GameTextures[tmap].b);

                                exitance[tseg].r=r;
                                exitance[tseg].g=g;
                                exitance[tseg].b=b;
                        }
                }
        }

        // Clip to 1.0
        for (i=0;i<TERRAIN_DEPTH*TERRAIN_WIDTH;i++)
        {
                exitance[i].r=std::min(exitance[i].r,1.0f);
                exitance[i].g=std::min(exitance[i].g,1.0f);
                exitance[i].b=std::min(exitance[i].b,1.0f);
        }


        // Now average the surrounding cells to get a 'smooth' shadow effect
        for (i=0;i<TERRAIN_DEPTH;i++)
        {
                for (t=0;t<TERRAIN_WIDTH;t++)
                {
                        int tseg=i*TERRAIN_WIDTH+t;
                        spectra total={0,0,0};
                        int num=0;

                        AddSpectra (&total,&total,&exitance[tseg]);
                        num++;

                        if (i!=0)
                        {
                                AddSpectra (&total,&total,&exitance[(i-1)*TERRAIN_WIDTH+t]);
                                num++;
                                if (t!=0)
                                {
                                        AddSpectra (&total,&total,&exitance[(i-1)*TERRAIN_WIDTH+(t-1)]);
                                        num++;
                                }

                        }
                        if (t!=0)
                        {
                                AddSpectra (&total,&total,&exitance[i*TERRAIN_WIDTH+(t-1)]);
                                num++;
                        }

                        total.r/=num;
                        total.g/=num;
                        total.b/=num;

                        Terrain_seg[tseg].r=total.r*255;
                        Terrain_seg[tseg].g=total.g*255;
                        Terrain_seg[tseg].b=total.b*255;
                        Terrain_seg[tseg].l=((total.r+total.g+total.b)*255)/3;
                }
        }

        free (exitance);

        if (do_dynamic)
                DoTerrainDynamicTable();

        UpdateTerrainLightmaps();
        OutrageMessageBox ("Terrain radiosity complete!\nIt is recommended you save the level so you won't have to rerun
this operation.");

}*/

void BuildLightmapUVs(int *room_list, int *face_list, int count, vector *lightmap_poly, int nv, int external) {
  matrix face_matrix, trans_matrix;
  vector fvec;
  vector avg_vert;
  vector verts[MAX_VERTS_PER_FACE * 5];
  vector facevert;
  vector rot_vert;
  int i, t;
  int lmi_handle;

  // find the center point of this face
  vm_MakeZero(&avg_vert);
  for (i = 0; i < nv; i++)
    avg_vert += lightmap_poly[i];

  avg_vert /= nv;

  // Make the orientation matrix
  // Reverse the normal because we're looking "at" the face, not from it
  fvec = -Rooms[room_list[0]].faces[face_list[0]].normal;

  vm_VectorToMatrix(&face_matrix, &fvec, NULL, NULL);
  // Make the transformation matrix

  angvec avec;
  vm_ExtractAnglesFromMatrix(&avec, &face_matrix);
  vm_AnglesToMatrix(&trans_matrix, avec.p, avec.h, avec.b);

  // Rotate all the points
  for (i = 0; i < nv; i++) {
    vector vert = lightmap_poly[i];

    vert -= avg_vert;
    vm_MatrixMulVector(&rot_vert, &vert, &trans_matrix);

    verts[i] = rot_vert;
  }

  // Find left most point
  int leftmost_point = -1;
  float leftmost_x = 900000.00f; // a big number

  for (i = 0; i < nv; i++) {
    if (verts[i].x < leftmost_x) {
      leftmost_point = i;
      leftmost_x = verts[i].x;
    }
  }

  ASSERT(leftmost_point != -1);

  // Find top most point
  int topmost_point = -1;
  float topmost_y = -900000.0f; // a big number

  for (i = 0; i < nv; i++) {
    if (verts[i].y > topmost_y) {
      topmost_point = i;
      topmost_y = verts[i].y;
    }
  }

  ASSERT(topmost_point != -1);

  // Find right most point
  int rightmost_point = -1;
  float rightmost_x = -900000.00f; // a big number

  for (i = 0; i < nv; i++) {
    if (verts[i].x > rightmost_x) {
      rightmost_point = i;
      rightmost_x = verts[i].x;
    }
  }

  ASSERT(rightmost_point != -1);

  // Find bottom most point
  int bottommost_point = -1;
  float bottommost_y = 900000.0f; // a big number

  for (i = 0; i < nv; i++) {
    if (verts[i].y < bottommost_y) {
      bottommost_point = i;
      bottommost_y = verts[i].y;
    }
  }

  ASSERT(bottommost_point != -1);

  // now set the base vertex, which is where we base uv 0,0 on

  vector base_vector;

  base_vector.x = verts[leftmost_point].x;
  base_vector.y = verts[topmost_point].y;
  base_vector.z = 0;

  // Figure out lightmap resolution
  float xdiff = verts[rightmost_point].x - verts[leftmost_point].x;
  float ydiff = verts[topmost_point].y - verts[bottommost_point].y;
  float max_diff = (float)std::max(xdiff, ydiff);

  int lightmap_x_res = -1, lightmap_y_res = -1;
  float xspacing = LightSpacing;
  float yspacing = LightSpacing;
  float spacing = LightSpacing;
  int res, done_spacing = 0;
  int xspace_int, yspace_int;

  // If the default spacing would make us go over our lightmap resolution
  // limit, then increase the spacing and try again
  while (!done_spacing) {
    res = (xdiff / xspacing);
    if (((xdiff / xspacing) - res) > 0)
      res++;

    res++;

    if (res > 126)
      xspacing += 1;
    else
      done_spacing = 1;
  }

  // Set a mininum, at least
  if (res < 2)
    res = 2;

  lightmap_x_res = res;

  done_spacing = 0;
  while (!done_spacing) {
    res = (ydiff / yspacing);
    if (((ydiff / yspacing) - res) > 0)
      res++;

    res++;

    if (res > 126)
      yspacing += 1;
    else
      done_spacing = 1;
  }

  // Set a mininum, at least
  if (res < 2)
    res = 2;

  lightmap_y_res = res;

  /*// Find power of 2 number
  for (i=0;i<=7;i++)
  {
          int low_num=1<i;
          int hi_num=2<<i;
          if (res<=hi_num && res>low_num)
          {
                  lightmap_res=hi_num;
                  break;
          }
  }*/

  if (external)
    lmi_handle = AllocLightmapInfo(lightmap_x_res, lightmap_y_res, LMI_EXTERNAL_ROOM);
  else
    lmi_handle = AllocLightmapInfo(lightmap_x_res, lightmap_y_res, LMI_ROOM);

  ASSERT(lmi_handle != BAD_LMI_INDEX);
  ASSERT(lmi_handle >= 0 && lmi_handle <= MAX_LIGHTMAP_INFOS);
  Lightmaps_for_rad++;

  // Now do best fit spacing
  if (BestFit) {
    xspace_int = (xdiff / lightmap_x_res);
    if ((xdiff - (lightmap_x_res * xspace_int)) > 0)
      xspace_int++;

    yspace_int = (ydiff / lightmap_y_res);
    if ((ydiff - (lightmap_y_res * yspace_int)) > 0)
      yspace_int++;
  } else {
    xspace_int = xspacing;
    yspace_int = yspacing;
  }

  // Figure out lightmap uvs

  // Rotate all the face points
  for (i = 0; i < count; i++) {
    Rooms[room_list[i]].faces[face_list[i]].lmi_handle = lmi_handle;

    for (t = 0; t < Rooms[room_list[i]].faces[face_list[i]].num_verts; t++) {
      room *rp = &Rooms[room_list[i]];
      face *fp = &rp->faces[face_list[i]];
      vector vert = rp->verts[fp->face_verts[t]];

      vert -= avg_vert;
      vm_MatrixMulVector(&rot_vert, &vert, &trans_matrix);

      facevert = rot_vert;

      // Find uv2s for this vertex
      fp->face_uvls[t].u2 = (facevert.x - verts[leftmost_point].x) / (float)(lightmap_x_res * xspace_int);
      fp->face_uvls[t].v2 = fabs((verts[topmost_point].y - facevert.y)) / (float)(lightmap_y_res * yspace_int);

      if (fp->face_uvls[t].u2 < 0)
        fp->face_uvls[t].u2 = 0;
      if (fp->face_uvls[t].v2 < 0)
        fp->face_uvls[t].v2 = 0;

      ASSERT(fp->face_uvls[t].u2 >= 0 && fp->face_uvls[t].u2 <= 1.0);
      ASSERT(fp->face_uvls[t].v2 >= 0 && fp->face_uvls[t].v2 <= 1.0);
    }
  }

  // Find upper left corner
  vm_TransposeMatrix(&trans_matrix);
  vm_MatrixMulVector(&rot_vert, &base_vector, &trans_matrix);
  LightmapInfo[lmi_handle].upper_left = rot_vert + avg_vert;

  LightmapInfo[lmi_handle].xspacing = xspace_int;
  LightmapInfo[lmi_handle].yspacing = yspace_int;
  LightmapInfo[lmi_handle].normal = Rooms[room_list[0]].faces[face_list[0]].normal;
  ScratchCenters[lmi_handle] = avg_vert;
}

#define MAX_COMBINES 50
#define LM_ADJACENT_FACE_THRESHOLD .999
uint8_t *RoomsAlreadyCombined[MAX_ROOMS];

/*
// Returns number of verts in dest if face a can be safely combined with face b
// Returns 0 if not
int CombineLightFaces( vector *dest_verts,vector *averts, int nva, vector *norma,vector *bverts, int nvb,vector
*normb,int aroom,int broom)
{
        int starta, startb, i;
        vector va;
        float dp;

        dp=vm_DotProduct (normb,norma);

        if (dp < LM_ADJACENT_FACE_THRESHOLD)
                return 0;

        ASSERT (nva > 2 );
        ASSERT (nvb > 2 );

        // Go through each vertex and get a match

        for (starta=0; starta<nva; starta++ )
        {
                int nexta=(starta+1)%nva;

                for (startb=0; startb<nvb; startb++ )
                {
                        int nextb=(startb+1)%nvb;

                        if ((PointsAreSame (&averts[starta],&bverts[nextb])) &&
                                  (PointsAreSame(&averts[nexta],&bverts[startb])))
                        {
                                //MATCH!!!!!!!!

                                int dnv = 0;

                                for (i=1; i<nva; i++ )
                                {
                                        ASSERT(dnv < MAX_VERTS_PER_FACE*5);
                                        dest_verts[dnv] = averts[(starta+i)%nva];
                                        va = dest_verts[dnv];
                                        dnv++;
                                }

                                if ( (PointsAreSame(&va,&bverts[(startb+2)%nvb])))
                                        mprintf(0, "WARNING!!! Faces were combined that caused the loss of a
vertex!\n");

                                for (i=1; i<nvb; i++ )
                                {
                                        ASSERT(dnv < MAX_VERTS_PER_FACE*5 );
                                        if ( (i==1) && (PointsAreSame(&va,&bverts[(startb+i+1)%nvb])))
                                                continue;
                                        else if ( (i==2) && (PointsAreSame(&va,&bverts[(startb+i)%nvb])))
                                                continue;
                                        else
                                        {
                                                dest_verts[dnv] = bverts[(startb+i)%nvb];
                                                dnv++;

                                        }
                                }
                                ASSERT( dnv > 2 );

                                return dnv;
                        }
                }
        }

         Now check for tjoint faces
        for (starta=0; starta<nva; starta++)
        {
                int nexta=(starta+1)%nva;

                for (startb=0; startb<nvb; startb++)
                {
                        vector line_a=averts[nexta]-averts[starta];



                }
        }

        // If these two faces are in the same room and are on the same plane
        // then we should combine them by default

        if (aroom!=-1 && aroom==broom)
        {
                int dnv = 0;
                int match=0;

                // Test to see if any points at all can touch

                for (i=0; i<nva && !match; i++ )
                {
                        for (int t=0; t<nvb && !match; t++ )
                        {
                                if (PointsAreSame(&averts[i],&bverts[t]))
                                        match=1;
                        }
                }

                if (!match)
                        return 0;

                // At least one point is the same, so combine them!
                for (i=0; i<nva; i++ )
                {
                        dest_verts[dnv] = averts[i];
                        dnv++;
                        ASSERT(dnv < MAX_VERTS_PER_FACE * 5);
                }

                for (i=0; i<nvb; i++ )
                {
                        dest_verts[dnv] = bverts[i];
                        dnv++;
                        ASSERT(dnv < MAX_VERTS_PER_FACE * 5);
                }

                return dnv;
        }

        return 0;
}*/

// Returns number of verts in dest if face a can be safely combined with face b
// Returns 0 if not
int CombineLightFaces(vector *dest_verts, vector *averts, int nva, vector *norma, vector *bverts, int nvb,
                      vector *normb, int aroom, int broom) {
  int i;

  float dp;

  dp = vm_DotProduct(normb, norma);

  if (dp < LM_ADJACENT_FACE_THRESHOLD)
    return 0;

  ASSERT(nva > 2);
  ASSERT(nvb > 2);

  // Go through each vertex and get a match

  // If these two faces are in the same room and are on the same plane
  // then we should combine them by default

  if (1 || (aroom != -1 && aroom == broom)) {
    int dnv = 0;
    int match = 0;

    // Don't go over our point limit
    if ((nva + nvb) >= (MAX_VERTS_PER_FACE * 5))
      return 0;

    // Test to see if any points at all can touch

    for (i = 0; i < nva && !match; i++) {
      for (int t = 0; t < nvb && !match; t++) {
        if (PointsAreSame(&averts[i], &bverts[t]))
          match = 1;
      }
    }

    if (!match)
      return 0;

    // At least one point is the same, so combine them!
    for (i = 0; i < nva; i++) {
      dest_verts[dnv] = averts[i];
      dnv++;
      ASSERT(dnv < MAX_VERTS_PER_FACE * 5);
    }

    for (i = 0; i < nvb; i++) {
      dest_verts[dnv] = bverts[i];
      dnv++;
      ASSERT(dnv < MAX_VERTS_PER_FACE * 5);
    }

    return dnv;
  }

  return 0;
}

// Given a roomnumber and a face, goes through the entire mine and checks to see
// if this face can share a lightmap with any other face
int TestLightAdjacency(int roomnum, int facenum, int external) {

  int i, t, k;
  room *arp = &Rooms[roomnum];
  face *afp = &arp->faces[facenum];

  vector averts[MAX_VERTS_PER_FACE * 5];
  vector bverts[MAX_VERTS_PER_FACE * 5];
  vector dest_verts[MAX_VERTS_PER_FACE * 5];

  int face_combine_list[MAX_COMBINES];
  int room_combine_list[MAX_COMBINES];

  ASSERT(roomnum >= 0 && roomnum < MAX_ROOMS);

  if (!AllowCombining)
    return 0;

  // if (GameTextures[afp->tmap].r>0 || GameTextures[afp->tmap].g>0 || GameTextures[afp->tmap].b>0)
  //	return 0;

  // Don't combine portals!
  if (afp->portal_num != -1)
    return 0;

  // Setup our 'base' face

  int anv = afp->num_verts;
  int total_faces = 1;

  room_combine_list[0] = roomnum;
  face_combine_list[0] = facenum;

  for (i = 0; i < afp->num_verts; i++)
    averts[i] = arp->verts[afp->face_verts[i]];

StartOver:

  // Go through each room and find an adjacent face
  for (i = roomnum; i < MAX_ROOMS; i++) {
    if (!Rooms[i].used)
      continue;

    if (Rooms[i].flags & RF_NO_LIGHT)
      continue;

    if (external && !(Rooms[i].flags & RF_EXTERNAL))
      continue;

    if (!external && (Rooms[i].flags & RF_EXTERNAL))
      continue;

    /*if (i!=roomnum) // Only combine faces in the same room
            continue;*/

    for (t = 0; t < Rooms[i].num_faces; t++) {
      if (total_faces >= MAX_COMBINES - 1)
        continue;

      if (&Rooms[i] == arp && t == facenum)
        continue; // don't do self

      // Don't do if already spoken fore
      if (RoomsAlreadyCombined[i][t])
        continue;

      room *brp = &Rooms[i];
      face *bfp = &brp->faces[t];

      // Don't do combine light sources
      // if (GameTextures[bfp->tmap].r>0 || GameTextures[bfp->tmap].g>0 || GameTextures[bfp->tmap].b>0)
      //	continue;
      // Don't combine portals!
      if (bfp->portal_num != -1)
        continue;

      for (k = 0; k < bfp->num_verts; k++)
        bverts[k] = brp->verts[bfp->face_verts[k]];

      int nv =
          CombineLightFaces(dest_verts, averts, anv, &afp->normal, bverts, bfp->num_verts, &bfp->normal, roomnum, i);

      // We have a combine!  Mark this face in the appropriate list
      // And update our new polygon
      if (nv > 0) {
        room_combine_list[total_faces] = i;
        face_combine_list[total_faces] = t;
        total_faces++;

        RoomsAlreadyCombined[roomnum][facenum] = 1;
        RoomsAlreadyCombined[i][t] = 1;
        for (k = 0; k < nv; k++)
          averts[k] = dest_verts[k];

        anv = nv;

        goto StartOver;
      }
    }
  }

  // Now build 1 lightmap to be shared across all the faces that were combined
  if (total_faces > 1) {
    BuildLightmapUVs(room_combine_list, face_combine_list, total_faces, averts, anv, external);
  }

  return 1;
}

// Computes the the mines UVs
// Faces can now share one lightmap, so this routine goes through and tries to
// combine as many faces as it can into one lightmap
void ComputeAllRoomLightmapUVs(int external) {
  int i, t, k;
  int not_combined = 0;

  for (i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used) {
      if (Rooms[i].flags & RF_NO_LIGHT)
        continue;

      if (external && !(Rooms[i].flags & RF_EXTERNAL))
        continue;

      if (!external && (Rooms[i].flags & RF_EXTERNAL))
        continue;

      RoomsAlreadyCombined[i] = (uint8_t *)mem_malloc(Rooms[i].num_faces);
      ASSERT(RoomsAlreadyCombined[i]);
      for (k = 0; k < Rooms[i].num_faces; k++)
        RoomsAlreadyCombined[i][k] = 0;
    }
  }

  for (i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used) {
      if (Rooms[i].flags & RF_NO_LIGHT)
        continue;

      if (external && !(Rooms[i].flags & RF_EXTERNAL))
        continue;

      if (!external && (Rooms[i].flags & RF_EXTERNAL))
        continue;

      for (t = 0; t < Rooms[i].num_faces; t++) {
        if (*(RoomsAlreadyCombined[i] + t) == 0)
          TestLightAdjacency(i, t, external);
      }
    }
  }

  // Now build lightmaps for any faces that couldn't be combined
  for (i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used) {
      if (Rooms[i].flags & RF_NO_LIGHT)
        continue;

      if (external && !(Rooms[i].flags & RF_EXTERNAL))
        continue;

      if (!external && (Rooms[i].flags & RF_EXTERNAL))
        continue;

      for (t = 0; t < Rooms[i].num_faces; t++) {
        if (!RoomsAlreadyCombined[i][t]) {
          vector verts[MAX_VERTS_PER_FACE * 5];
          int room_list[2], face_list[2];
          for (k = 0; k < Rooms[i].faces[t].num_verts; k++)
            verts[k] = Rooms[i].verts[Rooms[i].faces[t].face_verts[k]];

          room_list[0] = i;
          face_list[0] = t;

          BuildLightmapUVs(room_list, face_list, 1, verts, Rooms[i].faces[t].num_verts, external);
          not_combined++;
        }
      }
    }
  }

  mprintf(0, "%d faces couldn't be combined!\n", not_combined);

  // Free memory
  for (i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used) {
      if (Rooms[i].flags & RF_NO_LIGHT)
        continue;

      if (external && !(Rooms[i].flags & RF_EXTERNAL))
        continue;

      if (!external && (Rooms[i].flags & RF_EXTERNAL))
        continue;

      mem_free(RoomsAlreadyCombined[i]);
    }
  }
}

//	Returns number preceding val modulo modulus.
//	prevmod(3,4) = 2
//	prevmod(0,4) = 3
int SpecularPrevIndex(int val, int modulus) {
  if (val > 0)
    return val - 1;
  else
    return modulus - 1;
}

//	Returns number succeeding val modulo modulus.
//	succmod(3,4) = 0
//	succmod(0,4) = 1
int SpecularNextIndex(int val, int modulus) {
  if (val < modulus - 1)
    return val + 1;
  else
    return 0;
}

// Gets the left top, left bottom, right top, and right bottom indices.  Also finds
// lowest y index.
void GetSpecularVertexOrdering(spec_vertex *verts, int nv, int *vlt, int *vlb, int *vrt, int *vrb, int *bottom_y_ind) {
  int i;
  float min_y, max_y;
  int min_y_ind;
  int original_vrt;
  float min_x;

  // Scan all vertices, set min_y_ind to vertex with smallest y coordinate.
  min_y = verts[0].y;
  max_y = min_y;
  min_y_ind = 0;
  min_x = verts[0].x;
  *bottom_y_ind = 0;

  for (i = 1; i < nv; i++) {
    if (verts[i].y < min_y) {
      min_y = verts[i].y;
      min_y_ind = i;
      min_x = verts[i].x;
    } else if (verts[i].y == min_y) {
      if (verts[i].x < min_x) {
        min_y_ind = i;
        min_x = verts[i].x;
      }
    }
    if (verts[i].y > max_y) {
      max_y = verts[i].y;
      *bottom_y_ind = i;
    }
  }

  // Set "vertex left top", etc. based on vertex with topmost y coordinate
  *vlt = min_y_ind;
  *vrt = *vlt;
  *vlb = SpecularPrevIndex(*vlt, nv);
  *vrb = SpecularNextIndex(*vrt, nv);

  // If right edge is horizontal, then advance along polygon bound until it no longer is or until all
  // vertices have been examined.
  // (Left edge cannot be horizontal, because *vlt is set to leftmost point with highest y coordinate.)

  original_vrt = *vrt;

  while (verts[*vrt].y == verts[*vrb].y) {
    if (SpecularNextIndex(*vrt, nv) == original_vrt)
      break;

    *vrt = SpecularNextIndex(*vrt, nv);
    *vrb = SpecularNextIndex(*vrt, nv);
  }
}

/*
// Creates a map that contains all the interpolated normals for a particular face
// Used in specular mapping
void CreateNormalMapForFace (room *rp,face *fp)
{
        int w=lmi_w(fp->lmi_handle);
        int h=lmi_h(fp->lmi_handle);
        special_face *sfp=&SpecialFaces[fp->special_handle];
        spec_vertex spec_verts[MAX_VERTS_PER_FACE];
        vector vertnorms[MAX_VERTS_PER_FACE];
        int vlt,vlb,vrt,vrb,max_y_vertex,top_y,bottom_y,height;
        int no_height=0,no_width=0,no_right=0,no_left=0;
        lightmap_info *lmi_ptr=&LightmapInfo[fp->lmi_handle];
        uint16_t *src_data=lm_data(lmi_ptr->lm_handle);

        matrix facematrix;
        vector fvec=-lmi_ptr->normal;
        vm_VectorToMatrix(&facematrix,&fvec,NULL,NULL);

        sfp->normal_map=(uint8_t *)mem_malloc (w*h*3);
        ASSERT (sfp->normal_map);

        for (int i=0;i<(w*h);i++)
        {
                sfp->normal_map[i*3+0]=0;
                sfp->normal_map[i*3+1]=0;
                sfp->normal_map[i*3+2]=128;
        }

        for (i=0;i<fp->num_verts;i++)
        {
                vm_MatrixMulVector (&vertnorms[i],&sfp->vertnorms[i],&facematrix);
                spec_verts[i].x=fp->face_uvls[i].u2;
                spec_verts[i].y=fp->face_uvls[i].v2;
        }

        GetSpecularVertexOrdering(spec_verts,fp->num_verts,&vlt,&vlb,&vrt,&vrb,&max_y_vertex);

        top_y=spec_verts[vlt].y*h;
        bottom_y=spec_verts[max_y_vertex].y*h;

        height = bottom_y-top_y;
   if(height == 0)
                no_height=1;

        // Setup left interpolant
        int left_height=((spec_verts[vlb].y-spec_verts[vlt].y)*(float)h);

        float left_x=spec_verts[vlt].x*w;
        float delta_left_x=((spec_verts[vlb].x-spec_verts[vlt].x)*w)/left_height;
        vector left_vnorm=vertnorms[vlt];
        vector delta_left_vnorm=(vertnorms[vlb]-vertnorms[vlt])/left_height;
        int next_break_left = spec_verts[vlb].y*h;

        // Setup right interpolant

        int right_height=((spec_verts[vrb].y-spec_verts[vrt].y)*(float)h);

        float right_x=spec_verts[vrt].x*w;
        float delta_right_x=((spec_verts[vrb].x-spec_verts[vrt].x)*w)/right_height;
        vector right_vnorm=vertnorms[vrt];
        vector delta_right_vnorm=(vertnorms[vrb]-vertnorms[vrt])/right_height;
        int next_break_right = spec_verts[vrb].y*h;

        // Do the loop
        for (int y=top_y;y<=bottom_y;y++)
        {
                if (y==next_break_left && !no_height)
                {
                        while (y == (int)(spec_verts[vlb].y*h))
                        {
                                vlt = vlb;
                                vlb = SpecularPrevIndex(vlb,fp->num_verts);
                        }

                        // Setup left interpolant
                        left_height=((spec_verts[vlb].y-spec_verts[vlt].y)*(float)h);
                        left_x=spec_verts[vlt].x*w;
                        delta_left_x=((spec_verts[vlb].x-spec_verts[vlt].x)*w)/left_height;
                        left_vnorm=vertnorms[vlt];
                        delta_left_vnorm=(vertnorms[vlb]-vertnorms[vlt])/left_height;
                        next_break_left = spec_verts[vlb].y*h;

                }

                if (y==next_break_right && !no_height)
                {
                        while (y == (int)(spec_verts[vrb].y*h))
                        {
                                vrt = vrb;
                                vrb = SpecularNextIndex(vrb,fp->num_verts);
                        }
                        right_height=((spec_verts[vrb].y-spec_verts[vrt].y)*(float)h);
                        right_x=spec_verts[vrt].x*w;
                        delta_right_x=((spec_verts[vrb].x-spec_verts[vrt].x)*w)/right_height;
                        right_vnorm=vertnorms[vrt];
                        delta_right_vnorm=(vertnorms[vrb]-vertnorms[vrt])/right_height;
                        next_break_right = spec_verts[vrb].y*h;
                }

                int width=(right_x-left_x)+1;

                vector delta_vnorm=(right_vnorm-left_vnorm)/width;
                vector vnorm=left_vnorm;

                for (i=left_x;i<(left_x+width);i++)
                {
                        int offset=y*w+i;

                        if (!(src_data[offset] & OPAQUE_FLAG))
                                continue;

                        sfp->normal_map[(offset*3)+0]=((vnorm.x+1.0)/2.0)*255.0;
                        sfp->normal_map[(offset*3)+1]=((vnorm.y+1.0)/2.0)*255.0;
                        sfp->normal_map[(offset*3)+2]=((vnorm.z+1.0)/2.0)*255.0;
                        vnorm+=delta_vnorm;
                }

                // Update our interpolants
                left_x += delta_left_x;
                right_x += delta_right_x;
                right_vnorm+=delta_right_vnorm;
                left_vnorm+=delta_left_vnorm;
        }
}*/

// Frees memory for specular lighting
void CleanupSpecularLighting(int external) {
  int i, t;

  for (i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used) {
      if (Rooms[i].flags & RF_NO_LIGHT)
        continue;

      if ((Rooms[i].flags & RF_EXTERNAL) && !external)
        continue;

      if (!(Rooms[i].flags & RF_EXTERNAL) && external)
        continue;

      room *rp = &Rooms[i];

      for (t = 0; t < 4; t++) {
        mem_free(Room_strongest_value[i][t]);
      }

      for (t = 0; t < rp->num_faces; t++) {
        face *fp = &rp->faces[t];
        if (GameTextures[fp->tmap].flags & TF_SPECULAR) {
          ASSERT(fp->special_handle != BAD_SPECIAL_FACE_INDEX);
          int j, k, l;

          if (SpecialFaces[fp->special_handle].spec_instance[0].bright_color == 0) {
            // This face didn't get enough light, either free it up or
            // find another face that shares a lightmap and just use that
            // faces values

            int found = 0;
            float strongest_light = 0;
            face *best_face;

            for (l = 0; l < fp->num_verts; l++) {
              int vert_to_check = fp->face_verts[l];
              for (j = 0; j < rp->num_faces; j++) {
                face *this_fp = &rp->faces[j];

                if (this_fp == fp)
                  continue; // don't do self
                if (this_fp->lmi_handle != fp->lmi_handle)
                  continue; // only do faces that have same lightmaps

                if (this_fp->special_handle == BAD_SPECIAL_FACE_INDEX)
                  continue; // Only do specmaps

                if (SpecialFaces[this_fp->special_handle].spec_instance[0].bright_color == 0)
                  continue; // Don't do dark ones

                if (GameTextures[fp->tmap].flags & TF_SMOOTH_SPECULAR)
                  continue; // Don't do smooth shades

                for (k = 0; k < this_fp->num_verts; k++) {
                  if (vert_to_check == this_fp->face_verts[k]) {
                    // We have a match, so see if this face is brighter
                    // than all the others
                    int color = SpecialFaces[this_fp->special_handle].spec_instance[0].bright_color;
                    int r = (color >> 10) & 0x1f;
                    int g = (color >> 5) & 0x1f;
                    int b = (color & 0x1f);
                    float sr = (float)r / 31.0;
                    float sg = (float)g / 31.0;
                    float sb = (float)b / 31.0;

                    float mono_color = (sr * .3) + (sg * .59) + (sb * .11);
                    if (mono_color > strongest_light) {
                      strongest_light = mono_color;
                      found = 1;
                      best_face = this_fp;
                    }
                  }
                }
              }
            }
            // If after all that searching we couldn't find a good light, free it!
            if (!found) {
              FreeSpecialFace(fp->special_handle);
              fp->special_handle = BAD_SPECIAL_FACE_INDEX;
            } else {
              for (int z = 0; z < 4; z++)
                SpecialFaces[fp->special_handle].spec_instance[z] =
                    SpecialFaces[best_face->special_handle].spec_instance[z];
            }
          }
        }
      }
    }
  }
}

// Sets up memory usage for specular lighting
void SetupSpecularLighting(int external) {
  int i, t;
  ClearAllRoomSpecmaps(external);

  for (i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used) {
      if (Rooms[i].flags & RF_NO_LIGHT)
        continue;

      if ((Rooms[i].flags & RF_EXTERNAL) && !external)
        continue;

      if (!(Rooms[i].flags & RF_EXTERNAL) && external)
        continue;

      room *rp = &Rooms[i];

      // Calculate vertex normals for this room
      vector *vertnorms = (vector *)mem_malloc(sizeof(vector) * rp->num_verts);
      ASSERT(vertnorms);
      for (t = 0; t < rp->num_verts; t++) {
        int total = 0;
        vector normal;
        vm_MakeZero(&normal);

        for (int k = 0; k < rp->num_faces; k++) {
          face *fp = &rp->faces[k];
          for (int j = 0; j < fp->num_verts; j++) {
            if (fp->face_verts[j] == t) {
              total++;
              normal += fp->normal;
            }
          }
        }

        if (total != 0)
          normal /= total;

        vertnorms[t] = normal;
      }

      for (t = 0; t < 4; t++) {
        Room_strongest_value[i][t] = (float *)mem_malloc(sizeof(float) * rp->num_faces);
        ASSERT(Room_strongest_value[i][t]);
        memset(Room_strongest_value[i][t], 0, sizeof(float) * rp->num_faces);
      }

      for (t = 0; t < rp->num_faces; t++) {
        face *fp = &rp->faces[t];
        if (GameTextures[fp->tmap].flags & TF_SPECULAR) {
          ASSERT(fp->special_handle == BAD_SPECIAL_FACE_INDEX);

          int n;
          if (GameTextures[fp->tmap].flags & TF_SMOOTH_SPECULAR)
            n = AllocSpecialFace(SFT_SPECULAR, 4, true, fp->num_verts);
          else
            n = AllocSpecialFace(SFT_SPECULAR, 4);

          fp->special_handle = n;

          for (int k = 0; k < 4; k++) {
            SpecialFaces[n].spec_instance[k].bright_color = 0;
            vm_MakeZero(&SpecialFaces[n].spec_instance[k].bright_center);
          }

          // Get vertex normals for this punk
          if (GameTextures[fp->tmap].flags & TF_SMOOTH_SPECULAR) {
            for (k = 0; k < fp->num_verts; k++) {
              SpecialFaces[n].vertnorms[k] = vertnorms[fp->face_verts[k]];
            }
          }
        }
      }
      mem_free(vertnorms);
    }
  }
}

// Computes the the mines UVs
// Faces can now share one lightmap, so this routine goes through and tries to
// combine as many faces as it can into one lightmap
// Only works on one room
/*void ComputeSingleRoomLightmapUVs (int roomnum)
{
        int t,k;
        int not_combined=0;

        RoomsAlreadyCombined[roomnum]=(uint8_t *)mem_malloc (Rooms[roomnum].num_faces);
        ASSERT (RoomsAlreadyCombined[roomnum]);
        for (k=0;k<Rooms[roomnum].num_faces;k++)
                RoomsAlreadyCombined[roomnum][k]=0;


        for (t=0;t<Rooms[roomnum].num_faces;t++)
        {
                if (*(RoomsAlreadyCombined[roomnum]+t)==0)
                        TestLightAdjacency (roomnum,t,0);
        }

        for (t=0;t<Rooms[roomnum].num_faces;t++)
        {
                if (!RoomsAlreadyCombined[roomnum][t])
                {
                        vector verts[MAX_VERTS_PER_FACE];
                        int room_list[2],face_list[2];
                        for (k=0;k<Rooms[roomnum].faces[t].num_verts;k++)
                                verts[k]=Rooms[roomnum].verts[Rooms[roomnum].faces[t].face_verts[k]];

                        room_list[0]=i;
                        face_list[0]=t;

                        BuildLightmapUVs (room_list,face_list,1,verts,Rooms[roomnum].faces[t].num_verts,external);
                        not_combined++;
                }
        }

        mprintf(0,"%d faces couldn't be combined!\n",not_combined);

        // Free memory
        free (RoomsAlreadyCombined[roomnum]);
}*/
