diff --git a/TRDataControl/Transport/TRDataImporter.cs b/TRDataControl/Transport/TRDataImporter.cs index f228d184..8ed2758a 100644 --- a/TRDataControl/Transport/TRDataImporter.cs +++ b/TRDataControl/Transport/TRDataImporter.cs @@ -344,6 +344,7 @@ protected void ImportTextures(List blobs) // Packing passed, so remap mesh textures to their new object references Dictionary> globalRemap = new(); + Queue freeSlots = new(Level.GetFreeTextureSlots()); foreach (TRTextileRegion region in importRegions) { globalRemap[region.ID] = new(); @@ -352,13 +353,23 @@ protected void ImportTextures(List blobs) if (globalRemap[region.ID].ContainsKey(segment.Index)) continue; - if (Level.ObjectTextures.Count >= Data.TextureObjectLimit) + int newIndex; + if (freeSlots.Count > 0) + { + newIndex = freeSlots.Dequeue(); + Level.ObjectTextures[newIndex] = segment.Texture as TRObjectTexture; + } + else if (Level.ObjectTextures.Count < Data.TextureObjectLimit) + { + newIndex = Level.ObjectTextures.Count; + Level.ObjectTextures.Add(segment.Texture as TRObjectTexture); + } + else { throw new PackingException($"Limit of {Data.TextureObjectLimit} textures reached."); } - globalRemap[region.ID][segment.Index] = Level.ObjectTextures.Count; - Level.ObjectTextures.Add(segment.Texture as TRObjectTexture); + globalRemap[region.ID][segment.Index] = newIndex; } } diff --git a/TRDataControl/Transport/TRTextureRemapper.cs b/TRDataControl/Transport/TRTextureRemapper.cs index 4d7261b0..0a8bcf58 100644 --- a/TRDataControl/Transport/TRTextureRemapper.cs +++ b/TRDataControl/Transport/TRTextureRemapper.cs @@ -1,4 +1,5 @@ using System.Drawing; +using TRImageControl; using TRImageControl.Packing; using TRLevelControl.Model; @@ -89,7 +90,7 @@ static string Hash(TRObjectTexture t) { int j = _level.ObjectTextures.IndexOf(copies[i]); remap[j] = rootIndex; - _level.ObjectTextures[j].Atlas = ushort.MaxValue; + _level.ObjectTextures[j].Invalidate(); } } @@ -108,23 +109,8 @@ public void ResetUnusedTextures() .Distinct(); foreach (int unused in allIndices.Except(usedIndices)) { - _level.ObjectTextures[unused].Atlas = ushort.MaxValue; + _level.ObjectTextures[unused].Invalidate(); } - - // Remap all indices after deleting the nulls - Dictionary remap = new(); - List oldTextures = new(_level.ObjectTextures); - _level.ObjectTextures.RemoveAll(o => o.Atlas == ushort.MaxValue); - for (int i = 0; i < oldTextures.Count; i++) - { - if (oldTextures[i].Atlas != ushort.MaxValue) - { - remap[i] = _level.ObjectTextures.IndexOf(oldTextures[i]); - } - } - - // Update all faces - RemapTextures(remap); } private void RemapTextures(Dictionary remap) diff --git a/TRDataControl/Utilities/TRModelExtensions.cs b/TRDataControl/Utilities/TRModelExtensions.cs index 40614859..53672e37 100644 --- a/TRDataControl/Utilities/TRModelExtensions.cs +++ b/TRDataControl/Utilities/TRModelExtensions.cs @@ -1,5 +1,6 @@ using System.Security.Cryptography; using System.Text; +using TRImageControl; using TRLevelControl.Build; using TRLevelControl.Model; @@ -29,6 +30,19 @@ public static void ResetUnusedTextures(this TRLevelBase level) } } + public static List GetFreeTextureSlots(this TRLevelBase level) + { + List slots = new(); + for (int i = 0; i < level.ObjectTextures.Count; i++) + { + if (!level.ObjectTextures[i].IsValid()) + { + slots.Add(i); + } + } + return slots; + } + public static string ComputeSkeletonHash(this IEnumerable meshes) { using MemoryStream ms = new(); diff --git a/TRDataControlTests/Remapping/RemappingTests.cs b/TRDataControlTests/Remapping/RemappingTests.cs index 268bba7b..b36eafa5 100644 --- a/TRDataControlTests/Remapping/RemappingTests.cs +++ b/TRDataControlTests/Remapping/RemappingTests.cs @@ -8,10 +8,34 @@ namespace TRDataControlTests.IO; [TestClass] -[TestCategory("OriginalIO")] public class RemappingTests : TestBase { [TestMethod] + [TestCategory("DataTransport")] + [Description("Test that texture slots are freed correctly when requested.")] + public void TestFreeSlots() + { + TR1Level level = GetTR1TestLevel(); + + HashSet slots = new(); + Random random = new(); + while (slots.Count < 10) + { + slots.Add(random.Next(0, level.ObjectTextures.Count)); + } + + foreach (int slot in slots) + { + level.ObjectTextures[slot].Invalidate(); + Assert.IsFalse(level.ObjectTextures[slot].IsValid()); + } + + List freeSlots = level.GetFreeTextureSlots(); + CollectionAssert.AreEquivalent(slots.ToList(), freeSlots); + } + + [TestMethod] + [TestCategory("OriginalIO")] [Description("Test that object textures are correct after removing data.")] public void TestTextureRemoval() { @@ -46,6 +70,7 @@ string GetFaceID(TRFace face) } [TestMethod] + [TestCategory("OriginalIO")] [Description("As above, but with an added dependency on a different model.")] public void TestDependencies() { diff --git a/TRImageControl/Extensions/ColourExtensions.cs b/TRImageControl/Extensions/ColourExtensions.cs index 2938ac21..54f27bf2 100644 --- a/TRImageControl/Extensions/ColourExtensions.cs +++ b/TRImageControl/Extensions/ColourExtensions.cs @@ -191,4 +191,17 @@ public static int FindClosest(this IEnumerable palette, Color colour, int .Where(item => item.Index >= startIndex) .MinBy(item => item.Delta).Index; } + + public static bool IsValid(this TRTexture texture) + { + return texture.Atlas != 0 || texture.Bounds != _nullBounds; + } + + public static void Invalidate(this TRTexture texture) + { + texture.Atlas = 0; + texture.Bounds = _nullBounds; + } + + private static readonly Rectangle _nullBounds = new(0, 0, 1, 1); } diff --git a/TRImageControl/Packing/TRTextileSegment.cs b/TRImageControl/Packing/TRTextileSegment.cs index 7e1f0387..cb43f833 100644 --- a/TRImageControl/Packing/TRTextileSegment.cs +++ b/TRImageControl/Packing/TRTextileSegment.cs @@ -28,12 +28,12 @@ public void Commit(int tileIndex) public void Invalidate() { - Atlas = ushort.MaxValue; + Texture.Invalidate(); } public bool IsValid() { - return Atlas != ushort.MaxValue; + return Texture.IsValid(); } public override string ToString() diff --git a/TRImageControl/Packing/Types/TR1TexturePacker.cs b/TRImageControl/Packing/Types/TR1TexturePacker.cs index 5f532362..41455159 100644 --- a/TRImageControl/Packing/Types/TR1TexturePacker.cs +++ b/TRImageControl/Packing/Types/TR1TexturePacker.cs @@ -44,6 +44,9 @@ protected override List LoadObjectSegments() List segments = new(); for (int i = 0; i < _level.ObjectTextures.Count; i++) { + if (!_level.ObjectTextures[i].IsValid()) + continue; + segments.Add(new() { Index = i, diff --git a/TRImageControl/Packing/Types/TR2TexturePacker.cs b/TRImageControl/Packing/Types/TR2TexturePacker.cs index 461cd17c..b3382f73 100644 --- a/TRImageControl/Packing/Types/TR2TexturePacker.cs +++ b/TRImageControl/Packing/Types/TR2TexturePacker.cs @@ -40,6 +40,9 @@ protected override List LoadObjectSegments() List segments = new(); for (int i = 0; i < _level.ObjectTextures.Count; i++) { + if (!_level.ObjectTextures[i].IsValid()) + continue; + segments.Add(new() { Index = i, diff --git a/TRImageControl/Packing/Types/TR3TexturePacker.cs b/TRImageControl/Packing/Types/TR3TexturePacker.cs index 9c28df99..c209cbb5 100644 --- a/TRImageControl/Packing/Types/TR3TexturePacker.cs +++ b/TRImageControl/Packing/Types/TR3TexturePacker.cs @@ -40,6 +40,9 @@ protected override List LoadObjectSegments() List segments = new(); for (int i = 0; i < _level.ObjectTextures.Count; i++) { + if (!_level.ObjectTextures[i].IsValid()) + continue; + segments.Add(new() { Index = i, diff --git a/TRImageControl/Packing/Types/TR4TexturePacker.cs b/TRImageControl/Packing/Types/TR4TexturePacker.cs index aad0d1d3..72080167 100644 --- a/TRImageControl/Packing/Types/TR4TexturePacker.cs +++ b/TRImageControl/Packing/Types/TR4TexturePacker.cs @@ -122,7 +122,7 @@ protected override List LoadObjectSegments() for (int i = 0; i < _level.ObjectTextures.Count; i++) { TRObjectTexture texture = _level.ObjectTextures[i]; - if (group != null && !group.Contains(texture)) + if (group != null && !group.Contains(texture) || !texture.IsValid()) continue; segments.Add(new() diff --git a/TRImageControl/Packing/Types/TR5TexturePacker.cs b/TRImageControl/Packing/Types/TR5TexturePacker.cs index 0051d02a..059d9eb9 100644 --- a/TRImageControl/Packing/Types/TR5TexturePacker.cs +++ b/TRImageControl/Packing/Types/TR5TexturePacker.cs @@ -113,7 +113,7 @@ protected override List LoadObjectSegments() for (int i = 0; i < _level.ObjectTextures.Count; i++) { TRObjectTexture texture = _level.ObjectTextures[i]; - if (group != null && !group.Contains(texture)) + if (group != null && !group.Contains(texture) || !texture.IsValid()) continue; segments.Add(new() diff --git a/TRRandomizerCore/Processors/TR2/TR2TextureDeduplicator.cs b/TRRandomizerCore/Processors/TR2/TR2TextureDeduplicator.cs index b77874cf..ec23a503 100644 --- a/TRRandomizerCore/Processors/TR2/TR2TextureDeduplicator.cs +++ b/TRRandomizerCore/Processors/TR2/TR2TextureDeduplicator.cs @@ -102,7 +102,6 @@ protected override void ProcessImpl() TR2TextureRemapper remapper = new(level.Data); remapper.ResetUnusedTextures(); - remapper.Remap(); _outer.SaveLevel(level); } diff --git a/TRRandomizerCore/Resources/TR2/Textures/Deduplication/HOUSE.TR2-TextureRemap.json b/TRRandomizerCore/Resources/TR2/Textures/Deduplication/HOUSE.TR2-TextureRemap.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/TRRandomizerCore/Resources/TR2/Textures/Deduplication/HOUSE.TR2-TextureRemap.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/TRRandomizerCore/Resources/TR2/Textures/Mapping/BOAT.TR2-Textures.json b/TRRandomizerCore/Resources/TR2/Textures/Mapping/BOAT.TR2-Textures.json index 2913aacf..83190293 100644 --- a/TRRandomizerCore/Resources/TR2/Textures/Mapping/BOAT.TR2-Textures.json +++ b/TRRandomizerCore/Resources/TR2/Textures/Mapping/BOAT.TR2-Textures.json @@ -5,7 +5,7 @@ { "RoomNumber": 0, "RectangleIndices": [ 57 ], - "BackgroundIndex": 1464 + "BackgroundIndex": 1520 } ] } diff --git a/TRRandomizerCore/Resources/TR2/Textures/Mapping/CATACOMB.TR2-Textures.json b/TRRandomizerCore/Resources/TR2/Textures/Mapping/CATACOMB.TR2-Textures.json index f8b585da..2517c38c 100644 --- a/TRRandomizerCore/Resources/TR2/Textures/Mapping/CATACOMB.TR2-Textures.json +++ b/TRRandomizerCore/Resources/TR2/Textures/Mapping/CATACOMB.TR2-Textures.json @@ -105,7 +105,7 @@ { "RoomNumber": 73, "RectangleIndices": [ 9 ], - "BackgroundIndex": 1297 + "BackgroundIndex": 1314 } ] }, @@ -114,7 +114,7 @@ { "RoomNumber": 121, "RectangleIndices": [ 7 ], - "BackgroundIndex": 1330 + "BackgroundIndex": 1344 } ] } diff --git a/TRRandomizerCore/Resources/TR2/Textures/Mapping/FLOATING.TR2-Textures.json b/TRRandomizerCore/Resources/TR2/Textures/Mapping/FLOATING.TR2-Textures.json index 5c47ea9f..e6a303d7 100644 --- a/TRRandomizerCore/Resources/TR2/Textures/Mapping/FLOATING.TR2-Textures.json +++ b/TRRandomizerCore/Resources/TR2/Textures/Mapping/FLOATING.TR2-Textures.json @@ -5,7 +5,7 @@ { "RoomNumber": 40, "RectangleIndices": [ 29, 58 ], - "BackgroundIndex": 1611 + "BackgroundIndex": 1667 } ] } diff --git a/TRRandomizerCore/Resources/TR2/Textures/Mapping/FLOATING.TR2-UKBox-Textures.json b/TRRandomizerCore/Resources/TR2/Textures/Mapping/FLOATING.TR2-UKBox-Textures.json index 046c2653..d20cef59 100644 --- a/TRRandomizerCore/Resources/TR2/Textures/Mapping/FLOATING.TR2-UKBox-Textures.json +++ b/TRRandomizerCore/Resources/TR2/Textures/Mapping/FLOATING.TR2-UKBox-Textures.json @@ -5,7 +5,7 @@ { "RoomNumber": 40, "RectangleIndices": [ 29, 58 ], - "BackgroundIndex": 1627 + "BackgroundIndex": 1683 } ] } diff --git a/TRRandomizerCore/Resources/TR2/Textures/Mapping/ICECAVE.TR2-Textures.json b/TRRandomizerCore/Resources/TR2/Textures/Mapping/ICECAVE.TR2-Textures.json index 840311f5..30e2bb99 100644 --- a/TRRandomizerCore/Resources/TR2/Textures/Mapping/ICECAVE.TR2-Textures.json +++ b/TRRandomizerCore/Resources/TR2/Textures/Mapping/ICECAVE.TR2-Textures.json @@ -5,7 +5,7 @@ { "RoomNumber": 5, "RectangleIndices": [ 72 ], - "BackgroundIndex": 1360 + "BackgroundIndex": 1378 } ] } diff --git a/TRRandomizerCore/Resources/TR2/Textures/Mapping/SKIDOO.TR2-Textures.json b/TRRandomizerCore/Resources/TR2/Textures/Mapping/SKIDOO.TR2-Textures.json index d10c1992..381a0680 100644 --- a/TRRandomizerCore/Resources/TR2/Textures/Mapping/SKIDOO.TR2-Textures.json +++ b/TRRandomizerCore/Resources/TR2/Textures/Mapping/SKIDOO.TR2-Textures.json @@ -5,7 +5,7 @@ { "RoomNumber": 131, "RectangleIndices": [ 15 ], - "BackgroundIndex": 1462 + "BackgroundIndex": 1480 } ] }, @@ -14,7 +14,7 @@ { "RoomNumber": 124, "RectangleIndices": [ 4 ], - "BackgroundIndex": 1332 + "BackgroundIndex": 1350 } ] } diff --git a/TRRandomizerCore/Resources/TR2/Textures/Mapping/WALL.TR2-Textures.json b/TRRandomizerCore/Resources/TR2/Textures/Mapping/WALL.TR2-Textures.json index 431b600e..3b2d4acd 100644 --- a/TRRandomizerCore/Resources/TR2/Textures/Mapping/WALL.TR2-Textures.json +++ b/TRRandomizerCore/Resources/TR2/Textures/Mapping/WALL.TR2-Textures.json @@ -59,7 +59,7 @@ { "RoomNumber": 65, "RectangleIndices": [ 134 ], - "BackgroundIndex": 1130 + "BackgroundIndex": 1186 } ] }