/*
* 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/LightingDialog.cpp $
 * $Revision: 1.1.1.1 $
 * $Date: 2003-08-26 03:57:38 $
 * $Author: kevinb $
 *
 * Lighting keypad dialog
 *
 * $Log: not supported by cvs2svn $
 *
 * 67    9/13/99 12:24p Matt
 * Changed the copy-light-multiple function to work either in the whole
 * mine or the current room.
 *
 * 66    8/17/99 4:09p Matt
 * Added some accelerator keys, and a function to copy face light
 * multiples.
 *
 * 65    5/05/99 12:58a Matt
 * Added button to clear the lightmaps on a level.
 *
 * 64    4/18/99 5:42a Chris
 * Added the FQ_IGNORE_RENDER_THROUGH_PORTALS flag
 *
 * 63    4/03/99 7:55p Matt
 * Use new keypad update system instead of timer to update.
 *
 * 62    3/24/99 5:53p Jason
 * added per room ambience settings
 *
 * 61    3/23/99 5:13p Jason
 * fixed counting of invisible terrain cells
 *
 * 60    2/18/99 12:32p Jason
 * added room multiplier
 *
 * 59    2/05/99 4:54p Jason
 * added extra option to reset multiples
 *
 * 58    1/21/99 12:22p Jason
 * adding strobing and flickering
 *
 * 57    1/20/99 10:50a Jason
 * added new terrain
 *
 * 56    1/15/99 3:15p Jason
 * fixed global multilier bug
 *
 * 55    1/15/99 2:33p Jason
 * added global multiplier
 *
 * 54    10/06/98 11:48a Jason
 * fixed bug with last rev
 *
 * 53    10/06/98 11:45a Jason
 * added extra feature for Sean
 *
 * 52    8/04/98 11:52a Jason
 * fixed stupid bug
 *
 * 51    8/04/98 11:52a Jason
 * added reverse corona check
 *
 * 50    7/24/98 4:44p Jason
 * added corona button functionality
 *
 * 49    6/22/98 12:00p Chris
 * Working on sound system to make it in a nice shape.
 *
 * 48    6/16/98 3:48p Chris
 * Updated the sound system and added the start of sound streaming
 *
 * 47    6/05/98 5:22p Jason
 * added volumetric fog
 *
 * 46    5/26/98 5:56p Jason
 * only render coronas which are flagged
 *
 * 45    5/22/98 3:28p Jason
 * added specular lighting
 *
 * 44    5/20/98 5:44p Jason
 * incremental checkin for bumpmapping
 *
 * 43    4/29/98 2:19p Jason
 * added yes/no question to confirm lighting
 *
 * 42    4/24/98 12:58p Jason
 * added functions to resave all bitmaps
 *
 * 41    4/21/98 2:36p Jason
 * added some lighting options
 *
 * 40    4/02/98 12:23p Jason
 * trimmed some fat from our structures
 *
 * 39    3/24/98 10:40p Jason
 * made closer lightmap spacing
 *
 * 38    3/16/98 9:55p Chris
 * Working on optimizing fvi
 *
 * 37    2/26/98 10:48a Jason
 * fixed floating point/int problem in inputting face light multiplier
 *
 * 36    2/23/98 7:35p Chris
 * Made the rad. floating point
 *
 * 35    2/16/98 2:12p Jason
 * fixed bug with lightmaps and door shells
 *
 * 34    2/13/98 12:57p Jason
 * changes for adjusting light multiples
 *
 * 33    2/10/98 5:09p Jason
 * added a SetRoomPulse function for settinng pulse parameters for an
 * entire room
 *
 * 32    2/10/98 3:50p Jason
 * added pulsing walls
 *
 * 31    2/04/98 8:25p Jason
 * added light multiplier for faces
 *
 * 30    2/04/98 6:23p Matt
 * Changed object room number to indicate a terrain cell via a flag.  Got
 * rid of the object flag which used to indicate terrain.
 *
 * 29    1/15/98 7:34p Matt
 * Revamped error checking when computing face normals
 *
 * 28    1/02/98 5:32p Chris
 * More radical changes to the sound system
 *
 * 27    12/31/97 12:51p Jason
 * debugged and implementd BSP tree for lighting
 *
 * 26    12/18/97 12:24p Chris
 * Turned off sound system while doing casting rays.
 *
 * 25    12/16/97 4:17p Jason
 * added ignore_satellites and ignore terrain
 *
 * 24    12/10/97 12:23p Jason
 * added "SuperDetail"
 *
 *
 * 23    12/05/97 12:34p Jason
 * added ambient light to the terrain
 *
 * 22    11/28/97 6:41p Jason
 * added functions to blur lightmaps - not very helpful though
 *
 * 21    11/25/97 12:28p Jason
 * another pass at shadows
 *
 * 20    11/19/97 12:39p Jason
 * added dynamic terrain button
 *
 * 19    11/14/97 6:38p Jason
 * added ability to do lighting on a single room
 *
 * 18    11/14/97 1:13p Jason
 * added better light counting
 *
 * 17    10/28/97 3:41p Jason
 * added "use hemicubes" option
 *
 * 16    10/10/97 2:34p Jason
 * tweaked volumetrics
 *
 * 15    10/10/97 1:10p Jason
 * more support for volumetrics
 *
 * 14    10/10/97 11:38a Jason
 * put in better volumetric support
 *
 * 13    9/26/97 11:51a Jason
 * tweaked terrain radiosity
 *
 * 12    9/25/97 4:47p Jason
 * update alpha edit box via timer
 *
 * 11    9/23/97 5:53p Jason
 * added more custom options to radiosity
 *
 * 10    9/09/97 12:22p Matt
 * Renamed a flag
 *
 * 9     9/04/97 2:54p Jason
 * fixed small radiosity bug that wasn't flagging invisible elements
 *
 * 8     9/04/97 12:12p Jason
 * added lightmap visibility
 *
 * 7     9/02/97 12:54p Jason
 * classify faces as alphaed or not
 *
 * 6     9/02/97 11:47a Jason
 * Got alpha per vertex working
 *
 * 5     8/28/97 2:59p Jason
 * added confirmation for terrain radiosity
 *
 * 4     8/27/97 5:19p Jason
 * added "Count lights"
 *
 * 3     8/26/97 4:36p Jason
 * added terrain radiosity
 *
 * 2     8/12/97 1:10a Jason
 * added code to support radiosity lighting
 *
 * 8     6/03/97 4:55p Mark
 *
 * 8	6/03/97	4:53p	Jeff
 *	Added Context Sensitive Help
 *
 * 7     5/30/97 5:45p Jason
 * changes to support user-definable lighting parameters
 *
 * 6     5/23/97 10:48a Jason
 * checked in for safekeeping...
 *
 * 5     2/20/97 2:37p Mark
 *
 * 4     1/27/97 11:38a Samir
 * Added horizontal scrolling of keypad modeless dialog.
 *
 * $NoKeywords: $
 */

#include "stdafx.h"
#include "editor.h"
#include "LightingDialog.h"
#include "editor_lighting.h"
#include "gametexture.h"
#include "radiosity.h"
#include "hemicube.h"
#include "findintersection.h"
#include "erooms.h"
#include "vecmat.h"
#include "polymodel.h"
#include "lightmap_info.h"
#include "hlsoundlib.h"
#include "lighting.h"
#include "bsp.h"
#include "ddio.h"
#include "vclip.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CLightingDialog dialog

CLightingDialog::CLightingDialog(CWnd *pParent /*=NULL*/) : CKeypadDialog(CLightingDialog::IDD, pParent) {
  //{{AFX_DATA_INIT(CLightingDialog)
  // NOTE: the ClassWizard will add member initialization here
  //}}AFX_DATA_INIT
}

