Unreal File Format

Written by PantherD of Team Panther.

Thanks to Tim Sweeney for the UnrealEd mesh code
used to put together this information.
Also thanks to Legend Entertainment for releasing the code to 3ds2unr.

Edited by Paul Bourke


Unreal models come in a combination of two files. Data files which you will see as _d.3d and Aniv files which you'll see as _a.3d. Both are binary files. The data file contains a dataheader with number of polygons, number of vertices, etc. A lot of the header data is actually throw away (you'll see later in my document a typedef for the dataheader structure). You'll also find each polygon with it's three vertex indices. The index is the order of the vertex within the aniv file. All polygons are triangles. The Aniv file contains the a list of the vertices per each frame. The first part of the aniv file describes the number of frames, then the framesize (Number of vertices * bytesize of a vertex). Then all the vertices for every frame.

Data File

Data File Header

The data file header should be written to the file first. The only thing that you need to worry about is the number of polygons and the number of vertices. The rest of the information is not currently read by UnrealEd.

typedef struct datahead_struct {
   unsigned short  NumPolygons;
   unsigned short  NumVertices;
   unsigned short  BogusRot;
   unsigned short  BogusFrame;
   unsigned long   BogusNormX;
   unsigned long   BogusNormY;
   unsigned long   BogusNormZ;
   unsigned long   FixScale;
   unsigned long   Unused[3];
   unsigned char   Unknown[12];
} dataheader;

Data File Polygons

