Add Shift+Click area fill for brush tools and allow batch tile operations

This commit is contained in:
Stone_Red
2026-05-05 02:07:13 +02:00
parent 7ce2b5436f
commit 8b604070ee
2 changed files with 140 additions and 13 deletions
+9 -11
View File
@@ -21,7 +21,7 @@ internal class GridFiller
public int Width { get; }
public int Height { get; }
public GridFiller(int width, int height, List<Rectangle> obstacles)
public GridFiller(int width, int height)
{
Width = width;
Height = height;
@@ -36,8 +36,6 @@ internal class GridFiller
WeightGrid[y, x] = 1;
}
}
PlaceObstacles(obstacles);
}
public void FillGrid(int? x1 = null, int? y1 = null, int? x2 = null, int? y2 = null, bool fillAll = false, IProgress<float>? totalProgress = null, IProgress<float>? calculatingCandidatesProgress = null, IProgress<float>? placingCandidatesProgress = null)
@@ -167,7 +165,7 @@ internal class GridFiller
PlaceObstacles([rectangle]);
}
public void PlaceObstacles(IEnumerable<Rectangle> rectangles)
public void PlaceObstacles(IEnumerable<Rectangle> rectangles, bool recalculate = true)
{
lock (mutationLock)
{
@@ -207,7 +205,7 @@ internal class GridFiller
if (changed)
{
RecalculateAroundArea(minX, minY, maxX, maxY, () =>
RecalculateAroundArea(minX, minY, maxX, maxY, recalculate, () =>
{
foreach (Rectangle rectangle in clampedRectangles)
{
@@ -237,7 +235,7 @@ internal class GridFiller
RemoveObstacles([rectangle]);
}
public void RemoveObstacles(IEnumerable<Rectangle> rectangles)
public void RemoveObstacles(IEnumerable<Rectangle> rectangles, bool recalculate = true)
{
lock (mutationLock)
{
@@ -277,7 +275,7 @@ internal class GridFiller
if (changed)
{
RecalculateAroundArea(minX, minY, maxX, maxY, () =>
RecalculateAroundArea(minX, minY, maxX, maxY, recalculate, () =>
{
foreach (Rectangle rectangle in clampedRectangles)
{
@@ -310,7 +308,7 @@ internal class GridFiller
ResetTileWeights([new Point(x, y)]);
}
public void SetTileWeights(IEnumerable<Point> points, int weight)
public void SetTileWeights(IEnumerable<Point> points, int weight, bool recalculate = true)
{
lock (mutationLock)
{
@@ -337,7 +335,7 @@ internal class GridFiller
if (changed)
{
RecalculateAroundArea(minX, minY, maxX, maxY, () =>
RecalculateAroundArea(minX, minY, maxX, maxY, recalculate, () =>
{
foreach (Point point in points)
{
@@ -456,14 +454,14 @@ internal class GridFiller
return rectangle;
}
private void RecalculateAroundArea(int minX, int minY, int maxX, int maxY, Action applyChanges)
private void RecalculateAroundArea(int minX, int minY, int maxX, int maxY, bool enabled, Action applyChanges)
{
int left = int.Clamp(minX - RecalculationRadius, 0, Width);
int top = int.Clamp(minY - RecalculationRadius, 0, Height);
int right = int.Clamp(maxX + RecalculationRadius, 0, Width);
int bottom = int.Clamp(maxY + RecalculationRadius, 0, Height);
if (left >= right || top >= bottom)
if (left >= right || top >= bottom || !enabled)
{
applyChanges();
return;
@@ -52,6 +52,10 @@ public class LargeGridPathfindingGame : Game
private BrushMode brushMode = BrushMode.Weight;
private int paintWeight = 5;
private Vector2? previousMousePosition;
private bool wasShiftLeftDown;
private bool wasShiftRightDown;
private Point? fillSelectionStart;
private Point? fillSelectionCurrent;
private bool batchProcessingScheduled;
public LargeGridPathfindingGame()
@@ -147,6 +151,13 @@ public class LargeGridPathfindingGame : Game
MouseStateExtended mouseState = MouseExtended.GetState();
KeyboardStateExtended keyboardState = KeyboardExtended.GetState();
bool shiftPressed = keyboardState.IsKeyDown(Keys.LeftShift) || keyboardState.IsKeyDown(Keys.RightShift);
bool shiftLeftDown = shiftPressed && mouseState.IsButtonDown(MouseButton.Left);
bool shiftLeftClicked = shiftLeftDown && !wasShiftLeftDown;
bool shiftRightDown = shiftPressed && mouseState.IsButtonDown(MouseButton.Right);
bool shiftRightClicked = shiftRightDown && !wasShiftRightDown;
wasShiftLeftDown = shiftLeftDown;
wasShiftRightDown = shiftRightDown;
float movementSpeed = (float)Math.Pow(200, 2 - camera.Zoom);
@@ -254,6 +265,73 @@ public class LargeGridPathfindingGame : Game
});
}
if (shiftPressed)
{
if (fillSelectionStart.HasValue && TryGetMouseGridPoint(mouseState, out Point hoverGridPoint))
{
fillSelectionCurrent = hoverGridPoint;
}
else
{
fillSelectionCurrent = null;
}
if ((shiftLeftClicked || shiftRightClicked) && TryGetMouseGridPoint(mouseState, out Point clickedGridPoint))
{
if (!fillSelectionStart.HasValue)
{
fillSelectionStart = clickedGridPoint;
fillSelectionCurrent = clickedGridPoint;
}
else
{
List<Point> selectionPoints = GetRectanglePoints(fillSelectionStart.Value, clickedGridPoint);
Color indicatorColor = brushMode == BrushMode.Weight ? Color.Orange : Color.Red;
foreach (Point point in selectionPoints)
{
temporaryIndicators[point.ToVector2()] = indicatorColor;
}
PendingOperation operation;
if (brushMode == BrushMode.Weight && shiftLeftClicked)
{
operation = new PendingOperation(PendingOperationKind.SetWeight, paintWeight);
}
else if (brushMode == BrushMode.Weight && shiftRightClicked)
{
operation = new PendingOperation(PendingOperationKind.ResetWeight, 0);
}
else if (brushMode == BrushMode.Obstacle && shiftLeftClicked)
{
operation = new PendingOperation(PendingOperationKind.PlaceObstacle, 0);
}
else if (brushMode == BrushMode.Obstacle && shiftRightClicked)
{
operation = new PendingOperation(PendingOperationKind.RemoveObstacle, 0);
}
else
{
throw new InvalidOperationException("Invalid brush mode or mouse button state.");
}
EnqueuePendingOperations(selectionPoints, operation);
fillSelectionStart = null;
fillSelectionCurrent = null;
}
}
previousMousePosition = null;
base.Update(gameTime);
return;
}
else
{
fillSelectionStart = null;
fillSelectionCurrent = null;
fillSelectionCurrent = null;
}
// Paint and clear tiles via mouse input
if (mouseState.IsButtonDown(MouseButton.Left))
@@ -420,6 +498,20 @@ public class LargeGridPathfindingGame : Game
}
}
}
if (fillSelectionStart.HasValue)
{
Point selectionEnd = fillSelectionCurrent ?? fillSelectionStart.Value;
int left = Math.Min(fillSelectionStart.Value.X, selectionEnd.X);
int top = Math.Min(fillSelectionStart.Value.Y, selectionEnd.Y);
int right = Math.Max(fillSelectionStart.Value.X, selectionEnd.X);
int bottom = Math.Max(fillSelectionStart.Value.Y, selectionEnd.Y);
Rectangle selectionRectangle = new Rectangle(left * 10, top * 10, (right - left + 1) * 10, (bottom - top + 1) * 10);
spriteBatch.FillRectangle(selectionRectangle, Color.LightBlue * 0.2f, layerDepth: 0.24f);
spriteBatch.DrawRectangle(selectionRectangle, Color.Blue, 2f, layerDepth: 0.19f);
}
spriteBatch.End();
// Draw paths in a separate batch if needed
@@ -478,6 +570,7 @@ public class LargeGridPathfindingGame : Game
uiSpriteBatch.DrawString(uiFont, $"Agents: {agents.Count}", new Vector2(10, 70), Color.Black);
uiSpriteBatch.DrawString(uiFont, $"Brush mode: {brushMode} [O]", new Vector2(10, 90), Color.Black);
uiSpriteBatch.DrawString(uiFont, $"Weight: {paintWeight} [Keys 1-9, Weight mode]", new Vector2(10, 110), Color.Black);
uiSpriteBatch.DrawString(uiFont, "Area fill: hold Shift + left click twice", new Vector2(10, 130), Color.Black);
IReadOnlyList<ProgressTracker.ProgressData> progresses = progressTracker.GetProgresses();
@@ -487,7 +580,7 @@ public class LargeGridPathfindingGame : Game
string progress = progressData.Indeterminate ? "..." : progressData.Progress.ToString("P0");
uiSpriteBatch.DrawString(uiFont, $"{progressData.Name}: {progress}", new Vector2(10, 130 + (i * 20)), Color.Black);
uiSpriteBatch.DrawString(uiFont, $"{progressData.Name}: {progress}", new Vector2(10, 150 + (i * 20)), Color.Black);
}
uiSpriteBatch.End();
@@ -592,7 +685,8 @@ public class LargeGridPathfindingGame : Game
Debug.WriteLine("Initializing grid...");
ProgressTracker.ProgressData progressData = progressTracker.AddProgress("Initializing grid", true, out _);
gridFiller = new GridFiller(width, height, obstacles);
gridFiller = new GridFiller(width, height);
gridFiller.PlaceObstacles(obstacles, false);
progressTracker.RemoveProgress(progressData);
Debug.WriteLine("Filling grid...");
@@ -769,6 +863,41 @@ public class LargeGridPathfindingGame : Game
}
}
private bool TryGetMouseGridPoint(MouseStateExtended mouseState, out Point gridPoint)
{
Vector2 mousePosition = camera.ScreenToWorld(mouseState.Position.ToVector2());
Point point = new Point((int)mousePosition.X / 10, (int)mousePosition.Y / 10);
if (point.X < 0 || point.X >= gridFiller.Width || point.Y < 0 || point.Y >= gridFiller.Height)
{
gridPoint = default;
return false;
}
gridPoint = point;
return true;
}
private static List<Point> GetRectanglePoints(Point start, Point end)
{
int minX = Math.Min(start.X, end.X);
int minY = Math.Min(start.Y, end.Y);
int maxX = Math.Max(start.X, end.X);
int maxY = Math.Max(start.Y, end.Y);
List<Point> points = new((maxX - minX + 1) * (maxY - minY + 1));
for (int y = minY; y <= maxY; y++)
{
for (int x = minX; x <= maxX; x++)
{
points.Add(new Point(x, y));
}
}
return points;
}
private static List<Point> GetBrushPoints(Vector2 currentGridPosition, Vector2? previousGridPosition)
{
HashSet<Point> points = [];