Files
OpenRA/OpenRA.Game/Traits/World/ScreenMap.cs
let5sne.win10 9cf6ebb986
Some checks failed
Continuous Integration / Linux (.NET 8.0) (push) Has been cancelled
Continuous Integration / Windows (.NET 8.0) (push) Has been cancelled
Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 21:46:54 +08:00

288 lines
8.8 KiB
C#

#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Effects;
using OpenRA.Graphics;
using OpenRA.Primitives;
namespace OpenRA.Traits
{
public readonly struct ActorBoundsPair(Actor actor, Polygon bounds)
{
public readonly Actor Actor = actor;
public readonly Polygon Bounds = bounds;
public override int GetHashCode() { return Actor.GetHashCode() ^ Bounds.GetHashCode(); }
public override string ToString() { return $"{Actor.Info.Name}->{Bounds.GetType().Name}"; }
}
[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
public class ScreenMapInfo : TraitInfo
{
[Desc("Size of partition bins (world pixels)")]
public readonly int BinSize = 250;
public override object Create(ActorInitializer init) { return new ScreenMap(init.World, this); }
}
public class ScreenMap : IWorldLoaded
{
static readonly IEnumerable<FrozenActor> NoFrozenActors = [];
readonly Func<FrozenActor, bool> frozenActorIsValid = fa => fa.IsValid;
readonly Func<Actor, bool> actorIsInWorld = a => a.IsInWorld;
readonly Func<Actor, ActorBoundsPair> selectActorAndBounds;
readonly Cache<Player, SpatiallyPartitioned<FrozenActor>> partitionedMouseFrozenActors;
readonly SpatiallyPartitioned<Actor> partitionedMouseActors;
readonly Dictionary<Actor, ActorBoundsPair> partitionedMouseActorBounds = [];
readonly Cache<Player, SpatiallyPartitioned<FrozenActor>> partitionedRenderableFrozenActors;
readonly SpatiallyPartitioned<Actor> partitionedRenderableActors;
readonly SpatiallyPartitioned<IEffect> partitionedRenderableEffects;
// Updates are done in one pass to ensure all bound changes have been applied
readonly HashSet<Actor> addOrUpdateActors = [];
readonly HashSet<Actor> removeActors = [];
readonly Cache<Player, HashSet<FrozenActor>> addOrUpdateFrozenActors;
readonly Cache<Player, HashSet<FrozenActor>> removeFrozenActors;
WorldRenderer worldRenderer;
public ScreenMap(World world, ScreenMapInfo info)
{
var size = world.Map.Rules.TerrainInfo.TileSize;
var width = world.Map.MapSize.Width * size.Width;
var height = world.Map.MapSize.Height * size.Height;
partitionedMouseFrozenActors = new Cache<Player, SpatiallyPartitioned<FrozenActor>>(
_ => new SpatiallyPartitioned<FrozenActor>(width, height, info.BinSize));
partitionedMouseActors = new SpatiallyPartitioned<Actor>(width, height, info.BinSize);
selectActorAndBounds = a => partitionedMouseActorBounds[a];
partitionedRenderableFrozenActors = new Cache<Player, SpatiallyPartitioned<FrozenActor>>(
_ => new SpatiallyPartitioned<FrozenActor>(width, height, info.BinSize));
partitionedRenderableActors = new SpatiallyPartitioned<Actor>(width, height, info.BinSize);
partitionedRenderableEffects = new SpatiallyPartitioned<IEffect>(width, height, info.BinSize);
addOrUpdateFrozenActors = new Cache<Player, HashSet<FrozenActor>>(_ => []);
removeFrozenActors = new Cache<Player, HashSet<FrozenActor>>(_ => []);
}
public void WorldLoaded(World w, WorldRenderer wr) { worldRenderer = wr; }
public void AddOrUpdate(Player viewer, FrozenActor fa)
{
removeFrozenActors[viewer].Remove(fa);
addOrUpdateFrozenActors[viewer].Add(fa);
}
public void Remove(Player viewer, FrozenActor fa)
{
removeFrozenActors[viewer].Add(fa);
}
public void AddOrUpdate(Actor a)
{
removeActors.Remove(a);
addOrUpdateActors.Add(a);
}
public void Remove(Actor a)
{
removeActors.Add(a);
}
public void Add(IEffect effect, WPos position, Size size)
{
var screenPos = worldRenderer.ScreenPxPosition(position);
var screenWidth = Math.Abs(size.Width);
var screenHeight = Math.Abs(size.Height);
var screenBounds = new Rectangle(screenPos.X - screenWidth / 2, screenPos.Y - screenHeight / 2, screenWidth, screenHeight);
if (ValidBounds(screenBounds))
partitionedRenderableEffects.Add(effect, screenBounds);
}
public void Add(IEffect effect, WPos position, Sprite sprite)
{
var size = new Size((int)sprite.Size.X, (int)sprite.Size.Y);
Add(effect, position, size);
}
public void Update(IEffect effect, WPos position, Size size)
{
Remove(effect);
Add(effect, position, size);
}
public void Update(IEffect effect, WPos position, Sprite sprite)
{
var size = new Size((int)sprite.Size.X, (int)sprite.Size.Y);
Update(effect, position, size);
}
public void Remove(IEffect effect)
{
partitionedRenderableEffects.Remove(effect);
}
static bool ValidBounds(Rectangle bounds)
{
return bounds.Width > 0 && bounds.Height > 0;
}
public IEnumerable<FrozenActor> FrozenActorsAtMouse(Player viewer, int2 worldPx)
{
if (viewer == null)
return NoFrozenActors;
return partitionedMouseFrozenActors[viewer]
.At(worldPx)
.Where(frozenActorIsValid)
.Where(x => x.MouseBounds.Contains(worldPx));
}
public IEnumerable<FrozenActor> FrozenActorsAtMouse(Player viewer, MouseInput mi)
{
return FrozenActorsAtMouse(viewer, worldRenderer.Viewport.ViewToWorldPx(mi.Location));
}
public IEnumerable<ActorBoundsPair> ActorsAtMouse(int2 worldPx)
{
return partitionedMouseActors.At(worldPx)
.Where(actorIsInWorld)
.Select(selectActorAndBounds)
.Where(x => x.Bounds.Contains(worldPx));
}
public IEnumerable<ActorBoundsPair> ActorsAtMouse(MouseInput mi)
{
return ActorsAtMouse(worldRenderer.Viewport.ViewToWorldPx(mi.Location));
}
static Rectangle RectWithCorners(int2 a, int2 b)
{
return 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<ActorBoundsPair> ActorsInMouseBox(int2 a, int2 b)
{
return ActorsInMouseBox(RectWithCorners(a, b));
}
public IEnumerable<ActorBoundsPair> ActorsInMouseBox(Rectangle r)
{
return partitionedMouseActors.InBox(r)
.Where(actorIsInWorld)
.Select(selectActorAndBounds)
.Where(x => x.Bounds.IntersectsWith(r));
}
public IEnumerable<Actor> RenderableActorsInBox(int2 a, int2 b)
{
return partitionedRenderableActors.InBox(RectWithCorners(a, b)).Where(actorIsInWorld);
}
public IEnumerable<IEffect> RenderableEffectsInBox(int2 a, int2 b)
{
return partitionedRenderableEffects.InBox(RectWithCorners(a, b));
}
public IEnumerable<FrozenActor> RenderableFrozenActorsInBox(Player p, int2 a, int2 b)
{
if (p == null)
return NoFrozenActors;
return partitionedRenderableFrozenActors[p].InBox(RectWithCorners(a, b)).Where(frozenActorIsValid);
}
public void TickRender()
{
foreach (var a in addOrUpdateActors)
{
var mouseBounds = a.MouseBounds(worldRenderer);
if (!mouseBounds.IsEmpty)
{
partitionedMouseActors[a] = mouseBounds.BoundingRect;
partitionedMouseActorBounds[a] = new ActorBoundsPair(a, mouseBounds);
}
else
partitionedMouseActors.Remove(a);
var screenBounds = a.ScreenBounds(worldRenderer).Union();
if (!screenBounds.Size.IsEmpty)
partitionedRenderableActors[a] = screenBounds;
else
partitionedRenderableActors.Remove(a);
}
foreach (var a in removeActors)
{
partitionedMouseActors.Remove(a);
partitionedMouseActorBounds.Remove(a);
partitionedRenderableActors.Remove(a);
}
addOrUpdateActors.Clear();
removeActors.Clear();
foreach (var kv in addOrUpdateFrozenActors)
{
foreach (var fa in kv.Value)
{
var mouseBounds = fa.MouseBounds;
if (!mouseBounds.IsEmpty)
partitionedMouseFrozenActors[kv.Key][fa] = mouseBounds.BoundingRect;
else
partitionedMouseFrozenActors[kv.Key].Remove(fa);
var screenBounds = fa.ScreenBounds.Union();
if (!screenBounds.Size.IsEmpty)
partitionedRenderableFrozenActors[kv.Key][fa] = screenBounds;
else
partitionedRenderableFrozenActors[kv.Key].Remove(fa);
}
kv.Value.Clear();
}
foreach (var kv in removeFrozenActors)
{
foreach (var fa in kv.Value)
{
partitionedMouseFrozenActors[kv.Key].Remove(fa);
partitionedRenderableFrozenActors[kv.Key].Remove(fa);
}
kv.Value.Clear();
}
}
public IEnumerable<Rectangle> RenderBounds(Player viewer)
{
var bounds = partitionedRenderableActors.Values
.Concat(partitionedRenderableEffects.Values);
return viewer != null ? bounds.Concat(partitionedRenderableFrozenActors[viewer].Values) : bounds;
}
public IEnumerable<Polygon> MouseBounds(Player viewer)
{
var bounds = partitionedMouseActorBounds.Values.Select(a => a.Bounds);
return viewer != null ? bounds.Concat(partitionedMouseFrozenActors[viewer].Keys.Select(fa => fa.MouseBounds)) : bounds;
}
}
}