-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
"Modern A3D" file format and support #9
Comments
There appear to be two variants of this format, which I assume are different versions: one is seemingly used for map/prop geometry and is marked with ( Version 2All files I have inspected so far only contain a single object without any submeshes, instead the models are split into multiple files with an object each (unlike version 3) (this is also reflected in their naming scheme, for example: Structure// A3DM2 e.g. A3D Modern 2
struct A3DM2
{
char signature[4]; // "A3D\0"
int version; // 0x02000000, 2
int unknownInt1; // 0x01000000, 1
int fileSize; // This is not exactly the file size (always abit larger) but the range seems to be <10 bytes
char unknownBytes1[12];
char* objectName; // Null terminated string that has the object name
Vector3 unknownComponent; // Potentially origin or scale
char unknownBytes2[13];
int vertexCount; // Number of float triplets in the vertex buffer
char unknownBytes3[8];
Vector3* vertexBuffer; // Vertex buffer only contains float triplets
int unknownInt2; // this is usually equal to 2
Vector2* uvCoordinates; // Pairs of floats that are likely UVs, same length as the vertex buffer
int unknownInt3; // this is usually equal to 3
float* unknownFloatData; // Some kind of buffer containing sane float values
char* restOfFile;
}; Version 3I have focused my efforts on version 2 as of now however I made some brief observations. The file seems to name multiple objects as well as texture files; these strings are encoded using a preceding int instead of being null terminated (unlike version 2). The files I have seen are named Structurestruct A3DM3
{
char signature[3]; // "A3D"
short versionMajor; // 0x03, (this may be a variant field rather than a version)
short versionMinor; // 0x00, (unsure, older a3d2 format used major,minor shorts for version)
char* restOfFile;
}; |
@Pyogenics As far as I know my A3D2 format in the plugin was was fully working with all the files i had tested. I notice you mention Can you upload your test files? |
These files are from an even newer format than A3D2.0, only implemented in the newer kotlin versions of Alternativa3D; the version 2.0 I mention is from one of these newer files. The format is much different as shown by my earlier comments. |
Ah ok, i think i added support for 2.0 2.4 2.5 and 2.6 in the existing plugin |
Here is a sample of models I am using currently (extracted from Tanki Online) + some hex dumps I left in (whoops) models.zip |
The models are separated into multiple data blocks which encode various pieces of data about the model, they are started with a unique
Each block has their own structure and may vary between variants (for example: the material block of type 3 contains more data than type 2), here are their definitions in order of appearance in the file: Root blockContains all other blocks. struct A3D3_2_RootBlock
{
int marker; // 1
int unused;
A3D3_2_MaterialBlock materialBlock;
A3D3_2_MeshBlock meshBlock;
A3D3_2_TransformBlock transformBlock;
A3D3_2_ObjectBlock objectsBlock;
}; Material blockstruct A3D3_2_MaterialBlock
{
int marker; // 4
int unused;
int materialCount;
struct A3D3_2_Material {
char materialName[];
float unused[3]; // Unsure
char diffuseMap[]; // type 2 often has no textures assigned to its materials
} materialArray[]; // length of materialCount
} Mesh blockstruct A3D3_2_Mesh
{
int vertexCount;
int vertexBufferCount;
struct A3D3VertexBuffer {
int bufferType;
float bufferDataArray[]; // length of vertexCount * vertex size
} vertexBufferArray[];
};
struct A3D3_2_Submesh
{
int faceCount;
short indexArray[]; // length of faceCount * 3
int smoothGroupArray[]; // length of faceCount
int materialID;
};
struct A3D3_2_MeshBlock
{
int marker; // 2
int unused;
int meshCount;
A3D3_2_Mesh meshArray[];
int submeshCount; // Referred to as "surfaces" in old A3D formats
A3D3_2_Submesh submeshArray[];
}; Transform blockstruct A3D3_2_TransformBlock
{
int marker; // 3
int unused;
int transformCount;
struct A3D3Transform {
float position[3];
float rotation[4]; // quaternion
float scale[3];
} transformArray[]; // length of transformCount
int transformIDArray[]; length of transformCount
}; Object blockstruct A3D3_2_ObjectBlock
{
int marker; // 5
int unused;
int objectCount;
struct A3D3_2_ObjectInfo {
char objectName[]; // type 2 often has empty object names, this is likely why most are separated into multiple files (to avoid naming conflicts)
int meshID;
int transformID;
} objectInfoArray[]; // length of objectCount
}; |
Here are the blocks for version 3: Strings are read differently than the previous variants: struct string
{
int stringLength;
char string[]; // length of stringLength
char paddingBytes[]; // number of padding bytes depends on string length.
// python implementation: `paddingLength = (((stringLength + 3) // 4) * 4) - stringLength`
// the rounding determines how many bytes it is
} Root blockContains all other blocks. struct A3D3_3_RootBlock
{
int marker; // 1
int blockSize;
A3D3_3_MaterialBlock materialBlock;
A3D3_3_MeshBlock meshBlock;
A3D3_3_TransformBlock transformBlock;
A3D3_3_ObjectBlock objectsBlock;
}; Material blockThe same as type 2 but with the new string format. struct A3D3_3_MaterialBlock
{
int marker; // 4
int blockSize;
int materialCount;
struct A3D3_3_Material {
string materialName;
float unknown[3]; // Unsure, could be origin?
string diffuseMap; // type 2 often has no textures assigned to its materials
} materialArray[]; // length of materialCount
} Mesh blockstruct A3D3_3_Mesh
{
string meshName;
float unknown[7]; // Bound box data? (x-min, y-min, z-min, x-max, y-max, z-max, unknown)
int vertexCount;
int vertexBufferCount;
struct A3D3VertexBuffer {
int bufferType;
float bufferDataArray[]; // length of vertexCount * vertex size
} vertexBufferArray[];
};
struct A3D3_3_Submesh
{
int indexCount;
short indexArray[]; // length of indexCount
char paddingBytes[]; // length is calculated the same as string
};
struct A3D3_3_MeshBlock
{
int marker; // 2
int blockSize;
int meshCount;
A3D3_3_Mesh meshArray[];
int submeshCount; // Referred to as "surfaces" in old A3D formats
A3D3_3_Submesh submeshArray[]; // length of submeshCount
}; Transform blockstruct A3D3_3_TransformBlock
{
int marker; // 3
int blockSize;
int transformCount;
struct A3D3Transform {
string name;
float position[3];
float rotation[4]; // quaternion
float scale[3];
} transformArray[]; // length of transformCount
int transformIDArray[]; length of transformCount
}; Object blockstruct A3D3_3_ObjectBlock
{
int marker; // 5
int blockSize;
int objectCount;
struct A3D3_2_ObjectInfo {
int meshID;
int transformID;
int materialCount;
int materialIDArray[]; // length of materialCount
} objectInfoArray[]; // length of objectCount
}; |
After flash EOL in 2020 Alternativa rewrote their engine and main product, Tanki Online, in kotlin to deploy for mobile and HTML5. Recently I discovered that Alternativa has further extended the A3D model format past original A3D1 and A3D2 formats and is actively using it in Tanki Online. It only makes sense that this tool should also support the modern A3D formats, I will document my research and findings todo with this format here and in future may open a PR to follow up and add support for this newer format to my other PR.
The text was updated successfully, but these errors were encountered: