Implement TS-style terrain lighting.
This commit is contained in:
67
OpenRA.Mods.Common/Traits/TerrainLightSource.cs
Normal file
67
OpenRA.Mods.Common/Traits/TerrainLightSource.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
#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 OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Adds a localized circular light centered on the actor to the world's TerrainLightSource trait.")]
|
||||
public class TerrainLightSourceInfo : TraitInfo, INotifyEditorPlacementInfo, IRulesetLoaded, ILobbyCustomRulesIgnore
|
||||
{
|
||||
public readonly WDist Range = WDist.FromCells(10);
|
||||
public readonly float Intensity = 0;
|
||||
public readonly float RedTint = 0;
|
||||
public readonly float GreenTint = 0;
|
||||
public readonly float BlueTint = 0;
|
||||
|
||||
object INotifyEditorPlacementInfo.AddedToEditor(EditorActorPreview preview, World editorWorld)
|
||||
{
|
||||
var tint = new float3(RedTint, GreenTint, BlueTint);
|
||||
return editorWorld.WorldActor.Trait<TerrainLighting>().AddLightSource(preview.CenterPosition, Range, Intensity, tint);
|
||||
}
|
||||
|
||||
void INotifyEditorPlacementInfo.RemovedFromEditor(EditorActorPreview preview, World editorWorld, object data)
|
||||
{
|
||||
editorWorld.WorldActor.Trait<TerrainLighting>().RemoveLightSource((int)data);
|
||||
}
|
||||
|
||||
public void RulesetLoaded(Ruleset rules, ActorInfo ai)
|
||||
{
|
||||
if (!rules.Actors["world"].HasTraitInfo<TerrainLightingInfo>())
|
||||
throw new YamlException("TerrainLightSource can only be used with the world TerrainLighting trait.");
|
||||
}
|
||||
|
||||
public override object Create(ActorInitializer init) { return new TerrainLightSource(init.Self, this); }
|
||||
}
|
||||
|
||||
public sealed class TerrainLightSource : INotifyAddedToWorld, INotifyRemovedFromWorld
|
||||
{
|
||||
readonly TerrainLightSourceInfo info;
|
||||
readonly TerrainLighting terrainLighting;
|
||||
int lightingToken = -1;
|
||||
|
||||
public TerrainLightSource(Actor self, TerrainLightSourceInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
terrainLighting = self.World.WorldActor.Trait<TerrainLighting>();
|
||||
}
|
||||
|
||||
void INotifyAddedToWorld.AddedToWorld(Actor self)
|
||||
{
|
||||
lightingToken = terrainLighting.AddLightSource(self.CenterPosition, info.Range, info.Intensity, new float3(info.RedTint, info.GreenTint, info.BlueTint));
|
||||
}
|
||||
|
||||
void INotifyRemovedFromWorld.RemovedFromWorld(Actor self)
|
||||
{
|
||||
terrainLighting.RemoveLightSource(lightingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,8 +292,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (fogSprite != null)
|
||||
fogPos += fogSprite.Offset - 0.5f * fogSprite.Size;
|
||||
|
||||
shroudLayer.Update(uv, shroudSprite, shroudPos);
|
||||
fogLayer.Update(uv, fogSprite, fogPos);
|
||||
shroudLayer.Update(uv, shroudSprite, shroudPos, true);
|
||||
fogLayer.Update(uv, fogSprite, fogPos, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
var sprite = theater.TileSprite(tile);
|
||||
foreach (var kv in spriteLayers)
|
||||
kv.Value.Update(cell, palette == kv.Key ? sprite : null);
|
||||
kv.Value.Update(cell, palette == kv.Key ? sprite : null, false);
|
||||
}
|
||||
|
||||
void IRenderTerrain.RenderTerrain(WorldRenderer wr, Viewport viewport)
|
||||
|
||||
Reference in New Issue
Block a user