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.