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.
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.
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.
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.
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
-
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
-
The value parameter specifies the height of the divider and the
width of the side strips.
- 3: Wedged
-
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
}