POF data structure

From FreeSpace Wiki
Revision as of 05:31, 10 May 2022 by Asteroth (talk | contribs) (let's make some things clear in this spec)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

The following information was originally from the Descent Developer's Network pof specs page with additional clarity edits and fixes.

Introduction

These files are used to hold the ship model data. The data for the ship is basically a BSP tree that doesn't split polygons across the planes (since we're using zbuffering) and the planes are created by halving the largest dimension of each bounding box recursively. Due to the vastness of some of our models, this recursively split structure allows our collision detection and dynamic lighting to do quick out's on the model. For example, to check if a vector interects the model, I check against the bounding box. If it hits, I then check each of the two boxes it is split into. Then I recursively check each subdivided box as long as the vector is still interesecting.

We create the POF file by exporting a 3DS Max file with a special plugin tool to generate a ".P3D" file, which is essentially a 3d studio file only with texture uv's stored for each face vertex rather than for each vertex. We then run the .P3D file through an in-house Win32 program called 'BSPGEN' which creates the POF file. Depending on the model size, BSPGEN takes a few minutes or so.

Each ship is made up of a lot of subobjects where a subobject is a collection of polygons. For instance, each detail level of a hull of a ship is a subobject. A radar dish is a subobject. A piece of debris that the ship explodes into is a subobject. Each subobject can then have children subobjects. To draw the highest detail model of a ship, draw the subobject identified as detail level 0 and draw all it's children.

This document was written by John Slagel from Volition Inc., with revisions and bugfixing from Garry Knudson. FreeSpace 2 additions were made by Dave Baranec (Volition Inc.), Garry Knudson and Francis "Pastel" Avila.

POF block order list for retail FS2 models

Data types

This uses standard Intel data types.

  (int) == 4 bytes, signed
  (uint) == 4 bytes, unsigned
  (short) == 2 bytes, signed
  (ushort) == 2 bytes, unsigned
  (char) == 1 byte, signed
  (ubyte) == 1 byte, unsigned
  (float) == 4 bytes, signed
  (vector) == 3 floats, 12 bytes total
  (string) == an int specifying length of string and char[length] for the string itself
  (note: must be null-terminated and 4 byte aligned, but PCS2-created .pofs do not adhere to this)

Basic file structure

The file format is a binary file using standard Intel data types. The header consists of a signature and the file version number:

char[4] file_id   // must be 'PSPO'
int version       // Major*100+Minor

Notes:

  • PSPO stood for Parallax Software Polygon Object at one time. Conflict, Descent: FreeSpace shipped at POF version 20.14, so version would be equal to 2014 in most POF files (not all). FreeSpace 2 shipped at POF version 21.17, some files are 21.16. POF version 22 was added in June 2021; this version ensures data alignment and SLC2 chunk presence. Versions 21.18 and 22.01 add support for gun submodel offset angles.


  • The rest of the file is a bunch of chunks. Each chunk is:
char[4] chunk_id  // see below for available chunk types
int length        // length of the chunk.

Note: Contrary to BSP_Data structure, in pof data structure "Length" does not considers the first 8 bytes of the chunk (chunk id+length), so in order to go from the begining of the chunk to the next one you need to do lenght+8. Media:Pof_chunk.png

Chunk specs

Here is a breakdown of each of the chunk types:

  • 'OHDR' (FreeSpace 1) and 'HDR2' (FreeSpace 2) - Object header info
#ifdef version2116orhigher
 // FreeSpace 2
 float max_radius           // maximum radius of entire ship
 int obj_flags              // object flags. Bit 1 = Textures contain tiling
 int num_subobjects         // number of subobjects
#else
 // FreeSpace 1
 int num_subobjects         // number of subobjects
 float max_radius           // maximum radius of entire ship
 int obj_flags              // object flags. Bit 1 = Textures contain tiling
#endif

vector min_bounding         // min bounding box point
vector max_bounding         // max bounding box point

int num_detaillevels        // number of detail levels
for each detail_level, i {
 int sobj_detailevels[i]    // subobject number for detail level I, 0 being highest.
}

int num_debris              // number of debris pieces that model explodes into
for each debris piece, i
 int sobj_debris[i]         // subobject number for debris piece i

#ifdef version1903orhigher
 float mass                 // see notes below
 vector mass_center         // center of mass
 float[3][3] moment_inertia // moment of inertia
#endif

