Skip to content

Commit 335c989

Browse files
committed
Handle TR4R/TR5R PDP data
This updates the model builder to support TR4 and TR5 remastered, which has some changes in frame rotations and "null" model entries.
1 parent 3f5545a commit 335c989

8 files changed

Lines changed: 112 additions & 7 deletions

File tree

TRLevelControl/Build/TRModelBuilder.cs

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,15 @@ private void ReadModels(TRLevelReader reader)
207207
}
208208

209209
List<PlaceholderModel> animatedModels = _placeholderModels.FindAll(m => m.Animation != TRConsts.NoAnimation);
210+
if (_dataType == TRModelDataType.PDP && _version == TRGameVersion.TR5)
211+
{
212+
// TR5 PDP has entries for what seem like null models, with no animations, frames or meshes. These are not setup in the conventional way
213+
// per OG, with jumps in anim idx, so we address this here to avoid skewing other animation counts below.
214+
List<PlaceholderModel> invalidAnimModels = animatedModels.FindAll(m => m.ID > (uint)TR5Type.Lara && m.Animation == 0);
215+
invalidAnimModels.ForEach(m => m.Animation = TRConsts.NoAnimation);
216+
animatedModels.RemoveAll(invalidAnimModels.Contains);
217+
}
218+
210219
for (int i = 0; i < animatedModels.Count; i++)
211220
{
212221
PlaceholderModel model = animatedModels[i];
@@ -511,6 +520,14 @@ private TRAnimFrame BuildFrame(ref int frameIndex, int numRotations)
511520
: GetSingleRotation(rot0);
512521
}
513522

523+
if (_dataType == TRModelDataType.PDP && _version > TRGameVersion.TR3)
524+
{
525+
// Some TR4+ rots are marked as all despite only having one value set, so when we write
526+
// them back, we restore this mode to keep tests happy. If changing values otherwise, care
527+
// should be taken to set the mode to Auto.
528+
rot.Mode = rotMode;
529+
}
530+
514531
switch (rotMode)
515532
{
516533
case TRAngleMode.X:
@@ -548,16 +565,42 @@ private void TestTR5Changes(IEnumerable<TRModel> models)
548565
}
549566
}
550567

