From 981814c0fe369d118f2f99f4e2daf2ce862d51ce Mon Sep 17 00:00:00 2001 From: MikiraSora Date: Fri, 20 Sep 2024 09:30:18 +0800 Subject: [PATCH] add AdjustLaneIntersection() --- .../Drawing/Editors/DrawPlayableAreaHelper.cs | 303 +++++++++++------- 1 file changed, 190 insertions(+), 113 deletions(-) diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawPlayableAreaHelper.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawPlayableAreaHelper.cs index 03eb4e1c..fbe8ab9e 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawPlayableAreaHelper.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawPlayableAreaHelper.cs @@ -18,6 +18,7 @@ using OngekiFumenEditor.Base.OngekiObjects.BulletPalleteEnums; using Xv2CoreLib.HslColor; using OngekiFumenEditor.Modules.FumenVisualEditor.Graphics.Drawing.TargetImpl.Lane; +using System.Windows.Documents; namespace OngekiFumenEditor.Modules.FumenVisualEditor.Graphics.Drawing.Editors { @@ -84,13 +85,13 @@ public void DrawPlayField(IFumenEditorDrawingContext target, TGrid minTGrid, TGr return; /* - 画游戏(黑色可移动)区域 - 1. 计算一组轨道,每个轨道的节点都算一个point,如果存在轨道相交,那么相交点也算point - 如果一个水平面(即y相同)存在多个轨道头尾节点,那么就会分别算point - 2. 排列 point集合, 然后简化point和补全point - 3. 将 points集合两两成线,得到线的range[minY, maxY] , 得到Y对应的轨道以及在范围range内轨道所有节点 - 4. 将左右所有的节点合并成一个多边形,渲染 - */ + 画游戏(黑色可移动)区域 + 1. 计算一组轨道,每个轨道的节点都算一个point,如果存在轨道相交,那么相交点也算point + 如果一个水平面(即y相同)存在多个轨道头尾节点,那么就会分别算point + 2. 排列 point集合, 然后简化point和补全point + 3. 将 points集合两两成线,得到线的range[minY, maxY] , 得到Y对应的轨道以及在范围range内轨道所有节点 + 4. 将左右所有的节点合并成一个多边形,渲染 + */ const long defaultLeftX = -24 * XGrid.DEFAULT_RES_X; const long defaultRightX = 24 * XGrid.DEFAULT_RES_X; @@ -114,8 +115,8 @@ void EnumeratePoints(bool isRight, List result) 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 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); @@ -125,7 +126,7 @@ void appendPoint3(List list, float px, float py, int insertIdx) { var po = list.ElementAtOrDefault(insertIdx); if (po.X == px && po.Y == py) - return; + return; var p = new Vector2(px, py); list.Insert(insertIdx, p); @@ -192,49 +193,49 @@ void appendPoint(List list, XGrid xGrid, float y) 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.Count == 0 || sortedPoints.FirstOrDefault() > minTGrid.TotalGrid) + sortedPoints.Insert(0, minTGrid.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(); - - foreach ((var fromY, var toY) in segments) - { - var midY = ((fromY + toY) / 2); + var segments = sortedPoints.SequenceConsecutivelyWrap(2).Select(x => (x.FirstOrDefault(), x.LastOrDefault())).ToArray(); + + foreach ((var fromY, var toY) in segments) + { + var midY = ((fromY + toY) / 2); var midTGrid = TGrid.FromTotalGrid((int)midY); //获取这个segement范围内要选取的轨道 - var pickables = fumen.Lanes - .GetVisibleStartObjects(midTGrid, midTGrid) - .Where(x => x.LaneType == type) - .Select(x => (x.CalulateXGrid(midTGrid), x)) - .FilterNullBy(x => x.Item1) + var pickables = fumen.Lanes + .GetVisibleStartObjects(midTGrid, midTGrid) + .Where(x => x.LaneType == type) + .Select(x => (x.CalulateXGrid(midTGrid), 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 fromTGrid = TGrid.FromTotalGrid((int)fromY); - appendPoint(result, pickLane.CalulateXGrid(fromTGrid), fromY); - - foreach (var pos in pickLane.GenAllPath().Select(x => x.pos).SkipWhile(x => x.Y < fromY).TakeWhile(x => x.Y < toY)) - appendPoint2(result, pos.X, pos.Y); - - var toTGrid = TGrid.FromTotalGrid((int)toY); - appendPoint(result, pickLane.CalulateXGrid(toTGrid), toY); - } - else - { - //选取不到轨道,表示这个segement是两个轨道之间的空白区域,那么直接填上空白就行 - appendPoint2(result, defaultX, fromY); - appendPoint2(result, defaultX, toY); - } + (_, 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); + appendPoint(result, pickLane.CalulateXGrid(fromTGrid), fromY); + + foreach (var pos in pickLane.GenAllPath().Select(x => x.pos).SkipWhile(x => x.Y < fromY).TakeWhile(x => x.Y < toY)) + appendPoint2(result, pos.X, pos.Y); + + var toTGrid = TGrid.FromTotalGrid((int)toY); + appendPoint(result, pickLane.CalulateXGrid(toTGrid), toY); + } + else + { + //选取不到轨道,表示这个segement是两个轨道之间的空白区域,那么直接填上空白就行 + appendPoint2(result, defaultX, fromY); + appendPoint2(result, defaultX, toY); + } } - //todo 解决变速过快导致的精度丢失问题 + //解决变速过快导致的精度丢失问题 Vector2? interpolate(TGrid tGrid, float actualY, out bool isPickLane) { isPickLane = false; @@ -326,71 +327,70 @@ void appendPoint(List list, XGrid xGrid, float y) } } - ObjectPool>.Return(points); - } - - using var d3 = ObjectPool>.GetWithUsingDisposable(out var points, out _); - points.Clear(); - using var d4 = ObjectPool>.GetWithUsingDisposable(out var idxList, out _); - idxList.Clear(); - - void FillPoints(List ps, bool isRight) - { - var s = points.Count / 2; - - for (var i = 0; i < ps.Count; i++) - { - var cur = ps[i]; - - //remove dup - if (points.Count >= 2) + //optimze points + for (var i = 0; i < result.Count; i++) + { + if (i > 0) { - var prevY = points[^1]; - var prevX = points[^2]; - - if (prevY == cur.Y && prevX == cur.X) - continue; + if (result[i] == result[i - 1]) + { + //remove dup + result.RemoveAt(i - 1); + i--; + } } - //optimze prev point if able - if (points.Count >= 4) + if (i > 1) { - var prevY = points[^1]; - var prevX = points[^2]; + // prev2 --- prev --- cur + var prev2 = result[i - 2]; + var prev = result[i - 1]; + var cur = result[i]; - if ((prevY == cur.Y && prevY == points[^3]) || (prevX == cur.X && prevX == points[^4])) + if ((prev.Y == cur.Y && prev.Y == prev2.Y) || (prev.X == cur.X && prev.X == prev2.X)) { - //remove - points.RemoveAt(points.Count - 1); - points.RemoveAt(points.Count - 1); + //optimze prev point if able + result.RemoveAt(i - 1); + i--; } - } - - points.Add(cur.X); - points.Add(cur.Y); + } } - } - - using var d = ObjectPool>.GetWithUsingDisposable(out var leftPoints, out _); - leftPoints.Clear(); - using var d2 = ObjectPool>.GetWithUsingDisposable(out var rightPoints, out _); + + ObjectPool>.Return(points); + } + + using var d3 = ObjectPool>.GetWithUsingDisposable(out var tessellatePoints, out _); + tessellatePoints.Clear(); + using var d4 = ObjectPool>.GetWithUsingDisposable(out var idxList, out _); + idxList.Clear(); + + using var d = ObjectPool>.GetWithUsingDisposable(out var leftPoints, out _); + leftPoints.Clear(); + using var d2 = ObjectPool>.GetWithUsingDisposable(out var rightPoints, out _); rightPoints.Clear(); - - //计算左边墙的点 - EnumeratePoints(false, leftPoints); - FillPoints(leftPoints, false); - //计算右边墙的点,因为要组合成一个多边形,所以右半部分得翻转一下顺序 - var rightPointIdx = points.Count; + + //计算左右墙的点 + EnumeratePoints(false, leftPoints); EnumeratePoints(true, rightPoints); - rightPoints.Reverse(); - FillPoints(rightPoints, true); - //todo 解决左右墙交叉处理问题 - var pointPrint = string.Join(Environment.NewLine, points.SequenceWrap(2).Select(x => $"{x.FirstOrDefault(),-20}{x.LastOrDefault()}")); + //解决左右墙交叉处理问题 + AdjustLaneIntersection(leftPoints, rightPoints); - var tessellateList = ObjectPool>.Get(); - tessellateList.Clear(); - Earcut.Tessellate(points, idxList, tessellateList); + //合并提交,准备进行三角剖分 + foreach (var pos in leftPoints) + { + tessellatePoints.Add(pos.X); + tessellatePoints.Add(pos.Y); + } + foreach (var pos in rightPoints.AsEnumerable().Reverse()) + { + tessellatePoints.Add(pos.X); + tessellatePoints.Add(pos.Y); + } + + var tessellateList = ObjectPool>.Get(); + tessellateList.Clear(); + Earcut.Tessellate(tessellatePoints, idxList, tessellateList); polygonDrawing.Begin(target, OpenTK.Graphics.OpenGL.PrimitiveType.Triangles); @@ -398,8 +398,8 @@ void FillPoints(List ps, bool isRight) { foreach (var idx in seq) { - var x = (float)points[idx * 2 + 0]; - var y = (float)points[idx * 2 + 1]; + var x = (float)tessellatePoints[idx * 2 + 0]; + var y = (float)tessellatePoints[idx * 2 + 1]; polygonDrawing.PostPoint(new(x, y), playFieldForegroundColor); } } @@ -409,13 +409,11 @@ void FillPoints(List ps, bool isRight) var rightColor = new Vector4(0, 204f / 255, 102f / 255, 0.9f); //print WallLane - lineDrawing.Draw(target, points[..rightPointIdx] - .SequenceWrap(2) - .Select(x => new LineVertex(new((float)x.First(), (float)x.Last()), leftColor, VertexDash.Solider)), 6); + lineDrawing.Draw(target, leftPoints + .Select(p => new LineVertex(p, leftColor, VertexDash.Solider)), 6); - lineDrawing.Draw(target, points[rightPointIdx..] - .SequenceWrap(2) - .Select(x => new LineVertex(new((float)x.First(), (float)x.Last()), rightColor, VertexDash.Solider)), 6); + lineDrawing.Draw(target, rightPoints + .Select(p => new LineVertex(p, rightColor, VertexDash.Solider)), 6); var i = 0; foreach (var seq in tessellateList.SequenceWrap(3)) @@ -424,28 +422,107 @@ void FillPoints(List ps, bool isRight) var color = new Vector4(r, g, b, 0.9f); lineDrawing.Draw(target, seq.Append(seq.FirstOrDefault()) - .Select(idx => new LineVertex(new((float)points[idx * 2 + 0], (float)points[idx * 2 + 1]), color, new(6, 4))), 2); - + .Select(idx => new LineVertex(new((float)tessellatePoints[idx * 2 + 0], (float)tessellatePoints[idx * 2 + 1]), color, new(6, 4))), 2); + i += 3; } - void printPoints(IEnumerable data, Vector4 color) + void printPoints(IEnumerable data, Vector4 color) { - var points = data.SequenceWrap(2) - .Select(x => new Vector2((float)x.First(), (float)x.Last())) - .ToArray(); - BeginDebugPrint(target); - points.ForEach(x => DebugPrintPoint(x, color, 10)); + data.ForEach(x => DebugPrintPoint(x, color, 10)); EndDebugPrint(); } - printPoints(points[..rightPointIdx], leftColor); - printPoints(points[rightPointIdx..], rightColor); + printPoints(leftPoints, leftColor); + printPoints(rightPoints, rightColor); #endif ObjectPool>.Return(tessellateList); } + private void AdjustLaneIntersection(List leftPoints, List rightPoints) + { + Vector2? GetIntersection(Vector2 p1, Vector2 p2, Vector2 q1, Vector2 q2) + { + var r = new Vector2(p2.X - p1.X, p2.Y - p1.Y); + var s = new Vector2(q2.X - q1.X, q2.Y - q1.Y); + + float cross_r_s = r.X * s.Y - r.Y * s.X; + + if (Math.Abs(cross_r_s) < 1e-6) + return null; + + float t = ((q1.X - p1.X) * s.Y - (q1.Y - p1.Y) * s.X) / cross_r_s; + float u = ((q1.X - p1.X) * r.Y - (q1.Y - p1.Y) * r.X) / cross_r_s; + + if (t >= 0 && t <= 1 && u >= 0 && u <= 1) + return new Vector2(p1.X + t * r.X, p1.Y + t * r.Y); + + return null; + } + + //var leftPointsStr = string.Join(Environment.NewLine, leftPoints.Select(p => $"{p.X,-20}{p.Y}")); + //var rightPointsStr = string.Join(Environment.NewLine, rightPoints.Select(p => $"{p.X,-20}{p.Y}")); + + using var d = ObjectPool>.GetWithUsingDisposable(out var tempLeft, out _); + using var d2 = ObjectPool>.GetWithUsingDisposable(out var tempRight, out _); + + if (leftPoints.FirstOrDefault().X > rightPoints.FirstOrDefault().X) + exchange(0, 0); + + var leftIdx = 1; + var rightIdx = 1; + + void exchange(int li, int ri) + { + tempLeft.Clear(); + tempRight.Clear(); + + tempLeft.AddRange(leftPoints[li..]); + tempRight.AddRange(rightPoints[ri..]); + + leftPoints.RemoveRange(li, tempLeft.Count); + rightPoints.RemoveRange(ri, tempRight.Count); + + leftPoints.AddRange(tempRight); + rightPoints.AddRange(tempLeft); + } + + while (leftIdx < leftPoints.Count && rightIdx < rightPoints.Count) + { + (Vector2 from, Vector2 to) leftLine = (leftPoints[leftIdx - 1], leftPoints[leftIdx]); + (Vector2 from, Vector2 to) rightLine = (rightPoints[rightIdx - 1], rightPoints[rightIdx]); + + if (GetIntersection(leftLine.from, leftLine.to, rightLine.from, rightLine.to) is Vector2 intersectionPoint) + { + exchange(leftIdx, rightIdx); + + leftPoints.Insert(leftIdx, intersectionPoint); + rightPoints.Insert(rightIdx, intersectionPoint); + + leftIdx++; + rightIdx++; + } + + //看看哪一边idx需要递增 + if (rightLine.from.Y <= leftLine.to.Y && leftLine.to.Y <= rightLine.to.Y) + { + //left的末端在right范围内,那么left需要递增 + leftIdx++; + continue; + } + + if (leftLine.from.Y <= rightLine.to.Y && rightLine.to.Y <= leftLine.to.Y) + { + rightIdx++; + continue; + } + + leftIdx++; + rightIdx++; + } + } + [Conditional("DEBUG")] private void DebugPrintPoint(Vector2 p, Vector4 color, int v2) {