Specification: RMF Last edited 1 week ago2024-11-20 22:57:32 UTC

RMF (Rich Map Format) is an alternative to the MAP format which provides some more features such as saving VisGroups, object grouping, and paths. It was created for the Worldcraft editor by Ben Morris, and later updated by Valve once they purchased the rights to Worldcraft. Unlike MAP, RMF is a binary format, and cannot be read or edited in a text editor.

Definitions

All values are stored in little-endian format (least significant byte first), the same format used by x86/x64 CPUs.
// Types
char: 8-bit ASCII character (uint8_t)
byte: unsigned 8-bit integer (uint8_t)
float: 32-bit single-precision floating point value
int32: signed 32-bit integer (int32_t)

// Length-prefixed string
typedef struct {
    byte length;          // The length of the string (max 256)
    char[length] string;    // The ASCII-encoded string
} p_char;

// RGB colour
typedef struct {
    byte[3] rgb;      // One byte each for red, green and blue channel
} Color;

// RGBA colour
typedef struct {
    byte[4] rgba;     // One byte each for red, green, blue and alpha channel
} RgbaColor;

// 3D Vector
typedef struct {
    float x;
    float y;
    float z;
} Vector;

Overall Structure

typedef struct {
    float version;                      // File format version (2.2 is the most recent version)
    char[3] magic;                      // File format magic number, "RMF" in ASCII encoding
    int32 visgroup_count;               // Number of VisGroups
    VisGroup[visgroup_count] visgroups; // VisGroups objects
    World worldspawn;                   // The "CMapWorld" object which contains all other MapObjects as children objects
    Docinfo docinfo;                    // This is optional, the file is still valid if this is not present
} Rmf;

Map Objects

Worldcraft is built using C++ and the object oriented programming style, and this shows with the layout of the map objects. They share the same base structure (which we will call MapObjectBase) and the type of each object is indicated by the first field - the object type.
typedef struct {
    p_char object_type;                // CMapWorld, CMapSolid, CMapBrush, CMapEntity
    int32 visgroup_id;                 // ID of the VisGroup the object belongs to
    Color color;                       // Editor color of the object
    int32 child_count;                 // Number of child objects
    MapObject[child_count] brushes;    // List of child objects
} MapObjectBase;
CMapWorld and CMapEntity also have the same layout for entity data, which we'll call EntityData.
typedef struct {
    p_char classname;               // The entity's classname
    byte[4] unknown1;               // Unused
    int32 spawnflags;               // The entity's spawnflags
    int32 kv_count;                 // Number of entity's key-value pairs
    KeyValue[kv_count] keyvalues;   // Entity's key-value pairs (properties)
    byte[12] unknown2;              // Unused
} EntityData;

World (CMapWorld)

World is the root object of the object tree. There will only ever be one World object in the file.
typedef struct {
    MapObjectBase base_data;
    EntityData entity_data;
    int32 path_count;                   // Number of path objects
    Path[path_count] paths;             // Path objects (created with the Path tool)
} World;

Brush (CMapSolid)

A brush represents a solid object with textured faces. A brush will never have child objects.
typedef struct {
    MapObjectBase base_data;
    int32 face_count;       // Number of faces (polygons) making up the brush
    Face[face_count]        // The brush's faces
} Brush;

Entity (CMapEntity)

A entity can be either a point entity or a brush entity. A brush entity will have child objects (groups and solids only), a point entity will not.
typedef struct {
    MapObjectBase base_data;
    EntityData entity_data;
    byte[2] unknown1;               // Unused
    Vector origin;                  // The origin if it's a point entity
    byte[4] unknown2;               // Unused
} Entity;

Group (CMapGroup)

A group is just a collection of objects, and has no other special properties. A group should always have at least one child object, an empty group is considered invalid and should be discarded.
typedef struct {
    MapObjectBase base_data;
} Group;

Other data types

VisGroup

typedef struct {
    char[128] name;      // VisGroup name (null terminated)
    RgbaColor color;     // Editor color of the VisGroup
    int32 visgroup_id;   // VisGroup's ID number
    byte visible;        // Whether the VisGroup is visible or not
    byte[3] unknown;
} VisGroup;

Face

The angle value is what has already been applied to the face and is reflected in the right_axis and down_axis. In other words, any texture projected along right_axis and down_axis will already be rotated. You will mostly only use this to undo the rotation done to the texture.
typedef struct {
    char[260] texture_name;         // Name of the texture applied to the face (null terminated)
    Vector right_axis;              // The texture's projected right axis
    float shift_x;                  // Horizontal shift of the texture
    Vector down_axis;               // The texture's projected down axis
    float shift_y;                  // Vertical shift of the texture
    float angle;                    // The angle of the applied rotation
    float scale_x;                  // Horizontal scale multiplier
    float scale_y;                  // Vertical scale multiplier
    byte[16] unknown;               // Unused
    int32 vertex_count;             // Number of vertices in the face
    Vector[vertex_count] vertices;  // The vertices that make up the face
    Vector[3] plane_points;         // A triplet of points that define the face plane
} Face;