551-
private void DeconstructModel(T type, TRModel model)
568+
private PlaceholderModel CreateDeconstructedModel(T type, TRModel model)
552569
{
553-
PlaceholderModel placeholderModel = new()
570+
PlaceholderModel placeholder = new()
554571
{
555572
ID = (uint)(object)type,
556-
Animation = model.Animations.Count == 0 ? TRConsts.NoAnimation : (ushort)_placeholderAnimations.Count,
557-
FrameOffset = (_dataType == TRModelDataType.PDP || _observer == null)
558-
&& model.Animations.Count == 0 ? 0 : (uint)_frames.Count * sizeof(short),
559573
NumMeshes = (ushort)model.Meshes.Count,
560574
};
575+
576+
if (model.Animations.Count == 0)
577+
{
578+
if (_dataType == TRModelDataType.PDP && _version == TRGameVersion.TR5 && model.Meshes.Count == 0)
579+
{
580+
placeholder.Animation = 0;
581+
placeholder.IsNullTR5Model = true;
582+
}
583+
else
584+
{
585+
placeholder.Animation = TRConsts.NoAnimation;
586+
}
587+
588+
placeholder.FrameOffset = _dataType == TRModelDataType.PDP || _observer == null
589+
? 0
590+
: (uint)_frames.Count * sizeof(short);
591+
}
592+
else
593+
{
594+
placeholder.Animation = (ushort)_placeholderAnimations.Count;
595+
placeholder.FrameOffset = (uint)_frames.Count * sizeof(short);
596+
}
597+
598+
return placeholder;
599+
}
600+
601+
private void DeconstructModel(T type, TRModel model)
602+
{
603+
PlaceholderModel placeholderModel = CreateDeconstructedModel(type, model);
561604
_placeholderModels.Add(placeholderModel);
562605

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

881924
writer.Write(placeholderModel.ID);
882925
writer.Write(placeholderModel.NumMeshes);
883-
writer.Write(startingMesh);
884-
writer.Write(treePointer);
926+
writer.Write(placeholderModel.IsNullTR5Model ? (ushort)0 : startingMesh);
927+
writer.Write(placeholderModel.IsNullTR5Model ? 0 : treePointer);
885928
writer.Write(placeholderModel.FrameOffset);
886929
writer.Write(placeholderModel.Animation);
887930

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

898941
private TRAngleMode GetMode(TRAnimFrameRotation rot)
899942
{
943+
if (rot.Mode != TRAngleMode.Auto)
944+
{
945+
return rot.Mode;
946+
}
947+
900948
if (rot.X == 0 && rot.Y == 0)
901949
{
902950
// OG TR2+ levels (and TRR levels) use Z here, PDP uses X. Makes no difference
@@ -961,6 +1009,7 @@ class PlaceholderModel
9611009
public uint FrameOffset { get; set; }
9621010
public ushort Animation { get; set; }
9631011
public int AnimCount { get; set; }
1012+
public bool IsNullTR5Model { get; set; }
9641013
}
9651014

9661015
class PlaceholderAnimation
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using TRLevelControl.Build;
2+
using TRLevelControl.Model;
3+
4+
namespace TRLevelControl;
5+
6+
public class TR4PDPControl : TRPDPControlBase<TR4Type>
7+
{
8+
public TR4PDPControl(ITRLevelObserver observer = null)
9+
: base(observer) { }
10+
11+
protected override TRModelBuilder<TR4Type> CreateBuilder()
12+
=> new(TRGameVersion.TR4, TRModelDataType.PDP, null, true);
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using TRLevelControl.Build;
2+
using TRLevelControl.Model;
3+
4+
namespace TRLevelControl;
5+
6+
public class TR5PDPControl : TRPDPControlBase<TR5Type>
7+
{
8+
public TR5PDPControl(ITRLevelObserver observer = null)
9+
: base(observer) { }
10+
11+
protected override TRModelBuilder<TR5Type> CreateBuilder()
12+
=> new(TRGameVersion.TR5, TRModelDataType.PDP, null, true);
13+
}

TRLevelControl/Model/Common/Enums/TRAngleMode.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
public enum TRAngleMode
44
{
5+
Auto = -1,
56
All = 0,
67
X = 0x4000,
78
Y = 0x8000,

TRLevelControl/Model/Common/TRAnimFrameRotation.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
public class TRAnimFrameRotation : ICloneable
44
{
5+
public TRAngleMode Mode { get; set; } = TRAngleMode.Auto;
56
public short X { get; set; }
67
public short Y { get; set; }
78
public short Z { get; set; }

TRLevelControlTests/Base/TestBase.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,20 @@ public static void ReadWritePDP(string levelName, TRGameVersion version)
202202
control3.Write(models3, outputStream);
203203
break;
204204

205+
case TRGameVersion.TR4:
206+
observer = new TR4Observer(true);
207+
TR4PDPControl control4 = new(observer);
208+
TRDictionary<TR4Type, TRModel> models4 = control4.Read(inputStream);
209+
control4.Write(models4, outputStream);
210+
break;
211+
212+
case TRGameVersion.TR5:
213+
observer = new TR5Observer(true);
214+
TR5PDPControl control5 = new(observer);
215+
TRDictionary<TR5Type, TRModel> models5 = control5.Read(inputStream);
216+
control5.Write(models5, outputStream);
217+
break;
218+
205219
default:
206220
throw new NotImplementedException();
207221
}

TRLevelControlTests/TR4/IOTests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ public void TestRemasteredReadWrite(string levelName)
2424
ReadWriteLevel(levelName, TRGameVersion.TR4, true);
2525
}
2626

27+
[TestMethod]
28+
[DynamicData(nameof(GetAllLevels), DynamicDataSourceType.Method)]
29+
public void TestPDPReadWrite(string levelName)
30+
{
31+
ReadWritePDP(levelName, TRGameVersion.TR4);
32+
}
33+
2734
[TestMethod]
2835
[DynamicData(nameof(GetAllLevels), DynamicDataSourceType.Method)]
2936
public void TestAgressiveFloorData(string levelName)

TRLevelControlTests/TR5/IOTests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ public void TestRemasteredReadWrite(string levelName)
2424
ReadWriteLevel(levelName, TRGameVersion.TR5, true);
2525
}
2626

27+
[TestMethod]
28+
[DynamicData(nameof(GetAllLevels), DynamicDataSourceType.Method)]
29+
public void TestPDPReadWrite(string levelName)
30+
{
31+
ReadWritePDP(levelName, TRGameVersion.TR5);
32+
}
33+
2734
[TestMethod]
2835
[DynamicData(nameof(GetAllLevels), DynamicDataSourceType.Method)]
2936
public void TestAgressiveFloorData(string levelName)

0 commit comments

Comments
 (0)