/// <summary> /// Returns the first set of sprites with the given filename. /// If getUsedFrames is defined then the indices returned by the function call /// are guaranteed to be loaded. The value of other indices in the returned /// array are undefined and should never be accessed. /// </summary> public Sprite[] this[string filename, Func <int, IEnumerable <int> > getUsedFrames = null] { get { var allSprites = sprites.GetOrAdd(filename); var sprite = allSprites.FirstOrDefault(); if (!unloadedFrames.TryGetValue(filename, out var unloaded)) { unloaded = null; } // This is the first time that the file has been requested // Load all of the frames into the unused buffer and initialize // the loaded cache (initially empty) if (sprite == null) { unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out var fileMetadata); unloadedFrames[filename] = unloaded; metadata[filename] = fileMetadata; sprite = new Sprite[unloaded.Length]; allSprites.Add(sprite); } // HACK: The sequence code relies on side-effects from getUsedFrames var indices = getUsedFrames != null?getUsedFrames(sprite.Length) : Enumerable.Range(0, sprite.Length); // Load any unused frames into the SheetBuilder if (unloaded != null) { foreach (var i in indices) { if (unloaded[i] != null) { sprite[i] = SheetBuilders[SheetBuilder.FrameTypeToSheetType(unloaded[i].Type)].Add(unloaded[i]); unloaded[i] = null; } } // All frames have been loaded if (unloaded.All(f => f == null)) { unloadedFrames.Remove(filename); } } return(sprite); } }
public Theater(TileSet tileset, Action <uint, string> onMissingImage = null) { this.tileset = tileset; var allocated = false; Func <Sheet> allocate = () => { if (allocated) { throw new SheetOverflowException("Terrain sheet overflow. Try increasing the tileset SheetSize parameter."); } allocated = true; return(new Sheet(SheetType.Indexed, new Size(tileset.SheetSize, tileset.SheetSize))); }; random = new MersenneTwister(); var frameCache = new FrameCache(Game.ModData.DefaultFileSystem, Game.ModData.SpriteLoaders); foreach (var t in tileset.Templates) { var variants = new List <Sprite[]>(); foreach (var i in t.Value.Images) { ISpriteFrame[] allFrames; if (onMissingImage != null) { try { allFrames = frameCache[i]; } catch (FileNotFoundException) { onMissingImage(t.Key, i); continue; } } else { allFrames = frameCache[i]; } var frameCount = tileset.EnableDepth ? allFrames.Length / 2 : allFrames.Length; var indices = t.Value.Frames != null ? t.Value.Frames : Exts.MakeArray(t.Value.TilesCount, j => j); var start = indices.Min(); var end = indices.Max(); if (start < 0 || end >= frameCount) { throw new YamlException("Template `{0}` uses frames [{1}..{2}] of {3}, but only [0..{4}] actually exist" .F(t.Key, start, end, i, frameCount - 1)); } variants.Add(indices.Select(j => { var f = allFrames[j]; var tile = t.Value.Contains(j) ? t.Value[j] : null; // The internal z axis is inverted from expectation (negative is closer) var zOffset = tile != null ? -tile.ZOffset : 0; var zRamp = tile != null ? tile.ZRamp : 1f; var offset = new float3(f.Offset, zOffset); var type = SheetBuilder.FrameTypeToSheetType(f.Type); // Defer SheetBuilder creation until we know what type of frames we are loading! // TODO: Support mixed indexed and BGRA frames if (sheetBuilder == null) { sheetBuilder = new SheetBuilder(SheetBuilder.FrameTypeToSheetType(f.Type), allocate); } else if (type != sheetBuilder.Type) { throw new YamlException("Sprite type mismatch. Terrain sprites must all be either Indexed or RGBA."); } var s = sheetBuilder.Allocate(f.Size, zRamp, offset); Util.FastCopyIntoChannel(s, f.Data); if (tileset.EnableDepth) { var ss = sheetBuilder.Allocate(f.Size, zRamp, offset); Util.FastCopyIntoChannel(ss, allFrames[j + frameCount].Data); // s and ss are guaranteed to use the same sheet // because of the custom terrain sheet allocation s = new SpriteWithSecondaryData(s, s.Sheet, ss.Bounds, ss.Channel); } return(s); }).ToArray()); } var allSprites = variants.SelectMany(s => s); // Ignore the offsets baked into R8 sprites if (tileset.IgnoreTileSpriteOffsets) { allSprites = allSprites.Select(s => new Sprite(s.Sheet, s.Bounds, s.ZRamp, new float3(float2.Zero, s.Offset.Z), s.Channel, s.BlendMode)); } if (onMissingImage != null && !variants.Any()) { continue; } templates.Add(t.Value.Id, new TheaterTemplate(allSprites.ToArray(), variants.First().Count(), t.Value.Images.Length)); } // 1x1px transparent tile missingTile = sheetBuilder.Add(new byte[sheetBuilder.Type == SheetType.BGRA ? 4 : 1], new Size(1, 1)); Sheet.ReleaseBuffer(); }
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders) { SheetBuilders = new Cache <SpriteFrameType, SheetBuilder>(t => new SheetBuilder(SheetBuilder.FrameTypeToSheetType(t))); this.fileSystem = fileSystem; this.loaders = loaders; }