unit optionsform;

// This is copied and modified code from LODGen
// Credit to Ehamloptiran and Zilav for LODGen, part of TES5Edit

//============================================================================
// Save preset
procedure LODGenPresetSave(aFileName: string);
var
  ini: TMemIniFile;
  i, j: integer;
  s: string;
begin
  ini := TMemIniFile.Create(aFileName);
  ini.WriteString(wbGameName, 'World', cbWorld.Text);
  ini.WriteString(wbGameName, 'Export', edExport.Text);
  ini.WriteString(wbGameName, 'Destination', edDestination.Text);
  ini.WriteString(wbGameName, 'DataPath', edDataPath.Text);
  ini.WriteString(wbGameName, 'StorageUtilData', edSKSEPluginPath.Text);
  ini.WriteBool(wbGameName, 'DontFixTangents', chkDontFixTangents.Checked);
  ini.WriteBool(wbGameName, 'NoTangents', chkNoTangents.Checked);
  ini.WriteBool(wbGameName, 'NoColors', chkNoColors.Checked);
  ini.WriteBool(wbGameName, 'NoMaterial', chkNoMaterial.Checked);
  ini.WriteBool(wbGameName, 'RemoveUnseen', chkRemoveUnseen.Checked);
  ini.WriteBool(wbGameName, 'RemoveUnseenIgnoreWater', chkIgnoreWater.Checked);
  ini.WriteString(wbGameName, 'LODLevel', cbLODLevel.Text);
  ini.WriteString(wbGameName, 'LODX', cbLODX.Text);
  ini.WriteString(wbGameName, 'LODY', cbLODY.Text);
  ini.WriteString(wbGameName, 'LODScale', edLODScale.Text);
  ini.WriteString(wbGameName, 'NearGridToLoad', edMinion.Text);
  ini.WriteString(wbGameName, 'FarGridToLoad', edMaster.Text);
  ini.WriteBool(wbGameName, 'DoDynDOLOD', chkDoDYNDOLOD.Checked);
  ini.WriteBool(wbGameName, 'CreateAtlas', chkCreateAtlas.Checked);
  ini.WriteBool(wbGameName, 'UseCache', chkUseCache.Checked);
  ini.WriteBool(wbGameName, 'UseTreeCache', chkUseTreeCache.Checked);
  ini.WriteBool(wbGameName, 'UseOrgLOD', chkUseOrgLOD.Checked);
  ini.EraseSection(wbGameName + ' LODGen');
  for i := 0 to Pred(slLOD.Count) do begin
    j := slLOD.Objects[i];
    s := Format('%s,%s,%s,%s,%s,%s,%d', [
      slLOD[i],
      slLODTypes[j and $F],
      slLODTypes[j shr 4 and $F],
      slLODTypes[j shr 8 and $F],
      slGridTypes[j shr 12 and $F],
      slReferenceTypes[j shr 16 and $F],
      j shr 24
    ]);
    ini.WriteString(wbGameName + ' LODGen', 'LODGen' + IntToStr(Succ(i)), s);
  end;
  ini.UpdateFile;
  ini.Free;
end;

//============================================================================
// Load Rules
procedure LODGenRulesLoad(aFileName: string);
var
  ini: TMemIniFile;
  i, lod: integer;
  sl: TStringList;
