.wdb File Format (Part 1)

V-Chat worlds are held in a proprietary format that to my knowledge has never been publicly released. These are my notes on the .wdb file format, for anyone that might find them useful.

I figured most of this out after many hours detective work with a hex editor, mapping the file out starting with obvious text strings, common floating point values and gradually building up a picture through comparisons between the available .wdb files. At the time I assumed that it would approximate some of the VRML 1.0 elements (in content, if not in structure). Since then I’ve also compared with early versions of DirectX, which also helped. There are still a few mystery areas, but so far these haven’t prevented a reasonable reproduction of the worlds.

Defined here in pseudo-vb, the basic file structure is:

Structure VCWorld
  header As VCHeader	'the header
  fence As VCFence	'the fence
  numNodes As Integer	'how many VCNodes follow
  nodes() As VCNode	'a series of numNodes VCNode structures
End Structure

The file starts with a 12 byte VCHeader:

Structure VCHeader
  id() As Byte		'4 bytes, should be characters WDBV
  version As Byte	'always &H04
  b0 As Byte		'unidentified byte, always &H00
  b1 As Byte		'unidentified byte, always &H00
  b2 As Byte		'unidentified byte, always &H00
  bgBlue As Byte	'background colour blue
  bgGreen As Byte	'background colour green
  bgRed As Byte		'background colour red
  b3 As Byte		'unidentified byte, always &HFF
End Structure

This identifies the file format and (I assume) format version and sets an RGB8 value for the world background. There are three bytes (b0, b1 and b2) that I can’t decipher, though they may simply be part of the version number. Also byte b3 has an unidentified purpose.

Next comes a variable length VCFence section defining what I call ‘the fence’:

Structure VCFence
  numBorderDefs As UInt16	'number of line segments in the fence
  borderDefs() As VCBorderDef	'a series of numBorderDefs VCBorderDef structures
  yMax As Double
  yMin As Double
End Structure

Structure VCBorderDef
  xCoord0 As Double
  zCoord0 As Double
  xCoord1 As Double
  zCoord1 As Double
End Structure

The fence defines of the limits of travel within the world. It consists of a series of line segments defined by numBorderDefs (x0, z0), (x1, z1) pairs, followed by two numbers defining the minimum and maximum y coordinates of the viewer. The line segments may not be crossed, and form a collision boundary. No doubt this simplified geometry was necessary to reduce the processor loading back in 1995.

The simplest fence is in hanami, where numBorderDefs is zero, and the max and min y values are almost the same value. This results in a world where you can’t rise or fall, but there is no limit to travel otherwise. The fence for compass is similar, but allows plenty of vertical travel. At the other end of the scale there is hutchspace, which has 29 line segments defining its fence along with the vertical limits.

These are the only double precision numbers in the file format, so far as I can tell.

The world geometry is then defined by a series of VCNode structures:

Structure VCNode
  blockName As String		'name of the block, or none
  transform() As Single		'4x4 transform matrix
  animation As VCAnimation	'Animation parameters
  numGeometries As UInt16	'number of VCGeometry structures
  geometries() As VCGeometry	'A series of numGeometries VCGeometry structures
  numLights As UInt16		'number of VCLight structures
  lights() As VCLight		'a series of numLights VCLight structures
  numChildren As UInt16		'number of child nodes
End Structure

This starts with a string defining the node name, the first byte of which defines the number of following bytes in the string (i.e. null strings are simply an &H00 byte).

Next comes 16 single precision values which define a 4×4 transformation matrix that is applied to the geometry in this node and to all its child nodes.

The VCAnimation structure contains seven single precision values to control animation, which is limited to rotation about a single axis.

Structure VCAnimation
  originX As Single	'rotation centre x coord (?)
  originY As Single	'rotation centre y coord (?)
  originZ As Single	'rotation centre z coord (?)
  axisX As Single	'rotation axis vector x component
  axisY As Single	'rotation axis vector y component
  axisZ As Single	'rotation axis vector z component
  speed As Single	'rotation speed
End Structure

The first three are always [0.0, 0.0, 0.0]. I am guessing that these are a rotation origin offset that was never used.

The next three are either [0.0, 0.0, 0.0] or [0.0, 1.0, 0.0]. I have taken this to be a vector defining a rotation axis. Where the axis is [0.0, 0.0, 0.0], the speed value is always 0.0 i.e. no animation. Where it is [0.0, 1.0, 0.0] the speed value is non-zero (except for certain non-animated parts of hutchspace, lodge, standingstone and waterhole).

These non-zero values all coincide with animated elements that have a node name of the form “SPINxxxx” or “spinxxxx” where xxxx is a number. The the speed value in radians equals the xxxx value in tenths of a degree. In practice these range between 0.1 and 7.0 degrees and between 345.0 and 359.9 degrees (i.e. -0.1 and -15.0 degrees), indicating rotation speeds in clockwise and anticlockwise directions.

The V-Chat SDK 1.0 Beta document (which unfortunately does not define the .wdb file format!) has this to say about spinning animation:

Naming,  speed of spin, degrees of rotation. The degrees of rotation (determined by the content provider) of the spin will occur at a constant speed, determined by the processor speed of the end user’s machine. Content providers cannot specify a speed of rotation, and user with different processor powers will see spinning objects move at different rates.  Spin objects must be named: SPINXXXX, where XXXX = degree of rotation * 110 per frame about a vector which is central to the object and pointing vertically up.      e.g. SPIN3550 = -5 degrees rotation.”

Next comes numGeometries, defining how many VCGeometry structures follow, then the VCGeometry structures themselves. In practice, this is either 0 or 1.

Following that is numLights, defining how many VCLight structures follow, then the VCLight structures themselves. This too is either 0 (usually, but not always, when numGeometries = 1) or 1 (when numGeometries = 0). Hence in practice a VCNode contains either a single VCGeometry, a single VCLight structure, or neither (though the file format seems to be able to support multiple instances).

The final value numChildren defines how many child VCNodes belong to the current VCNode. There is no referencing or linking within the file. The VCNode hierarchy is defined by this number and by the order of VCNodes in the file, each of which may also have child VCNodes.

That covers the file header, fence and basic structure of the file. Next time I will delve into the VCGeometry and VCLight structures.

What do you think?