Merge pull request #4287 from pchote/shroud

Rewrite shroud renderer.
This commit is contained in:
Matthias Mailänder
2013-12-11 01:28:22 -08:00
25 changed files with 437 additions and 265 deletions

View File

@@ -17,6 +17,7 @@ NEW:
Added a new hotkey to select all units on screen (default: CTRL + A). Added a new hotkey to select all units on screen (default: CTRL + A).
Added a new hotkey to jump to production buildings (default: TAB). Added a new hotkey to jump to production buildings (default: TAB).
Changed default hotkey (PageUp/Down) for build palette cycling and made reverse user configurable. Changed default hotkey (PageUp/Down) for build palette cycling and made reverse user configurable.
Improved shroud/fog rendering.
Asset Browser: Asset Browser:
Fixed crashes when trying to load invalid filenames or sprites with just 1 frame. Fixed crashes when trying to load invalid filenames or sprites with just 1 frame.
Added support for all sprite types. Added support for all sprite types.
@@ -73,6 +74,7 @@ NEW:
Disabled the main menu target reticle showing when a window is open. Disabled the main menu target reticle showing when a window is open.
Added a display of the faction logos when the shellmap is disabled. Added a display of the faction logos when the shellmap is disabled.
Visceriods now heal on Tiberium and move faster there. Visceriods now heal on Tiberium and move faster there.
Implemented the original shroud artwork.
Dune 2000: Dune 2000:
Added buildable concrete walls. Added buildable concrete walls.
Fixed some cliffs being passable. Fixed some cliffs being passable.
@@ -80,6 +82,7 @@ NEW:
Fixed A* debug overlay. Fixed A* debug overlay.
Fixed R8 offsets for sprites with embedded palettes. Fixed R8 offsets for sprites with embedded palettes.
Implemented proper spice rendering. Implemented proper spice rendering.
Implemented the original shroud artwork.
Engine: Engine:
Replays are now saved in per-mod and per-version folders. Replays are now saved in per-mod and per-version folders.
Added password protection support for servers. Added password protection support for servers.
@@ -99,6 +102,7 @@ NEW:
Rewritten shp(ts) parser makes more efficient use of texture space. Rewritten shp(ts) parser makes more efficient use of texture space.
Added support for the dune 2 shp and pak formats. Added support for the dune 2 shp and pak formats.
Map format 6 requires the RequiresMod to be defined. Map format 6 requires the RequiresMod to be defined.
Added a multiplicitive blend mode.
Build system and packages: Build system and packages:
Added GeoIP to Makefile so it is installed properly. Added GeoIP to Makefile so it is installed properly.
Added desktop shortcut creation support to the Makefile and Windows installer. Added desktop shortcut creation support to the Makefile and Windows installer.
@@ -137,7 +141,8 @@ NEW:
Added OpenRA.Utility --map-upgrade for updating maps from format 5 to format 6. Added OpenRA.Utility --map-upgrade for updating maps from format 5 to format 6.
The map format has been changed. All user-installed maps will be upgraded on the first mod launch, or using OpenRA.Utility --map-upgrade. The map format has been changed. All user-installed maps will be upgraded on the first mod launch, or using OpenRA.Utility --map-upgrade.
Unified sprite loading allows any sprite type to be used anywhere: shp can now be used for terrain, and tmp for units. Unified sprite loading allows any sprite type to be used anywhere: shp can now be used for terrain, and tmp for units.
Harvestable resource definitions (ResourceTypes) have changed, and now specify their artwork using via sequences. Harvestable resource definitions (ResourceTypes) have changed, and now specify their artwork using sequences.
Shroud definitions (ShroudRenderer / ShroudPalette) have changed, and now specifies its artwork using sequences.
20130915: 20130915:
All mods: All mods:

View File

