The purpose of the network protocol is to allow data to be serialized and transmitted over a network in a way that is independent of the machine type of the transmitting end. This requires well-defined ways to represent various kinds of values, and how this is done is specified in this section.
These are the types defined below, collected for easy reference:
VNodeType, VNodeOwner, VNOParamType, VNTagType, VNOTagConstants, VNRealFormat, VNOMethodConstants, VNGLayerType, VNMLightType, VNMNoiseType, VNMRampType, VNMRampChannel, VNMBlendType, VNMFragmentType, VNBLayerType, VNABlockType, VNAConstants, VNTConstants
Integers are the most fundamental data types used in Verse. This section presents the various specific integer types in use. They all have explicit bit counts, since it is important to know the exact number of bits needed to represent a value when working in a network context.
Bits in integers are numbered from zero, which is the least significant bit (the bit with the value one) and up. The notation X:Y is occasionally used to refer to a subfield in an integer, consisting of the (X-Y)+1 bits with the indicated bits being the most and least significant, respectively. For example, 15:12 is the top half-byte of a uint16.
A signed 8-bit integer quantity. Can represent values between -128 and 127, inclusive.
A signed 16-bit integer quantity. Can represent values between -32768 and 32767, inclusive.
A signed 32-bit integer quantity. Can represent values between -2147483648 and 2147483647, inclusive.
An unsigned 8-bit integer quantity. Can represent values between 0 and 255, inclusive.
An unsigned 16-bit integer quantity. Can represent values between 0 and 65535, inclusive.
An unsigned 32-bit integer quantity. Can represent values between 0 and 4294967295, inclusive.
Real numbers are highly useful when dealing with 3D geometry data and related applications, so Verse defines two types for handling such numbers, below. They are called reals although they are of course not true real numbers in the mathematical sense, but approximated using floating-point numbers. The same holds true for integers however, and that name is natural so by analogy we have reals.
For clarity and convenience, there are many integer types defined that are simply aliases for one of the types defined above.
Having these aliases makes many descriptions easier to read, and in some situations (such as API function prototypes) makes them a lot more information-dense, descriptive, and generally useful. Note that these are not enumerations; there are no named values associated with these types.
Table 3-1. Integer Aliases
Used to represent boolean values, i.e. values that are either true or false. The values 0 (for false) and 1 (for true) are used.
Used to identify nodes.
Used to identify layers in nodes that have them.
Used to identify fragments in material nodes.
Used to identify buffers in audio and text nodes.
These are variable-length character strings, which are mainly used to assign and reference names to various pieces of data.
Strings have explicit (maximum) sizes, in 8-bit bytes. In the encoding actually used, a character is not a byte, but since the strings need to be used in a network context, this is the simplest way to put a known limit on their sizes. Of course, being of variable length, a string does not have to occupy its maximum size, but can vary its encoded size depending on the content.
These are integer types with a set of pre-defined values. For fields having an enumerated type, only values described in the enumeration are legal to use.
Unless otherwise noted, all enumerations are represented as the given integer value using an uint8.
Defines a set of integers that are used to identify the various node types in Verse.
Used to indicate whether a receiving client is actually the owner of a node or not.
Identifies the type of an object node method parameter.
Identifies the type of a node tag value.
These are constants used when working with node tags. The name is misleading.
Constants used when subscribing to transformations, to select the desired precision.
These are some constants used with the object node's method system.
Identifies the various types of layer supported by the geometry node.
Identifies the types of light that a material node light fragment can receive.
Identifies the types of noise generated by a material node noise fragment. Has plenty of room for expansion.
Identifies which kind of interpolation is done by a material node ramp fragment.
Identifies which color channel is to be affected by a material node ramp fragment.
Identifies which type of blending operation a material node blend fragment does.
Identifies the type of a material node fragment.
Identifies the type of a bitmap node layer, i.e. what type of value is stored in each of the pixels.
Identifies the data representation used for blocks of audio samples. Note that 24-bit integers are supported.
24-bit samples are represented at the application level as uint32s, with the 24-bit sample left-adjusted, thus occupying the topmost 24 bits. The bottom eight bits contain a copy of the topmost eight. This means that 24-bit and 32-bit samples can be used interchangably, and if you really need the true 24-bit data, just shift the value Verse gave you right by eight bits. In network transmissions, these duplicate bits are not transmitted, which is why the block size (see below) for 24-bit integer data is larger than for 32-bit.
As an example, consider a sample value of 0.7. As an 8-bit integer, this would be 0.7 * 27-1 = 0.7 * 127 = 88. As a 24-integer, taking the above rules into account, we get: 0.7 * 223-1 = 0.7 * 8388607 = 5872024. If we convert this decimal number to hexadecimal, to see the individual bytes, we get 0x00599998. Now, we shift this to the left 8 bits, which gives 0x59999800. Finally, copying the top byte into the least significant place gives us the final result, which is 0x59999859. As a reference, the same sample value as a 32-bit integer is: 0.7 * 231-1 = 0.7 * 2147483647 = 1503238552. This value converted to hexadecimal is 0x59999998. The difference between the 24- and 32-bit representations here is on the order of 0.00002%.
Values needed to work with the audio node. The VN_A_BLOCK_ symbols specify the number of individual samples in a block of the indicated type.
It is important to note that these are not maximums; they are the only valid block sizes used in Verse. There is no such thing as a shorter block, the full block size according to the table is always used. Set any un-used samples to 0.
All datatypes can be formed into arrays, which are simply linear sequences of values of one type.
The notation for an array is type[length] (where type is a type name and length an integer literal) for an array of known length, type[min..max] (where min and max are integer literals) for an array whose length can vary, type[SYMBOL] (where SYMBOL is a symbolic constant defined elsewhere) and type for an array whose length is not known. Extraneous information is needed to resolve how long a variable-length array is in each case; no length information is ever stored in the array itself.
Sometimes, an array length needs to be dynamic and not known in advance. In such cases, there is always some related (adjacent in a structure, for instance) field that gives the length, and the notation type[fieldname] is used to indicate this condition. Such a fieldname is in lower-case characters, to make it distinct from the case of a symbolic (upper-case) constant.
When a union type (see below) is made into an array, it is not defined if each element in the array has the same type, or if the type can vary between slots. This needs to be specified for each such use, and taken into account when encoding (or decoding) the data. The ability to vary the type of an array of unions per-slot gives a lot of flexibility.
These are types that collect several values into single entity. Each value is called a field of the structure.
Union types can only contain one of their member fields at any given time, and do not by themselves "know" which of the members is the one in use.
This type describes a quaternion, a way to represent an arbitrary rotation. It uses 32-bit precision for all its floating point members.
The first three fields, x y and z, is the vector part of the quaternion, while the final field w represents the cosine of half the angle of rotation.
A good description of quaternions and operations on them is the Matrix and Quaternion FAQ (external link).
This type describes a quaternion, a way to represent an arbitrary rotation. See the description of VNQuat32 above, the only difference is that this type is used when 64-bit precision is desired.
A union type that can hold a single value for a method parameter.
The vector and matrix members (vreal32_vec, vreal32_mat, vreal64_vec and vreal64_mat) are used for vectors of lengths 2, 3 and 4 inclusive and matrices of ranks 2x2, 3x3, and 4x4 inclusive.
Additional information, about which member is actually present, is needed to correctly interpret a value of this union type.
This structure is used to hold a "packed" version of a method call's parameters. The initial size field is the size, in bytes, of the structure as a whole, including the size field itself.
The array of VNOParam unions does not have uniform type over its length; each array member can have its own type. This means that additional data about the number of parameters and the type of each parameter is needed to properly create (pack) and interpret (unpack) these structures.
If the method call in question has no parameters, size will simply be two and no array will be present.
A union type that can hold the value of a node tag. Requires additional information to know which member is actually in use.
Represents the value of a single point in a material node ramp fragment.
A union that can hold a description of a single material node fragment. Its members are structures, since each fragment requires multiple values to define it.
A union that holds the pixels of a single tile from a bitmap node.
Note how the vuint1 member is actually an array of vuint8, which holds the complete data for a tile in a 1-bit per pixel layer. All pixels are packed in a left-to-right, bottom-to-top manner, with the most significant bits considered to the left of the less significant ones. The array has length 8, which gives it a total capacity of 8 * 8 = 64 bits, which is precisely what is needed for one tile. The first uint8 holds the first scanline of the tile, and bit 7 of that uint8 holds the topmost leftmost pixel of the tile.
Pixels in tiles can be indexed by a coordinate pair (x,y), with x and y both in the range [0,7] inclusive. Then, (0,0) is the topmost leftmost pixel, and (7,7) is the pixel at the bottom right corner of the tile. For multi-bit pixel types, a pixel is referenced as simply a[y * 8 + x], where a is the union member (one of vuint8, vuint16, vreal32 and vreal64). For one-bit tiles, the pixel is tested by the C expression (vuint1[y] & (128 >> x)).
A union that holds a the samples for a block of audio. The number of samples held depends on the sample type; knowledge of the union's intended type is always needed in order to correctly interpret it.