Files
OpenRA/OpenRA.Mods.Common/Traits/World/TerrainLighting.cs
RoosterDragon 36660b89e9 Silence IDE0290.
This rule recommends use of primary constructors. Apply the suggestions on simple POCOs, adjusting some to readonly/structs/records at the same time. For more complex classes, the use of primary constructors is more distracting than helpful, so silence the rule. IDEs can still offer fixes for using primary constructors, but it will not show up as a build issue.
2025-04-02 10:18:32 +03:00

133 lines
4.0 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 OpenRA.Primitives;
using OpenRA.Support;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
[Desc("Add to the world actor to apply a global lighting tint and allow actors using the TerrainLightSource to add localised lighting.")]
public class TerrainLightingInfo : TraitInfo, ILobbyCustomRulesIgnore
{
public readonly float Intensity = 1;
public readonly float HeightStep = 0;
public readonly float RedTint = 1;
public readonly float GreenTint = 1;
public readonly float BlueTint = 1;
[Desc("Size of light source partition bins (cells)")]
public readonly int BinSize = 10;
public override object Create(ActorInitializer init) { return new TerrainLighting(init.World, this); }
}
public sealed class TerrainLighting : ITerrainLighting
{
sealed class LightSource(WPos pos, CPos cell, WDist range, float intensity, in float3 tint)
{
public readonly WPos Pos = pos;
public readonly CPos Cell = cell;
public readonly WDist Range = range;
public readonly float Intensity = intensity;
public readonly float3 Tint = tint;
}
readonly TerrainLightingInfo info;
readonly Map map;
readonly Dictionary<int, LightSource> lightSources = [];
readonly SpatiallyPartitioned<LightSource> partitionedLightSources;
readonly float3 globalTint;
int nextLightSourceToken = 1;
public event Action<MPos> CellChanged = null;
public TerrainLighting(World world, TerrainLightingInfo info)
{
this.info = info;
map = world.Map;
globalTint = new float3(info.RedTint, info.GreenTint, info.BlueTint);
var tileScale = map.Grid.TileScale;
partitionedLightSources = new SpatiallyPartitioned<LightSource>(
(map.MapSize.X + 1) * tileScale,
(map.MapSize.Y + 1) * tileScale,
info.BinSize * tileScale);
}
static Rectangle Bounds(LightSource source)
{
var c = source.Pos;
var r = source.Range.Length;
return new Rectangle(c.X - r, c.Y - r, 2 * r, 2 * r);
}
public int AddLightSource(WPos pos, WDist range, float intensity, in float3 tint)
{
var token = nextLightSourceToken++;
var source = new LightSource(pos, map.CellContaining(pos), range, intensity, tint);
var bounds = Bounds(source);
lightSources.Add(token, source);
partitionedLightSources.Add(source, bounds);
if (CellChanged != null)
foreach (var c in map.FindTilesInCircle(source.Cell, (source.Range.Length + 1023) / 1024))
CellChanged(c.ToMPos(map));
return token;
}
public void RemoveLightSource(int token)
{
if (!lightSources.TryGetValue(token, out var source))
return;
lightSources.Remove(token);
partitionedLightSources.Remove(source);
if (CellChanged != null)
foreach (var c in map.FindTilesInCircle(source.Cell, (source.Range.Length + 1023) / 1024))
CellChanged(c.ToMPos(map));
}
float3 ITerrainLighting.TintAt(WPos pos)
{
using (new PerfSample("terrain_lighting"))
{
var uv = map.CellContaining(pos).ToMPos(map);
var tint = globalTint;
if (!map.Height.Contains(uv))
return tint;
var intensity = info.Intensity + info.HeightStep * map.Height[uv];
if (lightSources.Count > 0)
{
foreach (var source in partitionedLightSources.At(new int2(pos.X, pos.Y)))
{
var range = source.Range.Length;
var distance = (source.Pos - pos).Length;
if (distance > range)
continue;
var falloff = (range - distance) * 1f / range;
intensity += falloff * source.Intensity;
tint += falloff * source.Tint;
}
}
return intensity * tint;
}
}
}
}