Add blend space drawing in Multi Blend 2D editor

#1980
This commit is contained in:
Wojtek Figat
2024-12-10 10:42:40 +01:00
parent 3e5cb09381
commit 09414f9002
4 changed files with 395 additions and 94 deletions

View File

@@ -30,7 +30,7 @@ namespace FlaxEditor.Surface.Archetypes
/// Represents single blend point. /// Represents single blend point.
/// </summary> /// </summary>
/// <seealso cref="FlaxEngine.GUI.Control" /> /// <seealso cref="FlaxEngine.GUI.Control" />
protected class BlendPoint : Control internal class BlendPoint : Control
{ {
private readonly BlendPointsEditor _editor; private readonly BlendPointsEditor _editor;
private readonly int _index; private readonly int _index;
@@ -48,6 +48,11 @@ namespace FlaxEditor.Surface.Archetypes
/// </summary> /// </summary>
public int Index => _index; public int Index => _index;
/// <summary>
/// Flag that indicates that user is moving this point with a mouse.
/// </summary>
public bool IsMouseDown => _isMouseDown;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BlendPoint"/> class. /// Initializes a new instance of the <see cref="BlendPoint"/> class.
/// </summary> /// </summary>
@@ -211,6 +216,11 @@ namespace FlaxEditor.Surface.Archetypes
/// </summary> /// </summary>
public int PointsCount => (_node.Values.Length - 4) / 2; // 4 node values + 2 per blend point public int PointsCount => (_node.Values.Length - 4) / 2; // 4 node values + 2 per blend point
/// <summary>
/// BLend points array.
/// </summary>
internal IReadOnlyList<BlendPoint> BlendPoints => _blendPoints;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BlendPointsEditor"/> class. /// Initializes a new instance of the <see cref="BlendPointsEditor"/> class.
/// </summary> /// </summary>
@@ -374,6 +384,12 @@ namespace FlaxEditor.Surface.Archetypes
/// <returns>The blend point control position.</returns> /// <returns>The blend point control position.</returns>
public Float2 BlendSpacePosToBlendPointPos(Float2 pos) public Float2 BlendSpacePosToBlendPointPos(Float2 pos)
{ {
if (_rangeX.IsZero)
{
var data0 = (Float4)_node.Values[0];
_rangeX = new Float2(data0.X, data0.Y);
_rangeY = _is2D ? new Float2(data0.Z, data0.W) : Float2.Zero;
}
GetPointsArea(out var pointsArea); GetPointsArea(out var pointsArea);
if (_is2D) if (_is2D)
{ {
@@ -389,7 +405,7 @@ namespace FlaxEditor.Surface.Archetypes
pointsArea.Center.Y pointsArea.Center.Y
); );
} }
return pos - new Float2(BlendPoint.DefaultSize * 0.5f); return pos;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -532,81 +548,18 @@ namespace FlaxEditor.Surface.Archetypes
SetAsset((int)b.Tag, Guid.Empty); SetAsset((int)b.Tag, Guid.Empty);
} }
private void DrawAxis(bool vertical, Float2 start, Float2 end, ref Color gridColor, ref Color labelColor, Font labelFont, float value, bool isLast)
{
// Draw line
Render2D.DrawLine(start, end, gridColor);
// Draw label
var labelWidth = 50.0f;
var labelHeight = 10.0f;
var labelMargin = 2.0f;
string label = Utils.RoundTo2DecimalPlaces(value).ToString(System.Globalization.CultureInfo.InvariantCulture);
var hAlign = TextAlignment.Near;
Rectangle labelRect;
if (vertical)
{
labelRect = new Rectangle(start.X + labelMargin * 2, start.Y, labelWidth, labelHeight);
if (isLast)
return; // Don't overlap with the first horizontal label
}
else
{
labelRect = new Rectangle(start.X + labelMargin, start.Y - labelHeight - labelMargin, labelWidth, labelHeight);
if (isLast)
{
labelRect.X = start.X - labelMargin - labelRect.Width;
hAlign = TextAlignment.Far;
}
}
Render2D.DrawText(labelFont, label, labelRect, labelColor, hAlign, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
}
/// <inheritdoc /> /// <inheritdoc />
public override void Draw() public override void Draw()
{ {
var style = Style.Current; var style = Style.Current;
var rect = new Rectangle(Float2.Zero, Size); var rect = new Rectangle(Float2.Zero, Size);
var containsFocus = ContainsFocus; var containsFocus = ContainsFocus;
GetPointsArea(out var pointsArea);
var data0 = (Float4)_node.Values[0];
var rangeX = new Float2(data0.X, data0.Y);
// Background // Background
Render2D.DrawRectangle(rect, IsMouseOver ? style.TextBoxBackgroundSelected : style.TextBoxBackground); _node.DrawEditorBackground(ref rect);
//Render2D.DrawRectangle(pointsArea, Color.Red);
// Grid // Grid
int splits = 10; _node.DrawEditorGrid(ref rect);
var gridColor = style.TextBoxBackgroundSelected * 1.1f;
var labelColor = style.ForegroundDisabled;
var labelFont = style.FontSmall;
//var blendArea = BlendAreaRect;
var blendArea = pointsArea;
for (int i = 0; i <= splits; i++)
{
float alpha = (float)i / splits;
float x = blendArea.Left + blendArea.Width * alpha;
float value = Mathf.Lerp(rangeX.X, rangeX.Y, alpha);
DrawAxis(false, new Float2(x, rect.Height - 2), new Float2(x, 1), ref gridColor, ref labelColor, labelFont, value, i == splits);
}
if (_is2D)
{
var rangeY = new Float2(data0.Z, data0.W);
for (int i = 0; i <= splits; i++)
{
float alpha = (float)i / splits;
float y = blendArea.Top + blendArea.Height * alpha;
float value = Mathf.Lerp(rangeY.X, rangeY.Y, 1.0f - alpha);
DrawAxis(true, new Float2(1, y), new Float2(rect.Width - 2, y), ref gridColor, ref labelColor, labelFont, value, i == splits);
}
}
else
{
float y = blendArea.Center.Y;
Render2D.DrawLine(new Float2(1, y), new Float2(rect.Width - 2, y), gridColor);
}
base.Draw(); base.Draw();
@@ -808,6 +761,87 @@ namespace FlaxEditor.Surface.Archetypes
_editor.SetAsset(SelectedAnimationIndex, Guid.Empty); _editor.SetAsset(SelectedAnimationIndex, Guid.Empty);
} }
private void DrawAxis(bool vertical, Float2 start, Float2 end, ref Color gridColor, ref Color labelColor, Font labelFont, float value, bool isLast)
{
// Draw line
Render2D.DrawLine(start, end, gridColor);
// Draw label
var labelWidth = 50.0f;
var labelHeight = 10.0f;
var labelMargin = 2.0f;
string label = Utils.RoundTo2DecimalPlaces(value).ToString(System.Globalization.CultureInfo.InvariantCulture);
var hAlign = TextAlignment.Near;
Rectangle labelRect;
if (vertical)
{
labelRect = new Rectangle(start.X + labelMargin * 2, start.Y, labelWidth, labelHeight);
if (isLast)
return; // Don't overlap with the first horizontal label
}
else
{
labelRect = new Rectangle(start.X + labelMargin, start.Y - labelHeight - labelMargin, labelWidth, labelHeight);
if (isLast)
{
labelRect.X = start.X - labelMargin - labelRect.Width;
hAlign = TextAlignment.Far;
}
}
Render2D.DrawText(labelFont, label, labelRect, labelColor, hAlign, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
}
/// <summary>
/// Custom drawing logic for blend space background.
/// </summary>
public virtual void DrawEditorBackground(ref Rectangle rect)
{
var style = Style.Current;
Render2D.FillRectangle(rect, style.Background.AlphaMultiplied(0.5f));
Render2D.DrawRectangle(rect, IsMouseOver ? style.TextBoxBackgroundSelected : style.TextBoxBackground);
}
/// <summary>
/// Custom drawing logic for blend space grid.
/// </summary>
public virtual void DrawEditorGrid(ref Rectangle rect)
{
var style = Style.Current;
_editor.GetPointsArea(out var pointsArea);
var data0 = (Float4)Values[0];
var rangeX = new Float2(data0.X, data0.Y);
int splits = 10;
var gridColor = style.TextBoxBackgroundSelected * 1.1f;
var labelColor = style.ForegroundDisabled;
var labelFont = style.FontSmall;
//var blendArea = BlendAreaRect;
var blendArea = pointsArea;
for (int i = 0; i <= splits; i++)
{
float alpha = (float)i / splits;
float x = blendArea.Left + blendArea.Width * alpha;
float value = Mathf.Lerp(rangeX.X, rangeX.Y, alpha);
DrawAxis(false, new Float2(x, rect.Height - 2), new Float2(x, 1), ref gridColor, ref labelColor, labelFont, value, i == splits);
}
if (_editor.Is2D)
{
var rangeY = new Float2(data0.Z, data0.W);
for (int i = 0; i <= splits; i++)
{
float alpha = (float)i / splits;
float y = blendArea.Top + blendArea.Height * alpha;
float value = Mathf.Lerp(rangeY.X, rangeY.Y, 1.0f - alpha);
DrawAxis(true, new Float2(1, y), new Float2(rect.Width - 2, y), ref gridColor, ref labelColor, labelFont, value, i == splits);
}
}
else
{
float y = blendArea.Center.Y;
Render2D.DrawLine(new Float2(1, y), new Float2(rect.Width - 2, y), gridColor);
}
}
/// <summary> /// <summary>
/// Updates the editor UI. /// Updates the editor UI.
/// </summary> /// </summary>
@@ -972,6 +1006,10 @@ namespace FlaxEditor.Surface.Archetypes
private readonly FloatValueBox _animationX; private readonly FloatValueBox _animationX;
private readonly Label _animationYLabel; private readonly Label _animationYLabel;
private readonly FloatValueBox _animationY; private readonly FloatValueBox _animationY;
private Float2[] _triangles;
private Color[] _triangleColors;
private Float2[] _selectedTriangles;
private Color[] _selectedColors;
/// <inheritdoc /> /// <inheritdoc />
public MultiBlend2D(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) public MultiBlend2D(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
@@ -1040,6 +1078,143 @@ namespace FlaxEditor.Surface.Archetypes
} }
} }
private void ClearTriangles()
{
// Remove cache
_triangles = null;
_triangleColors = null;
_selectedTriangles = null;
_selectedColors = null;
}
private void CacheTriangles()
{
// Get locations of blend point vertices
int pointsCount = _editor.PointsCount;
int count = 0, j = 0;
for (int i = 0; i < pointsCount; i++)
{
var animId = (Guid)Values[5 + i * 2];
if (animId != Guid.Empty)
count++;
}
var vertices = new Float2[count];
for (int i = 0; i < pointsCount; i++)
{
var animId = (Guid)Values[5 + i * 2];
if (animId != Guid.Empty)
{
var dataA = (Float4)Values[4 + i * 2];
vertices[j++] = new Float2(dataA.X, dataA.Y);
}
}
// Triangulate
_triangles = FlaxEngine.Utilities.Delaunay2D.Triangulate(vertices);
_triangleColors = null;
// Fix incorrect triangles (mirror logic in AnimGraphBase::onNodeLoaded)
if (_triangles == null || _triangles.Length == 0)
{
// Insert dummy triangles to have something working (eg. blend points are on the same axis)
var triangles = new List<Float2>();
int verticesLeft = vertices.Length;
while (verticesLeft >= 3)
{
verticesLeft -= 3;
triangles.Add(vertices[verticesLeft + 0]);
triangles.Add(vertices[verticesLeft + 1]);
triangles.Add(vertices[verticesLeft + 2]);
}
if (verticesLeft == 1)
{
triangles.Add(vertices[0]);
triangles.Add(vertices[0]);
triangles.Add(vertices[0]);
}
else if (verticesLeft == 2)
{
triangles.Add(vertices[0]);
triangles.Add(vertices[1]);
triangles.Add(vertices[0]);
}
_triangles = triangles.ToArray();
}
// Project to the blend space for drawing
for (int i = 0; i < _triangles.Length; i++)
_triangles[i] = _editor.BlendSpacePosToBlendPointPos(_triangles[i]);
// Check if anything is selected
var selectedIndex = _selectedAnimation.SelectedIndex;
if (selectedIndex != -1)
{
// Find triangles that contain selected point
var dataA = (Float4)Values[4 + selectedIndex * 2];
var pos = _editor.BlendSpacePosToBlendPointPos(new Float2(dataA.X, dataA.Y));
var selectedTriangles = new List<Float2>();
var selectedColors = new List<Color>();
var style = Style.Current;
var triangleColor = style.TextBoxBackgroundSelected.AlphaMultiplied(0.6f);
var selectedTriangleColor = style.BackgroundSelected.AlphaMultiplied(0.6f);
_triangleColors = new Color[_triangles.Length];
for (int i = 0; i < _triangles.Length; i += 3)
{
var is0 = Float2.NearEqual(ref _triangles[i + 0], ref pos);
var is1 = Float2.NearEqual(ref _triangles[i + 1], ref pos);
var is2 = Float2.NearEqual(ref _triangles[i + 2], ref pos);
if (is0 || is1 || is2)
{
selectedTriangles.Add(_triangles[i + 0]);
selectedTriangles.Add(_triangles[i + 1]);
selectedTriangles.Add(_triangles[i + 2]);
selectedColors.Add(is0 ? Color.White : Color.Transparent);
selectedColors.Add(is1 ? Color.White : Color.Transparent);
selectedColors.Add(is2 ? Color.White : Color.Transparent);
}
_triangleColors[i + 0] = is0 ? selectedTriangleColor : triangleColor;
_triangleColors[i + 1] = is1 ? selectedTriangleColor : triangleColor;
_triangleColors[i + 2] = is2 ? selectedTriangleColor : triangleColor;
}
_selectedTriangles = selectedTriangles.ToArray();
_selectedColors = selectedColors.ToArray();
}
}
/// <inheritdoc />
public override void DrawEditorBackground(ref Rectangle rect)
{
base.DrawEditorBackground(ref rect);
// Draw triangulated multi blend space
var style = Style.Current;
if (_triangles == null)
CacheTriangles();
if (_triangleColors != null && (ContainsFocus || IsMouseOver))
Render2D.FillTriangles(_triangles, _triangleColors);
else
Render2D.FillTriangles(_triangles, style.TextBoxBackgroundSelected.AlphaMultiplied(0.6f));
Render2D.DrawTriangles(_triangles, style.Foreground);
}
/// <inheritdoc />
public override void DrawEditorGrid(ref Rectangle rect)
{
base.DrawEditorGrid(ref rect);
// Highlight selected blend point
var style = Style.Current;
var selectedIndex = _selectedAnimation.SelectedIndex;
if (selectedIndex != -1 && (ContainsFocus || IsMouseOver))
{
var point = _editor.BlendPoints[selectedIndex];
var highlightColor = point.IsMouseDown ? style.SelectionBorder : style.BackgroundSelected;
Render2D.PushTint(ref highlightColor);
Render2D.DrawTriangles(_selectedTriangles, _selectedColors);
Render2D.PopTint();
}
}
/// <inheritdoc /> /// <inheritdoc />
public override void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1) public override void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1)
{ {
@@ -1064,6 +1239,23 @@ namespace FlaxEditor.Surface.Archetypes
_animationX.Enabled = isValid; _animationX.Enabled = isValid;
_animationYLabel.Enabled = isValid; _animationYLabel.Enabled = isValid;
_animationY.Enabled = isValid; _animationY.Enabled = isValid;
ClearTriangles();
}
/// <inheritdoc />
public override void OnValuesChanged()
{
base.OnValuesChanged();
ClearTriangles();
}
/// <inheritdoc />
public override void OnLoaded(SurfaceNodeActions action)
{
base.OnLoaded(action);
ClearTriangles();
} }
} }
} }

