POF data structure

From FreeSpace Wiki
Revision as of 21:41, 31 March 2011 by Kopachris (talk | contribs) (Chunk specs)
Jump to: navigation, search

The following information is copied from the Descent Developer's Network pof specs page.


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: it is NOT null-terminated)

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


  • 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.

  • 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.

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
 // 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

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

#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]

#ifdef version2007orhigher
 int num_lights             // number of precalculated muzzle flash lights
 for each light {
  vector location
  int light_type            // type of light
    • 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
 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 {
 string name[p]             // name
 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]
   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 {
 string name[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 in front of 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_slots
for each slot, s {
 int num_guns[s]
 for each gun, g {
   vector point[g,s]
   vector norm[g,s]
    • Notes:

A "slot" 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 barrels and hence projectiles you'll get when you press the trigger. There is likely no practical max.

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

for each bank, b {
 int sobj_parent[b]       // parent subobject (the subobject with which this turret is associated) (*)
 int sobj_par_phys[b]     // physical parent subobject (the subobject to which the turret rotates) (*)

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

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

  • 'DOCK' - Data for docking points
int num_docks

for each dock, d {
 string properties[d]     // see notes below
 int num_spline_paths[d]
 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_thrusters
for each thruster,t {
 int num glows[t]
 #ifdef version2117orhigher // FreeSpace 2 change
  string properties
 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
 // 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

vector geometric_center
vector bounding_box_min_point
vector bounding_box_max_point

string submodel_name
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_glowpoints
for each glowpoint, 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 // Should also be 0
 string properties // Texture name, no file path or extension, like: "$glow_texture=..."
 vector point
 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.