@@ -31,7 +31,7 @@ namespace OpenRA.FileFormats.Graphics
IGraphicsDevice Create( Size size, WindowMode windowMode ); IGraphicsDevice Create( Size size, WindowMode windowMode );
} }
public enum BlendMode { None, Alpha, Additive, Subtractive } public enum BlendMode { None, Alpha, Additive, Subtractive, Multiply }
public interface IGraphicsDevice public interface IGraphicsDevice
{ {

View File

@@ -1,210 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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. For more information,
* see COPYING.
*/
#endregion
using System.Drawing;
using OpenRA.Traits;
namespace OpenRA.Graphics
{
public class ShroudRenderer
{
World world;
Map map;
Sprite[] shadowBits = Game.modData.SpriteLoader.LoadAllSprites("shadow");
Sprite[,] sprites, fogSprites;
int shroudHash;
bool initializePalettes = true;
PaletteReference fogPalette, shroudPalette;
static readonly byte[][] SpecialShroudTiles =
{
new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
new byte[] { 32, 32, 25, 25, 19, 19, 20, 20 },
new byte[] { 33, 33, 33, 33, 26, 26, 26, 26, 21, 21, 21, 21, 23, 23, 23, 23 },
new byte[] { 36, 36, 36, 36, 30, 30, 30, 30 },
new byte[] { 34, 16, 34, 16, 34, 16, 34, 16, 27, 22, 27, 22, 27, 22, 27, 22 },
new byte[] { 44 },
new byte[] { 37, 37, 37, 37, 37, 37, 37, 37, 31, 31, 31, 31, 31, 31, 31, 31 },
new byte[] { 40 },
new byte[] { 35, 24, 17, 18 },
new byte[] { 39, 39, 29, 29 },
new byte[] { 45 },
new byte[] { 43 },
new byte[] { 38, 28 },
new byte[] { 42 },
new byte[] { 41 },
new byte[] { 46 },
};
public ShroudRenderer(World world)
{
this.world = world;
this.map = world.Map;
sprites = new Sprite[map.MapSize.X, map.MapSize.Y];
fogSprites = new Sprite[map.MapSize.X, map.MapSize.Y];
// Force update on first render
shroudHash = -1;
}
Sprite ChooseShroud(Shroud s, int i, int j)
{
if (!s.IsExplored(i, j))
return shadowBits[0xf];
// bits are for unexploredness: up, right, down, left
var v = 0;
// bits are for unexploredness: TL, TR, BR, BL
var u = 0;
if (!s.IsExplored(i, j - 1)) { v |= 1; u |= 3; }
if (!s.IsExplored(i + 1, j)) { v |= 2; u |= 6; }
if (!s.IsExplored(i, j + 1)) { v |= 4; u |= 12; }
if (!s.IsExplored(i - 1, j)) { v |= 8; u |= 9; }
var uSides = u;
if (!s.IsExplored(i - 1, j - 1)) u |= 1;
if (!s.IsExplored(i + 1, j - 1)) u |= 2;
if (!s.IsExplored(i + 1, j + 1)) u |= 4;
if (!s.IsExplored(i - 1, j + 1)) u |= 8;
return shadowBits[SpecialShroudTiles[u ^ uSides][v]];
}
Sprite ChooseFog(Shroud s, int i, int j)
{
if (!s.IsVisible(i, j)) return shadowBits[0xf];
if (!s.IsExplored(i, j)) return shadowBits[0xf];
// bits are for unexploredness: up, right, down, left
var v = 0;
// bits are for unexploredness: TL, TR, BR, BL
var u = 0;
if (!s.IsVisible(i, j - 1)) { v |= 1; u |= 3; }
if (!s.IsVisible(i + 1, j)) { v |= 2; u |= 6; }
if (!s.IsVisible(i, j + 1)) { v |= 4; u |= 12; }
if (!s.IsVisible(i - 1, j)) { v |= 8; u |= 9; }
var uSides = u;
if (!s.IsVisible(i - 1, j - 1)) u |= 1;
if (!s.IsVisible(i + 1, j - 1)) u |= 2;
if (!s.IsVisible(i + 1, j + 1)) u |= 4;
if (!s.IsVisible(i - 1, j + 1)) u |= 8;
return shadowBits[SpecialShroudTiles[u ^ uSides][v]];
}
void GenerateSprites(Shroud shroud)
{
var hash = shroud != null ? shroud.Hash : 0;
if (shroudHash == hash)
return;
shroudHash = hash;
if (shroud == null)
{
// Players with no shroud see the whole map so we only need to set the edges
var b = map.Bounds;
for (int i = b.Left; i < b.Right; i++)
for (int j = b.Top; j < b.Bottom; j++)
{
var v = 0;
var u = 0;
if (j == b.Top) { v |= 1; u |= 3; }
if (i == b.Right - 1) { v |= 2; u |= 6; }
if (j == b.Bottom - 1) { v |= 4; u |= 12; }
if (i == b.Left) { v |= 8; u |= 9; }
var uSides = u;
if (i == b.Left && j == b.Top) u |= 1;
if (i == b.Right - 1 && j == b.Top) u |= 2;
if (i == b.Right - 1 && j == b.Bottom - 1) u |= 4;
if (i == b.Left && j == b.Bottom - 1) u |= 8;
sprites[i, j] = fogSprites[i, j] = shadowBits[SpecialShroudTiles[u ^ uSides][v]];
}
}
else
{
for (int i = map.Bounds.Left; i < map.Bounds.Right; i++)
for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
sprites[i, j] = ChooseShroud(shroud, i, j);
for (int i = map.Bounds.Left; i < map.Bounds.Right; i++)
for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
fogSprites[i, j] = ChooseFog(shroud, i, j);
}
}
internal void Draw(WorldRenderer wr, Shroud shroud)
{
if (initializePalettes)
{
if (world.LobbyInfo.GlobalSettings.Fog)
fogPalette = wr.Palette("fog");
shroudPalette = world.LobbyInfo.GlobalSettings.Fog ? wr.Palette("shroud") : wr.Palette("shroudfog");
initializePalettes = false;
}
GenerateSprites(shroud);
// We draw the shroud when disabled to hide the sharp map edges
var clipRect = wr.Viewport.CellBounds;
DrawShroud(wr, clipRect, sprites, shroudPalette);
if (world.LobbyInfo.GlobalSettings.Fog)
DrawShroud(wr, clipRect, fogSprites, fogPalette);
}
void DrawShroud(WorldRenderer wr, Rectangle clip, Sprite[,] s, PaletteReference pal)
{
for (var j = clip.Top; j < clip.Bottom; j++)
{
var starti = clip.Left;
var last = shadowBits[0x0f];
for (var i = clip.Left; i < clip.Right; i++)
{
if ((s[i, j] == shadowBits[0x0f] && last == shadowBits[0x0f])
|| (s[i, j] == shadowBits[0] && last == shadowBits[0]))
continue;
if (starti != i)
{
// Stretch a solid black sprite over the rows above
// TODO: This doesn't make sense for isometric terrain
Game.Renderer.WorldSpriteRenderer.DrawSprite(
s[starti, j],
Game.CellSize * new float2(starti, j),
pal,
new float2(Game.CellSize * (i - starti), Game.CellSize));
starti = i + 1;
}
Game.Renderer.WorldSpriteRenderer.DrawSprite(s[i, j], Game.CellSize * new float2(i, j), pal);
starti = i + 1;
last = s[i, j];
}
// Stretch a solid black sprite over the rows to the left
// TODO: This doesn't make sense for isometric terrain
if (starti < clip.Right)
Game.Renderer.WorldSpriteRenderer.DrawSprite(s[starti, j],
Game.CellSize * new float2(starti, j), pal,
new float2(Game.CellSize * (clip.Right - starti), Game.CellSize));
}
}
}
}

View File

@@ -37,7 +37,6 @@ namespace OpenRA.Graphics
public Viewport Viewport { get; private set; } public Viewport Viewport { get; private set; }
internal readonly TerrainRenderer terrainRenderer; internal readonly TerrainRenderer terrainRenderer;
internal readonly ShroudRenderer shroudRenderer;
internal readonly HardwarePalette palette; internal readonly HardwarePalette palette;
internal Cache<string, PaletteReference> palettes; internal Cache<string, PaletteReference> palettes;
Lazy<DeveloperMode> devTrait; Lazy<DeveloperMode> devTrait;
@@ -56,7 +55,6 @@ namespace OpenRA.Graphics
Theater = new Theater(world.TileSet); Theater = new Theater(world.TileSet);
terrainRenderer = new TerrainRenderer(world, this); terrainRenderer = new TerrainRenderer(world, this);
shroudRenderer = new ShroudRenderer(world);
devTrait = Lazy.New(() => world.LocalPlayer != null ? world.LocalPlayer.PlayerActor.Trait<DeveloperMode>() : null); devTrait = Lazy.New(() => world.LocalPlayer != null ? world.LocalPlayer.PlayerActor.Trait<DeveloperMode>() : null);
} }
@@ -132,7 +130,9 @@ namespace OpenRA.Graphics
world.OrderGenerator.RenderAfterWorld(this, world); world.OrderGenerator.RenderAfterWorld(this, world);
var renderShroud = world.RenderPlayer != null ? world.RenderPlayer.Shroud : null; var renderShroud = world.RenderPlayer != null ? world.RenderPlayer.Shroud : null;
shroudRenderer.Draw(this, renderShroud);
foreach (var a in world.ActorsWithTrait<IRenderShroud>())
a.Trait.RenderShroud(this, renderShroud);
if (devTrait.Value != null && devTrait.Value.ShowDebugGeometry) if (devTrait.Value != null && devTrait.Value.ShowDebugGeometry)
for (var i = 0; i < renderables.Count; i++) for (var i = 0; i < renderables.Count; i++)

