338 lines
12 KiB
C#
338 lines
12 KiB
C#
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
|
|
|
using FlaxEditor.CustomEditors;
|
|
using FlaxEditor.CustomEditors.Editors;
|
|
using FlaxEditor.GUI;
|
|
using FlaxEditor.GUI.Tabs;
|
|
using FlaxEngine;
|
|
using FlaxEngine.GUI;
|
|
|
|
namespace FlaxEditor.Tools.Foliage
|
|
{
|
|
/// <summary>
|
|
/// Foliage painting tab. Allows to add or remove foliage instances defined for the current foliage object.
|
|
/// </summary>
|
|
/// <seealso cref="GUI.Tabs.Tab" />
|
|
public class PaintTab : Tab
|
|
{
|
|
/// <summary>
|
|
/// The object for foliage painting settings adjusting via Custom Editor.
|
|
/// </summary>
|
|
[CustomEditor(typeof(ProxyObjectEditor))]
|
|
private sealed class ProxyObject
|
|
{
|
|
private readonly PaintFoliageGizmoMode _mode;
|
|
private readonly PaintTab _tab;
|
|
|
|
/// <summary>
|
|
/// The foliage actor.
|
|
/// </summary>
|
|
[HideInEditor]
|
|
public FlaxEngine.Foliage Foliage;
|
|
|
|
/// <summary>
|
|
/// The selected foliage instance type index.
|
|
/// </summary>
|
|
[HideInEditor]
|
|
public int SelectedFoliageTypeIndex;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ProxyObject"/> class.
|
|
/// </summary>
|
|
/// <param name="tab">The tab.</param>
|
|
/// <param name="mode">The mode.</param>
|
|
public ProxyObject(PaintTab tab, PaintFoliageGizmoMode mode)
|
|
{
|
|
_mode = mode;
|
|
_tab = tab;
|
|
SelectedFoliageTypeIndex = -1;
|
|
}
|
|
|
|
private FlaxEngine.FoliageType _type;
|
|
|
|
public void SyncOptions()
|
|
{
|
|
_type = Foliage.GetFoliageType(SelectedFoliageTypeIndex);
|
|
}
|
|
|
|
//
|
|
|
|
[EditorOrder(100), EditorDisplay("Brush", EditorDisplayAttribute.InlineStyle)]
|
|
public Brush Brush
|
|
{
|
|
get => _mode.CurrentBrush;
|
|
set { }
|
|
}
|
|
|
|
//
|
|
|
|
[EditorOrder(200), EditorDisplay("Painting"), Limit(0.0f), Tooltip("The foliage instances density defined in instances count per 1000x1000 units area.")]
|
|
public float Density
|
|
{
|
|
get => _type.PaintDensity;
|
|
set => _type.PaintDensity = value;
|
|
}
|
|
|
|
[EditorOrder(210), EditorDisplay("Painting"), Limit(0.0f), Tooltip("The minimum radius between foliage instances.")]
|
|
public float Radius
|
|
{
|
|
get => _type.PaintRadius;
|
|
set => _type.PaintRadius = value;
|
|
}
|
|
|
|
[EditorOrder(215), EditorDisplay("Painting"), Limit(0.0f, 360.0f), Tooltip("The minimum and maximum ground slope angle to paint foliage on it (in degrees).")]
|
|
public Vector2 PaintGroundSlopeAngleRange
|
|
{
|
|
get => new Vector2(_type.PaintGroundSlopeAngleMin, _type.PaintGroundSlopeAngleMax);
|
|
set
|
|
{
|
|
_type.PaintGroundSlopeAngleMin = value.X;
|
|
_type.PaintGroundSlopeAngleMax = value.Y;
|
|
}
|
|
}
|
|
|
|
[EditorOrder(220), EditorDisplay("Painting"), Tooltip("The scaling mode.")]
|
|
public FoliageScalingModes Scaling
|
|
{
|
|
get => _type.PaintScaling;
|
|
set => _type.PaintScaling = value;
|
|
}
|
|
|
|
[EditorOrder(230), EditorDisplay("Painting"), Limit(0.0f), CustomEditor(typeof(ActorTransformEditor.PositionScaleEditor)), Tooltip("The scale minimum values per axis.")]
|
|
public Vector3 ScaleMin
|
|
{
|
|
get => _type.PaintScaleMin;
|
|
set => _type.PaintScaleMin = value;
|
|
}
|
|
|
|
[EditorOrder(240), EditorDisplay("Painting"), Limit(0.0f), CustomEditor(typeof(ActorTransformEditor.PositionScaleEditor)), Tooltip("The scale maximum values per axis.")]
|
|
public Vector3 ScaleMax
|
|
{
|
|
get => _type.PaintScaleMax;
|
|
set => _type.PaintScaleMax = value;
|
|
}
|
|
|
|
//
|
|
|
|
[EditorOrder(300), EditorDisplay("Placement", "Offset Y"), Tooltip("The per-instance random offset range on axis Y (min-max).")]
|
|
public Vector2 OffsetY
|
|
{
|
|
get => _type.PlacementOffsetY;
|
|
set => _type.PlacementOffsetY = value;
|
|
}
|
|
|
|
[EditorOrder(310), EditorDisplay("Placement"), Limit(0.0f), Tooltip("The random pitch angle range (uniform in both ways around normal vector).")]
|
|
public float RandomPitchAngle
|
|
{
|
|
get => _type.PlacementRandomPitchAngle;
|
|
set => _type.PlacementRandomPitchAngle = value;
|
|
}
|
|
|
|
[EditorOrder(320), EditorDisplay("Placement"), Limit(0.0f), Tooltip("The random roll angle range (uniform in both ways around normal vector).")]
|
|
public float RandomRollAngle
|
|
{
|
|
get => _type.PlacementRandomRollAngle;
|
|
set => _type.PlacementRandomRollAngle = value;
|
|
}
|
|
|
|
[EditorOrder(330), EditorDisplay("Placement", "Align To Normal"), Tooltip("If checked, instances will be aligned to normal of the placed surface.")]
|
|
public bool AlignToNormal
|
|
{
|
|
get => _type.PlacementAlignToNormal;
|
|
set => _type.PlacementAlignToNormal = value;
|
|
}
|
|
|
|
[EditorOrder(340), EditorDisplay("Placement"), Tooltip("If checked, instances will use randomized yaw when placed. Random yaw uses will rotation range over the Y axis.")]
|
|
public bool RandomYaw
|
|
{
|
|
get => _type.PlacementRandomYaw;
|
|
set => _type.PlacementRandomYaw = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The custom editor for <see cref="ProxyObject"/>.
|
|
/// </summary>
|
|
/// <seealso cref="FlaxEditor.CustomEditors.Editors.GenericEditor" />
|
|
private sealed class ProxyObjectEditor : GenericEditor
|
|
{
|
|
/// <inheritdoc />
|
|
public override void Refresh()
|
|
{
|
|
// Sync selected foliage options once before update to prevent too many data copies when fetching data from UI properties accessors
|
|
var proxyObject = (ProxyObject)Values[0];
|
|
proxyObject.SyncOptions();
|
|
|
|
base.Refresh();
|
|
}
|
|
}
|
|
|
|
private readonly ProxyObject _proxy;
|
|
private readonly VerticalPanel _items;
|
|
private readonly CustomEditorPresenter _presenter;
|
|
|
|
/// <summary>
|
|
/// The parent foliage tab.
|
|
/// </summary>
|
|
public readonly FoliageTab Tab;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PaintTab"/> class.
|
|
/// </summary>
|
|
/// <param name="tab">The parent tab.</param>
|
|
/// <param name="mode">The gizmo mode.</param>
|
|
public PaintTab(FoliageTab tab, PaintFoliageGizmoMode mode)
|
|
: base("Paint")
|
|
{
|
|
Tab = tab;
|
|
Tab.SelectedFoliageChanged += OnSelectedFoliageChanged;
|
|
Tab.SelectedFoliageTypeIndexChanged += OnSelectedFoliageTypeIndexChanged;
|
|
Tab.SelectedFoliageTypesChanged += UpdateFoliageTypesList;
|
|
_proxy = new ProxyObject(this, mode);
|
|
|
|
// Main panel
|
|
var splitPanel = new SplitPanel(Orientation.Vertical, ScrollBars.Vertical, ScrollBars.Vertical)
|
|
{
|
|
SplitterValue = 0.2f,
|
|
AnchorPreset = AnchorPresets.StretchAll,
|
|
Offsets = Margin.Zero,
|
|
Parent = this
|
|
};
|
|
|
|
// Foliage types list
|
|
_items = new VerticalPanel
|
|
{
|
|
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
|
Offsets = new Margin(4, 4, 4, 0),
|
|
IsScrollable = true,
|
|
Parent = splitPanel.Panel1
|
|
};
|
|
|
|
// Options editor
|
|
// TODO: use editor undo for changing foliage type options
|
|
var editor = new CustomEditorPresenter(null, "No foliage type selected");
|
|
editor.Panel.Parent = splitPanel.Panel2;
|
|
editor.Modified += OnModified;
|
|
_presenter = editor;
|
|
}
|
|
|
|
private void OnModified()
|
|
{
|
|
Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage?.Scene);
|
|
}
|
|
|
|
private void OnSelectedFoliageChanged()
|
|
{
|
|
_proxy.SelectedFoliageTypeIndex = -1;
|
|
_proxy.Foliage = Tab.SelectedFoliage;
|
|
|
|
_presenter.Deselect();
|
|
|
|
UpdateFoliageTypesList();
|
|
}
|
|
|
|
private void OnSelectedFoliageTypeIndexChanged(int previousIndex, int currentIndex)
|
|
{
|
|
if (previousIndex != -1)
|
|
{
|
|
((ContainerControl)_items.Children[previousIndex]).Children[1].BackgroundColor = Color.Transparent;
|
|
}
|
|
|
|
_proxy.SelectedFoliageTypeIndex = currentIndex;
|
|
|
|
if (currentIndex != -1)
|
|
{
|
|
((ContainerControl)_items.Children[currentIndex]).Children[1].BackgroundColor = Style.Current.BackgroundSelected;
|
|
|
|
_presenter.Select(_proxy);
|
|
_presenter.BuildLayoutOnUpdate();
|
|
}
|
|
else
|
|
{
|
|
_presenter.Deselect();
|
|
}
|
|
}
|
|
|
|
private void UpdateFoliageTypesList()
|
|
{
|
|
var foliage = Tab.SelectedFoliage;
|
|
|
|
// Cleanup previous items
|
|
_items.DisposeChildren();
|
|
|
|
// Add new ones
|
|
float y = 0;
|
|
if (foliage != null)
|
|
{
|
|
int typesCount = foliage.FoliageTypesCount;
|
|
for (int i = 0; i < typesCount; i++)
|
|
{
|
|
var model = foliage.GetFoliageType(i).Model;
|
|
var asset = Tab.Editor.ContentDatabase.FindAsset(model.ID);
|
|
|
|
var itemPanel = new ContainerControl();
|
|
|
|
var itemCheck = new CheckBox
|
|
{
|
|
AnchorPreset = AnchorPresets.VerticalStretchLeft,
|
|
Offsets = new Margin(0, 18, 0, 0),
|
|
TooltipText = "If checked, enables painting with this foliage type.",
|
|
Tag = i,
|
|
Parent = itemPanel,
|
|
};
|
|
|
|
// Try restore painting with the given model ID
|
|
bool itemChecked;
|
|
if (!Tab.FoliageTypeModelIdsToPaint.TryGetValue(model.ID, out itemChecked))
|
|
{
|
|
// Enable by default
|
|
itemChecked = true;
|
|
Tab.FoliageTypeModelIdsToPaint[model.ID] = itemChecked;
|
|
}
|
|
itemCheck.Checked = itemChecked;
|
|
itemCheck.StateChanged += OnItemCheckStateChanged;
|
|
|
|
var itemView = new AssetSearchPopup.AssetItemView(asset)
|
|
{
|
|
AnchorPreset = AnchorPresets.StretchAll,
|
|
Offsets = new Margin(itemCheck.Width + 2, 0, 1, 1),
|
|
TooltipText = asset.NamePath,
|
|
Tag = i,
|
|
Parent = itemPanel,
|
|
};
|
|
itemView.Clicked += OnFoliageTypeListItemClicked;
|
|
|
|
itemPanel.Height = 34;
|
|
itemPanel.Parent = _items;
|
|
|
|
itemPanel.UnlockChildrenRecursive();
|
|
itemPanel.PerformLayout();
|
|
|
|
y += itemPanel.Height + _items.Spacing;
|
|
}
|
|
y += _items.Margin.Height;
|
|
}
|
|
_items.Height = y;
|
|
|
|
var selectedFoliageTypeIndex = Tab.SelectedFoliageTypeIndex;
|
|
if (selectedFoliageTypeIndex != -1)
|
|
{
|
|
((ContainerControl)_items.Children[selectedFoliageTypeIndex]).Children[1].BackgroundColor = Style.Current.BackgroundSelected;
|
|
}
|
|
}
|
|
|
|
private void OnItemCheckStateChanged(CheckBox item)
|
|
{
|
|
var index = (int)item.Tag;
|
|
var foliage = Tab.SelectedFoliage;
|
|
var model = foliage.GetFoliageType(index).Model;
|
|
Tab.FoliageTypeModelIdsToPaint[model.ID] = item.Checked;
|
|
}
|
|
|
|
private void OnFoliageTypeListItemClicked(ItemsListContextMenu.Item item)
|
|
{
|
|
Tab.SelectedFoliageTypeIndex = (int)item.Tag;
|
|
}
|
|
}
|
|
}
|