Skip to content

Commit

Permalink
Handle TR4R/TR5R PDP data
Browse files Browse the repository at this point in the history
This updates the model builder to support TR4 and TR5 remastered, which has some changes in frame rotations and "null" model entries.
  • Loading branch information
lahm86 committed Feb 22, 2025
1 parent 3f5545a commit 335c989
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 7 deletions.
63 changes: 56 additions & 7 deletions TRLevelControl/Build/TRModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,15 @@ private void ReadModels(TRLevelReader reader)
}

List<PlaceholderModel> animatedModels = _placeholderModels.FindAll(m => m.Animation != TRConsts.NoAnimation);
if (_dataType == TRModelDataType.PDP && _version == TRGameVersion.TR5)
{
// TR5 PDP has entries for what seem like null models, with no animations, frames or meshes. These are not setup in the conventional way
// per OG, with jumps in anim idx, so we address this here to avoid skewing other animation counts below.
List<PlaceholderModel> invalidAnimModels = animatedModels.FindAll(m => m.ID > (uint)TR5Type.Lara && m.Animation == 0);
invalidAnimModels.ForEach(m => m.Animation = TRConsts.NoAnimation);
animatedModels.RemoveAll(invalidAnimModels.Contains);
}

for (int i = 0; i < animatedModels.Count; i++)
{
PlaceholderModel model = animatedModels[i];
Expand Down Expand Up @@ -511,6 +520,14 @@ private TRAnimFrame BuildFrame(ref int frameIndex, int numRotations)
: GetSingleRotation(rot0);
}

if (_dataType == TRModelDataType.PDP && _version > TRGameVersion.TR3)
{
// Some TR4+ rots are marked as all despite only having one value set, so when we write
// them back, we restore this mode to keep tests happy. If changing values otherwise, care
// should be taken to set the mode to Auto.
rot.Mode = rotMode;
}

switch (rotMode)
{
case TRAngleMode.X:
Expand Down Expand Up @@ -548,16 +565,42 @@ private void TestTR5Changes(IEnumerable<TRModel> models)
}
}

private void DeconstructModel(T type, TRModel model)
private PlaceholderModel CreateDeconstructedModel(T type, TRModel model)
{
PlaceholderModel placeholderModel = new()
PlaceholderModel placeholder = new()
{
ID = (uint)(object)type,
Animation = model.Animations.Count == 0 ? TRConsts.NoAnimation : (ushort)_placeholderAnimations.Count,
FrameOffset = (_dataType == TRModelDataType.PDP || _observer == null)
&& model.Animations.Count == 0 ? 0 : (uint)_frames.Count * sizeof(short),
NumMeshes = (ushort)model.Meshes.Count,
};

if (model.Animations.Count == 0)
{
if (_dataType == TRModelDataType.PDP && _version == TRGameVersion.TR5 && model.Meshes.Count == 0)
{
placeholder.Animation = 0;
placeholder.IsNullTR5Model = true;
}
else
{
placeholder.Animation = TRConsts.NoAnimation;
}

placeholder.FrameOffset = _dataType == TRModelDataType.PDP || _observer == null
? 0
: (uint)_frames.Count * sizeof(short);
}
else
{
placeholder.Animation = (ushort)_placeholderAnimations.Count;
placeholder.FrameOffset = (uint)_frames.Count * sizeof(short);
}

return placeholder;
}

