// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using FlaxEngine; using FlaxEngine.GUI; using System; namespace FlaxEditor.Surface.Elements { /// /// Visject Surface output box element. /// /// [HideInEditor] public class OutputBox : Box { /// public OutputBox(SurfaceNode parentNode, NodeElementArchetype archetype) : base(parentNode, archetype, archetype.Position + new Vector2(parentNode.Archetype.Size.X, 0)) { } /// /// Draws the connection between two boxes. /// /// The start location. /// The end location. /// The connection color. /// The connection thickness. public static void DrawConnection(ref Vector2 start, ref Vector2 end, ref Color color, float thickness = 1) { // Calculate control points var dst = (end - start) * new Vector2(0.5f, 0.05f); Vector2 control1 = new Vector2(start.X + dst.X, start.Y + dst.Y); Vector2 control2 = new Vector2(end.X - dst.X, end.Y + dst.Y); // Draw line Render2D.DrawBezier(start, control1, control2, end, color, thickness); /* // Debug drawing control points Vector2 bSize = new Vector2(4, 4); Render2D.FillRectangle(new Rectangle(control1 - bSize * 0.5f, bSize), Color.Blue); Render2D.FillRectangle(new Rectangle(control2 - bSize * 0.5f, bSize), Color.Gold); */ } private static bool IsRight(ref Vector2 a, ref Vector2 b, ref Vector2 point) { return ((b.X - a.X) * (point.Y - a.Y) - (b.Y - a.Y) * (point.X - a.X)) <= 0; } /// /// Checks if a point intersects a bezier curve /// /// The start location. /// The end location. /// The point /// Distance at which its an intersection public static bool IntersectsConnection(ref Vector2 start, ref Vector2 end, ref Vector2 point, float distance) { if ((point.X - start.X) * (end.X - point.X) < 0) return false; var dst = (end - start) * new Vector2(0.25f, 0.05f); // Purposefully tweaked to 0.25 Vector2 control1 = new Vector2(start.X + dst.X, start.Y + dst.Y); Vector2 control2 = new Vector2(end.X - dst.X, end.Y + dst.Y); // I'm ignoring the start.Y + dst.Y Vector2 direction = end - start; float offset = Mathf.Sign(direction.Y) * distance; Vector2 pointAbove1 = new Vector2(control1.X, start.Y - offset); Vector2 pointAbove2 = new Vector2(end.X, end.Y - offset); Vector2 pointBelow1 = new Vector2(start.X, start.Y + offset); Vector2 pointBelow2 = new Vector2(control2.X, end.Y + offset); /* Render2D.DrawLine(pointAbove1, pointAbove2, Color.Red); Render2D.DrawRectangle(new Rectangle(pointAbove2 - new Vector2(5), new Vector2(10)), Color.Red); Render2D.DrawLine(pointBelow1, pointBelow2, Color.Green); Render2D.DrawRectangle(new Rectangle(pointBelow2 - new Vector2(5), new Vector2(10)), Color.Green); */ // TODO: are they parallel ^? return IsRight(ref pointAbove1, ref pointAbove2, ref point) == IsRight(ref pointBelow2, ref pointBelow1, ref point); // Taken from the Render2D.DrawBezier code float squaredDistance = distance; Vector2 d1 = control1 - start; Vector2 d2 = control2 - control1; Vector2 d3 = end - control2; float len = d1.Length + d2.Length + d3.Length; int segmentCount = Math.Min(Math.Max(Mathf.CeilToInt(len * 0.05f), 1), 100); float segmentCountInv = 1.0f / segmentCount; Bezier(ref start, ref control1, ref control2, ref end, 0, out Vector2 p); for (int i = 1; i <= segmentCount; i++) { Vector2 oldp = p; float t = i * segmentCountInv; Bezier(ref start, ref control1, ref control2, ref end, t, out p); CollisionsHelper.ClosestPointPointLine(ref point, ref oldp, ref p, out Vector2 result); if (Vector2.DistanceSquared(point, result) <= squaredDistance) { return true; } } return false; } private static void Bezier(ref Vector2 p0, ref Vector2 p1, ref Vector2 p2, ref Vector2 p3, float alpha, out Vector2 result) { Vector2.Lerp(ref p0, ref p1, alpha, out var p01); Vector2.Lerp(ref p1, ref p2, alpha, out var p12); Vector2.Lerp(ref p2, ref p3, alpha, out var p23); Vector2.Lerp(ref p01, ref p12, alpha, out var p012); Vector2.Lerp(ref p12, ref p23, alpha, out var p123); Vector2.Lerp(ref p012, ref p123, alpha, out result); } /// /// Draw all connections coming from this box. /// public void DrawConnections(ref Vector2 mousePosition) { // Draw all the connections var center = Size * 0.5f; var tmp = PointToParent(ref center); var startPos = Parent.PointToParent(ref tmp); var startHighlight = ConnectionsHighlightIntensity; for (int i = 0; i < Connections.Count; i++) { Box targetBox = Connections[i]; tmp = targetBox.PointToParent(ref center); Vector2 endPos = targetBox.Parent.PointToParent(ref tmp); var highlight = 1 + Mathf.Max(startHighlight, targetBox.ConnectionsHighlightIntensity); var color = _currentTypeColor * highlight; // if (PointInRectangle(ref startPos, ref endPos, ref mousePosition)) { for (int ix = 0; ix < 10000; ix++) { OutputBox.IntersectsConnection(ref startPos, ref endPos, ref mousePosition, 30f); } if (OutputBox.IntersectsConnection(ref startPos, ref endPos, ref mousePosition, 30f)) { highlight += 2; } } DrawConnection(ref startPos, ref endPos, ref color, highlight); } } private bool PointInRectangle(ref Vector2 a, ref Vector2 b, ref Vector2 point) { // This has the convenient property that it doesn't matter if a and b are switched Vector2 signs = (point - a) * (b - point); return signs.X >= 0 && signs.Y >= 0; } /// /// Draw a selected connections coming from this box. /// public void DrawSelectedConnection(Box targetBox) { // Draw all the connections var center = Size * 0.5f; var tmp = PointToParent(ref center); var startPos = Parent.PointToParent(ref tmp); tmp = targetBox.PointToParent(ref center); Vector2 endPos = targetBox.Parent.PointToParent(ref tmp); DrawConnection(ref startPos, ref endPos, ref _currentTypeColor, 2); } /// public override bool IsOutput => true; /// public override void Draw() { base.Draw(); // Box DrawBox(); // Draw text var style = Style.Current; var rect = new Rectangle(-100, 0, 100 - 2, Height); Render2D.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Far, TextAlignment.Center); } } }