void CLightingDialog::DoDataExchange(CDataExchange *pDX) {
  CDialog::DoDataExchange(pDX);
  //{{AFX_DATA_MAP(CLightingDialog)
  // NOTE: the ClassWizard will add DDX and DDV calls here
  //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CLightingDialog, CDialog)
//{{AFX_MSG_MAP(CLightingDialog)
ON_WM_SIZE()
ON_WM_VSCROLL()
ON_WM_HSCROLL()
ON_BN_CLICKED(IDC_LITEPAD_SETDEFAULT, OnLitepadSetdefault)
ON_BN_CLICKED(IDC_LITEPAD_SMOOTHALL, OnLitepadSmoothall)
ON_EN_KILLFOCUS(IDC_LIGHT_ITERATION_EDIT, OnKillfocusLightIterationEdit)
ON_WM_HELPINFO()
ON_BN_CLICKED(IDC_LITEPAD_TERRAIN_RADIOSITY, OnLitepadTerrainRadiosity)
ON_BN_CLICKED(IDC_LITEPAD_COUNT_LIGHTS, OnLitepadCountLights)
ON_BN_CLICKED(IDC_LITEPAD_NEXTVERTEX, OnLitepadNextvertex)
ON_WM_PAINT()
ON_EN_KILLFOCUS(IDC_ALPHA_EDIT, OnKillfocusAlphaEdit)
ON_BN_CLICKED(IDC_SHOW_LIGHTMAPS, OnShowLightmaps)
ON_EN_KILLFOCUS(IDC_HEMICUBE_RES_EDIT, OnKillfocusHemicubeResEdit)
ON_EN_KILLFOCUS(IDC_LIGHT_SPACING_EDIT, OnKillfocusLightSpacingEdit)
ON_BN_CLICKED(IDC_BESTFIT_CHECK, OnBestfitCheck)
ON_BN_CLICKED(IDC_CREATE_VOLUME_CONE, OnCreateVolumeCone)
ON_BN_CLICKED(IDC_CLEAR_VOLUMETRIC_ROOM, OnClearVolumetricRoom)
ON_BN_CLICKED(IDC_FILL_VOLUME_TEXTURE, OnFillVolumeTexture)
ON_EN_KILLFOCUS(IDC_ANGLE_EDIT, OnKillfocusAngleEdit)
ON_EN_KILLFOCUS(IDC_START_ALPHA_EDIT, OnKillfocusStartAlphaEdit)
ON_EN_KILLFOCUS(IDC_RESOLUTION_EDIT, OnKillfocusResolutionEdit)
ON_BN_CLICKED(IDC_EDGE_VOLUMETRIC, OnEdgeVolumetric)
ON_EN_KILLFOCUS(IDC_FALLOFF_EDIT, OnKillfocusFalloffEdit)
ON_BN_CLICKED(IDC_FARTHEST_POINT, OnFarthestPoint)
ON_EN_KILLFOCUS(IDC_PITCH_ANGLE_EDIT, OnKillfocusPitchAngleEdit)
ON_EN_KILLFOCUS(IDC_HEADING_ANGLE_EDIT, OnKillfocusHeadingAngleEdit)
ON_BN_CLICKED(IDC_HEMICUBE_CHECK, OnHemicubeCheck)
ON_BN_CLICKED(IDC_COUNT_TERRAIN_LIGHTS, OnCountTerrainLights)
ON_BN_CLICKED(IDC_CURROOM_RADIOSITY, OnCurroomRadiosity)
ON_BN_CLICKED(IDC_DYNAMIC_TERRAIN, OnDynamicTerrain)
ON_BN_CLICKED(IDC_BLUR_LIGHTMAPS, OnBlurLightmaps)
ON_EN_KILLFOCUS(IDC_IGNORE_LIMIT_EDIT, OnKillfocusIgnoreLimitEdit)
ON_EN_KILLFOCUS(IDC_RED_AMBIENT_EDIT, OnKillfocusRedAmbientEdit)
ON_EN_KILLFOCUS(IDC_GREEN_AMBIENT_EDIT, OnKillfocusGreenAmbientEdit)
ON_EN_KILLFOCUS(IDC_BLUE_AMBIENT_EDIT, OnKillfocusBlueAmbientEdit)
ON_BN_CLICKED(IDC_ELEMENT_CHECK, OnElementCheck)
ON_BN_CLICKED(IDC_IGNORE_TERRAIN, OnIgnoreTerrain)
ON_BN_CLICKED(IDC_IGNORE_SATELLITES, OnIgnoreSatellites)
ON_BN_CLICKED(IDC_USE_BSP_CHECK, OnUseBspCheck)
ON_EN_KILLFOCUS(IDC_LIGHT_MULTIPLY_EDIT, OnKillfocusLightMultiplyEdit)
ON_EN_KILLFOCUS(IDC_PULSE_TIME_EDIT, OnKillfocusPulseTimeEdit)
ON_EN_KILLFOCUS(IDC_PULSE_OFFSET_EDIT, OnKillfocusPulseOffsetEdit)
ON_BN_CLICKED(IDC_PULSE_ROOM, OnPulseRoom)
ON_BN_CLICKED(IDC_RESET_MULTIPLES, OnResetMultiples)
ON_BN_CLICKED(IDC_DESTROY_BSP, OnDestroyBsp)
ON_BN_CLICKED(IDC_COMBINE_FACES, OnCombineFaces)
ON_BN_CLICKED(IDC_VOLUME_LIGHTS, OnVolumeLights)
ON_BN_CLICKED(IDC_CORONA_CHECK, OnCoronaCheck)
ON_BN_CLICKED(IDC_FOG_CHECK, OnFogCheck)
ON_EN_KILLFOCUS(IDC_FOG_DENSITY, OnKillfocusFogDensity)
ON_EN_KILLFOCUS(IDC_FOG_RED, OnKillfocusFogRed)
ON_EN_KILLFOCUS(IDC_FOG_GREEN, OnKillfocusFogGreen)
ON_EN_KILLFOCUS(IDC_FOG_BLUE, OnKillfocusFogBlue)
ON_BN_CLICKED(IDC_ROOM_CORONAS, OnRoomCoronas)
ON_BN_CLICKED(IDC_MINE_CORONAS, OnMineCoronas)
ON_BN_CLICKED(IDC_REVERSE_CORONA, OnReverseCorona)
ON_EN_KILLFOCUS(IDC_GLOBAL_MULTIPLIER_EDIT, OnKillfocusGlobalMultiplierEdit)
ON_BN_CLICKED(IDC_STROBE_CHECK, OnStrobeCheck)
ON_BN_CLICKED(IDC_FLICKER_CHECK, OnFlickerCheck)
ON_EN_KILLFOCUS(IDC_ROOM_MULTIPLIER_EDIT, OnKillfocusRoomMultiplierEdit)
ON_EN_KILLFOCUS(IDC_ROOM_AMRED, OnKillfocusRoomAmred)
ON_EN_KILLFOCUS(IDC_ROOM_AMGREEN, OnKillfocusRoomAmgreen)
ON_EN_KILLFOCUS(IDC_ROOM_AMBLUE, OnKillfocusRoomAmblue)
ON_BN_CLICKED(IDC_CLEAR_LIGHTMAPS, OnClearLightmaps)
ON_BN_CLICKED(IDC_COPY_MULTIPLIER, OnCopyMultiplier)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CLightingDialog message handlers

void CLightingDialog::OnSize(UINT nType, int cx, int cy) { CKeypadDialog::OnSize(nType, cx, cy); }

void CLightingDialog::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar) {
  // TODO: Add your message handler code here and/or call default

  CKeypadDialog::OnVScroll(nSBCode, nPos, pScrollBar);
}

void CLightingDialog::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar) {
  // TODO: Add your message handler code here and/or call default

  CKeypadDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}

void CLightingDialog::OnLitepadSetdefault() {
  int i, alphaed = 0;

  Curroomp->faces[Curface].face_uvls[Curvert].alpha = 255;

  for (i = 0; i < Curroomp->faces[Curface].num_verts; i++)
    if (Curroomp->faces[Curface].face_uvls[i].alpha != 255)
      alphaed = 1;

  if (alphaed)
    Curroomp->faces[Curface].flags |= FF_VERTEX_ALPHA;
  else
    Curroomp->faces[Curface].flags &= ~FF_VERTEX_ALPHA;

  World_changed = 1;
  UpdateDialog();
}

void CLightingDialog::OnLitepadSmoothall() {
  int answer =
      MessageBox("Are you sure you wish to calculate lighting on the indoor mine?", "Light Question", MB_YESNO);
  if (answer == IDNO)
    return;

  MessageBox("I will now calculate radiosity for the indoor engine.  You may prematurely break out by pressing any key "
             "during the calculations",
             "Message", MB_OK);

  DoRadiosityForRooms();
  World_changed = 1;
}

