diff --git a/OpenRA.Game/Map/CellCoordsRegion.cs b/OpenRA.Game/Map/CellCoordsRegion.cs index 6ec0d928b2..2f6914f2c8 100644 --- a/OpenRA.Game/Map/CellCoordsRegion.cs +++ b/OpenRA.Game/Map/CellCoordsRegion.cs @@ -64,6 +64,11 @@ namespace OpenRA BottomRight = bottomRight; } + public bool Contains(CPos cell) + { + return cell.X >= TopLeft.X && cell.X <= BottomRight.X && cell.Y >= TopLeft.Y && cell.Y <= BottomRight.Y; + } + public override string ToString() { return $"{TopLeft}->{BottomRight}"; diff --git a/OpenRA.Mods.Cnc/Traits/World/TSEditorResourceLayer.cs b/OpenRA.Mods.Cnc/Traits/World/TSEditorResourceLayer.cs index 3b3e7ed294..3bf3b92e9a 100644 --- a/OpenRA.Mods.Cnc/Traits/World/TSEditorResourceLayer.cs +++ b/OpenRA.Mods.Cnc/Traits/World/TSEditorResourceLayer.cs @@ -46,7 +46,7 @@ namespace OpenRA.Mods.Cnc.Traits return false; // Cell is automatically valid if it contains a veinhole actor - if (actorLayer.PreviewsAt(neighbour).Any(a => info.VeinholeActors.Contains(a.Info.Name))) + if (actorLayer.PreviewsAtCell(neighbour).Any(a => info.VeinholeActors.Contains(a.Info.Name))) return true; // Neighbour must be flat or a cardinal slope, unless the resource cell itself is a slope diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs index 1c5bc12052..23de19fe38 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs @@ -165,7 +165,7 @@ namespace OpenRA.Mods.Common.Widgets tiles.Add(cell, new ClipboardTile(mapTiles[cell], mapResources[cell], resourceLayerContents, mapHeight[cell])); if (copyFilters.HasFlag(MapCopyFilters.Actors)) - foreach (var preview in selection.CellCoords.SelectMany(editorActorLayer.PreviewsAt).Distinct()) + foreach (var preview in editorActorLayer.PreviewsInCellRegion(selection.CellCoords)) previews.TryAdd(preview.ID, preview); } @@ -187,7 +187,7 @@ namespace OpenRA.Mods.Common.Widgets // Clear any existing actors in the paste cells. var selectionSize = clipboard.CellRegion.BottomRight - clipboard.CellRegion.TopLeft; var pasteRegion = new CellRegion(map.Grid.Type, pastePosition, pastePosition + selectionSize); - foreach (var regionActor in pasteRegion.CellCoords.SelectMany(editorActorLayer.PreviewsAt).ToHashSet()) + foreach (var regionActor in editorActorLayer.PreviewsInCellRegion(pasteRegion.CellCoords).ToList()) editorActorLayer.Remove(regionActor); } @@ -244,7 +244,7 @@ namespace OpenRA.Mods.Common.Widgets if (copyFilters.HasFlag(MapCopyFilters.Actors)) { // Clear existing actors. - foreach (var regionActor in undoClipboard.CellRegion.CellCoords.SelectMany(editorActorLayer.PreviewsAt).Distinct().ToList()) + foreach (var regionActor in editorActorLayer.PreviewsInCellRegion(undoClipboard.CellRegion.CellCoords).ToList()) editorActorLayer.Remove(regionActor); } diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs index 612babc24b..ba3decbc12 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs @@ -120,7 +120,7 @@ namespace OpenRA.Mods.Common.Widgets worldPixel = worldRenderer.Viewport.ViewToWorldPx(mi.Location); var cell = worldRenderer.Viewport.ViewToWorld(mi.Location); - var underCursor = editorLayer.PreviewsAt(worldPixel).MinByOrDefault(CalculateActorSelectionPriority); + var underCursor = editorLayer.PreviewsAtWorldPixel(worldPixel).MinByOrDefault(CalculateActorSelectionPriority); var resourceUnderCursor = resourceLayer?.GetResource(cell).Type; if (underCursor != null) diff --git a/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs index e1acae47be..0b730c0e45 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs @@ -40,8 +40,9 @@ namespace OpenRA.Mods.Common.Traits { readonly EditorActorLayerInfo info; readonly List previews = new(); - readonly Dictionary> cellMap = new(); + int2 cellOffset; + SpatiallyPartitioned cellMap; SpatiallyPartitioned screenMap; WorldRenderer worldRenderer; @@ -74,6 +75,12 @@ namespace OpenRA.Mods.Common.Traits foreach (var pr in Players.Players.Values) wr.UpdatePalettesForPlayer(pr.Name, pr.Color, false); + cellOffset = new int2(world.Map.AllCells.Min(c => c.X), world.Map.AllCells.Min((c) => c.Y)); + var cellOffsetMax = new int2(world.Map.AllCells.Max(c => c.X), world.Map.AllCells.Max((c) => c.Y)); + var mapCellSize = cellOffsetMax - cellOffset; + cellMap = new SpatiallyPartitioned( + mapCellSize.X, mapCellSize.Y, Exts.IntegerDivisionRoundingAwayFromZero(info.BinSize, world.Map.Grid.TileSize.Width)); + var ts = world.Map.Grid.TileSize; var width = world.Map.MapSize.X * ts.Width; var height = world.Map.MapSize.Y * ts.Height; @@ -102,7 +109,7 @@ namespace OpenRA.Mods.Common.Traits if (wr.World.Type != WorldType.Editor) return NoRenderables; - return PreviewsInBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight) + return PreviewsInScreenBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight) .SelectMany(p => p.Render()); } @@ -117,7 +124,7 @@ namespace OpenRA.Mods.Common.Traits if (wr.World.Type != WorldType.Editor) return NoRenderables; - return PreviewsInBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight) + return PreviewsInScreenBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight) .SelectMany(p => p.RenderAnnotations()); } @@ -147,13 +154,9 @@ namespace OpenRA.Mods.Common.Traits if (!preview.Bounds.IsEmpty) screenMap.Add(preview, preview.Bounds); - // Fallback to the actor's CenterPosition for the ActorMap if it has no Footprint - var footprint = preview.Footprint.Select(kv => kv.Key).ToArray(); - if (footprint.Length == 0) - footprint = new[] { worldRenderer.World.Map.CellContaining(preview.CenterPosition) }; - - foreach (var cell in footprint) - AddPreviewLocation(preview, cell); + var cellFootprintBounds = Footprint(preview).Select( + cell => new Rectangle(cell.X - cellOffset.X, cell.Y - cellOffset.Y, 1, 1)).Union(); + cellMap.Add(preview, cellFootprintBounds); preview.AddedToEditor(); @@ -166,26 +169,20 @@ namespace OpenRA.Mods.Common.Traits } } + IEnumerable Footprint(EditorActorPreview preview) + { + // Fallback to the actor's CenterPosition for the ActorMap if it has no Footprint + if (preview.Footprint.Count == 0) + return new[] { worldRenderer.World.Map.CellContaining(preview.CenterPosition) }; + return preview.Footprint.Keys; + } + public void Remove(EditorActorPreview preview) { previews.Remove(preview); screenMap.Remove(preview); - // Fallback to the actor's CenterPosition for the ActorMap if it has no Footprint - var footprint = preview.Footprint.Select(kv => kv.Key).ToArray(); - if (footprint.Length == 0) - footprint = new[] { worldRenderer.World.Map.CellContaining(preview.CenterPosition) }; - - foreach (var cell in footprint) - { - if (!cellMap.TryGetValue(cell, out var list)) - continue; - - list.Remove(preview); - - if (list.Count == 0) - cellMap.Remove(cell); - } + cellMap.Remove(preview); preview.RemovedFromEditor(); UpdateNeighbours(preview.Footprint); @@ -236,49 +233,46 @@ namespace OpenRA.Mods.Common.Traits { // Include actors inside the footprint too var cells = Util.ExpandFootprint(footprint.Keys, true); - foreach (var p in cells.SelectMany(c => PreviewsAt(c))) + foreach (var p in cells.SelectMany(PreviewsAtCell)) p.ReplaceInit(new RuntimeNeighbourInit(NeighbouringPreviews(p.Footprint))); } - void AddPreviewLocation(EditorActorPreview preview, CPos location) - { - if (!cellMap.TryGetValue(location, out var list)) - { - list = new List(); - cellMap.Add(location, list); - } - - list.Add(preview); - } - Dictionary NeighbouringPreviews(IReadOnlyDictionary footprint) { var cells = Util.ExpandFootprint(footprint.Keys, true).Except(footprint.Keys); - return cells.ToDictionary(c => c, c => PreviewsAt(c).Select(p => p.Info.Name).ToArray()); + return cells.ToDictionary(c => c, c => PreviewsAtCell(c).Select(p => p.Info.Name).ToArray()); } - public IEnumerable PreviewsInBox(int2 a, int2 b) + public IEnumerable PreviewsInScreenBox(int2 a, int2 b) { - return screenMap.InBox(Rectangle.FromLTRB(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Max(a.X, b.X), Math.Max(a.Y, b.Y))); + return PreviewsInScreenBox(Rectangle.FromLTRB(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Max(a.X, b.X), Math.Max(a.Y, b.Y))); } - public IEnumerable PreviewsInBox(Rectangle r) + public IEnumerable PreviewsInScreenBox(Rectangle r) { return screenMap.InBox(r); } - public IEnumerable PreviewsAt(CPos cell) + public IEnumerable PreviewsInCellRegion(CellCoordsRegion region) { - if (cellMap.TryGetValue(cell, out var list)) - return list; + return cellMap.InBox(Rectangle.FromLTRB( + region.TopLeft.X - cellOffset.X, + region.TopLeft.Y - cellOffset.Y, + region.BottomRight.X - cellOffset.X + 1, + region.BottomRight.Y - cellOffset.Y + 1)) + .Where(p => Footprint(p).Any(region.Contains)); + } - return Enumerable.Empty(); + public IEnumerable PreviewsAtCell(CPos cell) + { + return cellMap.At(new int2(cell.X - cellOffset.X, cell.Y - cellOffset.Y)) + .Where(p => Footprint(p).Any(fp => fp == cell)); } public SubCell FreeSubCellAt(CPos cell) { var map = worldRenderer.World.Map; - var previews = PreviewsAt(cell).ToList(); + var previews = PreviewsAtCell(cell).ToList(); if (previews.Count == 0) return map.Grid.DefaultSubCell; @@ -293,7 +287,7 @@ namespace OpenRA.Mods.Common.Traits return SubCell.Invalid; } - public IEnumerable PreviewsAt(int2 worldPx) + public IEnumerable PreviewsAtWorldPixel(int2 worldPx) { return screenMap.At(worldPx); } @@ -325,9 +319,9 @@ namespace OpenRA.Mods.Common.Traits public void PopulateRadarSignatureCells(Actor self, List<(CPos Cell, Color Color)> destinationBuffer) { - foreach (var previewsForCell in cellMap) - foreach (var preview in previewsForCell.Value) - destinationBuffer.Add((previewsForCell.Key, preview.RadarColor)); + foreach (var preview in cellMap.Items) + foreach (var cell in Footprint(preview)) + destinationBuffer.Add((cell, preview.RadarColor)); } public EditorActorPreview this[string id] diff --git a/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs b/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs index 0613a45362..fcef483161 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs @@ -68,8 +68,8 @@ namespace OpenRA.Mods.Common.Traits if (!world.Map.Rules.Actors.TryGetValue(reference.Type.ToLowerInvariant(), out Info)) throw new InvalidDataException($"Actor {id} of unknown type {reference.Type.ToLowerInvariant()}"); - UpdateFromCellChange(); GenerateFootprint(); + UpdateFromCellChange(null); tooltip = Info.TraitInfos().FirstOrDefault(info => info.EnabledByDefault) as TooltipInfoBase ?? Info.TraitInfos().FirstOrDefault(info => info.EnabledByDefault); @@ -79,8 +79,7 @@ namespace OpenRA.Mods.Common.Traits terrainRadarColorInfo = Info.TraitInfoOrDefault(); UpdateRadarColor(); - // TODO: updating all actors on the map is not very efficient. - onCellEntryChanged = _ => UpdateFromCellChange(); + onCellEntryChanged = cell => UpdateFromCellChange(cell); } public EditorActorPreview WithId(string id) @@ -88,8 +87,11 @@ namespace OpenRA.Mods.Common.Traits return new EditorActorPreview(worldRenderer, id, reference.Clone(), Owner); } - void UpdateFromCellChange() + void UpdateFromCellChange(CPos? cellChanged) { + if (cellChanged != null && !Footprint.ContainsKey(cellChanged.Value)) + return; + CenterPosition = PreviewPosition(worldRenderer.World, reference); GeneratePreviews(); GenerateBounds(); @@ -169,8 +171,8 @@ namespace OpenRA.Mods.Common.Traits foreach (var notify in Info.TraitInfos()) editorData[notify] = notify.AddedToEditor(this, worldRenderer.World); - // TODO: this should subscribe to ramp cell map as well. worldRenderer.World.Map.Height.CellEntryChanged += onCellEntryChanged; + worldRenderer.World.Map.Ramp.CellEntryChanged += onCellEntryChanged; } public void RemovedFromEditor() @@ -179,6 +181,7 @@ namespace OpenRA.Mods.Common.Traits kv.Key.RemovedFromEditor(this, worldRenderer.World, kv.Value); worldRenderer.World.Map.Height.CellEntryChanged -= onCellEntryChanged; + worldRenderer.World.Map.Ramp.CellEntryChanged -= onCellEntryChanged; } public void AddInit(T init) where T : ActorInit diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorSelectionLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorSelectionLogic.cs index 2d7432201b..d2c73a3d2e 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorSelectionLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorSelectionLogic.cs @@ -11,7 +11,6 @@ using System; using System.Collections.Generic; -using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.EditorBrushes; using OpenRA.Mods.Common.Traits; @@ -123,7 +122,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic tiles.Add(cell, new ClipboardTile(mapTiles[cell], mapResources[cell], resourceLayer?.GetResource(cell), mapHeight[cell])); if (copyFilters.HasFlag(MapCopyFilters.Actors)) - foreach (var preview in selection.CellCoords.SelectMany(editorActorLayer.PreviewsAt).Distinct()) + foreach (var preview in editorActorLayer.PreviewsInCellRegion(selection.CellCoords)) previews.TryAdd(preview.ID, preview); }