MM2 Reference

Block attributes

Common

Most attributes of the PSDL-file are specialized, created for a specific purpose. Some, however, are general attributes with several uses. This section describes them.

Texture references

Most geometry primitives use texture mappings. The textures are identified by one of two block attributes. Attribute 0x50 and 0x51 contains an index into the texture list. When stored in the PSDL-file, the indices are incremented by one for attribute 0x50. The value 0 is special, this value is used in both London and SF, but it's effect is still not examined.

Even though the attribute parameter is 16 bits wide, the values are only eight bits. For texture indices between zero and 254, use attribute 0x50 and for texture indices between 255 and 511, use attribute 0x51. In attribute 0x51, the 255 is subtracted from the index before it is stored in the PSDL-file.

Several block attributes use more than one texture. In most cases the texture attribute references the first texture in a list. This texture index is referenced to as texture n and block attributes sometimes uses texture index n, n + 1, n + 2, n + 3 and so on.

In a pseudo-C style structure, the texture attributes look like this:

struct Attribute_0050 { ushort id = 0x50; ushort textureIndex; // Index in texture list + 1 }
struct Attribute_0051 { ushort id = 0x51; ushort textureIndex; // Index in texture list - 255 }

Triangle fans

To create ground surfaces triangle fans are usually used. These are constructed by a list of vertices surrounding a pivot vertex in a counter-clockwise order. Often the triangle fan is degenerated to a convex polygon. This means that the pivot vertex is located on the perimeter of the attribute.

0x30-0x37_Triangle_fan.jpg

The triangle attributes are, in fact, an entire family of attributes. Eight different attributes can be used depending on the number of vertices the surface requires. Attribute 0x30 is the most general one, this can handle any number of vertices. Attributes 0x31 - 0x37 are limited to 1 - 7 triangles respectively. Attribute 0x31 requires three vertices, attribute 0x37 requires nine vertices.

struct Attribute_0030 { ushort id = 0x30; ushort nVertices; ushort[nVertices] vertexRefs; // Indices in the vertex list }
struct Attribute_0031 { ushort id = 0x31; ushort[3] vertexRefs; // Indices in the vertex list }
struct Attribute_0032 { ushort id = 0x32; ushort[4] vertexRefs; // Indices in the vertex list }
struct Attribute_0033 { ushort id = 0x33; ushort[5] vertexRefs; // Indices in the vertex list }
struct Attribute_0034 { ushort id = 0x34; ushort[6] vertexRefs; // Indices in the vertex list }
struct Attribute_0035 { ushort id = 0x35; ushort[7] vertexRefs; // Indices in the vertex list }
struct Attribute_0036 { ushort id = 0x36; ushort[8] vertexRefs; // Indices in the vertex list }
struct Attribute_0037 { ushort id = 0x37; ushort[9] vertexRefs; // Indices in the vertex list }

Roads

In a racing game, the roads are one of the most important features. There are several geometric primitives for defining roads, this section describes them.

Roads without sidewalks

For walkways and narrow alleys without sidewalks the family of attributes 0x10, 0x11 - 0x17 are used. These attributes are defined by pairs of vertices, cross-sections of the road.

0x10-0x17_Rectangle_strip.jpg
struct Attribute_0010 { ushort id = 0x10; ushort nSections; ushort[nSections * 2] vertexRefs; // Indices in the vertex list }
struct Attribute_0012 { ushort id = 0x12; ushort[2 * 2] vertexRefs; // Indices in the vertex list }
struct Attribute_0013 { ushort id = 0x13; ushort[3 * 2] vertexRefs; // Indices in the vertex list }
struct Attribute_0014 { ushort id = 0x14; ushort[4 * 2] vertexRefs; // Indices in the vertex list }
struct Attribute_0015 { ushort id = 0x15; ushort[5 * 2] vertexRefs; // Indices in the vertex list }
struct Attribute_0016 { ushort id = 0x16; ushort[6 * 2] vertexRefs; // Indices in the vertex list }
struct Attribute_0017 { ushort id = 0x17; ushort[7 * 2] vertexRefs; // Indices in the vertex list }

Roads with sidewalks

The most common roads in an MM2 city are the ones with sidewalks on both sides. These are easily defined using cross-sections of the road. Attribute family 0x00, 0x01 - 0x07 are attributes to use depending on the number of cross-sections in the particular road segment.

0x00-0x07_Road_strip.jpg

The vertices in each cross-section are organized like this: First comes the vertex defining the position of the outer edge of the left sidewalk, then follows the outer edge of the left road surface, the outer edge of the right road surface and finally the outer edge of the right sidewalk. MM2 automatically renders the vertical sides of the sidewalks and the road surface vertices are expected to be located 15 cm below the sidewalk vertices.

This type of roads use three textures. The texture attribute points out texture n, this texture is used to render the road surface. This texture is mirrored along the center line of the road. The sidewalks are rendered using texture n + 1 and texture n + 2 is the texture used across the entire road in the lowest LOD, or level of detail, of the road segment.

struct Attribute_0000 { ushort id = 0x00; ushort nSections; ushort[nSections * 4] vertexRefs; // Indices in the vertex list }
struct Attribute_0002 { ushort id = 0x02; ushort[2 * 4] vertexRefs; // Indices in the vertex list }
struct Attribute_0003 { ushort id = 0x03; ushort[3 * 4] vertexRefs; // Indices in the vertex list }
struct Attribute_0004 { ushort id = 0x04; ushort[4 * 4] vertexRefs; // Indices in the vertex list }
struct Attribute_0005 { ushort id = 0x05; ushort[5 * 4] vertexRefs; // Indices in the vertex list }
struct Attribute_0006 { ushort id = 0x06; ushort[6 * 4] vertexRefs; // Indices in the vertex list }
struct Attribute_0007 { ushort id = 0x07; ushort[7 * 4] vertexRefs; // Indices in the vertex list }

Divided roads

Some roads, typically wide avenues or highways, have a divider along the middle, separating the drive ways. In MM2, such roads are defined by the attribute family 0x40, 0x41 - 0x47.

0x40-0x47_Divided_road_strip.jpg

The vertices in each cross-section are organized like this: First comes the vertex defining the position of the outer edge of the left sidewalk, then follows the outer edge of the left road surface, the inner edge of the left road surface, the inner edge of the right road surface, the outer edge of the right road surface and finally the outer edge of the right sidewalk. MM2 automatically renders the vertical sides of the sidewalks and the road surface vertices are expected to be located 15 cm below the sidewalk vertices.

The road surface and sidewalks are rendered in the same way as the road with sidewalk attributes.

Divider type

Several types of dividers can be created by using various parameters to the divided road strip attributes. First we have a flags parameter, the flags paramter is divided into two parts, it is currently not 100% clear how many bits belong to which part, but the examined road attributes in SF and London indicates that at least the two lowest bits of the flags parameter is a divider type.

In addition to the flags parameter there is also an extra texture reference and a value parameter. These are used differently depending on the divider type.

0: Invisible
No divider is rendered, but the bound is there.
1: Flat
DividerFlatGFX.jpg
A flat divider is in the same height as the road surfaces, the value parameter defines how many times the texture is repeated across the divider.
2: Elevated
DividerElevatedGFX.jpg
The value parameter specifies the height of the divider and the width of the side strips.
3: Wedged
DividerWedgeGFX.jpg
A wedged divider is always one metre high. The top of the sloping sides are always 0.5 metres towards the center of the road from the given divider vertices. The remaining width between the two divider vertices are filled by a flat top strip.

Only two flags are known, besides the two type bits, those control the closing of the short-ends of the divider. Bit seven closes the divider at the start and bit eight closes the divider at the end. The first section of a divided road should close it's start, the last section should close it's end. Intermediary sections shouldn't close anything.

All dividers in these examples use the same textures: DividerTextures.png
The texture index, n, in this case n = 5, points to the texture used for the sides of an elevated divider. n + 1 is used for a flat divider, the sidestrips of an elevated divider and the sloping sides of a wedge divider. Texture n + 2 is used for the center strip of an elevated or wedge divider. Finally, texture n + 3 is used to close the dividers.

struct Attribute_0040 { ushort id = 0x40; ushort nSections; ubyte flags; ubyte texture; // Index in texture list + 1 for divider ushort value; ushort[nSections * 6] vertexRefs; // Indices in the vertex list }
struct Attribute_0042 { ushort id = 0x42; ubyte flags; ubyte texture; // Index in texture list + 1 for divider ushort value; ushort[2 * 6] vertexRefs; // Indices in the vertex list }
struct Attribute_0043 { ushort id = 0x43; ubyte flags; ubyte texture; // Index in texture list + 1 for divider ushort value; ushort[3 * 6] vertexRefs; // Indices in the vertex list }
struct Attribute_0044 { ushort id = 0x44; ubyte flags; ubyte texture; // Index in texture list + 1 for divider ushort value; ushort[4 * 6] vertexRefs; // Indices in the vertex list }
struct Attribute_0045 { ushort id = 0x45; ubyte flags; ubyte texture; // Index in texture list + 1 for divider ushort value; ushort[5 * 6] vertexRefs; // Indices in the vertex list }
struct Attribute_0046 { ushort id = 0x46; ubyte flags; ubyte texture; // Index in texture list + 1 for divider ushort value; ushort[6 * 6] vertexRefs; // Indices in the vertex list }
struct Attribute_0047 { ushort id = 0x47; ubyte flags; ubyte texture; // Index in texture list + 1 for divider ushort value; ushort[7 * 6] vertexRefs; // Indices in the vertex list }

Tunnels and railings

It is easy to define tunnels and railings following road segments in MM2. Just precede the road attribute with attribute 0x4b.

0x4b_Tunnel.jpg

This attribute has several parameters allowing for many different types of tunnel and roadside railing set-ups.

0: Left wall
1: Right wall
2: Wall
3: Flat ceiling
4: Close start left wall
5: Close end left wall
6: Close start right wall
7: Close end right wall
8: Subdivided ceiling
9: Offset start left wall
a: Offset end left wall
b: Offset start right wall
c: Offset end right wall
d: Subdivided inner walls
e: Thick walls
f: Unknown
struct Attribute_004b { ushort id = 0x4b; ushort flags; ushort leftHeight; ushort rightHeight; }

For intersections inside tunnels, use attribute 0x48 instead. This attribute has not been 100% explained yet.

struct Attribute_0048 { ushort id = 0x48; ushort unknown; // Number of possible walls? ushort flags; ushort leftHeight; // ? ushort rightHeight; // ? ushort unknown; ushort[6] wallDefinition; // One bit for each wall in the perimeter? }

Intersections

When defining intersections, a number of specific attributes are used.

Road surface

The road surface in an intersection is usually defined by a triangle fan. Not the regular triangle fan, but by an attribute of the family 0x29 - 0x2f. It is unclear if this family has a general variant that can handle any number of vertices, but if it does, this attribute should be 0x28. Attributes 0x29 to 0x2f are for one to seven triangles, three to nine vertices respectively.

0x29-0x2f_Road_triangle_fan.jpg

These attributes use texture n.

struct Attribute_0029 { ushort id = 0x29; ushort[3] vertexRefs; // Indices in the vertex list }
struct Attribute_002a { ushort id = 0x2a; ushort[4] vertexRefs; // Indices in the vertex list }
struct Attribute_002b { ushort id = 0x2b; ushort[5] vertexRefs; // Indices in the vertex list }
struct Attribute_002c { ushort id = 0x2c; ushort[6] vertexRefs; // Indices in the vertex list }
struct Attribute_002d { ushort id = 0x2d; ushort[7] vertexRefs; // Indices in the vertex list }
struct Attribute_002e { ushort id = 0x2e; ushort[8] vertexRefs; // Indices in the vertex list }
struct Attribute_002f { ushort id = 0x2f; ushort[9] vertexRefs; // Indices in the vertex list }

Sidewalk

Sidewalks are defined by a number of degenerated cross-sections. The attribute family 0x08, 0x0a - 0x0f are seven different attributes used depending on the number of cross-sections. Attribute 0x08 is for any numbre of cross-sections while 0x0a - 0x0f are for two to seven cross-sections respectively.

0x08-0x0f_Sidewalk_strip.jpg

The cross-sections are constructed like this: First comes the vertex on the road surface and then comes the vertex on the outer edge of the sidewalk. The road vertex is expected to be 15 cm below the outer vertex.

There are two special cases of sidewalk strip attributes. The attribute 0x0a is also used to define the end section of a sidewalk that ends.

0x0a_Sidewalk_special0.jpg 0x0a_Sidewalk_special1.jpg

This is indicated by setting both vertices in the first cross-section to zero or one. Zero or one indicates what side of the resulting triangle should be rendered. As usual, the first vertex is the road surface vertex, thus it is expected to be 15 cm below the second vertex representing the outer edge of the sidewalk.

Sidewalk attributes use texture n + 1.

struct Attribute_0008 { ushort id = 0x08; ushort nSections; ushort[nSections * 2] vertexRefs; // Indices in the vertex list }
struct Attribute_000a { ushort id = 0x0a; ushort[2 * 2] vertexRefs; // Indices in the vertex list }
struct Attribute_000b { ushort id = 0x0b; ushort[3 * 2] vertexRefs; // Indices in the vertex list }
struct Attribute_000c { ushort id = 0x0c; ushort[4 * 2] vertexRefs; // Indices in the vertex list }
struct Attribute_000d { ushort id = 0x0d; ushort[5 * 2] vertexRefs; // Indices in the vertex list }
struct Attribute_000e { ushort id = 0x0e; ushort[6 * 2] vertexRefs; // Indices in the vertex list }
struct Attribute_000f { ushort id = 0x0f; ushort[7 * 2] vertexRefs; // Indices in the vertex list }

Crosswalks

A crosswalk is defined as a two-section triangle strip in attribute 0x24. Four vertices defines it's shape and location.

0x24_Crosswalk_strip.jpg

Crosswalk attributes use texture n + 2.

struct Attribute_0024 { ushort id = 0x24; ushort[2 * 2] vertexRefs; // Indices in the vertex list }

Buildings

In PSDL, buildings with flat, textured facades can be made using a group of attributes. A block can have any number of facades, enabling complex structures. But if the facade itself requires a complicated 3D structure it should be modeled and stored in a PKG file and placed with INST. For more information about that, refer to the INST section.

Facade bounds

Each facade has a bounding box. Attribute 0x3c defines the extent of the complete wall, from ground floor, or even basement, to the roof.

0x3c_Facade_bound.jpg

The facade bound attribute follows the ground as defined by the two vertices for the bottom left and bottom right corners of the wall. The height of the wall is defined by a reference into the list of heights. The top side of a facade is always perfectly horizontal.

A facade bound attribute has one additional parameter. This defines the angle for the normal of the wall. This is used to determine the amount of light that hits the wall and thus giving a simple but effective lighting effect. The angle is an integer in the interval 0-63, allowing to define the angle in steps of 5.625 degrees.

struct Attribute_003c { ushort id = 0x3c; ushort normal; // Integer angle in the range 0-63 ushort height; // Index in the height list ushort[2] vertexRefs; // Indices in the vertex list }

Ground level facades

The ground floor must follow the ground surface at the bottom, even if the ground surface isn't perfectly flat. Attribute 0x1c allows this. The 0x1c attribute is defined the same way as the facade bound. But instead of the angle of the normal vector, this attribute has a parameter with a function that has yet to be uncovered.

0x1c_Ground_level_facade.jpg

The top side of a facade is always perfectly horizontal and the facade attributes use texture n.

struct Attribute_001c { ushort id = 0x1c; ushort height; // Index in the height list ushort unknown; // Most likely controlling the scale of the texture ushort[2] vertexRefs; // Indices in the vertex list }

Facades

Most facades are perfectly horizontal, both at the bottom and the top-it would be awkward with sloping floors inside. In MM2, the standard facade is defined using attribute 0x5e.

0x5e_Facade1.jpg 0x5e_Facade2.jpg

This attribute still defines the left and right side with two vertices, but the y-component of these vertices are not used. Instead heights from the height list are used for the y-component of both the bottom and the top.

The facade attribute uses texture n and it has two parameters controlling the scale of the texture. xRepeat and yRepeat indicate the number of times the texture should be repeated horizontally and vertically. This provides a useful function that allows for easy creation of several, identical floors with just one facade attribute. The images show one facade with xRepeat=3, yRepeat=1 and one with xRepeat=3, yRepeat=3.

struct Attribute_005e { ushort id = 0x5e; ushort bottom; // Index in the height list ushort top; // Index in the height list ushort xRepeat; // Number of times to repeat texture horizontally ushort yRepeat; // Number of times to repeat texture vertically ushort[2] vertexRefs; // Indices in the vertex list }

Roofs

Rooftops in MM2 are always flat. They are defined by the triangle fans in attribute family 0x60, 0x62-0x67. The vertices of these attributes are organized in the same way as the regular triangle fans, but these attributes also have a reference to a height in the height list. This height is used to replace the y-component of the vertices.

0x60-0x67_Roof_triangle_fan.jpg

Roof attributes use texture n.

struct Attribute_0060 { ushort id = 0x60; ushort nVertices; ushort height; // Index in the height list ushort[nVertices + 1] vertexRefs; // Indices in the vertex list }
struct Attribute_0062 { ushort id = 0x62; ushort height; // Index in the height list ushort[3] vertexRefs; // Indices in the vertex list }
struct Attribute_0063 { ushort id = 0x63; ushort height; // Index in the height list ushort[4] vertexRefs; // Indices in the vertex list }
struct Attribute_0064 { ushort id = 0x64; ushort height; // Index in the height list ushort[5] vertexRefs; // Indices in the vertex list }
struct Attribute_0065 { ushort id = 0x65; ushort height; // Index in the height list ushort[6] vertexRefs; // Indices in the vertex list }
struct Attribute_0066 { ushort id = 0x66; ushort height; // Index in the height list ushort[7] vertexRefs; // Indices in the vertex list }
struct Attribute_0067 { ushort id = 0x67; ushort height; // Index in the height list ushort[8] vertexRefs; // Indices in the vertex list }