View File

@@ -110,7 +110,6 @@
<Compile Include="Graphics\SequenceProvider.cs" /> <Compile Include="Graphics\SequenceProvider.cs" />
<Compile Include="Graphics\Sheet.cs" /> <Compile Include="Graphics\Sheet.cs" />
<Compile Include="Graphics\SheetBuilder.cs" /> <Compile Include="Graphics\SheetBuilder.cs" />
<Compile Include="Graphics\ShroudRenderer.cs" />
<Compile Include="Graphics\Sprite.cs" /> <Compile Include="Graphics\Sprite.cs" />
<Compile Include="Graphics\SpriteFont.cs" /> <Compile Include="Graphics\SpriteFont.cs" />
<Compile Include="Graphics\SpriteLoader.cs" /> <Compile Include="Graphics\SpriteLoader.cs" />

View File

@@ -191,6 +191,7 @@ namespace OpenRA.Traits
public interface IBlocksBullets { } public interface IBlocksBullets { }
public interface IPostRender { void RenderAfterWorld(WorldRenderer wr, Actor self); } public interface IPostRender { void RenderAfterWorld(WorldRenderer wr, Actor self); }
public interface IRenderShroud { void RenderShroud(WorldRenderer wr, Shroud shroud); }
public interface IPostRenderSelection { void RenderAfterWorld(WorldRenderer wr); } public interface IPostRenderSelection { void RenderAfterWorld(WorldRenderer wr); }
public interface IBodyOrientation public interface IBodyOrientation