KeyValue

typedef struct {
    p_char key;         // Key of the property
    p_char value;       // Value of the property
} KeyValue;

Path

Paths are placed using the Path tool in Hammer and offer an alternative to placing path_ entities manually. NOTE: Hammer's method of converting paths to entities is incorrect, and this tool should generally be avoided unless using a custom tool such as hlfix to convert your RMF to .map prior to compilation.
typedef struct {
    char[128] path_name;         // The base name for this path (null terminated)
    char[128] classname;         // Typically path_corner or path_track (null terminated)
    int32 path_type;             // The direction of the path
    int32 node_count;            // Number of nodes in the path
    PathNode[node_count] nodes;  // The nodes that make up the path
} Path;
The path_name will be used as the base name for the nodes belonging to this path, i.e. for a path named my_path each node will be named my_path01, my_path02, etc.

The path's direction or path_type can have one of three different values:
0: One way (from first to last node and stops there).
1: Circular (from first to last and back to first again, as an endless loop).
2: Ping pong (from first to last and reverse direction back to first, in an endless loop).

PathNode

typedef struct {
    Vector position;                // The node's position in world coordinates
    int32 index;                    // The node's index in the path
    char[128] name_override;        // Name to use instead of auto-generated one (null terminated)
    int32 kv_count;                 // Number of node's key-value pairs
    KeyValue[kv_count] keyvalues;   // Node's key-value pairs (properties)
} PathNode;

Docinfo

typedef struct {
    char[8] docinfo;                 // The string "DOCINFO" with a null terminator
    float docinfo_version;           // Possibly version number of the docinfo/camera data - always equal to 0.2
    int32 active_camera;             // Index of the active camera
    int32 camera_count;              // Number of camera objects
    Camera[camera_count];            // Camera objects
} Docinfo;

Camera

typedef struct {
    Vector eye_position;        // The position of the camera in world coordinates
    Vector lookat_position;     // The target position in world coordinates the camera will look at
} Camera;

RMF Versions and Differences

The RMF format evolved over time and the version number was increased each time. The following list is based on the known Worldcraft/Hammer releases.

Version v2.2

Latest version, documented above. Introduced with Worldcraft 3.3 and is the latest version of the format.

Version v1.8

Version saved by Worldcraft between versions 1.6 and 2.1. The only difference is in the Face structure:
typedef struct {
    char[260] texture_name;
    // right_axis not present
    float shift_x;
    // down_axis not present
    float shift_y;
    float angle;
    float scale_x;
    float scale_y;
    byte[16] unknown;
    int32 vertex_count;
    Vector[vertex_count] vertices;
    Vector[3] plane_points;
} Face18;
right_axis and down_axis are no longer present, the texture axes are determined by the normal of the face using Quake logic. See TextureAxisFromPlane in the Quake compiler tools for the implementation.

Version v1.6

Version saved by Worldcraft version 1.5b only. Again, the only difference is in the Face structure:
typedef struct {
    char[40] texture_name; // Texture name is only 40 chars
    // right_axis not present
    float shift_x;
    // down_axis not present
    float shift_y;
    float angle;
    float scale_x;
    float scale_y;
    byte[4] unknown; // Only 4 unused bytes instead of 16
    int32 vertex_count;
    Vector[vertex_count] vertices;
    Vector[3] plane_points;
} Face18;
The texture name is only 40 characters long in this version, and the unknown data is only 4 bytes long. Texture axes are calculated using the Quake method, same as v1.8.

Version v1.4

Version saved by Worldcraft version 1.3 only. Differences not known.

Version v0.9

Version saved by Worldcraft version 1.1a only. Differences not known.

Version v0.8

Version saved by Worldcraft between versions 1.0a and 1.1. Differences not known.

Any other versions are unknown. If you have a Worldcraft version prior to 1.0a or one that saves files in a different RMF version, please let us know!

Sources and further reference

1 Comment

Commented 9 months ago2024-02-08 15:40:38 UTC Comment #105968
I noticed a few things while looking into the various .rmf file versions:
  • VIS group colors appear to be stored as RGB, not RGBA - the 4th byte is always 0 in my test files.
  • In v1.6 and v1.8 files, colors are stored as BGR, not RGB.

You must log in to post a comment. You can login or register a new account.