begin
  ini := TMemIniFile.Create(aFileName);
  slLOD.Clear;
  sl := TStringList.Create;
  sl.StrictDelimiter := True;
  try
    i := 1;
    repeat
      sl.CommaText := ini.ReadString(wbGameName + ' LODGen', 'LODGen' + IntToStr(i), '');
      if sl.Count = 7 then begin
        lod := IndexIn(slLODTypes, sl[1]) + 
               IndexIn(slLODTypes, sl[2]) shl 4 + 
               IndexIn(slLODTypes, sl[3]) shl 8 +
               IndexIn(slGridTypes, sl[4]) shl 12 +
               IndexIn(slReferenceTypes, sl[5]) shl 16 +
               StrToIntDef(sl[6], 0) shl 24;
        slLOD.AddObject(sl[0], lod);
      end;
      Inc(i);
    until sl.Count <> 7;
  finally
    sl.Free;
  end;
  if slLOD.Count = 0 then
    slLOD.AddObject('\', $1000432);
  lvRules.Items.Count := slLOD.Count;
  lvRules.Refresh;
  ini.Free;
end;


//============================================================================
// Load preset
procedure LODGenPresetLoad(aFileName: string);
var
  ini: TMemIniFile;
begin
  ini := TMemIniFile.Create(aFileName);
  cbWorld.ItemIndex := IndexIn(cbWorld.Items, ini.ReadString(wbGameName, 'World', cbWorld.Text));
  edExport.Text := ini.ReadString(wbGameName, 'Export', edExport.Text);
  edDestination.Text := ini.ReadString(wbGameName, 'Destination', edDestination.Text);
  edDataPath.Text := ini.ReadString(wbGameName, 'DataPath', edDataPath.Text);
  edSKSEPluginPath.Text := ini.ReadString(wbGameName, 'StorageUtilData', edSKSEPluginPath.Text);
  chkDontFixTangents.Checked := ini.ReadBool(wbGameName, 'DontFixTangents', chkDontFixTangents.Checked);
  chkNoTangents.Checked := ini.ReadBool(wbGameName, 'NoTangents', chkNoTangents.Checked);
  chkNoColors.Checked := ini.ReadBool(wbGameName, 'NoColors', chkNoColors.Checked);
  chkNoMaterial.Checked := ini.ReadBool(wbGameName, 'NoMaterial', chkNoMaterial.Checked);
  chkRemoveUnseen.Checked := ini.ReadBool(wbGameName, 'RemoveUnseen', chkRemoveUnseen.Checked);
  chkIgnoreWater.Checked := ini.ReadBool(wbGameName, 'RemoveUnseenIgnoreWater', chkIgnoreWater.Checked);
  cbLODLevel.ItemIndex := IndexIn(cbLODLevel.Items, ini.ReadString(wbGameName, 'LODLevel', cbLODLevel.Text));
  cbLODX.ItemIndex := IndexIn(cbLODX.Items, ini.ReadString(wbGameName, 'LODX', cbLODX.Text));
  cbLODY.ItemIndex := IndexIn(cbLODY.Items, ini.ReadString(wbGameName, 'LODY', cbLODY.Text));
  edLODScale.Text := ini.ReadString(wbGameName, 'LODScale', edLODScale.Text);
  edMinion.Text := ini.ReadString(wbGameName, 'NearGridToLoad', edMinion.Text);
  edMaster.Text := ini.ReadString(wbGameName, 'FarGridToLoad', edMaster.Text);
  chkDoDYNDOLOD.Checked := ini.ReadBool(wbGameName, 'DoDynDOLOD', chkDoDYNDOLOD.Checked);
  chkCreateAtlas.Checked := ini.ReadBool(wbGameName, 'CreateAtlas', chkCreateAtlas.Checked);
  chkUseCache.Checked := ini.ReadBool(wbGameName, 'UseCache', chkUseCache.Checked);
  chkUseOrgLOD.Checked := ini.ReadBool(wbGameName, 'UseOrgLOD', chkUseOrgLOD.Checked);
  chkUseTreeCache.Checked := ini.ReadBool(wbGameName, 'UseTreeCache', chkUseTreeCache.Checked);
  if not FileExists(aFileName) then
    LODGenRulesLoad(IniPath + 'DynDOLOD-rules-default.ini')
  else
    LODGenRulesLoad(aFileName);
  ini.Free;
end;

//============================================================================
procedure btnSaveClick(Sender: TObject);
var
  dlgSave: TSaveDialog;
begin
  dlgSave := TSaveDialog.Create(nil);
  try
    dlgSave.Options := dlgSave.Options + [ofOverwritePrompt];
    dlgSave.Filter := 'DynDOLOD-LODGen preset (*.ini)|*.ini';
    dlgSave.InitialDir := IniPath;
    dlgSave.FileName := 'DynDOLOD-LODGen preset ' + cbWorld.Text + '.ini';
    if dlgSave.Execute then
      LODGenPresetSave(dlgSave.FileName);
  finally
    dlgSave.Free;
  end;
end;

//============================================================================
procedure btnLoadClick(Sender: TObject);
var
  dlgOpen: TOpenDialog;
begin
  dlgOpen := TOpenDialog.Create(nil);
  try
    dlgOpen.Filter := 'LODGen preset (*.ini)|*.ini';
    dlgOpen.InitialDir := IniPath;
    dlgOpen.FileName := 'DyDOLOD-LODGen preset ' + cbWorld.Text + '.ini';
    if dlgOpen.Execute then
      LODGenPresetLoad(dlgOpen.FileName);
  finally
    dlgOpen.Free;
  end;
end;

//============================================================================
procedure LODGenbtnLowClick(Sender: TObject);
begin
  LODGenRulesLoad(IniPath + 'DynDOLOD-rules-low.ini');
  chkUseCache.Checked := False;
end;

//============================================================================
procedure LODGenbtnMediumClick(Sender: TObject);
begin
  LODGenRulesLoad(IniPath + 'DynDOLOD-rules-medium.ini');
  chkUseCache.Checked := False;
end;

//============================================================================
procedure LODGenbtnHighClick(Sender: TObject);
begin
  LODGenRulesLoad(IniPath + 'DynDOLOD-rules-high.ini');
  chkUseCache.Checked := False;
end;

//============================================================================
procedure LODGenbtnDefaultClick(Sender: TObject);
begin
  LODGenRulesLoad(IniPath + 'DynDOLOD-rules-default.ini');

  cbLODGenWorldSelect(cbWorld);
  edDataPath.Text := DataPath;
  edDestination.Text := DataPath;
  edSKSEPluginPath.Text := DataPath + 'skse\plugins\StorageUtilData';
  lvRules.Items.Count := slLOD.Count;
  lvRules.Refresh;
  chkDontFixTangents.Checked := True;
  chkNoTangents.Checked := False;
  chkNoColors.Checked := False;
  chkNoMaterial.Checked := False;
  chkRemoveUnseen.Checked := True;
  chkIgnoreWater.Checked := False;
  cbLODLevel.ItemIndex := 0;
  cbLODX.ItemIndex := 0;
  cbLODY.ItemIndex := 0;
  edLODScale.Text := '';
  edMinion.Text := 11;
  edMaster.Text := 21;
  chkDoDynDOLOD.Checked := True;
  chkCreateAtlas.Checked := True;
  chkUseCache.Checked := True;
  chkUseTreeCache.Checked := True;
  if bCreateMod then
    chkUseOrgLOD.Checked := True
  else
    chkUseOrgLOD.Checked := False;
  chkRemoveUnseen.Enabled := TerrainDirectoryExists(cbWorld.Text);
end;

//============================================================================
procedure cbLODGenWorldSelect(Sender: TObject);
begin
  edExport.Text := ScriptsPath + 'LODGen ' + TComboBox(Sender).Text + '.txt';
  if edDestination.Text = '' then
    edDestination.Text := DataPath;
  chkRemoveUnseen.Enabled := TerrainDirectoryExists(cbWorld.Text);
  btnLODGenExe.Enabled := FileExists(edExport.Text);
  btnCreateAtlas.Enabled := FileExists(CachePath + 'atlas_map_' + Lowercase(cbWorld.Text) + '.txt');
end;

//============================================================================
procedure btnExportClick(Sender: TObject);
var
  dlgSave: TSaveDialog;
begin
  dlgSave := TSaveDialog.Create(nil);
  try
    dlgSave.Options := dlgSave.Options + [ofOverwritePrompt];
    dlgSave.Filter := 'LODGen data (*.txt)|*.txt';
    dlgSave.InitialDir := ScriptsPath;
    dlgSave.FileName := edExport.Text;
    if dlgSave.Execute then
      edExport.Text := dlgSave.FileName;
  finally
    dlgSave.Free;
  end;

  btnLODGenExe.Enabled := FileExists(edExport.Text);
end;

//============================================================================
procedure btnDataPathClick(Sender: TObject);
var
  s: string;
begin
  s := SelectDirectory('Data folder', '', edDataPath.Text, nil);
  if s <> '' then
    edDataPath.Text := s;
  chkRemoveUnseen.Enabled := TerrainDirectoryExists(cbWorld.Text);
end;

//============================================================================
// unpack landscape LOD files *.btr for selected worldspace
procedure btnUnpackLandscapeClick(Sender: TObject);
var
  slContainers, slAssets, slFiles: TStringList;
  i: integer;
  lodpath, fulllodpath: string;
begin
  lodpath := 'meshes\terrain\' + LowerCase(cbWorld.Text);
  sDataPath := IncludeTrailingBackslash(edDataPath.Text);
  fulllodpath := sDataPath + lodpath;
  if DirectoryExists(fulllodpath) then begin
    // path can be long and not fit into message dialog, repeat it in messages tab
    AddMessage('LOD folder already exists ' + fulllodpath + ' Do you wish to unpack there?');
    if MessageDlg('LOD folder already exists ' + fulllodpath + #13'Do you wish to unpack there?', mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
      Exit;
  end;
  AddMessage('Unpacking landscape LOD meshes into ' + fulllodpath + ', please wait..');
  lodpath := lodpath + '\' + LowerCase(cbWorld.Text); // to filter out objects and trees subfolders
  // list of available landscape LOD files for selected worldspace
  slFiles := TwbFastStringList.Create;
  slFiles.Sorted := True;
  slFiles.Duplicates := dupIgnore;
  slContainers := TStringList.Create;
  slAssets := TStringList.Create;
  try
    ResourceContainerList(slContainers);
    for i := 0 to Pred(slContainers.Count) do begin
      ResourceList(slContainers[i], slAssets);
      wbFilterStrings(slAssets, slFiles, lodpath);
    end;
  finally
    slAssets.Free;
    slContainers.Free;
  end;
  try
    try
      for i := 0 to Pred(slFiles.Count) do
        ResourceCopy('', slFiles[i], sDataPath);
      AddMessage('Done.');
      MessageDlg(IntToStr(slFiles.Count) + ' LOD files were unpacked.', mtInformation, [mbOk], 0);
    except
      on E: Exception do
        AddMessage('Error copying file ' + slFiles[i] + ': ' + E.Message);
    end;
  finally
    slFiles.Free;
  end;
  chkRemoveUnseen.Enabled := TerrainDirectoryExists(cbWorld.Text);
end;

//============================================================================
// unpack LOD files
procedure btnUnpackLODClick(Sender: TObject);
var
  slFolders, slContainers, slAssets, slFiles: TStringList;
  i, c, index: integer;
  lodpath, fulllodpath: string;
begin
  slFolders := TStringList.Create;
  slFolders.Add('meshes');

  // wbFilterStrings uses Pos()
  lodpath := '_lod';
  //*_lod*.nif

  for index := 0 to Pred(slFolders.Count) do begin
    sDataPath := IncludeTrailingBackslash(edDataPath.Text);
    fulllodpath := sDataPath + slFolders[index];
    if DirectoryExists(fulllodpath) then begin
      // path can be long and not fit into message dialog, repeat it in messages tab
      AddMessage('Folder already exists ' + fulllodpath + ' Do you wish to unpack there?');
      if MessageDlg('Folder already exists ' + fulllodpath + #13'Any existing file will be overwritten!' + #13'Do you wish to unpack there?', mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
        Exit;
    end;
    AddMessage('Unpacking LOD meshes into ' + fulllodpath + ', please wait ...');

    slFiles := TwbFastStringList.Create;
    slFiles.Sorted := True;
    slFiles.Duplicates := dupIgnore;
    slContainers := TStringList.Create;
    slAssets := TStringList.Create;

    try
      ResourceContainerList(slContainers);
      for i := 0 to Pred(slContainers.Count) do begin
        // no need to "unpack" if not in a BSA or a vanilla LOD file
        if (Pos('.bsa', Lowercase(slContainers[i])) > 0) and (wbStringListInString(slIgnoreBSAName, Lowercase(slContainers[i])) = -1) then begin
          //AddMessage('Checking ' + slContainers[i]);
          ResourceList(slContainers[i], slAssets);
          wbFilterStrings(slAssets, slFiles, lodpath);
        end;
      end;
    finally
      slAssets.Free;
      slContainers.Free;
    end;
    try
      try
        c := 0;
        for i := 0 to Pred(slFiles.Count) do begin
          // nif files only
          if ExtractFileExt(Lowercase(slFiles[i])) = '.nif' then begin
            AddMessage('found ' + slFiles[i]);
            ResourceCopy('', slFiles[i], sDataPath);
            inc(c);
          end;
        end;
        AddMessage('Done');
        MessageDlg(IntToStr(c) + ' LOD files were unpacked.', mtInformation, [mbOk], 0);
      except
        on E: Exception do
          AddMessage('Error copying file ' + slFiles[i] + ': ' + E.Message);
      end;
    finally
      slFiles.Free;
    end;
  end;
  slFolders.Free;
end;

//===========================================================================
procedure btnLODGenExeClick(Sender: TObject);
begin
  sDataPath := IncludeTrailingBackslash(edDataPath.Text);
  LODGenExport := edExport.Text;
  SetLODGenParams;
  ExecuteLODGen;
end;

//===========================================================================
procedure btnCreateAtlasClick(Sender: TObject);
begin
  sWorld := cbWorld.Text;
  sDestination := edDestination.Text;
  CreateAtlas(False);
end;

//===========================================================================
// listview custom data event
procedure LODGenlvRulesData(Sender: TObject; Item: TListItem);
var
  i: integer;
begin
  Item.Caption := slLOD[Item.Index];
  i := slLOD.Objects[Item.Index];
  Item.SubItems.Add(slLODTypes[i and $F]);
  Item.SubItems.Add(slLODTypes[i shr 4 and $F]);
  Item.SubItems.Add(slLODTypes[i shr 8 and $F]);
  if i shr 24 and 1 = 1 then Item.SubItems.Add('Yes') else Item.SubItems.Add('');
  Item.SubItems.Add(slGridTypes[i shr 12 and $F]);
  Item.SubItems.Add(slReferenceTypes[i shr 16 and $F]);
end;

//===========================================================================
// on double click event handler for mesh rules
procedure LODGenlvRulesDblClick(Sender: TObject);
begin
  LODGenRulesMenuEditClick(nil);
end;

//===========================================================================
// click event for lodgen rules Insert menu
procedure LODGenRulesMenuInsertClick(Sender: TObject);
var
  mesh: string;
  idx, lod, lod4, lod8, lod16, grid, Reference: integer;
  vwd: boolean;
begin
  if Assigned(lvRules.Selected) then
    idx := lvRules.Selected.Index
  else
    idx := 0;
  lod4 := 2;
  lod8 := 3;
  lod16 := 4;
  vwd := True;
  grid := 0;
  Reference := 0;
  if not LODGenEditRuleForm(mesh, lod4, lod8, lod16, grid, Reference, vwd) then
    Exit;
  lod := 0;
  if vwd then lod := lod or $01000000;
  slLOD.InsertObject(idx, mesh, lod + lod4 + lod8 shl 4 + lod16 shl 8 + grid shl 12 + Reference shl 16);
  lvRules.Items.Count := slLOD.Count;
  lvRules.Refresh;
  chkUseCache.Checked := False;
end;

//===========================================================================
// click event for lodgen rules Edit menu
procedure LODGenRulesMenuEditClick(Sender: TObject);
var
  mesh: string;
  idx, lod, lod4, lod8, lod16, grid, Reference: integer;
  vwd: boolean;
begin
  if not Assigned(lvRules.Selected) then
    Exit;
  idx := lvRules.Selected.Index;
  mesh := slLOD[idx];
  lod := slLOD.Objects[idx];
  if lod and $01000000 = $01000000 then vwd := True;
  lod4 := lod and $F;
  lod8 := lod shr 4 and $F;
  lod16 := lod shr 8 and $F;
  grid := lod shr 12 and $F;
  Reference := lod shr 16 and $F;
  if not LODGenEditRuleForm(mesh, lod4, lod8, lod16, grid, Reference, vwd) then
    Exit;
  lod := 0;
  if vwd then lod := lod or $01000000;
  slLOD[idx] := mesh;
  slLOD.Objects[idx] := lod + lod4 + lod8 shl 4 + lod16 shl 8 + grid shl 12 + Reference shl 16;
  lvRules.Refresh;
  chkUseCache.Checked := False;
end;

//===========================================================================
// does terrain folder exist and have files?
// now that LODGen can read from BSA not needed anymore
function TerrainDirectoryExists(sWorld: string): boolean;
var
  Rec: TSearchRec;
  lodpath, fulllodpath: string;
begin
  Result := True;
  exit;
  //lodpath := 'meshes\terrain\' + LowerCase(sWorld);
  //sDataPath := IncludeTrailingBackslash(edDataPath.Text);
  //fulllodpath := IncludeTrailingBackslash(sDataPath + lodpath);
  //if DirectoryExists(fulllodpath) then begin
  //  if FindFirst (fulllodpath + '*.btr', faAnyFile - faDirectory, Rec)=0 then
  //    Result := True;
  //  FindClose(Rec);
  //end;
end;

//============================================================================
// edit a mesh rule in a separate form
function LODGenEditRuleForm(var aMesh: string; var aLOD4, aLOD8, aLOD16, aGrid, aReference: integer;
  var aVWD: Boolean): Boolean;
var
  frmRule: TForm;
  pnl: TPanel;
  lbl: TLabel;
  btnOk, btnCancel: TButton;
  edMesh, edMod: TEdit;
  cb4, cb8, cb16, cbGrid, cbReference: TComboBox;
  chkVWD: TCheckBox;
  gbMeshRuleOptions: TGroupBox;
begin
  frmRule := TForm.Create(nil);
  try
    frmRule.Caption := 'Mesh LODGen export rule';
    frmRule.Width := 448;
    frmRule.Height := 448;
    frmRule.Position := poMainFormCenter;
    frmRule.BorderStyle := bsDialog;
    frmRule.KeyPreview := True;
    frmRule.OnKeyDown := FormKeyDown;
    frmRule.OnClose := frmRuleFormClose;

    edMesh := TEdit.Create(frmRule); edMesh.Parent := frmRule;
    edMesh.Name := 'edMesh';
    edMesh.Left := 98;
    edMesh.Top := 12;
    edMesh.Width := frmRule.Width - 118;
    CreateLabel(frmRule, 16, edMesh.Top + 4, 'Mesh mask / FID');

    lbl := CreateLabel(frmRule, 16, edMesh.Top + 28, '');
    lbl.AutoSize := False;
    lbl.WordWrap := True;
    lbl.Width := frmRule.Width - 32;
    lbl.Height := 48;
    lbl.Caption := 'Mesh mask - ''PathTo\FileName.nif'' of the full model used by a base element'#13 +
                   'FID - ''ModFileName;FormID'' of a reference, e.g. Skyrim.esm;000F64BF';

    gbMeshRuleOptions := TGroupBox.Create(frm); gbMeshRuleOptions.Parent := frmRule;
    gbMeshRuleOptions.Left := lvRules.Left;
    gbMeshRuleOptions.Top := lbl.Top + 28;
    gbMeshRuleOptions.Width := frmRule.Width - 36;
    gbMeshRuleOptions.Height := 144;
    gbMeshRuleOptions.Caption := 'LODGen Options';

    cb4 := TComboBox.Create(frmRule); cb4.Parent := gbMeshRuleOptions;
    cb4.Left := 80;
    cb4.Top := 18;
    cb4.Width := 150;
    cb4.Style := csDropDownList;
    cb4.Items.Assign(slLODTypes);
    CreateLabel(gbMeshRuleOptions, 16, cb4.Top + 4, 'LOD 4');

    cb8 := TComboBox.Create(frmRule); cb8.Parent := gbMeshRuleOptions;
    cb8.Left := 80;
    cb8.Top := cb4.Top + 28;
    cb8.Width := 150;
    cb8.Style := csDropDownList;
    cb8.Items.Assign(slLODTypes);
    CreateLabel(gbMeshRuleOptions, 16, cb8.Top + 4, 'LOD 8');

    cb16 := TComboBox.Create(frmRule); cb16.Parent := gbMeshRuleOptions;
    cb16.Left := 80;
    cb16.Top := cb8.Top + 28;
    cb16.Width := 150;
    cb16.Style := csDropDownList;
    cb16.Items.Assign(slLODTypes);
    CreateLabel(gbMeshRuleOptions, 16, cb16.Top + 4, 'LOD 16');

    chkVWD := TCheckBox.Create(frmRule); chkVWD.Parent := gbMeshRuleOptions;
    chkVWD.Left := 16;
    chkVWD.Top := cb16.Top + 28;
    chkVWD.Width := 350;
    chkVWD.Caption := 'Visible when distant objects only';

    gbMeshRuleOptions := TGroupBox.Create(frm); gbMeshRuleOptions.Parent := frmRule;
    gbMeshRuleOptions.Left := lvRules.Left;
    gbMeshRuleOptions.Top := edMesh.Top + gbMeshRuleOptions.Height + 74;
    gbMeshRuleOptions.Width := frmRule.Width - 36;
    gbMeshRuleOptions.Height := frmRule.Height - gbMeshRuleOptions.Top - 78;
    gbMeshRuleOptions.Caption := 'DynDOLOD Options';

    cbGrid := TComboBox.Create(frmRule); cbGrid.Parent := gbMeshRuleOptions;
    cbGrid.Left := 80;
    cbGrid.Top := 18;
    cbGrid.Width := 150;
    cbGrid.Style := csDropDownList;
    cbGrid.Items.Assign(slGridTypes);
    CreateLabel(gbMeshRuleOptions, 16, cbGrid.Top + 4, 'Grid');

    lbl := CreateLabel(frmRule, 16, cbGrid.Top + 28, '');
    lbl.Parent := gbMeshRuleOptions;
    lbl.AutoSize := False;
    lbl.WordWrap := True;
    lbl.Width := gbMeshRuleOptions.Width - 32;
    lbl.Height := 40;
    lbl.Caption := 'Full - full model even if LOD model available'#13 +
                   'LOD - LOD model, full model for animated and reference replace and as fall back';

    cbReference := TComboBox.Create(frmRule); cbReference.Parent := gbMeshRuleOptions;
    cbReference.Left := 80;
    cbReference.Top := lbl.Top + lbl.Height + 4;
    cbReference.Width := 150;
    cbReference.Style := csDropDownList;
    cbReference.Items.Assign(slReferenceTypes);
    CreateLabel(gbMeshRuleOptions, 16, cbReference.Top + 4, 'Reference');

    lbl := CreateLabel(frmRule, 16, cbReference.Top + 28, '');
    lbl.Parent := gbMeshRuleOptions;
    lbl.AutoSize := False;
    lbl.WordWrap := True;
    lbl.Width := gbMeshRuleOptions.Width - 32;
    lbl.Height := gbMeshRuleOptions.Height - cbReference.Top - 32;
    lbl.Caption := 'On cell attach:'#9'Unchanged - original full model, LOD off'#13 +
                   #9#9'Replace - original disabled, continue with LOD'#13 +
                   #9#9'Disable - original disabled, no LOD'#13 +
                   #9#9'Keep - original full model, continue with LOD';


    btnOk := TButton.Create(frmRule); btnOk.Parent := frmRule;
    btnOk.Caption := 'OK';
    btnOk.ModalResult := mrOk;
    btnOk.Left := frmRule.Width - 176;
    btnOk.Top := frmRule.Height - 62;
    btnCancel := TButton.Create(frmRule); btnCancel.Parent := frmRule;
    btnCancel.Caption := 'Cancel';
    btnCancel.ModalResult := mrCancel;
    btnCancel.Left := btnOk.Left + btnOk.Width + 8;
    btnCancel.Top := btnOk.Top;
    pnl := TPanel.Create(frmRule); pnl.Parent := frmRule; pnl.Left := 8; pnl.Top := btnOk.Top - 12; pnl.Width := frmRule.Width - 20; pnl.Height := 2;
    
    edMesh.Text := aMesh;
    cb4.ItemIndex := aLOD4;
    cb8.ItemIndex := aLOD8;
    cb16.ItemIndex := aLOD16;
    cbGrid.ItemIndex := aGrid;
    cbReference.ItemIndex := aReference;
    chkVWD.Checked := aVWD;
    
    if frmRule.ShowModal <> mrOk then
      Exit;
    
    aMesh := edMesh.Text;
    aLOD4 := cb4.ItemIndex;
    aLOD8 := cb8.ItemIndex;
    aLOD16 := cb16.ItemIndex;
    aGrid := cbGrid.ItemIndex;
    aReference := cbReference.ItemIndex;
    aVWD := chkVWD.Checked;
    Result := True;
  finally
    frmRule.Free;
  end;
end;

//============================================================================
procedure btnSKSEPluginPathClick(Sender: TObject);
var
  s: string;
begin
  s := SelectDirectory('Data folder', '', edSKSEPluginPath.Text, nil);
  if s <> '' then
    edSKSEPluginPath.Text := s;
end;

//============================================================================
procedure SetLODGenParams;
begin
  LODGenParams := Format(' "%s" ', [LODGenExport]);
  if chkDontFixTangents.Checked then
    LODGenParams := LODGenParams + ' --dontFixTangents';
  if chkNoTangents.Checked then
    LODGenParams := LODGenParams + ' --dontGenerateTangents';
  if chkNoColors.Checked then
    LODGenParams := LODGenParams + ' --dontGenerateVertexColors';
  if chkNoMaterial.Checked then
    LODGenParams := LODGenParams + ' --ignoreMaterial';
  if chkRemoveUnseen.Checked and TerrainDirectoryExists(cbWorld.Text) then begin
    LODGenParams := LODGenParams + ' --removeUnseenFaces';
    if chkIgnoreWater.Checked then
      LODGenParams := LODGenParams + ' --ignoreWater';
  end;
  if cbLODLevel.Text <> '' then
    LODGenParams := LODGenParams + ' --lodLevel ' + cbLODLevel.Text;
  if cbLODX.Text <> '' then
    LODGenParams := LODGenParams + ' --x ' + cbLODX.Text;
  if cbLODY.Text <> '' then
    LODGenParams := LODGenParams + ' --y ' + cbLODY.Text;
  if Trim(edLODScale.Text) <> '' then
    LODGenParams := LODGenParams + ' --globalScale ' + Trim(edLODScale.Text);
  // change 'textures' to 'textures\tes4' if lod mesh is in Oblivion nif format
  LODGenParams := LODGenParams + ' --skyblivionTexPath';
  //LODGenParams := LODGenParams + ' --verbose';
end;

//============================================================================
procedure LODGenOptionsForm;
var
  i: integer;
  mnRules: TPopupMenu;
  MenuItem: TMenuItem;
  pnl: TPanel;
  lbl, lblMinion, lblMaster: TLabel;
  lodpath, fulllodpath: string;
  gbLODGenOptions, gbDynDOLODOptions: TGroupBox;
begin
  frm := TForm.Create(nil);
  try
    frm.Caption := sTitle;
    frm.Width := WindowWidth;
    frm.Height := WindowHeight;
    frm.Position := poMainFormCenter;
    frm.BorderStyle := bsDialog;
    frm.KeyPreview := True;
    frm.OnKeyDown := FormKeyDown;
    frm.OnClose := frmOptionsFormClose;
    
    cbWorld := TComboBox.Create(frm); cbWorld.Parent := frm;
    cbWorld.Left := 54;
    cbWorld.Top := 12;
    cbWorld.Width := 260;
    cbWorld.Style := csDropDownList;
    cbWorld.DropDownCount := 20;
    cbWorld.OnSelect := cbLODGenWorldSelect;
    CreateLabel(frm, 16, cbWorld.Top + 4, 'World');
    
    FillWorldspacesWithLOD(cbWorld);
    if cbWorld.Items.Count > 0 then cbWorld.ItemIndex := 0;

    btnSave := TButton.Create(frm); btnSave.Parent := frm;
    btnSave.Caption := 'Save preset';
    btnSave.Left := cbWorld.Left + cbWorld.Width + 8;
    btnSave.Top := cbWorld.Top - 2;
    btnSave.OnClick := btnSaveClick;

    btnLoad := TButton.Create(frm); btnLoad.Parent := frm;
    btnLoad.Caption := 'Load preset';
    btnLoad.Left := btnSave.Left + btnSave.Width + 8;
    btnLoad.Top := btnSave.Top;
    btnLoad.OnClick := btnLoadClick;

    btnDefault := TButton.Create(frm); btnDefault.Parent := frm;
    btnDefault.Caption := 'Default';
    btnDefault.Left := btnLoad.Left + btnLoad.Width + 8;
    btnDefault.Top := btnSave.Top;
    btnDefault.OnClick := LODGenbtnDefaultClick;

    btnLow := TButton.Create(frm); btnLow.Parent := frm;
    btnLow.Caption := 'Low';
    btnLow.Left := btnDefault.Left + btnDefault.Width + 8;
    btnLow.Top := btnSave.Top;
    btnLow.OnClick := LODGenbtnLowClick;

    btnMedium := TButton.Create(frm); btnMedium.Parent := frm;
    btnMedium.Caption := 'Medium';
    btnMedium.Left := btnLow.Left + btnLow.Width + 8;
    btnMedium.Top := btnSave.Top;
    btnMedium.OnClick := LODGenbtnMediumClick;

    btnHigh := TButton.Create(frm); btnHigh.Parent := frm;
    btnHigh.Caption := 'High';
    btnHigh.Left := btnMedium.Left + btnMedium.Width + 8;
    btnHigh.Top := btnSave.Top;
    btnHigh.OnClick := LODGenbtnHighClick;

    edExport := TEdit.Create(frm); edExport.Parent := frm;
    edExport.Left := 120;
    edExport.Top := cbWorld.Top + cbWorld.Height + 12;
    edExport.Width := frm.Width - 182;
    //edExport.ReadOnly := True;
    CreateLabel(frm, 16, edExport.Top + 4, 'LODGen export');

    btnExport := TButton.Create(frm); btnExport.Parent := frm;
    btnExport.Top := edExport.Top - 1;
    btnExport.Left := edExport.Left + edExport.Width + 6;
    btnExport.Width := 32;
    btnExport.Height := 22;
    btnExport.Caption := '...';
    btnExport.OnClick := btnExportClick;

    edDestination := TEdit.Create(frm); edDestination.Parent := frm;
    edDestination.Left := edExport.Left;
    edDestination.Top := edExport.Top + 28;
    edDestination.Width := edExport.Width;
    edDestination.ReadOnly := True;
    CreateLabel(frm, 16, edDestination.Top + 4, 'Output path');

    btnDestination := TButton.Create(frm); btnDestination.Parent := frm;
    btnDestination.Top := edDestination.Top - 1;
    btnDestination.Left := edDestination.Left + edDestination.Width + 6;
    btnDestination.Width := 32;
    btnDestination.Height := 22;
    btnDestination.Caption := '...';
    btnDestination.OnClick := btnDestinationClick;

    edDataPath := TEdit.Create(frm); edDataPath.Parent := frm;
    edDataPath.Left := edExport.Left;
    edDataPath.Top := edDestination.Top + 28;
    edDataPath.Width := edExport.Width;
    edDataPath.ReadOnly := True;
    CreateLabel(frm, 16, edDataPath.Top + 4, 'Data path');

    btnDataPath := TButton.Create(frm); btnDataPath.Parent := frm;
    btnDataPath.Top := edDataPath.Top - 1;
    btnDataPath.Left := edDataPath.Left + edDataPath.Width + 6;
    btnDataPath.Width := 32;
    btnDataPath.Height := 22;
    btnDataPath.Caption := '...';
    btnDataPath.OnClick := btnDataPathClick;

    edSKSEPluginPath := TEdit.Create(frm); edSKSEPluginPath.Parent := frm;
    edSKSEPluginPath.Left := edExport.Left;
    edSKSEPluginPath.Top := edDataPath.Top + 28;
    edSKSEPluginPath.Width := edExport.Width;
    edSKSEPluginPath.ReadOnly := True;
    CreateLabel(frm, 16, edSKSEPluginPath.Top + 4, 'StorageUtilData');

    btnSKSEPluginPath := TButton.Create(frm); btnSKSEPluginPath.Parent := frm;
    btnSKSEPluginPath.Top := edSKSEPluginPath.Top - 1;
    btnSKSEPluginPath.Left := edSKSEPluginPath.Left + edSKSEPluginPath.Width + 6;
    btnSKSEPluginPath.Width := 32;
    btnSKSEPluginPath.Height := 22;
    btnSKSEPluginPath.Caption := '...';
    btnSKSEPluginPath.OnClick := btnSKSEPluginPathClick;

    lvRules := TListView.Create(frm); lvRules.Parent := frm;
    lvRules.Left := 16;
    lvRules.Top := edSKSEPluginPath.Top + 44;
    lvRules.Width := frm.Width - 40;
    lvRules.Height := frm.Height - 414;
    lvRules.ReadOnly := True;
    lvRules.ViewStyle := vsReport;
    lvRules.DragMode := dmAutomatic;
    lvRules.RowSelect := True;
    lvRules.GridLines := True;
    lvRules.DoubleBuffered := True;
    lvRules.Columns.Add.Caption := 'Mesh';
    lvRules.Columns[0].Width := lvRules.Width - 564;

    lvRules.Columns.Add.Caption := 'LOD4';
    lvRules.Columns[1].Width := 90;
    lvRules.Columns.Add.Caption := 'LOD8';
    lvRules.Columns[2].Width := 90;
    lvRules.Columns.Add.Caption := 'LOD16';
    lvRules.Columns[3].Width := 90;
    lvRules.Columns.Add.Caption := 'VWD Only';
    lvRules.Columns[4].Width := 70;

    lvRules.Columns.Add.Caption := 'Grid';
    lvRules.Columns[5].Width := 110;
    lvRules.Columns.Add.Caption := 'Reference';
    lvRules.Columns[6].Width := 90;

    lvRules.OwnerData := True;
    lvRules.OnData := LODGenlvRulesData;
    lvRules.OnSelectItem := lvRulesSelectItem;
    lvRules.OnDblClick := LODGenlvRulesDblClick;
    lvRules.OnDragOver := lvRulesDragOver;
    lvRules.OnDragDrop := lvRulesDragDrop;
    lvRules.Items.Count := slLOD.Count;
    CreateLabel(frm, 16, lvRules.Top - 16, 'Mesh lodgen rules (right click for menu, drag&&drop to change order). Order matters!');

    mnRules := TPopupMenu.Create(frm);
    lvRules.PopupMenu := mnRules;
    MenuItem := TMenuItem.Create(mnRules);
    MenuItem.Caption := 'Insert';
    MenuItem.OnClick := LODGenRulesMenuInsertClick;
    mnRules.Items.Add(MenuItem);
    MenuItem := TMenuItem.Create(mnRules);
    MenuItem.Caption := 'Delete';
    MenuItem.OnClick := RulesMenuDeleteClick;
    mnRules.Items.Add(MenuItem);
    MenuItem := TMenuItem.Create(mnRules);
    MenuItem.Caption := 'Edit';
    MenuItem.OnClick := LODGenRulesMenuEditClick;
    mnRules.Items.Add(MenuItem);
    
    gbLODGenOptions := TGroupBox.Create(frm); gbLODGenOptions.Parent := frm;
    gbLODGenOptions.Left := lvRules.Left;
    gbLODGenOptions.Top := lvRules.Top + lvRules.Height + 12;
    gbLODGenOptions.Width := lvRules.Width - 200;
    gbLODGenOptions.Height := 144;
    gbLODGenOptions.Caption := 'LODGen Options';
    
    chkDontFixTangents := TCheckBox.Create(frm); chkDontFixTangents.Parent := gbLODGenOptions;
    chkDontFixTangents.Left := 12;
    chkDontFixTangents.Top := 18;
    chkDontFixTangents.Width := 140;
    chkDontFixTangents.Caption := 'Don''t fix tangents';
    chkDontFixTangents.Hint := 'Don''t fix tangents.';
    chkDontFixTangents.ShowHint := True;
    chkDontFixTangents.Checked := True;

    chkNoTangents := TCheckBox.Create(frm); chkNoTangents.Parent := gbLODGenOptions;
    chkNoTangents.Left := 12;
    chkNoTangents.Top := 38;
    chkNoTangents.Width := 140;
    chkNoTangents.Caption := 'Don''t generate tangents';
    chkNoTangents.Hint := 'Don''t generate tangents. Useful when using _Far meshes.';
    chkNoTangents.ShowHint := True;

    chkNoColors := TCheckBox.Create(frm); chkNoColors.Parent := gbLODGenOptions;
    chkNoColors.Left := 12;
    chkNoColors.Top := 58;
    chkNoColors.Width := 180;
    chkNoColors.Caption := 'Don''t generate vertex colors';
    chkNoColors.Hint := 'Don''t generate vertex colors. Reduces lodgen files size.';
    chkNoColors.ShowHint := True;

    chkNoMaterial := TCheckBox.Create(frm); chkNoMaterial.Parent := gbLODGenOptions;
    chkNoMaterial.Left := 12;
    chkNoMaterial.Top := 78;
    chkNoMaterial.Width := 180;
    chkNoMaterial.Caption := 'Ignore material';
    chkNoMaterial.Hint := 'Don''t use objSnow/objAsh for statics with directional material.';
    chkNoMaterial.ShowHint := True;

    chkRemoveUnseen := TCheckBox.Create(frm); chkRemoveUnseen.Parent := gbLODGenOptions;
    chkRemoveUnseen.Left := 12;
    chkRemoveUnseen.Top := 98;
    chkRemoveUnseen.Width := 380;
    chkRemoveUnseen.Caption := 'Remove unseen faces';
    chkRemoveUnseen.Hint := 'Remove mesh parts that are under the landscape or water.';
    chkRemoveUnseen.ShowHint := True;
    chkRemoveUnseen.Checked := True;

    chkIgnoreWater := TCheckBox.Create(frm); chkIgnoreWater.Parent := gbLODGenOptions;
    chkIgnoreWater.Left := 32;
    chkIgnoreWater.Top := 118;
    chkIgnoreWater.Width := 150;
    chkIgnoreWater.Caption := 'Keep underwater LOD';
    chkIgnoreWater.Hint := 'Do not remove LOD geometry under water.';
    chkIgnoreWater.ShowHint := True;

    btnUnpackLandscape := TButton.Create(frm); btnUnpackLandscape.Parent := gbLODGenOptions;
    btnUnpackLandscape.Caption := 'Unpack landscape LOD';
    btnUnpackLandscape.Left := chkRemoveUnseen.Left + chkRemoveUnseen.Width + 4;
    btnUnpackLandscape.Top := chkRemoveUnseen.Top - 4;
    btnUnpackLandscape.Width := 150;
    btnUnpackLandscape.OnClick := btnUnpackLandscapeClick;

    cbLODLevel := TComboBox.Create(frm); cbLODLevel.Parent := gbLODGenOptions;
    cbLODLevel.Left := 440;
    cbLODLevel.Top := 18;
    cbLODLevel.Width := 40;
    cbLODLevel.Style := csDropDownList;
    cbLODLevel.Items.Text := ''#13'4'#13'8'#13'16';
    cbLODLevel.ItemIndex := 0;
    CreateLabel(gbLODGenOptions, cbLODLevel.Left - 160, cbLODLevel.Top + 4, 'Generate LOD level(s) only');

    cbLODX := TComboBox.Create(frm); cbLODX.Parent := gbLODGenOptions;
    cbLODX.Left := cbLODLevel.Left;
    cbLODX.Top := 42;
    cbLODX.Width := 40;
    cbLODX.Style := csDropDownList;
    cbLODX.DropDownCount := 20;
    cbLODX.Items.Text := #13;
    for i := -64 to 64 do
      cbLODX.Items.Text := cbLODX.Items.Text + IntToStr(i);
    cbLODX.ItemIndex := 0;
    cbLODY := TComboBox.Create(frm); cbLODY.Parent := gbLODGenOptions;
    cbLODY.Left := cbLODX.Left + 46;
    cbLODY.Top := cbLODX.Top;
    cbLODY.Width := 40;
    cbLODY.Style := csDropDownList;
    cbLODY.DropDownCount := 20;
    cbLODY.Items.Assign(cbLODX.Items);
    cbLODY.ItemIndex := 0;
    CreateLabel(gbLODGenOptions, cbLODX.Left - 160, cbLODX.Top + 4, 'Generate for cell(s) [x,y] only');

    edLODScale := TEdit.Create(frm); edLODScale.Parent := gbLODGenOptions;
    edLODScale.Left := cbLODLevel.Left;
    edLODScale.Top := 66;
    edLODScale.Width := 40;
    CreateLabel(gbLODGenOptions, edLODScale.Left - 160, edLODScale.Top + 4, 'Global LOD objects scale');

    gbDynDOLODOptions := TGroupBox.Create(frm); gbDynDOLODOptions.Parent := frm;
    gbDynDOLODOptions.Left := lvRules.Width - 176;
    gbDynDOLODOptions.Top := lvRules.Top + lvRules.Height + 12;
    gbDynDOLODOptions.Width := lvRules.Width - gbDynDOLODOptions.Left + 18;
    gbDynDOLODOptions.Height := 144;
    gbDynDOLODOptions.Caption := 'DynDOLOD Options';

    edMinion := TEdit.Create(frm); edMinion.Parent := gbDynDOLODOptions;
    edMinion.Left := 110;
    edMinion.Top := 18;
    edMinion.Width := 40;
    edMinion.Hint := 'Odd numbers only, less than FarGridsToLoad';
    edMinion.ShowHint := True;
    lblMinion := CreateLabel(gbDynDOLODOptions, edMinion.Left - 98, edMinion.Top + 4, 'NearGridToLoad');

    edMaster := TEdit.Create(frm); edMaster.Parent := gbDynDOLODOptions;
    edMaster.Left := 110;
    edMaster.Top := edMinion.Top + 18;
    edMaster.Width := 40;
    edMaster.Hint := 'Odd numbers only, greater than NearGridsToLoad';
    edMaster.ShowHint := True;
    lblMaster := CreateLabel(gbDynDOLODOptions, edMaster.Left - 98, edMaster.Top + 4, 'FarGridToLoad');

    chkDoDynDOLOD := TCheckBox.Create(frm); chkDoDynDOLOD.Parent := gbDynDOLODOptions;
    chkDoDynDOLOD.Left := 12;
    chkDoDynDOLOD.Top := edMaster.Top + 20;
    chkDoDynDOLOD.Width := 150;
    chkDoDynDOLOD.Caption := 'Generate DynDOLOD';
    chkDoDynDOLOD.Hint := 'Check to generate dynamic distant object LOD, which requires papyrus scripting';
    chkDoDynDOLOD.ShowHint := True;
    chkDoDynDOLOD.Checked := True;

    chkCreateAtlas := TCheckBox.Create(frm); chkCreateAtlas.Parent := gbDynDOLODOptions;
    chkCreateAtlas.Left := chkDoDynDOLOD.Left;
    chkCreateAtlas.Top := chkDoDynDOLOD.Top + 16;
    chkCreateAtlas.Width := 150;
    chkCreateAtlas.Caption := 'Create texture atlas';
    chkCreateAtlas.Hint := 'Check to create texture atlas. Can be unchecked if nif/dds files have not changed after initial generation';
    chkCreateAtlas.ShowHint := True;
    chkCreateAtlas.Checked := True;

    chkUseOrgLOD := TCheckBox.Create(frm); chkUseOrgLOD.Parent := gbDynDOLODOptions;
    chkUseOrgLOD.Left := 12;
    chkUseOrgLOD.Top := chkCreateAtlas.Top + 16;
    chkUseOrgLOD.Width := 150;
    chkUseOrgLOD.Hint := 'Check to use the existing default LOD level mesh data';
    chkUseOrgLOD.ShowHint := True;
    if bCreateMod then begin
      chkUseOrgLOD.Caption := 'Keep original LOD assignments';
      chkUseOrgLOD.Checked := True
    end
    else begin
      chkUseOrgLOD.Caption := 'Use original LOD assignments';
      chkUseOrgLOD.Checked := False;
    end;

    chkUseCache := TCheckBox.Create(frm); chkUseCache.Parent := gbDynDOLODOptions;
    chkUseCache.Left := 12;
    chkUseCache.Top := chkUseOrgLOD.Top + 16;
    chkUseCache.Width := 150;
    chkUseCache.Caption := 'Use cached base elements';
    chkUseCache.Hint := 'Uncheck when changing rules or meshes';
    chkUseCache.ShowHint := True;
    chkUseCache.Checked := True;

    chkUseTreeCache := TCheckBox.Create(frm); chkUseTreeCache.Parent := gbDynDOLODOptions;
    chkUseTreeCache.Left := 12;
    chkUseTreeCache.Top := chkUseCache.Top + 16;
    chkUseTreeCache.Width := 150;
    chkUseTreeCache.Caption := 'Use cached tree data';
    chkUseTreeCache.Hint := 'Uncheck when tree LOD changed';
    chkUseTreeCache.ShowHint := True;
    chkUseTreeCache.Checked := True;

    //lbl := CreateLabel(frm, lvRules.Left, gbDynDOLODOptions.Top + gbDynDOLODOptions.Height + 8, '');
    //lbl.AutoSize := False;
    //lbl.WordWrap := True;
    //lbl.Width := lvRules.Width;
    //lbl.Height := 40;
    //lbl.Caption := 'DynDOLOD and LODGen requires meshes used in LOD to exist in the Data folder, ' +
    //               'otherwise there will be missing distant objects in game. ' +
    //               'The DynDOLOD Packages comes with its own version of the original LOD nifs from Skyrim and the DLC.' +
    //               'Make sure to overwrite any original LOD meshes.';

    btnLODGenExe := TButton.Create(frm); btnLODGenExe.Parent := frm;
    btnLODGenExe.Caption := 'Execute LODGen.exe';
    btnLODGenExe.Left := 12;
    btnLODGenExe.Top := frm.Height - 62;
    btnLODGenExe.Width := 150;
    btnLODGenExe.Hint := 'Execute LODGen.exe with the export file selected above';
    btnLODGenExe.ShowHint := True;
    btnLODGenExe.OnClick := btnLODGenExeClick;
    btnLODGenExe.Enabled := False;
	
	btnCreateAtlas := TButton.Create(frm); btnCreateAtlas.Parent := frm;
    btnCreateAtlas.Caption := 'Rebuild atlas';
    btnCreateAtlas.Left := btnLODGenExe.Left + btnLODGenExe.Width + 12;
    btnCreateAtlas.Top := frm.Height - 62;
    btnCreateAtlas.Width := 150;
    btnCreateAtlas.Hint := 'Rebuilds the textures atlas from cached data for the selected worldspace';
    btnCreateAtlas.ShowHint := True;
    btnCreateAtlas.OnClick := btnCreateAtlasClick;
	btnCreateAtlas.Enabled := False;

    btnUnpackLOD := TButton.Create(frm); btnUnpackLOD.Parent := frm;
    btnUnpackLOD.Caption := 'Unpack LOD models';
    btnUnpackLOD.Left := btnCreateAtlas.Left + btnCreateAtlas.Width + 12;
    btnUnpackLOD.Top := frm.Height - 62;
    btnUnpackLOD.Width := 150;
    btnUnpackLOD.Hint := 'Unpacks LOD files from mods BSA to the Data folder selected above';
    btnUnpackLOD.ShowHint := True;
    btnUnpackLOD.OnClick := btnUnpackLODClick;

    btnOk := TButton.Create(frm); btnOk.Parent := frm;
    btnOk.Caption := 'OK';
    btnOk.ModalResult := mrOk;
    btnOk.Left := frm.Width - 176;
    btnOk.Top := frm.Height - 62;
    
    btnCancel := TButton.Create(frm); btnCancel.Parent := frm;
    btnCancel.Caption := 'Cancel';
    btnCancel.ModalResult := mrCancel;
    btnCancel.Left := btnOk.Left + btnOk.Width + 8;
    btnCancel.Top := btnOk.Top;
    
    pnl := TPanel.Create(frm); pnl.Parent := frm; pnl.Left := 8; pnl.Top := btnOk.Top - 12; pnl.Width := frm.Width - 20; pnl.Height := 2;  
    
    edDataPath.Text := DataPath;
    edSKSEPluginPath.Text := DataPath + 'skse\plugins\StorageUtilData';
    LODGenPresetLoad(IniPath + 'DynDOLOD-LODGen preset default.ini');
    if (edExport.Text = '') or (edDestination.Text = '') then
      cbLODGenWorldSelect(cbWorld);

    chkRemoveUnseen.Enabled := TerrainDirectoryExists(cbWorld.Text);
    btnLODGenExe.Enabled := FileExists(edExport.Text);
	btnCreateAtlas.Enabled := FileExists(CachePath + 'atlas_map_' + Lowercase(cbWorld.Text) + '.txt');

    if edMinion.Text = '' then
      edMinion.Text := 11;

    if edMaster.Text = '' then
      edMaster.Text := 21;

    if not bAdvanced then begin
      gbLODGenOptions.Visible := False;
	  btnCreateAtlas.Visible := False;
      btnUnpackLOD.Visible := False;
      gbDynDOLODOptions.Left := lvRules.Left;
      gbDynDOLODOptions.Width := lvRules.Width;
      edMinion.Left := lblMinion.Left + lblMinion.Width + 6;
      lblMaster.Top := edMinion.Top + 26;
      lblMaster.Left := lblMinion.Left;
      edMaster.Left := edMinion.Left;
      edMaster.Top := edMinion.Top + 22;
      chkDoDynDOLOD.Top := edMinion.Top;
      chkDoDynDOLOD.Left := edMaster.Left + edMaster.Width + 32;
      chkCreateAtlas.Top := chkDoDynDOLOD.Top + chkDoDynDOLOD.Height;
      chkCreateAtlas.Left := chkDoDynDOLOD.Left;
      chkUseOrgLOD.Top := edMinion.Top;
      chkUseOrgLOD.Left := chkDoDynDOLOD.Left + chkDoDynDOLOD.Width + chkUseCache.Width + 24;
      chkUseCache.Top := edMinion.Top;
      chkUseCache.Left := chkDoDynDOLOD.Left + chkDoDynDOLOD.Width;
      chkUseTreeCache.Top := chkUseCache.Top + chkUseCache.Height;
      chkUseTreeCache.Left := chkUseCache.Left;
      gbDynDOLODOptions.Height := chkUseTreeCache.Top + chkUseTreeCache.Height + 16;
      lvRules.Height := frm.Height - 328;
      gbDynDOLODOptions.Top := lvRules.Top + lvRules.Height + 6;
    end;

    if bCreateMod then begin
      cbWorld.Enabled := False;
      edExport.Enabled := False;
      btnExport.Enabled := False;
      edDestination.Enabled := False;
      btnDestination.Enabled := False;
      edSKSEPluginPath.Enabled := False;
      btnSKSEPluginPath.Enabled := False;
      chkDontFixTangents.Enabled := False;
      chkNoTangents.Enabled := False;
      chkNoColors.Enabled := False;
      chkNoMaterial.Enabled := False;
      chkRemoveUnseen.Enabled := False;
      chkIgnoreWater.Enabled := False;
      btnUnpackLandscape.Enabled := False;
      cbLODLevel.Enabled := False;
      cbLODX.Enabled := False;
      cbLODY.Enabled := False;
      edLODScale.Enabled := False;
      edMinion.Enabled := False;
      edMaster.Enabled := False;
      chkDoDynDOLOD.Enabled := False;
      chkCreateAtlas.Enabled := False;
      chkUseCache.Enabled := False;
      chkUseTreeCache.Enabled := False;
      btnLODGenExe.Enabled := False;
      chkUseOrgLOD.Checked := True;
      chkUseCache.Checked := False;
    end;

    if frm.ShowModal <> mrOk then
      Exit;

    if edMinion.Text > edMaster.Text then
      edMinion.Text := edMaster.Text - 2;

    if bCreateMod then
      chkUseCache.Checked := False;

    sExport := edExport.Text;
    sDestination := edDestination.Text;
    sDataPath := IncludeTrailingBackslash(edDataPath.Text);

    sSKSEPluginPath := IncludeTrailingBackslash(edSKSEPluginPath.Text);
    MinionGridsToLoad := edMinion.Text;
    MasterGridsToLoad := edMaster.Text;
    bUpdateDDOL := chkDoDynDOLOD.Checked;
    bCreateAtlas := chkCreateAtlas.Checked;
    bUseCache := chkUseCache.Checked;
    bUseTreeCache := chkUseTreeCache.Checked;
    bUseOrgLOD := chkUseOrgLOD.Checked;
     
    LODGenPresetSave(IniPath + 'DynDOLOD-LODGen preset default.ini');
    
    // lower case all rules since we are searching with Pos()
    for i := 0 to Pred(slLOD.Count) do
      slLOD[i] := LowerCase(slLOD[i]);

    LODGenWorld := ObjectToElement(cbWorld.Items.Objects[cbWorld.ItemIndex]);
    LODGenExport := edExport.Text;

    SetLODGenParams;

  finally
    frm.Free;
  end;
end;

end.
