diff --git a/Dependences/earcut.net b/Dependences/earcut.net index 3bc119b1..29aa438b 160000 --- a/Dependences/earcut.net +++ b/Dependences/earcut.net @@ -1 +1 @@ -Subproject commit 3bc119b13ccba1f18ff801a816c02939cc27fd53 +Subproject commit 29aa438bf2f493722d5fc34089727d44fe79de22 diff --git a/OngekiFumenEditor/Kernel/Graphics/ILineDrawing.cs b/OngekiFumenEditor/Kernel/Graphics/ILineDrawing.cs index da6042e2..86850a1e 100644 --- a/OngekiFumenEditor/Kernel/Graphics/ILineDrawing.cs +++ b/OngekiFumenEditor/Kernel/Graphics/ILineDrawing.cs @@ -3,21 +3,28 @@ namespace OngekiFumenEditor.Kernel.Graphics { - public interface ILineDrawing : IDrawing - { - public struct VertexDash - { - public int DashSize { get; set; } - public int GapSize { get; set; } + public interface ILineDrawing : IDrawing + { + public struct VertexDash + { + public int DashSize { get; set; } + public int GapSize { get; set; } - public static VertexDash Solider { get; } = new VertexDash() - { - GapSize = 0, - DashSize = 100 - }; - } + public VertexDash() { } + public VertexDash(int dashSize, int gapSize) + { + DashSize = dashSize; + GapSize = gapSize; + } - public record LineVertex(Vector2 Point, Vector4 Color, VertexDash Dash); - void Draw(IDrawingContext target, IEnumerable points, float lineWidth); - } + public static VertexDash Solider { get; } = new VertexDash() + { + GapSize = 0, + DashSize = 100 + }; + } + + public record LineVertex(Vector2 Point, Vector4 Color, VertexDash Dash); + void Draw(IDrawingContext target, IEnumerable points, float lineWidth); + } } diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawPlayableAreaHelper.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawPlayableAreaHelper.cs index 7700aace..77019b4f 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawPlayableAreaHelper.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawPlayableAreaHelper.cs @@ -13,6 +13,10 @@ using OngekiFumenEditor.Utils.ObjectPool; using EarcutNet; using System.Drawing; +using Microsoft.CodeAnalysis; +using System.Diagnostics; +using OngekiFumenEditor.Base.OngekiObjects.BulletPalleteEnums; +using Xv2CoreLib.HslColor; namespace OngekiFumenEditor.Modules.FumenVisualEditor.Graphics.Drawing.Editors { @@ -20,6 +24,7 @@ public class DrawPlayableAreaHelper { private ILineDrawing lineDrawing; private IPolygonDrawing polygonDrawing; + private ICircleDrawing circleDrawing; private Vector4 playFieldForegroundColor; private bool enablePlayFieldDrawing; @@ -30,6 +35,7 @@ public DrawPlayableAreaHelper() { lineDrawing = IoC.Get(); polygonDrawing = IoC.Get(); + circleDrawing = IoC.Get(); UpdateProps(); Properties.EditorGlobalSetting.Default.PropertyChanged += Default_PropertyChanged; @@ -89,6 +95,7 @@ 4. 将左右所有的节点合并成一个多边形,渲染 const long defaultRightX = 24 * XGrid.DEFAULT_RES_X; var fumen = target.Editor.Fumen; + var currentTGrid = target.Editor.GetCurrentTGrid(); void EnumeratePoints(bool isRight, List result) { @@ -102,15 +109,29 @@ void EnumeratePoints(bool isRight, List result) var points = ObjectPool>.Get(); points.Clear(); - var prevPos = new Vector2((float)MathUtils.Random(), (float)MathUtils.Random()); - void appendPoint2(List list, float x, float y) + var prevX = (float)Random(); + var prevY = (float)Random(); + + void appendPoint2(List list, float totalXGrid, float totalTGrid) + { + var px = (float)XGridCalculator.ConvertXGridToX(totalXGrid / XGrid.DEFAULT_RES_X, target.Editor); + var py = (float)target.ConvertToY(totalTGrid / TGrid.DEFAULT_RES_T); + + appendPoint3(list, px, py, list.Count); + } + + void appendPoint3(List list, float px, float py, int insertIdx) { - var p = new Vector2(x, y); - if (prevPos == p) - return; - list.Add(p); - prevPos = p; + var po = list.ElementAtOrDefault(insertIdx); + if (po.X == px && po.Y == py) + return; + + var p = new Vector2(px, py); + list.Insert(insertIdx, p); + + //DebugPrintPoint(p, isRight, false, 10); } + void appendPoint(List list, XGrid xGrid, float y) { if (xGrid is null) @@ -168,11 +189,13 @@ void appendPoint(List list, XGrid xGrid, float y) ); } - var sortedPoints = points.OrderBy(x => x).ToList(); + var sortedPoints = points.Where(x => minTGrid.TotalGrid < x && x < maxTGrid.TotalGrid).OrderBy(x => x).ToList(); + /* if (sortedPoints.Count == 0 || sortedPoints.FirstOrDefault() > minTGrid.TotalGrid) sortedPoints.Insert(0, minTGrid.TotalGrid); - if (sortedPoints.LastOrDefault() < maxTGrid.TotalGrid) - sortedPoints.Add(maxTGrid.TotalGrid); + */ + sortedPoints.InsertBySortBy(minTGrid.TotalGrid, x => x); + sortedPoints.InsertBySortBy(maxTGrid.TotalGrid, x => x); var segments = sortedPoints.SequenceConsecutivelyWrap(2).Select(x => (x.FirstOrDefault(), x.LastOrDefault())).ToArray(); @@ -190,7 +213,7 @@ void appendPoint(List list, XGrid xGrid, float y) .ToArray(); //选取轨道,如果存在多个轨道(即轨道交叉冲突了),那么就按左右边规则选取一个 - (var midXGrid, var pickLane) = pickables.IsEmpty() ? default : (isRight ? pickables.MaxBy(x => x.Item1) : pickables.MinBy(x => x.Item1)); + (_, var pickLane) = pickables.IsEmpty() ? default : (isRight ? pickables.MaxBy(x => x.Item1) : pickables.MinBy(x => x.Item1)); if (pickLane is not null) { var fromTGrid = TGrid.FromTotalGrid((int)fromY); @@ -205,11 +228,103 @@ void appendPoint(List list, XGrid xGrid, float y) else { //选取不到轨道,表示这个segement是两个轨道之间的空白区域,那么直接填上空白就行 - result.Add(new(defaultX, fromY)); - result.Add(new(defaultX, toY)); + appendPoint2(result, defaultX, fromY); + appendPoint2(result, defaultX, toY); } } + //todo 解决变速过快导致的精度丢失问题 + Vector2? interpolate(TGrid tGrid, float actualY, out bool isPickLane) + { + isPickLane = false; + var pickables = fumen.Lanes + .GetVisibleStartObjects(tGrid, tGrid) + .Where(x => x.LaneType == type) + .Where(x => + { + var laneMinY = target.ConvertToY(x.MinTGrid); + var laneMaxY = target.ConvertToY(x.MaxTGrid); + + return laneMinY <= actualY && actualY <= laneMaxY; + }) + .Select(x => (x.CalulateXGrid(tGrid), x)) + .FilterNullBy(x => x.Item1) + .ToArray(); + + (_, var pickLane) = pickables.IsEmpty() ? default : (isRight ? pickables.MaxBy(x => x.Item1) : pickables.MinBy(x => x.Item1)); + + if (pickLane is not null) + { + var itor = pickLane.GenAllPath().GetEnumerator(); + var prevOpt = default(OpenTK.Mathematics.Vector2?); + + while (itor.MoveNext()) + { + var cur = itor.Current.pos; + + if (cur.Y > tGrid.TotalGrid) + { + // prev ------------ cur + // ^ + // tGrid + + if (prevOpt is OpenTK.Mathematics.Vector2 prev) + { + var curPx = (float)XGridCalculator.ConvertXGridToX(cur.X / XGrid.DEFAULT_RES_X, target.Editor); + var curPy = (float)target.ConvertToY(cur.Y / TGrid.DEFAULT_RES_T); + var prevPx = (float)XGridCalculator.ConvertXGridToX(prev.X / XGrid.DEFAULT_RES_X, target.Editor); + var prevPy = (float)target.ConvertToY(prev.Y / TGrid.DEFAULT_RES_T); + + var nowPy = actualY; + var nowPx = (float)MathUtils.CalculateXFromTwoPointFormFormula(nowPy, prevPx, prevPy, curPx, curPy); + isPickLane = true; + return new(nowPx, nowPy); + } + } + + prevOpt = cur; + } + } + else + { + var defaultPx = (float)XGridCalculator.ConvertXGridToX(defaultX / XGrid.DEFAULT_RES_X, target.Editor); + return new(defaultPx, actualY); + } + + return default; + } + + if (minTGrid <= currentTGrid && currentTGrid <= maxTGrid) + { + var maxY = target.ConvertToY(maxTGrid); + var actualMaxY = target.Rect.TopLeft.Y; + + var maxDiff = maxY - actualMaxY; + if (Math.Abs(maxDiff) >= 1) + { + if (interpolate(maxTGrid, (float)Math.Max(actualMaxY, maxY), out var isPickLane) is Vector2 pp) + { + if (!isPickLane) + appendPoint3(result, (float)XGridCalculator.ConvertXGridToX(defaultX / XGrid.DEFAULT_RES_X, target.Editor), result.LastOrDefault().Y, result.Count); + appendPoint3(result, pp.X, pp.Y, result.Count); + } + } + + var minY = target.ConvertToY(minTGrid); + var actualMinY = target.Rect.ButtomRight.Y; + + var minDiff = minY - actualMinY; + if (Math.Abs(minDiff) >= 1) + { + if (interpolate(minTGrid, (float)Math.Max(actualMinY, minY), out var isPickLane) is Vector2 pp) + { + if (!isPickLane) + appendPoint3(result, (float)XGridCalculator.ConvertXGridToX(defaultX / XGrid.DEFAULT_RES_X, target.Editor), result.FirstOrDefault().Y, 0); + appendPoint3(result, pp.X, pp.Y, 0); + } + } + } + ObjectPool>.Return(points); } @@ -218,50 +333,126 @@ void appendPoint(List list, XGrid xGrid, float y) using var d4 = ObjectPool>.GetWithUsingDisposable(out var idxList, out _); idxList.Clear(); - void FillPoints(List ps) + void FillPoints(List ps, bool isRight) { - foreach (var point in ps) + var s = points.Count / 2; + + for (var i = 0; i < ps.Count; i++) { - var x = (float)XGridCalculator.ConvertXGridToX(point.X / XGrid.DEFAULT_RES_X, target.Editor); - var y = (float)target.ConvertToY(point.Y / TGrid.DEFAULT_RES_T); + var cur = ps[i]; - points.Add(x); - points.Add(y); - } + //remove dup + if (points.Count >= 2) + { + var prevY = points[^1]; + var prevX = points[^2]; + + if (prevY == cur.Y && prevX == cur.X) + continue; + } + + //optimze prev point if able + if (points.Count >= 4) + { + var prevY = points[^1]; + var prevX = points[^2]; + + if ((prevY == cur.Y && prevY == points[^3]) || (prevX == cur.X && prevX == points[^4])) + { + //remove + points.RemoveAt(points.Count - 1); + points.RemoveAt(points.Count - 1); + } + } + + points.Add(cur.X); + points.Add(cur.Y); + } + + if (isRight) + { + for (var j = s; j < points.Count / 2; j++) + DebugPrintPoint(new((float)points[2 * j], (float)points[2 * j + 1]), isRight, true, 10); + } } using var d = ObjectPool>.GetWithUsingDisposable(out var leftPoints, out _); leftPoints.Clear(); using var d2 = ObjectPool>.GetWithUsingDisposable(out var rightPoints, out _); - rightPoints.Clear(); + rightPoints.Clear(); + + BeginDebugPrint(target); //计算左边墙的点 EnumeratePoints(false, leftPoints); - FillPoints(leftPoints); + FillPoints(leftPoints, false); //计算右边墙的点,因为要组合成一个多边形,所以右半部分得翻转一下顺序 EnumeratePoints(true, rightPoints); rightPoints.Reverse(); - FillPoints(rightPoints); - + FillPoints(rightPoints, true); + //todo 解决左右墙交叉处理问题 + + EndDebugPrint(); + var tessellateList = ObjectPool>.Get(); tessellateList.Clear(); Earcut.Tessellate(points, idxList, tessellateList); + var r = string.Join(Environment.NewLine, points.SequenceWrap(2).Select(x => $"{x.FirstOrDefault(),-20}{x.LastOrDefault()}")); + polygonDrawing.Begin(target, OpenTK.Graphics.OpenGL.PrimitiveType.Triangles); + var i = 0; foreach (var seq in tessellateList.SequenceWrap(3)) { + var normal = i * 1.0f / tessellateList.Count / 2 + 0.5; + var hslColor = new HslColor(0.55, normal, 1); + + var rgb = hslColor.ToRgb(); + var color = new Vector4((float)rgb.R, (float)rgb.G, (float)rgb.B, 0.25f); + foreach (var idx in seq) { var x = (float)points[idx * 2 + 0]; var y = (float)points[idx * 2 + 1]; polygonDrawing.PostPoint(new(x, y), playFieldForegroundColor); } + i += 3; } + polygonDrawing.End(); + /* + i = 0; + foreach (var seq in tessellateList.SequenceWrap(3)) + { + var normal = i * 1.0f / tessellateList.Count / 2 + 0.5; + var hslColor = new HslColor(0.55, normal, 1); + var rgb = hslColor.ToRgb(); + var color = new Vector4((float)rgb.R, (float)rgb.G, (float)rgb.B, 1); + lineDrawing.Draw(target, seq.Append(seq.FirstOrDefault()).Select(idx => new LineVertex(new((float)points[idx * 2 + 0], (float)points[idx * 2 + 1]), color, new(2, 2))), 3); + i += 3; + } + */ ObjectPool>.Return(tessellateList); + } - polygonDrawing.End(); + [Conditional("DEBUG")] + private void DebugPrintPoint(Vector2 p, bool isRight, bool v1, int v2) + { + var color = isRight ? new Vector4(1, 0, 0, 0.5f) : new Vector4(0, 1, 0, 0.5f); + circleDrawing.Post(p, color, false, v2); + } + + [Conditional("DEBUG")] + private void BeginDebugPrint(IFumenEditorDrawingContext target) + { + circleDrawing.Begin(target); + } + + [Conditional("DEBUG")] + private void EndDebugPrint() + { + circleDrawing.End(); } } }