Split tileset artwork loading out of TileSet.

This simplifies terrain loading and allows for
non-square tiles in game.

The editor still relies on the old code for now.
This commit is contained in:
Paul Chote
2013-08-14 20:28:41 +12:00
parent 2b6b212d02
commit 387ac04d9f
7 changed files with 122 additions and 90 deletions

View File

@@ -16,16 +16,15 @@ namespace OpenRA.FileFormats
public class Terrain public class Terrain
{ {
public readonly List<byte[]> TileBitmapBytes = new List<byte[]>(); public readonly List<byte[]> TileBitmapBytes = new List<byte[]>();
public readonly int Width;
public readonly int Height;
public Terrain( Stream stream, int size ) public Terrain(Stream stream)
{ {
// Try loading as a cnc .tem // Try loading as a cnc .tem
BinaryReader reader = new BinaryReader( stream ); BinaryReader reader = new BinaryReader( stream );
int Width = reader.ReadUInt16(); Width = reader.ReadUInt16();
int Height = reader.ReadUInt16(); Height = reader.ReadUInt16();
if( Width != size || Height != size )
throw new InvalidDataException( "{0}x{1} != {2}x{2}".F(Width, Height, size ) );
/*NumTiles = */reader.ReadUInt16(); /*NumTiles = */reader.ReadUInt16();
/*Zero1 = */reader.ReadUInt16(); /*Zero1 = */reader.ReadUInt16();
@@ -65,8 +64,8 @@ namespace OpenRA.FileFormats
{ {
if (b != 255) if (b != 255)
{ {
stream.Position = ImgStart + b * size * size; stream.Position = ImgStart + b * Width * Height;
TileBitmapBytes.Add(new BinaryReader(stream).ReadBytes(size * size)); TileBitmapBytes.Add(new BinaryReader(stream).ReadBytes(Width * Height));
} }
else else
TileBitmapBytes.Add(null); TileBitmapBytes.Add(null);

View File

@@ -113,7 +113,7 @@ namespace OpenRA.FileFormats
foreach (var t in Templates) foreach (var t in Templates)
if (t.Value.Data == null) if (t.Value.Data == null)
using (var s = FileSystem.OpenWithExts(t.Value.Image, Extensions)) using (var s = FileSystem.OpenWithExts(t.Value.Image, Extensions))
t.Value.Data = new Terrain(s, TileSize); t.Value.Data = new Terrain(s);
} }
public void Save(string filepath) public void Save(string filepath)
@@ -144,23 +144,6 @@ namespace OpenRA.FileFormats
root.WriteToFile(filepath); root.WriteToFile(filepath);
} }
public byte[] GetBytes(TileReference<ushort,byte> r)
{
TileTemplate tile;
if (Templates.TryGetValue(r.type, out tile))
{
var data = tile.Data.TileBitmapBytes[r.index];
if (data != null)
return data;
}
byte[] missingTile = new byte[TileSize*TileSize];
for (var i = 0; i < missingTile.Length; i++)
missingTile[i] = 0x00;
return missingTile;
}
public string GetTerrainType(TileReference<ushort, byte> r) public string GetTerrainType(TileReference<ushort, byte> r)
{ {
var tt = Templates[r.type].Tiles; var tt = Templates[r.type].Tiles;

View File

@@ -18,7 +18,6 @@ namespace OpenRA.Graphics
{ {
class TerrainRenderer class TerrainRenderer
{ {
SheetBuilder sheetBuilder;
IVertexBuffer<Vertex> vertexBuffer; IVertexBuffer<Vertex> vertexBuffer;
World world; World world;
@@ -29,23 +28,6 @@ namespace OpenRA.Graphics
this.world = world; this.world = world;
this.map = world.Map; this.map = world.Map;
var allocated = false;
Func<Sheet> allocate = () =>
{
if (allocated)
throw new SheetOverflowException("Terrain sheet overflow");
allocated = true;
// TODO: Use a fixed sheet size specified in the tileset yaml
return SheetBuilder.AllocateSheet();
};
sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate);
var tileSize = new Size(Game.CellSize, Game.CellSize);
var tileMapping = new Cache<TileReference<ushort,byte>, Sprite>(
x => sheetBuilder.Add(world.TileSet.GetBytes(x), tileSize));
var terrainPalette = wr.Palette("terrain").Index; var terrainPalette = wr.Palette("terrain").Index;
var vertices = new Vertex[4 * map.Bounds.Height * map.Bounds.Width]; var vertices = new Vertex[4 * map.Bounds.Height * map.Bounds.Width];
int nv = 0; int nv = 0;
@@ -53,7 +35,7 @@ namespace OpenRA.Graphics
for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++) for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
for (var i = map.Bounds.Left; i < map.Bounds.Right; i++) for (var i = map.Bounds.Left; i < map.Bounds.Right; i++)
{ {
var tile = tileMapping[map.MapTiles.Value[i, j]]; var tile = wr.Theater.TileSprite(map.MapTiles.Value[i, j]);
Util.FastCreateQuad(vertices, Game.CellSize * new float2(i, j), tile, terrainPalette, nv, tile.size); Util.FastCreateQuad(vertices, Game.CellSize * new float2(i, j), tile, terrainPalette, nv, tile.size);
nv += 4; nv += 4;
} }
@@ -96,7 +78,7 @@ namespace OpenRA.Graphics
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer( Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
vertexBuffer, verticesPerRow * firstRow, verticesPerRow * (lastRow - firstRow), vertexBuffer, verticesPerRow * firstRow, verticesPerRow * (lastRow - firstRow),
PrimitiveType.QuadList, sheetBuilder.Current); PrimitiveType.QuadList, wr.Theater.Sheet);
foreach (var r in world.WorldActor.TraitsImplementing<IRenderOverlay>()) foreach (var r in world.WorldActor.TraitsImplementing<IRenderOverlay>())
r.Render(wr); r.Render(wr);

View File

@@ -0,0 +1,73 @@
#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.IO;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.FileFormats.Graphics;
using OpenRA.Traits;
namespace OpenRA.Graphics
{
public class Theater
{
SheetBuilder sheetBuilder;
Dictionary<ushort, Sprite[]> templates;
Sprite missingTile;
Sprite[] LoadTemplate(string filename, string[] exts)
{
using (var s = FileSystem.OpenWithExts(filename, exts))
{
var t = new Terrain(s);
return t.TileBitmapBytes
.Select(b => b != null ? sheetBuilder.Add(b, new Size(t.Width, t.Height)) : null)
.ToArray();
}
}
public Theater(TileSet tileset)
{
var allocated = false;
Func<Sheet> allocate = () =>
{
if (allocated)
throw new SheetOverflowException("Terrain sheet overflow");
allocated = true;
// TODO: Use a fixed sheet size specified in the tileset yaml
return SheetBuilder.AllocateSheet();
};
templates = new Dictionary<ushort, Sprite[]>();
sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate);
foreach (var t in tileset.Templates)
templates.Add(t.Value.Id, LoadTemplate(t.Value.Image, tileset.Extensions));
// 1x1px transparent tile
missingTile = sheetBuilder.Add(new byte[1], new Size(1, 1));
}
public Sprite TileSprite(TileReference<ushort, byte> r)
{
Sprite[] template;
if (templates.TryGetValue(r.type, out template))
if (template.Length > r.index && template[r.index] != null)
return template[r.index];
return missingTile;
}
public Sheet Sheet { get { return sheetBuilder.Current; } }
}
}

View File

@@ -33,6 +33,8 @@ namespace OpenRA.Graphics
public class WorldRenderer public class WorldRenderer
{ {
public readonly World world; public readonly World world;
public readonly Theater Theater;
internal readonly TerrainRenderer terrainRenderer; internal readonly TerrainRenderer terrainRenderer;
internal readonly ShroudRenderer shroudRenderer; internal readonly ShroudRenderer shroudRenderer;
internal readonly HardwarePalette palette; internal readonly HardwarePalette palette;
@@ -50,6 +52,7 @@ namespace OpenRA.Graphics
palette.Initialize(); palette.Initialize();
Theater = new Theater(world.TileSet);
terrainRenderer = new TerrainRenderer(world, this); terrainRenderer = new TerrainRenderer(world, this);
shroudRenderer = new ShroudRenderer(world); shroudRenderer = new ShroudRenderer(world);

View File

@@ -235,6 +235,7 @@
<Compile Include="Graphics\ContrailRenderable.cs" /> <Compile Include="Graphics\ContrailRenderable.cs" />
<Compile Include="Widgets\ViewportControllerWidget.cs" /> <Compile Include="Widgets\ViewportControllerWidget.cs" />
<Compile Include="Traits\Player\FrozenActorLayer.cs" /> <Compile Include="Traits\Player\FrozenActorLayer.cs" />
<Compile Include="Graphics\Theater.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj"> <ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">

View File

@@ -69,12 +69,8 @@ namespace OpenRA.Mods.RA
class Bridge: IRenderAsTerrain, INotifyDamageStateChanged class Bridge: IRenderAsTerrain, INotifyDamageStateChanged
{ {
static string cachedTileset; ushort template;
static Cache<TileReference<ushort,byte>, Sprite> sprites; Dictionary<CPos, byte> footprint;
Dictionary<ushort, Dictionary<CPos, Sprite>> TileSprites = new Dictionary<ushort, Dictionary<CPos, Sprite>>();
Dictionary<ushort, TileTemplate> Templates = new Dictionary<ushort, TileTemplate>();
ushort currentTemplate;
Actor self; Actor self;
BridgeInfo Info; BridgeInfo Info;
@@ -91,38 +87,21 @@ namespace OpenRA.Mods.RA
this.Type = self.Info.Name; this.Type = self.Info.Name;
} }
public void Create(ushort template, Dictionary<CPos, byte> subtiles) public void Create(ushort template, Dictionary<CPos, byte> footprint)
{ {
currentTemplate = template; this.template = template;
this.footprint = footprint;
// Create a new cache to store the tile data
if (cachedTileset != self.World.Map.Tileset)
{
cachedTileset = self.World.Map.Tileset;
var tileSize = new Size(Game.CellSize, Game.CellSize);
sprites = new Cache<TileReference<ushort,byte>, Sprite>(
x => Game.modData.SheetBuilder.Add(self.World.TileSet.GetBytes(x), tileSize));
}
// Cache templates and tiles for the different states
foreach (var t in Info.Templates)
{
Templates.Add(t.First,self.World.TileSet.Templates[t.First]);
TileSprites.Add(t.First, subtiles.ToDictionary(
a => a.Key,
a => sprites[new TileReference<ushort,byte>(t.First, (byte)a.Value)]));
}
// Set the initial custom terrain types // Set the initial custom terrain types
foreach (var c in TileSprites[currentTemplate].Keys) foreach (var c in footprint.Keys)
self.World.Map.CustomTerrain[c.X, c.Y] = GetTerrainType(c); self.World.Map.CustomTerrain[c.X, c.Y] = GetTerrainType(c);
} }
public string GetTerrainType(CPos cell) string GetTerrainType(CPos cell)
{ {
var dx = cell - self.Location; var dx = cell - self.Location;
var index = dx.X + Templates[currentTemplate].Size.X * dx.Y; var index = dx.X + self.World.TileSet.Templates[template].Size.X * dx.Y;
return self.World.TileSet.GetTerrainType(new TileReference<ushort, byte>(currentTemplate,(byte)index)); return self.World.TileSet.GetTerrainType(new TileReference<ushort, byte>(template, (byte)index));
} }
public void LinkNeighbouringBridges(World world, BridgeLayer bridges) public void LinkNeighbouringBridges(World world, BridgeLayer bridges)
@@ -136,27 +115,39 @@ namespace OpenRA.Mods.RA
public Bridge GetNeighbor(int[] offset, BridgeLayer bridges) public Bridge GetNeighbor(int[] offset, BridgeLayer bridges)
{ {
if (offset == null) return null; if (offset == null)
return null;
return bridges.GetBridge(self.Location + new CVec(offset[0], offset[1])); return bridges.GetBridge(self.Location + new CVec(offset[0], offset[1]));
} }
bool initializePalettes = true; IRenderable[] TemplateRenderables(WorldRenderer wr, PaletteReference palette, ushort template)
PaletteReference terrainPalette; {
return footprint.Select(c => (IRenderable)(new SpriteRenderable(
wr.Theater.TileSprite(new TileReference<ushort, byte>(template, c.Value)),
c.Key.CenterPosition, WVec.Zero, 0, palette, 1f, true))).ToArray();
}
bool initialized;
Dictionary<ushort, IRenderable[]> renderables;
public IEnumerable<IRenderable> RenderAsTerrain(WorldRenderer wr, Actor self) public IEnumerable<IRenderable> RenderAsTerrain(WorldRenderer wr, Actor self)
{ {
if (initializePalettes) if (!initialized)
{ {
terrainPalette = wr.Palette("terrain"); var palette = wr.Palette("terrain");
initializePalettes = false; renderables = new Dictionary<ushort, IRenderable[]>();
foreach (var t in Info.Templates)
renderables.Add(t.First, TemplateRenderables(wr, palette, t.First));
initialized = true;
} }
foreach (var t in TileSprites[currentTemplate]) return renderables[template];
yield return new SpriteRenderable(t.Value, t.Key.CenterPosition, WVec.Zero, 0, terrainPalette, 1f, true);
} }
void KillUnitsOnBridge() void KillUnitsOnBridge()
{ {
foreach (var c in TileSprites[currentTemplate].Keys) foreach (var c in footprint.Keys)
foreach (var a in self.World.ActorMap.GetUnitsAt(c)) foreach (var a in self.World.ActorMap.GetUnitsAt(c))
if (a.HasTrait<IPositionable>() && !a.Trait<IPositionable>().CanEnterCell(c)) if (a.HasTrait<IPositionable>() && !a.Trait<IPositionable>().CanEnterCell(c))
a.Kill(self); a.Kill(self);
@@ -208,21 +199,21 @@ namespace OpenRA.Mods.RA
bool killedUnits = false; bool killedUnits = false;
void UpdateState() void UpdateState()
{ {
var oldTemplate = currentTemplate; var oldTemplate = template;
currentTemplate = ChooseTemplate(); template = ChooseTemplate();
if (currentTemplate == oldTemplate) if (template == oldTemplate)
return; return;
// Update map // Update map
foreach (var c in TileSprites[currentTemplate].Keys) foreach (var c in footprint.Keys)
self.World.Map.CustomTerrain[c.X, c.Y] = GetTerrainType(c); self.World.Map.CustomTerrain[c.X, c.Y] = GetTerrainType(c);
// If this bridge repair operation connects two pathfinding domains, // If this bridge repair operation connects two pathfinding domains,
// update the domain index. // update the domain index.
var domainIndex = self.World.WorldActor.TraitOrDefault<DomainIndex>(); var domainIndex = self.World.WorldActor.TraitOrDefault<DomainIndex>();
if (domainIndex != null) if (domainIndex != null)
domainIndex.UpdateCells(self.World, TileSprites[currentTemplate].Keys); domainIndex.UpdateCells(self.World, footprint.Keys);
if (LongBridgeSegmentIsDead() && !killedUnits) if (LongBridgeSegmentIsDead() && !killedUnits)
{ {