Site Journal

.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.

Texture Animation

I wanted to preserve use of the original texture images with their vertically stacked animation frames, but also to have the animation work in a standalone x3d file that is inlined into the web page.

V-Chat geometry is stored in world.wdb files which very usefully include the names of animated textures in the form AnimXXYY, where XX is the number of animation frames in the texture and YY is the frame duration in 100ths of a second e.g. Anim0430 indicates a 4 frame animation, each frame held for 0.3 seconds.

The first step is to add a TextureTransform node to the animated texture image that scales it by a factor of XX on the y axis (which means a scale factor of 1/XX):

<TextureTransform DEF='ttAnim0430' scale='1 0.25' translation='0 0' />

Next a TimeSensor node is added that repeats at the right rate. In this case 4 frames at 0.3 seconds needs a 1.2 second cycleInterval:

<TimeSensor DEF='tsAnim0430' cycleInterval='1.2' loop='true' />

The TimeSensor output gets turned into the required texture translation coordinates by using a PositionInterpolator. This outputs a three dimensional coordinate (an SFVec3f), but it turns out that this can be routed to the two dimensional translation attribute (an SFVec2F) with no ill effects provided each keyValue is given as an x y z triple. (Lesson learned: much time wasted messing around with a CoordinateInterpolator, which doesn’t work). The example here needs:

<PositionInterpolator id='piAnim0430' DEF='ciAnim0430' key='0 0.25 0.25 0.5 0.5 0.75 0.75 1' keyValue='0 0 0 0 0 0 0 1 0 0 1 0 0 2 0 0 2 0 0 3 0 0 3 0'/>

Finally, a couple of ROUTE nodes plumb everything together:

<ROUTE fromNode='tsAnim0430' fromField='fraction_changed' toNode='piAnim0430' toField='set_fraction'/>
<ROUTE fromNode='piAnim0430' fromField='value_changed' toNode='ttAnim0430' toField='translation'/>

(Broken) Compass

Preview of Compass added, plenty of issues to address though: navigation, texture animation, geometry animation, rendering fidelity, links, portals and sound.

Update: Navigation fixed – Viewpoint orientation had negative y value. Added Ctrl-drag for strafe and raise/lower. Trapped a potential divide by zero in the moveIt() function.

Launch

Blimey. So much has changed in the 15 years since I last had a web site that I’m having to head off to learn new things at every turn. The first cut of HutchWorld is now working well enough to publish. I’ve added a simple touch interface for tablets, including using multi-touch for ‘strafe’ and up/down navigation. I’ll probably change the look of the site several times but for now this clean ‘Scaffold’ template does the job well.

Internet Explorer and Edge on Windows run no faster than 1 frame per second, compared to around 60 fps for everything else. I’ve spent far too long trying to fix that, so Chrome, Opera and Firefox are the windows choices, and Chrome, Firefox, Safari and Edge are the IOS choices. I haven’t yet been able to test on Linux, Android or MacOS – all feedback welcome.