1 #region Copyright & License Information 2 /* 3 * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) 4 * This file is part of OpenRA, which is free software. It is made 5 * available to you under the terms of the GNU General Public License 6 * as published by the Free Software Foundation, either version 3 of 7 * the License, or (at your option) any later version. For more 8 * information, see COPYING. 9 */ 10 #endregion 11 12 using System; 13 using System.Collections.Generic; 14 using System.IO; 15 using System.Linq; 16 using OpenRA.Primitives; 17 using OpenRA.Support; 18 19 namespace OpenRA.Graphics 20 { 21 class TheaterTemplate 22 { 23 public readonly Sprite[] Sprites; 24 public readonly int Stride; 25 public readonly int Variants; 26 TheaterTemplate(Sprite[] sprites, int stride, int variants)27 public TheaterTemplate(Sprite[] sprites, int stride, int variants) 28 { 29 Sprites = sprites; 30 Stride = stride; 31 Variants = variants; 32 } 33 } 34 35 public sealed class Theater : IDisposable 36 { 37 readonly Dictionary<ushort, TheaterTemplate> templates = new Dictionary<ushort, TheaterTemplate>(); 38 SheetBuilder sheetBuilder; 39 readonly Sprite missingTile; 40 readonly MersenneTwister random; 41 TileSet tileset; 42 Theater(TileSet tileset)43 public Theater(TileSet tileset) 44 { 45 this.tileset = tileset; 46 var allocated = false; 47 48 Func<Sheet> allocate = () => 49 { 50 if (allocated) 51 throw new SheetOverflowException("Terrain sheet overflow. Try increasing the tileset SheetSize parameter."); 52 allocated = true; 53 54 return new Sheet(SheetType.Indexed, new Size(tileset.SheetSize, tileset.SheetSize)); 55 }; 56 57 random = new MersenneTwister(); 58 59 var frameCache = new FrameCache(Game.ModData.DefaultFileSystem, Game.ModData.SpriteLoaders); 60 foreach (var t in tileset.Templates) 61 { 62 var variants = new List<Sprite[]>(); 63 64 foreach (var i in t.Value.Images) 65 { 66 var allFrames = frameCache[i]; 67 var frameCount = tileset.EnableDepth ? allFrames.Length / 2 : allFrames.Length; 68 var indices = t.Value.Frames != null ? t.Value.Frames : Enumerable.Range(0, frameCount); 69 variants.Add(indices.Select(j => 70 { 71 var f = allFrames[j]; 72 var tile = t.Value.Contains(j) ? t.Value[j] : null; 73 74 // The internal z axis is inverted from expectation (negative is closer) 75 var zOffset = tile != null ? -tile.ZOffset : 0; 76 var zRamp = tile != null ? tile.ZRamp : 1f; 77 var offset = new float3(f.Offset, zOffset); 78 var type = SheetBuilder.FrameTypeToSheetType(f.Type); 79 80 // Defer SheetBuilder creation until we know what type of frames we are loading! 81 // TODO: Support mixed indexed and BGRA frames 82 if (sheetBuilder == null) 83 sheetBuilder = new SheetBuilder(SheetBuilder.FrameTypeToSheetType(f.Type), allocate); 84 else if (type != sheetBuilder.Type) 85 throw new InvalidDataException("Sprite type mismatch. Terrain sprites must all be either Indexed or RGBA."); 86 87 var s = sheetBuilder.Allocate(f.Size, zRamp, offset); 88 Util.FastCopyIntoChannel(s, f.Data); 89 90 if (tileset.EnableDepth) 91 { 92 var ss = sheetBuilder.Allocate(f.Size, zRamp, offset); 93 Util.FastCopyIntoChannel(ss, allFrames[j + frameCount].Data); 94 95 // s and ss are guaranteed to use the same sheet 96 // because of the custom terrain sheet allocation 97 s = new SpriteWithSecondaryData(s, s.Sheet, ss.Bounds, ss.Channel); 98 } 99 100 return s; 101 }).ToArray()); 102 } 103 104 var allSprites = variants.SelectMany(s => s); 105 106 // Ignore the offsets baked into R8 sprites 107 if (tileset.IgnoreTileSpriteOffsets) 108 allSprites = allSprites.Select(s => new Sprite(s.Sheet, s.Bounds, s.ZRamp, new float3(float2.Zero, s.Offset.Z), s.Channel, s.BlendMode)); 109 110 templates.Add(t.Value.Id, new TheaterTemplate(allSprites.ToArray(), variants.First().Count(), t.Value.Images.Length)); 111 } 112 113 // 1x1px transparent tile 114 missingTile = sheetBuilder.Add(new byte[sheetBuilder.Type == SheetType.BGRA ? 4 : 1], new Size(1, 1)); 115 116 Sheet.ReleaseBuffer(); 117 } 118 TileSprite(TerrainTile r, int? variant = null)119 public Sprite TileSprite(TerrainTile r, int? variant = null) 120 { 121 TheaterTemplate template; 122 if (!templates.TryGetValue(r.Type, out template)) 123 return missingTile; 124 125 if (r.Index >= template.Stride) 126 return missingTile; 127 128 var start = template.Variants > 1 ? variant.HasValue ? variant.Value : random.Next(template.Variants) : 0; 129 return template.Sprites[start * template.Stride + r.Index]; 130 } 131 TemplateBounds(TerrainTemplateInfo template, Size tileSize, MapGridType mapGrid)132 public Rectangle TemplateBounds(TerrainTemplateInfo template, Size tileSize, MapGridType mapGrid) 133 { 134 Rectangle? templateRect = null; 135 136 var i = 0; 137 for (var y = 0; y < template.Size.Y; y++) 138 { 139 for (var x = 0; x < template.Size.X; x++) 140 { 141 var tile = new TerrainTile(template.Id, (byte)(i++)); 142 var tileInfo = tileset.GetTileInfo(tile); 143 144 // Empty tile 145 if (tileInfo == null) 146 continue; 147 148 var sprite = TileSprite(tile); 149 var u = mapGrid == MapGridType.Rectangular ? x : (x - y) / 2f; 150 var v = mapGrid == MapGridType.Rectangular ? y : (x + y) / 2f; 151 152 var tl = new float2(u * tileSize.Width, (v - 0.5f * tileInfo.Height) * tileSize.Height) - 0.5f * sprite.Size; 153 var rect = new Rectangle((int)(tl.X + sprite.Offset.X), (int)(tl.Y + sprite.Offset.Y), (int)sprite.Size.X, (int)sprite.Size.Y); 154 templateRect = templateRect.HasValue ? Rectangle.Union(templateRect.Value, rect) : rect; 155 } 156 } 157 158 return templateRect.HasValue ? templateRect.Value : Rectangle.Empty; 159 } 160 161 public Sheet Sheet { get { return sheetBuilder.Current; } } 162 Dispose()163 public void Dispose() 164 { 165 sheetBuilder.Dispose(); 166 } 167 } 168 } 169