Merge branch 'master' into Visject-DescriptionPanel
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -111,6 +111,11 @@ namespace FlaxEditor.CustomEditors
|
||||
/// </summary>
|
||||
public PropertyNameLabel LinkedLabel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layout for this editor. Used to calculate bounds.
|
||||
/// </summary>
|
||||
public LayoutElementsContainer Layout => _layout;
|
||||
|
||||
internal virtual void Initialize(CustomEditorPresenter presenter, LayoutElementsContainer layout, ValueContainer values)
|
||||
{
|
||||
_layout = layout;
|
||||
|
||||
@@ -38,6 +38,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// </summary>
|
||||
public readonly int Index;
|
||||
|
||||
private Rectangle _arrangeButtonRect;
|
||||
private bool _arrangeButtonInUse;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CollectionItemLabel"/> class.
|
||||
/// </summary>
|
||||
@@ -50,6 +53,12 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
Index = index;
|
||||
|
||||
SetupContextMenu += OnSetupContextMenu;
|
||||
_arrangeButtonRect = new Rectangle(2, 3, 12, 12);
|
||||
|
||||
// Extend margin of the label to support a dragging handle.
|
||||
Margin m = Margin;
|
||||
m.Left += 16;
|
||||
Margin = m;
|
||||
}
|
||||
|
||||
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
|
||||
@@ -71,6 +80,107 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
b.Enabled = !Editor._readOnly;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnEndMouseCapture()
|
||||
{
|
||||
base.OnEndMouseCapture();
|
||||
|
||||
_arrangeButtonInUse = false;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
var style = FlaxEngine.GUI.Style.Current;
|
||||
|
||||
var mousePosition = PointFromScreen(Input.MouseScreenPosition);
|
||||
var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey;
|
||||
Render2D.DrawSprite(FlaxEditor.Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
|
||||
if (_arrangeButtonInUse && ArrangeAreaCheck(out _, out var arrangeTargetRect))
|
||||
{
|
||||
Render2D.FillRectangle(arrangeTargetRect, style.Selection);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ArrangeAreaCheck(out int index, out Rectangle rect)
|
||||
{
|
||||
var child = Editor.ChildrenEditors[0];
|
||||
var container = child.Layout.ContainerControl;
|
||||
var mousePosition = container.PointFromScreen(Input.MouseScreenPosition);
|
||||
var barSidesExtend = 20.0f;
|
||||
var barHeight = 5.0f;
|
||||
var barCheckAreaHeight = 40.0f;
|
||||
var pos = mousePosition.Y + barCheckAreaHeight * 0.5f;
|
||||
|
||||
for (int i = 0; i < container.Children.Count / 2; i++)
|
||||
{
|
||||
var containerChild = container.Children[i * 2]; // times 2 to skip the value editor
|
||||
if (Mathf.IsInRange(pos, containerChild.Top, containerChild.Top + barCheckAreaHeight) || (i == 0 && pos < containerChild.Top))
|
||||
{
|
||||
index = i;
|
||||
var p1 = containerChild.UpperLeft;
|
||||
rect = new Rectangle(PointFromParent(p1) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var p2 = container.Children[((container.Children.Count / 2) - 1) * 2].BottomLeft;
|
||||
if (pos > p2.Y)
|
||||
{
|
||||
index = (container.Children.Count / 2) - 1;
|
||||
rect = new Rectangle(PointFromParent(p2) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
index = -1;
|
||||
rect = Rectangle.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _arrangeButtonRect.Contains(ref location))
|
||||
{
|
||||
_arrangeButtonInUse = true;
|
||||
Focus();
|
||||
StartMouseCapture();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _arrangeButtonInUse)
|
||||
{
|
||||
_arrangeButtonInUse = false;
|
||||
EndMouseCapture();
|
||||
if (ArrangeAreaCheck(out var index, out _))
|
||||
{
|
||||
Editor.Shift(Index, index);
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
if (_arrangeButtonInUse)
|
||||
{
|
||||
_arrangeButtonInUse = false;
|
||||
EndMouseCapture();
|
||||
}
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
private void OnMoveUpClicked()
|
||||
{
|
||||
Editor.Move(Index, Index - 1);
|
||||
@@ -106,6 +216,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
private bool _canReorder = true;
|
||||
|
||||
private Rectangle _arrangeButtonRect;
|
||||
private bool _arrangeButtonInUse;
|
||||
|
||||
public void Setup(CollectionEditor editor, int index, bool canReorder = true)
|
||||
{
|
||||
HeaderHeight = 18;
|
||||
@@ -123,10 +236,92 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
MouseButtonRightClicked += OnMouseButtonRightClicked;
|
||||
if (_canReorder)
|
||||
{
|
||||
// TODO: Drag drop
|
||||
HeaderTextMargin = new Margin(18, 0, 0, 0);
|
||||
_arrangeButtonRect = new Rectangle(16, 3, 12, 12);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ArrangeAreaCheck(out int index, out Rectangle rect)
|
||||
{
|
||||
var container = Parent;
|
||||
var mousePosition = container.PointFromScreen(Input.MouseScreenPosition);
|
||||
var barSidesExtend = 20.0f;
|
||||
var barHeight = 5.0f;
|
||||
var barCheckAreaHeight = 40.0f;
|
||||
var pos = mousePosition.Y + barCheckAreaHeight * 0.5f;
|
||||
|
||||
for (int i = 0; i < (container.Children.Count + 1) / 2; i++) // Add 1 to pretend there is a spacer at the end.
|
||||
{
|
||||
var containerChild = container.Children[i * 2]; // times 2 to skip the value editor
|
||||
if (Mathf.IsInRange(pos, containerChild.Top, containerChild.Top + barCheckAreaHeight) || (i == 0 && pos < containerChild.Top))
|
||||
{
|
||||
index = i;
|
||||
var p1 = containerChild.UpperLeft;
|
||||
rect = new Rectangle(PointFromParent(p1) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var p2 = container.Children[container.Children.Count - 1].BottomLeft;
|
||||
if (pos > p2.Y)
|
||||
{
|
||||
index = ((container.Children.Count + 1) / 2) - 1;
|
||||
rect = new Rectangle(PointFromParent(p2) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
index = -1;
|
||||
rect = Rectangle.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
if (_canReorder)
|
||||
{
|
||||
var style = FlaxEngine.GUI.Style.Current;
|
||||
|
||||
var mousePosition = PointFromScreen(Input.MouseScreenPosition);
|
||||
var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey;
|
||||
Render2D.DrawSprite(FlaxEditor.Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
|
||||
if (_arrangeButtonInUse && ArrangeAreaCheck(out _, out var arrangeTargetRect))
|
||||
{
|
||||
Render2D.FillRectangle(arrangeTargetRect, style.Selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _arrangeButtonRect.Contains(ref location))
|
||||
{
|
||||
_arrangeButtonInUse = true;
|
||||
Focus();
|
||||
StartMouseCapture();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _arrangeButtonInUse)
|
||||
{
|
||||
_arrangeButtonInUse = false;
|
||||
EndMouseCapture();
|
||||
if (ArrangeAreaCheck(out var index, out _))
|
||||
{
|
||||
Editor.Shift(Index, index);
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
private void OnMouseButtonRightClicked(DropPanel panel, Float2 location)
|
||||
{
|
||||
if (LinkedEditor == null)
|
||||
@@ -324,7 +519,6 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
(elementType.GetProperties().Length == 1 && elementType.GetFields().Length == 0) ||
|
||||
elementType.Equals(new ScriptType(typeof(JsonAsset))) ||
|
||||
elementType.Equals(new ScriptType(typeof(SettingsBase)));
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
// Apply spacing
|
||||
@@ -440,6 +634,39 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
SetValue(cloned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shifts the specified item at the given index and moves it through the list to the other item. It supports undo.
|
||||
/// </summary>
|
||||
/// <param name="srcIndex">Index of the source item.</param>
|
||||
/// <param name="dstIndex">Index of the destination to move to.</param>
|
||||
private void Shift(int srcIndex, int dstIndex)
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
var cloned = CloneValues();
|
||||
if (dstIndex > srcIndex)
|
||||
{
|
||||
for (int i = srcIndex; i < dstIndex; i++)
|
||||
{
|
||||
var tmp = cloned[i + 1];
|
||||
cloned[i + 1] = cloned[i];
|
||||
cloned[i] = tmp;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = srcIndex; i > dstIndex; i--)
|
||||
{
|
||||
var tmp = cloned[i - 1];
|
||||
cloned[i - 1] = cloned[i];
|
||||
cloned[i] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
SetValue(cloned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the item at the specified index. It supports undo.
|
||||
/// </summary>
|
||||
|
||||
@@ -1364,6 +1364,7 @@ namespace FlaxEditor
|
||||
public byte AutoReloadScriptsOnMainWindowFocus;
|
||||
public byte ForceScriptCompilationOnStartup;
|
||||
public byte UseAssetImportPathRelative;
|
||||
public byte EnableParticlesPreview;
|
||||
public byte AutoRebuildCSG;
|
||||
public float AutoRebuildCSGTimeoutMs;
|
||||
public byte AutoRebuildNavMesh;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using FlaxEngine;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace FlaxEditor.Gizmo
|
||||
{
|
||||
@@ -15,91 +16,120 @@ namespace FlaxEditor.Gizmo
|
||||
[HideInEditor]
|
||||
private sealed class Renderer : PostProcessEffect
|
||||
{
|
||||
private IntPtr _debugDrawContext;
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct Data
|
||||
{
|
||||
public Matrix WorldMatrix;
|
||||
public Matrix ViewProjectionMatrix;
|
||||
public Float4 GridColor;
|
||||
public Float3 ViewPos;
|
||||
public float Far;
|
||||
public Float3 Padding;
|
||||
public float GridSize;
|
||||
}
|
||||
|
||||
private static readonly uint[] _triangles =
|
||||
{
|
||||
0, 2, 1, // Face front
|
||||
1, 3, 0,
|
||||
};
|
||||
|
||||
private GPUBuffer[] _vbs = new GPUBuffer[1];
|
||||
private GPUBuffer _vertexBuffer;
|
||||
private GPUBuffer _indexBuffer;
|
||||
private GPUPipelineState _psGrid;
|
||||
private Shader _shader;
|
||||
|
||||
public Renderer()
|
||||
{
|
||||
Order = -100;
|
||||
UseSingleTarget = true;
|
||||
Location = PostProcessEffectLocation.BeforeForwardPass;
|
||||
Location = PostProcessEffectLocation.Default;
|
||||
_shader = FlaxEngine.Content.LoadAsyncInternal<Shader>("Shaders/Editor/Grid");
|
||||
}
|
||||
|
||||
~Renderer()
|
||||
{
|
||||
if (_debugDrawContext != IntPtr.Zero)
|
||||
{
|
||||
DebugDraw.FreeContext(_debugDrawContext);
|
||||
_debugDrawContext = IntPtr.Zero;
|
||||
}
|
||||
Destroy(ref _psGrid);
|
||||
Destroy(ref _vertexBuffer);
|
||||
Destroy(ref _indexBuffer);
|
||||
_shader = null;
|
||||
}
|
||||
|
||||
public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output)
|
||||
public override unsafe void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output)
|
||||
{
|
||||
if (_shader == null)
|
||||
return;
|
||||
Profiler.BeginEventGPU("Editor Grid");
|
||||
|
||||
if (_debugDrawContext == IntPtr.Zero)
|
||||
_debugDrawContext = DebugDraw.AllocateContext();
|
||||
DebugDraw.SetContext(_debugDrawContext);
|
||||
DebugDraw.UpdateContext(_debugDrawContext, 1.0f / Mathf.Max(Engine.FramesPerSecond, 1));
|
||||
|
||||
var viewPos = (Vector3)renderContext.View.Position;
|
||||
var plane = new Plane(Vector3.Zero, Vector3.UnitY);
|
||||
var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos);
|
||||
|
||||
var options = Editor.Instance.Options.Options;
|
||||
float space = options.Viewport.ViewportGridScale, size;
|
||||
if (dst <= 500.0f)
|
||||
Float3 camPos = renderContext.View.WorldPosition;
|
||||
float gridSize = renderContext.View.Far + 20000;
|
||||
|
||||
// Lazy-init resources
|
||||
if (_vertexBuffer == null)
|
||||
{
|
||||
size = 8000;
|
||||
_vertexBuffer = new GPUBuffer();
|
||||
var desc = GPUBufferDescription.Vertex(sizeof(Float3), 4);
|
||||
_vertexBuffer.Init(ref desc);
|
||||
}
|
||||
else if (dst <= 2000.0f)
|
||||
if (_indexBuffer == null)
|
||||
{
|
||||
space *= 2;
|
||||
size = 8000;
|
||||
_indexBuffer = new GPUBuffer();
|
||||
fixed (uint* ptr = _triangles)
|
||||
{
|
||||
var desc = GPUBufferDescription.Index(sizeof(uint), _triangles.Length, new IntPtr(ptr));
|
||||
_indexBuffer.Init(ref desc);
|
||||
}
|
||||
}
|
||||
else
|
||||
if (_psGrid == null)
|
||||
{
|
||||
space *= 20;
|
||||
size = 100000;
|
||||
_psGrid = new GPUPipelineState();
|
||||
var desc = GPUPipelineState.Description.Default;
|
||||
desc.BlendMode = BlendingMode.AlphaBlend;
|
||||
desc.CullMode = CullMode.TwoSided;
|
||||
desc.VS = _shader.GPU.GetVS("VS_Grid");
|
||||
desc.PS = _shader.GPU.GetPS("PS_Grid");
|
||||
_psGrid.Init(ref desc);
|
||||
}
|
||||
|
||||
float bigLineIntensity = 0.8f;
|
||||
Color bigColor = Color.Gray * bigLineIntensity;
|
||||
Color color = bigColor * 0.8f;
|
||||
int count = (int)(size / space);
|
||||
int midLine = count / 2;
|
||||
int bigLinesMod = count / 8;
|
||||
|
||||
Vector3 start = new Vector3(0, 0, size * -0.5f);
|
||||
Vector3 end = new Vector3(0, 0, size * 0.5f);
|
||||
|
||||
for (int i = 0; i <= count; i++)
|
||||
// Update vertices of the plane
|
||||
// TODO: perf this operation in a Vertex Shader
|
||||
float y = 1.5f; // Add small bias to reduce Z-fighting with geometry at scene origin
|
||||
var vertices = new Float3[]
|
||||
{
|
||||
start.X = end.X = i * space + start.Z;
|
||||
Color lineColor = color;
|
||||
if (i == midLine)
|
||||
lineColor = Color.Blue * bigLineIntensity;
|
||||
else if (i % bigLinesMod == 0)
|
||||
lineColor = bigColor;
|
||||
DebugDraw.DrawLine(start, end, lineColor);
|
||||
new Float3(-gridSize + camPos.X, y, -gridSize + camPos.Z),
|
||||
new Float3(gridSize + camPos.X, y, gridSize + camPos.Z),
|
||||
new Float3(-gridSize + camPos.X, y, gridSize + camPos.Z),
|
||||
new Float3(gridSize + camPos.X, y, -gridSize + camPos.Z),
|
||||
};
|
||||
fixed (Float3* ptr = vertices)
|
||||
{
|
||||
context.UpdateBuffer(_vertexBuffer, new IntPtr(ptr), (uint)(sizeof(Float3) * vertices.Length));
|
||||
}
|
||||
|
||||
start = new Vector3(size * -0.5f, 0, 0);
|
||||
end = new Vector3(size * 0.5f, 0, 0);
|
||||
|
||||
for (int i = 0; i <= count; i++)
|
||||
// Update constant buffer data
|
||||
var cb = _shader.GPU.GetCB(0);
|
||||
if (cb != IntPtr.Zero)
|
||||
{
|
||||
start.Z = end.Z = i * space + start.X;
|
||||
Color lineColor = color;
|
||||
if (i == midLine)
|
||||
lineColor = Color.Red * bigLineIntensity;
|
||||
else if (i % bigLinesMod == 0)
|
||||
lineColor = bigColor;
|
||||
DebugDraw.DrawLine(start, end, lineColor);
|
||||
var data = new Data();
|
||||
Matrix.Multiply(ref renderContext.View.View, ref renderContext.View.Projection, out var viewProjection);
|
||||
data.WorldMatrix = Matrix.Identity;
|
||||
Matrix.Transpose(ref viewProjection, out data.ViewProjectionMatrix);
|
||||
data.ViewPos = renderContext.View.WorldPosition;
|
||||
data.GridColor = options.Viewport.ViewportGridColor;
|
||||
data.Far = renderContext.View.Far;
|
||||
data.GridSize = options.Viewport.ViewportGridViewDistance;
|
||||
context.UpdateCB(cb, new IntPtr(&data));
|
||||
}
|
||||
|
||||
DebugDraw.Draw(ref renderContext, input.View(), null, true);
|
||||
DebugDraw.SetContext(IntPtr.Zero);
|
||||
// Draw geometry using custom Pixel Shader and Vertex Shader
|
||||
context.BindCB(0, cb);
|
||||
context.BindIB(_indexBuffer);
|
||||
_vbs[0] = _vertexBuffer;
|
||||
context.BindVB(_vbs);
|
||||
context.SetState(_psGrid);
|
||||
context.SetRenderTarget(renderContext.Buffers.DepthBuffer.View(), input.View());
|
||||
context.DrawIndexed((uint)_triangles.Length);
|
||||
|
||||
Profiler.EndEventGPU();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ API_CLASS(Namespace="FlaxEditor", Name="Editor", NoSpawn, NoConstructor) class M
|
||||
byte AutoReloadScriptsOnMainWindowFocus = 1;
|
||||
byte ForceScriptCompilationOnStartup = 1;
|
||||
byte UseAssetImportPathRelative = 1;
|
||||
byte EnableParticlesPreview = 1;
|
||||
byte AutoRebuildCSG = 1;
|
||||
float AutoRebuildCSGTimeoutMs = 50;
|
||||
byte AutoRebuildNavMesh = 1;
|
||||
|
||||
@@ -228,7 +228,7 @@ namespace FlaxEditor.Modules
|
||||
new SearchResult { Name = item.ShortName, Type = assetItem.TypeName, Item = item }
|
||||
};
|
||||
}
|
||||
var actor = FlaxEngine.Object.Find<Actor>(ref id);
|
||||
var actor = FlaxEngine.Object.Find<Actor>(ref id, true);
|
||||
if (actor != null)
|
||||
{
|
||||
return new List<SearchResult>
|
||||
@@ -236,6 +236,16 @@ namespace FlaxEditor.Modules
|
||||
new SearchResult { Name = actor.Name, Type = actor.TypeName, Item = actor }
|
||||
};
|
||||
}
|
||||
var script = FlaxEngine.Object.Find<Script>(ref id, true);
|
||||
if (script != null && script.Actor != null)
|
||||
{
|
||||
string actorPathStart = $"{script.Actor.Name}/";
|
||||
|
||||
return new List<SearchResult>
|
||||
{
|
||||
new SearchResult { Name = $"{actorPathStart}{script.TypeName}", Type = script.TypeName, Item = script }
|
||||
};
|
||||
}
|
||||
}
|
||||
Profiler.BeginEvent("ContentFinding.Search");
|
||||
|
||||
@@ -388,6 +398,13 @@ namespace FlaxEditor.Modules
|
||||
Editor.Instance.SceneEditing.Select(actor);
|
||||
Editor.Instance.Windows.EditWin.Viewport.FocusSelection();
|
||||
break;
|
||||
case Script script:
|
||||
if (script.Actor != null)
|
||||
{
|
||||
Editor.Instance.SceneEditing.Select(script.Actor);
|
||||
Editor.Instance.Windows.EditWin.Viewport.FocusSelection();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -192,6 +192,7 @@ namespace FlaxEditor.Options
|
||||
internalOptions.AutoReloadScriptsOnMainWindowFocus = (byte)(Options.General.AutoReloadScriptsOnMainWindowFocus ? 1 : 0);
|
||||
internalOptions.ForceScriptCompilationOnStartup = (byte)(Options.General.ForceScriptCompilationOnStartup ? 1 : 0);
|
||||
internalOptions.UseAssetImportPathRelative = (byte)(Options.General.UseAssetImportPathRelative ? 1 : 0);
|
||||
internalOptions.EnableParticlesPreview = (byte)(Options.Visual.EnableParticlesPreview ? 1 : 0);
|
||||
internalOptions.AutoRebuildCSG = (byte)(Options.General.AutoRebuildCSG ? 1 : 0);
|
||||
internalOptions.AutoRebuildCSGTimeoutMs = Options.General.AutoRebuildCSGTimeoutMs;
|
||||
internalOptions.AutoRebuildNavMesh = (byte)(Options.General.AutoRebuildNavMesh ? 1 : 0);
|
||||
|
||||
@@ -129,5 +129,19 @@ namespace FlaxEditor.Options
|
||||
[DefaultValue(50.0f), Limit(25.0f, 500.0f, 5.0f)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(220), Tooltip("The default editor viewport grid scale.")]
|
||||
public float ViewportGridScale { get; set; } = 50.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the view distance you can see the grid.
|
||||
/// </summary>
|
||||
[DefaultValue(2500.0f)]
|
||||
[EditorDisplay("Grid"), EditorOrder(300), Tooltip("The maximum distance you will be able to see the grid.")]
|
||||
public float ViewportGridViewDistance { get; set; } = 2500.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the grid color.
|
||||
/// </summary>
|
||||
[DefaultValue(typeof(Color), "0.5,0.5,0.5,1.0")]
|
||||
[EditorDisplay("Grid"), EditorOrder(310), Tooltip("The color for the viewport grid.")]
|
||||
public Color ViewportGridColor { get; set; } = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,5 +59,12 @@ namespace FlaxEditor.Options
|
||||
[DefaultValue(true)]
|
||||
[EditorDisplay("Quality", "Enable MSAA For Debug Draw"), EditorOrder(500), Tooltip("Determines whether enable MSAA for DebugDraw primitives rendering. Helps with pixel aliasing but reduces performance.")]
|
||||
public bool EnableMSAAForDebugDraw { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether show looping particle effects in Editor viewport to simulate in-game look.
|
||||
/// </summary>
|
||||
[DefaultValue(true)]
|
||||
[EditorDisplay("Preview"), EditorOrder(1000)]
|
||||
public bool EnableParticlesPreview { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,6 +936,142 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 43,
|
||||
Title = "Rotate UV",
|
||||
Description = "Rotates 2D vector by given angle around (0,0) origin",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(250, 40),
|
||||
ConnectionsHints = ConnectionsHint.Vector,
|
||||
DefaultValues =
|
||||
[
|
||||
0.0f,
|
||||
],
|
||||
Elements =
|
||||
[
|
||||
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
|
||||
NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0),
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 2),
|
||||
]
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 44,
|
||||
Title = "Cone Gradient",
|
||||
Description = "Creates cone gradient around normalized UVs (range [-1; 1]), angle is in radians (range [0; TwoPi])",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(175, 40),
|
||||
ConnectionsHints = ConnectionsHint.Vector,
|
||||
DefaultValues =
|
||||
[
|
||||
0.0f,
|
||||
],
|
||||
Elements =
|
||||
[
|
||||
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
|
||||
NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0),
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2),
|
||||
]
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 45,
|
||||
Title = "Cycle Gradient",
|
||||
Description = "Creates 2D sphere mask gradient around normalized UVs (range [-1; 1])",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(175, 20),
|
||||
ConnectionsHints = ConnectionsHint.Vector,
|
||||
Elements =
|
||||
[
|
||||
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 1),
|
||||
]
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 46,
|
||||
Title = "Falloff and Offset",
|
||||
Description = "",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(175, 60),
|
||||
ConnectionsHints = ConnectionsHint.Numeric,
|
||||
DefaultValues =
|
||||
[
|
||||
0.0f,
|
||||
0.9f
|
||||
],
|
||||
Elements =
|
||||
[
|
||||
NodeElementArchetype.Factory.Input(0, "Value", true, typeof(float), 0),
|
||||
NodeElementArchetype.Factory.Input(1, "Offset", true, typeof(float), 1, 0),
|
||||
NodeElementArchetype.Factory.Input(2, "Falloff", true, typeof(float), 2, 1),
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 3),
|
||||
]
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 47,
|
||||
Title = "Linear Gradient",
|
||||
Description = "x = Gradient along X axis, y = Gradient along Y axis",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(175, 60),
|
||||
ConnectionsHints = ConnectionsHint.Vector,
|
||||
DefaultValues =
|
||||
[
|
||||
0.0f,
|
||||
false
|
||||
],
|
||||
Elements =
|
||||
[
|
||||
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
|
||||
NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0),
|
||||
NodeElementArchetype.Factory.Input(2, "Mirror", true, typeof(bool), 2, 1),
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 3),
|
||||
]
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 48,
|
||||
Title = "Radial Gradient",
|
||||
Description = "",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(175, 40),
|
||||
ConnectionsHints = ConnectionsHint.Vector,
|
||||
DefaultValues =
|
||||
[
|
||||
0.0f,
|
||||
],
|
||||
Elements =
|
||||
[
|
||||
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
|
||||
NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0),
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2),
|
||||
]
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 49,
|
||||
Title = "Ring Gradient",
|
||||
Description = "x = InnerMask,y = OuterMask,z = Mask",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(175, 80),
|
||||
ConnectionsHints = ConnectionsHint.Vector,
|
||||
DefaultValues =
|
||||
[
|
||||
1.0f,
|
||||
0.8f,
|
||||
0.05f,
|
||||
],
|
||||
Elements =
|
||||
[
|
||||
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
|
||||
NodeElementArchetype.Factory.Input(1, "OuterBounds", true, typeof(float), 1, 0),
|
||||
NodeElementArchetype.Factory.Input(2, "InnerBounds", true, typeof(float), 2, 1),
|
||||
NodeElementArchetype.Factory.Input(3, "Falloff", true, typeof(float), 3, 2),
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 4),
|
||||
]
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ namespace FlaxEditor.Surface
|
||||
return RootContext.GetParameter(name);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnParamReordered()
|
||||
{
|
||||
MarkAsEdited();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnParamCreated(SurfaceParameter param)
|
||||
{
|
||||
|
||||
@@ -350,6 +350,213 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ReorderParamAction : IUndoAction
|
||||
{
|
||||
/// <summary>
|
||||
/// The window reference.
|
||||
/// </summary>
|
||||
public IVisjectSurfaceWindow Window;
|
||||
|
||||
/// <summary>
|
||||
/// The parameters editor for this action.
|
||||
/// </summary>
|
||||
public ParametersEditor Editor;
|
||||
|
||||
/// <summary>
|
||||
/// The old index the parameter was at.
|
||||
/// </summary>
|
||||
public int OldIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The new index the parameter will be at.
|
||||
/// </summary>
|
||||
public int NewIndex;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ActionString => "Reorder Parameter";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Window = null;
|
||||
Editor = null;
|
||||
}
|
||||
|
||||
public void Swap(int oldIdx, int newIdx)
|
||||
{
|
||||
if (oldIdx == newIdx)
|
||||
return; // ?
|
||||
|
||||
var parameters = Window.VisjectSurface.Parameters;
|
||||
if (newIdx > oldIdx)
|
||||
{
|
||||
for (int i = oldIdx; i < newIdx; i++)
|
||||
{
|
||||
SurfaceParameter old = parameters[i + 1];
|
||||
parameters[i + 1] = parameters[i];
|
||||
parameters[i] = old;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = oldIdx; i > newIdx; i--)
|
||||
{
|
||||
SurfaceParameter old = parameters[i - 1];
|
||||
parameters[i - 1] = parameters[i];
|
||||
parameters[i] = old;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Do()
|
||||
{
|
||||
Swap(OldIndex, NewIndex);
|
||||
Window.VisjectSurface.OnParamReordered();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
Swap(NewIndex, OldIndex);
|
||||
Window.VisjectSurface.OnParamReordered();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom draggable property name label that handles reordering visject parameters.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.CustomEditors.GUI.DraggablePropertyNameLabel" />
|
||||
[HideInEditor]
|
||||
public class ParameterPropertyNameLabel : DraggablePropertyNameLabel
|
||||
{
|
||||
private ParametersEditor _editor;
|
||||
private IVisjectSurfaceWindow _window;
|
||||
private Rectangle _arrangeButtonRect;
|
||||
private bool _arrangeButtonInUse;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ParameterPropertyNameLabel(string name, ParametersEditor editor)
|
||||
: base(name)
|
||||
{
|
||||
_editor = editor;
|
||||
_window = _editor.Values[0] as IVisjectSurfaceWindow;
|
||||
_arrangeButtonRect = new Rectangle(2, 3, 12, 12);
|
||||
|
||||
// Extend margin of the label to support a dragging handle
|
||||
Margin m = Margin;
|
||||
m.Left += 16;
|
||||
Margin = m;
|
||||
}
|
||||
|
||||
private bool ArrangeAreaCheck(out int index, out Rectangle rect)
|
||||
{
|
||||
var child = _editor.ChildrenEditors[0];
|
||||
var container = child.Layout.ContainerControl;
|
||||
var mousePosition = container.PointFromScreen(Input.MouseScreenPosition);
|
||||
var barSidesExtend = 20.0f;
|
||||
var barHeight = 5.0f;
|
||||
var barCheckAreaHeight = 40.0f;
|
||||
var pos = mousePosition.Y + barCheckAreaHeight * 0.5f;
|
||||
|
||||
for (int i = 0; i < container.Children.Count / 2; i++)
|
||||
{
|
||||
var containerChild = container.Children[i * 2]; // times 2 to skip the value editor
|
||||
if (Mathf.IsInRange(pos, containerChild.Top, containerChild.Top + barCheckAreaHeight) || (i == 0 && pos < containerChild.Top))
|
||||
{
|
||||
index = i;
|
||||
var p1 = containerChild.UpperLeft;
|
||||
rect = new Rectangle(PointFromParent(p1) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var p2 = container.Children[((container.Children.Count / 2) - 1) * 2].BottomLeft;
|
||||
if (pos > p2.Y)
|
||||
{
|
||||
index = (container.Children.Count / 2) - 1;
|
||||
rect = new Rectangle(PointFromParent(p2) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
index = -1;
|
||||
rect = Rectangle.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var style = Style.Current;
|
||||
var mousePosition = PointFromScreen(Input.MouseScreenPosition);
|
||||
var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey;
|
||||
Render2D.DrawSprite(Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
|
||||
if (_arrangeButtonInUse && ArrangeAreaCheck(out _, out var arrangeTargetRect))
|
||||
{
|
||||
Render2D.FillRectangle(arrangeTargetRect, style.Selection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _arrangeButtonRect.Contains(ref location))
|
||||
{
|
||||
_arrangeButtonInUse = true;
|
||||
Focus();
|
||||
StartMouseCapture();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _arrangeButtonInUse)
|
||||
{
|
||||
_arrangeButtonInUse = false;
|
||||
EndMouseCapture();
|
||||
ArrangeAreaCheck(out var index, out _);
|
||||
var action = new ReorderParamAction
|
||||
{
|
||||
OldIndex = (int)Tag,
|
||||
NewIndex = index,
|
||||
Window = _window,
|
||||
Editor = _editor
|
||||
};
|
||||
action.Do();
|
||||
_window.Undo.AddAction(action);
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
if (_arrangeButtonInUse)
|
||||
{
|
||||
_arrangeButtonInUse = false;
|
||||
EndMouseCapture();
|
||||
}
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnSizeChanged()
|
||||
{
|
||||
base.OnSizeChanged();
|
||||
|
||||
// Center the drag button vertically
|
||||
_arrangeButtonRect = new Rectangle(2, Mathf.Ceil((Height - 12) * 0.5f) + 1, 12, 12);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom editor for editing Visject Surface parameters collection.
|
||||
/// </summary>
|
||||
@@ -431,10 +638,10 @@ namespace FlaxEditor.Surface
|
||||
attributes
|
||||
);
|
||||
|
||||
var propertyLabel = new DraggablePropertyNameLabel(name)
|
||||
var propertyLabel = new ParameterPropertyNameLabel(name, this)
|
||||
{
|
||||
Tag = pIndex,
|
||||
Drag = OnDragParameter
|
||||
Drag = OnDragParameter,
|
||||
};
|
||||
if (!p.IsPublic)
|
||||
propertyLabel.TextColor = propertyLabel.TextColor.RGBMultiplied(0.7f);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "LogContext.h"
|
||||
#include "Engine/Core/Types/Guid.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Threading/ThreadLocal.h"
|
||||
|
||||
struct LogContextThreadData
|
||||
{
|
||||
LogContextData* Ptr;
|
||||
uint32 Count;
|
||||
uint32 Capacity;
|
||||
|
||||
void Push(LogContextData&& item)
|
||||
{
|
||||
if (Count == Capacity)
|
||||
{
|
||||
Capacity = Capacity == 0 ? 32 : Capacity * 2;
|
||||
auto ptr = (LogContextData*)Allocator::Allocate(Capacity * sizeof(LogContextData));
|
||||
Platform::MemoryCopy(ptr, Ptr, Count * sizeof(LogContextData));
|
||||
Allocator::Free(Ptr);
|
||||
Ptr = ptr;
|
||||
}
|
||||
Ptr[Count++] = MoveTemp(item);
|
||||
}
|
||||
|
||||
void Pop()
|
||||
{
|
||||
Count--;
|
||||
}
|
||||
|
||||
LogContextData Peek()
|
||||
{
|
||||
return Count > 0 ? Ptr[Count - 1] : LogContextData();
|
||||
}
|
||||
};
|
||||
|
||||
ThreadLocal<LogContextThreadData> GlobalLogContexts;
|
||||
|
||||
String LogContext::GetInfo()
|
||||
{
|
||||
LogContextData context = LogContext::Get();
|
||||
if (context.ObjectID != Guid::Empty)
|
||||
return String::Format(TEXT("(Loading source was {0})"), context.ObjectID);
|
||||
return String::Empty;
|
||||
}
|
||||
|
||||
void LogContext::Push(const Guid& id)
|
||||
{
|
||||
LogContextData context;
|
||||
context.ObjectID = id;
|
||||
auto& stack = GlobalLogContexts.Get();
|
||||
stack.Push(MoveTemp(context));
|
||||
}
|
||||
|
||||
void LogContext::Pop()
|
||||
{
|
||||
auto& stack = GlobalLogContexts.Get();
|
||||
stack.Pop();
|
||||
}
|
||||
|
||||
LogContextData LogContext::Get()
|
||||
{
|
||||
auto& stack = GlobalLogContexts.Get();
|
||||
return stack.Peek();
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Config.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
|
||||
class String;
|
||||
struct Guid;
|
||||
|
||||
/// <summary>
|
||||
/// Log context data structure. Contains different kinds of context data for different situtations.
|
||||
/// </summary>
|
||||
API_STRUCT(NoDefault) struct FLAXENGINE_API LogContextData
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_STRUCTURE(LogContextData)
|
||||
|
||||
/// <summary>
|
||||
/// A GUID for an object which this context applies to.
|
||||
/// </summary>
|
||||
API_FIELD() Guid ObjectID;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<LogContextData>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Log context interaction class. Methods are thread local, and as such, the context is as well.
|
||||
/// This system is used to pass down important information to be logged through large callstacks
|
||||
/// which don't have any reason to be passing down the information otherwise.
|
||||
/// </summary>
|
||||
API_CLASS(Static) class FLAXENGINE_API LogContext
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(LogContext)
|
||||
|
||||
/// <summary>
|
||||
/// Adds a log context element to the stack to be displayed in warning and error logs.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the object this context applies to.</param>
|
||||
API_FUNCTION() static void Push(const Guid& id);
|
||||
|
||||
/// <summary>
|
||||
/// Pops a log context element off of the stack and discards it.
|
||||
/// </summary>
|
||||
API_FUNCTION() static void Pop();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the log context element off the top of stack.
|
||||
/// </summary>
|
||||
/// <returns>The log context element at the top of the stack.</returns>
|
||||
API_FUNCTION() static LogContextData Get();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string which represents the current log context on the stack.
|
||||
/// </summary>
|
||||
/// <returns>The formatted string representing the current log context.</returns>
|
||||
API_FUNCTION() static String GetInfo();
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Helper structure used to call LogContext Push/Pop within single code block.
|
||||
/// </summary>
|
||||
struct LogContextScope
|
||||
{
|
||||
FORCE_INLINE LogContextScope(const Guid& id)
|
||||
{
|
||||
LogContext::Push(id);
|
||||
}
|
||||
|
||||
FORCE_INLINE ~LogContextScope()
|
||||
{
|
||||
LogContext::Pop();
|
||||
}
|
||||
};
|
||||
@@ -79,6 +79,26 @@ namespace FlaxEngine
|
||||
/// </summary>
|
||||
public Float2 UpperRight => new Float2(Location.X + Size.X, Location.Y);
|
||||
|
||||
/// <summary>
|
||||
/// Gets position of the bottom right corner of the rectangle
|
||||
/// </summary>
|
||||
public Float2 LowerRight => Location + Size;
|
||||
|
||||
/// <summary>
|
||||
/// Gets position of the bottom left corner of the rectangle
|
||||
/// </summary>
|
||||
public Float2 LowerLeft => new Float2(Location.X, Location.Y + Size.Y);
|
||||
|
||||
/// <summary>
|
||||
/// Gets position of the upper left corner of the rectangle
|
||||
/// </summary>
|
||||
public Float2 TopLeft => Location;
|
||||
|
||||
/// <summary>
|
||||
/// Gets position of the upper right corner of the rectangle
|
||||
/// </summary>
|
||||
public Float2 TopRight => new Float2(Location.X + Size.X, Location.Y);
|
||||
|
||||
/// <summary>
|
||||
/// Gets position of the bottom right corner of the rectangle
|
||||
/// </summary>
|
||||
|
||||
@@ -117,6 +117,30 @@ public:
|
||||
return Location + Float2(Size.X, 0);
|
||||
}
|
||||
|
||||
// Gets position of the bottom right corner of the rectangle
|
||||
Float2 GetLowerRight() const
|
||||
{
|
||||
return Location + Size;
|
||||
}
|
||||
|
||||
// Gets position of the bottom left corner of the rectangle
|
||||
Float2 GetLowerLeft() const
|
||||
{
|
||||
return Location + Float2(0, Size.Y);
|
||||
}
|
||||
|
||||
// Gets position of the upper left corner of the rectangle
|
||||
Float2 GetTopLeft() const
|
||||
{
|
||||
return Location;
|
||||
}
|
||||
|
||||
// Gets position of the upper right corner of the rectangle
|
||||
Float2 GetTopRight() const
|
||||
{
|
||||
return Location + Float2(Size.X, 0);
|
||||
}
|
||||
|
||||
// Gets position of the bottom right corner of the rectangle
|
||||
Float2 GetBottomRight() const
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Core/Cache.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/LogContext.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Serialization/JsonTools.h"
|
||||
#include "Engine/Serialization/ISerializeModifier.h"
|
||||
@@ -247,6 +248,7 @@ void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISeria
|
||||
CHECK(obj);
|
||||
#endif
|
||||
ISerializeModifier* modifier = context.GetModifier();
|
||||
LogContextScope logContext(obj->GetID());
|
||||
|
||||
// Check for prefab instance
|
||||
Guid prefabObjectId;
|
||||
|
||||
@@ -446,15 +446,21 @@ void ParticleEffect::Update()
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/Managed/ManagedEditor.h"
|
||||
|
||||
void ParticleEffect::UpdateExecuteInEditor()
|
||||
{
|
||||
// Auto-play in Editor
|
||||
if (!Editor::IsPlayMode && !_isStopped)
|
||||
if (!Editor::IsPlayMode && !_isStopped && IsLooping && PlayOnStart && Editor::Managed->ManagedEditorOptions.EnableParticlesPreview)
|
||||
{
|
||||
_isPlaying = true;
|
||||
Update();
|
||||
}
|
||||
else if (!Editor::IsPlayMode && _isPlaying)
|
||||
{
|
||||
_isPlaying = false;
|
||||
ResetSimulation();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -126,10 +126,11 @@ namespace FlaxEngine
|
||||
/// </summary>
|
||||
/// <param name="id">Unique ID of the object.</param>
|
||||
/// <typeparam name="T">Type of the object.</typeparam>
|
||||
/// <param name="skipLog">Whether or not to log warnings when objects aren't found.</param>
|
||||
/// <returns>Found object or null if missing.</returns>
|
||||
public static T Find<T>(ref Guid id) where T : Object
|
||||
public static T Find<T>(ref Guid id, bool skipLog = false) where T : Object
|
||||
{
|
||||
return Internal_FindObject(ref id, typeof(T)) as T;
|
||||
return Internal_FindObject(ref id, typeof(T), skipLog) as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -137,10 +138,11 @@ namespace FlaxEngine
|
||||
/// </summary>
|
||||
/// <param name="id">Unique ID of the object.</param>
|
||||
/// <param name="type">Type of the object.</param>
|
||||
/// <param name="skipLog">Whether or not to log warnings when objects aren't found.</param>
|
||||
/// <returns>Found object or null if missing.</returns>
|
||||
public static Object Find(ref Guid id, Type type)
|
||||
public static Object Find(ref Guid id, Type type, bool skipLog = false)
|
||||
{
|
||||
return Internal_FindObject(ref id, type);
|
||||
return Internal_FindObject(ref id, type, skipLog);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -335,7 +337,7 @@ namespace FlaxEngine
|
||||
internal static partial string Internal_GetTypeName(IntPtr obj);
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_FindObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
|
||||
internal static partial Object Internal_FindObject(ref Guid id, [MarshalUsing(typeof(Interop.SystemTypeMarshaller))] Type type);
|
||||
internal static partial Object Internal_FindObject(ref Guid id, [MarshalUsing(typeof(Interop.SystemTypeMarshaller))] Type type, [MarshalAs(UnmanagedType.U1)] bool skipLog = false);
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_TryFindObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
|
||||
internal static partial Object Internal_TryFindObject(ref Guid id, [MarshalUsing(typeof(Interop.SystemTypeMarshaller))] Type type);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "ManagedCLR/MCore.h"
|
||||
#include "ManagedCLR/MException.h"
|
||||
#include "Internal/StdTypesContainer.h"
|
||||
#include "Engine/Core/LogContext.h"
|
||||
#include "Engine/Core/ObjectsRemovalService.h"
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Core/Types/Stopwatch.h"
|
||||
@@ -880,7 +881,7 @@ ScriptingObject* Scripting::FindObject(Guid id, const MClass* type)
|
||||
// Check type
|
||||
if (!type || result->Is(type))
|
||||
return result;
|
||||
LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}.", id, String(result->GetType().Fullname), String(type->GetFullName()));
|
||||
LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}. {3}", id, String(result->GetType().Fullname), String(type->GetFullName()), LogContext::GetInfo());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -899,7 +900,7 @@ ScriptingObject* Scripting::FindObject(Guid id, const MClass* type)
|
||||
return asset;
|
||||
}
|
||||
|
||||
LOG(Warning, "Unable to find scripting object with ID={0}. Required type {1}.", id, String(type->GetFullName()));
|
||||
LOG(Warning, "Unable to find scripting object with ID={0}. Required type {1}. {2}", id, String(type->GetFullName()), LogContext::GetInfo());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "BinaryModule.h"
|
||||
#include "Engine/Level/Actor.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/LogContext.h"
|
||||
#include "Engine/Core/Types/Pair.h"
|
||||
#include "Engine/Utilities/StringConverter.h"
|
||||
#include "Engine/Content/Asset.h"
|
||||
@@ -708,7 +709,7 @@ DEFINE_INTERNAL_CALL(MString*) ObjectInternal_GetTypeName(ScriptingObject* obj)
|
||||
return MUtils::ToString(obj->GetType().Fullname);
|
||||
}
|
||||
|
||||
DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MTypeObject* type)
|
||||
DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MTypeObject* type, bool skipLog = false)
|
||||
{
|
||||
if (!id->IsValid())
|
||||
return nullptr;
|
||||
@@ -725,15 +726,20 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MTypeObject*
|
||||
{
|
||||
if (klass && !obj->Is(klass))
|
||||
{
|
||||
LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}.", *id, String(obj->GetType().Fullname), String(klass->GetFullName()));
|
||||
if (!skipLog)
|
||||
LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}. {3}", *id, String(obj->GetType().Fullname), String(klass->GetFullName()), LogContext::GetInfo());
|
||||
return nullptr;
|
||||
}
|
||||
return obj->GetOrCreateManagedInstance();
|
||||
}
|
||||
if (klass)
|
||||
LOG(Warning, "Unable to find scripting object with ID={0}. Required type {1}.", *id, String(klass->GetFullName()));
|
||||
else
|
||||
LOG(Warning, "Unable to find scripting object with ID={0}", *id);
|
||||
|
||||
if (!skipLog)
|
||||
{
|
||||
if (klass)
|
||||
LOG(Warning, "Unable to find scripting object with ID={0}. Required type {1}. {2}", *id, String(klass->GetFullName()), LogContext::GetInfo());
|
||||
else
|
||||
LOG(Warning, "Unable to find scripting object with ID={0}", *id);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -569,6 +569,106 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
|
||||
// Do the inverse interpolation and saturate it
|
||||
value = writeLocal(ValueType::Float, String::Format(TEXT("saturate((({0} - {1}) / ({2} - {1})))"), gradient.Value, lowerEdge.Value, upperEdge.Value), node);
|
||||
}
|
||||
// Rotate UV [Rotator Simple]
|
||||
case 43:
|
||||
{
|
||||
//cosine = cos(rotation);
|
||||
//sine = sin(rotation);
|
||||
//float2 out = float2(cosine * uv.x + sine * uv.y,cosine * uv.y - sine * uv.x);
|
||||
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
|
||||
const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
|
||||
auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node);
|
||||
auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node);
|
||||
value = writeLocal(ValueType::Float2, String::Format(TEXT("float2({1} * {0}.x + {2} * {0}.y, {1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node);
|
||||
break;
|
||||
}
|
||||
// Cone Gradient
|
||||
case 44:
|
||||
{
|
||||
//float gradient = angle - abs(atan2(uv.x,uv.y));
|
||||
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
|
||||
const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
|
||||
value = writeLocal(ValueType::Float, String::Format(TEXT("{1} - abs(atan2({0}.x, {0}.y))"), uv.Value, rotationAngle.Value), node);
|
||||
break;
|
||||
}
|
||||
// Cycle Gradient
|
||||
case 45:
|
||||
{
|
||||
//float gradient = 1 - length(uv * 2);
|
||||
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
|
||||
value = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2.0)"), uv.Value), node);
|
||||
break;
|
||||
}
|
||||
// Falloff and Offset
|
||||
case 46:
|
||||
{
|
||||
//float out = clamp((((Value - (1 - Offset)) + Falloff) / Falloff),0,1)
|
||||
const auto in = tryGetValue(node->GetBox(0), ShaderGraphValue::Zero);
|
||||
const auto graphValue = tryGetValue(node->GetBox(1), node->Values[0].AsFloat);
|
||||
const auto falloff = tryGetValue(node->GetBox(2), node->Values[1].AsFloat);
|
||||
value = writeLocal(ValueType::Float, String::Format(TEXT("saturate(((({0} - (1.0 - {1})) + {2}) / {2}))"), in.Value, graphValue.Value, falloff.Value), node);
|
||||
break;
|
||||
}
|
||||
// Linear Gradient
|
||||
case 47:
|
||||
{
|
||||
// float2 uv = Input0.xy;
|
||||
// float r = Input0.z;
|
||||
// float2 A = 1.0 - float2(cos(r) * uv.x + sin(r) * uv.y, cos(r) * uv.y - sin(r) * uv.x);
|
||||
// float2 out = float2(Mirror ? abs(A.x < 1.0 ? (A.x - 0.5) * 2 : (2 - ((A.x - 0.5) * 2)) * -1) : A.x < 1.0 ? (A.x - 0.5) * 2 : 1,Mirror ? abs(A.y < 1.0 ? (A.y - 0.5) * 2 : (2 - ((A.y - 0.5) * 2)) * -1) : A.y < 1.0 ? (A.y - 0.5) * 2 : 1);
|
||||
|
||||
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
|
||||
const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
|
||||
const auto mirror = tryGetValue(node->GetBox(2), node->Values[1].AsBool).AsBool();
|
||||
|
||||
auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node);
|
||||
auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node);
|
||||
auto a = writeLocal(ValueType::Float2, String::Format(TEXT("1.0 - float2({1} * {0}.x + {2} * {0}.y, {1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node);
|
||||
value = writeLocal(
|
||||
ValueType::Float2, String::Format(TEXT
|
||||
(
|
||||
"float2({0} ? abs({1}.x < 1.0 ? ({1}.x - 0.5) * 2 : (2 - (({1}.x - 0.5) * 2)) * -1) : {1}.x < 1.0 ? ({1}.x - 0.5) * 2 : 1,{0} ? abs({1}.y < 1.0 ? ({1}.y - 0.5) * 2 : (2 - (({1}.y - 0.5) * 2)) * -1) : {1}.y < 1.0 ? ({1}.y - 0.5) * 2 : 1)"
|
||||
), mirror.Value, a.Value),
|
||||
node);
|
||||
break;
|
||||
}
|
||||
// Radial Gradient
|
||||
case 48:
|
||||
{
|
||||
//float gradient = clamp(atan2(uv.x,uv.y) - angle,0.0,1.0);
|
||||
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
|
||||
const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
|
||||
value = writeLocal(ValueType::Float, String::Format(TEXT("saturate(atan2({0}.x, {0}.y) - {1})"), uv.Value, rotationAngle.Value), node);
|
||||
break;
|
||||
}
|
||||
// Ring Gradient
|
||||
case 49:
|
||||
{
|
||||
// Nodes:
|
||||
// float c = CycleGradient(uv)
|
||||
// float InnerMask = FalloffAndOffset(c,(OuterBounds - Falloff),Falloff)
|
||||
// float OuterMask = FalloffAndOffset(1-c,1-InnerBounds,Falloff)
|
||||
// float Mask = OuterMask * InnerMask;
|
||||
|
||||
// TODO: check if there is some useless operators
|
||||
|
||||
//expanded
|
||||
//float cycleGradient = 1 - length(uv * 2);
|
||||
//float InnerMask = clamp((((c - (1 - (OuterBounds - Falloff))) + Falloff) / Falloff),0,1)
|
||||
//float OuterMask = clamp(((((1-c) - (1 - (1-InnerBounds))) + Falloff) / Falloff),0,1)
|
||||
//float Mask = OuterMask * InnerMask;
|
||||
|
||||
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
|
||||
const auto outerBounds = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
|
||||
const auto innerBounds = tryGetValue(node->GetBox(2), node->Values[1].AsFloat).AsFloat();
|
||||
const auto falloff = tryGetValue(node->GetBox(3), node->Values[2].AsFloat).AsFloat();
|
||||
auto c = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2.0)"), uv.Value), node);
|
||||
auto innerMask = writeLocal(ValueType::Float, String::Format(TEXT("saturate(((({0} - (1.0 - ({1} - {2}))) + {2}) / {2}))"), c.Value, outerBounds.Value, falloff.Value), node);
|
||||
auto outerMask = writeLocal(ValueType::Float, String::Format(TEXT("saturate(((((1.0 - {0}) - (1.0 - (1.0 - {1}))) + {2}) / {2}))"), c.Value, innerBounds.Value, falloff.Value), node);
|
||||
auto mask = writeLocal(ValueType::Float, String::Format(TEXT("{0} * {1}"), innerMask.Value, outerMask.Value), node);
|
||||
value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0}, {1}, {2})"), innerMask.Value, outerMask.Value, mask.Value), node);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace FlaxEngine.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Radial menu control that arranges child controls (of type Image) in a circle.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
|
||||
public class RadialMenu : ContainerControl
|
||||
{
|
||||
private bool _materialIsDirty = true;
|
||||
private float _angle;
|
||||
private float _selectedSegment;
|
||||
private int _highlightSegment = -1;
|
||||
private MaterialBase _material;
|
||||
private MaterialInstance _materialInstance;
|
||||
private int _segmentCount;
|
||||
private Color _highlightColor;
|
||||
private Color _foregroundColor;
|
||||
private Color _selectionColor;
|
||||
private float _edgeOffset;
|
||||
private float _thickness = 0.2f;
|
||||
|
||||
private float USize => Size.X < Size.Y ? Size.X : Size.Y;
|
||||
private bool ShowMatProp => _material != null;
|
||||
|
||||
/// <summary>
|
||||
/// The material to use for menu background drawing.
|
||||
/// </summary>
|
||||
[EditorOrder(1)]
|
||||
public MaterialBase Material
|
||||
{
|
||||
get => _material;
|
||||
set
|
||||
{
|
||||
if (_material == value)
|
||||
return;
|
||||
_material = value;
|
||||
Object.Destroy(ref _materialInstance);
|
||||
_materialIsDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the edge offset.
|
||||
/// </summary>
|
||||
[EditorOrder(2), Range(0, 1)]
|
||||
public float EdgeOffset
|
||||
{
|
||||
get => _edgeOffset;
|
||||
set
|
||||
{
|
||||
_edgeOffset = Math.Clamp(value, 0, 1);
|
||||
_materialIsDirty = true;
|
||||
PerformLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the thickness.
|
||||
/// </summary>
|
||||
[EditorOrder(3), Range(0, 1), VisibleIf(nameof(ShowMatProp))]
|
||||
public float Thickness
|
||||
{
|
||||
get => _thickness;
|
||||
set
|
||||
{
|
||||
_thickness = Math.Clamp(value, 0, 1);
|
||||
_materialIsDirty = true;
|
||||
PerformLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets control background color (transparent color (alpha=0) means no background rendering).
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public new Color BackgroundColor
|
||||
{
|
||||
get => base.BackgroundColor;
|
||||
set
|
||||
{
|
||||
_materialIsDirty = true;
|
||||
base.BackgroundColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the highlight.
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public Color HighlightColor
|
||||
{
|
||||
get => _highlightColor;
|
||||
set
|
||||
{
|
||||
_materialIsDirty = true;
|
||||
_highlightColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the foreground.
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public Color ForegroundColor
|
||||
{
|
||||
get => _foregroundColor;
|
||||
set
|
||||
{
|
||||
_materialIsDirty = true;
|
||||
_foregroundColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the selection.
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public Color SelectionColor
|
||||
{
|
||||
get => _selectionColor;
|
||||
set
|
||||
{
|
||||
_materialIsDirty = true;
|
||||
_selectionColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The selected callback
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public Action<int> Selected;
|
||||
|
||||
/// <summary>
|
||||
/// The allow change selection when inside
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public bool AllowChangeSelectionWhenInside;
|
||||
|
||||
/// <summary>
|
||||
/// The center as button
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public bool CenterAsButton;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RadialMenu"/> class.
|
||||
/// </summary>
|
||||
public RadialMenu()
|
||||
: this(0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RadialMenu"/> class.
|
||||
/// </summary>
|
||||
/// <param name="x">Position X coordinate</param>
|
||||
/// <param name="y">Position Y coordinate</param>
|
||||
/// <param name="width">Width</param>
|
||||
/// <param name="height">Height</param>
|
||||
public RadialMenu(float x, float y, float width = 100, float height = 100)
|
||||
: base(x, y, width, height)
|
||||
{
|
||||
var style = Style.Current;
|
||||
if (style != null)
|
||||
{
|
||||
BackgroundColor = style.BackgroundNormal;
|
||||
HighlightColor = style.BackgroundSelected;
|
||||
ForegroundColor = style.BackgroundHighlighted;
|
||||
SelectionColor = style.BackgroundSelected;
|
||||
}
|
||||
_material = Content.LoadAsyncInternal<MaterialBase>("Engine/DefaultRadialMenu");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RadialMenu"/> class.
|
||||
/// </summary>
|
||||
/// <param name="location">Position</param>
|
||||
/// <param name="size">Size</param>
|
||||
public RadialMenu(Float2 location, Float2 size)
|
||||
: this(location.X, location.Y, size.X, size.Y)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void DrawSelf()
|
||||
{
|
||||
if (_materialInstance == null && Material != null)
|
||||
{
|
||||
_materialInstance = Material.CreateVirtualInstance();
|
||||
_materialIsDirty = true;
|
||||
}
|
||||
if (_materialInstance != null)
|
||||
{
|
||||
if (_materialIsDirty)
|
||||
{
|
||||
_materialInstance.SetParameterValue("RadialMenu_EdgeOffset", Math.Clamp(1 - _edgeOffset, 0, 1));
|
||||
_materialInstance.SetParameterValue("RadialMenu_Thickness", Math.Clamp(_thickness, 0, 1));
|
||||
_materialInstance.SetParameterValue("RadialMenu_Angle", (1.0f / _segmentCount) * Mathf.Pi);
|
||||
_materialInstance.SetParameterValue("RadialMenu_SegmentCount", _segmentCount);
|
||||
_materialInstance.SetParameterValue("RadialMenu_HighlightColor", _highlightColor);
|
||||
_materialInstance.SetParameterValue("RadialMenu_ForegroundColor", _foregroundColor);
|
||||
_materialInstance.SetParameterValue("RadialMenu_BackgroundColor", BackgroundColor);
|
||||
_materialInstance.SetParameterValue("RadialMenu_Rotation", -_angle + Mathf.Pi);
|
||||
UpdateSelectionColor();
|
||||
_materialIsDirty = false;
|
||||
}
|
||||
Render2D.DrawMaterial(_materialInstance, new Rectangle(Float2.Zero, new Float2(Size.X < Size.Y ? Size.X : Size.Y)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
if (_materialInstance != null)
|
||||
{
|
||||
if (_highlightSegment == -1)
|
||||
{
|
||||
var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f;
|
||||
var max = (1 - _edgeOffset) * USize * 0.5f;
|
||||
var val = ((USize * 0.5f) - location).Length;
|
||||
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside)
|
||||
{
|
||||
UpdateAngle(ref location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (_materialInstance == null)
|
||||
return base.OnMouseDown(location, button);
|
||||
|
||||
var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f;
|
||||
var max = (1 - _edgeOffset) * USize * 0.5f;
|
||||
var val = ((USize * 0.5f) - location).Length;
|
||||
var c = val < min && CenterAsButton;
|
||||
var selected = (int)_selectedSegment;
|
||||
selected++;
|
||||
if (Mathf.IsInRange(val, min, max) || c)
|
||||
{
|
||||
_highlightSegment = c ? 0 : selected;
|
||||
UpdateSelectionColor();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_highlightSegment = -1;
|
||||
UpdateSelectionColor();
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (_materialInstance == null)
|
||||
return base.OnMouseDown(location, button);
|
||||
|
||||
if (_highlightSegment >= 0)
|
||||
{
|
||||
Selected?.Invoke(_highlightSegment);
|
||||
_highlightSegment = -1;
|
||||
UpdateSelectionColor();
|
||||
var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f;
|
||||
var max = (1 - _edgeOffset) * USize * 0.5f;
|
||||
var val = ((USize * 0.5f) - location).Length;
|
||||
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside)
|
||||
{
|
||||
UpdateAngle(ref location);
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
if (_materialInstance == null)
|
||||
return;
|
||||
|
||||
_selectedSegment = 0;
|
||||
_highlightSegment = -1;
|
||||
Selected?.Invoke(_highlightSegment);
|
||||
UpdateSelectionColor();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnChildrenChanged()
|
||||
{
|
||||
_segmentCount = 0;
|
||||
for (int i = 0; i < Children.Count; i++)
|
||||
{
|
||||
if (Children[i] is Image)
|
||||
{
|
||||
_segmentCount++;
|
||||
}
|
||||
}
|
||||
_materialIsDirty = true;
|
||||
|
||||
base.OnChildrenChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void PerformLayout(bool force = false)
|
||||
{
|
||||
var sa = -1.0f / _segmentCount * Mathf.TwoPi;
|
||||
var midp = USize * 0.5f;
|
||||
var mp = ((1 - _edgeOffset) - (_thickness * 0.5f)) * midp;
|
||||
float f = 0;
|
||||
if (_segmentCount % 2 != 0)
|
||||
{
|
||||
f += sa * 0.5f;
|
||||
}
|
||||
if (_materialInstance == null)
|
||||
{
|
||||
for (int i = 0; i < Children.Count; i++)
|
||||
{
|
||||
Children[i].Center = Rotate2D(new Float2(0, mp), f) + midp;
|
||||
f += sa;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Children.Count; i++)
|
||||
{
|
||||
if (Children[i] is Image)
|
||||
{
|
||||
Children[i].Center = Rotate2D(new Float2(0, mp), f) + midp;
|
||||
f += sa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.PerformLayout(force);
|
||||
}
|
||||
|
||||
private void UpdateSelectionColor()
|
||||
{
|
||||
Color color;
|
||||
if (_highlightSegment == -1)
|
||||
{
|
||||
color = _foregroundColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CenterAsButton)
|
||||
{
|
||||
color = _highlightSegment > 0 ? SelectionColor : _foregroundColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = SelectionColor;
|
||||
}
|
||||
}
|
||||
_materialInstance.SetParameterValue("RadialMenu_SelectionColor", color);
|
||||
}
|
||||
|
||||
private void UpdateAngle(ref Float2 location)
|
||||
{
|
||||
var size = new Float2(USize);
|
||||
var p = (size * 0.5f) - location;
|
||||
var sa = (1.0f / _segmentCount) * Mathf.TwoPi;
|
||||
_angle = Mathf.Atan2(p.X, p.Y);
|
||||
_angle = Mathf.Ceil((_angle - (sa * 0.5f)) / sa) * sa;
|
||||
_selectedSegment = _angle;
|
||||
_selectedSegment = Mathf.RoundToInt((_selectedSegment < 0 ? Mathf.TwoPi + _selectedSegment : _selectedSegment) / sa);
|
||||
if (float.IsNaN(_angle) || float.IsInfinity(_angle))
|
||||
_angle = 0;
|
||||
_materialInstance.SetParameterValue("RadialMenu_Rotation", -_angle + Mathf.Pi);
|
||||
}
|
||||
|
||||
private static Float2 Rotate2D(Float2 point, float angle)
|
||||
{
|
||||
return new Float2(Mathf.Cos(angle) * point.X + Mathf.Sin(angle) * point.Y,
|
||||
Mathf.Cos(angle) * point.Y - Mathf.Sin(angle) * point.X);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// Implementation based on:
|
||||
// "The Best Darn Grid Shader (Yet)", Medium, Oct 2023
|
||||
// Ben Golus
|
||||
// https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#3e73
|
||||
|
||||
#define USE_FORWARD true;
|
||||
|
||||
#include "./Flax/Common.hlsl"
|
||||
|
||||
META_CB_BEGIN(0, Data)
|
||||
float4x4 WorldMatrix;
|
||||
float4x4 ViewProjectionMatrix;
|
||||
float4 GridColor;
|
||||
float3 ViewPos;
|
||||
float Far;
|
||||
float3 Padding;
|
||||
float GridSize;
|
||||
META_CB_END
|
||||
|
||||
// Geometry data passed to the vertex shader
|
||||
struct ModelInput
|
||||
{
|
||||
float3 Position : POSITION;
|
||||
};
|
||||
|
||||
// Interpolants passed from the vertex shader
|
||||
struct VertexOutput
|
||||
{
|
||||
float4 Position : SV_Position;
|
||||
float2 TexCoord : TEXCOORD0;
|
||||
float3 WorldPosition : TEXCOORD1;
|
||||
};
|
||||
|
||||
// Interpolants passed to the pixel shader
|
||||
struct PixelInput
|
||||
{
|
||||
float4 Position : SV_Position;
|
||||
noperspective float2 TexCoord : TEXCOORD0;
|
||||
float3 WorldPosition : TEXCOORD1;
|
||||
};
|
||||
|
||||
// Vertex shader function for grid rendering
|
||||
META_VS(true, FEATURE_LEVEL_ES2)
|
||||
META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true)
|
||||
META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true)
|
||||
VertexOutput VS_Grid(ModelInput input)
|
||||
{
|
||||
VertexOutput output;
|
||||
output.WorldPosition = mul(float4(input.Position.xyz, 1), WorldMatrix).xyz;
|
||||
output.Position = mul(float4(input.Position.xyz, 1), ViewProjectionMatrix);
|
||||
return output;
|
||||
}
|
||||
|
||||
float invLerp(float from, float to, float value)
|
||||
{
|
||||
return (value - from) / (to - from);
|
||||
}
|
||||
|
||||
float remap(float origFrom, float origTo, float targetFrom, float targetTo, float value)
|
||||
{
|
||||
float rel = invLerp(origFrom, origTo, value);
|
||||
return lerp(targetFrom, targetTo, rel);
|
||||
}
|
||||
|
||||
float ddLength(float a)
|
||||
{
|
||||
return length(float2(ddx(a), ddy(a)));
|
||||
}
|
||||
|
||||
float GetLine(float pos, float scale, float thickness)
|
||||
{
|
||||
float lineWidth = thickness;
|
||||
float coord = (pos * 0.01) * scale;
|
||||
|
||||
float2 uvDDXY = float2(ddx(coord), ddy(coord));
|
||||
|
||||
float deriv = float(length(uvDDXY.xy));
|
||||
float drawWidth = clamp(lineWidth, deriv, 0.5);
|
||||
float lineAA = deriv * 1.5;
|
||||
float gridUV = abs(coord);
|
||||
float grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);
|
||||
grid2 *= saturate(lineWidth / drawWidth);
|
||||
grid2 = lerp(grid2, lineWidth, saturate(deriv * 2.0 - 1.0));
|
||||
|
||||
float grid = lerp(grid2, 1.0, grid2);
|
||||
return grid;
|
||||
}
|
||||
|
||||
float GetGrid(float3 pos, float scale, float thickness)
|
||||
{
|
||||
float lineWidth = thickness;
|
||||
float2 coord = (pos.xz * 0.01) * scale;
|
||||
|
||||
float4 uvDDXY = float4(ddx(coord), ddy(coord));
|
||||
|
||||
float2 deriv = float2(length(uvDDXY.xz), length(uvDDXY.yw));
|
||||
float2 drawWidth = clamp(lineWidth, deriv, 0.5);
|
||||
float2 lineAA = deriv * 1.5;
|
||||
float2 gridUV = 1.0 - abs(frac(coord) * 2.0 - 1.0);
|
||||
float2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);
|
||||
grid2 *= saturate(lineWidth / drawWidth);
|
||||
grid2 = lerp(grid2, lineWidth, saturate(deriv * 2.0 - 1.0));
|
||||
|
||||
float grid = lerp(grid2.x, 1.0, grid2.y);
|
||||
return grid;
|
||||
}
|
||||
|
||||
float4 GetColor(float3 pos, float scale)
|
||||
{
|
||||
float dist = 1 - saturate(distance(float3(ViewPos.x, 0, ViewPos.z), pos) / GridSize);
|
||||
|
||||
// Line width
|
||||
float g1LW = 0.01;
|
||||
// Major line Z
|
||||
float l1 = GetLine(pos.x, 1, g1LW * 2);
|
||||
// Major line X
|
||||
float l2 = GetLine(pos.z, 1, g1LW);
|
||||
|
||||
// Main grid
|
||||
float g1 = GetGrid(pos, 1, g1LW * 0.8);
|
||||
float g2 = GetGrid(pos, 2, g1LW * 0.4);
|
||||
float g3 = GetGrid(pos, 0.1, g1LW * 2);
|
||||
|
||||
float camFadeLarge = clamp(invLerp(2500, 4000, abs(ViewPos.y)),0, g3);
|
||||
g3 *= camFadeLarge;
|
||||
|
||||
float g4 = GetGrid(pos, 10, g1LW);
|
||||
float camFadeTiny = clamp(invLerp(150, 100, abs(ViewPos.y)), 0, g4);
|
||||
g4 *= camFadeTiny;
|
||||
|
||||
float grid = 0;
|
||||
grid = max(l1, l2);
|
||||
grid = max(grid, g1);
|
||||
grid = max(grid, g2);
|
||||
grid = max(grid, g3);
|
||||
grid = max(grid, g4);
|
||||
|
||||
float4 color = grid * GridColor;
|
||||
color = lerp(color, float4(1,0,0,1), l2);
|
||||
color = lerp(color, float4(0,0,1,1), l1);
|
||||
color *= dist;
|
||||
return color;
|
||||
}
|
||||
|
||||
// Pixel shader function for grid rendering
|
||||
META_PS(true, FEATURE_LEVEL_ES2)
|
||||
float4 PS_Grid(PixelInput input) : SV_Target
|
||||
{
|
||||
float4 color = GetColor(input.WorldPosition, 1);
|
||||
return color;
|
||||
}
|
||||
Reference in New Issue
Block a user