Skip to content

Commit

Permalink
Refactor texture remapping (LostArtefacts#668)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahm86 authored May 7, 2024
1 parent 55cddd5 commit 201294b
Show file tree
Hide file tree
Showing 92 changed files with 6,247 additions and 6,626 deletions.
190 changes: 19 additions & 171 deletions TRDataControl/Helpers/TRModelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,180 +7,28 @@ namespace TRDataControl;

public static class TRModelExtensions
{
// This should be handled by TRTextureRemapper

public static void ResetUnusedTextures(this TR1Level level)
{
ResetUnusedObjectTextures(level.ObjectTextures);
ResetUnusedSpriteTextures(level.Sprites.SelectMany(s => s.Value.Textures));
}

public static void ResetUnusedTextures(this TR2Level level)
{
ResetUnusedObjectTextures(level.ObjectTextures);
ResetUnusedSpriteTextures(level.Sprites.SelectMany(s => s.Value.Textures));
}

public static void ResetUnusedTextures(this TR3Level level)
{
ResetUnusedObjectTextures(level.ObjectTextures);
ResetUnusedSpriteTextures(level.Sprites.SelectMany(s => s.Value.Textures));
}

private static void ResetUnusedObjectTextures(IEnumerable<TRObjectTexture> objectTextures)
{
foreach (TRObjectTexture texture in objectTextures)
{
if (texture.Atlas == ushort.MaxValue)
{
texture.Invalidate();
}
public static void ResetUnusedTextures(this TRLevelBase level)
{
switch (level.Version.Game)
{
case TRGameVersion.TR1:
new TR1TextureRemapper(level as TR1Level).ResetUnusedTextures();
break;
case TRGameVersion.TR2:
new TR2TextureRemapper(level as TR2Level).ResetUnusedTextures();
break;
case TRGameVersion.TR3:
new TR3TextureRemapper(level as TR3Level).ResetUnusedTextures();
break;
case TRGameVersion.TR4:
new TR4TextureRemapper(level as TR4Level).ResetUnusedTextures();
break;
case TRGameVersion.TR5:
new TR5TextureRemapper(level as TR5Level).ResetUnusedTextures();
break;
}
}

private static void ResetUnusedSpriteTextures(IEnumerable<TRSpriteTexture> spriteTextures)
{
foreach (TRSpriteTexture texture in spriteTextures)
{
if (texture.Atlas == ushort.MaxValue)
{
texture.Invalidate();
}
}
}

// See TextureTransportHandler.ResetUnusedTextures
public static bool IsValid(this TRObjectTexture texture)
{
if (texture.Atlas == 0)
{
return texture.Size.Width == 0 && texture.Size.Height == 0;
}

return texture.Atlas != ushort.MaxValue;
}

public static void Invalidate(this TRObjectTexture texture)
{
texture.Atlas = 0;
texture.Size = new(0, 0);
}

// See TextureTransportHandler.ResetUnusedTextures
public static bool IsValid(this TRSpriteTexture texture)
{
if (texture.Atlas == 0)
{
if (texture.X == 0 && texture.Y == 0 && texture.Width == 1 && texture.Height == 1)
{
return false;
}
}

return texture.Atlas != ushort.MaxValue;
}

public static void Invalidate(this TRSpriteTexture texture)
{
texture.Atlas = 0;
texture.X = texture.Y = 0;
texture.Width = texture.Height = 1;
}

public static List<int> GetInvalidObjectTextureIndices(this TR1Level level)
{
return GetInvalidObjectTextureIndices(level.ObjectTextures);
}

public static List<int> GetInvalidObjectTextureIndices(this TR2Level level)
{
return GetInvalidObjectTextureIndices(level.ObjectTextures);
}

public static List<int> GetInvalidObjectTextureIndices(this TR3Level level)
{
return GetInvalidObjectTextureIndices(level.ObjectTextures);
}

private static List<int> GetInvalidObjectTextureIndices(List<TRObjectTexture> objectTextures)
{
List<int> reusableIndices = new();
for (int i = 0; i < objectTextures.Count; i++)
{
if (!objectTextures[i].IsValid())
{
reusableIndices.Add(i);
}
}
return reusableIndices;
}

// Given a precompiled dictionary of old texture index to new, this will ensure that
// all Meshes, RoomData and AnimatedTextures point to the new correct index.
public static void ReindexTextures(this TR2Level level, Dictionary<int, int> indexMap, bool defaultToOriginal = true)
{
if (indexMap.Count == 0)
{
return;
}

foreach (TRMesh mesh in level.DistinctMeshes)
{
foreach (TRMeshFace face in mesh.TexturedFaces)
{
face.Texture = ConvertTextureReference(face.Texture, indexMap, defaultToOriginal);
}
}

foreach (TR2Room room in level.Rooms)
{
foreach (TRFace face in room.Mesh.Faces)
{
face.Texture = ConvertTextureReference(face.Texture, indexMap, defaultToOriginal);
}
}

// #137 Ensure animated textures are reindexed too (these are just groups of texture indices)
// They have to remain unique it seems, otherwise the animation speed is too fast, so while we
// have removed the duplicated textures, we can re-add duplicate texture objects while there is
// enough space in that array.
List<TRObjectTexture> textures = level.ObjectTextures.ToList();
foreach (TRAnimatedTexture anim in level.AnimatedTextures)
{
for (int i = 0; i < anim.Textures.Count; i++)
{
anim.Textures[i] = ConvertTextureReference(anim.Textures[i], indexMap, defaultToOriginal);
}

ushort previousIndex = anim.Textures[0];
for (int i = 1; i < anim.Textures.Count; i++)
{
if (anim.Textures[i] == previousIndex && textures.Count < 2048)
{
textures.Add(textures[anim.Textures[i]]);
anim.Textures[i] = (ushort)(textures.Count - 1);
}
previousIndex = anim.Textures[i];
}
}

if (textures.Count > level.ObjectTextures.Count)
{
level.ObjectTextures.Clear();
level.ObjectTextures.AddRange(textures);
}
}

private static ushort ConvertTextureReference(ushort textureReference, Dictionary<int, int> indexMap, bool defaultToOriginal)
{
if (indexMap.ContainsKey(textureReference))
{
return (ushort)indexMap[textureReference];
}

return defaultToOriginal ? textureReference : (ushort)0;
}

public static string ComputeSkeletonHash(this IEnumerable<TRMesh> meshes)
{
using MemoryStream ms = new();
Expand Down
22 changes: 18 additions & 4 deletions TRDataControl/Transport/TR1/TR1DataExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ protected override TR1Blob CreateBlob(TR1Level level, TR1Type id, TRBlobType blo
};
}

protected override TRTextureRemapper<TR1Level> CreateRemapper()
=> new TR1TextureRemapper();
protected override TRTextureRemapper<TR1Level> CreateRemapper(TR1Level level)
=> new TR1TextureRemapper(level);

protected override bool IsMasterType(TR1Type type)
=> type == TR1Type.Lara;
Expand Down Expand Up @@ -77,7 +77,7 @@ protected override void PreCreation(TR1Level level, TR1Type type, TRBlobType blo
AmendSkaterBoyDeath(level);
break;
case TR1Type.CowboyHeadless:
//AmendDXtre3DTextures(definition);
AmendHeadlessCowboySFX(level);
break;
case TR1Type.Natla:
AmendNatlaDeath(level);
Expand Down Expand Up @@ -151,6 +151,21 @@ public static void AmendSkaterBoyDeath(TR1Level level)
}
}

public static void AmendHeadlessCowboySFX(TR1Level level)
{
// Originally silent in the Folklorist's Diary, but achieved using ID 15, which has been repurposed
// in TR1X as LaraWetFeet. Restore the gunshots and just remove the fake death sound.
foreach (TRAnimCommand cmd in level.Models[TR1Type.Cowboy].Animations[4].Commands)
{
if (cmd is TRSFXCommand sfxCmd && sfxCmd.SoundID == 15)
{
sfxCmd.SoundID = (short)TR1SFX.LaraMagnums;
}
}

level.Models[TR1Type.Cowboy].Animations[7].Commands.RemoveAll(a => a is TRSFXCommand);
}

public static void AmendNatlaDeath(TR1Level level)
{
level.Models[TR1Type.Natla].Animations[13].Commands.Add(new TRSFXCommand
Expand All @@ -164,7 +179,6 @@ public static void AddMovingBlockSFX(TR1Level level, string baseLevelDirectory)
{
// ToQ moving blocks are silent but we want them to scrape along the floor when they move.
// Import the trapdoor closing SFX from Vilcabamba and adjust the animations accordingly.

if (!level.SoundEffects.ContainsKey(TR1SFX.TrapdoorClose))
{
TR1Level vilcabamba = new TR1LevelControl().Read(Path.Combine(baseLevelDirectory, TR1LevelNames.VILCABAMBA));
Expand Down
13 changes: 7 additions & 6 deletions TRDataControl/Transport/TR1/TR1DataImporter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using TRImageControl;
using Newtonsoft.Json;
using TRImageControl;
using TRImageControl.Packing;
using TRLevelControl.Model;

Expand Down Expand Up @@ -28,8 +29,11 @@ protected override List<TR1Type> GetExistingTypes()
return new(Level.Models.Keys.Concat(Level.Sprites.Keys));
}

protected override TRTextureRemapper<TR1Level> CreateRemapper()
=> new TR1TextureRemapper();
protected override TRTextureRemapper<TR1Level> CreateRemapper(TR1Level level)
=> new TR1TextureRemapper(level);

protected override AbstractTextureRemapGroup<TR1Type, TR1Level> GetRemapGroup()
=> JsonConvert.DeserializeObject<TR1TextureRemapGroup>(File.ReadAllText(TextureRemapPath));

protected override bool IsMasterType(TR1Type type)
=> type == TR1Type.Lara;
Expand Down Expand Up @@ -59,9 +63,6 @@ protected override TRDictionary<TR1Type, TRSpriteSequence> SpriteSequences
protected override List<TRCinematicFrame> CinematicFrames
=> Level.CinematicFrames;

protected override List<TRObjectTexture> ObjectTextures
=> Level.ObjectTextures;

protected override ushort ImportColour(TR1Blob blob, ushort currentIndex)
{
return blob.Palette8.ContainsKey(currentIndex)
Expand Down
15 changes: 5 additions & 10 deletions TRDataControl/Transport/TR1/TR1TextureRemapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,12 @@ namespace TRDataControl;

public class TR1TextureRemapper : TRTextureRemapper<TR1Level>
{
public override List<TRAnimatedTexture> AnimatedTextures
=> _level.AnimatedTextures;

public override List<TRObjectTexture> ObjectTextures
=> _level.ObjectTextures;

public override IEnumerable<TRFace> Faces
=> _level.Rooms.Select(r => r.Mesh)
.SelectMany(m => m.Faces)
.Concat(_level.DistinctMeshes.SelectMany(m => m.TexturedFaces));
public override IEnumerable<TRFace> RoomFaces
=> _level.Rooms.Select(r => r.Mesh).SelectMany(m => m.Faces);

protected override TRTexturePacker CreatePacker()
=> new TR1TexturePacker(_level, 32);

public TR1TextureRemapper(TR1Level level)
: base(level) { }
}
4 changes: 2 additions & 2 deletions TRDataControl/Transport/TR2/TR2DataExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ protected override TR2Blob CreateBlob(TR2Level level, TR2Type id, TRBlobType blo
};
}

protected override TRTextureRemapper<TR2Level> CreateRemapper()
=> new TR2TextureRemapper();
protected override TRTextureRemapper<TR2Level> CreateRemapper(TR2Level level)
=> new TR2TextureRemapper(level);

protected override bool IsMasterType(TR2Type type)
=> type == TR2Type.Lara;
Expand Down
13 changes: 7 additions & 6 deletions TRDataControl/Transport/TR2/TR2DataImporter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using TRImageControl;
using Newtonsoft.Json;
using TRImageControl;
using TRImageControl.Packing;
using TRLevelControl.Model;

Expand All @@ -21,8 +22,11 @@ public TR2DataImporter(bool isCommunityPatch = false)
protected override List<TR2Type> GetExistingTypes()
=> new(Level.Models.Keys.Concat(Level.Sprites.Keys));

protected override TRTextureRemapper<TR2Level> CreateRemapper()
=> new TR2TextureRemapper();
protected override TRTextureRemapper<TR2Level> CreateRemapper(TR2Level level)
=> new TR2TextureRemapper(level);

protected override AbstractTextureRemapGroup<TR2Type, TR2Level> GetRemapGroup()
=> JsonConvert.DeserializeObject<TR2TextureRemapGroup>(File.ReadAllText(TextureRemapPath));

protected override bool IsMasterType(TR2Type type)
=> type == TR2Type.Lara;
Expand All @@ -45,9 +49,6 @@ protected override TRDictionary<TR2Type, TRSpriteSequence> SpriteSequences
protected override List<TRCinematicFrame> CinematicFrames
=> Level.CinematicFrames;

protected override List<TRObjectTexture> ObjectTextures
=> Level.ObjectTextures;

protected override ushort ImportColour(TR2Blob blob, ushort currentIndex)
{
if (!blob.Palette16.ContainsKey(currentIndex))
Expand Down
15 changes: 5 additions & 10 deletions TRDataControl/Transport/TR2/TR2TextureRemapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,12 @@ namespace TRDataControl;

public class TR2TextureRemapper : TRTextureRemapper<TR2Level>
{
public override List<TRAnimatedTexture> AnimatedTextures
=> _level.AnimatedTextures;

public override List<TRObjectTexture> ObjectTextures
=> _level.ObjectTextures;

public override IEnumerable<TRFace> Faces
=> _level.Rooms.Select(r => r.Mesh)
.SelectMany(m => m.Faces)
.Concat(_level.DistinctMeshes.SelectMany(m => m.TexturedFaces));
public override IEnumerable<TRFace> RoomFaces
=> _level.Rooms.Select(r => r.Mesh).SelectMany(m => m.Faces);

protected override TRTexturePacker CreatePacker()
=> new TR2TexturePacker(_level, 32);

public TR2TextureRemapper(TR2Level level)
: base(level) { }
}
4 changes: 2 additions & 2 deletions TRDataControl/Transport/TR3/TR3DataExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ protected override TR3Blob CreateBlob(TR3Level level, TR3Type id, TRBlobType blo
};
}

protected override TRTextureRemapper<TR3Level> CreateRemapper()
=> new TR3TextureRemapper();
protected override TRTextureRemapper<TR3Level> CreateRemapper(TR3Level level)
=> new TR3TextureRemapper(level);

protected override bool IsMasterType(TR3Type type)
=> type == TR3Type.Lara;
Expand Down
Loading

0 comments on commit 201294b

Please sign in to comment.