Skip to content

Commit

Permalink
Update TRR5 level IO
Browse files Browse the repository at this point in the history
TR5R rooms are now much easier to read. Some padding has also been removed.
  • Loading branch information
lahm86 committed Feb 16, 2025
1 parent 8230889 commit 3f5545a
Show file tree
Hide file tree
Showing 12 changed files with 357 additions and 132 deletions.
169 changes: 154 additions & 15 deletions TRLevelControl/Build/Room/TR5RoomBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,92 @@ public class TR5RoomBuilder
{
private static readonly string _xela = "XELA";

public static List<TR5Room> ReadRooms(TRLevelReader reader)
public static List<TR5Room> ReadRooms(TRLevelReader reader, bool remastered)
{
uint numRooms = reader.ReadUInt32();
uint numRooms = remastered ? reader.ReadUInt16() : reader.ReadUInt32();
List<TR5Room> rooms = new();
for (int i = 0; i < numRooms; i++)
{
rooms.Add(ReadRoom(reader));
rooms.Add(remastered ? ReadRemasteredRoom(reader) : ReadClassicRoom(reader));
}

return rooms;
}

private static TR5Room ReadRoom(TRLevelReader reader)
private static TR5Room ReadRemasteredRoom(TRLevelReader reader)
{
TR5Room room = new()
{
Info = reader.ReadRoomInfo(TRGameVersion.TR4),
};

ushort numPortals = reader.ReadUInt16();
room.Portals = reader.ReadRoomPortals(numPortals);

room.NumZSectors = reader.ReadUInt16();
room.NumXSectors = reader.ReadUInt16();
room.Sectors = reader.ReadRoomSectors(room.NumXSectors * room.NumZSectors, TRGameVersion.TR5);

room.Colour = reader.ReadRGBA();

ushort numLights = reader.ReadUInt16();
room.Lights = ReadLights(reader, numLights);

int numFogBulbs = reader.ReadInt32();
Queue<TR5FogBulb> fogBulbs = new(ReadFogBulbs(reader, numFogBulbs));

for (int i = 0; i < room.Lights.Count && fogBulbs.Count > 0; i++)
{
if (room.Lights[i] is TR5FogBulb)
{
room.Lights[i] = fogBulbs.Dequeue();
}
}

ushort numStatics = reader.ReadUInt16();
room.StaticMeshes = ReadStaticMeshes(reader, numStatics);

room.AlternateRoom = reader.ReadInt16();
room.Flags = (TRRoomFlag)reader.ReadInt16();
room.WaterScheme = reader.ReadByte();
room.ReverbMode = (TRPSXReverbMode)reader.ReadByte();
room.AlternateGroup = reader.ReadByte();

uint numRoomlets = reader.ReadUInt32();
for (int i = 0; i < numRoomlets; i++)
{
ushort numVertices = reader.ReadUInt16();
reader.ReadUInt16(); // water verts
reader.ReadUInt16(); // shore verts
ushort numRectangles = reader.ReadUInt16();
ushort numTriangles = reader.ReadUInt16();
reader.ReadUInt16(); // water rects
reader.ReadUInt16(); // water tris
reader.ReadTR5Vertex(); // Box min
reader.ReadTR5Vertex(); // Box max

List<TR5RoomVertex> vertices = ReadVertices(reader, numVertices, true);
List<TRMeshFace> rects = reader.ReadMeshFaces(numRectangles, TRFaceType.Rectangle, TRGameVersion.TR5);
List<TRMeshFace> tris = reader.ReadMeshFaces(numTriangles, TRFaceType.Triangle, TRGameVersion.TR5);

foreach (TRMeshFace face in rects.Concat(tris))
{
for (int j = 0; j < face.Vertices.Count; j++)
{
face.Vertices[j] += (ushort)room.Mesh.Vertices.Count;
}
}

room.Mesh.Vertices.AddRange(vertices);
room.Mesh.Rectangles.AddRange(rects);
room.Mesh.Triangles.AddRange(tris);
}

reader.ReadUInt32();
return room;
}

private static TR5Room ReadClassicRoom(TRLevelReader reader)
{
string xela = new(reader.ReadChars(4));
Debug.Assert(xela == _xela);
Expand Down Expand Up @@ -118,7 +191,7 @@ private static TR5Room ReadRoom(TRLevelReader reader)
reader.ReadUntil(dataStartPos + header.VerticesStartOffset);
foreach (TR5Roomlet roomlet in roomlets)
{
roomlet.Vertices = ReadVertices(reader, roomlet.NumVertices);
roomlet.Vertices = ReadVertices(reader, roomlet.NumVertices, false);

if (room.Mesh.Vertices.Count > 0)
{
Expand Down Expand Up @@ -293,7 +366,7 @@ private static List<TR5RoomStaticMesh> ReadStaticMeshes(TRLevelReader reader, lo
return meshes;
}

private static List<TR5RoomVertex> ReadVertices(TRLevelReader reader, long numVertices)
private static List<TR5RoomVertex> ReadVertices(TRLevelReader reader, long numVertices, bool remastered)
{
List<TR5RoomVertex> vertices = new();
for (int i = 0; i < numVertices; i++)
Expand All @@ -309,22 +382,81 @@ private static List<TR5RoomVertex> ReadVertices(TRLevelReader reader, long numVe
Z = (short)vertex.Z,
},
Normal = reader.ReadTR5Vertex(),
Colour = new(reader.ReadUInt32())
Colour = remastered ? reader.ReadRGBA() : new(reader.ReadUInt32())
});
}
return vertices;
}

public static void WriteRooms(TRLevelWriter writer, List<TR5Room> rooms)
public static void WriteRooms(TRLevelWriter writer, List<TR5Room> rooms, bool remastered)
{
writer.Write((uint)rooms.Count);
foreach (TR5Room room in rooms)
if (remastered)
{
writer.Write((ushort)rooms.Count);
rooms.ForEach(r => WriteRemasteredRoom(writer, r));
}
else
{
WriteRoom(writer, room);
writer.Write((uint)rooms.Count);
rooms.ForEach(r => WriteClassicRoom(writer, r));
}
}

private static void WriteRoom(TRLevelWriter writer, TR5Room room)
private static void WriteRemasteredRoom(TRLevelWriter writer, TR5Room room)
{
writer.Write(room.Info, TRGameVersion.TR4);
writer.Write((ushort)room.Portals.Count);
writer.Write(room.Portals);
writer.Write(room.NumZSectors);
writer.Write(room.NumXSectors);
writer.Write(room.Sectors, TRGameVersion.TR5);
writer.Write(room.Colour);

writer.Write((ushort)room.Lights.Count);
WriteLights(writer, room.Lights);

List<TR5FogBulb> fogBulbs = room.Lights.Where(l => l is TR5FogBulb).Cast<TR5FogBulb>().ToList();
writer.Write(fogBulbs.Count);
WriteFogBulbs(writer, fogBulbs);

writer.Write((ushort)room.StaticMeshes.Count);
WriteStaticMeshes(writer, room.StaticMeshes);

writer.Write(room.AlternateRoom);
writer.Write((short)room.Flags);
writer.Write((byte)room.WaterScheme);
writer.Write((byte)room.ReverbMode);
writer.Write(room.AlternateGroup);

writer.Write((uint)1); // One roomlet
writer.Write((ushort)room.Mesh.Vertices.Count);
writer.Write(Enumerable.Repeat((ushort)0, 2)); // waterVertCount, shoreVertCount
writer.Write((ushort)room.Mesh.Rectangles.Count);
writer.Write((ushort)room.Mesh.Triangles.Count);
writer.Write(Enumerable.Repeat((ushort)0, 2)); // waterRectCount, waterTriCount

// Min/max bounds
writer.Write(new TR5Vertex
{
X = TRConsts.Step4,
Y = room.Info.YTop,
Z = TRConsts.Step4,
});
writer.Write(new TR5Vertex
{
X = (room.NumXSectors - 1) * TRConsts.Step4 - 1,
Y = room.Info.YBottom,
Z = (room.NumZSectors - 1) * TRConsts.Step4 - 1,
});

WriteVertices(writer, room.Mesh.Vertices, true);
writer.Write(ConvertToMeshFaces(room.Mesh.Rectangles), TRGameVersion.TR5);
writer.Write(ConvertToMeshFaces(room.Mesh.Triangles), TRGameVersion.TR5);

writer.Write((uint)0);
}

private static void WriteClassicRoom(TRLevelWriter writer, TR5Room room)
{
TR5RoomHeader header = new()
{
Expand Down Expand Up @@ -410,7 +542,7 @@ private static void WriteRoom(TRLevelWriter writer, TR5Room room, TR5RoomHeader
writer.Write(ConvertToMeshFaces(room.Mesh.Triangles), TRGameVersion.TR5);

header.VerticesStartOffset = (uint)writer.BaseStream.Position;
WriteVertices(writer, room.Mesh.Vertices);
WriteVertices(writer, room.Mesh.Vertices, false);
}

private static void WriteHeader(TRLevelWriter writer, TR5RoomHeader header)
Expand Down Expand Up @@ -570,15 +702,22 @@ private static void WriteStaticMeshes(TRLevelWriter writer, List<TR5RoomStaticMe
}
}

private static void WriteVertices(TRLevelWriter writer, List<TR5RoomVertex> vertices)
private static void WriteVertices(TRLevelWriter writer, List<TR5RoomVertex> vertices, bool remastered)
{
foreach (TR5RoomVertex vertex in vertices)
{
writer.Write((float)vertex.Vertex.X);
writer.Write((float)vertex.Vertex.Y);
writer.Write((float)vertex.Vertex.Z);
writer.Write(vertex.Normal);
writer.Write(vertex.Colour.ToARGB());
if (remastered)
{
writer.Write(vertex.Colour);
}
else
{
writer.Write(vertex.Colour.ToARGB());
}
}
}

Expand Down
9 changes: 6 additions & 3 deletions TRLevelControl/Build/TRModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class TRModelBuilder<T>
private readonly TRGameVersion _version;
private readonly TRModelDataType _dataType;
private readonly ITRLevelObserver _observer;
private readonly bool _remastered;


private List<TRAnimation> _animations;
private List<TRAnimDispatch> _dispatches;
Expand All @@ -24,11 +26,12 @@ public class TRModelBuilder<T>
private Dictionary<TRAnimDispatch, short> _dispatchToAnimMap;
private Dictionary<TRAnimDispatch, short> _dispatchFrameBase;

public TRModelBuilder(TRGameVersion version, TRModelDataType dataType, ITRLevelObserver observer = null)
public TRModelBuilder(TRGameVersion version, TRModelDataType dataType, ITRLevelObserver observer = null, bool remastered = false)
{
_version = version;
_dataType = dataType;
_observer = observer;
_remastered = remastered;
}

public TRDictionary<T, TRModel> ReadModelData(TRLevelReader reader, IMeshProvider meshProvider)
Expand Down Expand Up @@ -197,7 +200,7 @@ private void ReadModels(TRLevelReader reader)
Animation = reader.ReadUInt16()
});

