Map format documentation
MurphyIdiot
NS2 programmer Join Date: 2008-04-17 Member: 64095Members, Super Administrators, NS2 Developer, Subnautica Developer, Pistachionauts, Future Perfect Developer
in Modding
Hey everyone,
I got a request to release some information about the map format. So this is some very basic documentation about the format. I hope it helps and if somebody can copy this onto the wiki and any other sites with this type of documentation, that would be helpful.
The first 3 bytes of the file are a magic number which are the characters "LVL".
The next unsigned byte is an integer version number for the map format. The engine supports loading older versions of the format in addition to the current version.
Next, the engine loads chunks until it reaches the end of the file.
There are 4 types of chunks:
Chunk_Object (Id 1)
Chunk_Mesh (Id 2)
Chunk_Layers (Id 3)
Chunk_Groups (Id 5)
In the first pass, it only reads the groups and layers as data from these chunks are needed while loading the other chunks.
A chunk consists of:
Chunk Id (an unsigned 32 bit integer).
Chunk length (an unsigned 32 bit).
The chunk itself.
The groups chunk consists of:
Number of groups (an unsigned 32 bit integer)
A list of groups.
Each group consists of:
A wide string name (length of a string is encoded in the first 4 bytes as an unsigned integer).
A boolean field for visibility (ignored by the engine, only used in the editor, booleans are encoded as 32 bit unsigned integers)
A color (also ignored by the engine, encoded as red, green, blue, and alpha, each field being encoded as a 1 byte unsigned integer)
A group Id (an unsigned 32 bit integer)
The layers chunk consists of:
Number of layers (a signed 32 bit integer)
A list of layers.
Each layer consists of:
A wide string name.
A boolean field for visibility (ignored by the engine)
A color (ignored by the engine)
A layer Id (an unsigned 32 bit integer)
The binary reader then resets itself back to the start of the chunks for the second pass. This time the mesh and object chunks are processed, skipping the other chunks.
A mesh chunk consists of a series of chunks specific to a mesh. These mesh chunks are:
Chunk_Vertices (Id 1)
Chunk_Materials (Id 4)
Chunk_Faces (Id 3)
Chunk_Triangles (Id 5)
Chunk_MappingGroups (Id 7)
Chunk_GeometryGroups (Id 8)
Chunk_FaceLayers (Id 6)
There are 2 passes. The first pass processes every chunk except Chunk_FaceLayers. The second pass only processes Chunk_FaceLayers.
A Chunk_Vertices consists of:
Number of vertices (an unsigned 32 bit integer)
A list of Vec3s (each Vec3 is three 32 bit floats representing x, y, and z)
(Note: Post version 8 of the map format there is also an unsigned 8 bit integer after each Vec3 representing a boolean which indicates that smoothing is enabled for the vertex)
A Chunk_Materials consists of:
Number of materials (an unsigned 32 bit integer)
A list of string material names (these will be referenced as an index into this list of materials by other chunks)
A Chunk_Faces consists of:
Number of faces (an unsigned 32 bit integer)
A list of faces where each face consists of:
An angle (32 bit float)
An offset (Vec2)
A scale (Vec2)
A mapping group Id (unsigned 32 bit integer)
A material Id (unsigned 32 bit integer)
Number of edge loops (unsigned 32 bit integer)
(these are skipped while the game is loading)
The border edge loop
A list of edge loops
A edge loop consists of:
Number of edges (an unsigned 32 bit integer)
A list of edges where each edge consists of:
A boolean indicating if the edge is flipped
The edge index (unsigned 32 bit integer)
A Chunk_Triangles consists of:
Number of "ghost" vertices (unsigned 32 bit integer, these are added to the list of all vertices read in Chunk_Vertices)
A list of "ghost" vertices (each vertex is a Vec3)
Number of smoothed normals (unsigned 32 bit integer)
A list of smoothed normals (each smoothed normal is a Vec3)
Number of faces (unsigned 32 bit integer)
Number of triangles (unsigned 32 bit integer)
A list of faces where each face consists of:
Number of face triangles (unsigned 32 bit integer)
A list of face triangles where each face triangle consists of:
3 vertex indices (each index is an unsigned 32 bit integer)
3 smoothed normal indices (each index is an unsigned 32 bit integer)
A Chunk_MappingGroups consists of:
Number of mapping groups (unsigned 32 bit integer)
A list of mapping groups where each group consists of:
An Id (unsigned 32 bit integer)
An angle (32 bit float)
Scale (Vec2)
Offset (Vec2)
Normal (Vec3)
A Chunk_GeometryGroups consists of:
Number of vertex groups (unsigned 32 bit integer)
A list of vertex groups where each group consists of:
An Id (unsigned 32 bit integer)
Number of vertex indices (each index is an unsigned 32 bit integer, these are ignored by the game while loading)
Number of edge groups (unsigned 32 bit integer)
A list of edge groups where each group consists of:
An Id (unsigned 32 bit integer)
Number of edge indices (each index is an unsigned 32 bit intergers, these are ignored by the game while loading)
Number of face groups (unsigned 32 bit integer)
A list of face groups where each group consists of:
An Id (unsigned 32 bit integer)
The number of faces in this group (unsigned 32 bit integer)
A list of faces where each face consists of:
A face index (unsigned 32 bit integer)
A Chunk_FaceLayers consists of:
Number of face layers (unsigned 32 bit integer)
Format (unsigned 32 bit integer, must be 2)
A list of face layers where each face layer consists of:
A boolean representing that this face has layers (nothing else to read for this face layer if this field is false)
Number of layer bit values (unsigned 32 bit integer)
A list of layer bit values where each value consists of:
A bit mask (unsigned 32 bit integer)
The final chuck to cover is Chunk_Object. Each Chunk_Object represents 1 entity.
A Chunk_Object consists of:
A boolean which is true when this entity includes layer data
The layer format (unsigned 32 bit integer)
Number of layer bit values (unsigned 32 bit integer)
A list of layer bit values where each value consists of:
A bit mask (unsigned 32 bit integer)
The entities' group Id (unsigned 32 bit integer)
The entities' class name (string value)
Until the end of the Chunk_Object chunk, properties are read from the chunk
These entity properties are read from chunks. The chunk Ids are Chunk_Property (1) and Chunk_Property2 (2). Only Chunk_Property2 is used in the newer versions of the map format.
These are the different type of properties:
Type_String = 0 (string value)
Type_Bool = 1 (boolean value)
Type_Real = 2 (float value)
Type_Integer = 3 (integer value)
Type_FileName = 4 (string value)
Type_Color = 5 (float value)
Type_Percentage = 6 (float value)
Type_Angle = 7 (float value)
Type_Time = 8 (float value)
Type_Distance = 9 (float value)
Type_Choice = 10 (special value)
Each chunk starts with an Id (unsigned 32 bit integer) and the chunk length (unsigned 32 bit integer)
Each chunk consists of:
A name (string)
A type (unsigned 32 bit integer)
Number of components (unsigned 32 bit integer)
A boolean representing if the property is animated (animated fields are ignored while the engine is loading the entity)
The property is parsed based on the type of property.
For float types:
A list of components where each component consists of:
Component value (32 bit float)
Any components beyond the fourth are ignored
Next the components are assigned based on the type of property
Type_Real, Type_Percentage, Type_Time, and Type_Distance use only the first component
Type_Angle uses component 1 for roll, component 2 for pitch, and component 3 for yaw
Type_Color uses component 1 for red, component 2 for green, component 3 for blue, and component 4 for alpha (alpha defaults to 1 if there is no component 4)
For string types:
A wide string value (converted to non-wide string value while parsing)
For boolean types:
The boolean value
All other components are ignored
For integer types:
A signed 32 bit integer value
All other components are ignored
For Type_Choice:
The choice value (signed 32 bit integer)
All other components are ignored
That should cover the basics. I probably missed some details and may have even made a mistake or 2 but this should at least give you an idea of how the file format works for NS2 levels.
I got a request to release some information about the map format. So this is some very basic documentation about the format. I hope it helps and if somebody can copy this onto the wiki and any other sites with this type of documentation, that would be helpful.
The first 3 bytes of the file are a magic number which are the characters "LVL".
The next unsigned byte is an integer version number for the map format. The engine supports loading older versions of the format in addition to the current version.
Next, the engine loads chunks until it reaches the end of the file.
There are 4 types of chunks:
Chunk_Object (Id 1)
Chunk_Mesh (Id 2)
Chunk_Layers (Id 3)
Chunk_Groups (Id 5)
In the first pass, it only reads the groups and layers as data from these chunks are needed while loading the other chunks.
A chunk consists of:
Chunk Id (an unsigned 32 bit integer).
Chunk length (an unsigned 32 bit).
The chunk itself.
The groups chunk consists of:
Number of groups (an unsigned 32 bit integer)
A list of groups.
Each group consists of:
A wide string name (length of a string is encoded in the first 4 bytes as an unsigned integer).
A boolean field for visibility (ignored by the engine, only used in the editor, booleans are encoded as 32 bit unsigned integers)
A color (also ignored by the engine, encoded as red, green, blue, and alpha, each field being encoded as a 1 byte unsigned integer)
A group Id (an unsigned 32 bit integer)
The layers chunk consists of:
Number of layers (a signed 32 bit integer)
A list of layers.
Each layer consists of:
A wide string name.
A boolean field for visibility (ignored by the engine)
A color (ignored by the engine)
A layer Id (an unsigned 32 bit integer)
The binary reader then resets itself back to the start of the chunks for the second pass. This time the mesh and object chunks are processed, skipping the other chunks.
A mesh chunk consists of a series of chunks specific to a mesh. These mesh chunks are:
Chunk_Vertices (Id 1)
Chunk_Materials (Id 4)
Chunk_Faces (Id 3)
Chunk_Triangles (Id 5)
Chunk_MappingGroups (Id 7)
Chunk_GeometryGroups (Id 8)
Chunk_FaceLayers (Id 6)
There are 2 passes. The first pass processes every chunk except Chunk_FaceLayers. The second pass only processes Chunk_FaceLayers.
A Chunk_Vertices consists of:
Number of vertices (an unsigned 32 bit integer)
A list of Vec3s (each Vec3 is three 32 bit floats representing x, y, and z)
(Note: Post version 8 of the map format there is also an unsigned 8 bit integer after each Vec3 representing a boolean which indicates that smoothing is enabled for the vertex)
A Chunk_Materials consists of:
Number of materials (an unsigned 32 bit integer)
A list of string material names (these will be referenced as an index into this list of materials by other chunks)
A Chunk_Faces consists of:
Number of faces (an unsigned 32 bit integer)
A list of faces where each face consists of:
An angle (32 bit float)
An offset (Vec2)
A scale (Vec2)
A mapping group Id (unsigned 32 bit integer)
A material Id (unsigned 32 bit integer)
Number of edge loops (unsigned 32 bit integer)
(these are skipped while the game is loading)
The border edge loop
A list of edge loops
A edge loop consists of:
Number of edges (an unsigned 32 bit integer)
A list of edges where each edge consists of:
A boolean indicating if the edge is flipped
The edge index (unsigned 32 bit integer)
A Chunk_Triangles consists of:
Number of "ghost" vertices (unsigned 32 bit integer, these are added to the list of all vertices read in Chunk_Vertices)
A list of "ghost" vertices (each vertex is a Vec3)
Number of smoothed normals (unsigned 32 bit integer)
A list of smoothed normals (each smoothed normal is a Vec3)
Number of faces (unsigned 32 bit integer)
Number of triangles (unsigned 32 bit integer)
A list of faces where each face consists of:
Number of face triangles (unsigned 32 bit integer)
A list of face triangles where each face triangle consists of:
3 vertex indices (each index is an unsigned 32 bit integer)
3 smoothed normal indices (each index is an unsigned 32 bit integer)
A Chunk_MappingGroups consists of:
Number of mapping groups (unsigned 32 bit integer)
A list of mapping groups where each group consists of:
An Id (unsigned 32 bit integer)
An angle (32 bit float)
Scale (Vec2)
Offset (Vec2)
Normal (Vec3)
A Chunk_GeometryGroups consists of:
Number of vertex groups (unsigned 32 bit integer)
A list of vertex groups where each group consists of:
An Id (unsigned 32 bit integer)
Number of vertex indices (each index is an unsigned 32 bit integer, these are ignored by the game while loading)
Number of edge groups (unsigned 32 bit integer)
A list of edge groups where each group consists of:
An Id (unsigned 32 bit integer)
Number of edge indices (each index is an unsigned 32 bit intergers, these are ignored by the game while loading)
Number of face groups (unsigned 32 bit integer)
A list of face groups where each group consists of:
An Id (unsigned 32 bit integer)
The number of faces in this group (unsigned 32 bit integer)
A list of faces where each face consists of:
A face index (unsigned 32 bit integer)
A Chunk_FaceLayers consists of:
Number of face layers (unsigned 32 bit integer)
Format (unsigned 32 bit integer, must be 2)
A list of face layers where each face layer consists of:
A boolean representing that this face has layers (nothing else to read for this face layer if this field is false)
Number of layer bit values (unsigned 32 bit integer)
A list of layer bit values where each value consists of:
A bit mask (unsigned 32 bit integer)
The final chuck to cover is Chunk_Object. Each Chunk_Object represents 1 entity.
A Chunk_Object consists of:
A boolean which is true when this entity includes layer data
The layer format (unsigned 32 bit integer)
Number of layer bit values (unsigned 32 bit integer)
A list of layer bit values where each value consists of:
A bit mask (unsigned 32 bit integer)
The entities' group Id (unsigned 32 bit integer)
The entities' class name (string value)
Until the end of the Chunk_Object chunk, properties are read from the chunk
These entity properties are read from chunks. The chunk Ids are Chunk_Property (1) and Chunk_Property2 (2). Only Chunk_Property2 is used in the newer versions of the map format.
These are the different type of properties:
Type_String = 0 (string value)
Type_Bool = 1 (boolean value)
Type_Real = 2 (float value)
Type_Integer = 3 (integer value)
Type_FileName = 4 (string value)
Type_Color = 5 (float value)
Type_Percentage = 6 (float value)
Type_Angle = 7 (float value)
Type_Time = 8 (float value)
Type_Distance = 9 (float value)
Type_Choice = 10 (special value)
Each chunk starts with an Id (unsigned 32 bit integer) and the chunk length (unsigned 32 bit integer)
Each chunk consists of:
A name (string)
A type (unsigned 32 bit integer)
Number of components (unsigned 32 bit integer)
A boolean representing if the property is animated (animated fields are ignored while the engine is loading the entity)
The property is parsed based on the type of property.
For float types:
A list of components where each component consists of:
Component value (32 bit float)
Any components beyond the fourth are ignored
Next the components are assigned based on the type of property
Type_Real, Type_Percentage, Type_Time, and Type_Distance use only the first component
Type_Angle uses component 1 for roll, component 2 for pitch, and component 3 for yaw
Type_Color uses component 1 for red, component 2 for green, component 3 for blue, and component 4 for alpha (alpha defaults to 1 if there is no component 4)
For string types:
A wide string value (converted to non-wide string value while parsing)
For boolean types:
The boolean value
All other components are ignored
For integer types:
A signed 32 bit integer value
All other components are ignored
For Type_Choice:
The choice value (signed 32 bit integer)
All other components are ignored
That should cover the basics. I probably missed some details and may have even made a mistake or 2 but this should at least give you an idea of how the file format works for NS2 levels.
Comments
Thanks for taking the time to write this.
Is it possible to get some information on the model format?
I wrote a reader and writer for the level format in LuaJIT.
It's working now, but during development there were a few off-by-one and floating point issues :P
This is refinery read and then rewritten:
There seems to be some undocumented chunks.
These are at the top-level:
Id 4: A single wide-string containing XML about various rendering and camera information. Editor settings, maybe?
Id 6: Always 68-bytes long. First four bytes are "10 00 00 00". 16*4 is 64, so that may be length? I have no clue to what the 64 integers are, though. Tram: all zero. Docking: first is "00 00 00 00", rest are "ff 00 00 00". Descent is crazy.
Id 7: Pathing settings. Is this in the Chunk_Property2 form?
And this is inside Chunk_Mesh:
Id 2: Binary information. Edges, maybe?
Again, thank you for the documentation!
Id 4: Chunk_Viewports
Id 6: Chunk_CustomColors
Id 7: Chunk_EditorSettings
Inside Chunk_Mesh:
Id 2: Chunk_Edges
And also the need for a working nav mesh would make things ... tricky ...
Coming to a store neer you!: Fall 2009
Would be awesome to see a map made to look like a compleatly smached up space station, If buildings could just be played on wonky surfaces and the commander view wasnt hindered, this could make for some awesome map designs
So it not be lost in the depths.
His description of the Chunk_GeometryGroups makes it seem like each list is embedded in the previous, when really the Chunk_GeometryGroups contains all 3 lists. To be clearer:
Before I get to all the technical details, some information about the clipboard format that the spark editor uses:
-ONLY geometry and objects (props, entities, lights, reflection probes, etc.) can be copied to the clipboard.
-Layers and groups are NOT copied to the clipboard.
-Editor settings are NOT copied to the clipboard.
-The clipboard format is pretty close to the .level format, but differs in a few areas.
-There are a lot of unused values leftover, I suspect, from repurposing the .level code to output to the clipboard.
Okay, now things are going to get a little bit more technical. The data is divided into two distinct areas: geometry and objects. If you only copied one type or the other, you will only see one big chunk, not two chunks but one just happens to be empty, or something like that. Geometry always comes first, then objects
I'll walk through the format with an example I just copied. It consists of a plane that I first cut arbitrarily (forming a 6-sided square, with a diagonal slash going through roughly the middle). I then inset another square into one of the two 4-sided shapes that was created by the first cut. I then made a square, completely separate from the other geometry, gave it a texture, and used the displacement tool on it. In addition to this geometry, I have two entities: a prop_static gorge model, and a reflection_probe.
Here's what that looks like in the editor:
I copy all of this into the clipboard, and can open this up with Java. The MIME type is "application/spark editor".
EDIT: using code tags to preserve my indentations.
That's it for me today.