3D Object Records
The following description has been tested on all the 3D Objects contained in Arch3D.BSA and appears to be sound. There are two records which have some problems (offsets 0x008B8D58 and 0x013274C6) and don't follow the known format of the other 10249 records. They have a large section of repeating bytes just after the 3D object header and their Data2 section is not the standard. These records could very well be corrupt or not used or of a special, undiscovered, format.
Each BSA record contains the information for one 3D object. Note that each record appears to be similar to the .3D file format used in Battlespire. One difference is that here the .3D files are contained in one big file, while in Battlespire they are in individual files. Records range from 212 to 81394 bytes in size.
A 3D Object Record normally has the following sections:
- 3D Object Header
- Always the first 64 bytes in each 3D Object Record.
- This provides offsets to reference the other sections of the record.
- The Point Structures which define the 3D Object Record's geometry are stored here.
- The Plane Structures which define the 3D Object Record's geometry are stored here.
- Plane Structures refer to elements of the PointList.
- The Normals for each Plane Structure are stored here.
- Each Plane Structure implies a 24 byte value, stored here.
- Each 3D Object Record refers to zero or more ObjectData Structures, stored here.
Two records contain a block of repeating bytes of unknown purpose not referenced within their 3D Object Record Header.
 3D Object Header
Each 3D Object is described by a header, which is always the first 64 bytes of the 3D Object Record. This header reports the 3D Object's Version (necessary for interpreting the 3D Object's geometry), number and location of Point structures Plane, etc.
|0-3||String (7-bit ASCII)||Version||Appears to be a version number. The value is not null-terminated. Most of the records report "v2.7", though 135 report "v2.6" and 9 report "v2.5". The "v2.5" records require special care when decoding their geometry (see below).|
|4-7||Int32||PointCount||The count of Point Structures in the 3D Object's PointList. All records must report at least 3 for their PointCount field. Values should not be negative.|
|8-11||Int32||PlaneCount||The count of Plane Structures in the 3D Object's PlaneList. All records must report at least 1 for their PlaneCount field. Values should not be negative.|
|12-15||UInt32||Unknown1||An unknown field.|
|24-27||Int32||PlaneDataOffset||The offset relative to the 3D Object Record for the 3D Object's PlaneDataList. The position of the PlaneDataList would simply be
|28-31||Int32||ObjectDataOffset||The offset relative to the 3D Object Record for the 3D Object's ObjectDataList. The position of the ObjectDataList would simply be
|32-35||Int32||ObjectDataCount||The count of ObjectData elements in the 3D Object Record's ObjectDataList.|
|48-51||Int32||PointListOffset||The offset relative to the 3D Object Record for the 3D Object's PointList. The position of the PointList would simply be
|52-55||Int32||NormalListOffset||The offset relative to the 3D Object Record for the 3D Object's NormalList. The position of the NormalList would simply be
|60-63||Int32||PlaneListOffset||The offset relative to the 3D Object Record for the 3D Object's PlaneList. The position of the PlaneList would simply be
 PointList Section
This section is a contiguous list of PointCount Point structures.
 Point Structure
Each Point structure is an Int32 triplet:
While the PointList Section is usually adjacent to the Object Header Section, there are two files where the PointList Section begins some distance after the Object Header Section.
 PlaneList Section
The PlaneList Section contains PlaneCount Plane Structure elements describing the geometry of the 3D Object Record. This includes the necessary points to describe the geometry, as well as the necessary texture information to display the object.
Each Plane Structure is described by her own header, followed by three or more PlanePoint structures.
Plane Structure elements are contiguous within the PlaneList; that is to say the second Plane Structure immediately follows the first Plane Structure.
 Plane Structure Header
Each Plane Structure is described by a header. From this data, one can load the Plane's geometry and texture file information.
The 8 byte long header is described as followed:
|0||Int8||PlanePointCount||The count of PlanePoint structures describing this Plane's geometry.|
|1||UInt8||Unknown1||Usually 0x00 for most plane records (about 2% are non-zero). Values range from 0x00 to 0xFF although most are repeating values in the 0x00 to 0x40 range.|
|2-3||UInt16||Texture||This is the texture information compressed into a bitfield.|
|4-7||UInt32||Unkown2||Almost always 0x00000000, 0x00010000, 0x00010001, or 0x00010002 and rarely a wide range of other values. Probably two short values.|
 Texture Structure
The Texture field in the Plane Header is a bitfield containing the FileIndex and ImageIndex fields compressed into a single UInt16. It can be interpreted as follows:
||The ordinal index of the image to use in the texture file described by FileIndex.|
||The extension of the file to use.|
Ex: If ImageIndex were 7, and FileIndex were 214, one would use the eighth image within TEXTURE.214 for the referring Plane Structure.
 PlanePoint Structure
Immediately following the Plane Header is a set of PlanePointCount PlanePoint structures. Each structure contains a reference to a Point entry in the Point List Section, as well as the UV coordinates.
The 8 byte structure is as follows:
|0-3||Int32||PointOffset||This is an offset to a Point Structure within the current 3D Object's PointList. The offset is relative to the 3D Object's PointListOffset field. No values should be negative. This field must be interpreted in context of the 3D Object's Version field.|
|4-5||Int16||U||The texture UV's U component for the Point referenced.|
|6-7||Int16||V||The texture UV's V component for the Point referenced.|
See UV Texture Coordinates for interpretting Daggerfall UV coordinate values.
 NormalList Section
The NormalList is a PlaneCount set of Point structures, containing the Normals for each Plane structure. Each Point structure element within the NormalList is associated by ordinal to the corresponding Plane Structure; that is to say the first Point structure in the NormalList is the normal for the first Plane structure. Normals are normalized and scaled by 256, so a normal pointing straight down the X axis would be 256 0 0.
 PlaneDataList Section
For each Plane structure, there is a corresponding PlaneData structure within the 3D Object Record's PlaneDataList, by ordinal.
The purpose of the data is currently unknown.
|0-23||Int8[ 24 ]||Unknown||Unknown purpose|
 ObjectDataList Section
There are ObjectDataCount elements of ObjectData Structures within the ObjectDataList. Each ObjectData structure contains zero or more 6 byte long ObjectDataValue elements. The ObjectData elements are contiguous; each immediately follows their predecessor.
Each ObjectData Structure is composed of an ObjectDataHeader Structure, and zero or more ObjectDataValue structures. The ObjectDataValue structures, if present, immediately follow the ObjectDataHeader.
 ObjectData Structure Header
|0-15||Int32[ 4 ]||Numbers||Could be a Point Structure and an Int32 magnitude, but in which order?|
|16-17||Int16||SubrecordCount||The count of the number of ObjectDataValue Structure immediately following.|
Each ObjectData Structure contains zero or more ObjectDataValue Structures. An ObjectDataValue structure appears to be a 6 byte field of an unknown purpose.
|0-5||Int8[ 6 ]||Value||Unknown purpose.|
 Special Objects
The following list of objects (by RecordId) in the ARCH3D file have special mention:
- Object 4722 (0x8B8D58)
- This v2.6 3D object has a truncated, or different, ObjectDataList which does not conform with the known format.
- Object 7614 (0x13274C6)
- This v2.7 object has an invalid ObjectDataOffset value (it points to the middle of the PlaneDataList section).
All but 25 of the 10251 records have unique RecordId's. It is not known if the Daggerfall engine loads the first match, or the last match when two or more records collide by RecordId values.