mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-25 14:08:34 +01:00
Compare commits
No commits in common. "b9f2de8290ea699c88572fdc2c09412af6efdda4" and "fcca5300ae04efd2525d8a0bd967c193c250a130" have entirely different histories.
b9f2de8290
...
fcca5300ae
4 changed files with 33 additions and 152 deletions
|
@ -49,14 +49,8 @@ Removals
|
|||
- Marked Tooltip.Paragraph as obsolete in favor of new Paragraphs collection
|
||||
|
||||
### MLEM.Data
|
||||
Additions
|
||||
- Added the ability to add padding to RuntimeTexturePacker texture regions
|
||||
- Added the ability to pack UniformTextureAtlas and DataTextureAtlas using RuntimeTexturePacker
|
||||
|
||||
Improvements
|
||||
- Premultiply textures when using RawContentManager
|
||||
- Allow enumerating all region names of a DataTextureAtlas
|
||||
- Cache RuntimeTexturePacker texture data while packing to improve performance
|
||||
|
||||
Fixes
|
||||
- Fixed SoundEffectReader incorrectly claiming it could read ogg and mp3 files
|
||||
|
|
|
@ -50,13 +50,9 @@ namespace MLEM.Data {
|
|||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an enumerable of all of the <see cref="TextureRegion"/> values in this atlas.
|
||||
/// Returns an enumerable of all of the <see cref="TextureRegion"/>s in this atlas.
|
||||
/// </summary>
|
||||
public IEnumerable<TextureRegion> Regions => this.regions.Values;
|
||||
/// <summary>
|
||||
/// Returns an enumerable of all of the <see cref="TextureRegion"/> names in this atlas.
|
||||
/// </summary>
|
||||
public IEnumerable<string> RegionNames => this.regions.Keys;
|
||||
|
||||
private readonly Dictionary<string, TextureRegion> regions = new Dictionary<string, TextureRegion>();
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ using Microsoft.Xna.Framework;
|
|||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Textures;
|
||||
using static MLEM.Extensions.TextureExtensions;
|
||||
|
||||
namespace MLEM.Data {
|
||||
/// <summary>
|
||||
|
@ -35,7 +34,6 @@ namespace MLEM.Data {
|
|||
public TimeSpan LastTotalTime => this.LastCalculationTime + this.LastPackTime;
|
||||
|
||||
private readonly List<Request> textures = new List<Request>();
|
||||
private readonly Dictionary<Texture2D, TextureData> dataCache = new Dictionary<Texture2D, TextureData>();
|
||||
private readonly bool autoIncreaseMaxWidth;
|
||||
private readonly bool forcePowerOfTwo;
|
||||
private readonly bool forceSquare;
|
||||
|
@ -44,13 +42,13 @@ namespace MLEM.Data {
|
|||
private int maxWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new runtime texture packer with the given settings.
|
||||
/// Creates a new runtime texture packer with the given settings
|
||||
/// </summary>
|
||||
/// <param name="maxWidth">The maximum width that the packed texture can have. Defaults to 2048.</param>
|
||||
/// <param name="autoIncreaseMaxWidth">Whether the maximum width should be increased if there is a texture to be packed that is wider than <see cref="maxWidth"/>. Defaults to false.</param>
|
||||
/// <param name="forcePowerOfTwo">Whether the resulting <see cref="PackedTexture"/> should have a width and height that is a power of two.</param>
|
||||
/// <param name="forceSquare">Whether the resulting <see cref="PackedTexture"/> should be square regardless of required size.</param>
|
||||
/// <param name="disposeTextures">Whether the original textures submitted to this texture packer should be disposed after packing.</param>
|
||||
/// <param name="forcePowerOfTwo">Whether the resulting <see cref="PackedTexture"/> should have a width and height that is a power of two</param>
|
||||
/// <param name="forceSquare">Whether the resulting <see cref="PackedTexture"/> should be square regardless of required size</param>
|
||||
/// <param name="disposeTextures">Whether the original textures submitted to this texture packer should be disposed after packing</param>
|
||||
public RuntimeTexturePacker(int maxWidth = 2048, bool autoIncreaseMaxWidth = false, bool forcePowerOfTwo = false, bool forceSquare = false, bool disposeTextures = false) {
|
||||
this.maxWidth = maxWidth;
|
||||
this.autoIncreaseMaxWidth = autoIncreaseMaxWidth;
|
||||
|
@ -60,89 +58,37 @@ namespace MLEM.Data {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new <see cref="UniformTextureAtlas"/> to this texture packer to be packed.
|
||||
/// The passed <see cref="Action{T}"/> is invoked in <see cref="Pack"/> and provides the caller with the resulting dictionary of texture regions on the <see cref="PackedTexture"/>, mapped to their x and y positions on the original <see cref="UniformTextureAtlas"/>.
|
||||
/// Note that the resulting data cannot be converted back into a <see cref="UniformTextureAtlas"/>, since the resulting texture regions might be scattered throughout the <see cref="PackedTexture"/>.
|
||||
/// </summary>
|
||||
/// <param name="atlas">The texture atlas to pack.</param>
|
||||
/// <param name="result">The result callback which will receive the resulting texture regions.</param>
|
||||
/// <param name="padding">The padding that the texture should have around itself. This can be useful if texture bleeding issues occur due to texture coordinate rounding.</param>
|
||||
/// <param name="padWithPixels">Whether the texture's padding should be filled with a copy of the texture's border, rather than transparent pixels. This value only has an effect if <paramref name="padding"/> is greater than 0.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to add data to a packer that has already been packed, or when trying to add a texture width a width greater than the defined max width.</exception>
|
||||
public void Add(UniformTextureAtlas atlas, Action<Dictionary<Point, TextureRegion>> result, int padding = 0, bool padWithPixels = false) {
|
||||
var resultRegions = new Dictionary<Point, TextureRegion>();
|
||||
for (var x = 0; x < atlas.RegionAmountX; x++) {
|
||||
for (var y = 0; y < atlas.RegionAmountY; y++) {
|
||||
var pos = new Point(x, y);
|
||||
this.Add(atlas[pos], r => {
|
||||
resultRegions.Add(pos, r);
|
||||
if (resultRegions.Count >= atlas.RegionAmountX * atlas.RegionAmountY)
|
||||
result.Invoke(resultRegions);
|
||||
}, padding, padWithPixels);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new <see cref="DataTextureAtlas"/> to this texture packer to be packed.
|
||||
/// The passed <see cref="Action{T}"/> is invoked in <see cref="Pack"/> and provides the caller with the resulting dictionary of texture regions on the <see cref="PackedTexture"/>, mapped to their name on the original <see cref="DataTextureAtlas"/>.
|
||||
/// Note that the resulting data cannot be converted back into a <see cref="DataTextureAtlas"/>, since the resulting texture regions might be scattered throughout the <see cref="PackedTexture"/>.
|
||||
/// </summary>
|
||||
/// <param name="atlas">The texture atlas to pack.</param>
|
||||
/// <param name="result">The result callback which will receive the resulting texture regions.</param>
|
||||
/// <param name="padding">The padding that the texture should have around itself. This can be useful if texture bleeding issues occur due to texture coordinate rounding.</param>
|
||||
/// <param name="padWithPixels">Whether the texture's padding should be filled with a copy of the texture's border, rather than transparent pixels. This value only has an effect if <paramref name="padding"/> is greater than 0.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to add data to a packer that has already been packed, or when trying to add a texture width a width greater than the defined max width.</exception>
|
||||
public void Add(DataTextureAtlas atlas, Action<Dictionary<string, TextureRegion>> result, int padding = 0, bool padWithPixels = false) {
|
||||
var atlasRegions = atlas.RegionNames.ToArray();
|
||||
var resultRegions = new Dictionary<string, TextureRegion>();
|
||||
foreach (var region in atlasRegions) {
|
||||
this.Add(atlas[region], r => {
|
||||
resultRegions.Add(region, r);
|
||||
if (resultRegions.Count >= atlasRegions.Length)
|
||||
result.Invoke(resultRegions);
|
||||
}, padding, padWithPixels);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new <see cref="Texture2D"/> to this texture packer to be packed.
|
||||
/// Adds a new texture to this texture packer to be packed.
|
||||
/// The passed <see cref="Action{T}"/> is invoked in <see cref="Pack"/> and provides the caller with the resulting texture region on the <see cref="PackedTexture"/>.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture to pack.</param>
|
||||
/// <param name="result">The result callback which will receive the resulting texture region.</param>
|
||||
/// <param name="padding">The padding that the texture should have around itself. This can be useful if texture bleeding issues occur due to texture coordinate rounding.</param>
|
||||
/// <param name="padWithPixels">Whether the texture's padding should be filled with a copy of the texture's border, rather than transparent pixels. This value only has an effect if <paramref name="padding"/> is greater than 0.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to add data to a packer that has already been packed, or when trying to add a texture width a width greater than the defined max width.</exception>
|
||||
public void Add(Texture2D texture, Action<TextureRegion> result, int padding = 0, bool padWithPixels = false) {
|
||||
this.Add(new TextureRegion(texture), result, padding, padWithPixels);
|
||||
/// <param name="texture">The texture to pack</param>
|
||||
/// <param name="result">The result callback which will receive the resulting texture region</param>
|
||||
public void Add(Texture2D texture, Action<TextureRegion> result) {
|
||||
this.Add(new TextureRegion(texture), result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new <see cref="TextureRegion"/> to this texture packer to be packed.
|
||||
/// The passed <see cref="Action{T}"/> is invoked in <see cref="Pack"/> and provides the caller with the resulting texture region on the <see cref="PackedTexture"/>.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture region to pack.</param>
|
||||
/// <param name="result">The result callback which will receive the resulting texture region.</param>
|
||||
/// <param name="padding">The padding that the texture should have around itself. This can be useful if texture bleeding issues occur due to texture coordinate rounding.</param>
|
||||
/// <param name="padWithPixels">Whether the texture's padding should be filled with a copy of the texture's border, rather than transparent pixels. This value only has an effect if <paramref name="padding"/> is greater than 0.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to add data to a packer that has already been packed, or when trying to add a texture width a width greater than the defined max width.</exception>
|
||||
public void Add(TextureRegion texture, Action<TextureRegion> result, int padding = 0, bool padWithPixels = false) {
|
||||
/// <param name="texture">The texture to pack</param>
|
||||
/// <param name="result">The result callback which will receive the resulting texture region</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to add data to a packer that has already been packed, or when trying to add a texture width a width greater than the defined max width</exception>
|
||||
public void Add(TextureRegion texture, Action<TextureRegion> result) {
|
||||
if (this.PackedTexture != null)
|
||||
throw new InvalidOperationException("Cannot add texture to a texture packer that is already packed");
|
||||
var paddedWidth = texture.Width + 2 * padding;
|
||||
if (paddedWidth > this.maxWidth) {
|
||||
if (texture.Width > this.maxWidth) {
|
||||
if (this.autoIncreaseMaxWidth) {
|
||||
this.maxWidth = paddedWidth;
|
||||
this.maxWidth = texture.Width;
|
||||
} else {
|
||||
throw new InvalidOperationException($"Cannot add texture with width {texture.Width} to a texture packer with max width {this.maxWidth}");
|
||||
}
|
||||
}
|
||||
this.textures.Add(new Request(texture, result, padding, padWithPixels));
|
||||
this.textures.Add(new Request(texture, result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs all of the textures and texture regions added using <see cref="Add(Microsoft.Xna.Framework.Graphics.Texture2D,System.Action{MLEM.Textures.TextureRegion},int,bool)"/> into one texture.
|
||||
/// Packs all of the textures and texture regions added using <see cref="Add(Microsoft.Xna.Framework.Graphics.Texture2D,System.Action{MLEM.Textures.TextureRegion})"/> into one texture.
|
||||
/// The resulting texture will be stored in <see cref="PackedTexture"/>.
|
||||
/// All of the result callbacks that were added will also be invoked.
|
||||
/// </summary>
|
||||
|
@ -154,8 +100,10 @@ namespace MLEM.Data {
|
|||
|
||||
// set pack areas for each request
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
foreach (var request in this.textures.OrderByDescending(t => t.Texture.Width * t.Texture.Height))
|
||||
request.PackedArea = this.FindFreeArea(request);
|
||||
foreach (var request in this.textures.OrderByDescending(t => t.Texture.Width * t.Texture.Height)) {
|
||||
var area = this.FindFreeArea(new Point(request.Texture.Width, request.Texture.Height));
|
||||
request.PackedArea = area;
|
||||
}
|
||||
stopwatch.Stop();
|
||||
this.LastCalculationTime = stopwatch.Elapsed;
|
||||
|
||||
|
@ -174,21 +122,19 @@ namespace MLEM.Data {
|
|||
stopwatch.Restart();
|
||||
using (var data = this.PackedTexture.GetTextureData()) {
|
||||
foreach (var request in this.textures)
|
||||
this.CopyRegion(data, request);
|
||||
CopyRegion(data, request);
|
||||
}
|
||||
stopwatch.Stop();
|
||||
this.LastPackTime = stopwatch.Elapsed;
|
||||
|
||||
// invoke callbacks
|
||||
foreach (var request in this.textures) {
|
||||
var packedArea = request.PackedArea.Shrink(new Point(request.Padding));
|
||||
request.Result.Invoke(new TextureRegion(this.PackedTexture, packedArea));
|
||||
request.Result.Invoke(new TextureRegion(this.PackedTexture, request.PackedArea));
|
||||
if (this.disposeTextures)
|
||||
request.Texture.Texture.Dispose();
|
||||
}
|
||||
|
||||
this.textures.Clear();
|
||||
this.dataCache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -198,7 +144,6 @@ namespace MLEM.Data {
|
|||
this.PackedTexture?.Dispose();
|
||||
this.PackedTexture = null;
|
||||
this.textures.Clear();
|
||||
this.dataCache.Clear();
|
||||
this.LastCalculationTime = TimeSpan.Zero;
|
||||
this.LastPackTime = TimeSpan.Zero;
|
||||
}
|
||||
|
@ -208,11 +153,7 @@ namespace MLEM.Data {
|
|||
this.Reset();
|
||||
}
|
||||
|
||||
private Rectangle FindFreeArea(Request request) {
|
||||
var size = new Point(request.Texture.Width, request.Texture.Height);
|
||||
size.X += request.Padding * 2;
|
||||
size.Y += request.Padding * 2;
|
||||
|
||||
private Rectangle FindFreeArea(Point size) {
|
||||
var pos = new Point(0, 0);
|
||||
var lowestY = int.MaxValue;
|
||||
while (true) {
|
||||
|
@ -238,27 +179,14 @@ namespace MLEM.Data {
|
|||
}
|
||||
}
|
||||
|
||||
private void CopyRegion(TextureData destination, Request request) {
|
||||
// we cache texture data in case multiple requests use the same underlying texture
|
||||
// this collection doesn't need to be disposed since we don't actually edit these textures
|
||||
if (!this.dataCache.TryGetValue(request.Texture.Texture, out var data)) {
|
||||
data = request.Texture.Texture.GetTextureData();
|
||||
this.dataCache.Add(request.Texture.Texture, data);
|
||||
private static void CopyRegion(TextureExtensions.TextureData destination, Request request) {
|
||||
using (var data = request.Texture.Texture.GetTextureData()) {
|
||||
for (var x = 0; x < request.Texture.Width; x++) {
|
||||
for (var y = 0; y < request.Texture.Height; y++) {
|
||||
var dest = request.PackedArea.Location + new Point(x, y);
|
||||
var src = request.Texture.Position + new Point(x, y);
|
||||
destination[dest] = data[src];
|
||||
}
|
||||
|
||||
var location = request.PackedArea.Location + new Point(request.Padding);
|
||||
for (var x = -request.Padding; x < request.Texture.Width + request.Padding; x++) {
|
||||
for (var y = -request.Padding; y < request.Texture.Height + request.Padding; y++) {
|
||||
Color srcColor;
|
||||
if (!request.PadWithPixels && (x < 0 || y < 0 || x >= request.Texture.Width || y >= request.Texture.Height)) {
|
||||
// if we're out of bounds and not padding with pixels, we make it transparent
|
||||
srcColor = Color.Transparent;
|
||||
} else {
|
||||
// otherwise, we just use the closest pixel that is actually in bounds, causing the border pixels to be doubled up
|
||||
var src = new Point(MathHelper.Clamp(x, 0, request.Texture.Width - 1), MathHelper.Clamp(y, 0, request.Texture.Height - 1));
|
||||
srcColor = data[request.Texture.Position + src];
|
||||
}
|
||||
destination[location + new Point(x, y)] = srcColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -274,15 +202,11 @@ namespace MLEM.Data {
|
|||
|
||||
public readonly TextureRegion Texture;
|
||||
public readonly Action<TextureRegion> Result;
|
||||
public readonly int Padding;
|
||||
public readonly bool PadWithPixels;
|
||||
public Rectangle PackedArea;
|
||||
|
||||
public Request(TextureRegion texture, Action<TextureRegion> result, int padding, bool padWithPixels) {
|
||||
public Request(TextureRegion texture, Action<TextureRegion> result) {
|
||||
this.Texture = texture;
|
||||
this.Result = result;
|
||||
this.Padding = padding;
|
||||
this.PadWithPixels = padWithPixels;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using FontStashSharp;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
@ -23,7 +21,6 @@ using MLEM.Textures;
|
|||
using MLEM.Ui;
|
||||
using MLEM.Ui.Elements;
|
||||
using MLEM.Ui.Style;
|
||||
using MonoGame.Extended;
|
||||
using MonoGame.Extended.Tiled;
|
||||
using MonoGame.Extended.ViewportAdapters;
|
||||
|
||||
|
@ -317,36 +314,6 @@ namespace Sandbox {
|
|||
button.Size = new Vector2(30, 50);
|
||||
}
|
||||
this.UiSystem.Add("Keybinds", keybindPanel);
|
||||
|
||||
var packer = new RuntimeTexturePacker();
|
||||
var regions = new List<TextureRegion>();
|
||||
packer.Add(new UniformTextureAtlas(tex, 16, 16), r => {
|
||||
regions.AddRange(r.Values);
|
||||
Console.WriteLine($"Returned {r.Count} regions: {string.Join(", ", r.Select(kv => kv.Key + ": " + kv.Value.Area))}");
|
||||
}, 1, true);
|
||||
packer.Add(this.Content.LoadTextureAtlas("Textures/Furniture"), r => {
|
||||
regions.AddRange(r.Values);
|
||||
Console.WriteLine($"Returned {r.Count} regions: {string.Join(", ", r.Select(kv => kv.Key + ": " + kv.Value.Area))}");
|
||||
}, 1, true);
|
||||
packer.Pack(this.GraphicsDevice);
|
||||
packer.PackedTexture.SaveAsPng(File.Create("_Packed.png"), packer.PackedTexture.Width, packer.PackedTexture.Height);
|
||||
|
||||
this.OnDraw += (g, t) => {
|
||||
this.SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
|
||||
var x = 0;
|
||||
var y = 10;
|
||||
foreach (var r in regions) {
|
||||
const int sc = 5;
|
||||
this.SpriteBatch.DrawRectangle(new Vector2(x, y), new Vector2(r.Width * sc, r.Height * sc), Color.Green);
|
||||
this.SpriteBatch.Draw(r, new Vector2(x, y), Color.White, 0, Vector2.Zero, sc, SpriteEffects.None, 0);
|
||||
x += r.Width * sc + 1;
|
||||
if (x >= 1000) {
|
||||
x = 0;
|
||||
y += 50;
|
||||
}
|
||||
}
|
||||
this.SpriteBatch.End();
|
||||
};
|
||||
}
|
||||
|
||||
protected override void DoUpdate(GameTime gameTime) {
|
||||
|
|
Loading…
Reference in a new issue