// Copyright (c) 2012-2023 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 { /// /// Foliage painting tab. Allows to add or remove foliage instances defined for the current foliage object. /// /// public class PaintTab : Tab { /// /// The object for foliage painting settings adjusting via Custom Editor. /// [CustomEditor(typeof(ProxyObjectEditor))] private sealed class ProxyObject { private readonly PaintFoliageGizmoMode _mode; private readonly PaintTab _tab; /// /// The foliage actor. /// [HideInEditor] public FlaxEngine.Foliage Foliage; /// /// The selected foliage instance type index. /// [HideInEditor] public int SelectedFoliageTypeIndex; /// /// Initializes a new instance of the class. /// /// The tab. /// The mode. 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 Float2 PaintGroundSlopeAngleRange { get => new Float2(_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.ScaleEditor)), Tooltip("The scale minimum values per axis.")] public Float3 ScaleMin { get => _type.PaintScaleMin; set => _type.PaintScaleMin = value; } [EditorOrder(240), EditorDisplay("Painting"), Limit(0.0f), CustomEditor(typeof(ActorTransformEditor.ScaleEditor)), Tooltip("The scale maximum values per axis.")] public Float3 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 Float2 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; } } /// /// The custom editor for . /// /// private sealed class ProxyObjectEditor : GenericEditor { /// 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; /// /// The parent foliage tab. /// public readonly FoliageTab Tab; /// /// Initializes a new instance of the class. /// /// The parent tab. /// The gizmo mode. 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 != null ? _proxy.Foliage.Scene : null); } 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; _proxy.SyncOptions(); _presenter.Select(_proxy); _presenter.BuildLayoutOnUpdate(); } else { _presenter.Deselect(); } _presenter.Panel.Focus(); } 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 if (!Tab.FoliageTypeModelIdsToPaint.TryGetValue(model.ID, out bool 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; } } }