if (_version == TRGameVersion.TR5)
if (_version == TRGameVersion.TR5 && !_remastered)
{
reader.ReadUInt16(); // Skip padding
}
Expand Down Expand Up @@ -882,7 +885,7 @@ private void WriteModels(TRLevelWriter writer, TRDictionary<T, TRModel> models)
writer.Write(placeholderModel.FrameOffset);
writer.Write(placeholderModel.Animation);

if (_version == TRGameVersion.TR5)
if (_version == TRGameVersion.TR5 && !_remastered)
{
writer.Write(_tr5ModelPadding);
}
Expand Down
8 changes: 4 additions & 4 deletions TRLevelControl/Build/TRSpriteBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ public TRSpriteBuilder(TRGameVersion version)
_version = version;
}

public TRDictionary<T, TRSpriteSequence> ReadSprites(TRLevelReader reader)
public TRDictionary<T, TRSpriteSequence> ReadSprites(TRLevelReader reader, bool remastered = false)
{
if (_version >= TRGameVersion.TR4)
{
string sprMarker = new(reader.ReadChars(_sprMarker.Length));
Debug.Assert(sprMarker == _sprMarker);
if (_version == TRGameVersion.TR5)
if (_version == TRGameVersion.TR5 && !remastered)
{
byte end = reader.ReadByte();
Debug.Assert(end == 0);
Expand Down Expand Up @@ -55,12 +55,12 @@ public TRDictionary<T, TRSpriteSequence> ReadSprites(TRLevelReader reader)
return sprites;
}

public void WriteSprites(TRLevelWriter writer, TRDictionary<T, TRSpriteSequence> sprites)
public void WriteSprites(TRLevelWriter writer, TRDictionary<T, TRSpriteSequence> sprites, bool remastered = false)
{
if (_version >= TRGameVersion.TR4)
{
writer.Write(_sprMarker.ToCharArray());
if (_version == TRGameVersion.TR5)
if (_version == TRGameVersion.TR5 && !remastered)
{
writer.Write((byte)0);
}
Expand Down
Loading

0 comments on commit 3f5545a

Please sign in to comment.