View File

@@ -263,9 +263,7 @@ namespace OpenRA.Traits
public bool IsVisible(CPos xy) { return IsVisible(xy.X, xy.Y); } public bool IsVisible(CPos xy) { return IsVisible(xy.X, xy.Y); }
public bool IsVisible(int x, int y) public bool IsVisible(int x, int y)
{ {
// Visibility is allowed to extend beyond the map cordon so that if (!map.IsInMap(x, y))
// the fog tiles are not visible at the edge of the world
if (x < 0 || x >= map.MapSize.X || y < 0 || y >= map.MapSize.Y)
return false; return false;
if (Disabled || !self.World.LobbyInfo.GlobalSettings.Fog) if (Disabled || !self.World.LobbyInfo.GlobalSettings.Fog)

View File

@@ -0,0 +1,59 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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. For more information,
* see COPYING.
*/
#endregion
using System.IO;
using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
class FogPaletteFromR8Info : ITraitInfo
{
[Desc("Internal palette name")]
public readonly string Name = null;
[Desc("Filename to load")]
public readonly string Filename = null;
[Desc("Palette byte offset")]
public readonly long Offset = 0;
public readonly bool AllowModifiers = true;
public readonly bool InvertColor = false;
public object Create(ActorInitializer init) { return new FogPaletteFromR8(this); }
}
class FogPaletteFromR8 : IPalette
{
readonly FogPaletteFromR8Info info;
public FogPaletteFromR8(FogPaletteFromR8Info info) { this.info = info; }
public void InitPalette(WorldRenderer wr)
{
var colors = new uint[256];
using (var s = FileSystem.Open(info.Filename))
{
s.Seek(info.Offset, SeekOrigin.Begin);
for (var i = 0; i < 256; i++)
{
var packed = s.ReadUInt16();
// Fog is rendered with half opacity
colors[i] = (uint)((255 << 24) | ((packed & 0xF800) << 7) | ((packed & 0x7E0) << 4) | ((packed & 0x1f) << 2));
if (info.InvertColor)
colors[i] ^= 0x00FFFFFF;
}
}
wr.AddPalette(info.Name, new Palette(colors), info.AllowModifiers);
}
}
}

View File

@@ -80,6 +80,7 @@
<Compile Include="Render\WithCrumbleOverlay.cs" /> <Compile Include="Render\WithCrumbleOverlay.cs" />
<Compile Include="PaletteFromR8.cs" /> <Compile Include="PaletteFromR8.cs" />
<Compile Include="D2kResourceLayer.cs" /> <Compile Include="D2kResourceLayer.cs" />
<Compile Include="FogPaletteFromR8.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>

View File

@@ -10,8 +10,8 @@
using System.IO; using System.IO;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.Traits;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Mods.RA namespace OpenRA.Mods.RA
{ {
@@ -24,6 +24,7 @@ namespace OpenRA.Mods.RA
[Desc("Palette byte offset")] [Desc("Palette byte offset")]
public readonly long Offset = 0; public readonly long Offset = 0;
public readonly bool AllowModifiers = true; public readonly bool AllowModifiers = true;
public readonly bool InvertColor = false;
public object Create(ActorInitializer init) { return new PaletteFromR8(this); } public object Create(ActorInitializer init) { return new PaletteFromR8(this); }
} }
@@ -42,10 +43,11 @@ namespace OpenRA.Mods.RA
for (var i = 0; i < 256; i++) for (var i = 0; i < 256; i++)
{ {
// The custom palette is scaled into the range 0-128.
// This makes the move-flash match the original game, but may not be correct in other cases.
var packed = s.ReadUInt16(); var packed = s.ReadUInt16();
colors[i] = (uint)((255 << 24) | ((packed & 0xF800) << 7) | ((packed & 0x7E0) << 4) | ((packed & 0x1f) << 2)); colors[i] = (uint)((255 << 24) | ((packed & 0xF800) << 8) | ((packed & 0x7E0) << 5) | ((packed & 0x1f) << 3));
if (info.InvertColor)
colors[i] ^= 0x00FFFFFF;
} }
} }