void CLightingDialog::OnKillfocusLightIterationEdit() {
  CEdit *ebox;
  char str[20];

  ebox = (CEdit *)GetDlgItem(IDC_LIGHT_ITERATION_EDIT);
  ebox->GetWindowText(str, 20);

  rad_MaxStep = atoi(str);
}

BOOL CLightingDialog::OnInitDialog() {
  CDialog::OnInitDialog();

  m_angle = 30;
  m_resolution = 50;
  m_start_alpha = 1.0;
  m_falloff = 20;
  m_pitch_angle = 0;
  m_heading_angle = 0;
  UpdateDialog();

  return TRUE; // return TRUE unless you set the focus to a control
               // EXCEPTION: OCX Property Pages should return FALSE
}

BOOL CLightingDialog::OnHelpInfo(HELPINFO *pHelpInfo) {
  // TODO: Add your message handler code here and/or call default
  WinHelp(HID_LIGHTINGTAB, HELP_CONTEXT);
  return TRUE;
  // return CDialog::OnHelpInfo(pHelpInfo);
}

void CLightingDialog::OnLitepadTerrainRadiosity() {
  int answer = MessageBox("Are you sure you wish to calculate lighting on the terrain?", "Light Question", MB_YESNO);
  if (answer == IDNO)
    return;

  Sound_system.KillSoundLib(false);

  MessageBox("I will now calculate radiosity for the terrain engine.  You may prematurely break out by pressing any "
             "key during the calculations",
             "Message", MB_OK);
  DoRadiosityForTerrain();
  World_changed = 1;

  Sound_system.InitSoundLib(Descent, Sound_mixer, Sound_quality, false);
}

void CLightingDialog::OnLitepadCountLights() {
  int lightcount, i, t;
  char str[1000];

  for (lightcount = 0, i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used && !(Rooms[i].flags & RF_EXTERNAL)) {
      for (t = 0; t < Rooms[i].num_faces; t++) {
        int texnum = Rooms[i].faces[t].tmap;
        if (GameTextures[texnum].r > 0 || GameTextures[texnum].g > 0 || GameTextures[texnum].b > 0)
          lightcount++;
      }
    }
  }

  for (i = 0; i <= Highest_object_index; i++) {
    if (OBJECT_OUTSIDE(&Objects[i]))
      continue;

    if (Objects[i].type != OBJ_NONE && Objects[i].lighting_render_type == LRT_LIGHTMAPS) {
      poly_model *po = &Poly_models[Objects[i].rtype.pobj_info.model_num];

      if (!po->new_style)
        continue;

      for (t = 0; t < po->n_models; t++) {
        bsp_info *sm = &po->submodel[t];

        if (IsNonRenderableSubmodel(po, t))
          continue;

        for (int j = 0; j < sm->num_faces; j++) {
          if (sm->faces[j].texnum == -1)
            continue;

          int texnum = po->textures[sm->faces[j].texnum];
          if (GameTextures[texnum].r > 0 || GameTextures[texnum].g > 0 || GameTextures[texnum].b > 0)
            lightcount++;
        }
      }
    }
  }

  sprintf(str,
          "There are %d lights present in the mine.  Do you wish for me to set the 'light iterations' control to match "
          "this number?",
          lightcount);
  int answer = MessageBox(str, "Light Question", MB_YESNO);
  if (answer == IDNO)
    return;

  CEdit *ebox;

  ebox = (CEdit *)GetDlgItem(IDC_LIGHT_ITERATION_EDIT);
  sprintf(str, "%d", lightcount);
  ebox->SetWindowText(str);

  rad_MaxStep = lightcount;
}

void CLightingDialog::OnLitepadNextvertex() {
  if (++Curvert >= Curroomp->faces[Curface].num_verts)
    Curvert = 0;

  EditorStatus("Vertex %d selected.", Curvert);
  UpdateDialog();

  State_changed = 1;
}

extern float GlobalMultiplier;
void CLightingDialog::UpdateDialog() {
  CEdit *ebox;
  float aval;
  char str[100];

  // Do volumetrics
  if (Curroomp && Curroomp->used) {
    aval = Ubyte_to_float[Curroomp->faces[Curface].face_uvls[Curvert].alpha];

    ebox = (CEdit *)GetDlgItem(IDC_ALPHA_EDIT);
    sprintf(str, "%f", aval);
    ebox->SetWindowText(str);

    uint8_t pval = Curroomp->pulse_time;

    ebox = (CEdit *)GetDlgItem(IDC_PULSE_TIME_EDIT);
    sprintf(str, "%d", pval);
    ebox->SetWindowText(str);

    pval = Curroomp->pulse_offset;

    ebox = (CEdit *)GetDlgItem(IDC_PULSE_OFFSET_EDIT);
    sprintf(str, "%d", pval);
    ebox->SetWindowText(str);

    ebox = (CEdit *)GetDlgItem(IDC_FOG_DENSITY);
    sprintf(str, "%f", Curroomp->fog_depth);
    ebox->SetWindowText(str);

    ebox = (CEdit *)GetDlgItem(IDC_FOG_RED);
    sprintf(str, "%f", Curroomp->fog_r);
    ebox->SetWindowText(str);

    ebox = (CEdit *)GetDlgItem(IDC_FOG_GREEN);
    sprintf(str, "%f", Curroomp->fog_g);
    ebox->SetWindowText(str);

    ebox = (CEdit *)GetDlgItem(IDC_FOG_BLUE);
    sprintf(str, "%f", Curroomp->fog_b);
    ebox->SetWindowText(str);
  } else {
    ebox = (CEdit *)GetDlgItem(IDC_ALPHA_EDIT);
    ebox->SetWindowText("No room selected");
  }

  // Set element check
  CheckDlgButton(IDC_ELEMENT_CHECK, Shoot_from_patch ? 0 : 1);
  CheckDlgButton(IDC_COMBINE_FACES, AllowCombining);
  CheckDlgButton(IDC_IGNORE_TERRAIN, Ignore_terrain);
  CheckDlgButton(IDC_IGNORE_SATELLITES, Ignore_satellites);
  CheckDlgButton(IDC_USE_BSP_CHECK, UseBSP);
  CheckDlgButton(IDC_VOLUME_LIGHTS, UseVolumeLights);

  if (Curroomp == NULL) {
    CheckDlgButton(IDC_CORONA_CHECK, 0);
    CheckDlgButton(IDC_FOG_CHECK, 0);
    CheckDlgButton(IDC_STROBE_CHECK, 0);
    CheckDlgButton(IDC_FLICKER_CHECK, 0);

  } else {
    CheckDlgButton(IDC_CORONA_CHECK, (Curroomp->faces[Curface].flags & FF_CORONA) ? 1 : 0);
    CheckDlgButton(IDC_FOG_CHECK, (Curroomp->flags & RF_FOG) ? 1 : 0);
    CheckDlgButton(IDC_STROBE_CHECK, (Curroomp->flags & RF_STROBE) ? 1 : 0);
    CheckDlgButton(IDC_FLICKER_CHECK, (Curroomp->flags & RF_FLICKER) ? 1 : 0);

    ebox = (CEdit *)GetDlgItem(IDC_ROOM_MULTIPLIER_EDIT);
    sprintf(str, "%f", Room_multiplier[Curroomp - Rooms]);
    ebox->SetWindowText(str);

    ebox = (CEdit *)GetDlgItem(IDC_ROOM_AMRED);
    sprintf(str, "%f", Room_ambience_r[Curroomp - Rooms]);
    ebox->SetWindowText(str);

    ebox = (CEdit *)GetDlgItem(IDC_ROOM_AMGREEN);
    sprintf(str, "%f", Room_ambience_g[Curroomp - Rooms]);
    ebox->SetWindowText(str);

    ebox = (CEdit *)GetDlgItem(IDC_ROOM_AMBLUE);
    sprintf(str, "%f", Room_ambience_b[Curroomp - Rooms]);
    ebox->SetWindowText(str);
  }

  // Do Global multiplier
  ebox = (CEdit *)GetDlgItem(IDC_GLOBAL_MULTIPLIER_EDIT);
  sprintf(str, "%f", GlobalMultiplier);
  ebox->SetWindowText(str);

  // Do maxsteps for radiosity
  ebox = (CEdit *)GetDlgItem(IDC_LIGHT_ITERATION_EDIT);
  sprintf(str, "%d", rad_MaxStep);
  ebox->SetWindowText(str);

  ebox = (CEdit *)GetDlgItem(IDC_HEMICUBE_RES_EDIT);
  sprintf(str, "%d", Hemicube_resolution);
  ebox->SetWindowText(str);

  ebox = (CEdit *)GetDlgItem(IDC_LIGHT_SPACING_EDIT);
  sprintf(str, "%d", LightSpacing);
  ebox->SetWindowText(str);

  ebox = (CEdit *)GetDlgItem(IDC_ANGLE_EDIT);
  sprintf(str, "%d", m_angle);
  ebox->SetWindowText(str);

  ebox = (CEdit *)GetDlgItem(IDC_RESOLUTION_EDIT);
  sprintf(str, "%d", m_resolution);
  ebox->SetWindowText(str);

  ebox = (CEdit *)GetDlgItem(IDC_START_ALPHA_EDIT);
  sprintf(str, "%f", m_start_alpha);
  ebox->SetWindowText(str);

  ebox = (CEdit *)GetDlgItem(IDC_FALLOFF_EDIT);
  sprintf(str, "%f", m_falloff);
  ebox->SetWindowText(str);

  ebox = (CEdit *)GetDlgItem(IDC_PITCH_ANGLE_EDIT);
  sprintf(str, "%d", m_pitch_angle);
  ebox->SetWindowText(str);

  ebox = (CEdit *)GetDlgItem(IDC_HEADING_ANGLE_EDIT);
  sprintf(str, "%d", m_heading_angle);
  ebox->SetWindowText(str);

  ebox = (CEdit *)GetDlgItem(IDC_IGNORE_LIMIT_EDIT);
  sprintf(str, "%f", Ignore_limit);
  ebox->SetWindowText(str);

  ebox = (CEdit *)GetDlgItem(IDC_RED_AMBIENT_EDIT);
  sprintf(str, "%.2f", Ambient_red);
  ebox->SetWindowText(str);

  ebox = (CEdit *)GetDlgItem(IDC_GREEN_AMBIENT_EDIT);
  sprintf(str, "%.2f", Ambient_green);
  ebox->SetWindowText(str);

  ebox = (CEdit *)GetDlgItem(IDC_BLUE_AMBIENT_EDIT);
  sprintf(str, "%.2f", Ambient_blue);
  ebox->SetWindowText(str);

  ebox = (CEdit *)GetDlgItem(IDC_LIGHT_MULTIPLY_EDIT);
  sprintf(str, "%.2f", (Curroomp->faces[Curface].light_multiple) / 4.0);
  ebox->SetWindowText(str);
}

