Occlusion Interior Metadata (naOcclusionInteriorMetadata)

The next generation of RAGE games, including GTA5 and RDR3, use an advanced audio occlusion system for interiors to accurately simulate how audio would filter and attenuate in the real world.

This system is driven by Occlusion Interior Metadata files, internally known as naOcclusionInteriorMetadata, which are a type of tuning file (PSO #mt).

naOcclusionInteriorMetadata1

Field Type Description
PortalInfoList array of naOcclusionPortalInfoMetadata2 Portal metadata entries
PathNodeList array of naOcclusionPathNodeMetadata3 Path node metadata entries

naOcclusionPortalInfoMetadata

Field Type Description
InteriorProxyHash uint324 Interior instance proxy hash5
PortalIdx int32 Portal index6
RoomIdx int32 Room index7
DestInteriorHash uint324 Destination interior instance proxy hash5; otherwise same as InteriorProxyHash
DestRoomIdx int32 Destination room index
PortalEntityList array of naOcclusionPortalEntityMetadata8 Attached portal entity metadata entries

naOcclusionPortalEntityMetadata

Field Type Description
LinkType uint32 Link type for the attached portal entity; 1 if portal leads to the same interior, 2 if the portal leads to a different interior
MaxOcclusion float32 Occlusion influence of attached entity; the higher the value, the stronger the occlusion
EntityModelHashkey uint32 Hash string of the attached entity
IsDoor bool True if the attached entity is a door, false otherwise
IsGlass bool True if the attached entity is a window or glass, false otherwise

naOcclusionPathNodeMetadata

Field Type Description
Key uint324 Path node key9
PathNodeChildList array of naOcclusionPathNodeChildMetadata10 Path node child metadata entries

naOcclusionPathNodeChildMetadata

Field Type Description
PathNodeKey uint324 Path node key9
PortalInfoIdx int32 Index of portal info entry in PortalInfoList

Notes
1 The filename of these #mt’s is the unsigned proxy hash. :leftwards_arrow_with_hook:
2 Array is sorted by ascending RoomIdx, and then further sorted by PortalIdx. :leftwards_arrow_with_hook:
3 Array is sorted by ‘path type’, then by ascending Key value. :leftwards_arrow_with_hook:
4 Confirmed to be uint32. Although CodeWalker parses these as int32s. :leftwards_arrow_with_hook:
5 Proxy hash is generated as such: :leftwards_arrow_with_hook:

 // CMloInstanceDef::CEntityDef::rage__fwEntityDef.position
vec3 mloPosition;
// CMloInstanceDef::CEntityDef::rage__fwEntityDef.archetypeName
int32 mloName; 
uint32 proxyHash = ((mloName) ^ (mloPosition.X * 100) ^ (mloPosition.Y * 100) ^ (mloPosition.Z * 100)) & 0xFFFFFFFF;

6 This is not the same index as the array of CMloPortalDef’s in CMloInstanceDef’s. Instead, a special list is generated that includes two entries per CMloPortalDef’s; one for each portal face. The PortalIdx is the local index of the portal face belonging to the RoomIdx in question. A detailed example is included below. :leftwards_arrow_with_hook:

v_trevors
     limbo[0]
             0->6 [0][0] // RoomIdx -> DestRoomIdx [PortalIdx][CMloPortalDef]
             0->4 [1][1]
             0->4 [2][2]
             0->4 [3][3]
             0->1 [4][11]
             0->7 [5][12]
     rm_bedroom01[1]
             1->5 [0][4]
             1->0 [1][11]
     rm_bedroom02[2]
             2->5 [0][5]
     rm_Bathroom[3]
             3->5 [0][6]
     rm_Lounge[4]
             4->0 [0][1]
             4->0 [1][2]
             4->0 [2][3]
             4->5 [3][7]
             4->7 [4][9]
             4->7 [5][10]
     rm_hallway[5]
             5->1 [0][4]
             5->2 [1][5]
             5->3 [2][6]
             5->4 [3][7]
             5->6 [4][8]
     rm_frontDoor[6]
             6->0 [0][0]
             6->5 [1][8]
     rm_Kitchen[7]
             7->4 [0][9]
             7->4 [1][10]
             7->0 [2][12]

7 This is the CMloRoomDef index as it would appear in a CMloInstanceDef. :leftwards_arrow_with_hook:
8 Likely sorted by EntityModelHashkey. Citation needed. :leftwards_arrow_with_hook:
9 Path Node Key is the difference between two room keys plus 1-5 (1-5 likely represents audio channels). :leftwards_arrow_with_hook:

// Room Key formula
uint32 roomKey = (room.name == "limbo") ? (uint32)(jooat("outside")) : (uint32)(proxyHash ^ jooat(room.name));

// Path Node Key formula
uint32 pathNodeKey = (roomA == roomB) ? 0 : (roomA.key - roomB.key) + (uint32)pathType; // pathType is an int 1-5, likely an audio channel

10 Array is sorted by ascending PortalInfoIdx value. :leftwards_arrow_with_hook:

6 Likes

Struct dumps do seem to indicate a number of these are indeed uint types.

2 Likes

Thanks, I’ll sort out the details shortly. Interesting that Codewalker is parsing these as signed int32s.

I would say the only last mystery regarding occlusion interior metadata is what the +(1-5) values actually do for Keys. I figured out the conditions for when the path node keys should have +(1-5) added to it, but not what the values actually signify. I have a strong hunch it is a ‘link type’ now that I think about it.

2 Likes

The additional index seems to be used in an inner method of naEnvironmentGroup regarding occlusion calculation, where depending on a flag (0x40 in a field in naEnvironmentGroup) being set it will either iterate up or down to find a matching link with that index. Another field seems to dictate the upper limit for the index lookup.

The logic can be summarized as follows:

if nonReverseLookup:
    while not foundMatchingPath:
        # clamping behavior
        [lookupPath: [makeHash: ..., index]]
        index++

I’ll check what defines these limits and this flag, though the field offsets are low and may be hard to find.

2 Likes

The ‘limit’ or type seems slightly more relevant, indeed: ‘Shoreline’ for example has 2, as does ‘CollisionMLO’ (an indoor collision?). The default is 3, but say audScriptAudioEntity and audHeliAudioEntity have 5 again. I suspect this is therefore some tuning value for overriding certain sounds’ occlusion levels.

Looking up sequentially makes sense as not all rooms would define all types, and I guess it’s rather some ‘priority’ level instead: a script sound is supposed to be loud, as is a helicopter. It can scan both ways I guess for some sort of ‘leading outside’, perhaps? I’ve not seen this 0x40 flag set anywhere I could triivially find, but I guess it scans for the closest priority override if one is not found.

2 Likes