View File

@@ -482,6 +482,7 @@
<Compile Include="Widgets\Logic\SettingsLogic.cs" /> <Compile Include="Widgets\Logic\SettingsLogic.cs" />
<Compile Include="AttackBomber.cs" /> <Compile Include="AttackBomber.cs" />
<Compile Include="Effects\Rank.cs" /> <Compile Include="Effects\Rank.cs" />
<Compile Include="ShroudRenderer.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj"> <ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">

View File

@@ -15,8 +15,6 @@ using OpenRA.Traits;
namespace OpenRA.Mods.RA namespace OpenRA.Mods.RA
{ {
public enum ShroudPaletteType { Shroud, Fog, Combined };
[Desc("Adds the hard-coded shroud palette to the game")] [Desc("Adds the hard-coded shroud palette to the game")]
class ShroudPaletteInfo : ITraitInfo class ShroudPaletteInfo : ITraitInfo
{ {
@@ -24,7 +22,7 @@ namespace OpenRA.Mods.RA
public readonly string Name = "shroud"; public readonly string Name = "shroud";
[Desc("Palette type")] [Desc("Palette type")]
public readonly ShroudPaletteType Type = ShroudPaletteType.Combined; public readonly bool Fog = false;
public object Create(ActorInitializer init) { return new ShroudPalette(this); } public object Create(ActorInitializer init) { return new ShroudPalette(this); }
} }
@@ -37,35 +35,24 @@ namespace OpenRA.Mods.RA
public void InitPalette(WorldRenderer wr) public void InitPalette(WorldRenderer wr)
{ {
var c = info.Type == ShroudPaletteType.Shroud ? Shroud : var c = info.Fog ? Fog : Shroud;
info.Type == ShroudPaletteType.Fog ? Fog : Combined;
wr.AddPalette(info.Name, new Palette(Exts.MakeArray(256, i => (uint)c[i % 8].ToArgb())), false); wr.AddPalette(info.Name, new Palette(Exts.MakeArray(256, i => (uint)c[i % 8].ToArgb())), false);
} }
static Color[] Shroud = new[] {
Color.Transparent, Color.Green,
Color.Blue, Color.Yellow,
Color.Black,
Color.FromArgb(128,0,0,0),
Color.Transparent,
Color.Transparent
};
static Color[] Fog = new[] { static Color[] Fog = new[] {
Color.Transparent, Color.Green, Color.Transparent, Color.Green,
Color.Blue, Color.Yellow, Color.Blue, Color.Yellow,
Color.FromArgb(128,0,0,0), Color.FromArgb(128,0,0,0),
Color.FromArgb(128,0,0,0), Color.FromArgb(96,0,0,0),
Color.FromArgb(128,0,0,0), Color.FromArgb(64,0,0,0),
Color.FromArgb(64,0,0,0) Color.FromArgb(32,0,0,0)
}; };
static Color[] Combined = new[] { static Color[] Shroud = new[] {
Color.Transparent, Color.Green, Color.Transparent, Color.Green,
Color.Blue, Color.Yellow, Color.Blue, Color.Yellow,
Color.Black, Color.Black,
Color.FromArgb(192,0,0,0), Color.FromArgb(160,0,0,0),
Color.FromArgb(128,0,0,0), Color.FromArgb(128,0,0,0),
Color.FromArgb(64,0,0,0) Color.FromArgb(64,0,0,0)
}; };

View File

@@ -0,0 +1,267 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.FileFormats.Graphics;
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
public class ShroudRendererInfo : ITraitInfo
{
public string Sequence = "shroud";
public string[] Variants = new[] { "shroud" };
[Desc("Bitfield of shroud directions for each frame. Lower four bits are",
"corners clockwise from TL; upper four are edges clockwise from top")]
public int[] Index = new[] { 12, 9, 8, 3, 1, 6, 4, 2, 13, 11, 7, 14 };
[Desc("Use the upper four bits when calculating frame")]
public bool UseExtendedIndex = false;
[Desc("Palette index for synthesized unexplored tile")]
public int ShroudColor = 12;
public BlendMode ShroudBlend = BlendMode.Alpha;
public object Create(ActorInitializer init) { return new ShroudRenderer(init.world, this); }
}
public class ShroudRenderer : IRenderShroud, IWorldLoaded
{
struct ShroudTile
{
public CPos Position;
public float2 ScreenPosition;
public int Variant;
public Sprite Fog;
public Sprite Shroud;
}
Sprite[] sprites;
Sprite unexploredTile;
int[] spriteMap;
ShroudTile[] tiles;
int tileStride, variantStride;
int shroudHash;
PaletteReference fogPalette, shroudPalette;
Rectangle bounds;
bool useExtendedIndex;
public ShroudRenderer(World world, ShroudRendererInfo info)
{
var map = world.Map;
bounds = map.Bounds;
useExtendedIndex = info.UseExtendedIndex;
tiles = new ShroudTile[map.MapSize.X * map.MapSize.Y];
tileStride = map.MapSize.X;
// Force update on first render
shroudHash = -1;
// Load sprite variants
sprites = new Sprite[info.Variants.Length * info.Index.Length];
variantStride = info.Index.Length;
for (var j = 0; j < info.Variants.Length; j++)
{
var seq = SequenceProvider.GetSequence(info.Sequence, info.Variants[j]);
for (var i = 0; i < info.Index.Length; i++)
sprites[j * variantStride + i] = seq.GetSprite(i);
}
// Mapping of shrouded directions -> sprite index
spriteMap = new int[useExtendedIndex ? 256 : 16];
for (var i = 0; i < info.Index.Length; i++)
spriteMap[info.Index[i]] = i;
// Set individual tile variants to reduce tiling
for (var i = 0; i < tiles.Length; i++)
tiles[i].Variant = Game.CosmeticRandom.Next(info.Variants.Length);
// Synthesize unexplored tile if it isn't defined
if (!info.Index.Contains(0))
{
var size = new Size(Game.modData.Manifest.TileSize, Game.modData.Manifest.TileSize);
var data = Exts.MakeArray<byte>(size.Width * size.Height, _ => (byte)info.ShroudColor);
var s = Game.modData.SheetBuilder.Add(data, size);
unexploredTile = new Sprite(s.sheet, s.bounds, s.offset, s.channel, info.ShroudBlend);
}
else
unexploredTile = sprites[spriteMap[0]];
}
static int FoggedEdges(Shroud s, CPos p, bool useExtendedIndex)
{
if (!s.IsVisible(p.X, p.Y))
return 15;
// If a side is shrouded then we also count the corners
var u = 0;
if (!s.IsVisible(p.X, p.Y - 1)) u |= 0x13;
if (!s.IsVisible(p.X + 1, p.Y)) u |= 0x26;
if (!s.IsVisible(p.X, p.Y + 1)) u |= 0x4C;
if (!s.IsVisible(p.X - 1, p.Y)) u |= 0x89;
var uside = u & 0x0F;
if (!s.IsVisible(p.X - 1, p.Y - 1)) u |= 0x01;
if (!s.IsVisible(p.X + 1, p.Y - 1)) u |= 0x02;
if (!s.IsVisible(p.X + 1, p.Y + 1)) u |= 0x04;
if (!s.IsVisible(p.X - 1, p.Y + 1)) u |= 0x08;
// RA provides a set of frames for tiles with shrouded
// corners but unshrouded edges. We want to detect this
// situation without breaking the edge -> corner enabling
// in other combinations. The XOR turns off the corner
// bits that are enabled twice, which gives the behavior
// we want here.
return useExtendedIndex ? u ^ uside : u & 0x0F;
}
static int ShroudedEdges(Shroud s, CPos p, bool useExtendedIndex)
{
if (!s.IsExplored(p.X, p.Y))
return 15;
// If a side is shrouded then we also count the corners
var u = 0;
if (!s.IsExplored(p.X, p.Y - 1)) u |= 0x13;
if (!s.IsExplored(p.X + 1, p.Y)) u |= 0x26;
if (!s.IsExplored(p.X, p.Y + 1)) u |= 0x4C;
if (!s.IsExplored(p.X - 1, p.Y)) u |= 0x89;
var uside = u & 0x0F;
if (!s.IsExplored(p.X - 1, p.Y - 1)) u |= 0x01;
if (!s.IsExplored(p.X + 1, p.Y - 1)) u |= 0x02;
if (!s.IsExplored(p.X + 1, p.Y + 1)) u |= 0x04;
if (!s.IsExplored(p.X - 1, p.Y + 1)) u |= 0x08;
// RA provides a set of frames for tiles with shrouded
// corners but unshrouded edges. We want to detect this
// situation without breaking the edge -> corner enabling
// in other combinations. The XOR turns off the corner
// bits that are enabled twice, which gives the behavior
// we want here.
return useExtendedIndex ? u ^ uside : u & 0x0F;
}
static int ObserverShroudedEdges(CPos p, Rectangle bounds, bool useExtendedIndex)
{
var u = 0;
if (p.Y == bounds.Top) u |= 0x13;
if (p.X == bounds.Right - 1) u |= 0x26;
if (p.Y == bounds.Bottom - 1) u |= 0x4C;
if (p.X == bounds.Left) u |= 0x89;
var uside = u & 0x0F;
if (p.X == bounds.Left && p.Y == bounds.Top) u |= 0x01;
if (p.X == bounds.Right - 1 && p.Y == bounds.Top) u |= 0x02;
if (p.X == bounds.Right - 1 && p.Y == bounds.Bottom - 1) u |= 0x04;
if (p.X == bounds.Left && p.Y == bounds.Bottom - 1) u |= 0x08;
return useExtendedIndex ? u ^ uside : u & 0x0F;
}
public void WorldLoaded(World w, WorldRenderer wr)
{
// Cache the tile positions to avoid unnecessary calculations
for (var i = bounds.Left; i < bounds.Right; i++)
{
for (var j = bounds.Top; j < bounds.Bottom; j++)
{
var k = j * tileStride + i;
tiles[k].Position = new CPos(i, j);
tiles[k].ScreenPosition = wr.ScreenPosition(tiles[k].Position.CenterPosition);
}
}
if (w.LobbyInfo.GlobalSettings.Fog)
fogPalette = wr.Palette("fog");
shroudPalette = wr.Palette("shroud");
}
Sprite GetTile(int flags, int variant)
{
if (flags == 0)
return null;
if (flags == 15)
return unexploredTile;
return sprites[variant * variantStride + spriteMap[flags]];
}
void Update(Shroud shroud)
{
var hash = shroud != null ? shroud.Hash : 0;
if (shroudHash == hash)
return;
shroudHash = hash;
if (shroud == null)
{
// Players with no shroud see the whole map so we only need to set the edges
for (var k = 0; k < tiles.Length; k++)
{
var shrouded = ObserverShroudedEdges(tiles[k].Position, bounds, useExtendedIndex);
tiles[k].Shroud = GetTile(shrouded, tiles[k].Variant);
tiles[k].Fog = GetTile(shrouded, tiles[k].Variant);
}
}
else
{
for (var k = 0; k < tiles.Length; k++)
{
var shrouded = ShroudedEdges(shroud, tiles[k].Position, useExtendedIndex);
var fogged = FoggedEdges(shroud, tiles[k].Position, useExtendedIndex);
tiles[k].Shroud = GetTile(shrouded, tiles[k].Variant);
tiles[k].Fog = GetTile(fogged, tiles[k].Variant);
}
}
}
public void RenderShroud(WorldRenderer wr, Shroud shroud)
{
Update(shroud);
var clip = wr.Viewport.CellBounds;
var width = clip.Width;
for (var j = clip.Top; j < clip.Bottom; j++)
{
var start = j * tileStride + clip.Left;
for (var k = 0; k < width; k++)
{
var s = tiles[start + k].Shroud;
var f = tiles[start + k].Fog;
if (s != null)
{
var pos = tiles[start + k].ScreenPosition - 0.5f * s.size;
Game.Renderer.WorldSpriteRenderer.DrawSprite(s, pos, shroudPalette);
}
if (f != null)
{
var pos = tiles[start + k].ScreenPosition - 0.5f * f.size;
Game.Renderer.WorldSpriteRenderer.DrawSprite(f, pos, fogPalette);
}
}
}
}
}
}

View File

@@ -177,6 +177,12 @@ namespace OpenRA.Renderer.Sdl2
ErrorHandler.CheckGlError(); ErrorHandler.CheckGlError();
Gl.glBlendEquation(Gl.GL_FUNC_REVERSE_SUBTRACT); Gl.glBlendEquation(Gl.GL_FUNC_REVERSE_SUBTRACT);
break; break;
case BlendMode.Multiply:
Gl.glEnable(Gl.GL_BLEND);
ErrorHandler.CheckGlError();
Gl.glBlendFuncSeparate(Gl.GL_DST_COLOR, Gl.GL_ZERO, Gl.GL_ONE, Gl.GL_ONE_MINUS_SRC_ALPHA);
ErrorHandler.CheckGlError();
break;
} }
ErrorHandler.CheckGlError(); ErrorHandler.CheckGlError();