void CLightingDialog::OnPaint() {
  CPaintDC dc(this); // device context for painting

  UpdateDialog();

  // Do not call CDialog::OnPaint() for painting messages
}

void CLightingDialog::OnKillfocusAlphaEdit() {
  CEdit *ebox;
  char str[40];
  float aval;
  int i, alphaed = 0;

  ebox = (CEdit *)GetDlgItem(IDC_ALPHA_EDIT);
  ebox->GetWindowText(str, 20);

  aval = atof(str);
  if (aval < 0)
    aval = 0;
  if (aval > 1.0)
    aval = 1.0;

  Curroomp->faces[Curface].face_uvls[Curvert].alpha = Float_to_ubyte(aval);

  for (i = 0; i < Curroomp->faces[Curface].num_verts; i++)
    if (Curroomp->faces[Curface].face_uvls[i].alpha != 255)
      alphaed = 1;

  if (alphaed)
    Curroomp->faces[Curface].flags |= FF_VERTEX_ALPHA;
  else
    Curroomp->faces[Curface].flags &= ~FF_VERTEX_ALPHA;

  UpdateDialog();
  World_changed = 1;
}

extern bool Outline_lightmaps;
void CLightingDialog::OnShowLightmaps() {
  int c = IsDlgButtonChecked(IDC_SHOW_LIGHTMAPS);

  if (c)
    Outline_lightmaps = 1;
  else
    Outline_lightmaps = 0;

  State_changed = 1;
}

void CLightingDialog::OnKillfocusHemicubeResEdit() {
  CEdit *ebox;
  char str[20];
  int val;

  ebox = (CEdit *)GetDlgItem(IDC_HEMICUBE_RES_EDIT);
  ebox->GetWindowText(str, 20);

  val = atoi(str);

  if (val < 50)
    val = 50;
  if (val > 2048)
    val = 2048;

  if ((val % 2) == 1)
    val++;

  Hemicube_resolution = val;
  UpdateDialog();
}

void CLightingDialog::OnKillfocusLightSpacingEdit() {
  CEdit *ebox;
  char str[20];
  int val;

  ebox = (CEdit *)GetDlgItem(IDC_LIGHT_SPACING_EDIT);
  ebox->GetWindowText(str, 20);

  val = atoi(str);

  if (val < 1)
    val = 1;
  if (val > 20)
    val = 20;

  LightSpacing = val;
  UpdateDialog();
}

void CLightingDialog::OnBestfitCheck() {
  int c = IsDlgButtonChecked(IDC_BESTFIT_CHECK);

  BestFit = c;

  State_changed = 1;
}

void CLightingDialog::UpdateKeypad(int mask) {
  if (mask & (KUF_CURROOM_CHANGED + KUF_CURFACE_CHANGED + KUF_CURPORTAL_CHANGED))
    UpdateDialog();
}

void CLightingDialog::ShootRayForLighting(vector *dest, vector *from, vector *to, int startseg) {
  fvi_info hit_info;
  fvi_query fq;

  // shoot a ray from the light position to the current vertex
  fq.p0 = from;
  fq.p1 = to;
  fq.startroom = startseg;

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

  fvi_FindIntersection(&fq, &hit_info);

  *dest = hit_info.hit_pnt;
}

#define MAX_VOLUMETRIC_DISTANCE 5000

// Creates a volumetric cone of light that casts from the center of the current face
void CLightingDialog::OnCreateVolumeCone() {
  int i, t;

  ASSERT(Curface < Curroomp->num_faces);
  int resolution = m_resolution;
  int first_vert_index, first_face_index, center_index;

  vector faceverts[MAX_VERTS_PER_FACE];
  vector center;
  matrix face_matrix;

  vector fvec = Curroomp->faces[Curface].normal;

  float pitch_increment = 65536 / resolution;
  float heading_increment = 65536 / resolution;
  float desired_angle = m_angle;
  float pitch, heading;

  // Fixup angle for our system
  desired_angle = (desired_angle / 360.0) * 65536;

  // Get face matrix
  vm_VectorToMatrix(&face_matrix, &fvec, NULL, NULL);
  vm_TransposeMatrix(&face_matrix);

  // Get polygon center
  face *fp = &Curroomp->faces[Curface];
  for (i = 0; i < fp->num_verts; i++)
    faceverts[i] = Curroomp->verts[fp->face_verts[i]];
  vm_GetCentroid(&center, faceverts, fp->num_verts);

  // Add vertices to room
  center_index = Curroomp->num_verts;
  RoomAddVertices(Curroomp, resolution + 1);
  Curroomp->verts[center_index] = center;
  first_vert_index = center_index + 1;

  for (i = 0; i < resolution; i++) {
    vector dest_vec;
    vector hit_vec;

    matrix tempm;

    // Get our direction
    heading = FixCos(heading_increment * i);
    pitch = FixSin(pitch_increment * i);

    vm_AnglesToMatrix(&tempm, pitch * desired_angle, heading * desired_angle, 0);
    vm_MatrixMulVector(&dest_vec, &tempm.fvec, &face_matrix);

    dest_vec *= m_falloff;
    dest_vec += center;

    // Now shoot a ray out
    ShootRayForLighting(&hit_vec, &center, &dest_vec, Curroomp - Rooms);

    Curroomp->verts[first_vert_index + i] = hit_vec;
  }

  // Add faces to room
  first_face_index = Curroomp->num_faces;
  RoomAddFaces(Curroomp, resolution);

  for (i = 0; i < resolution; i++) {
    int a = center_index;
    int b = first_vert_index + i;
    int c = first_vert_index + ((i + 1) % resolution);

    // First do front face
    fp = &Curroomp->faces[first_face_index + i];

    InitRoomFace(fp, 3);
    fp->flags |= FF_VOLUMETRIC;
    fp->tmap = D3EditState.texdlg_texture;

    fp->face_verts[0] = a;
    fp->face_verts[1] = b;
    fp->face_verts[2] = c;

    if (!ComputeFaceNormal(Curroomp, first_face_index + i))
      Int3(); // Get Jason

    AssignDefaultUVsToRoomFace(Curroomp, first_face_index + i);

    // Now assign appropriate alpha

    fp->face_uvls[0].alpha = Float_to_ubyte(m_start_alpha);
    for (t = 1; t < 3; t++) {
      vector subvec = Curroomp->verts[fp->face_verts[t]] - center;
      float mag = vm_GetMagnitude(&subvec);
      float norm = 1 - (mag / m_falloff);
      if (norm < 0)
        norm = 0;
      fp->face_uvls[t].alpha = Float_to_ubyte(norm * m_start_alpha);
    }
  }
  World_changed = 1;
}

