Implement TS-style terrain lighting.
This commit is contained in:
141
OpenRA.Mods.Common/Traits/TerrainLighting.cs
Normal file
141
OpenRA.Mods.Common/Traits/TerrainLighting.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 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, 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, 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)
|
||||
{
|
||||
LightSource source;
|
||||
if (!lightSources.TryGetValue(token, out 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user