{{indexmenu_n>2}}
====== 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 1024×1024 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 [[trs:floordata|[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.
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)
{{anchor:tr_room_info}}
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:
{{anchor:tr5_room_info}}
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.
{{anchor:tr_room_portal}}
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''.
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 ====
//**Core: **''FLOOR_INFO''//
All the geometry specified here is //collisional geometry//.
{{anchor:tr_room_sector}}
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.
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 [[trs:floordata|[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 [[trs:npc_behaviour#boxes|[Boxes]]] array entry, which is basically a subset of sectors with same height configuration. It is primarily used for AI pathfinding (see the [[trs:npc_behaviour|Non-player character behaviour]] chapter for more details).
{{:icons:tr3.png?nolink&}}{{:icons:tr4.png?nolink&}}{{:icons:tr5.png?nolink&}}In these games, ''BoxIndex'' field is more complicated:
struct tr3_room_sector // 8 bytes
{
uint16_t FDindex; // Index into FloorData[]
uint16_t Material : 4; // Material index, used
uint16_t Box : 11; // Actual box index
uint16_t Stopper : 1;
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
};
^ Hex value ^ ''%%0x8000%%'' ^ ''%%0x7FF0%%'' ^^^^^^^^^^^ ''%%0x000F%%'' ^^^^
^ Bit ^ 15 ^ 14 ^ 13 ^ 12 ^ 11 ^ 10 ^ 9 ^ 8 ^ 7 ^ 6 ^ 5 ^ 4 ^ 3 ^ 2 ^ 1 ^ 0 ^
^ Field | ''%%Stopper%%'' | ''%%Box%%'' ||||||||||| ''%%Material%%'' ||||
''Material'' is the //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 [[trs:miscellany#flipeffects|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 (full 1s), 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 ====
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 ===
{{anchor:tr_room_light}}
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:
{{anchor:tr2_room_light}}
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 ===
{{anchor:tr3_room_light}}
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
if (LightType == 0) // Sun
{
int16_t nx, ny, nz; // Most likely a normal
int16_t Unused;
}
else if (LightType == 1) // Point
{
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 ===
{{anchor:tr4_room_light}}
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
{{anchor:fog_bulb}}
//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:
|{{:fe_palette:00.png?nolink&}}|'' 0'' = 0,0,0 |{{:fe_palette:07.png?nolink&}}|'' 7'' = 0,64,192 |{{:fe_palette:14.png?nolink&}}|''14'' = 111,255,223|{{:fe_palette:21.png?nolink&}}|''21'' = 0,30,16 |
|{{:fe_palette:01.png?nolink&}}|'' 1'' = 245,200,60|{{:fe_palette:08.png?nolink&}}|'' 8'' = 0,128,0 |{{:fe_palette:15.png?nolink&}}|''15'' = 244,216,152|{{:fe_palette:22.png?nolink&}}|''22'' = 250,222,167|
|{{:fe_palette:02.png?nolink&}}|'' 2'' = 120,196,112|{{:fe_palette:09.png?nolink&}}|'' 9'' = 150,172,157|{{:fe_palette:16.png?nolink&}}|''16'' = 248,192,60|{{:fe_palette:23.png?nolink&}}|''23'' = 218,175,117|
|{{:fe_palette:03.png?nolink&}}|'' 3'' = 202,204,230|{{:fe_palette:10.png?nolink&}}|''10'' = 128,128,128|{{:fe_palette:17.png?nolink&}}|''17'' = 252,0,0 |{{:fe_palette:24.png?nolink&}}|''24'' = 225,191,78|
|{{:fe_palette:04.png?nolink&}}|'' 4'' = 128,64,0 |{{:fe_palette:11.png?nolink&}}|''11'' = 204,163,123|{{:fe_palette:18.png?nolink&}}|''18'' = 198,95,87 |{{:fe_palette:25.png?nolink&}}|''25'' = 77,140,141|
|{{:fe_palette:05.png?nolink&}}|'' 5'' = 64,64,64 |{{:fe_palette:12.png?nolink&}}|''12'' = 177,162,140|{{:fe_palette:19.png?nolink&}}|''19'' = 226,151,118|{{:fe_palette:26.png?nolink&}}|''26'' = 4,181,154 |
|{{:fe_palette:06.png?nolink&}}|'' 6'' = 243,232,236|{{:fe_palette:13.png?nolink&}}|''13'' = 0,223,191 |{{:fe_palette:20.png?nolink&}}|''20'' = 248,235,206|{{:fe_palette:27.png?nolink&}}|''27'' = 255,174,0 |
=== TR5 Room Lighting ===
{{anchor:tr5_room_light}}
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 shadow; // 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
};
struct tr5_fog_bulb // 36 bytes
{
float x, y, z; // Position of light, in world coordinates
float rad, sqrad;
float den;
float r, g, b;
};
''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'' $\text{LightDirectionVectorX} = \cos(X) \cdot \sin(Y)$
* $\text{LightDirectionVectorY} = \sin(X)$
* $\text{LightDirectionVectorZ} = \cos(X) \cdot \cos(Y)$
''x2'', ''y2'', ''z2'', ''dx2'', ''dy2'' and ''dz2'' values repeat previous corresponding information in long data types instead of floats.
==== 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 ===
{{anchor:tr_room_vertex}}
struct tr_room_vertex // 8 bytes
{
tr_vertex Vertex;
int16_t Lighting;
};
''Vertex'' is the coordinates of the vertex, relative to [[trs:room_geometry#tr_room_info|[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:
{{anchor:tr2_room_vertex}}
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 [[trs:room_geometry#tr_room|[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 ===
{{anchor:tr3_room_vertex}}
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:// {{:icons:tr4.png?nolink&}}{{:icons:tr5.png?nolink&}} Broken in these game versions. Instead, same "wave" effect is produced as with //Bit 13//, but with slightly less glow.
''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:
{{anchor:tr5_room_vertex}}
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 ====
{{anchor:tr_room_sprite}}
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.
This is not a “real” C/C++ structure, in that the arrays are sized by the ''NumXXX'' elements that precede them. Also [[trs:room_geometry#tr_room_vertex|[tr_room_vertex]]] could be replaced by any other version-specific room vertex type ([[trs:room_geometry#tr3_room_vertex|[tr3_room_vertex]]], etc.).
{{anchor:tr_room_data}}
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 ====
//**Core: **''MESH_INFO''//
Positions and IDs of static meshes (e.g. skeletons, spiderwebs, furniture, trees). This is comparable to the [[trs:meshes_models#tr_entity|[tr_entity]]] structure, except that static meshes have no animations and are confined to a single room.
=== TR1 Room Static Mesh Structure ===
{{anchor:tr_room_staticmesh}}
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:
$RealRotation = \frac{Rotation}{16384} \cdot -90$
=== TR2 Room Static Mesh Structure ===
TR2 again uses an extended version:
{{anchor:tr2_room_staticmesh}}
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 ===
{{anchor:tr3_room_staticmesh}}
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): 0b0RRRRRGGGGGBBBBB. 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 3×3 sector grid (actually 3069×3069 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 ====
{{anchor:tr5_room_layer}}
struct tr5_room_layer // 56 bytes
{
uint16_t NumLayerVertices; // Number of vertices in this layer (2 bytes)
uint16_t NumLayerVerticesWater; // Number of underwater vertices in this layer (2 bytes)
uint16_t NumLayerVerticesShore;
uint16_t NumLayerRectangles; // Number of rectangles in this layer (2 bytes)
uint16_t NumLayerTriangles; // Number of triangles in this layer (2 bytes)
uint16_t NumLayerRectanglesWater; // Number of rectangles containing water vertices
uint16_t NumLayerTrianglesWater; // Number of trianglescontaining water vertices
uint16_t Filler; // 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), internal pointer
uint32_t VerticesOffset; // Those fields are overwritten at level loading
uint32_t PolyOffset; // by the ones present in the tr5_room struct + an offset
uint32_t PrelightOffset; // i.e. the values are not read, the fields are there for storage purposes
}
''NumLayerVerticesShore'' 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 [[trs:room_geometry#tr_room_info|[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// [[trs:room_geometry#tr5_room|[tr5_room]]], which will be described independently.
{{anchor:tr_room}}
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
tr_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 ====
{{anchor:tr2_room}}
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
tr2_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 [[trs:room_geometry#tr_room_vertex|[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 present, 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 ====
{{anchor:tr3_room}}
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
tr3_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
tr3_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). Type 3 (sunset) works the same as in TR2.
==== TR4 Room Structure ====
{{anchor:tr4_room}}
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
tr3_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
tr3_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 [[trs:room_geometry#tr2_room|[tr2_room]]] structure. Note it’s //not in [[trs:fundamentals#tr_colour4|[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 [[trs:floordata#trigfunc_0x03|flipmap trigger action]] description in [[trs:floordata#trigger_actions|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 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.
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.
There are multiple pointer fields in the structure, they are relative to the **end** of the ''tr5_room'' structure, i.e. to the beginning of the ''tr5_room_data'' structure.
{{anchor:tr5_room}}
struct tr5_room // 216 bytes
{
char XELA[4]; // So-called "XELA landmark"
uint32_t RoomDataSize; // size of following fields (208 bytes) + tr5_room_data (dynamic)
char* RoomData; // is filled by the game at run time. Always 0xCDCDCDCD in level files (4 bytes),
uint16_t* NumPortals; // points to tr5_room_data.NumPortals
tr3_room_sector* SectorList; // points to tr5_room_data.SectorList
tr5_room_light* Lights; // points to tr5_room_data.Lights
tr3_room_staticmesh* StaticMeshes; // points to tr5_room_data.StaticMeshes
tr5_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;
int8_t MeshEffect;
int8_t bound_active;
int16_t left; // always 0x7FFF
int16_t right; // always 0
int16_t top; // always 0x7FFF
int16_t bottom; // always 0
int16_t test_left; // always 0xCDCD
int16_t test_right; // always 0xCDCD
int16_t test_top; // always 0xCDCD
int16_t test_bottom; // always 0xCDCD
int16_t item_number; // always 0xFFFF (-1)
int16_t fx_number; // always 0xFFFF (-1)
uint16_t AlternateRoom;
uint16_t Flags;
uint32_t nVerts;
uint32_t nWaterVerts; // Always 0
uint32_t nShoreVerts; // Always 0
uint32_t Separator; // 0xCDCDCDCD, internally a pointer
uint32_t Separator; // internally a pointer to the face data
float RoomX;
float RoomY;
float RoomZ;
uint32_t vnormals; // internal pointer
uint32_t fnormals; // internal pointer
uint32_t prelight; // internal pointer
uint32_t prelightwater; // internal pointer
uint32_t watercalc; // internal pointer, 0 for normal rooms and 0xCDCDCDCD for null rooms
uint32_t verts; // internal pointer
uint32_t NumRoomTriangles;
uint32_t NumRoomRectangles;
tr5_room_light* RoomLights; // points to tr5_room_data.RoomLights
tr5_fog_bulb* FogBulbs; // points to tr5_room_data.FogBulbs
uint32_t NumLights2; // Always same as NumLights
uint32_t NumFogBulbs;
int32_t RoomYTop;
int32_t RoomYBottom;
uint32_t NumLayers;
tr5_room_layer* Layers; // points to tr5_room_data.Layers
tr5_room_vertex* Vertices; // points to tr5_room_data.Vertices
void* PolyOffset; // points to tr5_room_data.Faces
void* PrelightOffset; // points to tr5_room_data.Faces, internal?
uint32_t NumVertices;
// Always 0xCDCDCDCD
float fLeft;
float fRight;
float fTop;
float fBottom;
}
// immediately after
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)
tr3_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 * sizeof(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.
''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 [[:trs:room_geometry#tr5_room_light|[tr5_room_light]]] units).
''RoomYTop'' and ''RoomYBottom'' are equal to ''yTop'' and ''yBottom'' values in [[:trs:room_geometry#tr_room_info|[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.
''VerticesSize'' is the size of vertex data block in bytes. Therefore, it //must// be a multiple of [[:trs:room_geometry#tr5_room_vertex|[tr5_room_vertex]]] size, else it means the block size is wrong.
''Faces'' is a sequential data array for the room polygons (both [[:trs:fundamentals#tr_face4|[tr_face4]]] and [[:trs:fundamentals#tr_face3|[tr_face3]]]),
''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. Sound effects played without internal flag ''SFX_WATER'' (''0x01'') will not play.
* **Bit 3** — {{:icons:tr2.png?nolink&}}{{:icons:tr3.png?nolink&}}{{:icons:tr4.png?nolink&}}{{:icons:tr5.png?nolink&}}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** — {{:icons:tr2.png?nolink&}}{{:icons:tr3.png?nolink&}}{{:icons:tr4.png?nolink&}}{{:icons:tr5.png?nolink&}}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** — {{:icons:tr3.png?nolink&}}{{:icons:tr4.png?nolink&}}{{:icons:tr5.png?nolink&}}Room is inside. Used in official levels. No apparent effects.
* **Bit 7** — {{:icons:tr3.png?nolink&}}{{:icons:tr4.png?nolink&}}{{:icons:tr5.png?nolink&}}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** — {{:icons:tr1.png?nolink&16x16}}Skybox-like room. These must be rendered before other rooms with depth testing disabled, as these skybox rooms generally overlap each other and depth testing generall leads to z-fightig flickering.
* **Bit 8** — {{:icons:tr3.png?nolink&}}{{:icons:tr4.png?nolink&}}{{:icons:tr5.png?nolink&}}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** — {{:icons:tr3.png?nolink&}}{{:icons:tr4.png?nolink&}}{{:icons:tr5.png?nolink&}}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** — {{:icons:tr4.png?nolink&}}{{:icons:tr5.png?nolink&}}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** — {{:icons:tr4.png?nolink&}}{{:icons:tr5.png?nolink&}}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).
{{:icons:tr3.png?nolink&}}{{:icons:tr4.png?nolink&}}{{:icons:tr5.png?nolink&}}''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.
{{:icons:tr3.png?nolink&}}{{:icons:tr4.png?nolink&}}{{:icons:tr5.png?nolink&}}''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.