View File

@@ -179,6 +179,12 @@ namespace OpenRA.Renderer.SdlCommon
ErrorHandler.CheckGlError(); ErrorHandler.CheckGlError();
Gl.glBlendEquation(Gl.GL_FUNC_REVERSE_SUBTRACT); Gl.glBlendEquation(Gl.GL_FUNC_REVERSE_SUBTRACT);
break; break;
case BlendMode.Multiply:
Gl.glEnable(Gl.GL_BLEND);
ErrorHandler.CheckGlError();
Gl.glBlendFuncSeparate(Gl.GL_DST_COLOR, Gl.GL_ZERO, Gl.GL_ONE, Gl.GL_ONE_MINUS_SRC_ALPHA);
ErrorHandler.CheckGlError();
break;
} }
ErrorHandler.CheckGlError(); ErrorHandler.CheckGlError();
} }

Binary file not shown.

View File

@@ -273,10 +273,9 @@ World:
Type: Shroud Type: Shroud
ShroudPalette@fog: ShroudPalette@fog:
Name: fog Name: fog
Type: Fog Fog: true
ShroudPalette@combined: ShroudRenderer:
Name: shroudfog Variants: typea, typeb, typec, typed
Type: Combined
Country@gdi: Country@gdi:
Name: GDI Name: GDI
Race: gdi Race: gdi

