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.