#ifdef version2014orhigher
 int num_cross_sections     // number of cross sections (used for exploding ship) (*)
 for each cross_section, i {
  float depth[i]
  float radius[i]
 }
#endif

// these are currently unused by the engine
#ifdef version2007orhigher
 int num_lights             // number of precalculated muzzle flash lights
 for each light {
  vector location
  int light_type            // type of light
 }
#endif
    • Notes:

for version<2009, mass is a volume mass for version>=2009, mass is an area mass conversion: area_mass=4.65*(vol_mass^2/3); also scale moment_inertia by vol_mass/area_mass (*) if there is no cross_section data, num_cross_sections is -1 instead of 0, as one would expect.


  • 'TXTR' - A list of textures used on this ship. The order they appear here is the number that a face uses to reference a particular texture.
int num_textures
for each texture,i
 int tex_filename_length[i] 
 string tex_filename[i]    // texture filename
  • 'PINF' - Miscellaneous info about the POF file, command line, etc.

Contains a block of NULL-terminated strings. Just read chunk size bytes and stuff it into a string.

  • 'PATH' - Paths for docking and AI ships to follow
int num_paths
for each path, p {
 int name_length
 string name[p]             // name
 int parent_length
 string parent[p]           // parent's name
 int num_verts[p]
 for each vert, v {
   vector pos[p,v]
   float radius[p,v]
   int num_turrets[p,v]     // the following data is unused by the engine
   for each turret, t {
    int sobj_number[p,v,t]  // subobject number
   }
 }
}
  • 'SPCL' - Data for special points
int num_special_points
for each special_point, p {
 int name_length[p]
 string name[p]
 int properties_length[p]
 string properties[p]
 vector point[p]
 float radius[p]
}
  • 'SHLD' - Data for the shield mesh
int num_vertices
for each vertex, v {
 vector position[v]
}

int num faces
for each face, f {
 vector face_normal[f]
 int[3] face_vertices    // indexed into vertex list
 int[3] neighbors        // indexed into face list
}


  • 'EYE ' - Data for eye points (Where pilot looks in 1st person views). Note the space after EYE.
int num_eye_positions
for each eye_position, e {
 int sobj_number[e]      // subobject number this eye is attached to
 vector sobj_offset[e]   // offset from subobject
 vector normal[e]
}
  • 'GPNT' and 'MPNT' - Gun and Missile firing points
int num_banks
for each bank, s {
 int num_guns[s]
 for each gun, g {
  vector point[g,s]
  vector norm[g,s]
  #ifdef version2201orhigher
   float offset[g,s]
  #endif
 }
}
    • Notes:

A "bank" is what you see in the loadout screen. Primaries have a max of 2 and secondaries of 3 for player-flyable ships. "Guns" are the actual number of gunpoints and hence projectiles you'll get when you press the trigger. There is likely no practical max. "Offset" refers to external_model_angle_offset which allows weapon models mounted on this gunpoint to be rotated from the ship's orientation. This was added in 21.18 and 22.01.


  • 'TGUN' and 'TMIS' - Turret Gun and Turret Missile firing points.
int num_turrets

for each turret, t {
 int sobj_base[t]       // the base subobject of this turret (*)
 int sobj_gun[t]        // the gun subobject, or barrels of this turret(*)

 vector turret_normal[t]
 int num_firing_points[t]
 for each firing_point {
  vector position[t,f]
 }
}
    • Notes:

For multipart turrets, sobj_gun is the "barrel" of a turret, and the firing points will be in this sobj's axial frame. sobj_base is the "base" of a turret, which the barrel will rotate with. For single-part turrets, sobj_base == sobj_gun.


  • 'DOCK' - Data for docking points
int num_docks

for each dock, d {
 int properties_length
 string properties[d]     // see notes below
 int num_spline_paths[d]  // although multiple paths can be associated to the docking point, only the first will be used by the engine
 for each spline_path, p {
  int path_number[d,p]
 }
 
 int num_points
 for each point, d {
  vector position
  vector normal
 }
}
    • Note: Properties… if $name= found, then this is name. If name is cargo then this is a cargo bay.


  • 'FUEL' - Data for engine thruster glows