View File

@@ -361,3 +361,17 @@ resources:
Length: * Length: *
bti12: bti12 bti12: bti12
Length: * Length: *
shroud:
typea: shadow
Start: 0
Length: 12
typeb: shadow
Start: 12
Length: 12
typec: shadow
Start: 24
Length: 12
typed: shadow
Start: 36
Length: 12

Binary file not shown.

View File

@@ -414,21 +414,28 @@ World:
Name: moveflash Name: moveflash
Filename: DATA.R8 Filename: DATA.R8
Offset: 2572352 Offset: 2572352
A: 64 InvertColor: true
PaletteFromRGBA@disabled: PaletteFromRGBA@disabled:
Name: disabled Name: disabled
R: 0 R: 0
G: 0 G: 0
B: 0 B: 0
A: 180 A: 180
ShroudPalette@shroud: PaletteFromR8@shroud:
Type: Shroud Name: shroud
ShroudPalette@fog: Filename: DATA.R8
Offset: 12007
InvertColor: true
FogPaletteFromR8@fog:
Name: fog Name: fog
Type: Fog Filename: DATA.R8
ShroudPalette@combined: Offset: 12007
Name: shroudfog InvertColor: true
Type: Combined ShroudRenderer:
Variants: typea, typeb, typec, typed
Index: 11, 3, 7, 9, 6, 13, 12, 14, 4, 8, 2, 1, 5, 10
ShroudColor: 31
ShroudBlend: Multiply
Country@Atreides: Country@Atreides:
Name: Atreides Name: Atreides
Race: atreides Race: atreides