void CLightingDialog::OnClearVolumetricRoom() {
  int i;

StartOver:
  for (i = 0; i < Curroomp->num_faces; i++) {
    face *fp = &Curroomp->faces[i];
    if (fp->flags & FF_VOLUMETRIC) {
      DeleteRoomFace(Curroomp, i);
      goto StartOver;
    }
  }

  World_changed = 1;
}

void CLightingDialog::OnFillVolumeTexture() {
  int i;

  for (i = 0; i < Curroomp->num_faces; i++) {
    face *fp = &Curroomp->faces[i];
    if (fp->flags & FF_VOLUMETRIC)
      Curroomp->faces[i].tmap = D3EditState.texdlg_texture;
  }

  World_changed = 1;
}

void CLightingDialog::OnKillfocusAngleEdit() {
  CEdit *ebox;
  char str[20];
  int val;

  ebox = (CEdit *)GetDlgItem(IDC_ANGLE_EDIT);
  ebox->GetWindowText(str, 20);

  val = atoi(str);

  if (val < 0)
    val = 0;
  if (val > 90)
    val = 70;

  m_angle = val;
  UpdateDialog();
}

void CLightingDialog::OnKillfocusStartAlphaEdit() {
  CEdit *ebox;
  char str[20];
  float val;

  ebox = (CEdit *)GetDlgItem(IDC_START_ALPHA_EDIT);
  ebox->GetWindowText(str, 20);

  val = atof(str);

  if (val < 0)
    val = 0;
  if (val > 1)
    val = 1;

  m_start_alpha = val;
  UpdateDialog();
}

void CLightingDialog::OnKillfocusResolutionEdit() {
  CEdit *ebox;
  char str[20];
  int val;

  ebox = (CEdit *)GetDlgItem(IDC_RESOLUTION_EDIT);
  ebox->GetWindowText(str, 20);

  val = atoi(str);

  if (val < 1)
    val = 1;
  if (val > 200)
    val = 200;

  m_resolution = val;
  UpdateDialog();
}

void CLightingDialog::OnEdgeVolumetric() {
  int i, t;
  float rad, mytan, tan_table[360];

  ASSERT(Curface < Curroomp->num_faces);
  int resolution = m_resolution;
  int first_vert_index, first_face_index, index, face_index;
  face *cur_fp = &Curroomp->faces[Curface];
  vector subvec;
  int total_face_verts, num_new_verts;

  float falloff_distance = 0;
  vector nvec = Curroomp->faces[Curface].normal;
  vector fvec;

  vector faceverts[MAX_VERTS_PER_FACE];
  vector center;
  int pitch = m_pitch_angle;
  int heading = m_heading_angle;

  if (pitch < 0)
    pitch += 360;
  if (heading < 0)
    heading += 360;

  int desired_pitch = pitch * (65536.0 / 360.0);
  int desired_heading = heading * (65536.0 / 360.0);
  matrix tempm, face_matrix;

  // Get facematrix
  vm_VectorToMatrix(&face_matrix, &nvec, NULL, NULL);
  vm_TransposeMatrix(&face_matrix);

  // Get adjusted angle of direction
  vm_AnglesToMatrix(&tempm, desired_pitch, desired_heading, 0);
  vm_MatrixMulVector(&fvec, &tempm.fvec, &face_matrix);

  // Build tan table
  for (i = 0; i < 360; i++) {
    rad = (float)(3.14 * (float)i / 180.0);
    mytan = tan(rad);
    tan_table[i] = mytan;
  }

  // Get polygon center
  face *fp = &Curroomp->faces[Curface];
  for (i = 0; i < fp->num_verts; i++)
    faceverts[i] = Curroomp->verts[fp->face_verts[i]];
  vm_GetCentroid(&center, faceverts, fp->num_verts);

  // Add vertices to room
  total_face_verts = cur_fp->num_verts;
  first_vert_index = Curroomp->num_verts;
  RoomAddVertices(Curroomp, cur_fp->num_verts * resolution * 2);
  num_new_verts = cur_fp->num_verts * resolution * 2;
  index = first_vert_index;

  // Compute the rays
  for (i = 0; i < total_face_verts; i++) {
    vector vec_a = Curroomp->verts[cur_fp->face_verts[i]];
    vector vec_b = Curroomp->verts[cur_fp->face_verts[(i + 1) % total_face_verts]];
    subvec = vec_b - vec_a;
    vector add_vec = subvec / resolution;

    for (t = 0; t < resolution; t++, index += 2) {
      vector dest_vec;
      vector hit_vec;
      vector src_vec;
      vector norm_vec;
      vector subvec;
      float cone_distance, mag;

      src_vec = vec_a + (add_vec * t);
      src_vec += (cur_fp->normal / 64.0);
      dest_vec = src_vec + (fvec * m_falloff);

      cone_distance = tan_table[m_angle] * m_falloff;
      norm_vec = center + (fvec * m_falloff);
      subvec = dest_vec - norm_vec;
      mag = vm_GetMagnitude(&subvec);
      if (mag < cone_distance) {
        vm_NormalizeVector(&subvec);
        dest_vec = norm_vec + (subvec * cone_distance);
      }

      // Now shoot a ray out
      ShootRayForLighting(&hit_vec, &src_vec, &dest_vec, Curroomp - Rooms);

      Curroomp->verts[index] = src_vec;
      Curroomp->verts[index + 1] = hit_vec;
    }
  }

  // Add faces to room
  first_face_index = Curroomp->num_faces;
  RoomAddFaces(Curroomp, resolution * total_face_verts);
  index = first_vert_index;
  face_index = first_face_index;

  for (i = 0; i < total_face_verts; i++) {
    for (t = 0; t < resolution; t++, index += 2, face_index++) {
      int a = index;
      int b = (index + 2);
      int c = (index + 3);
      int d = index + 1;

      if ((b - first_vert_index) >= num_new_verts)
        b -= num_new_verts;
      if ((c - first_vert_index) >= num_new_verts)
        c -= num_new_verts;

      // First do front face
      face *fp = &Curroomp->faces[face_index];

      InitRoomFace(fp, 4);
      fp->flags |= FF_VOLUMETRIC;
      fp->tmap = D3EditState.texdlg_texture;

      fp->face_verts[0] = a;
      fp->face_verts[1] = b;
      fp->face_verts[2] = c;
      fp->face_verts[3] = d;

      if (!ComputeFaceNormal(Curroomp, face_index))
        Int3(); // Get Jason

      AssignDefaultUVsToRoomFace(Curroomp, face_index);

      // Now assign appropriate alpha

      fp->face_uvls[0].alpha = Float_to_ubyte(m_start_alpha);
      fp->face_uvls[1].alpha = Float_to_ubyte(m_start_alpha);

      subvec = Curroomp->verts[c] - Curroomp->verts[b];
      float mag = vm_GetMagnitude(&subvec);
      float norm = 1 - (mag / m_falloff);
      if (norm < 0)
        norm = 0;
      fp->face_uvls[2].alpha = Float_to_ubyte(norm * m_start_alpha);

      subvec = Curroomp->verts[d] - Curroomp->verts[a];
      mag = vm_GetMagnitude(&subvec);
      norm = 1 - (mag / m_falloff);
      if (norm < 0)
        norm = 0;
      fp->face_uvls[3].alpha = Float_to_ubyte(norm * m_start_alpha);
    }
  }
  World_changed = 1;
}

