Files
FlaxEngine/Source/Editor/Tools/Terrain/EditTab.cs

406 lines
14 KiB
C#

// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using FlaxEditor.GUI;
using FlaxEditor.GUI.Tabs;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Tools.Terrain
{
/// <summary>
/// Carve tab related to terrain editing. Allows to pick a terrain patch and remove it or add new patches. Can be used to modify selected chunk properties.
/// </summary>
/// <seealso cref="Tab" />
[HideInEditor]
public class EditTab : Tab
{
/// <summary>
/// The parent carve tab.
/// </summary>
public readonly CarveTab CarveTab;
/// <summary>
/// The related edit terrain gizmo.
/// </summary>
public readonly EditTerrainGizmoMode Gizmo;
private readonly ComboBox _modeComboBox;
private readonly Label _selectionInfoLabel;
private readonly Button _deletePatchButton;
private readonly Button _exportTerrainButton;
private readonly ContainerControl _chunkProperties;
private readonly AssetPicker _chunkOverrideMaterial;
private bool _isUpdatingUI;
/// <summary>
/// Initializes a new instance of the <see cref="EditTab"/> class.
/// </summary>
/// <param name="tab">The parent tab.</param>
/// <param name="gizmo">The related gizmo.</param>
public EditTab(CarveTab tab, EditTerrainGizmoMode gizmo)
: base("Edit")
{
CarveTab = tab;
Gizmo = gizmo;
CarveTab.SelectedTerrainChanged += OnSelectionChanged;
Gizmo.SelectedChunkCoordChanged += OnSelectionChanged;
// Main panel
var panel = new Panel(ScrollBars.Both)
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
Parent = this
};
// Mode
var modeLabel = new Label(4, 4, 40, 18)
{
HorizontalAlignment = TextAlignment.Near,
Text = "Mode:",
Parent = panel,
};
_modeComboBox = new ComboBox(modeLabel.Right + 4, 4, 110)
{
Parent = panel,
};
_modeComboBox.AddItem("Edit Chunk");
_modeComboBox.AddItem("Add Patch");
_modeComboBox.AddItem("Remove Patch");
_modeComboBox.AddItem("Export terrain");
_modeComboBox.SelectedIndex = 0;
_modeComboBox.SelectedIndexChanged += (combobox) => Gizmo.EditMode = (EditTerrainGizmoMode.Modes)combobox.SelectedIndex;
Gizmo.ModeChanged += OnGizmoModeChanged;
// Info
_selectionInfoLabel = new Label(modeLabel.X, modeLabel.Bottom + 4, 40, 18 * 3)
{
VerticalAlignment = TextAlignment.Near,
HorizontalAlignment = TextAlignment.Near,
Parent = panel,
};
// Chunk Properties
_chunkProperties = new Panel(ScrollBars.None)
{
Location = new Float2(_selectionInfoLabel.X, _selectionInfoLabel.Bottom + 4),
Parent = panel,
};
var chunkOverrideMaterialLabel = new Label(0, 0, 90, 64)
{
HorizontalAlignment = TextAlignment.Near,
Text = "Override Material",
Parent = _chunkProperties,
};
_chunkOverrideMaterial = new AssetPicker(new ScriptType(typeof(MaterialBase)), new Float2(chunkOverrideMaterialLabel.Right + 4, 0))
{
Width = 300.0f,
Parent = _chunkProperties,
};
_chunkOverrideMaterial.SelectedItemChanged += OnSelectedChunkOverrideMaterialChanged;
_chunkProperties.Size = new Float2(_chunkOverrideMaterial.Right + 4, _chunkOverrideMaterial.Bottom + 4);
// Delete patch
_deletePatchButton = new Button(_selectionInfoLabel.X, _selectionInfoLabel.Bottom + 4)
{
Text = "Delete Patch",
Parent = panel,
};
_deletePatchButton.Clicked += OnDeletePatchButtonClicked;
// Export terrain
_exportTerrainButton = new Button(_selectionInfoLabel.X, _selectionInfoLabel.Bottom + 4)
{
Text = "Export terrain",
Parent = panel,
};
_exportTerrainButton.Clicked += OnExportTerrainButtonClicked;
// Update UI to match the current state
OnSelectionChanged();
OnGizmoModeChanged();
}
[Serializable]
private class DeletePatchAction : IUndoAction
{
[Serialize]
private Guid _terrainId;
[Serialize]
private Int2 _patchCoord;
[Serialize]
private string _data;
/// <inheritdoc />
public string ActionString => "Delete terrain patch";
public DeletePatchAction(FlaxEngine.Terrain terrain, ref Int2 patchCoord)
{
if (terrain == null)
throw new ArgumentException(nameof(terrain));
_terrainId = terrain.ID;
_patchCoord = patchCoord;
_data = TerrainTools.SerializePatch(terrain, patchCoord);
}
/// <inheritdoc />
public void Do()
{
var terrain = Object.Find<FlaxEngine.Terrain>(ref _terrainId);
if (terrain == null)
{
Editor.LogError("Missing terrain actor.");
return;
}
terrain.GetPatchBounds(terrain.GetPatchIndex(_patchCoord), out var patchBounds);
terrain.RemovePatch(_patchCoord);
OnPatchEdit(terrain, ref patchBounds);
}
/// <inheritdoc />
public void Undo()
{
var terrain = Object.Find<FlaxEngine.Terrain>(ref _terrainId);
if (terrain == null)
{
Editor.LogError("Missing terrain actor.");
return;
}
terrain.AddPatch(_patchCoord);
TerrainTools.DeserializePatch(terrain, _patchCoord, _data);
terrain.GetPatchBounds(terrain.GetPatchIndex(_patchCoord), out var patchBounds);
OnPatchEdit(terrain, ref patchBounds);
}
private void OnPatchEdit(FlaxEngine.Terrain terrain, ref BoundingBox patchBounds)
{
Editor.Instance.Scene.MarkSceneEdited(terrain.Scene);
var editorOptions = Editor.Instance.Options.Options;
bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode;
// Auto NavMesh rebuild
if (!isPlayMode && editorOptions.General.AutoRebuildNavMesh)
{
if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
{
Navigation.BuildNavMesh(terrain.Scene, patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
}
}
}
/// <inheritdoc />
public void Dispose()
{
_data = null;
}
}
private void OnDeletePatchButtonClicked()
{
if (_isUpdatingUI)
return;
var patchCoord = Gizmo.SelectedPatchCoord;
if (!CarveTab.SelectedTerrain.HasPatch(patchCoord))
return;
var action = new DeletePatchAction(CarveTab.SelectedTerrain, ref patchCoord);
action.Do();
CarveTab.Editor.Undo.AddAction(action);
}
[Serializable]
private class EditChunkMaterialAction : IUndoAction
{
[Serialize]
private Guid _terrainId;
[Serialize]
private Int2 _patchCoord;
[Serialize]
private Int2 _chunkCoord;
[Serialize]
private Guid _beforeMaterial;
[Serialize]
private Guid _afterMaterial;
/// <inheritdoc />
public string ActionString => "Edit terrain chunk material";
public EditChunkMaterialAction(FlaxEngine.Terrain terrain, ref Int2 patchCoord, ref Int2 chunkCoord, MaterialBase toSet)
{
if (terrain == null)
throw new ArgumentException(nameof(terrain));
_terrainId = terrain.ID;
_patchCoord = patchCoord;
_chunkCoord = chunkCoord;
_beforeMaterial = terrain.GetChunkOverrideMaterial(patchCoord, chunkCoord)?.ID ?? Guid.Empty;
_afterMaterial = toSet?.ID ?? Guid.Empty;
}
/// <inheritdoc />
public void Do()
{
Set(ref _afterMaterial);
}
/// <inheritdoc />
public void Undo()
{
Set(ref _beforeMaterial);
}
/// <inheritdoc />
public void Dispose()
{
}
private void Set(ref Guid id)
{
var terrain = Object.Find<FlaxEngine.Terrain>(ref _terrainId);
if (terrain == null)
{
Editor.LogError("Missing terrain actor.");
return;
}
terrain.SetChunkOverrideMaterial(_patchCoord, _chunkCoord, FlaxEngine.Content.LoadAsync<MaterialBase>(id));
Editor.Instance.Scene.MarkSceneEdited(terrain.Scene);
}
}
private void OnSelectedChunkOverrideMaterialChanged()
{
if (_isUpdatingUI)
return;
var patchCoord = Gizmo.SelectedPatchCoord;
var chunkCoord = Gizmo.SelectedChunkCoord;
var action = new EditChunkMaterialAction(CarveTab.SelectedTerrain, ref patchCoord, ref chunkCoord, _chunkOverrideMaterial.Validator.SelectedAsset as MaterialBase);
action.Do();
CarveTab.Editor.Undo.AddAction(action);
}
private void OnExportTerrainButtonClicked()
{
if (_isUpdatingUI)
return;
if (FileSystem.ShowBrowseFolderDialog(null, null, "Select the output folder", out var outputFolder))
return;
TerrainTools.ExportTerrain(CarveTab.SelectedTerrain, outputFolder);
}
private void OnSelectionChanged()
{
var terrain = CarveTab.SelectedTerrain;
if (terrain == null)
{
_selectionInfoLabel.Text = "Select a terrain to modify its properties.";
_chunkProperties.Visible = false;
_deletePatchButton.Visible = false;
_exportTerrainButton.Visible = false;
}
else
{
var patchCoord = Gizmo.SelectedPatchCoord;
switch (Gizmo.EditMode)
{
case EditTerrainGizmoMode.Modes.Edit:
{
var chunkCoord = Gizmo.SelectedChunkCoord;
_selectionInfoLabel.Text = string.Format(
"Selected terrain: {0}\nPatch: {1}x{2}\nChunk: {3}x{4}",
terrain.Name,
patchCoord.X, patchCoord.Y,
chunkCoord.X, chunkCoord.Y
);
_chunkProperties.Visible = true;
_deletePatchButton.Visible = false;
_exportTerrainButton.Visible = false;
_isUpdatingUI = true;
if (terrain.HasPatch(patchCoord))
{
_chunkOverrideMaterial.Validator.SelectedAsset = terrain.GetChunkOverrideMaterial(patchCoord, chunkCoord);
_chunkOverrideMaterial.Enabled = true;
}
else
{
_chunkOverrideMaterial.Validator.SelectedAsset = null;
_chunkOverrideMaterial.Enabled = false;
}
_isUpdatingUI = false;
break;
}
case EditTerrainGizmoMode.Modes.Add:
{
if (terrain.HasPatch(patchCoord))
{
_selectionInfoLabel.Text = string.Format(
"Selected terrain: {0}\nMove mouse cursor at location without a patch.",
terrain.Name
);
}
else
{
_selectionInfoLabel.Text = string.Format(
"Selected terrain: {0}\nPatch to add: {1}x{2}\nTo add a new patch press the left mouse button.",
terrain.Name,
patchCoord.X, patchCoord.Y
);
}
_chunkProperties.Visible = false;
_deletePatchButton.Visible = false;
_exportTerrainButton.Visible = false;
break;
}
case EditTerrainGizmoMode.Modes.Remove:
{
_selectionInfoLabel.Text = string.Format(
"Selected terrain: {0}\nPatch: {1}x{2}",
terrain.Name,
patchCoord.X, patchCoord.Y
);
_chunkProperties.Visible = false;
_deletePatchButton.Visible = true;
_exportTerrainButton.Visible = false;
break;
}
case EditTerrainGizmoMode.Modes.Export:
{
_selectionInfoLabel.Text = string.Format(
"Selected terrain: {0}",
terrain.Name
);
_chunkProperties.Visible = false;
_deletePatchButton.Visible = false;
_exportTerrainButton.Visible = true;
break;
}
}
}
}
private void OnGizmoModeChanged()
{
_modeComboBox.SelectedIndex = (int)Gizmo.EditMode;
OnSelectionChanged();
}
}
}