View File

@@ -304,10 +304,32 @@ moveflsh:
Start: 3621 Start: 3621
Length: 5 Length: 5
Tick: 80 Tick: 80
BlendMode: Subtractive BlendMode: Multiply
resources: resources:
spice: BLOXBASE spice: BLOXBASE
Frames: 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 300, 301, 320, 321 Frames: 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 300, 301, 320, 321
Length: 54 Length: 54
Offset: -16,-16 Offset: -16,-16
shroud:
typea: DATA
Start: 40
Length: 14
Offset: -16,-16
BlendMode: Multiply
typeb: DATA
Start: 56
Length: 14
Offset: -16,-16
BlendMode: Multiply
typec: DATA
Start: 72
Length: 14
Offset: -16,-16
BlendMode: Multiply
typed: DATA
Start: 88
Length: 14
Offset: -16,-16
BlendMode: Multiply

View File

@@ -616,10 +616,10 @@ World:
Type: Shroud Type: Shroud
ShroudPalette@fog: ShroudPalette@fog:
Name: fog Name: fog
Type: Fog Fog: true
ShroudPalette@combined: ShroudRenderer:
Name: shroudfog Index: 255, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 20, 40, 56, 65, 97, 130, 148, 194, 24, 33, 66, 132, 28, 41, 67, 134, 1, 2, 4, 8, 3, 6, 12, 9, 7, 14, 13, 11, 5, 10, 15, 255
Type: Combined UseExtendedIndex: true
Country@0: Country@0:
Name: Allies Name: Allies
Race: allies Race: allies

View File

@@ -487,3 +487,7 @@ resources:
Length: * Length: *
gem04: gem04 gem04: gem04
Length: * Length: *
shroud:
shroud: shadow
Length: *

View File

@@ -101,10 +101,10 @@ World:
Type: Shroud Type: Shroud
ShroudPalette@fog: ShroudPalette@fog:
Name: fog Name: fog
Type: Fog Fog: true
ShroudPalette@combined: ShroudRenderer:
Name: shroudfog Index: 255, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 20, 40, 56, 65, 97, 130, 148, 194, 24, 33, 66, 132, 28, 41, 67, 134, 1, 2, 4, 8, 3, 6, 12, 9, 7, 14, 13, 11, 5, 10, 15, 255
Type: Combined UseExtendedIndex: true
VoxelNormalsPalette@normals: VoxelNormalsPalette@normals:
Name: normals Name: normals
Type: TiberianSun Type: TiberianSun

View File

@@ -254,3 +254,7 @@ moveflsh:
resources: resources:
fake: shadow fake: shadow
Length: * Length: *
shroud:
shroud: shadow
Length: *