void CLightingDialog::OnKillfocusFalloffEdit() {
  CEdit *ebox;
  char str[20];
  int val;

  ebox = (CEdit *)GetDlgItem(IDC_FALLOFF_EDIT);
  ebox->GetWindowText(str, 20);

  val = atoi(str);

  if (val < 2)
    val = 2;
  if (val > 2000)
    val = 2000;

  m_falloff = val;
  UpdateDialog();
}

void CLightingDialog::OnFarthestPoint() {
  face *fp = &Curroomp->faces[Curface];
  float max_distance = 0;
  char str[255];
  int i;

  for (i = 0; i < fp->num_verts; i++) {
    vector dest_vec;
    vector hit_vec;
    vector src_vec;
    vector subvec;

    src_vec = Curroomp->verts[fp->face_verts[i]];
    src_vec += (fp->normal / 64.0);
    dest_vec = src_vec + (fp->normal * MAX_VOLUMETRIC_DISTANCE);

    // Now shoot a ray out
    ShootRayForLighting(&hit_vec, &src_vec, &dest_vec, Curroomp - Rooms);

    subvec = hit_vec - src_vec;
    float mag = vm_GetMagnitude(&subvec);
    if (mag > max_distance)
      max_distance = mag;
  }

  sprintf(str, "The farthest point is %f units away.  Do you wish to set the falloff rate to this value?",
          max_distance);
  int answer = MessageBox(str, "Light Question", MB_YESNO);
  if (answer == IDNO)
    return;

  m_falloff = max_distance;
  UpdateDialog();
}

void CLightingDialog::OnKillfocusPitchAngleEdit() {
  CEdit *ebox;
  char str[20];
  int val;

  ebox = (CEdit *)GetDlgItem(IDC_PITCH_ANGLE_EDIT);
  ebox->GetWindowText(str, 20);

  val = atoi(str);

  if (val < -70)
    val = -70;
  if (val > 70)
    val = 70;

  m_pitch_angle = val;
  UpdateDialog();
}

void CLightingDialog::OnKillfocusHeadingAngleEdit() {
  CEdit *ebox;
  char str[20];
  int val;

  ebox = (CEdit *)GetDlgItem(IDC_HEADING_ANGLE_EDIT);
  ebox->GetWindowText(str, 20);

  val = atoi(str);

  if (val < -70)
    val = -70;
  if (val > 70)
    val = 70;

  m_heading_angle = val;
  UpdateDialog();
}

void CLightingDialog::OnHemicubeCheck() {
  int c = IsDlgButtonChecked(IDC_HEMICUBE_CHECK);

  if (c)
    D3EditState.hemicube_radiosity = 1;
  else
    D3EditState.hemicube_radiosity = 0;
}

void CLightingDialog::OnCountTerrainLights() {
  int lightcount, i, t;
  char str[1000];

  for (lightcount = 0, i = 0; i < TERRAIN_WIDTH * TERRAIN_DEPTH; i++) {
    if (Terrain_seg[i].flags & TF_INVISIBLE)
      continue;

    int texnum = Terrain_tex_seg[Terrain_seg[i].texseg_index].tex_index;
    if (GameTextures[texnum].r > 0 || GameTextures[texnum].g > 0 || GameTextures[texnum].b > 0)
      lightcount += 2;
  }

  lightcount += Terrain_sky.num_satellites;

  for (i = 0; i <= Highest_object_index; i++) {
    if (!OBJECT_OUTSIDE(&Objects[i]))
      continue;

    if (Objects[i].type != OBJ_NONE && Objects[i].lighting_render_type == LRT_LIGHTMAPS) {
      poly_model *po = &Poly_models[Objects[i].rtype.pobj_info.model_num];

      if (!po->new_style)
        continue;

      for (t = 0; t < po->n_models; t++) {
        bsp_info *sm = &po->submodel[t];

        if (IsNonRenderableSubmodel(po, t))
          continue;

        for (int j = 0; j < sm->num_faces; j++) {
          if (sm->faces[j].texnum == -1)
            continue;

          int texnum = po->textures[sm->faces[j].texnum];
          if (GameTextures[texnum].r > 0 || GameTextures[texnum].g > 0 || GameTextures[texnum].b > 0)
            lightcount++;
        }
      }
    }
  }

  // Count rooms
  for (i = 0; i < MAX_ROOMS; i++) {
    if (Rooms[i].used && (Rooms[i].flags & RF_EXTERNAL)) {
      for (t = 0; t < Rooms[i].num_faces; t++) {
        int texnum = Rooms[i].faces[t].tmap;
        if (GameTextures[texnum].r > 0 || GameTextures[texnum].g > 0 || GameTextures[texnum].b > 0)
          lightcount++;
      }
    }
  }

  sprintf(str,
          "There are %d lights present on the terrain.  Do you wish for me to set the 'light iterations' control to "
          "match this number?",
          lightcount);
  int answer = MessageBox(str, "Light Question", MB_YESNO);
  if (answer == IDNO)
    return;

  CEdit *ebox;

  ebox = (CEdit *)GetDlgItem(IDC_LIGHT_ITERATION_EDIT);
  sprintf(str, "%d", lightcount);
  ebox->SetWindowText(str);

  rad_MaxStep = lightcount;
}

void CLightingDialog::OnCurroomRadiosity() {
  MessageBox("I will now calculate radiosity for the current room.  You may prematurely break out by pressing any key "
             "during the calculations",
             "Message", MB_OK);

  DoRadiosityForCurrentRoom(Curroomp);
  World_changed = 1;
}

void DoTerrainDynamicTable();
void CLightingDialog::OnDynamicTerrain() {
  if ((MessageBox("Are you sure you wish to do dynamic lighting on the terrain.  The reason I ask is that this "
                  "procedure takes a long time, and I don't want to upset you.",
                  "Question", MB_YESNO)) == IDYES)
    DoTerrainDynamicTable();
}

void CLightingDialog::OnBlurLightmaps() {

  if ((MessageBox("Are you sure you wish to blur all lightmaps?  This operation cannot be undone...", "Question",
                  MB_YESNO)) == IDYES) {
    BlurLightmapInfos(LMI_ROOM);
    BlurLightmapInfos(LMI_ROOM_OBJECT);
    BlurLightmapInfos(LMI_EXTERNAL_ROOM);
    BlurLightmapInfos(LMI_TERRAIN_OBJECT);

    OutrageMessageBox("Blurring complete.");
  }
}

void CLightingDialog::OnKillfocusIgnoreLimitEdit() {
  CEdit *ebox;
  char str[20];
  float val;

  ebox = (CEdit *)GetDlgItem(IDC_IGNORE_LIMIT_EDIT);
  ebox->GetWindowText(str, 20);

  val = atof(str);

  if (val < 0 || val > .5) {
    OutrageMessageBox("Invalid value.  Must be in the range 0 to .5!");
    return;
  }

  Ignore_limit = val;
  UpdateDialog();
}

void CLightingDialog::OnKillfocusRedAmbientEdit() {
  CEdit *ebox;
  char str[20];
  float val;

  ebox = (CEdit *)GetDlgItem(IDC_RED_AMBIENT_EDIT);
  ebox->GetWindowText(str, 20);

  val = atof(str);

  if (val < 0 || val > 1.0) {
    OutrageMessageBox("Invalid value.  Must be in the range 0 to 1.0!");
    return;
  }

  Ambient_red = val;
  UpdateDialog();
}

void CLightingDialog::OnKillfocusGreenAmbientEdit() {
  CEdit *ebox;
  char str[20];
  float val;

  ebox = (CEdit *)GetDlgItem(IDC_GREEN_AMBIENT_EDIT);
  ebox->GetWindowText(str, 20);

  val = atof(str);

  if (val < 0 || val > 1.0) {
    OutrageMessageBox("Invalid value.  Must be in the range 0 to 1.0!");
    return;
  }

  Ambient_green = val;
  UpdateDialog();
}

void CLightingDialog::OnKillfocusBlueAmbientEdit() {
  CEdit *ebox;
  char str[20];
  float val;

  ebox = (CEdit *)GetDlgItem(IDC_BLUE_AMBIENT_EDIT);
  ebox->GetWindowText(str, 20);

  val = atof(str);

  if (val < 0 || val > 1.0) {
    OutrageMessageBox("Invalid value.  Must be in the range 0 to 1.0!");
    return;
  }

  Ambient_blue = val;
  UpdateDialog();
}

