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 |
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.
\(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.
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. |
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).
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:
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 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 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.
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 |
0 = 0,0,0 |
7 = 0,64,192 |
14 = 111,255,223 |
21 = 0,30,16 |
||||
1 = 245,200,60 |
8 = 0,128,0 |
15 = 244,216,152 |
22 = 250,222,167 |
||||
2 = 120,196,112 |
9 = 150,172,157 |
16 = 248,192,60 |
23 = 218,175,117 |
||||
3 = 202,204,230 |
10 = 128,128,128 |
17 = 252,0,0 |
24 = 225,191,78 |
||||
4 = 128,64,0 |
11 = 204,163,123 |
18 = 198,95,87 |
25 = 77,140,141 |
||||
5 = 64,64,64 |
12 = 177,162,140 |
19 = 226,151,118 |
26 = 4,181,154 |
||||
6 = 243,232,236 |
13 = 0,223,191 |
20 = 248,235,206 |
27 = 255,174,0 |
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
andy
\(\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
};
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.
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).
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)
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.
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.
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
};
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.
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 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.
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)
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.
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).
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.'
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.
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. Ifeffect_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. Ifeffect_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.
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)
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.
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
, if you need to obtain this information.
((NumXSectors * NumZSectors)*8)
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.
|
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.