The polygon structure includes references to the three vertices, the type of polgyon, the color (which I honestly don't know what that is good for), the texture UV coords and a texture number. The flags are currently unused by UnrealEd. The texture number will correspond with whatever is in the .uc class file for the object. The only texture information stored for a polgyon is the UV coordinates. The actual texture image file reference is done with the .uc class file.

The mVertex[3] is an array of the 3 vertices. Each vertex (ex. MVertex[0] = 2) stores the index of the vertex coordinate information stored in the aniv file. The values will be [0 to (n - 1)], n being the number of vertices in the mesh. My example points the first polgyon of the triangle to whatever the 3rd vertices in the aniv file coordinate are.

The UV texture coordinates are stored as a value within a 256 x 256 grid. Standard UV coordinates are [0 to 1] with values between. You can correspond 256 to 1 for Unreal. Typical Unreal texture map images are [128x128] or [256x256] pixels. It's a two dimensional array the first dimension being 0 or 1 indicating whether it's a U or V coord:


[0][0] - U coordinate of the first vertex
[1][0] - V coordinate of the first vertex
[0][1] - U coordinate of the second vertex
[1][1] - V coordinate of the second vertex
[0][3] - U coordinate of the third vertex
[1][3] - V coordinate of the third vertex

James Mesh Types

0 = Normal one-sided
1 = Normal two-sided
2 = Translucent two-sided
3 = Masked two-sided
4 = Modulation blended two-sided
8 = Placeholder triangle for weapon positioning (invisible)
typedef struct unreal_tri_struct {
   unsigned short mVertex[3]; // Vertex indices
   char mType;                // James' Mesh Type
   char mColor;               // Color for flat and Gourand Shaded
   unsigned char mTex[3][2];  // Texture UV coordinates
   char mTextureNum;          // Source texture offset
   char mFlags;               // Unreal Mesh Flags (unused)
} unreal_tri;

This is my datafile output function that I used for my UnrealSaver plugin (note this also output a UV text file I am messing with as well):

void CreateDataFile(MeshData *mesh) {
   FILE *data_fp;
   FILE *uv_fp;
   dataheader dh;
   unreal_tri *unrealpoly;
   int i;
   memset(&dh, '\0', sizeof(dataheader));
   dh.NumPolygons = mesh->NumPolygons;
   dh.NumVertices = mesh->NumVertices;
   data_fp = fopen(mesh->config->datafile, "wb");
   uv_fp = fopen(mesh->config->uvfile, "w");
   unrealpoly = malloc(sizeof(unreal_tri));
   if (data_fp) {

      //write the Header
      fwrite(&dh, sizeof(dataheader), 1, data_fp);
      //write the polygon data
      mesh->PolyList->curr_poly = mesh->PolyList->start_poly;
      while (mesh->PolyList->curr_poly != NULL) {
         // copy polygon data to the unrealpolygon for storage
         for (i = 0; i < 3; i++) {
            unrealpoly->mVertex[i] = mesh->PolyList->curr_poly->mVertex[i];
            unrealpoly->mTex[i][0] = mesh->PolyList->curr_poly->mTex[i][0];
            unrealpoly->mTex[i][1] = mesh->PolyList->curr_poly->mTex[i][1];
            //write the UV data out to uvd file - 8/29/98 
            fprintf(uv_fp, "%d %d ", unrealpoly->mTex[i][0], unrealpoly->mTex[i][1]);

         //write newline for next polygon
         fprintf(uv_fp, "\n");
         unrealpoly->mType = mesh->PolyList->curr_poly->mType;
         unrealpoly->mColor = mesh->PolyList->curr_poly->mColor;
         unrealpoly->mTextureNum = mesh->PolyList->curr_poly->mTextureNum;
         unrealpoly->mFlags = mesh->PolyList->curr_poly->mFlags;
         fwrite(unrealpoly, sizeof(unreal_tri), 1, data_fp);
         mesh->PolyList->curr_poly = mesh->PolyList->curr_poly->next_poly;

Aniv File

The aniv file is actually pretty simple. Each vertices is stored in an unsigned long (examine my code below for the formula). The first step is to write the number of frames, then the framesize (number of vertices * sizeof(unsigned long) ).

int CreateAnimFile(MeshData *mesh) {
   FILE *anim_fp;
   unsigned long unreal_vertex;
   int frames_written;
   short FrameSize;
   frame *curr_frame;
   vertices *curr_vertices;
   frames_written = 0;
   anim_fp = fopen(mesh->config->anivfile, "wb");
   if (anim_fp) {
      //write number of frames
      fwrite(&mesh->frame_header->number_of_frames, sizeof(short), 1, anim_fp);
      //write framesize
      FrameSize = mesh->NumVertices * sizeof(unsigned long);
      fwrite(&FrameSize, sizeof(FrameSize), 1, anim_fp);
      //write the vertices (converted to unreal unsigned long storage)
      curr_frame = mesh->frame_header->frame_list;
      while (curr_frame != NULL ) {
         curr_vertices = curr_frame->VertexList->start_vertex;
         while (curr_vertices != NULL) {
            unreal_vertex = ( (int)(curr_vertices->x * 8.0) & 0x7ff ) | ( ( (int)(curr_vertices->y * 8.0) & 0x7ff) << 11 ) | ( ( (int)(curr_vertices->z * 4.0) & 0x3ff) << 22 );
            fwrite(&unreal_vertex, sizeof(unsigned long), 1, anim_fp);
            curr_vertices = curr_vertices->next_vertex;
         curr_frame = curr_frame->next_frame;
   return frames_written;

UC Class File

void CreateUCFile(MeshData *mesh) {
   FILE *fp;
   int texture_loop;
   fp = fopen(mesh->config->ucfile, "w");
   if (fp) {
      fprintf(fp, "#exec MESH IMPORT MESH=%s ANIVFILE=MODELS\\%s_a.3d DATAFILE=MODELS\\%s_d.3d X=0 Y=0 Z=0\n", mesh->config->classname, mesh->config->classname, mesh->config->classname);
      fprintf(fp, "#exec MESH ORIGIN MESH=%s X=0 Y=0 Z=0\n", mesh->config->classname);
      //Print out the anmimation sequences, using the all, an intial still and one last sample
      fprintf(fp, "#exec MESH SEQUENCE MESH=%s SEQ=All STARTFRAME=0 NUMFRAMES=%d\n", mesh->config->classname, mesh->NumFrames);
      fprintf(fp, "#exec MESH SEQUENCE MESH=%s SEQ=Still STARTFRAME=0 NUMFRAMES=1\n", mesh->config->classname);
      fprintf(fp, "#exec MESH SEQUENCE MESH=%s SEQ=YouNameIt STARTFRAME=0 NUMFRAMES=%d\n", mesh->config->classname, mesh->NumFrames);
      //Print out textures
      for (texture_loop = 0; texture_loop <= mesh->NumTextures; texture_loop++)
         fprintf(fp, "#exec TEXTURE IMPORT NAME=%s FILE=MODELS\\%s.PCX GROUP=\"Skins\"\n", mesh->texturename[texture_loop], mesh->texturename[texture_loop]);
      fprintf(fp, "#exec MESHMAP SCALE MESHMAP=%s X=0.5 Y=0.5 Z=1.0\n", mesh->config->classname);
      for (texture_loop = 0; texture_loop <= mesh->NumTextures; texture_loop++)
         fprintf(fp, "#exec MESHMAP SETTEXTURE MESHMAP=%s NUM=%d TEXTURE=%s\n", mesh->config->classname, texture_loop, mesh->texturename[texture_loop]);