void CLightingDialog::OnElementCheck() {
  int c = IsDlgButtonChecked(IDC_ELEMENT_CHECK);

  if (c)
    Shoot_from_patch = 0;
  else
    Shoot_from_patch = 1;
}

void CLightingDialog::OnIgnoreTerrain() {
  int c = IsDlgButtonChecked(IDC_IGNORE_TERRAIN);

  if (c)
    Ignore_terrain = 1;
  else
    Ignore_terrain = 0;
}

void CLightingDialog::OnIgnoreSatellites() {
  int c = IsDlgButtonChecked(IDC_IGNORE_SATELLITES);

  if (c)
    Ignore_satellites = 1;
  else
    Ignore_satellites = 0;
}

void CLightingDialog::OnUseBspCheck() {
  int c = IsDlgButtonChecked(IDC_USE_BSP_CHECK);

  if (c)
    UseBSP = 1;
  else
    UseBSP = 0;
}

void CLightingDialog::OnKillfocusLightMultiplyEdit() {
  if (Curroomp == NULL)
    return;

  CEdit *ebox;
  char str[20];
  float val;

  ebox = (CEdit *)GetDlgItem(IDC_LIGHT_MULTIPLY_EDIT);
  ebox->GetWindowText(str, 20);

  val = atof(str);

  if (val < .25 || val > 63.75) {
    OutrageMessageBox("Invalid value, setting to one!");
    val = 1.0;
  }

  Curroomp->faces[Curface].light_multiple = (val * 4.0);
  UpdateDialog();
}

void CLightingDialog::OnKillfocusPulseTimeEdit() {
  CEdit *ebox;
  char str[20];
  int aval;

  ebox = (CEdit *)GetDlgItem(IDC_PULSE_TIME_EDIT);
  ebox->GetWindowText(str, 20);

  aval = atol(str);
  if (aval < 0)
    aval = 0;
  if (aval > 255)
    aval = 255;

  Curroomp->pulse_time = aval;

  UpdateDialog();
  World_changed = 1;
}

void CLightingDialog::OnKillfocusPulseOffsetEdit() {
  CEdit *ebox;
  char str[20];
  int aval;

  ebox = (CEdit *)GetDlgItem(IDC_PULSE_OFFSET_EDIT);
  ebox->GetWindowText(str, 20);

  aval = atol(str);
  if (aval < 0)
    aval = 0;
  if (aval > 255)
    aval = 255;

  Curroomp->pulse_offset = aval;

  UpdateDialog();
  World_changed = 1;
}

void CLightingDialog::OnPulseRoom() {
  int ptime = Curroomp->pulse_time;
  int poffset = Curroomp->pulse_offset;

  SetRoomPulse(Curroomp, ptime, poffset);
  UpdateDialog();
  World_changed = 1;
}

void CLightingDialog::OnResetMultiples() {
  int testroom;
  int answer = MessageBox("Are you sure you wish to reset all light multiples?", "Light Question", MB_YESNO);
  if (answer == IDNO)
    return;

  answer = MessageBox("Just for this room? (Answering no does it for the entire mine)", "Light Question", MB_YESNO);

  if (answer == IDNO)
    testroom = -1;
  else
    testroom = Curroomp - Rooms;

  int i;

  for (i = 0; i <= Highest_room_index; i++) {
    if (Rooms[i].used) {
      if (testroom != -1 && i != testroom)
        continue;

      for (int t = 0; t < Rooms[i].num_faces; t++) {
        Rooms[i].faces[t].light_multiple = 4;
      }
    }
  }

  OutrageMessageBox("Light multiples reset!");
}

void CLightingDialog::OnDestroyBsp() {
  int answer = MessageBox("Are you sure you wish to destroy the BSP tree?", "Light Question", MB_YESNO);
  if (answer == IDNO)
    return;

  if (BSP_initted) {
    DestroyBSPTree(&MineBSP);
  }

  OutrageMessageBox("BSP tree destroyed!");
}

void CLightingDialog::OnCombineFaces() {
  int c = IsDlgButtonChecked(IDC_COMBINE_FACES);

  if (c)
    AllowCombining = 1;
  else
    AllowCombining = 0;
}

// Takes a bitmap name and resaves it
void ResaveBitmap(char *name) {
  int bm_handle = bm_FindBitmapName(name);
  if (bm_handle >= 0) {
    char search[256];

    // Make sure its paged in
    bm_data(bm_handle, 0);

    ddio_MakePath(search, LocalD3Dir, "data", "graphics", GameBitmaps[bm_handle].name, NULL);
    mprintf(0, "Resaving bitmap %s.\n", search);

    bm_SaveFileBitmap(search, bm_handle);
  } else {
    char search[256];

    ddio_MakePath(search, LocalD3Dir, "data", "graphics", name, NULL);

    bm_handle = bm_AllocLoadFileBitmap(search, 0);
    if (bm_handle >= 0) {
      // Make sure its paged in
      bm_data(bm_handle, 0);

      mprintf(0, "Resaving bitmap %s.\n", search);

      bm_SaveFileBitmap(search, bm_handle);
    } else
      Int3();
  }
}

// Takes a vclip name and resaves it
void ResaveVClip(char *name) {
  int bm_handle = FindVClipName(name);
  if (bm_handle >= 0) {
    char search[256];

    ddio_MakePath(search, LocalD3Dir, "data", "graphics", GameVClips[bm_handle].name, NULL);
    mprintf(0, "Resaving vclip %s.\n", search);

    SaveVClip(search, bm_handle);
  } else {
    char search[256];

    ddio_MakePath(search, LocalD3Dir, "data", "graphics", name, NULL);

    bm_handle = AllocLoadVClip(search, NOT_TEXTURE, 0);
    if (bm_handle >= 0) {
      mprintf(0, "Resaving vclip %s.\n", search);

      SaveVClip(search, bm_handle);
    } else
      Int3();
  }
}

void ResaveAllBitmaps() {
  char buffer[_MAX_PATH];
  char search[256];

  ddio_MakePath(search, LocalD3Dir, "data", "graphics", "*.ogf", NULL);

  if (ddio_FindFileStart(search, buffer)) {
    ResaveBitmap(buffer);

    while ((ddio_FindNextFile(buffer))) {
      ResaveBitmap(buffer);
    }
  }

  ddio_FindFileClose();

  ddio_MakePath(search, LocalD3Dir, "data", "graphics", "*.oaf", NULL);

  if (ddio_FindFileStart(search, buffer)) {
    ResaveVClip(buffer);

    while ((ddio_FindNextFile(buffer))) {
      ResaveVClip(buffer);
    }
  }

  ddio_FindFileClose();
}

void CLightingDialog::OnVolumeLights() {
  int c = IsDlgButtonChecked(IDC_VOLUME_LIGHTS);

  if (c)
    UseVolumeLights = 1;
  else
    UseVolumeLights = 0;
}

void CLightingDialog::OnCoronaCheck() {
  if (Curroomp == NULL)
    return;

  int c = IsDlgButtonChecked(IDC_CORONA_CHECK);

  if (c)
    Curroomp->faces[Curface].flags |= FF_CORONA;
  else
    Curroomp->faces[Curface].flags &= ~FF_CORONA;
}

void CLightingDialog::OnFogCheck() {
  if (Curroomp == NULL)
    return;

  int c = IsDlgButtonChecked(IDC_FOG_CHECK);

  if (c)
    Curroomp->flags |= RF_FOG;
  else
    Curroomp->flags &= ~RF_FOG;
}

void CLightingDialog::OnKillfocusFogDensity() {

  CEdit *ebox;
  char str[20];
  float aval;

  ebox = (CEdit *)GetDlgItem(IDC_FOG_DENSITY);
  ebox->GetWindowText(str, 20);

  aval = atof(str);
  if (aval < 0)
    aval = 5;
  if (aval > 5000)
    aval = 5000;

  Curroomp->fog_depth = aval;

  UpdateDialog();
  World_changed = 1;
}

