Skip to content

Latest commit

 

History

History
1060 lines (794 loc) · 48.5 KB

room_geometry.asc

File metadata and controls

1060 lines (794 loc) · 48.5 KB

Room Geometry

Overview

A room in TR2 is simply a rectangular three-dimensional area. A room may be indoors'' or outdoors,'' may or may not be enclosed, may be accessible or inaccessible to Lara, may or may not contain doors or objects.

All rooms have portals,'' called doors'' in some documentation, which are pathways to adjacent rooms. There are two kinds of portals — visibility portals and collisional portals. Visibility portals are for determining how much of a room (if any) is visible from another room, while collisional portals are for enabling an object to travel from one room to another.

The visibility portals are most likely for doing ``portal rendering'', which is a visibility-calculation scheme that goes as follows: the viewpoint is a member of some room, which is then listed as visible from it. This room’s portals are checked for visibility from that viewpoint, and visible portals have their opposite-side rooms marked as visible. These rooms are then checked for portals that are visible from the viewpoint through the viewpoint’s room’s portals, and visible ones have their opposite-side rooms marked as visible. This operation is repeated, with viewing through intermediate portals, until all visible portals have been found. The result is a tree of rooms, starting from the viewpoint’s room; only those rooms and their contents need to be rendered.

It is clear that both visibility and collision calculations require that objects have room memberships given for them, and indeed we shall find that most map objects have room memberships.

Rooms may overlap; as we shall see, this is involved in how horizontal collisional portals are implemented. However, different rooms may overlap without either being directly accessible from the other; there are several inadvertent examples of such 5D space'' in the Tomb Raider series. The only possibly deliberate example I know of is the flying saucer in Area 51'' in TR3, whose interior is bigger than its exterior.

A room can have an alternate room'' specified for it; that means that that room can be replaced by that alternate as the game is running. This trick is used to produce such tricks as empty rooms vs. rooms full of water, scenery rearrangements (for example, the dynamited house in Bartoli’s Hideout'' in TR2), and so forth. An empty room is first created, and then a full room is created at its location from a copy of it. The empty room then has that full room set as its alternate, and when that room is made to alternate, one sees a full room rather than an empty one.

The rooms are stored sequentially in an array, and Room Numbers'' are simply indices into this array (e.g. Room Number 5'' is simply Rooms[5]; the first room is Rooms[0]).

Rooms are divided into sectors (or squares), which are 1024x1024 unit squares that form a grid on the \(X-Z\) plane. Sectors are the defining area for floor/ceiling heights and triggers (e.g. a tiger appears and attacks when Lara steps on a given square); the various attributes of each sector are stored in the Sector Data (described in this section) and the [FloorData]. As an aside, Sectors correspond to the squares,'' easily visible in all of the Tomb Raider games, that experienced players count when gauging jumps; they also account for some of the game’s less-appealing graphic artifacts. Careful tiling and texture construction can make these squares'' almost invisible.

Note

Each room has two types of surface geometry — rendered and collisional. The former are what is seen, while the latter control how objects collide and 'interact' with the world. Furthermore, these two types are specified separately in the room data — each type is completely independent of other, i. e. collisional geometry shouldn’t exactly match visible room geometry.

While this distinctive feature was never used in originals (collisional room meshes'' fully resembled visible room meshes''), it is now extensively used by level editing community with the help of a program called meta2tr. This utility allows level builder to replace visible geometry generated by TRLE with any custom geometry, usually modelled in Metasequoia 3D editor (hence the name of meta2tr utility).

Rooms are defined with a complex structure, which is described below ``inside-out,'' meaning that the smaller component structures are described first, followed by the larger structures that are built using the smaller structures.

Room Structures

Room header

\(X / Z\) indicate the base position of the room mesh in world coordinates (\(Y\) is always zero-relative)

struct tr_room_info    // 16 bytes
{
    int32_t x;             // X-offset of room (world coordinates)
    int32_t z;             // Z-offset of room (world coordinates)
    int32_t yBottom;
    int32_t yTop;
};

yBottom is actually largest value, but indicates lowest point in the room. yTop is actually smallest value, but indicates highest point in the room.

TR5 uses an extended version of this structure:

struct tr5_room_info    // 20 bytes
{
    int32_t x;             // X-offset of room (world coordinates)
    int32_t y;             // Y-offset of room (world coordinates) - only in TR5
    int32_t z;             // Z-offset of room (world coordinates)
    int32_t yBottom;
    int32_t yTop;
};

The additional y value is usually 0.

Portal Structure

These portals, sometimes called doors'', define the view from a room into another room. This can be through a real'' door, a window, or even some open area that makes the rooms look like one big room. Note that rooms'' here are really just areas; they aren’t necessarily enclosed. The portal structure below defines only visibility portals, not an actual door model, texture, or action (if any). And if the portal is not properly oriented, the camera cannot see'' through it.

struct tr_room_portal  // 32 bytes
{
    uint16_t  AdjoiningRoom; // Which room this portal leads to
    tr_vertex Normal;
    tr_vertex Vertices[4];
};

Normal field tells which way the portal faces (the normal points away from the adjacent room; to be seen through, it must point toward the viewpoint).

Vertices are the corners of this portal (the right-hand rule applies with respect to the normal). If the right-hand-rule is not followed, the portal will contain visual artifacts instead of a viewport to AdjoiningRoom.

