141 lines
4.1 KiB
C#
141 lines
4.1 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
|
|
* 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
|
|
{
|
|
[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
|
|
{
|
|
class LightSource
|
|
{
|
|
public readonly WPos Pos;
|
|
public readonly CPos Cell;
|
|
public readonly WDist Range;
|
|
public readonly float Intensity;
|
|
public readonly float3 Tint;
|
|
|
|
public LightSource(WPos pos, CPos cell, WDist range, float intensity, in float3 tint)
|
|
{
|
|
Pos = pos;
|
|
Cell = cell;
|
|
Range = range;
|
|
Intensity = intensity;
|
|
Tint = tint;
|
|
}
|
|
}
|
|
|
|
readonly TerrainLightingInfo info;
|
|
readonly Map map;
|
|
readonly Dictionary<int, LightSource> lightSources = new Dictionary<int, LightSource>();
|
|
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 cellSize = map.Grid.Type == MapGridType.RectangularIsometric ? 1448 : 1024;
|
|
partitionedLightSources = new SpatiallyPartitioned<LightSource>(
|
|
(map.MapSize.X + 1) * cellSize,
|
|
(map.MapSize.Y + 1) * cellSize,
|
|
info.BinSize * cellSize);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|