int num_thruster_banks
for each thruster_bank,t {
 int num_glows[t]
 
 #ifdef version2117orhigher // FreeSpace 2 change
  int properties_length
  string properties
 #endif
 
 for each glow {
  vector pos[t,g]
  vector norm[t,g]   // used to tell if behind glow
  float radius[t,g]
 }
}
  • 'SOBJ' (FreeSpace 1) and 'OBJ2' (FreeSpace 2) - Data for a subobject. Contains some info and a bunch of vertices and polygons in the form of a BSP tree or Octree depending on how you look at it.
int submodel_number  // What submodel number this is.

#ifdef version2116orhigher
 // FreeSpace 2
 float radius        // radius of this subobject
 int submodel_parent // What submodel is this model's parent. Equal to -1 if none.
 vector offset       // Offset to from parent object <- Added 09/10/98
#else
 // FreeSpace 1
 int submodel_parent // What submodel is this model's parent. Equal to -1 if none.
 vector offset       // Offset to from parent object <- Added 09/10/98
 float radius        // radius of this subobject
#endif

vector geometric_center
vector bounding_box_min_point
vector bounding_box_max_point

int submodel_name_length
string submodel_name
int properites_length
string properites
int movement_type
int movement_axis

int reserved         // must be 0
int bsp_data_size    // number of bytes now following
char[bsp_data_size] bsp_data  // contains actual polygons, etc.
    • Note: bsp_data is explained here


  • 'INSG' - Squad logo/Insignia data chunk (FreeSpace 2 only)
int num_insignias
for each insignia, i {
 int detail_level // ship detail level
 int num_faces
 int num_vertices
 for each vertice, j {
  vector vertex_position
 }

 vector offset // offset of the insignia in model coords
 for each face, j {
  for 0 to 2, k {
   int vertex_index // vertex index for this face
   float u_texture_coordinate
   float v_texture_coordinate
  }
 }
}


  • 'ACEN' - Auto-Centering info (FreeSpace 2 only)
vector point // autocentering point for the entire model
    • The autocentering point was basically just a little convenient extra data we stuck in models where the pivot point of the model wasn't at the center. Since we rotate ships in the tech room by rotating around the pivot point, it looked dumb when the colossus' rear end was in the middle of the screen and it was spinning on it. So the autocenter point was just a point pretty much near the model we could use to push an extra matrix onto our transform stack and have it show up centered around it. The point itself is in model coordinates.
  • 'GLOW' - Glowpoint info (FS2_Open only)
int num_glowbanks
for each glowbank, g {
 int disp_time // displacement time for blinking (e.g. for Orion runway lights)
 int on_time
 int off_time
 int obj_parent // parent subobject number
 int LOD // Should be 0
 int type
 int num_glowpoints // Number of glowpoints in this bank
 int properties_length
 string properties // Texture name, no file path or extension, like: "$glow_texture=..."
 for each glowpoint, p {
  vector point // location vector
  vector norm // (0,0,0) is omnidirectional
  float radius
 }
}
  • 'SLDC' - Shield mesh collision tree info (FS2_Open only)
uint tree_size
for each node, n {
 ubyte type // 0 = SPLIT, 1 = LEAF/polylist
 uint size
 if !type { // SPLIT
  vector bound_min
  vector bound_max
  uint front_offset
  uint back_offset
 }
 else { // LEAF
  vector bound_min
  vector bound_max
  uint num_polygons
  for each polygon, p {
   uint polygon_addr // indexed into shield mesh face list?
  }
 }
}
    • The shield mesh collision tree speeds up collision calculation by adding bounding boxes around each polygon in the shield mesh. It works basically like a BSP tree.
    • The first byte of each node is a size 1 char, this makes all the following data to be in unaligned memory access positions, thus making the SLDC system, as it is, incompatible with ARM arch.
  • 'SLC2' - Revised version of Shield mesh collision tree info (FS2_Open and POF version 2118 only)
uint tree_size
for each node, n {
 int type // 0 = SPLIT, 1 = LEAF/polylist
 uint size
 if !type { // SPLIT
  vector bound_min
  vector bound_max
  uint front_offset
  uint back_offset
 }
 else { // LEAF
  vector bound_min
  vector bound_max
  uint num_polygons
  for each polygon, p {
   uint polygon_addr // indexed into shield mesh face list?
  }
 }
}
    • The shield mesh collision tree speeds up collision calculation by adding bounding boxes around each polygon in the shield mesh. It works basically like a BSP tree.
    • Revised version of SLDC chunk, now with a int for type to ensure data alignment.