Note

The original portal testing algorithm performs a breadth-first visibility check of the bounding boxes of the screen-projected portal vertices, and also compares the vector from the camera to the first portal vertex to its normal. This works pretty fine, unless there are denormalized bounding boxes, which the original TR solves by expanding the portal bounding box to its maximum if a projected edge crosses the screen plane or its boundaries.

Room Sector Structure

All the geometry specified here is collisional geometry.

struct tr_room_sector // 8 bytes
{
    uint16_t FDindex;    // Index into FloorData[]
    uint16_t BoxIndex;   // Index into Boxes[] (-1 if none)
    uint8_t  RoomBelow;  // 255 is none
    int8_t   Floor;      // Absolute height of floor
    uint8_t  RoomAbove;  // 255 if none
    int8_t   Ceiling;    // Absolute height of ceiling
};

Floor and Ceiling are signed numbers of 256 units of height (relative to 0) — e.g. Floor 0x04 corresponds to \(Y = 1024\) in world coordinates. Therefore, 256 units is a minimum vertical stride of collisional geometry. However, this rule could be broken by specific entities, which Lara can stand on. But horizontal sector dimensions, which, as mentioned earlier, are 1024 x 1024 (in world coordinates), could not. Therefore, minimal horizontal platform dimensions, on which Lara can stand and grab, are 1024 x 1024 as well.

Note
This implies that, while \(X\) and \(Z\) can be quite large, \(Y\) is constrained to -32768..32512.

Floor and Ceiling value of 0x81 is a magic number used to indicate impenetrable walls around the sector. Floor values are used by the game engine to determine what objects Lara can traverse and how. Relative steps of 1 (-256) can be walked up; steps of 2..7 (-512..-1792) can/must be jumped up; steps larger than 7 (-2048..-32768) cannot be jumped up (too tall).

RoomAbove and RoomBelow values indicate what neighboring rooms are in these directions — the number of the room below this one and the number of the room above this one. If RoomAbove is not none, then the ceiling is a collisional portal to that room, while if RoomBelow is not none, then the floor is a 'collisional portal' to that room.

Also, RoomBelow value is extensively used by engine to determine actual sector data and triggers in so-called stacked room setups, when one room is placed above another through collisional portal. The thing is, engine uses sector data and triggers only for the lowest sector of the stacked room setup, so it recursively scans for a lowest room to determine which sector to use.

FDindex is a pointer to specific entry in [FloorData] array, which keeps all the information about sector flags, triggers and other parameters. While it is implied that one FDindex entry may be shared between several sectors, it is usually not the case with original Tomb Raider levels built with TRLE. However, 'Dxtre3d' takes advantage of this feature and may optimize similar sectors to share same FDindex pointer.

BoxIndex is a pointer to special [Boxes] array entry, which is basically a subset of sectors with same height configuration. It is primarily used for AI pathfinding (see the Non-player character behaviour chapter for more details).

{TR3}{TR4}{TR5} In these games, BoxIndex field is more complicated, and actually contains two packed values. Bits 4..14 contain the actual box index, and bits 0..3 contain material index, which is used to produce specific footstep sound, when Lara is walking or running in this sector. On PlayStation game versions, this index was also used to determine if footprint textures should be applied to this particular place. Both procedures are invoked via FOOTPRINT_FX flipeffect, which is described in corresponding section.

Majority of material index values are the same across game versions, but some of them exist only in particular game. Here is the description:

  • 0 — Mud

  • 1 — Snow (TR3 and TR5 only)

  • 2 — Sand

  • 3 — Gravel

  • 4 — Ice (TR3 and TR5 only)

  • 5 — Water (unused, as water footstep is only activated in water rooms)

  • 6 — Stone (unused, as it is default footstep sound)

  • 7 — Wood

  • 8 — Metal

  • 9 — Marble (TR4 only)

  • 10 — Grass (same sound effect as sand)

  • 11 — Concrete (same sound effect as stone, hence unused)

  • 12 — Old wood (same sound effect as wood)

  • 13 — Old metal (same sound effect as metal)

Mud, snow, sand, grass and maybe some other materials produce footprints in PlayStation version.

Furthermore, in TR3-5, actual box index may contain special value 2047, which is most likely indicates that this sector is a slope on which Lara can slide (and, therefore, possibly impassable by most NPCs).

Room Light Structure

Note
TR engines always used static room lights only for processing lighting on entities (such as Lara, enemies, doors, and others). This is called external lighting. For room meshes, they used so-called internal, or pre-baked lighting, which is done on level building stage: lights are calculated and applied to room faces via vertex colours. There is no way to change room lighting when the level is compiled — meaning, any changes in light positions, intensities and colour won’t affect room faces.

There are four different types of room light structures. First one is used in TR1-2, second is used in TR3, third is used in TR4, and fourth is used in TR5. Here is the description of each:

TR1 Room Lighting
struct tr_room_light   // 18 bytes
{
     int32_t x, y, z;       // Position of light, in world coordinates
    uint16_t Intensity1;    // Light intensity
    uint32_t Fade1;         // Falloff value
};

X/Y/Z are in world coordinates. Intensity1/Intensity2 are almost always equal. This lighting only affects externally-lit objects. Tomb Raider 1 has only the first of the paired Intensity and Fade values.

