/// <summary> /// Checks whether two items can be packed to the same texture /// </summary> private bool AreAtlasItemsCompatible(List <TextureTools.AtlasItem> items, TextureTools.AtlasItem item1, TextureTools.AtlasItem item2) { if (item1.CookingRules.GenerateOpacityMask != item2.CookingRules.GenerateOpacityMask) { return(false); } if (item1.CookingRules.WrapMode != item2.CookingRules.WrapMode) { return(false); } if (item1.CookingRules.MinFilter != item2.CookingRules.MinFilter) { return(false); } if (item1.CookingRules.MagFilter != item2.CookingRules.MagFilter) { return(false); } if (item1.CookingRules.MipMaps != item2.CookingRules.MipMaps) { return(false); } if (items.Count > 0) { if (item1.CookingRules.WrapMode != TextureWrapMode.Clamp || item2.CookingRules.WrapMode != TextureWrapMode.Clamp) { return(false); } } switch (AssetCooker.Platform) { case TargetPlatform.Android: case TargetPlatform.iOS: return(item1.CookingRules.PVRFormat == item2.CookingRules.PVRFormat && item1.BitmapInfo.HasAlpha == item2.BitmapInfo.HasAlpha); case TargetPlatform.Win: case TargetPlatform.Mac: return(item1.CookingRules.DDSFormat == item2.CookingRules.DDSFormat); default: throw new ArgumentException(); } }
private void PackItemsToAtlas(List <TextureTools.AtlasItem> items, Size atlasSize, out double packRate) { items.ForEach(i => i.Allocated = false); // Reserve space for default one-pixel padding. atlasSize.Width += 2; atlasSize.Height += 2; var rectAllocator = new RectAllocator(atlasSize); TextureTools.AtlasItem firstAllocatedItem = null; foreach (var item in items) { var padding = item.CookingRules.AtlasItemPadding; var paddedItemSize = new Size(item.BitmapInfo.Width + padding * 2, item.BitmapInfo.Height + padding * 2); if (firstAllocatedItem == null || AreAtlasItemsCompatible(items, firstAllocatedItem, item)) { if (rectAllocator.Allocate(paddedItemSize, out item.AtlasRect)) { item.Allocated = true; firstAllocatedItem = firstAllocatedItem ?? item; } } } packRate = rectAllocator.GetPackRate(); // Adjust item rects according to theirs paddings. foreach (var item in items) { if (!item.Allocated) { continue; } var atlasRect = item.AtlasRect; atlasRect.A += new IntVector2(item.CookingRules.AtlasItemPadding); atlasRect.B -= new IntVector2(item.CookingRules.AtlasItemPadding); // Don't leave space between item rectangle and texture boundaries for items with 1 pixel padding. if (item.CookingRules.AtlasItemPadding == 1) { atlasRect.A -= new IntVector2(1); atlasRect.B -= new IntVector2(1); } item.AtlasRect = atlasRect; } }
private void BuildAtlasChain(string atlasChain) { for (var i = 0; i < AssetCooker.MaxAtlasChainLength; i++) { var atlasPath = AssetCooker.GetAtlasPath(atlasChain, i); if (AssetCooker.OutputBundle.FileExists(atlasPath)) { AssetCooker.DeleteFileFromBundle(atlasPath); } else { break; } } var pluginItems = new Dictionary <string, List <TextureTools.AtlasItem> >(); var items = new Dictionary <AtlasOptimization, List <TextureTools.AtlasItem> >(); items[AtlasOptimization.Memory] = new List <TextureTools.AtlasItem>(); items[AtlasOptimization.DrawCalls] = new List <TextureTools.AtlasItem>(); foreach (var fileInfo in AssetBundle.Current.EnumerateFileInfos(null, textureExtension)) { var cookingRules = AssetCooker.CookingRulesMap[fileInfo.Path]; if (cookingRules.TextureAtlas == atlasChain) { var item = new TextureTools.AtlasItem { Path = Path.ChangeExtension(fileInfo.Path, atlasPartExtension), CookingRules = cookingRules, SourceExtension = Path.GetExtension(fileInfo.Path) }; var bitmapInfo = TextureTools.BitmapInfo.FromFile(AssetCooker.InputBundle, fileInfo.Path); if (bitmapInfo == null) { using (var bitmap = TextureTools.OpenAtlasItemBitmapAndRescaleIfNeeded(AssetCooker.Platform, item)) { item.BitmapInfo = TextureTools.BitmapInfo.FromBitmap(bitmap); } } else { var srcTexturePath = AssetPath.Combine(The.Workspace.AssetsDirectory, Path.ChangeExtension(item.Path, item.SourceExtension)); if (TextureTools.ShouldDownscale(AssetCooker.Platform, bitmapInfo, item.CookingRules)) { TextureTools.DownscaleTextureInfo(AssetCooker.Platform, bitmapInfo, srcTexturePath, item.CookingRules); } // Ensure that no image exceeded maxAtlasSize limit TextureTools.DownscaleTextureToFitAtlas(bitmapInfo, srcTexturePath); item.BitmapInfo = bitmapInfo; } var k = cookingRules.AtlasPacker; if (!string.IsNullOrEmpty(k) && k != "Default") { List <TextureTools.AtlasItem> l; if (!pluginItems.TryGetValue(k, out l)) { pluginItems.Add(k, l = new List <TextureTools.AtlasItem>()); } l.Add(item); } else { items[cookingRules.AtlasOptimization].Add(item); } } } var initialAtlasId = 0; foreach (var kv in items) { if (kv.Value.Any()) { if (AssetCooker.Platform == TargetPlatform.iOS) { Predicate <PVRFormat> isRequireSquare = (format) => { return (format == PVRFormat.PVRTC2 || format == PVRFormat.PVRTC4 || format == PVRFormat.PVRTC4_Forced); }; var square = kv.Value.Where(item => isRequireSquare(item.CookingRules.PVRFormat)).ToList(); var nonSquare = kv.Value.Where(item => !isRequireSquare(item.CookingRules.PVRFormat)).ToList(); initialAtlasId = PackItemsToAtlas(atlasChain, square, kv.Key, initialAtlasId, true); initialAtlasId = PackItemsToAtlas(atlasChain, nonSquare, kv.Key, initialAtlasId, false); } else { initialAtlasId = PackItemsToAtlas(atlasChain, kv.Value, kv.Key, initialAtlasId, false); } } } var packers = PluginLoader.CurrentPlugin.AtlasPackers.ToDictionary(i => i.Metadata.Id, i => i.Value); foreach (var kv in pluginItems) { if (!packers.ContainsKey(kv.Key)) { throw new InvalidOperationException($"Packer {kv.Key} not found"); } initialAtlasId = packers[kv.Key](atlasChain, kv.Value, initialAtlasId); } }