private void DeconstructModel(T type, TRModel model)
{
PlaceholderModel placeholderModel = CreateDeconstructedModel(type, model);
_placeholderModels.Add(placeholderModel);

_trees.AddRange(model.MeshTrees);
Expand Down Expand Up @@ -880,8 +923,8 @@ private void WriteModels(TRLevelWriter writer, TRDictionary<T, TRModel> models)

writer.Write(placeholderModel.ID);
writer.Write(placeholderModel.NumMeshes);
writer.Write(startingMesh);
writer.Write(treePointer);
writer.Write(placeholderModel.IsNullTR5Model ? (ushort)0 : startingMesh);
writer.Write(placeholderModel.IsNullTR5Model ? 0 : treePointer);
writer.Write(placeholderModel.FrameOffset);
writer.Write(placeholderModel.Animation);

Expand All @@ -897,6 +940,11 @@ private void WriteModels(TRLevelWriter writer, TRDictionary<T, TRModel> models)

private TRAngleMode GetMode(TRAnimFrameRotation rot)
{
if (rot.Mode != TRAngleMode.Auto)
{
return rot.Mode;
}

if (rot.X == 0 && rot.Y == 0)
{
// OG TR2+ levels (and TRR levels) use Z here, PDP uses X. Makes no difference
Expand Down Expand Up @@ -961,6 +1009,7 @@ class PlaceholderModel
public uint FrameOffset { get; set; }
public ushort Animation { get; set; }
public int AnimCount { get; set; }
public bool IsNullTR5Model { get; set; }
}

class PlaceholderAnimation
Expand Down
13 changes: 13 additions & 0 deletions TRLevelControl/Control/TR4/TR4PDPControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using TRLevelControl.Build;
using TRLevelControl.Model;

namespace TRLevelControl;

public class TR4PDPControl : TRPDPControlBase<TR4Type>
{
public TR4PDPControl(ITRLevelObserver observer = null)
: base(observer) { }

protected override TRModelBuilder<TR4Type> CreateBuilder()
=> new(TRGameVersion.TR4, TRModelDataType.PDP, null, true);
}
13 changes: 13 additions & 0 deletions TRLevelControl/Control/TR5/TR5PDPControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using TRLevelControl.Build;
using TRLevelControl.Model;

namespace TRLevelControl;

public class TR5PDPControl : TRPDPControlBase<TR5Type>
{
public TR5PDPControl(ITRLevelObserver observer = null)
: base(observer) { }

protected override TRModelBuilder<TR5Type> CreateBuilder()
=> new(TRGameVersion.TR5, TRModelDataType.PDP, null, true);
}
1 change: 1 addition & 0 deletions TRLevelControl/Model/Common/Enums/TRAngleMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

public enum TRAngleMode
{
Auto = -1,
All = 0,
X = 0x4000,
Y = 0x8000,
Expand Down
1 change: 1 addition & 0 deletions TRLevelControl/Model/Common/TRAnimFrameRotation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

public class TRAnimFrameRotation : ICloneable
{
public TRAngleMode Mode { get; set; } = TRAngleMode.Auto;
public short X { get; set; }
public short Y { get; set; }
public short Z { get; set; }
Expand Down
14 changes: 14 additions & 0 deletions TRLevelControlTests/Base/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,20 @@ public static void ReadWritePDP(string levelName, TRGameVersion version)
control3.Write(models3, outputStream);
break;

case TRGameVersion.TR4:
observer = new TR4Observer(true);
TR4PDPControl control4 = new(observer);
TRDictionary<TR4Type, TRModel> models4 = control4.Read(inputStream);
control4.Write(models4, outputStream);
break;

case TRGameVersion.TR5:
observer = new TR5Observer(true);
TR5PDPControl control5 = new(observer);
TRDictionary<TR5Type, TRModel> models5 = control5.Read(inputStream);
control5.Write(models5, outputStream);
break;

default:
throw new NotImplementedException();
}
Expand Down
7 changes: 7 additions & 0 deletions TRLevelControlTests/TR4/IOTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ public void TestRemasteredReadWrite(string levelName)
ReadWriteLevel(levelName, TRGameVersion.TR4, true);
}

[TestMethod]
[DynamicData(nameof(GetAllLevels), DynamicDataSourceType.Method)]
public void TestPDPReadWrite(string levelName)
{
ReadWritePDP(levelName, TRGameVersion.TR4);
}

[TestMethod]
[DynamicData(nameof(GetAllLevels), DynamicDataSourceType.Method)]
public void TestAgressiveFloorData(string levelName)
Expand Down
7 changes: 7 additions & 0 deletions TRLevelControlTests/TR5/IOTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ public void TestRemasteredReadWrite(string levelName)
ReadWriteLevel(levelName, TRGameVersion.TR5, true);
}

[TestMethod]
[DynamicData(nameof(GetAllLevels), DynamicDataSourceType.Method)]
public void TestPDPReadWrite(string levelName)
{
ReadWritePDP(levelName, TRGameVersion.TR5);
}

[TestMethod]
[DynamicData(nameof(GetAllLevels), DynamicDataSourceType.Method)]
public void TestAgressiveFloorData(string levelName)
Expand Down

0 comments on commit 335c989

Please sign in to comment.