Document Version 3.0 preview (last edit: 2018-11-20) Including Tomb Raider Also includes |
|
1. Introduction
Until now the Tomb Raider community, especially programmers and resource hacking enthusiasts, referenced a document called TRosettaStone, which hadn’t been updated since 1999, and its revised version (we can call it TRosettaStone 2), which was done by E. Popov in 2000 or 2001. Both of these documents served well for their time, but they were plagued by many errors and inaccuracies.
Many people noted these errors and inaccuracies, but nobody of them revised existing documentation, keeping all their knowledge just in their head. It is widely known that original document lacks some info here and there, and something is wrongly described — but it’s not mentioned anywhere (except some lost forum posts). So when a newbie programmer comes to community, he has to come through all the same trials and errors as everyone else once came.
The purpose of TRosettaStone 3 is to replace original document and serve as the comprehensive source of information for anyone willing to participate in classic-era Tomb Raider software development. We will use original TRosettaStone as a basis, heavily borrowing from E. Popov revision. Also, this document will widely borrow information from TREP user’s manual, NGLE manual, and a bunch of forum threads and messages concerning internal game engine structures.
However, while original TRosettaStone and update by Popov were aimed primarily at exploring already existing game assets, such as levels, sound files, scripts, etc., this document is being created by and as a reference for programmers who are creating open-source reimplementations of original game engines. Because of this, we will sometimes explore not only game file formats, but also internal game logic, procedures and other related aspects. It will be updated synchronously with development of OpenTomb, one of such reimplementation projects, which has received lots of feedback from the community.
1.1. Description
This document contains detailed descriptions of the classic-era Tomb Raider data file formats. It is assumed that the reader has knowledge and experience programming in C or C++ and has at least a passing familiarity with graphics programming. This document is self-contained; all hyperlinks refer only to itself.
All information in this document was derived independently, without the aid or assistance of anyone at Core Design or Eidos. As such, the information in this document may contain errors or omissions, and many structure and variable names were deduced from the interpretation of the data (and therefore could be misleading or completely wrong). However, we re-use certain variable and function names from original Tomb Raider debug builds and mappings/symbols from the leaked Tomb Raider Chronicles PSX SDK.
All the information in this document was tested and is therefore plausible, but could also be a misinterpretation. All information herein is provided as is — you get what you pay for, and this one’s free. This is a spare-time project that set out to document the Tomb Raider file formats.
1.2. Conventions
Generally, game versions are referenced by abbreviations:
-
TR1 refers to Tomb Raider and Tomb Raider: Unfinished Business
-
TR2 refers to Tomb Raider II and Tomb Raider II: The Golden Mask
-
TR3 refers to Tomb Raider III and Tomb Raider III: The Lost Artifact
-
TR4 refers to Tomb Raider: The Last Revelation
-
TR5 refers to Tomb Raider: Chronicles
|
As level formats are concerned, TR4 usually applies not only to original game, but to custom levels built by fans using Tomb Raider Level Editor
(winroomedit.exe ) version .49 , used to work with TR4-specific level file format. This level editor version is the only one level editor officially released
by Eidos / Core, along with TR5. |
When we provide some version-specific info about certain structures or methods, this information will be marked with special bullet images, defining engine
versions for which this info is applicable:
is for TR1,
is for TR2,
is for TR3,
is for TR4, and
is for TR5. Version-specific
information will continue up to the next paragraph or list entry, until otherwise noted.
Also, if external programs and utilities are involved, here are abbreviations for them:
-
TRLE refers to Tomb Raider Level Editor — an official tool by Core Design used to build levels.
-
NGLE refers to TRLE version which was unofficially patched and now extensively used by level editing community.
-
Dxtre3d refers to so-called unofficial level editor developed by Felix aka Turbo Pascal.
-
TE refers to new advanced level editor called Tomb Editor, developed by joint efforts of TRLE community with MontyTRC as lead programmer.
-
TREP refers to a binary patcher which is used to patch TR4 engine in its Level Editor version (bundled with TR5) for some advanced features and upgrades by level editing community.
-
TRNG is another patcher with same purpose as TREP, however, incompatible with it. TRNG offers even more advanced features and upgrades to old TR4 engine.
-
FLEP refers to a similar patcher as TREP, which is used for same TR4 engine, albeit previously modified by TRNG.
1.3. Current Unknowns
-
Room light structure from TR4 needs additional description.
-
Same for TR5 room light structure.
-
Exact description of what is exactly “room layer” in TR5.
-
Clarify the meaning of Bit 4 in [tr2_room_vertex] structure.
-
Clarify what’s the purpose of
Normal
field in [tr5_room_vertex] structure, and if reallyAttributes
field was removed. -
Whole [tr5_room] structure needs detailed analysis with all its extra Unknown fields.
-
Clarify fog bulb values which affect its radius.
-
Clarify the CUTSEQ.bin packed coordinates structure format.
1.4. Copyright note
Tomb Raider, Tomb Raider Gold, Unfinished Business, Tomb Raider II, Tomb Raider III, Tomb Raider: The Last Revelation, Tomb Raider Chronicles, Lara Croft, and all images and data within the data files and game engine are Copyright © Square Enix.
2. The Fundamentals
2.1. File Types
Tomb Raider is driven by various sets of files — level files, script files, FMVs, audio tracks and sound files. In TR4 and TR5, there is also specific file type which contains cutscene data — cutseq pack.
2.1.1. The Script Files
The script file structure differs from version to version.
In TR1, all script info was embedded into executable file (TOMB.EXE
), and thus is hardcoded. TR2 and TR3 had unified TOMBPC.DAT
file, which contains all the
text strings describing the various elements in the game (e.g. the game engine knows about “Key 1”; it looks in TOMBPC.DAT
to determine the name to be
displayed in Lara’s inventory, such as “Rusty Key” or “Taste rostige” or “Clé Rouillée”), the level and cut-scene filenames (e.g. WALL.TR2
,
CUT3.TR2
), the order in which they are to be played, and various per-level and per-game configuration options (e.g. what weapons and objects Lara starts the
level with, whether or not the “cheat” codes work, etc.).
TR4 and TR5 introduced a new script format, where the actual script defining the gameflow was separated from text strings used in game — hence, both TR4 and
TR5 have two .DAT
files — SCRIPT.DAT
and LANGUAGE.DAT
, where LANGUAGE
differs depending on regional origin of the game — US.DAT
, FRENCH.DAT
,
JAPANESE.DAT
, and so on.
2.1.2. The Level Files
The level files, {level-name}.PHD/TUB/TR2/TR4/TRC
, contain everything about the level, including the geographical geometry, the geometry (meshes) of all
animate and inanimate objects in the level, all the textures and colour data, all animation data, index information (and, in TR1, TR4 and TR5 — also the
actual sound sample data) for all sounds, accessibility maps — everything necessary to run the game. For whatever reason, Core has included everything in one
file instead of breaking it up into logical groupings; this means that every level contains all the meshes, textures, sound information, and animation data for
Lara and all of her weapons. There are a fair number of other redundancies, too.
Since TR4, the level file is divided into several chunks, each of them being compressed with zlib. Usually, each chunk of compressed data is preceded by two 32-bit unsigned integers defining the uncompressed size of the chunk and the compressed size of the chunk. Therefore, the engine allocates an empty buffer equal to the uncompressed size of a specific chunk, and another buffer equal to the compressed size. The compressed data is loaded directly within it based on the compressed size. The compressed data is then decompressed into the result buffer and the buffer containing the compressed data is destroyed. In TR5, those chunks aren’t compressed anymore.
|
It’s good to note the origins of level file extension. While it is obvious that TR2/TR4/TRC extensions specify abbreviations of the game name. |
2.1.3. FMVs (Full Motion Videos)
TR1-3 shared the same proprietary Eidos codec for videos, called Escape. The extension for such files is .RPL
, that’s why they occasionally (and
mistakingly) called Replay codec. Signature feature of RPL videos is that they are always interlaced with black stripes; most likely, this was used to conserve
disk space (however, PlayStation videos were in .STR
format, which is basic MPEG compression, and they had no interlacing — but suffered from blocking
issues). In TR1 and TR2, framerate was limited to 15 FPS, while in TR3 it was doubled to 30 FPS.
For a long time, Escape codec was largely unexplored and barely reverse-engineered; there was only an abandoned open source Mplayer implementation for some Escape codec versions, but recent ffmpeg revisions feature fully functional decoder for Escape videos.
Since TR4, all FMVs are in Bink Video format, which is much more common and easy to rip, convert and explore.
2.1.4. Sound Files — Audio Tracks
These are long sound files which occasionally play either on some in-game events (e.g. approaching certain important checkpoint in game, like big hall with
ladder and two wolves in “Caves” — it triggers danger music theme) or in looped manner as background ambience. Audio tracks are stored differently across TR
game versions — CD-Audio in TR1-TR2, single merged file CDAUDIO.WAD
in TR3, and separate audio files in TR4 and TR5.
2.1.5. Sound Files — Samples
TR2 and TR3 also featured external sound sample files, which allowed to share samples between all level files. This sound file is called MAIN.SFX
, and usually
placed in DATA
subfolder. Hence, engine loads sound samples not from level files (as it’s done in TR1, TR4 and TR5 — see above), but rather from this
MAIN.SFX
file.
2.1.6. Cut Sequence Packs
TR4 and TR5 featured special data type containing all the necessary information to play in-game cutscenes. While in earlier games such info was embedded into
the level file itself, and generally, cutscenes themselves were separate level files (easily distinguished by their filenames, e.g. CUT1.TR2
etc.), TR4
changed this approach, and cutscenes could be loaded and played right inside level files at runtime.
The data for such cutscene setup was packed into single file titled CUTSEQ.PAK
in TR4 or CUTSEQ.BIN
in TR5. There will be a special section describing whole
cutseq file format.
2.2. Basic Data Types
For the purposes of further discussion, the following are assumed:
|
specifies an 8-bit signed integer (range -128..127) |
|
specifies an 8-bit unsigned integer (range 0..255) |
|
specifies a 16-bit signed integer (range -32768..32767) |
|
specifies a 16-bit unsigned integer (range 0..65535) |
|
specifies a 32-bit signed integer (range -2147483648..2147483647) |
|
specifies a 32-bit unsigned integer (range 0..4294967295) |
|
specifies a 32-bit IEEE-754 floating-point number |
|
specifies a 32-bit non-trivial 16.16 fixed point value — see further |
|
specifies a 16-bit non-trivial 8.8 fixed point value — see further |
All multi-byte integers ({u}int16_t
, {u}int32_t
) are stored in little-endian (Intel-x86, etc.) format, with the least significant byte stored first and the
most significant byte stored last. When using this data in platforms with big-endian (PowerPC, etc.) number format, be sure to reverse the order of bytes.
2.2.1. 16.16 Fixed Point Data Types
These very specific data types mimic floating-point behaviour, while remaining integer. It is done by splitting floating-point value into whole and
fractional parts, and keeping each part as int16_t
and uint16_t
correspondingly for fixed
type and as uint8_t
and uint8_t
for ufixed16
type. Whole part is kept as it is, while fractional part is multiplied by 65536 (for fixed
) or by 255 (for ufixed16
), and then kept as unsigned integer. So, the formula to calculate floating-point from fixed
is:
Formula to calculate floating-point from ufixed16
is:
…where is whole part of mixed float (signed for fixed
, unsigned for ufixed16
), and is fractional part (unsigned).
|
The reason why such complicated setup was invented is to avoid using floating-point numbers. In 90% of all cases, Tomb Raider engines use integer numbers, even for geometry calculations and animation interpolations. The root of this setup lies in multi-platform nature of the code, which was simultaneously written for PC and PlayStation. While PCs had enough computational power to deal with floats at that time, PlayStation relied only on integers. However, some internal variables and constants (like drawing distance, fog distance constants and some light properties) are PC-specific and stored in floating point numbers. Also, last game in series, TR5, extensively used floating-point numbers for certain data types — like colours, vertices and coordinates. |
2.2.2. Data Alignment
Data alignment is something one has to be careful about. When some entity gets an address that is a multiple of , it is said to be -byte aligned. The reason it is important here is that some systems prefer multibyte alignment for multibyte quantities, and compilers for such systems may pad the data to get the “correct” alignments, thus making the in-memory structures out of sync with their file counterparts. However, a compiler may be commanded to use a lower level of alignment, one that will not cause padding. And for TR’s data structures, 2-byte alignment should be successful in nearly all cases, with exceptions noted below.
To set single-byte alignment in any recent compiler, use the following compiler directive:
#pragma pack(push, 1)
To return to the project’s default alignment, use the following directive:
#pragma pack(pop)
2.3. Basic Terms
2.3.1. Coordinates
The world coordinate system is oriented with the plane horizontal and vertical, with being “up” (e.g.
decreasing values indicate increasing altitude). The world coordinate system is specified using int32_t
values; however, the geography is
limited to the / quadrant for reasons that are explained below. Mesh coordinates are relative and are specified using int16_t
.
There are some additional coordinate values used, such as “the number of 1024-unit blocks between points A and B”; these are simply scaled versions of more conventional coordinates.
2.3.2. Colours
All colours in TR are specified either explicitly (using either the [tr_colour] structure, described below, 16-bit structures or 32-bit structures) or implicitly, by indexing one of the palettes. However, it is only applicable to TR1-3 — there is no palette in TR4 and TR5.
In TR1-3, mesh surfaces could be either coloured or textured. Coloured surfaces are “painted” with a single colour that is either specified explicitly or using an index into the palette.
Beginning from TR4, coloured faces feature was removed, so each face must have a texture attached to it.
2.3.3. Textures
Textured surfaces map textures (bitmapped images) from the texture tiles (textiles) to each point on the mesh surface. This is done using conventional UV mapping, which is specified in “Object Textures” below; each object texture specifies a mapping from a set of vertices to locations in the textile, and these texture vertices are associated with position vertices specified here. Each textile is a 256x256 pixels wide area.
The 16-bit textile array, which contains [tr_textile16] structures, specifies colours using 16-bit ARGB, where the highest bit (0x8000
) is a crude alpha
channel (really just simple transparency — 0 = transparent, 1 = opaque). The next 5 bits (0x7C00
) specify the red channel, the next 5 bits (0x03E0
)
specify the green channel, and the last 5 bits (0x001F
) specify the blue channel, each on a scale from 0..31.
If, for some reason, 16-bit textures are turned off, all colours and textures use an 8-bit palette that is stored in the level file. This
palette consists of a 256-element array of [tr_colour] structures, each designating some colour; textures and other elements that need to reference a colour
specify an index (0..255) into the
Palette[]
array. There is also a 16-bit palette, which is used for identifying colours of solid polygons. The 16-bit
palette contains up to 256 four-byte entries; the first three bytes are a [tr_colour], while the last byte is ignored (set to 0).
The 32-bit textile array, which contains [tr4_textile32] structures, specifies colours using 32-bit ARGB, where the highest byte (A) is unused. The next bytes specify (in this order) the red / green / blue channels. The 16-bit and 32-bit textile arrays depict the same graphics data, but of course the 32-bit array has a better colour resolution. It’s the one used if you select a 32-bit A8R8G8B8 texture format in the setup menu from TR4 and TR5.
2.3.4. Meshes and Sprites
There are two basic types of “visible objects” in TR2 — meshes and sprites.
Meshes are collections of textured or coloured polygons that are assembled to form a three-dimensional object (such as a tree, a tiger, or Lara herself). The “rooms” themselves are also composed of meshes. Mesh objects may contain more than one mesh; though these meshes are moved relative to each other, each mesh is rigid.
Sprites are two-dimensional images that are inserted into three-dimensional space, such as the “secret” dragons, ammunition, medi-packs, etc. There are also animated sprite sequences, such as the fire at the end of “The Great Wall.” Core had presumably used this method to reduce CPU utilization on the PlayStation and/or the earlier PCs. Sprites become less and less abundant; TR2 has very few scenery sprites, and TR3’s pickups are models instead of sprites.
2.3.5. Entities
Each Tomb Raider game has an internal hardcoded set of entity types, each of them linked to specific model (hence, entity type and model can be considered equal). Entity is an individual object with its own specific function and purpose. Almost every “moving” or “acting” thing you see is an entity — like enemies, doors, pick-up items, and even Lara herself.
A level can contain numerous instances of the same entity type, e.g. ten crocodiles, five similar doors and switches, and so on.
Entities are referenced in one of two ways — as an offset into an array (e.g. Entities[i]
) or internally, using an unique index . In the latter case, the related array (Entities[]
) is searched until a matching index is found. Each entity also refers to its entity type
by TypeID
to select behaviour and model to draw. In this case, Models[]
array is searched for matching TypeID
until one found.
2.3.6. Animations
There are three basic types of animations in TR, two corresponding with textures — sprite animations and animated textures — and one corresponding directly with meshes.
Sprite Animations
Sprite animation (sprite sequences) consists simply of a series of sprites that are to be displayed one after another, e.g. grenade explosions. Sprite animations were quite common in earlier games (TR1 and TR2), while in TR3 onwards there are almost no sprite animations — only notable example is fire particle sprites and water splash effect.
Animated Textures
These are either a list of textures cycled through in endless loop, or (in TR4-5) a single texture with shifting coordinates, creating an illusion of “rolling” image.
Mesh Animations
Mesh animations are much more complex than sprite and texture animations, and done by what is essentially a skeletal-modeling scheme. These involve some arrays (Frames[] and MeshTree[]) of offsets and rotations for each element of a composite mesh. Frames are then grouped into an array (Animations[]) that describes discrete “movements”, e.g. Lara taking a step or a tiger striking with its paw. The animations are “sewn together” by a state change array and an animation dispatch array, which, together with state information about the character, ensure that the animation is fluid (e.g. if Lara is running and the player releases the RUN key, she will stop; depending upon which of her feet was down at the time, either her left or right foot will strike the floor as part of the “stop” animation. The correct animation (left foot stop vs. right foot stop) is selected using these structures and the state information).
2.3.7. Lighting
There are two main types of lighting in Tomb Raider, constant and vertex. Constant lighting means that all parts of an object have the same illumination, while in vertex lighting, each polygon vertex has its own light value, and the illumination of the polygon interiors is interpolated from the vertex values.
Furthermore, lighting can be either internal or external. Internal lighting is specified in an object’s data, external lighting is calculated using the room’s light sources (ambient light, point light sources, spotlights (TR4-5), dynamic lights).
When available, external lighting also uses the vertex normals to calculate the incoming light at each vertex. Light intensities are described either with a single value or with a 16 bits color value (you can see it more like a “color filter”), depending mainly on the TR version.
Light intensities are described with a single value in TR1 and a pair of values in TR2 and TR3; the paired values are almost always equal, and the pairing may reflect some feature that was only imperfectly implemented, such as off/on or minimum/maximum values. In TR1 and TR2, the light values go from 0 (maximum light) to 8192 (minimum light), while in TR3, the light values go from 0 (minimum light) to 32767 (maximum light).
2.3.8. Sound Samples
There are two ways for sound samples to play.
First one is basically sound emitter sitting at a static global position in level, and continuously emitting specified sound (such as waterfalls — these are in
SoundSources[]
). Second one is triggered sounds — these are sounds played when some event happens, such as at certain animation frames (footsteps and other
Lara sounds), when doors open and close, and when weapons are fired.
Either way, each played sound is referred to using a three-layer indexing scheme, to provide a maximum amount of abstraction. An internal sound index references
SoundMap[]
, which points to a SoundDetails[]
record, which in turn points to a SampleIndices[]
entry, which in turn points to a sound sample.
SoundDetails[]
, contains such features as sound intensity, how many sound samples to choose from, among others. The sound samples themselves are in Microsoft
WAVE format, and, as already mentioned, they are embedded either in the data files (TR1, TR4 and TR5) or in a separate file (MAIN.SFX
) in TR2 and TR3.
2.4. Basic Data Structures
Much of the .TR2 file is comprised of structures based on a few fundamental data structures, described below.
2.4.1. Colour Structures
This is how most colours are specified.
struct tr_colour // 3 bytes
{
uint8_t Red; // Red component (0 -- darkest, 255 -- brightest)
uint8_t Green; // Green component (0 -- darkest, 255 -- brightest)
uint8_t Blue; // Blue component (0 -- darkest, 255 -- brightest)
};
(Some compilers will pad this structure to make 4 bytes; one must either read and write 3 bytes explicitly, or else use a simple array of bytes instead of this structure.)
And as mentioned earlier, the 16-bit palette uses a similar structure:
In TR5, there is new additional colour type composed of floating-point numbers. This type is primarily used in light structures.
2.4.2. Vertex Structures
This is how vertices are specified, using relative coordinates. They are generally formed into lists, such that other entities (such as quads or triangles) can refer to them by simply using their index in the list.
As with colours, TR5 introduced additional vertex type comprised of floating-point numbers:
2.4.3. Rectangular (Quad) Face Definition
Four vertices (the values are indices into the appropriate vertex list) and a texture (an index into the object-texture list) or colour (index into 8-bit
palette or 16-bit palette). If the rectangle is a coloured polygon (not textured), the .Texture element contains two indices: the low byte (Texture & 0xFF
)
is an index into the 256-colour palette, while the high byte (Texture >> 8
) is in index into the 16-bit palette, when present. A textured rectangle will have
its vertices mapped onto all 4 vertices of an object texture, in appropriate correspondence.
Texture
field can have the bit 15 set: when it is, the face is double-sided (i.e. visible from both sides).
If the rectangle is a coloured polygon (not textured), the .Texture element contains two indices: the low byte (
Texture & 0xFF
) is an index
into the 256-colour palette, while the high byte (Texture >> 8
) is in index into the 16-bit palette, when present.
TR4 and later introduced an extended version only used for meshes, not for triangles and quads making rooms:
The only difference is the extra field Effects
. It has this layout:
-
Bit 0: if set, face has additive alpha blending (same meaning that when the
Attribute
field of [tr_object_texture] is 2, but this flag overrides it).
|
|
Bit 0 set, blending enabled |
Bit 0 not set, blending disabled |
-
Bit 1: if set, face has environment mapping effect (so-called “shiny effect” in TRLE community). Environment map is derived from special pre-rendered texture.
-
Bits 2..7: strength of environment mapping effect. The bigger the value is, the more visible the effect is.
|
|
Shiny effect at max |
No shiny effect |
-
Note that only externally lit meshes can use environment mapping in original engines. If you use it with internally lit meshes, you will crash the game.
-
TR4 engine doesn’t support environmental map for Lara’s joints. It simply wasn’t implemented, so if you apply effect to Lara joints, game will crash. For TR5, a special object called Lara’s catsuit was developed to support environmental map on transformed meshes.
2.4.4. Triangular Face Definition
These structures has the same layout than the quad face definitions, except a textured triangle will have its vertices mapped onto the first 3 vertices of an object texture, in appropriate correspondence. Moreover, a triangle has only 3 vertices, not 4.
struct tr4_mesh_face3 // 10 bytes
{
uint16_t Vertices[3];
uint16_t Texture;
uint16_t Effects; // TR4-5 ONLY: alpha blending and environment mapping strength
};
All the info about Texture
and Effects
fields is also similar to same info from [tr_face4] and [tr4_mesh_face4] respectively.
2.4.5. 8-bit Texture Tile
Each uint8_t
represents a pixel whose colour is in the 8-bit palette.
2.4.6. 16-bit Texture Tile
Each uint16_t
represents a pixel whose colour is of the form ARGB, MSB-to-LSB:
1-bit transparency (0
= transparent, 1
= opaque) (0x8000
)
5-bit red channel (0x7C00
)
5-bit green channel (0x03E0
)
5-bit blue channel (0x001F
)
3. Room Geometry
3.1. 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 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.
|
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.
3.2. Room Structures
3.2.1. Room header
indicate the base position of the room mesh in world coordinates ( 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.
3.2.2. 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
.
|
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. |
3.2.3. 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 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 and can be quite large, 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).
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.
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).
3.2.4. 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
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
struct tr3_room_light // 24 bytes
{
int32_t x, y, z; // Position of light, in world coordinates
tr_colour4 Colour; // Colour of the light
uint32_t Intensity;
uint32_t Fade; // Falloff value
};
Intensity
is the power of the light and ranges mainly from 0 (low power) to 0x1FFF (high power). Though, values greater than 0x1FFF do exist and their
meanings are unknown. Fade
is the distance max the light can shine on. Range is mainly from 0 to 0x7FFF, but negative values do exist and their meanings are
unknown.
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
is somewhat similar to D3D light type, but there are some differences.
-
0 — Sun
-
1 — Light
-
2 — Spot
-
3 — Shadow
-
4 — 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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|