Intensity1 ranges from 0 (dark) to 0x1FFF (bright). However, some rooms occasionally have some lights with intensity greater than 0x1FFF (for example, look at room #9, 2nd light in level1.phd). Fade1 is the maximum distance the light shines on, and ranges from 0 to 0x7FFF.

TR2 Room Lighting

TR2 uses an extended version of TR1 light structure:

struct tr2_room_light   // 24 bytes
{
     int32_t x, y, z;       // Position of light, in world coordinates
    uint16_t Intensity1;    // Light intensity
    uint16_t Intensity2;    // Only in TR2
    uint32_t Fade1;         // Falloff value
    uint32_t Fade2;         // Only in TR2
};

Intensity2 and Fade2 values are seemingly not used. Intensity1 can go very well beyond 0x1FFF, right to 0x7FFF (ultra bright light). Above 0x7FFF, it is always black, so the number is pseudo-signed (negative values are always treated as zero).

TR3 Room Lighting

TR3 introduced a rudimentary ``light type'' concept. Although only possible types are sun (type 0) and point light (type 1). It is not clear how sun affetcts room lighting, but somehow it interpolates with all other light objects in a given room. Both light types are kept using the same structure, with two last 8 bytes used as union.

struct tr3_room_light   // 24 bytes
{
        int32_t x, y, z;       // Position of light, in world coordinates
      tr_colour Colour;        // Colour of the light
	    uint8_t LightType;     // Only 2 types - sun and point lights
		  union				   // 8 bytes
		  {
		        tr3_room_spotlight;
		        tr3_room_pointlight;
		  }
};

Depending on LightType, last 8 bytes are parsed differently, either as tr3_room_spotlight or tr3_toom_sun structure:

struct tr3_room_sun // 8 bytes
{
        int16_t nx, ny, nz; // Normal?
        int16_t Unused;
};

nx, ny and nz is most likely a normal.

struct tr3_room_spotlight // 8 bytes
{
        int32_t Intensity;
        int32_t Fade;		// Falloff value
};

Intensity is the power of the light and ranges mainly from 0 (low power) to 0x1FFF (high power). Fade is the distance max the light can shine on. Range is mainly from 0 to 0x7FFF.

TR4 Room Lighting
struct tr4_room_light   // 46 bytes
{
        int32_t x, y, z;       // Position of light, in world coordinates
      tr_colour Colour;        // Colour of the light

        uint8_t LightType;
        uint8_t Unknown;       // Always 0xFF?
        uint8_t Intensity;

          float In;            // Also called hotspot in TRLE manual
          float Out;           // Also called falloff in TRLE manual
          float Length;
          float CutOff;

          float dx, dy, dz;    // Direction - used only by sun and spot lights
};

LightType was extended and is now somewhat similar to D3D light type, but there are some differences.

  • 0 — Sun

  • 1 — Light

  • 2 — Spot

  • 3 — Shadow

  • 4 — Fog bulb

Note

Fog bulb is a special case of room light, which actually don’t work as usual light. It serves as a point in space, where a kind of volumetric fog effect is generated. It works only if user has enabled corresponding option in game setup.

Fog bulbs don’t use Colour field to define its colour. However, Red field of a Colour structure is used to define fog density. Colour itself can be only changed with special flipeffect #28 trigger function, which takes a value from the timer field of the trigger to index into hardcoded RGB table of pre-defined colours. The table consists of 28 RGB values:

00

0 = 0,0,0

07

7 = 0,64,192

14

14 = 111,255,223

21

21 = 0,30,16

01

1 = 245,200,60

08

8 = 0,128,0

15

15 = 244,216,152

22

22 = 250,222,167

02

2 = 120,196,112

09

9 = 150,172,157

16

16 = 248,192,60

23

23 = 218,175,117

03

3 = 202,204,230

10

10 = 128,128,128

17

17 = 252,0,0

24

24 = 225,191,78

04

4 = 128,64,0

11

11 = 204,163,123

18

18 = 198,95,87

25

25 = 77,140,141

05

5 = 64,64,64

12

12 = 177,162,140

19

19 = 226,151,118

26

26 = 4,181,154

06

6 = 243,232,236

13

13 = 0,223,191

20

20 = 248,235,206

27

27 = 255,174,0

TR5 Room Lighting
struct tr5_room_light   // 88 bytes
{
        float x, y, z;       // Position of light, in world coordinates
        float r, g, b;       // Colour of the light

       uint32_t Separator    // Dummy value = 0xCDCDCDCD

        float In;            // Cosine of the IN value for light / size of IN value
        float Out;           // Cosine of the OUT value for light / size of OUT value
        float RadIn;         // (IN radians) * 2
        float RadOut;        // (OUT radians) * 2
        float Range;         // Range of light

        float dx, dy, dz;    // Direction - used only by sun and spot lights
      int32_t x2, y2, z2;    // Same as position, only in integer.
      int32_t dx2, dy2, dz2; // Same as direction, only in integer.

      uint8_t LightType;

      uint8_t Filler[3];     // Dummy values = 3 x 0xCD
};

x,y,z values shouldn’t be used by sun type light, but sun seems to have a large x value (9 million, give or take), a zero y value, and a small z value (4..20) in the original TR5 levels.

In and Out values aren’t used by sun type. For the spot type, these are the hotspot and falloff angle cosines. For the light and shadow types, these are the TR units for the hotspot / falloff (1024 = 1 sector).

RadIn, RadOut and Range are only used by the spot light type.

dx, dy and dz values are used only by the sun and spot type lights. They describe the directional vector of the light. This can be obtained by:

  • if both x and y \(\mathnormal{LightDirectionVectorX} = \cos(X) * \sin(Y)\)

  • \(\mathnormal{LightDirectionVectorY} = \sin(X)\)

  • \(\mathnormal{LightDirectionVectorZ} = \cos(X) * \cos(Y)\)

x2, y2, z2, dx2, dy2 and dz2 values repeat previous corresponding information in long data types instead of floats.

Additionally, TR5 implements separate data structure for fog bulbs, which are kept separately from all other lights in room structure:

struct tr5_fog_bulb   // 36 bytes
{
        float x, y, z;       // Position of light, in world coordinates
        float r, g, b;       // Colour of the light

       uint32_t Separator    // Dummy value = 0xCDCDCDCD

        float In;            // Size of IN value
        float Out;           // Size of OUT value
};

Room Vertex Structure

This defines the vertices within a room. As mentioned above, room lighting is internal vertex lighting, except for necessarily external sources like flares, flame emitters and gunflashes. Room ambient lights and point sources are ignored.

As TR3 introduced colored lighting, room vertex structure drastically changed. It changed once again in TR5, when floating-point numbers were introduced. So we’ll define vertex structure for TR1-2, TR3-4 and TR5 independently.

TR1-2 Room Vertex Structure
struct tr_room_vertex  // 8 bytes
{
     tr_vertex Vertex;
       int16_t Lighting;
};

Vertex is the coordinates of the vertex, relative to [tr_room_info] x and z values.

Lighting ranges from 0 (bright) to 0x1FFF (dark). This value is ignored by TR2, and Lighting2 is used instead with the same brightness range.

TR2 uses an extended version of the structure:

struct tr2_room_vertex  // 12 bytes
{
     tr_vertex Vertex;
       int16_t Lighting;
      uint16_t Attributes; // A set of flags for special rendering effects
       int16_t Lighting2;  // Almost always equal to Lighting1
};

Attributes field is a set of flags, and their meaning is:

  • Bits 0..4 are only used together in combination with the LightMode field of the [tr_room] structure. See below.

  • Bit 15: When used in room filled with water, don’t move the vertices of the room when viewed from above (normally, when viewed from above, the vertices of a room filled with water moves to simulate the refraction of lights in water). Note that when viewed from inside the room filled with water, the vertices of the other rooms outside still moves.

Lighting field is ignored by TR2, and Lighting2 is used instead with the same brightness range — from 0 (bright) to 0x1FFF (dark).

TR3-4 Room Vertex Structure
struct tr3_room_vertex  // 12 bytes
{
     tr_vertex Vertex;
       int16_t Lighting;   // Value is ignored!
      uint16_t Attributes; // A set of flags for special rendering effects
      uint16_t Colour;     // 15-bit colour
};

Lighting value is ignored by the engine, as now each vertex has its own defined 15-bit colour (see below).

Attributes bit flags were extended. Also, old bits 0..4 aren’t working properly anymore, because effect lighting was broken in TR3. Here is the list of new flags:

  • Bit 13: Water / quicksand surface ``wave'' movement. Brightness is also shifting, if this flag is set (but it’s not the same type as with Bit 14, it’s much less noticeable).

  • Bit 14: Simulates caustics by constantly shifting vertex colour brightness. Used mainly in underwater rooms, but can be used in rooms without water. In TR2, there was a similar effect, but it was assigned for all vertices in any water room.

  • Bit 15: {TR4}{TR5} Broken in these game versions. Instead, same ``wave'' effect is produced as with Bit 13, but with slightly less glow.

Note
The amplitude of the `wave'' effect depends on `WaterScheme value specified in room structure.

Colour value specifies vertex colour in 15-bit format (each colour occupies 5 bits). Therefore, each colour value’s maximum is 31. You can use this code to get each colour:

  • Red: ((Colour & 0x7C00) >> 10)

  • Green: ((Colour & 0x03E0) >> 5)

  • Blue: (Colour & 0x001F)

TR5 Room Vertex Structure

In TR5, room vertex structure was almost completely changed. Coordinates were converted to floats, and normal was added:

struct tr5_room_vertex  // 28 bytes
{
    tr5_vertex Vertex;     // Vertex is now floating-point
    tr5_vertex Normal;
      uint32_t Colour;     // 32-bit colour
};

There is no more Attributes field in room vertex structure for TR5.

Room Sprite Structure

struct tr_room_sprite  // 4 bytes
{
    int16_t Vertex;       // Offset into vertex list
    int16_t Texture;      // Offset into sprite texture list
};

Vertex indicates an index into room vertex list (Room.Vertices[room_sprite.Vertex]), which acts as a point in space where to display a sprite.

Texture is an index into the sprite texture list.

Room Data Structure

This is the whole geometry of the ``room,'' including walls, floors, ceilings, and other embedded landscape. It does not include objects that Lara can interact with (keyholes, moveable blocks, moveable doors, etc.), neither does it include static meshes (mentioned below in the next section).

The surfaces specified here are rendered surfaces.

Caution
This is not a `real'' C/C++ structure, in that the arrays are sized by the `NumXXX elements that precede them. Also [tr_room_vertex] could be replaced by any other version-specific room vertex type ([tr3_room_vertex], etc.).
virtual struct tr_room_data    // (variable length)
{
    int16_t NumVertices;                   // Number of vertices in the following list
    tr2_room_vertex Vertices[NumVertices]; // List of vertices (relative coordinates)

    int16_t NumRectangles;                 // Number of textured rectangles
    tr_face4 Rectangles[NumRectangles];    // List of textured rectangles

    int16_t NumTriangles;                  // Number of textured triangles
    tr_face3 Triangles[NumTriangles];      // List of textured triangles

    int16_t NumSprites;                    // Number of sprites
    tr_room_sprite Sprites[NumSprites];    // List of sprites
};

Room Static Mesh Structure

Positions and IDs of static meshes (e.g. skeletons, spiderwebs, furniture, trees). This is comparable to the [tr_entity] structure, except that static meshes have no animations and are confined to a single room.

TR1 Room Static Mesh Structure
struct tr_room_staticmesh  // 18 bytes
{
    uint32_t x, y, z;    // Absolute position in world coordinates
    uint16_t Rotation;
    uint16_t Intensity1;
    uint16_t MeshID;     // Which StaticMesh item to draw
};

Intensity1 ranges from 0 (bright) to 0x1FFF (dark).

In Rotation field, high two bits (0xC000) indicate steps of 90 degrees (e.g. (Rotation >> 14) * 90). However, when parsing this value, no extra bitshifting is needed, as you can simply interpret it using this formula:

float Real_Rotation = (float)Rotation / 16384.0f * -90;
TR2 Room Static Mesh Structure

TR2 again uses an extended version:

struct tr2_room_staticmesh  // 20 bytes
{
    uint32_t x, y, z;    // Absolute position in world coordinates
    uint16_t Rotation;
    uint16_t Intensity1;
    uint16_t Intensity2; // Absent in TR1
    uint16_t MeshID;     // Which StaticMesh item to draw
};

Intensity2 is seemingly not used, as changing this value does nothing.

TR3-5 Room Static Mesh Structure
virtual struct tr3_room_staticmesh  // 20 bytes
{
    uint32_t x, y, z;    // Absolute position in world coordinates
    uint16_t Rotation;
    uint16_t Colour;     // 15-bit colour
    uint16_t Unused;     // Not used!
    uint16_t MeshID;     // Which StaticMesh item to draw
};

Colour value specifies vertex colour in 15-bit format (each colour occupies 5 bits): 0x0[red]RRRRR[green]GGGGG[blue]BBBBB. Therefore, each colour value’s maximum is 31. You can use this code to get each colour:

  • Red: ((Colour & 0x7C00) >> 10)

  • Green: ((Colour & 0x03E0) >> 5)

  • Blue: (Colour & 0x001F)

TR5 Room Structure Changes

In TR5 the room format was drastically changed. The room itself is made up of sections. These sections encompass a 3x3 sector grid (actually 3069x3069 pixels). Historically, these sections are referred as layers, however, more proper name for them is volumes. Layers are organized in a quadtree-like structure, and their purpose was presumably optimizing rendering by some kind of space partitioning and culling invisible volumes.

Another thing to note is that some rooms in TR5 do not actually contain visible mesh data. If concerned, we will refer to these rooms as null rooms.

TR5 Room Layer Structure

struct tr5_room_layer   // 56 bytes
{
    uint32_t NumLayerVertices;   // Number of vertices in this layer (4 bytes)
    uint16_t UnknownL1;
    uint16_t NumLayerRectangles; // Number of rectangles in this layer (2 bytes)
    uint16_t NumLayerTriangles;  // Number of triangles in this layer (2 bytes)
    uint16_t UnknownL2;

    uint16_t Filler;             // Always 0
    uint16_t Filler2;            // Always 0

    // The following 6 floats define the bounding box for the layer

    float    LayerBoundingBoxX1;
    float    LayerBoundingBoxY1;
    float    LayerBoundingBoxZ1;
    float    LayerBoundingBoxX2;
    float    LayerBoundingBoxY2;
    float    LayerBoundingBoxZ2;

    uint32_t Filler3;     // Always 0 (4 bytes)
    uint32_t UnknownL6;   // Unknown
    uint32_t UnknownL7;   // Unknown
    uint32_t UnknownL8;   // Always the same throughout the level.
}

UnknownL2 appears to be the number of double sided textures in this layer, however is sometimes 1 off (2 bytes).

The Whole Room Structure

Here’s where all the room data come together.

Room structure differs drastically across different game versions (especially in TR5). For this reason, we will define each version of Room structure independently, to avoid confusion. Also, version-specific fields will be described in each version’s section in a `backwards-compatible'' manner, while common fields with version-specific variations, such as `Flags, will be described afterwards in separate section.

'These are not `real'' C/C++ structures, in that the arrays are sized by the `NumXXX elements that precede them.'

TR1 Room Structure

As it’s stored in the file, the [tr_room_info] structure comes first, followed by a uint32_t NumDataWords, which specifies the number of 16-bit words to follow. Those data words must be parsed in order to interpret and construct the variable-length arrays of vertices, meshes, doors, and sectors. Such setup is also applicable to all variations of room structures, except [tr5_room], which will be described independently.

virtual struct tr_room  // (variable length)
{
    tr_room_info info;           // Where the room exists, in world coordinates

    uint32_t NumDataWords;       // Number of data words (uint16_t's)
    uint16_t Data[NumDataWords]; // The raw data from which the rest of this is derived

    tr_room_data RoomData;       // The room mesh

    uint16_t NumPortals;                 // Number of visibility portals to other rooms
    tr_room_portal Portals[NumPortals];  // List of visibility portals

    uint16_t NumZsectors;                                  // ``Width'' of sector list
    uint16_t NumXsectors;                                  // ``Height'' of sector list
    tr_room_sector SectorList[NumXsectors * NumZsectors];  // List of sectors in this room

    int16_t AmbientIntensity;

    uint16_t NumLights;                 // Number of lights in this room
    tr_room_light Lights[NumLights];    // List of lights

    uint16_t NumStaticMeshes;                            // Number of static meshes
    tr2_room_staticmesh StaticMeshes[NumStaticMeshes];   // List of static meshes

    int16_t AlternateRoom;
    int16_t Flags;
};

AmbientIntensity is a brightness value which affects only externally-lit objects. It ranges from 0 (bright) to 0x1FFF (dark).

AlternateRoom (or, as it is called in TRLE terms, flipped room) is the number of the room that this room can flip with. In the terms of the gameplay, flipped room is a state change of the same room — for example, empty or flooded with water, filled with sand or debris. Alternate room usually has the same boundaries as original room, but altered geometry and/or texturing. Detailed description of alternate rooms will be provided in a separate section.

TR2 Room Structure

virtual struct tr2_room  // (variable length)
{
    tr_room_info info;           // Where the room exists, in world coordinates

    uint32_t NumDataWords;       // Number of data words (uint16_t's)
    uint16_t Data[NumDataWords]; // The raw data from which the rest of this is derived

    tr_room_data RoomData;       // The room mesh

    uint16_t NumPortals;                 // Number of visibility portals to other rooms
    tr_room_portal Portals[NumPortals];  // List of visibility portals

    uint16_t NumZsectors;                                  // ``Width'' of sector list
    uint16_t NumXsectors;                                  // ``Height'' of sector list
    tr_room_sector SectorList[NumXsectors * NumZsectors];  // List of sectors in this room

    int16_t AmbientIntensity;
    int16_t AmbientIntensity2;  // Usually the same as AmbientIntensity
    int16_t LightMode;

    uint16_t NumLights;                 // Number of point lights in this room
    tr_room_light Lights[NumLights];    // List of point lights

    uint16_t NumStaticMeshes;                            // Number of static meshes
    tr_room_staticmesh StaticMeshes[NumStaticMeshes];   // List of static meshes

    int16_t AlternateRoom;
    int16_t Flags;
};

AmbientIntensity2 value is usually equal to AmbientIntensity value. Seems it’s not used.

LightMode specifies lighting mode special effect, which is applied to all room vertices in conjunction with 5 lowest bits of Attributes field belonging to [tr_room_vertex] structure. Here we will refer these 5 bits value to as effect_value:

  • 0 — Normal lighting mode, no special effects.

  • 1 — Produces flickering effect, with effect_value acting the same way — as intensity multiplier.

  • 2 — If effect_value is in 1-15 range, then vertex lighting is cyclically fading to more bright value. The lower the value is, the deeper the fade to full vertex lighting is. If effect_value is in 17-30 range (not 31!), then vertex lighting is cyclically fading to more dark value. The higher the value is, the deeper the fade to black is. If effect_value is 16 or 0, no effect is produced. So practically, effect_value serves as a multiplier to overall effect brightness.

  • 3 — If sunset gameflow script opcode is set, rooms with this light type will gradually dim all lights for 20 minutes. This happens in Bartoli’s Hideout. Sunset state isn’t saved in savegames and will be reset on reload.

TR3 Room Structure

virtual struct tr3_room  // (variable length)
{
    tr_room_info info;           // Where the room exists, in world coordinates

    uint32_t NumDataWords;       // Number of data words (uint16_t's)
    uint16_t Data[NumDataWords]; // The raw data from which the rest of this is derived

    tr_room_data RoomData;       // The room mesh

    uint16_t NumPortals;                 // Number of visibility portals to other rooms
    tr_room_portal Portals[NumPortals];  // List of visibility portals

    uint16_t NumZsectors;                                  // ``Width'' of sector list
    uint16_t NumXsectors;                                  // ``Height'' of sector list
    tr_room_sector SectorList[NumXsectors * NumZsectors];  // List of sectors in this room

    int16_t AmbientIntensity;   // Affects externally-lit objects
    int16_t LightMode;  		// Broken in this game version

    uint16_t NumLights;                 // Number of point lights in this room
    tr3_room_light Lights[NumLights];   // List of point lights

    uint16_t NumStaticMeshes;                            // Number of static meshes
    tr_room_staticmesh StaticMeshes[NumStaticMeshes];    // List of static meshes

    int16_t AlternateRoom;
    int16_t Flags;

    uint8_t WaterScheme;
    uint8_t ReverbInfo;

    uint8_t Filler;  // Unused.
};

LightMode types are broken and produce aburpt "bumpy ceiling" effect. It happens because internally same data field was reused for vertex waving effects (seen in quicksand and water rooms)

TR4 Room Structure

virtual struct tr4_room  // (variable length)
{
    tr_room_info info;           // Where the room exists, in world coordinates

    uint32_t NumDataWords;       // Number of data words (uint16_t's)
    uint16_t Data[NumDataWords]; // The raw data from which the rest of this is derived

    tr_room_data RoomData;       // The room mesh

    uint16_t NumPortals;                 // Number of visibility portals to other rooms
    tr_room_portal Portals[NumPortals];  // List of visibility portals

    uint16_t NumZsectors;                                  // ``Width'' of sector list
    uint16_t NumXsectors;                                  // ``Height'' of sector list
    tr_room_sector SectorList[NumXsectors * NumZsectors]; // List of sectors in this room

    uint32_t RoomColour;        // In ARGB format!

    uint16_t NumLights;                 // Number of point lights in this room
    tr4_room_light Lights[NumLights];   // List of point lights

    uint16_t NumStaticMeshes;                           // Number of static meshes
    tr_room_staticmesh StaticMeshes[NumStaticMeshes];   // List of static meshes

    int16_t AlternateRoom;
    int16_t Flags;

    uint8_t WaterScheme;
    uint8_t ReverbInfo;

    uint8_t AlternateGroup;  // Replaces Filler from TR3
};

RoomColour replaces AmbientIntensity and AmbientIntensity2 values from [tr2_room] structure. Note it’s not in [tr_colour4] format, because colour order is reversed. It should be treated as ARGB, where A is unused.

AlternateGroup was introduced in TR4 to solve long-existing engine limitation, which flipped all alternate rooms at once (see flipmap trigger action description in Trigger actions section). Since TR4, engine only flips rooms which have similar index in room’s AlternateGroup field and trigger operand.

TR5 Room Structure

As it was mentioned before, TR5 room structure was almost completely changed, when compared to previous versions. For example, TR5 completely throws out a concept of [tr_room_data] structure, shuffles numerous values and structures in almost chaotic manner, and introduces a bunch of completely new parameters (mostly to deal with layers). Also, there is vast amount of fillers and separators, which contain no specific data.

Note
The one possible reason for such ridiculous structure change is an attempt to crypt file format, so it won’t be accessed by unofficial level editing tools, which received major development by that time. Another possible reason is whole TR5 development process was rushed, as the team developed Tomb Raider: Angel of Darkness at the very same time.
virtual struct tr5_room // (variable length)
{
    char XELA[4];           // So-called "XELA landmark"

    uint32_t RoomDataSize;

    uint32_t Seperator;     // 0xCDCDCDCD (4 bytes)

    uint32_t EndSDOffset;
    uint32_t StartSDOffset;

    uint32_t Separator;     // Either 0 or 0xCDCDCDCD

    uint32_t EndPortalOffset;

    tr_room_info info;

    uint16_t NumZSectors;
    uint16_t NumXSectors;

    uint32_t RoomColour;   // In ARGB format!

    uint16_t NumLights;
    uint16_t NumStaticMeshes;

    uint8_t  ReverbInfo;
    uint8_t  AlternateGroup;
    uint16_t WaterScheme;

    uint32_t Filler[2];    // Both always 0x00007FFF
    uint32_t Separator[2]; // Both always 0xCDCDCDCD
    uint32_t Filler;       // Always 0xFFFFFFFF

    uint16_t AlternateRoom;
    uint16_t Flags;

    uint32_t Unknown1;
    uint32_t Unknown2;     // Always 0
    uint32_t Unknown3;     // Always 0

    uint32_t Separator;    // 0xCDCDCDCD

    uint16_t Unknown4;
    uint16_t Unknown5;

    float RoomX;
    float RoomY;
    float RoomZ;

    uint32_t Separator[4]; // Always 0xCDCDCDCD
    uint32_t Separator;    // 0 for normal rooms and 0xCDCDCDCD for null rooms
    uint32_t Separator;    // Always 0xCDCDCDCD

    uint32_t NumRoomTriangles;
    uint32_t NumRoomRectangles;

    tr5_room_light* RoomLights; // points to tr5_room_data.Lights
    tr5_fog_bulb* FogBulbs;     // points to tr5_room_data.FogBulbs

    uint32_t NumLights2;    // Always same as NumLights

    uint32_t NumFogBulbs;      // If set, there is an unknown data after Lights

    int32_t RoomYTop;
    int32_t RoomYBottom;

    uint32_t NumLayers;

    tr5_room_layer* LayerOffset;    // points to tr5_room_data.Layers
    tr5_room_vertex* VerticesOffset; // points to tr5_room_data.Vertices
    uint32_t PolyOffset;
    uint32_t PolyOffset2;   // Same as PolyOffset

    uint32_t NumVertices;

    uint32_t Separator[4];  // Always 0xCDCDCDCD
}

// Immediately after previous block

virtual struct tr5_room_data
{
    tr5_room_light Lights[NumLights];    // Data for the lights (88 bytes * NumRoomLights)
    tr5_fog_bulb FogBulbs[NumFogBulbs];  // Data for the fog bulbs (36 bytes * NumFogBulbs)
    tr_room_sector SectorList[NumXSectors * NumZSectors]; // List of sectors in this room

    uint16_t NumPortals;                 // Number of visibility portals to other rooms
    tr_room_portal Portals[NumPortals];  // List of visibility portals

    uint16_t Separator;  // Always 0xCDCD

    tr3_room_staticmesh StaticMeshes[NumStaticMeshes];   // List of static meshes

    tr5_room_layer Layers[NumLayers]; // Data for the room layers (volumes) (56 bytes * NumLayers)
    uint8_t Faces[(NumRoomRectangles * sizeof(tr_face4) + NumRoomTriangles * (tr_face3)];
    tr5_room_vertex Vertices[NumVertices];
}

XELA landmark seemingly serves as a header for room structure. It is clear that XELA is a reversed ALEX, which is most likely the name of TR5 programmer, 'Alex Davis'. It probably indicates that Alex Davis is responsible for changes in room structures.

RoomDataSize is a handy value determining the size of the following data. You can use this value to quickly parse thru to the next room.

EndSDOffset: usually this number +216 will give you the offset from the start of the room data to the end of the SectorData section. However, it is known that this uint32_t could be equal to 0xFFFFFFFF, so to calculate the end of SectorData, it is better to use the following value StartSDOffset + 216
((NumXSectors * NumZSectors)*8)
, if you need to obtain this information.

StartSDOffset: This number +216 will give you the offset from the start of the room to the start of the SectorData section.

EndPortalOffset: this number +216 will give you the offset from the start of the room to the end of the portal data.

RoomX, RoomY and RoomZ values are positions of room in world coordinates. NOTE: If room is null room, then each of these values will be 0xCDCDCDCD.

NumRoomTriangles and NumRoomRectangles are respectively the numbers of triangular and rectangular faces in a given room. NOTE: If room is null room, each of these values will be 0xCDCDCDCD.

LightDataSize is the size of the light data in bytes ('not' in [tr5_room_light] units).

RoomYTop and RoomYBottom are equal to yTop and yBottom values in [tr_room_info] structure. If room is a null room, both of these values are 0xCDCDCDCD.

NumLayers is a number of layers (volumes) in this room.

LayerOffset: this number +216 will give you an offset from the start of the room data to the start of the layer data.

VerticesOffset: this number +216 will give you an offset from the start of the room data to the start of the verex data.

PolyOffset: this number +216 will give you an offset from the start of the room data to the start of the rectangle/triangle data.

NumVertices is the size of vertex data block in bytes. Therefore, it must be a multiple of [tr5_room_vertex] size, else it means the block size is wrong.

Faces is a sequential data array for the room polygons (both [tr_face4] and [tr_face3]),

Note
Faces array is strictly linked with NumLayers value. The data is sequentially structured for each layer — at first it lists first layer’s rectangles then triangles, followed by the second layer’s rectangles and triangles, and so on, until all layers are done.

Common Fields of a Room Structure

Flags is an array of various flag bits, which meaning is as follows:

  • Bit 0 — Room is filled with water.

  • Bit 3 — {TR2}{TR3}{TR4}{TR5} Set if the skybox can be seen from this room. Used to speed things up: if no rendered room has this bit set, then the sky can never been seen, so it is not rendered. Else, if at least one visible room has this bit set, then the sky must be drawn because it is (could be) visible.

  • Bit 5 — {TR2}{TR3}{TR4}{TR5} Lara’s ponytail gets blown by the wind. Beginning with TR3, some particle types are also be blown, if they end up in such room (particle type is specified by certain particle flag).

  • Bit 6 — {TR3}{TR4}{TR5} Unknown. A lot of rooms have this bit set but it seems it does nothing…​

  • Bit 7 — {TR3}{TR4}{TR5} Different meaning in TR3 and TR4/5. In TR3, it means that room is filled with quicksand, while in TR4/5 it presumably blocks global lens flare from appearing in that room (in TRLE, checkbox which sets this flag is named NL).

  • Bit 8 — {TR3}{TR4}{TR5} Creates caustics effect similar to that used in water rooms. TRLE sets this bit when the M option is used (in the same time, the degree of fading intensity typed by the user is put in the water_scheme byte).

  • Bit 9 — {TR3}{TR4}{TR5} The room has some water reflectivity. TRLE sets this bit when the R ('reflectivity') option is used (in the same time, the amount of reflectivity typed by the user + 5 is put in the water_scheme byte). When the flag is set for normal room and there is water room below it, game engine creates ``reflection effect'' above the water surface — effectively it means that all the vertices at the bottom of the room receive caustics effect described well above.

  • Bit 10 — unused. Was re-used in NGLE as a flag specifying room with snow.

  • Bit 11 — {TR4}{TR5} Not found in any original TR levels, but when the D flag is set in the TRLE, this bit is set. Was re-used in NGLE as a flag specifying room with rain.

  • Bit 12 — {TR4}{TR5} Not found in any original TR levels, but when the P flag is set in the TRLE, this bit is set. Was also re-used in NGLE as a flag specifying cold room (a room which produce damage on Lara).

{TR3}{TR4}{TR5} WaterScheme is used for different purposes. If room is a water room, then it specifies underwater caustics patterns. If it is set for normal room placed above the water room, then it controls wave strength effect applied to the faces adjoining water room. Maximum value in both cases is 15.

{TR3}{TR4}{TR5} ReverbInfo defines room reverberation type. It affects sound postprocessing, if listener position belongs to that room. This feature was present only in PlayStation versions of the game, but not on PC. Nevertheless, the info is preserved in PC level files. Here are the types of reverberation:

  • 0 — Outside. No (or barely heard) reverberation.

  • 1 — Small room. Little reverberation.

  • 2 — Medium room.

  • 3 — Large room.

  • 4 — Pipe. Highest reverberation level. Almost never used.