View File

@@ -301,12 +301,12 @@ void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Float2
IBIndex += 3; IBIndex += 3;
} }
void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Color& color0, const Color& color1, const Color& color2) FORCE_INLINE void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Color& color0, const Color& color1, const Color& color2)
{ {
WriteTri(p0, p1, p2, Float2::Zero, Float2::Zero, Float2::Zero, color0, color1, color2); WriteTri(p0, p1, p2, Float2::Zero, Float2::Zero, Float2::Zero, color0, color1, color2);
} }
void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Float2& uv0, const Float2& uv1, const Float2& uv2) FORCE_INLINE void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Float2& uv0, const Float2& uv1, const Float2& uv2)
{ {
WriteTri(p0, p1, p2, uv0, uv1, uv2, Color::Black, Color::Black, Color::Black); WriteTri(p0, p1, p2, uv0, uv1, uv2, Color::Black, Color::Black, Color::Black);
} }
@@ -1816,8 +1816,8 @@ void DrawLines(const Float2* points, int32 pointsCount, const Color& color1, con
// Ending cap // Ending cap
{ {
ApplyTransform(points[0], p1t); ApplyTransform(points[pointsCount - 2], p1t);
ApplyTransform(points[1], p2t); //ApplyTransform(points[pointsCount - 1], p2t);
const Float2 capDirection = thicknessHalf * Float2::Normalize(p2t - p1t); const Float2 capDirection = thicknessHalf * Float2::Normalize(p2t - p1t);
@@ -1962,9 +1962,56 @@ void Render2D::DrawBlur(const Rectangle& rect, float blurStrength)
WriteRect(rect, Color::White); WriteRect(rect, Color::White);
} }
void Render2D::DrawTriangles(const Span<Float2>& vertices, const Color& color, float thickness)
{
RENDER2D_CHECK_RENDERING_STATE;
CHECK(vertices.Length() % 3 == 0);
Float2 points[2];
for (int32 i = 0; i < vertices.Length(); i += 3)
{
#if 0
// TODO: fix this
DrawLines(&vertices.Get()[i], 3, color, color, thickness);
#else
points[0] = vertices.Get()[i + 0];
points[1] = vertices.Get()[i + 1];
DrawLines(points, 2, color, color, thickness);
points[0] = vertices.Get()[i + 2];
DrawLines(points, 2, color, color, thickness);
points[1] = vertices.Get()[i + 0];
DrawLines(points, 2, color, color, thickness);
#endif
}
}
void Render2D::DrawTriangles(const Span<Float2>& vertices, const Span<Color>& colors, float thickness)
{
RENDER2D_CHECK_RENDERING_STATE;
CHECK(vertices.Length() % 3 == 0);
Float2 points[2];
Color cols[2];
for (int32 i = 0; i < vertices.Length(); i += 3)
{
points[0] = vertices.Get()[i + 0];
points[1] = vertices.Get()[i + 1];
cols[0] = colors.Get()[i + 0];
cols[1] = colors.Get()[i + 1];
DrawLines(points, 2, cols[0], cols[1], thickness);
points[0] = vertices.Get()[i + 2];
cols[0] = colors.Get()[i + 2];
DrawLines(points, 2, cols[0], cols[1], thickness);
points[1] = vertices.Get()[i + 0];
cols[1] = colors.Get()[i + 0];
DrawLines(points, 2, cols[0], cols[1], thickness);
}
}
void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& uvs) void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& uvs)
{ {
RENDER2D_CHECK_RENDERING_STATE; RENDER2D_CHECK_RENDERING_STATE;
CHECK(vertices.Length() % 3 == 0);
CHECK(vertices.Length() == uvs.Length()); CHECK(vertices.Length() == uvs.Length());
Render2DDrawCall& drawCall = DrawCalls.AddOne(); Render2DDrawCall& drawCall = DrawCalls.AddOne();
@@ -1979,14 +2026,24 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices
void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& uvs, const Color& color) void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& uvs, const Color& color)
{ {
Color colors[3] = { (Color)color, (Color)color, (Color)color }; RENDER2D_CHECK_RENDERING_STATE;
Span<Color> spancolor(colors, 3); CHECK(vertices.Length() % 3 == 0);
DrawTexturedTriangles(t, vertices, uvs, spancolor); CHECK(vertices.Length() == uvs.Length());
Render2DDrawCall& drawCall = DrawCalls.AddOne();
drawCall.Type = DrawCallType::FillTexture;
drawCall.StartIB = IBIndex;
drawCall.CountIB = vertices.Length();
drawCall.AsTexture.Ptr = t;
for (int32 i = 0; i < vertices.Length(); i += 3)
WriteTri(vertices[i], vertices[i + 1], vertices[i + 2], uvs[i], uvs[i + 1], uvs[i + 2], color, color, color);
} }
void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& uvs, const Span<Color>& colors) void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& uvs, const Span<Color>& colors)
{ {
RENDER2D_CHECK_RENDERING_STATE; RENDER2D_CHECK_RENDERING_STATE;
CHECK(vertices.Length() % 3 == 0);
CHECK(vertices.Length() == uvs.Length()); CHECK(vertices.Length() == uvs.Length());
CHECK(vertices.Length() == colors.Length()); CHECK(vertices.Length() == colors.Length());
@@ -2021,6 +2078,19 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<uint16>& indices,
} }
} }
void Render2D::FillTriangles(const Span<Float2>& vertices, const Color& color)
{
RENDER2D_CHECK_RENDERING_STATE;
Render2DDrawCall& drawCall = DrawCalls.AddOne();
drawCall.Type = NeedAlphaWithTint(color) ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
drawCall.StartIB = IBIndex;
drawCall.CountIB = vertices.Length();
for (int32 i = 0; i < vertices.Length(); i += 3)
WriteTri(vertices[i], vertices[i + 1], vertices[i + 2], color, color, color);
}
void Render2D::FillTriangles(const Span<Float2>& vertices, const Span<Color>& colors, bool useAlpha) void Render2D::FillTriangles(const Span<Float2>& vertices, const Span<Color>& colors, bool useAlpha)
{ {
CHECK(vertices.Length() == colors.Length()); CHECK(vertices.Length() == colors.Length());

View File

@@ -415,6 +415,22 @@ public:
/// <param name="blurStrength">The blur strength defines how blurry the background is. Larger numbers increase blur, resulting in a larger runtime cost on the GPU.</param> /// <param name="blurStrength">The blur strength defines how blurry the background is. Larger numbers increase blur, resulting in a larger runtime cost on the GPU.</param>
API_FUNCTION() static void DrawBlur(const Rectangle& rect, float blurStrength); API_FUNCTION() static void DrawBlur(const Rectangle& rect, float blurStrength);
/// <summary>
/// Draws vertices array.
/// </summary>
/// <param name="vertices">The vertices array.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The line thickness.</param>
API_FUNCTION() static void DrawTriangles(const Span<Float2>& vertices, const Color& color, float thickness = 1.0f);
/// <summary>
/// Draws vertices array.
/// </summary>
/// <param name="vertices">The vertices array.</param>
/// <param name="colors">The colors array.</param>
/// <param name="thickness">The line thickness.</param>
API_FUNCTION() static void DrawTriangles(const Span<Float2>& vertices, const Span<Color>& colors, float thickness = 1.0f);
/// <summary> /// <summary>
/// Draws vertices array. /// Draws vertices array.
/// </summary> /// </summary>
@@ -451,13 +467,20 @@ public:
/// <param name="colors">The colors array.</param> /// <param name="colors">The colors array.</param>
API_FUNCTION() static void DrawTexturedTriangles(GPUTexture* t, const Span<uint16>& indices, const Span<Float2>& vertices, const Span<Float2>& uvs, const Span<Color>& colors); API_FUNCTION() static void DrawTexturedTriangles(GPUTexture* t, const Span<uint16>& indices, const Span<Float2>& vertices, const Span<Float2>& uvs, const Span<Color>& colors);
/// <summary>
/// Draws vertices array.
/// </summary>
/// <param name="vertices">The vertices array.</param>
/// <param name="color">The color.</param>
API_FUNCTION() static void FillTriangles(const Span<Float2>& vertices, const Color& color);
/// <summary> /// <summary>
/// Draws vertices array. /// Draws vertices array.
/// </summary> /// </summary>
/// <param name="vertices">The vertices array.</param> /// <param name="vertices">The vertices array.</param>
/// <param name="colors">The colors array.</param> /// <param name="colors">The colors array.</param>
/// <param name="useAlpha">If true alpha blending will be enabled.</param> /// <param name="useAlpha">If true alpha blending will be enabled.</param>
API_FUNCTION() static void FillTriangles(const Span<Float2>& vertices, const Span<Color>& colors, bool useAlpha); API_FUNCTION() static void FillTriangles(const Span<Float2>& vertices, const Span<Color>& colors, bool useAlpha = true);
/// <summary> /// <summary>
/// Fills a triangular area. /// Fills a triangular area.

View File

@@ -7,10 +7,11 @@
#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Array.h"
/// <summary> /// <summary>
/// Helper class with Delaunay triangulation algorithm implementation, /// Helper class with Delaunay triangulation algorithm implementation (2D space).
/// </summary> /// </summary>
class Delaunay2D API_CLASS(Internal, Static, Namespace="FlaxEngine.Utilities") class Delaunay2D
{ {
DECLARE_SCRIPTING_TYPE_MINIMAL(Delaunay2D);
public: public:
struct Triangle struct Triangle
{ {
@@ -31,14 +32,35 @@ public:
} }
}; };
template<typename TVertexArray, typename TTrianglesArray> /// <summary>
static void Triangulate(const TVertexArray& vertices, TTrianglesArray& triangles) /// Triangulates input vertices array into the list of triangle vertices.
/// </summary>
/// <param name="vertices">Input list of vertices.</param>
/// <returns>Result list of triangles. Each triangle is made out of sequence of 3 vertices. Empty if no valid triangle built.</returns>
API_FUNCTION() static Array<Float2> Triangulate(const Array<Float2>& vertices)
{
Array<Triangle> triangles;
Triangulate(vertices, triangles);
Array<Float2> result;
result.Resize(triangles.Count() * 3);
int32 c = 0;
for (const Triangle& t : triangles)
{
result.Get()[c++] = vertices[t.Indices[0]];
result.Get()[c++] = vertices[t.Indices[1]];
result.Get()[c++] = vertices[t.Indices[2]];
}
return result;
}
template<typename TrianglesArray = Triangle>
static void Triangulate(const Array<Float2>& vertices, TrianglesArray& triangles)
{ {
// Skip if no change to produce any triangles // Skip if no change to produce any triangles
if (vertices.Count() < 3) if (vertices.Count() < 3)
return; return;
TVertexArray points = vertices; Array<Float2> points = vertices;
Array<Edge> polygon; Array<Edge> polygon;
Rectangle rect; Rectangle rect;
@@ -142,8 +164,7 @@ private:
} }
}; };
template<typename TVertexArray> static bool CircumCircleContains(const Array<Float2>& vertices, const Triangle& triangle, int32 vertexIndex)
static bool CircumCircleContains(const TVertexArray& vertices, const Triangle& triangle, int vertexIndex)
{ {
Float2 p1 = vertices[triangle.Indices[0]]; Float2 p1 = vertices[triangle.Indices[0]];
Float2 p2 = vertices[triangle.Indices[1]]; Float2 p2 = vertices[triangle.Indices[1]];
@@ -157,25 +178,20 @@ private:
(ab * (p3.Y - p2.Y) + cd * (p1.Y - p3.Y) + ef * (p2.Y - p1.Y)) / (p1.X * (p3.Y - p2.Y) + p2.X * (p1.Y - p3.Y) + p3.X * (p2.Y - p1.Y)), (ab * (p3.Y - p2.Y) + cd * (p1.Y - p3.Y) + ef * (p2.Y - p1.Y)) / (p1.X * (p3.Y - p2.Y) + p2.X * (p1.Y - p3.Y) + p3.X * (p2.Y - p1.Y)),
(ab * (p3.X - p2.X) + cd * (p1.X - p3.X) + ef * (p2.X - p1.X)) / (p1.Y * (p3.X - p2.X) + p2.Y * (p1.X - p3.X) + p3.Y * (p2.X - p1.X))); (ab * (p3.X - p2.X) + cd * (p1.X - p3.X) + ef * (p2.X - p1.X)) / (p1.Y * (p3.X - p2.X) + p2.Y * (p1.X - p3.X) + p3.Y * (p2.X - p1.X)));
circum *= 0.5; circum *= 0.5f;
float r = Float2::DistanceSquared(p1, circum); float r = Float2::DistanceSquared(p1, circum);
float d = Float2::DistanceSquared(vertices[vertexIndex], circum); float d = Float2::DistanceSquared(vertices[vertexIndex], circum);
return d <= r; return d <= r;
} }
template<typename TVertexArray> static bool EdgeCompare(const Array<Float2>& vertices, const Edge& a, const Edge& b)
static bool EdgeCompare(const TVertexArray& vertices, const Edge& a, const Edge& b)
{ {
if (Float2::Distance(vertices[a.Indices[0]], vertices[b.Indices[0]]) < ZeroTolerance && Vector2::Distance(vertices[a.Indices[1]], vertices[b.Indices[1]]) < ZeroTolerance) if (Float2::Distance(vertices[a.Indices[0]], vertices[b.Indices[0]]) < ZeroTolerance &&
{ Float2::Distance(vertices[a.Indices[1]], vertices[b.Indices[1]]) < ZeroTolerance)
return true; return true;
} if (Float2::Distance(vertices[a.Indices[0]], vertices[b.Indices[1]]) < ZeroTolerance &&
Float2::Distance(vertices[a.Indices[1]], vertices[b.Indices[0]]) < ZeroTolerance)
if (Float2::Distance(vertices[a.Indices[0]], vertices[b.Indices[1]]) < ZeroTolerance && Vector2::Distance(vertices[a.Indices[1]], vertices[b.Indices[0]]) < ZeroTolerance)
{
return true; return true;
}
return false; return false;
} }
}; };