void CLightingDialog::OnKillfocusFogRed() {
  CEdit *ebox;
  char str[20];
  float aval;

  ebox = (CEdit *)GetDlgItem(IDC_FOG_RED);
  ebox->GetWindowText(str, 20);

  aval = atof(str);
  if (aval < 0)
    aval = 0;
  if (aval > 1.0)
    aval = 1;

  Curroomp->fog_r = aval;

  UpdateDialog();
  World_changed = 1;
}

void CLightingDialog::OnKillfocusFogGreen() {
  CEdit *ebox;
  char str[20];
  float aval;

  ebox = (CEdit *)GetDlgItem(IDC_FOG_GREEN);
  ebox->GetWindowText(str, 20);

  aval = atof(str);
  if (aval < 0)
    aval = 0;
  if (aval > 1.0)
    aval = 1;

  Curroomp->fog_g = aval;

  UpdateDialog();
  World_changed = 1;
}

void CLightingDialog::OnKillfocusFogBlue() {
  CEdit *ebox;
  char str[20];
  float aval;

  ebox = (CEdit *)GetDlgItem(IDC_FOG_BLUE);
  ebox->GetWindowText(str, 20);

  aval = atof(str);
  if (aval < 0)
    aval = 0;
  if (aval > 1.0)
    aval = 1;

  Curroomp->fog_b = aval;

  UpdateDialog();
  World_changed = 1;
}

void CLightingDialog::OnRoomCoronas() {
  int rev, answer, only_cur_texture;

  rev = IsDlgButtonChecked(IDC_REVERSE_CORONA);

  if (rev)
    answer = MessageBox("Are you sure you wish to remove all coronas in the current room?", "Light Question", MB_YESNO);
  else
    answer = MessageBox("Are you sure you wish to make all lights in the current room have coronas?", "Light Question",
                        MB_YESNO);

  if (answer == IDNO)
    return;

  only_cur_texture = MessageBox(
      "Do you wish to do this operation only on the faces that have the same texture as the currently selected face?",
      "Light Question", MB_YESNO);

  int i;

  for (i = 0; i < Curroomp->num_faces; i++) {
    if (only_cur_texture == IDYES) {
      if (Curroomp->faces[i].tmap != Curroomp->faces[Curface].tmap)
        continue;
    }

    if (rev)
      Curroomp->faces[i].flags &= ~FF_CORONA;
    else
      Curroomp->faces[i].flags |= FF_CORONA;
  }

  World_changed = 1;

  OutrageMessageBox("Operation complete!");
  UpdateDialog();
}

void CLightingDialog::OnMineCoronas() {
  int answer, only_cur_texture;
  int rev;

  rev = IsDlgButtonChecked(IDC_REVERSE_CORONA);

  if (rev)
    answer = MessageBox("Are you sure you wish to remove all coronas in this level?", "Light Question", MB_YESNO);
  else
    answer =
        MessageBox("Are you sure you wish to make all lights in the mine have coronas?", "Light Question", MB_YESNO);

  if (answer == IDNO)
    return;

  only_cur_texture = MessageBox(
      "Do you wish to do this operation only on the faces that have the same texture as the currently selected face?",
      "Light Question", MB_YESNO);

  int i, t;

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

    for (t = 0; t < Rooms[i].num_faces; t++) {
      if (only_cur_texture == IDYES) {
        if (Rooms[i].faces[t].tmap != Curroomp->faces[Curface].tmap)
          continue;
      }

      if (!rev)
        Rooms[i].faces[t].flags |= FF_CORONA;
      else
        Rooms[i].faces[t].flags &= ~FF_CORONA;
    }
  }

  World_changed = 1;

  OutrageMessageBox("Operation complete!");
  UpdateDialog();
}

void CLightingDialog::OnReverseCorona() {
  // TODO: Add your control notification handler code here
}

void CLightingDialog::OnKillfocusGlobalMultiplierEdit() {
  CEdit *ebox;
  char str[20];
  float aval;

  ebox = (CEdit *)GetDlgItem(IDC_GLOBAL_MULTIPLIER_EDIT);
  ebox->GetWindowText(str, 20);

  aval = atof(str);
  if (aval < 0)
    aval = 0;

  GlobalMultiplier = aval;

  UpdateDialog();
}

void CLightingDialog::OnStrobeCheck() {
  if (Curroomp == NULL)
    return;

  int c = IsDlgButtonChecked(IDC_STROBE_CHECK);

  if (c)
    Curroomp->flags |= RF_STROBE;
  else
    Curroomp->flags &= ~RF_STROBE;
}

void CLightingDialog::OnFlickerCheck() {
  if (Curroomp == NULL)
    return;

  int c = IsDlgButtonChecked(IDC_FLICKER_CHECK);

  if (c)
    Curroomp->flags |= RF_FLICKER;
  else
    Curroomp->flags &= ~RF_FLICKER;
}

void CLightingDialog::OnKillfocusRoomMultiplierEdit() {
  CEdit *ebox;
  char str[20];
  float aval;

  ebox = (CEdit *)GetDlgItem(IDC_ROOM_MULTIPLIER_EDIT);
  ebox->GetWindowText(str, 20);

  aval = atof(str);
  if (aval < 0)
    aval = 0;

  Room_multiplier[Curroomp - Rooms] = aval;

  UpdateDialog();
}

void CLightingDialog::OnKillfocusRoomAmred() {
  CEdit *ebox;
  char str[20];
  float aval;

  ebox = (CEdit *)GetDlgItem(IDC_ROOM_AMRED);
  ebox->GetWindowText(str, 20);

  aval = atof(str);
  if (aval < 0)
    aval = 0;

  Room_ambience_r[Curroomp - Rooms] = aval;

  UpdateDialog();
}

void CLightingDialog::OnKillfocusRoomAmgreen() {
  CEdit *ebox;
  char str[20];
  float aval;

  ebox = (CEdit *)GetDlgItem(IDC_ROOM_AMGREEN);
  ebox->GetWindowText(str, 20);

  aval = atof(str);
  if (aval < 0)
    aval = 0;

  Room_ambience_g[Curroomp - Rooms] = aval;

  UpdateDialog();
}

void CLightingDialog::OnKillfocusRoomAmblue() {
  CEdit *ebox;
  char str[20];
  float aval;

  ebox = (CEdit *)GetDlgItem(IDC_ROOM_AMBLUE);
  ebox->GetWindowText(str, 20);

  aval = atof(str);
  if (aval < 0)
    aval = 0;

  Room_ambience_b[Curroomp - Rooms] = aval;

  UpdateDialog();
}

#include "object_lighting.h"

void CLightingDialog::OnClearLightmaps() {
  if (OutrageMessageBox(
          MBOX_YESNO,
          "Are you sure you want to clear the lightmaps?  If you say yes, your level will have no lighting.") != IDYES)
    return;

  ClearAllRoomLightmaps(0);
  ClearAllObjectLightmaps(0);
}

int CopyFaceLightMultipleRoom(room *rp, int tmap, float multiple) {
  int n_changed = 0;

  for (int f = 0; f < rp->num_faces; f++)
    if (rp->faces[f].tmap == tmap) {
      rp->faces[f].light_multiple = multiple;
      n_changed++;
    }

  return n_changed;
}

void CopyFaceLightMultiple(room *rp, int facenum) {
  int answer = OutrageMessageBox(
      MBOX_YESNOCANCEL,
      "Do you want to copy the light multiple from face %d to all faces in the mine that have the same texture?\n\n"
      "YES means copy to faces in the whole mine.\n"
      "NO means copy just to faces in the same room.\n"
      "CANCEL will abort this operation.",
      facenum);

  if (answer == IDCANCEL)
    return;

  int n_changed = 0;
  if (answer == IDNO)
    n_changed = CopyFaceLightMultipleRoom(rp, rp->faces[facenum].tmap, rp->faces[facenum].light_multiple);
  else {
    for (int r = 0; r <= Highest_room_index; r++) {
      if (Rooms[r].used)
        n_changed += CopyFaceLightMultipleRoom(&Rooms[r], rp->faces[facenum].tmap, rp->faces[facenum].light_multiple);
    }
  }

  n_changed--; // don't count the source face

  if (n_changed) {
    EditorStatus("The light multiple has been changed for %d faces.", n_changed);
    World_changed = 1;
  } else
    EditorStatus("No faces had the same texture as face %d.", facenum);
}

void CLightingDialog::OnCopyMultiplier() { CopyFaceLightMultiple(Curroomp, Curface); }
