#include "xentax.h"
#include "x_lwo.h"

template<class T>
inline uint32 GetVXBytes(T index)
{
 return ((index < 0xFF00) ? 2 : 4);
}

void WriteVX(std::ofstream& ofile, uint32 index)
{
 if(index < 0xFF00)
    BE_write_uint16(ofile, (uint16)index);
 else {
    reverse_byte_order(&index);
    index |= 0x000000FF;
    reverse_byte_order(&index);
    BE_write_uint32(ofile, index);
   }
}

lwFileSaver::lwFileSaver()
{
 tags.chunksize = 0;
 tags.currindex = 0;
}

lwFileSaver::~lwFileSaver()
{
}

bool lwFileSaver::hasTxuv(const char* name)const
{
 for(std::map<uint16, lwLAYR>::const_iterator liter = layrs.begin(); liter != layrs.end(); liter++) {
     const std::map<uint16, lwPNTS>& pntss = liter->second.pntss;
     for(std::map<uint16, lwPNTS>::const_iterator piter = pntss.begin(); piter != pntss.end(); piter++) {
         const std::map<std::string, lwTXUV>& uvmap = piter->second.txuv;
         if(piter->second.txuv.find(name) != uvmap.end()) return true;
        }
    }
 return false;
}

bool lwFileSaver::insertClip(uint32 clipindex, const char* filename)
{
 if(!filename) return false;
 if(!strlen(filename)) return false;

 // create CLIP
 lwCLIP item;
 item.chunksize = 0;
 item.filename = filename;

 // compute chunksize
 item.chunksize += 0x04; // clip index
 item.chunksize += 0x04; // STIL
 item.chunksize += 0x02; // STIL chunksize
 item.chunksize += align02(item.filename.length() + 1); // string
 item.chunksize += 0x04; // FLAG
 item.chunksize += 0x02; // FLAG chunksize
 item.chunksize += 0x02; // FLAG data
 item.chunksize += 0x02; // FLAG data

 // add clip
 clips.insert(std::map<uint32, lwCLIP>::value_type(clipindex, item));
 return true;
}

bool lwFileSaver::insertLayr(uint16 layer, const char* name)
{
 // validate
 using namespace std;
 if(!(layer < LWO_MAX_LAYERS)) return false;

 // create LAYR
 lwLAYR item;
 item.chunksize = 0;
 item.flags = 0;
 item.pivot_x = 0;
 item.pivot_y = 0;
 item.pivot_z = 0; 
 item.name = (name ? name : "");

 // compute LAYR chunksize
 item.chunksize += 0x02; // number
 item.chunksize += 0x02; // flags
 item.chunksize += 0x0C; // pivot
 item.chunksize += align02(item.name.length() + 1); // name

 // add LAYR
 layrs.insert(map<uint32, lwLAYR>::value_type(layer, item));
 return true;
}

bool lwFileSaver::insertPnts(uint16 layer, uint16 pointset, const boost::shared_array<lwVertex3D>& data, uint32 elem)
{
 // validate
 using namespace std;
 if(!(layer < LWO_MAX_LAYERS)) return error("lwFileSave: Invalid layer number.");
 if(!data.get()) return error("lwFileSaver: Invalid pointer argument.");
 if(!elem) return error("lwFileSaver: Invalid number of point elements.");

 // validate
 map<uint16, lwLAYR>::iterator parent = layrs.find(layer);
 if(layrs.find(layer) == layrs.end()) return error("lwFileSaver: Layer not found.");

 // create PNTS
 lwPNTS item;
 item.chunksize = 0;
 item.data = data;
 item.elem = elem;

 // compute PNTS chunksize
 item.chunksize += elem*0x04; // x
 item.chunksize += elem*0x04; // y
 item.chunksize += elem*0x04; // z

 // pointset must not exist
 map<uint16, lwPNTS>& pmap = parent->second.pntss;
 if(pmap.find(pointset) != pmap.end()) return error("lwFileSaver: Point set already exists.");

 // add PNTS
 typedef map<uint16, lwPNTS>::value_type value_type;
 pmap.insert(value_type(pointset, item));
 return true;
}

bool lwFileSaver::insertTxuv(uint16 layer, uint16 pointset, const char* name, const boost::shared_array<lwRefVertex2D>& data, uint32 elem)
{
 // validate
 using namespace std;
 if(!(layer < LWO_MAX_LAYERS)) return error("lwFileSave: Invalid layer number.");
 if(!data.get()) return error("lwFileSaver: Invalid pointer argument.");
 if(!elem) return error("lwFileSaver: Invalid number of point elements.");
 if(!strlen(name)) return error("lwFileSaver: UV maps must have a valid name.");

 // validate
 map<uint16, lwLAYR>::iterator parent = layrs.find(layer);
 if(layrs.find(layer) == layrs.end()) return error("lwFileSaver: Layer not found.");

 // create TXUV
 lwTXUV item;
 item.chunksize = 0;
 item.data = data;
 item.elem = elem;

 // compute TXUV chunksize
 item.chunksize += 0x04; // TXUV
 item.chunksize += 0x02; // dimension
 item.chunksize += align02(strlen(name) + 1); // name
 for(uint32 i = 0; i < elem; i++) item.chunksize += GetVXBytes(data[i].reference); // VX
 item.chunksize += elem*0x04; // u
 item.chunksize += elem*0x04; // v

 // pointset must exist
 map<uint16, lwPNTS>::iterator pset = parent->second.pntss.find(pointset);
 if(pset == parent->second.pntss.end()) return error("lwFileSaver: A point set must exist to add a UV map.");

 // validate references
 for(uint32 i = 0; i < elem; i++)
     if(!(data[i].reference < pset->second.elem))
        return error("lwFileSaver: UV index out of range.");

 // add TXUV
 typedef map<string, lwTXUV>::value_type value_type;
 pset->second.txuv.insert(value_type(name, item));
 return true;
}

bool lwFileSaver::insertWght(uint16 layer, uint16 pointset, const char* name, const boost::shared_array<lwRefVertex1D>& data, uint32 elem)
{
 // validate
 using namespace std;
 if(!(layer < LWO_MAX_LAYERS)) return error("lwFileSave: Invalid layer number.");
 if(!data.get()) return error("lwFileSaver: Invalid pointer argument.");
 if(!elem) return error("lwFileSaver: Invalid number of point elements.");
 if(!strlen(name)) return error("lwFileSaver: Weight maps must have a valid name.");

 // validate
 map<uint16, lwLAYR>::iterator parent = layrs.find(layer);
 if(layrs.find(layer) == layrs.end()) return error("lwFileSaver: Layer not found.");

 // create WGHT
 lwWGHT item;
 item.chunksize = 0;
 item.data = data;
 item.elem = elem;

 // compute WGHT chunksize
 item.chunksize += 0x04; // WGHT
 item.chunksize += 0x02; // dimension
 item.chunksize += align02(strlen(name) + 1); // name
 for(uint32 i = 0; i < elem; i++) item.chunksize += GetVXBytes(data[i].reference); // VX
 item.chunksize += elem*0x04; // weight

 // pointset must exist
 map<uint16, lwPNTS>::iterator pset = parent->second.pntss.find(pointset);
 if(pset == parent->second.pntss.end()) return error("lwFileSaver: A point set must exist to add a weight map.");

 // validate references
 for(uint32 i = 0; i < elem; i++)
     if(!(data[i].reference < pset->second.elem))
        return error("lwFileSaver: Weight index out of range.");

 // add WGHT
 typedef map<string, lwWGHT>::value_type value_type;
 pset->second.wght.insert(value_type(name, item));
 return true;
}

bool lwFileSaver::insertMorf(uint16 layer, uint16 pointset, const char* name, const boost::shared_array<lwRefVertex3D>& data, uint32 elem)
{
 // validate
 using namespace std;
 if(!(layer < LWO_MAX_LAYERS)) return error("lwFileSave: Invalid layer number.");
 if(!data.get()) return error("lwFileSaver: Invalid pointer argument.");
 if(!elem) return error("lwFileSaver: Invalid number of point elements.");
 if(!strlen(name)) return error("lwFileSaver: Moph maps must have a valid name.");

 // validate
 map<uint16, lwLAYR>::iterator parent = layrs.find(layer);
 if(layrs.find(layer) == layrs.end()) return error("lwFileSaver: Layer not found.");

 // create MORF
 lwMORF item;
 item.chunksize = 0;
 item.data = data;
 item.elem = elem;

 // compute MORF chunksize
 item.chunksize += 0x04; // MORF
 item.chunksize += 0x02; // dimension
 item.chunksize += align02(strlen(name) + 1); // name
 for(uint32 i = 0; i < elem; i++) item.chunksize += GetVXBytes(data[i].reference); // VX
 item.chunksize += elem*0x04; // x
 item.chunksize += elem*0x04; // y
 item.chunksize += elem*0x04; // z

 // pointset must exist
 map<uint16, lwPNTS>::iterator pset = parent->second.pntss.find(pointset);
 if(pset == parent->second.pntss.end()) return error("lwFileSaver: A point set must exist to add a morph map.");

 // validate references
 for(uint32 i = 0; i < elem; i++)
     if(!(data[i].reference < pset->second.elem))
        return error("lwFileSaver: Morph index out of range.");

 // add TXUV
 typedef map<string, lwMORF>::value_type value_type;
 pset->second.morf.insert(value_type(name, item));
 return true;
}

bool lwFileSaver::insertPols(uint16 layer, uint16 pointset, const char* name, const boost::shared_array<lwTriangle>& data, uint32 elem)
{
 // validate
 using namespace std;
 if(!(layer < LWO_MAX_LAYERS)) return error("lwFileSave: Invalid layer number.");
 if(!data.get()) return error("lwFileSaver: Invalid pointer argument.");
 if(!elem) return error("lwFileSaver: Invalid number of triangle elements.");

 // layer must exist
 map<uint16, lwLAYR>::iterator parent = layrs.find(layer);
 if(layrs.find(layer) == layrs.end()) return error("lwFileSaver: Layer not found.");

 // pointset must exist
 map<uint16, lwPNTS>::iterator pset = parent->second.pntss.find(pointset);
 if(pset == parent->second.pntss.end()) return error("lwFileSaver: A point set must exist to add polygons.");

 // create POLS
 lwPOLS item;
 item.chunksize = 0;
 item.name = (((name == nullptr) || !strlen(name)) ? "Default" : name);
 item.data = data;
 item.elem = elem;

 // compute POLS chunksize
 item.chunksize += 0x04; // FACE
 for(uint32 i = 0; i < elem; i++) {
     item.chunksize += 0x02; // number of vertices
     item.chunksize += GetVXBytes(data[i].a); // VX
     item.chunksize += GetVXBytes(data[i].b); // VX
     item.chunksize += GetVXBytes(data[i].c); // VX
    }

 // validate references
 for(uint32 i = 0; i < elem; i++) {
     if(!(data[i].a < pset->second.elem)) return error("lwFileSaver: Face index out of range.");
     if(!(data[i].b < pset->second.elem)) return error("lwFileSaver: Face index out of range.");
     if(!(data[i].c < pset->second.elem)) return error("lwFileSaver: Face index out of range.");
    }

 // create surface
 if(!insertSurf(item.name.c_str()))
    return false;

 // find surface TAGS index
 typedef boost::bimap<uint16, std::string> bmap_t;
 bmap_t::right_iterator tagiter = tags.surftags.right.find(item.name);
 if(tagiter == tags.surftags.right.end()) return error("lwFileSaver: Could not find surface tag index.");

 // create PTAG
 item.surf.chunksize = 0;    // PTAG chunksize
 item.surf.type = LWO_SURF;  // PTAG type

 // compute PTAG chunksize
 item.surf.chunksize += 0x04; // SURF
 if(item.elem < 0xFF00) item.surf.chunksize += 0x02 * item.elem; // VX data
 else item.surf.chunksize += (0x02 * 0xFF00) + (0x04 * (item.elem - 0xFF00)); // VX data
 item.surf.chunksize += 0x02 * item.elem; // tag index data

 // compute PTAG data
 for(uint32 i = 0; i < item.elem; i++) {
     typedef map<uint32, uint16>::value_type value_type;
     item.surf.tags.insert(value_type(i, tagiter->second));
    }

 // set POLS
 pset->second.face.push_back(item);
 return true;
}

bool lwFileSaver::insertJnts(uint16 layer, uint16 jointset, const boost::shared_array<lwVertex3D>& data, uint32 elem)
{
 // validate
 using namespace std;
 if(!(layer < LWO_MAX_LAYERS)) return error("Invalid layer number.");
 if(!data.get()) return error("Invalid pointer argument.");
 if(!elem) return error("Invalid number of joints.");

 // find layer
 map<uint16, lwLAYR>::iterator parent = layrs.find(layer);
 if(layrs.find(layer) == layrs.end()) return error("Layer not found.");

 // create JNTS
 lwJNTS item;
 item.chunksize = 0;
 item.data = data;
 item.elem = elem;

 // compute JNTS chunksize
 item.chunksize += elem*0x04; // x
 item.chunksize += elem*0x04; // y
 item.chunksize += elem*0x04; // z

 // jointset must not exist
 map<uint16, lwJNTS>& jmap = parent->second.jntss;
 if(jmap.find(jointset) != jmap.end()) return error("Joint set already exists.");

 // add JNTS
 typedef map<uint16, lwJNTS>::value_type value_type;
 jmap.insert(value_type(jointset, item));
 return true;
}

bool lwFileSaver::insertSkel(uint16 layer, uint16 jointset, const char* name, const boost::shared_array<lwSkelegon>& data, uint32 elem)
{
 // validate
 using namespace std;
 if(!(layer < LWO_MAX_LAYERS)) return error("Invalid layer number.");
 if(!name || !strlen(name)) return error("Invalid skeleton name.");
 if(!data.get()) return error("Invalid pointer argument.");
 if(!elem) return error("Invalid number of skelegon elements.");

 // layer must exist
 map<uint16, lwLAYR>::iterator parent = layrs.find(layer);
 if(layrs.find(layer) == layrs.end()) return error("Layer not found.");

 // jointset must exist
 map<uint16, lwJNTS>::iterator jset = parent->second.jntss.find(jointset);
 if(jset == parent->second.jntss.end()) return error("Jointset does not exist.");

 // create SKEL
 lwSKEL item;
 item.chunksize = 0;
 item.name = name;
 item.data = data;
 item.elem = elem;
 item.stag.chunksize = 0;
 item.btag.chunksize = 0;
 item.ptag.chunksize = 0;
 item.utag.chunksize = 0;

 // compute SKEL chunksize
 item.chunksize += 0x04; // BONE
 for(uint32 i = 0; i < elem; i++) {
     item.chunksize += 0x02; // number of vertices
     item.chunksize += GetVXBytes(data[i].a); // VX
     item.chunksize += GetVXBytes(data[i].b); // VX
    }

 // validate joint references
 for(uint32 i = 0; i < elem; i++) {
     if(!(data[i].a < jset->second.elem)) return error("Joint index out of range.");
     if(!(data[i].b < jset->second.elem)) return error("Joint index out of range.");
    }

 // create surface
 if(!insertSurf(name))
    return false;

 // find surface TAGS index
 typedef boost::bimap<uint16, std::string> bmap_t;
 bmap_t::right_iterator tagiter = tags.surftags.right.find(item.name);
 if(tagiter == tags.surftags.right.end()) return error("Could not find surface tag index.");

 // create PTAG:SURF
 item.stag.chunksize = 0;   // PTAG chunksize
 item.stag.type = LWO_SURF; // PTAG type

 // compute PTAG:SURF chunksize
 item.stag.chunksize += 0x04; // SURF
 if(item.elem < 0xFF00) item.stag.chunksize += 0x02 * item.elem; // VX data
 else item.stag.chunksize += (0x02 * 0xFF00) + (0x04 * (item.elem - 0xFF00)); // VX data
 item.stag.chunksize += 0x02 * item.elem; // tag index data

 // compute PTAG:SURF data
 for(uint32 i = 0; i < item.elem; i++) {
     typedef map<uint32, uint16>::value_type value_type;
     item.stag.tags.insert(value_type(i, tagiter->second));
    }

 // create bones and parts
 for(uint32 i = 0; i < item.elem; i++) {
     if(!insertBone(data[i].name.c_str())) return false;
     if(!insertPart(data[i].name.c_str())) return false;
    }

 // create PTAG:BONE
 item.btag.chunksize = 0;
 item.btag.type = LWO_BONE;

 // compute PTAG:BONE chunksize
 item.btag.chunksize += 0x04; // PART
 if(item.elem < 0xFF00) item.btag.chunksize += 0x02 * item.elem; // VX data
 else item.btag.chunksize += (0x02 * 0xFF00) + (0x04 * (item.elem - 0xFF00)); // VX data
 item.btag.chunksize += 0x02 * item.elem; // tag index data

 // compute PTAG:BONE data
 for(uint32 i = 0; i < item.elem; i++) {
     bmap_t::right_iterator iter = tags.bonetags.right.find(data[i].name);
     if(iter == tags.bonetags.right.end()) return error("Could not find bone tag index.");
     typedef map<uint32, uint16>::value_type value_type;
     item.btag.tags.insert(value_type(i, iter->second));
    }

 // create PTAG:PART
 item.ptag.chunksize = 0;
 item.ptag.type = LWO_PART;

 // compute PTAG:PART chunksize
 item.ptag.chunksize += 0x04; // PART
 if(item.elem < 0xFF00) item.ptag.chunksize += 0x02 * item.elem; // VX data
 else item.ptag.chunksize += (0x02 * 0xFF00) + (0x04 * (item.elem - 0xFF00)); // VX data
 item.ptag.chunksize += 0x02 * item.elem; // tag index data

 // compute PTAG:PART data
 for(uint32 i = 0; i < item.elem; i++) {
     bmap_t::right_iterator iter = tags.parttags.right.find(data[i].name);
     if(iter == tags.parttags.right.end()) return error("Could not find part tag index.");
     typedef map<uint32, uint16>::value_type value_type;
     item.ptag.tags.insert(value_type(i, iter->second));
    }

 // set SKEL
 jset->second.skel.push_back(item);
 return true;
}

bool lwFileSaver::insertBone(const char* name)
{
 // validate
 using namespace std;
 if(!name) return error("Invalid part name.");
 if(!strlen(name)) return error("Invalid part name.");

 // BONE already exists
 if(tags.bonetags.right.find(name) != tags.bonetags.right.end())
    return error("Bone names must be unique.");

 // add BONE to TAGS
 typedef boost::bimap<uint16, string>::value_type value_type;
 tags.bonetags.insert(value_type(tags.currindex, name));
 tags.chunksize += align02(strlen(name) + 1);
 tags.currindex++;

 return true;
}

bool lwFileSaver::insertPart(const char* name)
{
 // validate
 using namespace std;
 if(!name) return error("Invalid part name.");
 if(!strlen(name)) return error("Invalid part name.");

 // PART already exists (OK, not an error)
 if(tags.parttags.right.find(name) != tags.parttags.right.end())
    return true;

 // add PART to TAGS
 typedef boost::bimap<uint16, string>::value_type value_type;
 tags.parttags.insert(value_type(tags.currindex, name));
 tags.chunksize += align02(strlen(name) + 1);
 tags.currindex++;

 return true;
}

bool lwFileSaver::insertSurf(const char* name)
{
 // validate
 using namespace std;
 if(!name) return error("lwFileSaver: Invalid surface name.");
 if(!strlen(name)) return error("lwFileSaver: Invalid surface name.");

 // surface already exists (OK, not an error)
 if(surfs.find(name) != surfs.end())
    return true;

 // create SURF
 lwSURF item;
 item.chunksize = 0;
 item.flags = LWO_SURF_FLAGS_COLR | LWO_SURF_FLAGS_DIFF | LWO_SURF_FLAGS_SPEC | LWO_SURF_FLAGS_TRAN;
 item.colr[0] = 0.7843137f;
 item.colr[1] = 0.7843137f;
 item.colr[2] = 0.7843137f;
 item.diff = 1.0f;
 item.spec = 0.0f;
 item.tran = 0.0f;
 item.colr_map.chunksize = 0;
 item.diff_map.chunksize = 0;
 item.spec_map.chunksize = 0;
 item.tran_map.chunksize = 0;

 // compute SURF chunksize
 item.chunksize += align02(strlen(name) + 1); // name
 item.chunksize += 0x02; // source
 item.chunksize += 0x04; // COLR
 item.chunksize += 0x02; // COLOR sub-chunksize
 item.chunksize += 0x04; // color
 item.chunksize += 0x04; // color
 item.chunksize += 0x04; // color
 item.chunksize += 0x02; // envelope
 item.chunksize += 0x04; // DIFF
 item.chunksize += 0x02; // DIFF sub-chunksize
 item.chunksize += 0x04; // intensity
 item.chunksize += 0x02; // envelope
 item.chunksize += 0x04; // SPEC
 item.chunksize += 0x02; // SPEC sub-chunksize
 item.chunksize += 0x04; // intensity
 item.chunksize += 0x02; // envelope
 item.chunksize += 0x04; // TRAN
 item.chunksize += 0x02; // TRAN sub-chunksize
 item.chunksize += 0x04; // intensity
 item.chunksize += 0x02; // envelope

 // add SURF to TAGS
 typedef boost::bimap<uint16, string>::value_type tags_item;
 tags.surftags.insert(tags_item(tags.currindex, name));
 tags.chunksize += align02(strlen(name) + 1);
 tags.currindex++;

 // add SURF
 typedef map<string, lwSURF>::value_type value_type;
 surfs.insert(value_type(name, item));

 return true;
}

bool lwFileSaver::setSurfColr(const char* name, real32 r, real32 g, real32 b)
{
 // validate
 using namespace std;
 if(!name) return error("setSurfColor: Invalid surface name.");
 if(!strlen(name)) return error("setSurfColor: Invalid surface name.");

 // find surface
 map<string, lwSURF>::iterator iter = surfs.find(name);
 if(iter == surfs.end()) return error("setSurfColor: Surface name does not exist.");

 // set color
 iter->second.flags |= LWO_SURF_FLAGS_COLR;
 iter->second.colr[0] = r;
 iter->second.colr[1] = g;
 iter->second.colr[2] = b;

 return true;
}

bool lwFileSaver::setSurfDiff(const char* name, real32 intensity)
{
 // validate
 using namespace std;
 if(!name) return error("Invalid surface name.");
 if(!strlen(name)) return error("Invalid surface name.");

 // find surface
 map<string, lwSURF>::iterator iter = surfs.find(name);
 if(iter == surfs.end()) return error("Surface name does not exist.");

 // set color
 iter->second.flags |= LWO_SURF_FLAGS_DIFF;
 iter->second.diff = intensity;

 return true;
}

bool lwFileSaver::setSurfSpec(const char* name, real32 intensity)
{
 // validate
 using namespace std;
 if(!name) return error("Invalid surface name.");
 if(!strlen(name)) return error("Invalid surface name.");

 // find surface
 map<string, lwSURF>::iterator iter = surfs.find(name);
 if(iter == surfs.end()) return error("Surface name does not exist.");

 // set color
 iter->second.flags |= LWO_SURF_FLAGS_SPEC;
 iter->second.spec = intensity;

 return true;
}

bool lwFileSaver::setSurfTran(const char* name, real32 intensity)
{
 // validate
 using namespace std;
 if(!name) return error("Invalid surface name.");
 if(!strlen(name)) return error("Invalid surface name.");

 // find surface
 map<string, lwSURF>::iterator iter = surfs.find(name);
 if(iter == surfs.end()) return error("Surface name does not exist.");

 // set color
 iter->second.flags |= LWO_SURF_FLAGS_TRAN;
 iter->second.tran = intensity;

 return true;
}

bool lwFileSaver::enableSurfColrImag(const char* name)
{
 // validate
 using namespace std;
 if(!name) return error("Invalid surface name.");
 if(!strlen(name)) return error("Invalid surface name.");

 // find surface
 map<string, lwSURF>::iterator iter = surfs.find(name);
 if(iter == surfs.end()) return error("Surface name does not exist.");

 // texture channel already enabled
 if(iter->second.flags & LWO_SURF_FLAGS_COLR_BLOK) return true;

 // set default PROJ
 iter->second.colr_map.proj.chunksize = 0x02;
 iter->second.colr_map.proj.mode = 0x05;

 // set default AXIS
 iter->second.colr_map.axis.chunksize = 0x02;
 iter->second.colr_map.axis.axis = 0x02;

 // set default IMAG
 iter->second.colr_map.imag.chunksize = 0x00;
 iter->second.colr_map.imag.clipindex = 0;

 // set default WRAP
 iter->second.colr_map.wrap.chunksize = 0x04;
 iter->second.colr_map.wrap.wrap_w = 1; // REPEAT
 iter->second.colr_map.wrap.wrap_h = 1; // REPEAT

 // set default VMAP
 iter->second.colr_map.vmap.chunksize = 0x00;
 iter->second.colr_map.vmap.name = "";

 // set default AAST
 iter->second.colr_map.aast.chunksize = 0x06;
 iter->second.colr_map.aast.flags = 0x01;
 iter->second.colr_map.aast.strength = 1.0f;

 // set default PIXB
 iter->second.colr_map.pixb.chunksize = 0x02;
 iter->second.colr_map.pixb.flags = 0x01;

 // set default IMAP
 iter->second.colr_map.imap.chunksize = 0x0;
 iter->second.colr_map.imap.chunksize += 0x02; // ordinal
 iter->second.colr_map.imap.chunksize += 0x0A; // CHAN chunk
 iter->second.colr_map.imap.chunksize += 0x08; // ENAB chunk
 iter->second.colr_map.imap.chunksize += 0x08; // NEGA chunk
 iter->second.colr_map.imap.chunksize += 0x0E; // OPAC chunk
 iter->second.colr_map.imap.chunksize += 0x08; // AXIS chunk

 // set default IMAP:CHAN
 iter->second.colr_map.imap.chan.chunksize = 0x04;
 iter->second.colr_map.imap.chan.channel = LWO_COLR;

 // set default IMAP:ENAB
 iter->second.colr_map.imap.enab.chunksize = 0x02;
 iter->second.colr_map.imap.enab.enable = 0x01;

 // set default IMAP:NEGA
 iter->second.colr_map.imap.nega.chunksize = 0x02;
 iter->second.colr_map.imap.nega.enable = 0x00;

 // set default IMAP:OPAC
 iter->second.colr_map.imap.opac.chunksize = 0x08;
 iter->second.colr_map.imap.opac.type = 0x07;
 iter->second.colr_map.imap.opac.opacity = 1.0f;

 // set default IMAP:AXIS
 iter->second.colr_map.imap.axis.chunksize = 0x02;
 iter->second.colr_map.imap.axis.axis = 0x01; 

 // set defalut TMAP
 iter->second.colr_map.tmap.chunksize = 0x0;
 iter->second.colr_map.tmap.chunksize += 0x14; // CNTR chunk
 iter->second.colr_map.tmap.chunksize += 0x14; // SIZE chunk
 iter->second.colr_map.tmap.chunksize += 0x14; // ROTA chunk
 iter->second.colr_map.tmap.chunksize += 0x16; // FALL chunk
 iter->second.colr_map.tmap.chunksize += 0x08; // CSYS chunk

 // set default TMAP:CNTR
 iter->second.colr_map.tmap.cntr.chunksize = 0x0E;
 iter->second.colr_map.tmap.cntr.x = 0.0f;
 iter->second.colr_map.tmap.cntr.y = 0.0f;
 iter->second.colr_map.tmap.cntr.z = 0.0f;

 // set default TMAP:SIZE
 iter->second.colr_map.tmap.size.chunksize = 0x0E;
 iter->second.colr_map.tmap.size.x = 0.0f;
 iter->second.colr_map.tmap.size.y = 0.0f;
 iter->second.colr_map.tmap.size.z = 0.0f;

 // set default TMAP:ROTA
 iter->second.colr_map.tmap.rota.chunksize = 0x0E;
 iter->second.colr_map.tmap.rota.x = 0.0f;
 iter->second.colr_map.tmap.rota.y = 0.0f;
 iter->second.colr_map.tmap.rota.z = 0.0f;

 // set default TMAP:FALL
 iter->second.colr_map.tmap.fall.chunksize = 0x10;
 iter->second.colr_map.tmap.fall.type = 0;
 iter->second.colr_map.tmap.fall.x = 0.0f;
 iter->second.colr_map.tmap.fall.y = 0.0f;
 iter->second.colr_map.tmap.fall.z = 0.0f;

 // set default TMAP:CSYS
 iter->second.colr_map.tmap.csys.chunksize = 0x02;
 iter->second.colr_map.tmap.csys.type = 0;

 // compute BLOK chunksize
 iter->second.colr_map.chunksize += 0x06 + iter->second.colr_map.imap.chunksize; // IMAP
 iter->second.colr_map.chunksize += 0x06 + iter->second.colr_map.tmap.chunksize; // TMAP
 iter->second.colr_map.chunksize += 0x06 + iter->second.colr_map.proj.chunksize; // PROJ
 iter->second.colr_map.chunksize += 0x06 + iter->second.colr_map.axis.chunksize; // AXIS
 iter->second.colr_map.chunksize += 0x06 + iter->second.colr_map.wrap.chunksize; // WRAP
 iter->second.colr_map.chunksize += 0x06 + iter->second.colr_map.aast.chunksize; // AAST
 iter->second.colr_map.chunksize += 0x06 + iter->second.colr_map.pixb.chunksize; // PIXB

 // enable texture channel
 iter->second.chunksize += 0x06 + iter->second.colr_map.chunksize; // BLOK
 iter->second.flags |= LWO_SURF_FLAGS_COLR_BLOK;

 return true;
}

bool lwFileSaver::enableSurfDiffImag(const char* name)
{
 // validate
 using namespace std;
 if(!name) return error("Invalid surface name.");
 if(!strlen(name)) return error("Invalid surface name.");

 // find surface
 map<string, lwSURF>::iterator iter = surfs.find(name);
 if(iter == surfs.end()) return error("Surface name does not exist.");

 // texture channel already enabled
 if(iter->second.flags & LWO_SURF_FLAGS_DIFF_BLOK) return true;

 // set default PROJ
 iter->second.diff_map.proj.chunksize = 0x02;
 iter->second.diff_map.proj.mode = 0x05;

 // set default AXIS
 iter->second.diff_map.axis.chunksize = 0x02;
 iter->second.diff_map.axis.axis = 0x02;

 // set default IMAG
 iter->second.diff_map.imag.chunksize = 0x00;
 iter->second.diff_map.imag.clipindex = 0;

 // set default WRAP
 iter->second.diff_map.wrap.chunksize = 0x04;
 iter->second.diff_map.wrap.wrap_w = 1; // REPEAT
 iter->second.diff_map.wrap.wrap_h = 1; // REPEAT

 // set default VMAP
 iter->second.diff_map.vmap.chunksize = 0x00;
 iter->second.diff_map.vmap.name = "";

 // set default AAST
 iter->second.diff_map.aast.chunksize = 0x06;
 iter->second.diff_map.aast.flags = 0x01;
 iter->second.diff_map.aast.strength = 1.0f;

 // set default PIXB
 iter->second.diff_map.pixb.chunksize = 0x02;
 iter->second.diff_map.pixb.flags = 0x01;

 // set default IMAP
 iter->second.diff_map.imap.chunksize = 0x0;
 iter->second.diff_map.imap.chunksize += 0x02; // ordinal
 iter->second.diff_map.imap.chunksize += 0x0A; // CHAN chunk
 iter->second.diff_map.imap.chunksize += 0x08; // ENAB chunk
 iter->second.diff_map.imap.chunksize += 0x08; // NEGA chunk
 iter->second.diff_map.imap.chunksize += 0x0E; // OPAC chunk
 iter->second.diff_map.imap.chunksize += 0x08; // AXIS chunk

 // set default IMAP:CHAN
 iter->second.diff_map.imap.chan.chunksize = 0x04;
 iter->second.diff_map.imap.chan.channel = LWO_DIFF;

 // set default IMAP:ENAB
 iter->second.diff_map.imap.enab.chunksize = 0x02;
 iter->second.diff_map.imap.enab.enable = 0x01;

 // set default IMAP:NEGA
 iter->second.diff_map.imap.nega.chunksize = 0x02;
 iter->second.diff_map.imap.nega.enable = 0x00;

 // set default IMAP:OPAC
 iter->second.diff_map.imap.opac.chunksize = 0x08;
 iter->second.diff_map.imap.opac.type = 0x07;
 iter->second.diff_map.imap.opac.opacity = 1.0f;

 // set default IMAP:AXIS
 iter->second.diff_map.imap.axis.chunksize = 0x02;
 iter->second.diff_map.imap.axis.axis = 0x01; 

 // set defalut TMAP
 iter->second.diff_map.tmap.chunksize = 0x0;
 iter->second.diff_map.tmap.chunksize += 0x14; // CNTR chunk
 iter->second.diff_map.tmap.chunksize += 0x14; // SIZE chunk
 iter->second.diff_map.tmap.chunksize += 0x14; // ROTA chunk
 iter->second.diff_map.tmap.chunksize += 0x16; // FALL chunk
 iter->second.diff_map.tmap.chunksize += 0x08; // CSYS chunk

 // set default TMAP:CNTR
 iter->second.diff_map.tmap.cntr.chunksize = 0x0E;
 iter->second.diff_map.tmap.cntr.x = 0.0f;
 iter->second.diff_map.tmap.cntr.y = 0.0f;
 iter->second.diff_map.tmap.cntr.z = 0.0f;

 // set default TMAP:SIZE
 iter->second.diff_map.tmap.size.chunksize = 0x0E;
 iter->second.diff_map.tmap.size.x = 0.0f;
 iter->second.diff_map.tmap.size.y = 0.0f;
 iter->second.diff_map.tmap.size.z = 0.0f;

 // set default TMAP:ROTA
 iter->second.diff_map.tmap.rota.chunksize = 0x0E;
 iter->second.diff_map.tmap.rota.x = 0.0f;
 iter->second.diff_map.tmap.rota.y = 0.0f;
 iter->second.diff_map.tmap.rota.z = 0.0f;

 // set default TMAP:FALL
 iter->second.diff_map.tmap.fall.chunksize = 0x10;
 iter->second.diff_map.tmap.fall.type = 0;
 iter->second.diff_map.tmap.fall.x = 0.0f;
 iter->second.diff_map.tmap.fall.y = 0.0f;
 iter->second.diff_map.tmap.fall.z = 0.0f;

 // set default TMAP:CSYS
 iter->second.diff_map.tmap.csys.chunksize = 0x02;
 iter->second.diff_map.tmap.csys.type = 0;

 // compute BLOK chunksize
 iter->second.diff_map.chunksize += 0x06 + iter->second.diff_map.imap.chunksize; // IMAP
 iter->second.diff_map.chunksize += 0x06 + iter->second.diff_map.tmap.chunksize; // TMAP
 iter->second.diff_map.chunksize += 0x06 + iter->second.diff_map.proj.chunksize; // PROJ
 iter->second.diff_map.chunksize += 0x06 + iter->second.diff_map.axis.chunksize; // AXIS
 iter->second.diff_map.chunksize += 0x06 + iter->second.diff_map.wrap.chunksize; // WRAP
 iter->second.diff_map.chunksize += 0x06 + iter->second.diff_map.aast.chunksize; // AAST
 iter->second.diff_map.chunksize += 0x06 + iter->second.diff_map.pixb.chunksize; // PIXB

 // enable texture channel
 iter->second.chunksize += 0x06 + iter->second.diff_map.chunksize; // BLOK
 iter->second.flags |= LWO_SURF_FLAGS_DIFF_BLOK;

 return true;
}

bool lwFileSaver::enableSurfSpecImag(const char* name)
{
 // validate
 using namespace std;
 if(!name) return error("Invalid surface name.");
 if(!strlen(name)) return error("Invalid surface name.");

 // find surface
 map<string, lwSURF>::iterator iter = surfs.find(name);
 if(iter == surfs.end()) return error("Surface name does not exist.");

 // texture channel already enabled
 if(iter->second.flags & LWO_SURF_FLAGS_SPEC_BLOK) return true;

 // set default PROJ
 iter->second.spec_map.proj.chunksize = 0x02;
 iter->second.spec_map.proj.mode = 0x05;

 // set default AXIS
 iter->second.spec_map.axis.chunksize = 0x02;
 iter->second.spec_map.axis.axis = 0x02;

 // set default IMAG
 iter->second.spec_map.imag.chunksize = 0x00;
 iter->second.spec_map.imag.clipindex = 0;

 // set default WRAP
 iter->second.spec_map.wrap.chunksize = 0x04;
 iter->second.spec_map.wrap.wrap_w = 1; // REPEAT
 iter->second.spec_map.wrap.wrap_h = 1; // REPEAT

 // set default VMAP
 iter->second.spec_map.vmap.chunksize = 0x00;
 iter->second.spec_map.vmap.name = "";

 // set default AAST
 iter->second.spec_map.aast.chunksize = 0x06;
 iter->second.spec_map.aast.flags = 0x01;
 iter->second.spec_map.aast.strength = 1.0f;

 // set default PIXB
 iter->second.spec_map.pixb.chunksize = 0x02;
 iter->second.spec_map.pixb.flags = 0x01;

 // set default IMAP
 iter->second.spec_map.imap.chunksize = 0x0;
 iter->second.spec_map.imap.chunksize += 0x02; // ordinal
 iter->second.spec_map.imap.chunksize += 0x0A; // CHAN chunk
 iter->second.spec_map.imap.chunksize += 0x08; // ENAB chunk
 iter->second.spec_map.imap.chunksize += 0x08; // NEGA chunk
 iter->second.spec_map.imap.chunksize += 0x0E; // OPAC chunk
 iter->second.spec_map.imap.chunksize += 0x08; // AXIS chunk

 // set default IMAP:CHAN
 iter->second.spec_map.imap.chan.chunksize = 0x04;
 iter->second.spec_map.imap.chan.channel = LWO_SPEC;

 // set default IMAP:ENAB
 iter->second.spec_map.imap.enab.chunksize = 0x02;
 iter->second.spec_map.imap.enab.enable = 0x01;

 // set default IMAP:NEGA
 iter->second.spec_map.imap.nega.chunksize = 0x02;
 iter->second.spec_map.imap.nega.enable = 0x00;

 // set default IMAP:OPAC
 iter->second.spec_map.imap.opac.chunksize = 0x08;
 iter->second.spec_map.imap.opac.type = 0x07;
 iter->second.spec_map.imap.opac.opacity = 1.0f;

 // set default IMAP:AXIS
 iter->second.spec_map.imap.axis.chunksize = 0x02;
 iter->second.spec_map.imap.axis.axis = 0x01; 

 // set defalut TMAP
 iter->second.spec_map.tmap.chunksize = 0x0;
 iter->second.spec_map.tmap.chunksize += 0x14; // CNTR chunk
 iter->second.spec_map.tmap.chunksize += 0x14; // SIZE chunk
 iter->second.spec_map.tmap.chunksize += 0x14; // ROTA chunk
 iter->second.spec_map.tmap.chunksize += 0x16; // FALL chunk
 iter->second.spec_map.tmap.chunksize += 0x08; // CSYS chunk

 // set default TMAP:CNTR
 iter->second.spec_map.tmap.cntr.chunksize = 0x0E;
 iter->second.spec_map.tmap.cntr.x = 0.0f;
 iter->second.spec_map.tmap.cntr.y = 0.0f;
 iter->second.spec_map.tmap.cntr.z = 0.0f;

 // set default TMAP:SIZE
 iter->second.spec_map.tmap.size.chunksize = 0x0E;
 iter->second.spec_map.tmap.size.x = 0.0f;
 iter->second.spec_map.tmap.size.y = 0.0f;
 iter->second.spec_map.tmap.size.z = 0.0f;

 // set default TMAP:ROTA
 iter->second.spec_map.tmap.rota.chunksize = 0x0E;
 iter->second.spec_map.tmap.rota.x = 0.0f;
 iter->second.spec_map.tmap.rota.y = 0.0f;
 iter->second.spec_map.tmap.rota.z = 0.0f;

 // set default TMAP:FALL
 iter->second.spec_map.tmap.fall.chunksize = 0x10;
 iter->second.spec_map.tmap.fall.type = 0;
 iter->second.spec_map.tmap.fall.x = 0.0f;
 iter->second.spec_map.tmap.fall.y = 0.0f;
 iter->second.spec_map.tmap.fall.z = 0.0f;

 // set default TMAP:CSYS
 iter->second.spec_map.tmap.csys.chunksize = 0x02;
 iter->second.spec_map.tmap.csys.type = 0;

 // compute BLOK chunksize
 iter->second.spec_map.chunksize += 0x06 + iter->second.spec_map.imap.chunksize; // IMAP
 iter->second.spec_map.chunksize += 0x06 + iter->second.spec_map.tmap.chunksize; // TMAP
 iter->second.spec_map.chunksize += 0x06 + iter->second.spec_map.proj.chunksize; // PROJ
 iter->second.spec_map.chunksize += 0x06 + iter->second.spec_map.axis.chunksize; // AXIS
 iter->second.spec_map.chunksize += 0x06 + iter->second.spec_map.wrap.chunksize; // WRAP
 iter->second.spec_map.chunksize += 0x06 + iter->second.spec_map.aast.chunksize; // AAST
 iter->second.spec_map.chunksize += 0x06 + iter->second.spec_map.pixb.chunksize; // PIXB

 // enable texture channel
 iter->second.chunksize += 0x06 + iter->second.spec_map.chunksize; // BLOK
 iter->second.flags |= LWO_SURF_FLAGS_SPEC_BLOK;

 return true;
}

bool lwFileSaver::enableSurfTranImag(const char* name)
{
 // validate
 using namespace std;
 if(!name) return error("Invalid surface name.");
 if(!strlen(name)) return error("Invalid surface name.");

 // find surface
 map<string, lwSURF>::iterator iter = surfs.find(name);
 if(iter == surfs.end()) return error("Surface name does not exist.");

 // texture channel already enabled
 if(iter->second.flags & LWO_SURF_FLAGS_TRAN_BLOK) return true;

 // set default PROJ
 iter->second.tran_map.proj.chunksize = 0x02;
 iter->second.tran_map.proj.mode = 0x05;

 // set default AXIS
 iter->second.tran_map.axis.chunksize = 0x02;
 iter->second.tran_map.axis.axis = 0x02;

 // set default IMAG
 iter->second.tran_map.imag.chunksize = 0x00;
 iter->second.tran_map.imag.clipindex = 0;

 // set default WRAP
 iter->second.tran_map.wrap.chunksize = 0x04;
 iter->second.tran_map.wrap.wrap_w = 1; // REPEAT
 iter->second.tran_map.wrap.wrap_h = 1; // REPEAT

 // set default VMAP
 iter->second.tran_map.vmap.chunksize = 0x00;
 iter->second.tran_map.vmap.name = "";

 // set default AAST
 iter->second.tran_map.aast.chunksize = 0x06;
 iter->second.tran_map.aast.flags = 0x01;
 iter->second.tran_map.aast.strength = 1.0f;

 // set default PIXB
 iter->second.tran_map.pixb.chunksize = 0x02;
 iter->second.tran_map.pixb.flags = 0x01;

 // set default IMAP
 iter->second.tran_map.imap.chunksize = 0x0;
 iter->second.tran_map.imap.chunksize += 0x02; // ordinal
 iter->second.tran_map.imap.chunksize += 0x0A; // CHAN chunk
 iter->second.tran_map.imap.chunksize += 0x08; // ENAB chunk
 iter->second.tran_map.imap.chunksize += 0x08; // NEGA chunk
 iter->second.tran_map.imap.chunksize += 0x0E; // OPAC chunk
 iter->second.tran_map.imap.chunksize += 0x08; // AXIS chunk

 // set default IMAP:CHAN
 iter->second.tran_map.imap.chan.chunksize = 0x04;
 iter->second.tran_map.imap.chan.channel = LWO_TRAN;

 // set default IMAP:ENAB
 iter->second.tran_map.imap.enab.chunksize = 0x02;
 iter->second.tran_map.imap.enab.enable = 0x01;

 // set default IMAP:NEGA
 iter->second.tran_map.imap.nega.chunksize = 0x02;
 iter->second.tran_map.imap.nega.enable = 0x00;

 // set default IMAP:OPAC
 iter->second.tran_map.imap.opac.chunksize = 0x08;
 iter->second.tran_map.imap.opac.type = 0x07;
 iter->second.tran_map.imap.opac.opacity = 1.0f;

 // set default IMAP:AXIS
 iter->second.tran_map.imap.axis.chunksize = 0x02;
 iter->second.tran_map.imap.axis.axis = 0x01; 

 // set defalut TMAP
 iter->second.tran_map.tmap.chunksize = 0x0;
 iter->second.tran_map.tmap.chunksize += 0x14; // CNTR chunk
 iter->second.tran_map.tmap.chunksize += 0x14; // SIZE chunk
 iter->second.tran_map.tmap.chunksize += 0x14; // ROTA chunk
 iter->second.tran_map.tmap.chunksize += 0x16; // FALL chunk
 iter->second.tran_map.tmap.chunksize += 0x08; // CSYS chunk

 // set default TMAP:CNTR
 iter->second.tran_map.tmap.cntr.chunksize = 0x0E;
 iter->second.tran_map.tmap.cntr.x = 0.0f;
 iter->second.tran_map.tmap.cntr.y = 0.0f;
 iter->second.tran_map.tmap.cntr.z = 0.0f;

 // set default TMAP:SIZE
 iter->second.tran_map.tmap.size.chunksize = 0x0E;
 iter->second.tran_map.tmap.size.x = 0.0f;
 iter->second.tran_map.tmap.size.y = 0.0f;
 iter->second.tran_map.tmap.size.z = 0.0f;

 // set default TMAP:ROTA
 iter->second.tran_map.tmap.rota.chunksize = 0x0E;
 iter->second.tran_map.tmap.rota.x = 0.0f;
 iter->second.tran_map.tmap.rota.y = 0.0f;
 iter->second.tran_map.tmap.rota.z = 0.0f;

 // set default TMAP:FALL
 iter->second.tran_map.tmap.fall.chunksize = 0x10;
 iter->second.tran_map.tmap.fall.type = 0;
 iter->second.tran_map.tmap.fall.x = 0.0f;
 iter->second.tran_map.tmap.fall.y = 0.0f;
 iter->second.tran_map.tmap.fall.z = 0.0f;

 // set default TMAP:CSYS
 iter->second.tran_map.tmap.csys.chunksize = 0x02;
 iter->second.tran_map.tmap.csys.type = 0;

 // compute BLOK chunksize
 iter->second.tran_map.chunksize += 0x06 + iter->second.tran_map.imap.chunksize; // IMAP
 iter->second.tran_map.chunksize += 0x06 + iter->second.tran_map.tmap.chunksize; // TMAP
 iter->second.tran_map.chunksize += 0x06 + iter->second.tran_map.proj.chunksize; // PROJ
 iter->second.tran_map.chunksize += 0x06 + iter->second.tran_map.axis.chunksize; // AXIS
 iter->second.tran_map.chunksize += 0x06 + iter->second.tran_map.wrap.chunksize; // WRAP
 iter->second.tran_map.chunksize += 0x06 + iter->second.tran_map.aast.chunksize; // AAST
 iter->second.tran_map.chunksize += 0x06 + iter->second.tran_map.pixb.chunksize; // PIXB

 // enable texture channel
 iter->second.chunksize += 0x06 + iter->second.tran_map.chunksize; // BLOK
 iter->second.flags |= LWO_SURF_FLAGS_TRAN_BLOK;

 return true;
}

bool lwFileSaver::setSurfVmap(const char* surface, const char* mapname, uint32 type)
{
 // validate
 using namespace std;
 if(!surface) return error("Invalid surface name.");
 if(!strlen(surface)) return error("Invalid surface name.");

 // find surface
 map<string, lwSURF>::iterator iter = surfs.find(surface);
 if(iter == surfs.end()) return error("Surface name does not exist.");

 // texture channel not enabled
 lwSURF* surf = &(iter->second);
 if(!(surf->flags & LWO_SURF_FLAGS_COLR_BLOK)) return error("Texture channel not enabled.");

 // find UV map name
 if(!hasTxuv(mapname)) return error("UV map name not found.");

 // set block
 lwBLOK* blok = nullptr;
 if(type & LWO_SURF_FLAGS_COLR_BLOK) blok = &(iter->second.colr_map);
 else if(type & LWO_SURF_FLAGS_DIFF_BLOK) blok = &(iter->second.diff_map);
 else if(type & LWO_SURF_FLAGS_SPEC_BLOK) blok = &(iter->second.spec_map);
 else if(type & LWO_SURF_FLAGS_TRAN_BLOK) blok = &(iter->second.tran_map);

 // adjust chunksizes
 uint32 prev_vmap_chunksize = blok->vmap.chunksize;
 if(prev_vmap_chunksize) {
    blok->chunksize -= prev_vmap_chunksize;
    surf->chunksize -= prev_vmap_chunksize;
   }
 else {
    blok->chunksize += 0x06;
    surf->chunksize += 0x06;
   }

 // set VMAP data
 blok->vmap.name = mapname;
 blok->vmap.chunksize += align02(blok->vmap.name.length() + 1);

 // adjust chunksizes
 blok->chunksize += blok->vmap.chunksize;
 surf->chunksize += blok->vmap.chunksize;

 return true;
}

bool lwFileSaver::setSurfColrVmap(const char* surface, const char* mapname)
{
 return setSurfVmap(surface, mapname, LWO_SURF_FLAGS_COLR_BLOK);
}

bool lwFileSaver::setSurfDiffVmap(const char* surface, const char* mapname)
{
 return setSurfVmap(surface, mapname, LWO_SURF_FLAGS_DIFF_BLOK);
}

bool lwFileSaver::setSurfSpecVmap(const char* surface, const char* mapname)
{
 return setSurfVmap(surface, mapname, LWO_SURF_FLAGS_SPEC_BLOK);
}

bool lwFileSaver::setSurfTranVmap(const char* surface, const char* mapname)
{
 return setSurfVmap(surface, mapname, LWO_SURF_FLAGS_TRAN_BLOK);
}

bool lwFileSaver::setSurfImag(const char* surface, uint32 clipindex, uint32 type)
{
 // validate
 using namespace std;
 if(!surface) return error("Invalid surface name.");
 if(!strlen(surface)) return error("Invalid surface name.");
 if(!clipindex) return error("Invalid clip index.");

 // find surface
 map<string, lwSURF>::iterator iter = surfs.find(surface);
 if(iter == surfs.end()) return error("Surface name does not exist.");

 // texture channel not enabled
 lwSURF* surf = &(iter->second);
 if(!(surf->flags & type)) return error("Texture channel not enabled.");

 // find clip index
 map<uint32, lwCLIP>::iterator clipiter = clips.find(clipindex);
 if(clipiter == clips.end()) return error("Clip index not found.");

 // set block
 lwBLOK* blok = nullptr;
 if(type & LWO_SURF_FLAGS_COLR_BLOK) blok = &(iter->second.colr_map);
 else if(type & LWO_SURF_FLAGS_DIFF_BLOK) blok = &(iter->second.diff_map);
 else if(type & LWO_SURF_FLAGS_SPEC_BLOK) blok = &(iter->second.spec_map);
 else if(type & LWO_SURF_FLAGS_TRAN_BLOK) blok = &(iter->second.tran_map);

 // adjust chunksizes
 uint32 prev_imag_chunksize = blok->imag.chunksize;
 if(prev_imag_chunksize) {
    blok->chunksize -= prev_imag_chunksize;
    surf->chunksize -= prev_imag_chunksize;
   }
 else {
    blok->chunksize += 0x06;
    surf->chunksize += 0x06;
   }

 // set IMAG data
 blok->imag.clipindex = clipindex;
 blok->imag.chunksize += GetVXBytes(clipindex);

 // adjust chunksizes
 blok->chunksize += blok->imag.chunksize;
 surf->chunksize += blok->imag.chunksize;

 return true;
}

bool lwFileSaver::setSurfColrImag(const char* surface, uint32 clipindex)
{
 return setSurfImag(surface, clipindex, LWO_SURF_FLAGS_COLR_BLOK);
}

bool lwFileSaver::setSurfDiffImag(const char* surface, uint32 clipindex)
{
 return setSurfImag(surface, clipindex, LWO_SURF_FLAGS_DIFF_BLOK);
}

bool lwFileSaver::setSurfSpecImag(const char* surface, uint32 clipindex)
{
 return setSurfImag(surface, clipindex, LWO_SURF_FLAGS_SPEC_BLOK);
}

bool lwFileSaver::setSurfTranImag(const char* surface, uint32 clipindex)
{
 return setSurfImag(surface, clipindex, LWO_SURF_FLAGS_TRAN_BLOK);
}

bool lwFileSaver::setSurfNega(const char* surface, uint16 state, uint32 type)
{
 // validate
 using namespace std;
 if(!surface) return error("Invalid surface name.");
 if(!strlen(surface)) return error("Invalid surface name.");

 // find surface
 map<string, lwSURF>::iterator iter = surfs.find(surface);
 if(iter == surfs.end()) return error("Surface name does not exist.");

 // texture channel not enabled
 lwSURF* surf = &(iter->second);
 if(!(surf->flags & type)) return error("Texture channel not enabled.");

 // set block
 lwBLOK* blok = nullptr;
 if(type & LWO_SURF_FLAGS_COLR_BLOK) blok = &(iter->second.colr_map);
 else if(type & LWO_SURF_FLAGS_DIFF_BLOK) blok = &(iter->second.diff_map);
 else if(type & LWO_SURF_FLAGS_SPEC_BLOK) blok = &(iter->second.spec_map);
 else if(type & LWO_SURF_FLAGS_TRAN_BLOK) blok = &(iter->second.tran_map);

 // set NEGA data
 blok->imap.nega.enable = state;
 return true;
}

bool lwFileSaver::setSurfColrNega(const char* surface, uint16 state)
{
 return setSurfNega(surface, state, LWO_SURF_FLAGS_COLR_BLOK);
}

bool lwFileSaver::setSurfDiffNega(const char* surface, uint16 state)
{
 return setSurfNega(surface, state, LWO_SURF_FLAGS_DIFF_BLOK);
}

bool lwFileSaver::setSurfSpecNega(const char* surface, uint16 state)
{
 return setSurfNega(surface, state, LWO_SURF_FLAGS_SPEC_BLOK);
}

bool lwFileSaver::setSurfTranNega(const char* surface, uint16 state)
{
 return setSurfNega(surface, state, LWO_SURF_FLAGS_TRAN_BLOK);
}

bool lwFileSaver::save(const char* filename)const
{
 // create file
 using namespace std;
 ofstream ofile(filename, ios::binary);
 if(!ofile) return error("Failed to create lwo file.");

 //
 // COMPUTE FORM CHUNKSIZE
 //

 // keep track of FORM chunksize
 uint32 FORM_chunksize = 0;
 FORM_chunksize += 0x04; // LWO2

 // TAGS contribution
 if(tags.chunksize) {
     FORM_chunksize += 0x08;
     FORM_chunksize += tags.chunksize;
   }

 // CLIP contribution
 for(map<uint32, lwCLIP>::const_iterator i = clips.begin(); i != clips.end(); i++) {
     FORM_chunksize += 0x08;
     FORM_chunksize += i->second.chunksize;
    }

 // LAYR contribution
 for(map<uint16, lwLAYR>::const_iterator i = layrs.begin(); i != layrs.end(); i++)
    {
     FORM_chunksize += 0x08;
     FORM_chunksize += i->second.chunksize;

     // PNTS contribution
     for(map<uint16, lwPNTS>::const_iterator j = i->second.pntss.begin(); j != i->second.pntss.end(); j++)
        {
         FORM_chunksize += 0x08;
         FORM_chunksize += j->second.chunksize;
         // TXUV contribution
         for(map<string, lwTXUV>::const_iterator k = j->second.txuv.begin(); k != j->second.txuv.end(); k++) {
             FORM_chunksize += 0x08;
             FORM_chunksize += k->second.chunksize;
            }
         // WGHT contribution
         for(map<string, lwWGHT>::const_iterator k = j->second.wght.begin(); k != j->second.wght.end(); k++) {
             FORM_chunksize += 0x08;
             FORM_chunksize += k->second.chunksize;
            }
         // MORF contribution
         for(map<string, lwMORF>::const_iterator k = j->second.morf.begin(); k != j->second.morf.end(); k++) {
             FORM_chunksize += 0x08;
             FORM_chunksize += k->second.chunksize;
            }
         // POLS contribution
         for(size_t faceindex = 0; faceindex < j->second.face.size(); faceindex++)
            {
             // POLS[faceindex]:FACE contribution
             if(j->second.face[faceindex].chunksize) {
                FORM_chunksize += 0x08;
                FORM_chunksize += j->second.face[faceindex].chunksize;
               }
             // PTAG[faceindex]:SURF contribution
             if(j->second.face[faceindex].surf.chunksize) {
                FORM_chunksize += 0x08;
                FORM_chunksize += j->second.face[faceindex].surf.chunksize;
               }
            }
        }

     // JNTS contribution
     for(map<uint16, lwJNTS>::const_iterator j = i->second.jntss.begin(); j != i->second.jntss.end(); j++)
        {
         // JNTS contribution
         FORM_chunksize += 0x08;
         FORM_chunksize += j->second.chunksize;

         // SKEL contribution
         for(size_t skelindex = 0; skelindex < j->second.skel.size(); skelindex++)
            {
             // POLS[skelindex]:BONE contribution
             if(j->second.skel[skelindex].chunksize) {
                FORM_chunksize += 0x08;
                FORM_chunksize += j->second.skel[skelindex].chunksize;
               }
             // PTAG[skelindex]:SURF contribution
             if(j->second.skel[skelindex].stag.chunksize) {
                FORM_chunksize += 0x08;
                FORM_chunksize += j->second.skel[skelindex].stag.chunksize;
               }
             // PTAG[skelindex]:BONE contribution
             if(j->second.skel[skelindex].btag.chunksize) {
                FORM_chunksize += 0x08;
                FORM_chunksize += j->second.skel[skelindex].btag.chunksize;
               }
             // PTAG[skelindex]:PART contribution
             if(j->second.skel[skelindex].ptag.chunksize) {
                FORM_chunksize += 0x08;
                FORM_chunksize += j->second.skel[skelindex].ptag.chunksize;
               }
             // PTAG[skelindex]:BNUP contribution
             if(j->second.skel[skelindex].utag.chunksize) {
                FORM_chunksize += 0x08;
                FORM_chunksize += j->second.skel[skelindex].utag.chunksize;
               }
            }
        }
    }

 // SURF contribution
 for(map<string, lwSURF>::const_iterator i = surfs.begin(); i != surfs.end(); i++) {
     FORM_chunksize += 0x08;
     FORM_chunksize += i->second.chunksize;
    }

 //
 // SAVE DATA
 //

 // save FORM + FORM_chunksize + LWO2
 BE_write_uint32(ofile, LWO_FORM);
 BE_write_uint32(ofile, FORM_chunksize);
 BE_write_uint32(ofile, LWO_LWO2);

 // save TAGS data
 uint32 n_tags = tags.currindex;
 if(tags.chunksize && n_tags)
   {
    // construct TAGS data
    typedef boost::bimap<uint16, string>::const_iterator bmiterator;
    vector<string> tagdata(n_tags);
    for(bmiterator i = tags.surftags.begin(); i != tags.surftags.end(); i++) tagdata[i->left] = i->right;
    for(bmiterator i = tags.bonetags.begin(); i != tags.bonetags.end(); i++) tagdata[i->left] = i->right;
    for(bmiterator i = tags.parttags.begin(); i != tags.parttags.end(); i++) tagdata[i->left] = i->right;
    for(bmiterator i = tags.bnuptags.begin(); i != tags.bnuptags.end(); i++) tagdata[i->left] = i->right;
   
    // save TAGS data
    BE_write_uint32(ofile, LWO_TAGS);
    BE_write_uint32(ofile, tags.chunksize);
    for(uint32 i = 0; i < n_tags; i++) write_aligned_string_02(ofile, tagdata[i].c_str());
   }

 // save CLIP data
 for(map<uint32, lwCLIP>::const_iterator i = clips.begin(); i != clips.end(); i++)
    {
     // CLIP
     BE_write_uint32(ofile, LWO_CLIP);
     BE_write_uint32(ofile, i->second.chunksize);
     BE_write_uint32(ofile, i->first);

     // STIL
     BE_write_uint32(ofile, LWO_STIL);
     BE_write_uint16(ofile, (uint16)align02(i->second.filename.length() + 1));
     write_aligned_string_02(ofile, i->second.filename.c_str());

     // FLAGS subchunk
     BE_write_uint32(ofile, LWO_FLAG);
     BE_write_uint16(ofile, 0x0004);
     BE_write_uint16(ofile, 0x0800);
     BE_write_uint16(ofile, 0x0080);
    }

 // save LAYR data
 for(map<uint16, lwLAYR>::const_iterator i = layrs.begin(); i != layrs.end(); i++)
    {
     // save LAYR chunk
     BE_write_uint32(ofile, LWO_LAYR); // LAYR
     BE_write_uint32(ofile, i->second.chunksize); // chunksize
     BE_write_uint16(ofile, i->first); // number
     BE_write_uint16(ofile, i->second.flags); // flags
     BE_write_real32(ofile, i->second.pivot_x); // pivot
     BE_write_real32(ofile, i->second.pivot_y); // pivot
     BE_write_real32(ofile, i->second.pivot_z); // pivot
     if(i->second.name.length()) write_aligned_string_02(ofile, i->second.name.c_str()); // name
     else BE_write_uint16(ofile, 0); // name

     // save PNTS
     const map<uint16, lwPNTS>& pmap = i->second.pntss;
     for(map<uint16, lwPNTS>::const_iterator j = pmap.begin(); j != pmap.end(); j++)
        {
         // save PNTS chunk
         BE_write_uint32(ofile, LWO_PNTS); // PNTS
         BE_write_uint32(ofile, j->second.chunksize); // chunksize
         for(uint32 index = 0; index < j->second.elem; index++) { // data
             BE_write_real32(ofile, j->second.data[index].x); // x
             BE_write_real32(ofile, j->second.data[index].y); // y
             BE_write_real32(ofile, j->second.data[index].z); // z
            }

         // save TXUV data
         for(map<string, lwTXUV>::const_iterator k = j->second.txuv.begin(); k != j->second.txuv.end(); k++)
            {
             // save VMAP chunk
             BE_write_uint32(ofile, LWO_VMAP); // VMAP
             BE_write_uint32(ofile, k->second.chunksize); // chunksize
             BE_write_uint32(ofile, LWO_TXUV); // TXUV
             BE_write_uint16(ofile, 2); // dimension
             write_aligned_string_02(ofile, k->first.c_str()); // name
             for(uint32 index = 0; index < k->second.elem; index++) {
                 WriteVX(ofile, k->second.data[index].reference); // reference
                 BE_write_real32(ofile, k->second.data[index].x); // x
                 BE_write_real32(ofile, k->second.data[index].y); // y
                }
            }

         // save WGHT data
         for(map<string, lwWGHT>::const_iterator k = j->second.wght.begin(); k != j->second.wght.end(); k++)
            {
             // save VMAP chunk
             BE_write_uint32(ofile, LWO_VMAP); // VMAP
             BE_write_uint32(ofile, k->second.chunksize); // chunksize
             BE_write_uint32(ofile, LWO_WGHT); // WGHT
             BE_write_uint16(ofile, 1); // dimension
             write_aligned_string_02(ofile, k->first.c_str()); // name
             for(uint32 index = 0; index < k->second.elem; index++) {
                 WriteVX(ofile, k->second.data[index].reference); // reference
                 BE_write_real32(ofile, k->second.data[index].x); // x
                }
            }

         // save MORF data
         for(map<string, lwMORF>::const_iterator k = j->second.morf.begin(); k != j->second.morf.end(); k++)
            {
             // save VMAP chunk
             BE_write_uint32(ofile, LWO_VMAP); // VMAP
             BE_write_uint32(ofile, k->second.chunksize); // chunksize
             BE_write_uint32(ofile, LWO_MORF); // MORF
             BE_write_uint16(ofile, 3); // dimension
             write_aligned_string_02(ofile, k->first.c_str()); // name
             for(uint32 index = 0; index < k->second.elem; index++) {
                 WriteVX(ofile, k->second.data[index].reference); // reference
                 BE_write_real32(ofile, k->second.data[index].x); // x
                 BE_write_real32(ofile, k->second.data[index].y); // y
                 BE_write_real32(ofile, k->second.data[index].z); // z
                }
            }

         // save POLS data
         for(size_t faceindex = 0; faceindex < j->second.face.size(); faceindex++)
            {
             // save POLS data
             const lwPOLS& pols = j->second.face[faceindex];
             if(pols.chunksize) {
                BE_write_uint32(ofile, LWO_POLS);
                BE_write_uint32(ofile, pols.chunksize);
                BE_write_uint32(ofile, LWO_FACE);
                for(uint32 index = 0; index < pols.elem; index++) {
                    BE_write_uint16(ofile, 3);
                    WriteVX(ofile, pols.data[index].a);
                    WriteVX(ofile, pols.data[index].b);
                    WriteVX(ofile, pols.data[index].c);
                   }
               }

             // save PTAG:SURF data
             const lwPTAG& ptag = pols.surf;
             if(ptag.chunksize) {
                BE_write_uint32(ofile, LWO_PTAG);
                BE_write_uint32(ofile, ptag.chunksize);
                BE_write_uint32(ofile, ptag.type);
                for(map<uint32, uint16>::const_iterator k = ptag.tags.begin(); k != ptag.tags.end(); k++) {
                    WriteVX(ofile, k->first);
                    BE_write_uint16(ofile, k->second);
                   }
               }
            }
        }

     // save JNTS
     const map<uint16, lwJNTS>& jmap = i->second.jntss;
     for(map<uint16, lwJNTS>::const_iterator j = jmap.begin(); j != jmap.end(); j++)
        {
         // save PNTS chunk
         BE_write_uint32(ofile, LWO_PNTS); // PNTS
         BE_write_uint32(ofile, j->second.chunksize); // chunksize
         for(uint32 index = 0; index < j->second.elem; index++) { // data
             BE_write_real32(ofile, j->second.data[index].x); // x
             BE_write_real32(ofile, j->second.data[index].y); // y
             BE_write_real32(ofile, j->second.data[index].z); // z
            }

         // save SKEL data
         for(size_t skelindex = 0; skelindex < j->second.skel.size(); skelindex++)
            {
             // save SKEL data
             const lwSKEL& skel = j->second.skel[skelindex];
             if(skel.chunksize) {
                BE_write_uint32(ofile, LWO_POLS); // POLS
                BE_write_uint32(ofile, skel.chunksize); // chunksize
                BE_write_uint32(ofile, LWO_BONE); // BONE
                for(uint32 index = 0; index < skel.elem; index++) { // data
                    BE_write_uint16(ofile, 2); // vertices
                    WriteVX(ofile, skel.data[index].a); // a
                    WriteVX(ofile, skel.data[index].b); // b
                   }
               }

             // save PTAG:SURF data
             const lwPTAG& stag = skel.stag;
             if(stag.chunksize) {
                BE_write_uint32(ofile, LWO_PTAG);
                BE_write_uint32(ofile, stag.chunksize);
                BE_write_uint32(ofile, stag.type);
                for(map<uint32, uint16>::const_iterator k = stag.tags.begin(); k != stag.tags.end(); k++) {
                    WriteVX(ofile, k->first);
                    BE_write_uint16(ofile, k->second);
                   }
               }

             // save PTAG:BONE data
             const lwPTAG& btag = skel.btag;
             if(btag.chunksize) {
                BE_write_uint32(ofile, LWO_PTAG);
                BE_write_uint32(ofile, btag.chunksize);
                BE_write_uint32(ofile, btag.type);
                for(map<uint32, uint16>::const_iterator k = btag.tags.begin(); k != btag.tags.end(); k++) {
                    WriteVX(ofile, k->first);
                    BE_write_uint16(ofile, k->second);
                   }
               }

             // save PTAG:PART data
             const lwPTAG& ptag = skel.ptag;
             if(ptag.chunksize) {
                BE_write_uint32(ofile, LWO_PTAG);
                BE_write_uint32(ofile, ptag.chunksize);
                BE_write_uint32(ofile, ptag.type);
                for(map<uint32, uint16>::const_iterator k = ptag.tags.begin(); k != ptag.tags.end(); k++) {
                    WriteVX(ofile, k->first);
                    BE_write_uint16(ofile, k->second);
                   }
               }

             // save PTAG:BNUP data
             const lwPTAG& utag = skel.utag;
             if(utag.chunksize)
               {
               }
            }
        }
    }

 // save SURF data
 for(map<string, lwSURF>::const_iterator i = surfs.begin(); i != surfs.end(); i++)
    {
     // part 1
     BE_write_uint32(ofile, LWO_SURF); // SURF
     BE_write_uint32(ofile, i->second.chunksize); // chunksize
     write_aligned_string_02(ofile, i->first.c_str()); // name
     BE_write_uint16(ofile, 0); // source

     // part 2
     if(i->second.flags & LWO_SURF_FLAGS_COLR) {
        BE_write_uint32(ofile, LWO_COLR);
        BE_write_uint16(ofile, 0x0E);
        BE_write_real32(ofile, i->second.colr[0]);
        BE_write_real32(ofile, i->second.colr[1]);
        BE_write_real32(ofile, i->second.colr[2]);
        BE_write_uint16(ofile, 0x00);
       }
     if(i->second.flags & LWO_SURF_FLAGS_DIFF) {
        BE_write_uint32(ofile, LWO_DIFF);
        BE_write_uint16(ofile, 0x06);
        BE_write_real32(ofile, i->second.diff);
        BE_write_uint16(ofile, 0x00);
       }
     if(i->second.flags & LWO_SURF_FLAGS_SPEC) {
        BE_write_uint32(ofile, LWO_SPEC);
        BE_write_uint16(ofile, 0x06);
        BE_write_real32(ofile, i->second.spec);
        BE_write_uint16(ofile, 0x00);
       }
     if(i->second.flags & LWO_SURF_FLAGS_TRAN) {
        BE_write_uint32(ofile, LWO_TRAN);
        BE_write_uint16(ofile, 0x06);
        BE_write_real32(ofile, i->second.tran);
        BE_write_uint16(ofile, 0x00);
       }

     // part 3
     if(i->second.flags & LWO_SURF_FLAGS_COLR_BLOK)
       {
        // BLOK
        BE_write_uint32(ofile, LWO_BLOK);
        BE_write_uint16(ofile, i->second.colr_map.chunksize);

        // BLOK:IMAP
        BE_write_uint32(ofile, LWO_IMAP);
        BE_write_uint16(ofile, i->second.colr_map.imap.chunksize);
        BE_write_uint16(ofile, 0x8000); // constant ordinal

        // BLOK:IMAP:CHAN
        BE_write_uint32(ofile, LWO_CHAN);
        BE_write_uint16(ofile, i->second.colr_map.imap.chan.chunksize);
        BE_write_uint32(ofile, i->second.colr_map.imap.chan.channel);

        // BLOK:IMAP:ENAB
        BE_write_uint32(ofile, LWO_ENAB);
        BE_write_uint16(ofile, i->second.colr_map.imap.enab.chunksize);
        BE_write_uint16(ofile, i->second.colr_map.imap.enab.enable);

        // BLOK:IMAP:NEGA
        BE_write_uint32(ofile, LWO_NEGA);
        BE_write_uint16(ofile, i->second.colr_map.imap.nega.chunksize);
        BE_write_uint16(ofile, i->second.colr_map.imap.nega.enable);

        // BLOK:IMAP:OPAC
        BE_write_uint32(ofile, LWO_OPAC);
        BE_write_uint16(ofile, i->second.colr_map.imap.opac.chunksize);
        BE_write_uint16(ofile, i->second.colr_map.imap.opac.type);
        BE_write_real32(ofile, i->second.colr_map.imap.opac.opacity);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLOK:IMAP:AXIS
        BE_write_uint32(ofile, LWO_AXIS);
        BE_write_uint16(ofile, i->second.colr_map.imap.axis.chunksize);
        BE_write_uint16(ofile, i->second.colr_map.imap.axis.axis);

        // BLOK:TMAP
        uint16 TMAP_chunksize = 0x5A;
        BE_write_uint32(ofile, LWO_TMAP);
        BE_write_uint16(ofile, i->second.colr_map.tmap.chunksize);

        // BLCK:TMAP:CNTR
        BE_write_uint32(ofile, LWO_CNTR);
        BE_write_uint16(ofile, i->second.colr_map.tmap.cntr.chunksize);
        BE_write_real32(ofile, i->second.colr_map.tmap.cntr.x);
        BE_write_real32(ofile, i->second.colr_map.tmap.cntr.y);
        BE_write_real32(ofile, i->second.colr_map.tmap.cntr.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:SIZE
        BE_write_uint32(ofile, LWO_SIZE);
        BE_write_uint16(ofile, i->second.colr_map.tmap.size.chunksize);
        BE_write_real32(ofile, i->second.colr_map.tmap.size.x);
        BE_write_real32(ofile, i->second.colr_map.tmap.size.y);
        BE_write_real32(ofile, i->second.colr_map.tmap.size.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:ROTA
        BE_write_uint32(ofile, LWO_ROTA);
        BE_write_uint16(ofile, i->second.colr_map.tmap.rota.chunksize);
        BE_write_real32(ofile, i->second.colr_map.tmap.rota.x);
        BE_write_real32(ofile, i->second.colr_map.tmap.rota.y);
        BE_write_real32(ofile, i->second.colr_map.tmap.rota.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:FALL
        BE_write_uint32(ofile, LWO_FALL);
        BE_write_uint16(ofile, i->second.colr_map.tmap.fall.chunksize);
        BE_write_uint16(ofile, i->second.colr_map.tmap.fall.type);
        BE_write_real32(ofile, i->second.colr_map.tmap.fall.x);
        BE_write_real32(ofile, i->second.colr_map.tmap.fall.y);
        BE_write_real32(ofile, i->second.colr_map.tmap.fall.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:CSYS
        BE_write_uint32(ofile, LWO_CSYS);
        BE_write_uint16(ofile, i->second.colr_map.tmap.csys.chunksize);
        BE_write_uint16(ofile, i->second.colr_map.tmap.csys.type);

        // BLCK:PROJ
        BE_write_uint32(ofile, LWO_PROJ);
        BE_write_uint16(ofile, i->second.colr_map.proj.chunksize);
        BE_write_uint16(ofile, i->second.colr_map.proj.mode);

        // BLCK:AXIS
        BE_write_uint32(ofile, LWO_AXIS);
        BE_write_uint16(ofile, i->second.colr_map.axis.chunksize);
        BE_write_uint16(ofile, i->second.colr_map.axis.axis);

        // BLCK:IMAG
        if(i->second.colr_map.imag.chunksize) {
           BE_write_uint32(ofile, LWO_IMAG);
           BE_write_uint16(ofile, i->second.colr_map.imag.chunksize);
           WriteVX(ofile, i->second.colr_map.imag.clipindex);
          }

        // BLCK:WRAP
        BE_write_uint32(ofile, LWO_WRAP);
        BE_write_uint16(ofile, i->second.colr_map.wrap.chunksize);
        BE_write_uint16(ofile, i->second.colr_map.wrap.wrap_w);
        BE_write_uint16(ofile, i->second.colr_map.wrap.wrap_h);

        // BLCK:VMAP
        if(i->second.colr_map.vmap.chunksize) {
           BE_write_uint32(ofile, LWO_VMAP);
           BE_write_uint16(ofile, i->second.colr_map.vmap.chunksize);
           write_aligned_string_02(ofile, i->second.colr_map.vmap.name.c_str());
          }

        // BLCK:AAST
        BE_write_uint32(ofile, LWO_AAST);
        BE_write_uint16(ofile, i->second.colr_map.aast.chunksize);
        BE_write_uint16(ofile, i->second.colr_map.aast.flags);
        BE_write_real32(ofile, i->second.colr_map.aast.strength);

        // BLCK:PIXB
        BE_write_uint32(ofile, LWO_PIXB);
        BE_write_uint16(ofile, i->second.colr_map.pixb.chunksize);
        BE_write_uint16(ofile, i->second.colr_map.pixb.flags);
       }
     if(i->second.flags & LWO_SURF_FLAGS_DIFF_BLOK)
       {
        // BLOK
        BE_write_uint32(ofile, LWO_BLOK);
        BE_write_uint16(ofile, i->second.diff_map.chunksize);

        // BLOK:IMAP
        BE_write_uint32(ofile, LWO_IMAP);
        BE_write_uint16(ofile, i->second.diff_map.imap.chunksize);
        BE_write_uint16(ofile, 0x8000); // constant ordinal

        // BLOK:IMAP:CHAN
        BE_write_uint32(ofile, LWO_CHAN);
        BE_write_uint16(ofile, i->second.diff_map.imap.chan.chunksize);
        BE_write_uint32(ofile, i->second.diff_map.imap.chan.channel);

        // BLOK:IMAP:ENAB
        BE_write_uint32(ofile, LWO_ENAB);
        BE_write_uint16(ofile, i->second.diff_map.imap.enab.chunksize);
        BE_write_uint16(ofile, i->second.diff_map.imap.enab.enable);

        // BLOK:IMAP:NEGA
        BE_write_uint32(ofile, LWO_NEGA);
        BE_write_uint16(ofile, i->second.diff_map.imap.nega.chunksize);
        BE_write_uint16(ofile, i->second.diff_map.imap.nega.enable);

        // BLOK:IMAP:OPAC
        BE_write_uint32(ofile, LWO_OPAC);
        BE_write_uint16(ofile, i->second.diff_map.imap.opac.chunksize);
        BE_write_uint16(ofile, i->second.diff_map.imap.opac.type);
        BE_write_real32(ofile, i->second.diff_map.imap.opac.opacity);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLOK:IMAP:AXIS
        BE_write_uint32(ofile, LWO_AXIS);
        BE_write_uint16(ofile, i->second.diff_map.imap.axis.chunksize);
        BE_write_uint16(ofile, i->second.diff_map.imap.axis.axis);

        // BLOK:TMAP
        uint16 TMAP_chunksize = 0x5A;
        BE_write_uint32(ofile, LWO_TMAP);
        BE_write_uint16(ofile, i->second.diff_map.tmap.chunksize);

        // BLCK:TMAP:CNTR
        BE_write_uint32(ofile, LWO_CNTR);
        BE_write_uint16(ofile, i->second.diff_map.tmap.cntr.chunksize);
        BE_write_real32(ofile, i->second.diff_map.tmap.cntr.x);
        BE_write_real32(ofile, i->second.diff_map.tmap.cntr.y);
        BE_write_real32(ofile, i->second.diff_map.tmap.cntr.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:SIZE
        BE_write_uint32(ofile, LWO_SIZE);
        BE_write_uint16(ofile, i->second.diff_map.tmap.size.chunksize);
        BE_write_real32(ofile, i->second.diff_map.tmap.size.x);
        BE_write_real32(ofile, i->second.diff_map.tmap.size.y);
        BE_write_real32(ofile, i->second.diff_map.tmap.size.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:ROTA
        BE_write_uint32(ofile, LWO_ROTA);
        BE_write_uint16(ofile, i->second.diff_map.tmap.rota.chunksize);
        BE_write_real32(ofile, i->second.diff_map.tmap.rota.x);
        BE_write_real32(ofile, i->second.diff_map.tmap.rota.y);
        BE_write_real32(ofile, i->second.diff_map.tmap.rota.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:FALL
        BE_write_uint32(ofile, LWO_FALL);
        BE_write_uint16(ofile, i->second.diff_map.tmap.fall.chunksize);
        BE_write_uint16(ofile, i->second.diff_map.tmap.fall.type);
        BE_write_real32(ofile, i->second.diff_map.tmap.fall.x);
        BE_write_real32(ofile, i->second.diff_map.tmap.fall.y);
        BE_write_real32(ofile, i->second.diff_map.tmap.fall.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:CSYS
        BE_write_uint32(ofile, LWO_CSYS);
        BE_write_uint16(ofile, i->second.diff_map.tmap.csys.chunksize);
        BE_write_uint16(ofile, i->second.diff_map.tmap.csys.type);

        // BLCK:PROJ
        BE_write_uint32(ofile, LWO_PROJ);
        BE_write_uint16(ofile, i->second.diff_map.proj.chunksize);
        BE_write_uint16(ofile, i->second.diff_map.proj.mode);

        // BLCK:AXIS
        BE_write_uint32(ofile, LWO_AXIS);
        BE_write_uint16(ofile, i->second.diff_map.axis.chunksize);
        BE_write_uint16(ofile, i->second.diff_map.axis.axis);

        // BLCK:IMAG
        if(i->second.diff_map.imag.chunksize) {
           BE_write_uint32(ofile, LWO_IMAG);
           BE_write_uint16(ofile, i->second.diff_map.imag.chunksize);
           WriteVX(ofile, i->second.diff_map.imag.clipindex);
          }

        // BLCK:WRAP
        BE_write_uint32(ofile, LWO_WRAP);
        BE_write_uint16(ofile, i->second.diff_map.wrap.chunksize);
        BE_write_uint16(ofile, i->second.diff_map.wrap.wrap_w);
        BE_write_uint16(ofile, i->second.diff_map.wrap.wrap_h);

        // BLCK:VMAP
        if(i->second.diff_map.vmap.chunksize) {
           BE_write_uint32(ofile, LWO_VMAP);
           BE_write_uint16(ofile, i->second.diff_map.vmap.chunksize);
           write_aligned_string_02(ofile, i->second.diff_map.vmap.name.c_str());
          }

        // BLCK:AAST
        BE_write_uint32(ofile, LWO_AAST);
        BE_write_uint16(ofile, i->second.diff_map.aast.chunksize);
        BE_write_uint16(ofile, i->second.diff_map.aast.flags);
        BE_write_real32(ofile, i->second.diff_map.aast.strength);

        // BLCK:PIXB
        BE_write_uint32(ofile, LWO_PIXB);
        BE_write_uint16(ofile, i->second.diff_map.pixb.chunksize);
        BE_write_uint16(ofile, i->second.diff_map.pixb.flags);
       }
     if(i->second.flags & LWO_SURF_FLAGS_SPEC_BLOK)
       {
        // BLOK
        BE_write_uint32(ofile, LWO_BLOK);
        BE_write_uint16(ofile, i->second.spec_map.chunksize);

        // BLOK:IMAP
        BE_write_uint32(ofile, LWO_IMAP);
        BE_write_uint16(ofile, i->second.spec_map.imap.chunksize);
        BE_write_uint16(ofile, 0x8000); // constant ordinal

        // BLOK:IMAP:CHAN
        BE_write_uint32(ofile, LWO_CHAN);
        BE_write_uint16(ofile, i->second.spec_map.imap.chan.chunksize);
        BE_write_uint32(ofile, i->second.spec_map.imap.chan.channel);

        // BLOK:IMAP:ENAB
        BE_write_uint32(ofile, LWO_ENAB);
        BE_write_uint16(ofile, i->second.spec_map.imap.enab.chunksize);
        BE_write_uint16(ofile, i->second.spec_map.imap.enab.enable);

        // BLOK:IMAP:NEGA
        BE_write_uint32(ofile, LWO_NEGA);
        BE_write_uint16(ofile, i->second.spec_map.imap.nega.chunksize);
        BE_write_uint16(ofile, i->second.spec_map.imap.nega.enable);

        // BLOK:IMAP:OPAC
        BE_write_uint32(ofile, LWO_OPAC);
        BE_write_uint16(ofile, i->second.spec_map.imap.opac.chunksize);
        BE_write_uint16(ofile, i->second.spec_map.imap.opac.type);
        BE_write_real32(ofile, i->second.spec_map.imap.opac.opacity);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLOK:IMAP:AXIS
        BE_write_uint32(ofile, LWO_AXIS);
        BE_write_uint16(ofile, i->second.spec_map.imap.axis.chunksize);
        BE_write_uint16(ofile, i->second.spec_map.imap.axis.axis);

        // BLOK:TMAP
        uint16 TMAP_chunksize = 0x5A;
        BE_write_uint32(ofile, LWO_TMAP);
        BE_write_uint16(ofile, i->second.spec_map.tmap.chunksize);

        // BLCK:TMAP:CNTR
        BE_write_uint32(ofile, LWO_CNTR);
        BE_write_uint16(ofile, i->second.spec_map.tmap.cntr.chunksize);
        BE_write_real32(ofile, i->second.spec_map.tmap.cntr.x);
        BE_write_real32(ofile, i->second.spec_map.tmap.cntr.y);
        BE_write_real32(ofile, i->second.spec_map.tmap.cntr.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:SIZE
        BE_write_uint32(ofile, LWO_SIZE);
        BE_write_uint16(ofile, i->second.spec_map.tmap.size.chunksize);
        BE_write_real32(ofile, i->second.spec_map.tmap.size.x);
        BE_write_real32(ofile, i->second.spec_map.tmap.size.y);
        BE_write_real32(ofile, i->second.spec_map.tmap.size.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:ROTA
        BE_write_uint32(ofile, LWO_ROTA);
        BE_write_uint16(ofile, i->second.spec_map.tmap.rota.chunksize);
        BE_write_real32(ofile, i->second.spec_map.tmap.rota.x);
        BE_write_real32(ofile, i->second.spec_map.tmap.rota.y);
        BE_write_real32(ofile, i->second.spec_map.tmap.rota.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:FALL
        BE_write_uint32(ofile, LWO_FALL);
        BE_write_uint16(ofile, i->second.spec_map.tmap.fall.chunksize);
        BE_write_uint16(ofile, i->second.spec_map.tmap.fall.type);
        BE_write_real32(ofile, i->second.spec_map.tmap.fall.x);
        BE_write_real32(ofile, i->second.spec_map.tmap.fall.y);
        BE_write_real32(ofile, i->second.spec_map.tmap.fall.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:CSYS
        BE_write_uint32(ofile, LWO_CSYS);
        BE_write_uint16(ofile, i->second.spec_map.tmap.csys.chunksize);
        BE_write_uint16(ofile, i->second.spec_map.tmap.csys.type);

        // BLCK:PROJ
        BE_write_uint32(ofile, LWO_PROJ);
        BE_write_uint16(ofile, i->second.spec_map.proj.chunksize);
        BE_write_uint16(ofile, i->second.spec_map.proj.mode);

        // BLCK:AXIS
        BE_write_uint32(ofile, LWO_AXIS);
        BE_write_uint16(ofile, i->second.spec_map.axis.chunksize);
        BE_write_uint16(ofile, i->second.spec_map.axis.axis);

        // BLCK:IMAG
        if(i->second.spec_map.imag.chunksize) {
           BE_write_uint32(ofile, LWO_IMAG);
           BE_write_uint16(ofile, i->second.spec_map.imag.chunksize);
           WriteVX(ofile, i->second.spec_map.imag.clipindex);
          }

        // BLCK:WRAP
        BE_write_uint32(ofile, LWO_WRAP);
        BE_write_uint16(ofile, i->second.spec_map.wrap.chunksize);
        BE_write_uint16(ofile, i->second.spec_map.wrap.wrap_w);
        BE_write_uint16(ofile, i->second.spec_map.wrap.wrap_h);

        // BLCK:VMAP
        if(i->second.spec_map.vmap.chunksize) {
           BE_write_uint32(ofile, LWO_VMAP);
           BE_write_uint16(ofile, i->second.spec_map.vmap.chunksize);
           write_aligned_string_02(ofile, i->second.spec_map.vmap.name.c_str());
          }

        // BLCK:AAST
        BE_write_uint32(ofile, LWO_AAST);
        BE_write_uint16(ofile, i->second.spec_map.aast.chunksize);
        BE_write_uint16(ofile, i->second.spec_map.aast.flags);
        BE_write_real32(ofile, i->second.spec_map.aast.strength);

        // BLCK:PIXB
        BE_write_uint32(ofile, LWO_PIXB);
        BE_write_uint16(ofile, i->second.spec_map.pixb.chunksize);
        BE_write_uint16(ofile, i->second.spec_map.pixb.flags);
       }
     if(i->second.flags & LWO_SURF_FLAGS_TRAN_BLOK)
       {
        // BLOK
        BE_write_uint32(ofile, LWO_BLOK);
        BE_write_uint16(ofile, i->second.tran_map.chunksize);

        // BLOK:IMAP
        BE_write_uint32(ofile, LWO_IMAP);
        BE_write_uint16(ofile, i->second.tran_map.imap.chunksize);
        BE_write_uint16(ofile, 0x8000); // constant ordinal

        // BLOK:IMAP:CHAN
        BE_write_uint32(ofile, LWO_CHAN);
        BE_write_uint16(ofile, i->second.tran_map.imap.chan.chunksize);
        BE_write_uint32(ofile, i->second.tran_map.imap.chan.channel);

        // BLOK:IMAP:ENAB
        BE_write_uint32(ofile, LWO_ENAB);
        BE_write_uint16(ofile, i->second.tran_map.imap.enab.chunksize);
        BE_write_uint16(ofile, i->second.tran_map.imap.enab.enable);

        // BLOK:IMAP:NEGA
        BE_write_uint32(ofile, LWO_NEGA);
        BE_write_uint16(ofile, i->second.tran_map.imap.nega.chunksize);
        BE_write_uint16(ofile, i->second.tran_map.imap.nega.enable);

        // BLOK:IMAP:OPAC
        BE_write_uint32(ofile, LWO_OPAC);
        BE_write_uint16(ofile, i->second.tran_map.imap.opac.chunksize);
        BE_write_uint16(ofile, i->second.tran_map.imap.opac.type);
        BE_write_real32(ofile, i->second.tran_map.imap.opac.opacity);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLOK:IMAP:AXIS
        BE_write_uint32(ofile, LWO_AXIS);
        BE_write_uint16(ofile, i->second.tran_map.imap.axis.chunksize);
        BE_write_uint16(ofile, i->second.tran_map.imap.axis.axis);

        // BLOK:TMAP
        uint16 TMAP_chunksize = 0x5A;
        BE_write_uint32(ofile, LWO_TMAP);
        BE_write_uint16(ofile, i->second.tran_map.tmap.chunksize);

        // BLCK:TMAP:CNTR
        BE_write_uint32(ofile, LWO_CNTR);
        BE_write_uint16(ofile, i->second.tran_map.tmap.cntr.chunksize);
        BE_write_real32(ofile, i->second.tran_map.tmap.cntr.x);
        BE_write_real32(ofile, i->second.tran_map.tmap.cntr.y);
        BE_write_real32(ofile, i->second.tran_map.tmap.cntr.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:SIZE
        BE_write_uint32(ofile, LWO_SIZE);
        BE_write_uint16(ofile, i->second.tran_map.tmap.size.chunksize);
        BE_write_real32(ofile, i->second.tran_map.tmap.size.x);
        BE_write_real32(ofile, i->second.tran_map.tmap.size.y);
        BE_write_real32(ofile, i->second.tran_map.tmap.size.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:ROTA
        BE_write_uint32(ofile, LWO_ROTA);
        BE_write_uint16(ofile, i->second.tran_map.tmap.rota.chunksize);
        BE_write_real32(ofile, i->second.tran_map.tmap.rota.x);
        BE_write_real32(ofile, i->second.tran_map.tmap.rota.y);
        BE_write_real32(ofile, i->second.tran_map.tmap.rota.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:FALL
        BE_write_uint32(ofile, LWO_FALL);
        BE_write_uint16(ofile, i->second.tran_map.tmap.fall.chunksize);
        BE_write_uint16(ofile, i->second.tran_map.tmap.fall.type);
        BE_write_real32(ofile, i->second.tran_map.tmap.fall.x);
        BE_write_real32(ofile, i->second.tran_map.tmap.fall.y);
        BE_write_real32(ofile, i->second.tran_map.tmap.fall.z);
        BE_write_uint16(ofile, 0x00); // envelope

        // BLCK:TMAP:CSYS
        BE_write_uint32(ofile, LWO_CSYS);
        BE_write_uint16(ofile, i->second.tran_map.tmap.csys.chunksize);
        BE_write_uint16(ofile, i->second.tran_map.tmap.csys.type);

        // BLCK:PROJ
        BE_write_uint32(ofile, LWO_PROJ);
        BE_write_uint16(ofile, i->second.tran_map.proj.chunksize);
        BE_write_uint16(ofile, i->second.tran_map.proj.mode);

        // BLCK:AXIS
        BE_write_uint32(ofile, LWO_AXIS);
        BE_write_uint16(ofile, i->second.tran_map.axis.chunksize);
        BE_write_uint16(ofile, i->second.tran_map.axis.axis);

        // BLCK:IMAG
        if(i->second.tran_map.imag.chunksize) {
           BE_write_uint32(ofile, LWO_IMAG);
           BE_write_uint16(ofile, i->second.tran_map.imag.chunksize);
           WriteVX(ofile, i->second.tran_map.imag.clipindex);
          }

        // BLCK:WRAP
        BE_write_uint32(ofile, LWO_WRAP);
        BE_write_uint16(ofile, i->second.tran_map.wrap.chunksize);
        BE_write_uint16(ofile, i->second.tran_map.wrap.wrap_w);
        BE_write_uint16(ofile, i->second.tran_map.wrap.wrap_h);

        // BLCK:VMAP
        if(i->second.tran_map.vmap.chunksize) {
           BE_write_uint32(ofile, LWO_VMAP);
           BE_write_uint16(ofile, i->second.tran_map.vmap.chunksize);
           write_aligned_string_02(ofile, i->second.tran_map.vmap.name.c_str());
          }

        // BLCK:AAST
        BE_write_uint32(ofile, LWO_AAST);
        BE_write_uint16(ofile, i->second.tran_map.aast.chunksize);
        BE_write_uint16(ofile, i->second.tran_map.aast.flags);
        BE_write_real32(ofile, i->second.tran_map.aast.strength);

        // BLCK:PIXB
        BE_write_uint32(ofile, LWO_PIXB);
        BE_write_uint16(ofile, i->second.tran_map.pixb.chunksize);
        BE_write_uint16(ofile, i->second.tran_map.pixb.flags);
       }
    }

 return true;
}

void lwFileSaverExample(void)
{
 using namespace std;
 lwFileSaver saver;

 // image clips
 saver.insertClip(1, "image1.tga");
 saver.insertClip(2, "image2.tga");

 // layer #0
 saver.insertLayr(0, nullptr);
 boost::shared_array<lwVertex3D> data1(new lwVertex3D[3]);
 data1[0].x = data1[0].y = data1[0].z = 0.0f;
 data1[1].x = data1[1].y = data1[1].z = 1.0f;
 data1[2].x = data1[2].y = data1[2].z = 2.0f;
 saver.insertPnts(0, 0, data1, 3);

 boost::shared_array<lwRefVertex2D> uv1(new lwRefVertex2D[3]);
 uv1[0].reference = 0; uv1[0].x = 0.0f; uv1[0].y = 0.0f;
 uv1[1].reference = 1; uv1[1].x = 0.0f; uv1[1].y = 0.5f;
 uv1[2].reference = 2; uv1[2].x = 0.0f; uv1[2].y = 1.0f;
 saver.insertTxuv(0, 0, "uv1", uv1, 3);

 boost::shared_array<lwRefVertex1D> w1(new lwRefVertex1D[3]);
 w1[0].reference = 0; w1[0].x = 0.1f;
 w1[1].reference = 1; w1[1].x = 0.2f;
 w1[2].reference = 2; w1[2].x = 0.3f;
 saver.insertWght(0, 0, "w1", w1, 3);

 boost::shared_array<lwRefVertex3D> m1(new lwRefVertex3D[3]);
 m1[0].reference = 0; m1[0].x = m1[0].y = m1[0].z = -1.0f;
 m1[1].reference = 1; m1[1].x = m1[1].y = m1[1].z = -1.0f;
 m1[2].reference = 2; m1[2].x = m1[2].y = m1[2].z = -1.0f;
 saver.insertMorf(0, 0, "m1", m1, 3);

 boost::shared_array<lwTriangle> p1(new lwTriangle[1]);
 p1[0].a = 0;
 p1[0].b = 1;
 p1[0].c = 2;
 saver.insertPols(0, 0, "surface_000", p1, 1);

 // layer #1
 saver.insertLayr(1, nullptr);
 boost::shared_array<lwVertex3D> data2(new lwVertex3D[3]);
 data2[0].x = data2[0].y = data2[0].z = 3.0f;
 data2[1].x = data2[1].y = data2[1].z = 4.0f;
 data2[2].x = data2[2].y = data2[2].z = 5.0f;
 saver.insertPnts(1, 0, data2, 3);

 boost::shared_array<lwRefVertex2D> uv2(new lwRefVertex2D[3]);
 uv2[0].reference = 0; uv2[0].x = 1.0; uv2[0].y = 0.0f;
 uv2[1].reference = 1; uv2[1].x = 1.0; uv2[1].y = 0.5f;
 uv2[2].reference = 2; uv2[2].x = 1.0; uv2[2].y = 1.0f;
 saver.insertTxuv(1, 0, "uv2", uv2, 3);

 boost::shared_array<lwRefVertex1D> w2(new lwRefVertex1D[3]);
 w2[0].reference = 0; w2[0].x = 0.7f;
 w2[1].reference = 1; w2[1].x = 0.8f;
 w2[2].reference = 2; w2[2].x = 0.9f;
 saver.insertWght(1, 0, "w2", w2, 3);

 boost::shared_array<lwRefVertex3D> m2(new lwRefVertex3D[3]);
 m2[0].reference = 0; m2[0].x = m2[0].y = m2[0].z = 1.0f;
 m2[1].reference = 1; m2[1].x = m2[1].y = m2[1].z = 1.0f;
 m2[2].reference = 2; m2[2].x = m2[2].y = m2[2].z = 1.0f;
 saver.insertMorf(1, 0, "m2", m2, 3);

 boost::shared_array<lwTriangle> p2(new lwTriangle[1]);
 p2[0].a = 0;
 p2[0].b = 1;
 p2[0].c = 2;
 saver.insertPols(1, 0, "surface_001", p2, 1);

 saver.insertSurf("surface_000");
 saver.insertSurf("surface_001");
 saver.insertSurf("surface_002");
 saver.setSurfColr("surface_000", 1.0f, 0.0f, 0.0f);
 saver.setSurfColr("surface_001", 0.0f, 1.0f, 0.0f);
 saver.setSurfColr("surface_002", 0.0f, 0.0f, 1.0f);

 // enable surface UV map
 saver.enableSurfColrImag("surface_000");
 saver.enableSurfDiffImag("surface_000");
 saver.enableSurfSpecImag("surface_000");
 saver.enableSurfTranImag("surface_000");

 // set surface UV map 
 saver.setSurfColrVmap("surface_000", "uv1");
 saver.setSurfDiffVmap("surface_000", "uv1");
 saver.setSurfSpecVmap("surface_000", "uv1");
 saver.setSurfTranVmap("surface_000", "uv1");

 // set surface image
 saver.setSurfColrImag("surface_000", 1);
 saver.setSurfTranImag("surface_000", 1);
 saver.setSurfTranNega("surface_000", 1);

 // enable surface UV map
 saver.enableSurfColrImag("surface_001");
 saver.enableSurfDiffImag("surface_001");
 saver.enableSurfSpecImag("surface_001");
 saver.enableSurfTranImag("surface_001");

 // set surface UV map 
 saver.setSurfColrVmap("surface_001", "uv2");
 saver.setSurfDiffVmap("surface_001", "uv2");
 saver.setSurfSpecVmap("surface_001", "uv2");
 saver.setSurfTranVmap("surface_001", "uv2");

 // set surface image
 saver.setSurfColrImag("surface_001", 1);

 //
 // HOW TO: SKELETONS
 //

 // add joints
 uint32 n_joints = 4;
 boost::shared_array<lwVertex3D> joints(new lwVertex3D[n_joints]);
 joints[0].x = 1.0f; joints[0].y = 1.0f; joints[0].z = 0.0f;
 joints[1].x = 1.0f; joints[1].y = 2.0f; joints[1].z = 0.0f;
 joints[2].x = 2.0f; joints[2].y = 2.0f; joints[2].z = 0.0f;
 joints[3].x = 2.0f; joints[3].y = 1.0f; joints[3].z = 0.0f;
 saver.insertJnts(0, 0, joints, n_joints);

 // add bones
 uint32 n_bones = 3;
 boost::shared_array<lwSkelegon> bones(new lwSkelegon[n_bones]);
 bones[0].name = "root"; bones[0].a = 0; bones[0].b = 1;
 bones[1].name = "hand"; bones[1].a = 1; bones[1].b = 2;
 bones[2].name = "fing"; bones[2].a = 2; bones[2].b = 3;
 saver.insertSkel(0, 0, "skeleton", bones, n_bones);

 //
 // HOW TO: SAVE LWO
 //

 saver.save("test.lwo");
}

/*
inline uint32 lwConvertToVX(uint32 index)
{
 if(index < 0xFF00) return index;
 reverse_byte_order(&index);
 index |= 0x000000FF;
 reverse_byte_order(&index);
 return index;
}

class lwJointPolygonTagsChunkSizeCalc : public JOINTVISITOR {
 private :
  std::ofstream& ofile;
  uint32 chunksize;
 public :
  lwJointPolygonTagsChunkSizeCalc(std::ofstream& out) : ofile(out), chunksize(0)
  {
  }
  uint32 getChunkSize(void)const
  {
   return chunksize;
  }
  return_type operator ()(argument1 joint, argument2 index)
  {
   chunksize += align02(joint.name.length() + 1);
   return true;
  }
};

class lwJointPolygonTagsSaver : public JOINTVISITOR {
 private :
  std::ofstream& ofile;
 public :
  lwJointPolygonTagsSaver(std::ofstream& out) : ofile(out)
  {
  }
  return_type operator ()(argument1 joint, argument2 index)
  {
   write_aligned_string_02(ofile, joint.name.c_str());
   return true;
  }
};

class lwJointPointSaver : public JOINTVISITOR {
 private :
  std::ofstream& ofile;
 public :
  lwJointPointSaver(std::ofstream& out) : ofile(out)
  {
  }
  return_type operator ()(argument1 joint, argument2 index)
  {
   BE_write_real32(ofile, -joint.matrix[12]);
   BE_write_real32(ofile, -joint.matrix[13]);
   BE_write_real32(ofile, -joint.matrix[14]);
   return true;
  }
};

class lwJointPolygonChunkSizeCalc : public JOINTVISITOR {
 private :
  std::ofstream& ofile;
  uint32 chunksize;
 public :
  lwJointPolygonChunkSizeCalc(std::ofstream& out) : ofile(out), chunksize(0)
  {
  }
  uint32 getChunkSize(void)const
  {
   return chunksize;
  }
  return_type operator ()(argument1 joint, argument2 index)
  {
   // skip root joint
   if(joint.parent == INVALID_JOINT) return true;
   chunksize += 0x02; // for number of vertices + flags
   chunksize += 0x04; // for indices
   return true;
  }
};

class lwJointPolygonSaver : public JOINTVISITOR {
 private :
  std::ofstream& ofile;
  std::map<uint32, uint32> assoc;
  uint32 point_id;
 public :
  lwJointPolygonSaver(std::ofstream& out) : ofile(out), point_id(0)
  {
   // TODO:
   // We need to set a different initial point_id when we have
   // multiple skeletons.
  }
  return_type operator ()(argument1 joint, argument2 joint_id)
  {
   // add (joint_id, point_id) association data to map
   assoc.insert(std::map<uint32, uint32>::value_type(joint_id, point_id));

   // polygon for each parent-child bone
   if(joint.parent != INVALID_JOINT)
     {
      // get parent point_id
      std::map<uint32, uint32>::iterator iter = assoc.find(joint.parent);
      if(iter == assoc.end()) return false; // every child must have a parent

      // polygon data
      BE_write_uint16(ofile, 0x0002);
      BE_write_uint16(ofile, (uint16)iter->second);
      BE_write_uint16(ofile, (uint16)point_id);
     }

   // update current point index
   point_id++;
   return true;
  }
};

class lwJointPolyTagChunkSizeCalc : public JOINTVISITOR {
 private :
  std::ofstream& ofile;
  uint32 chunksize;
 public :
  lwJointPolyTagChunkSizeCalc(std::ofstream& out) : ofile(out), chunksize(0)
  {
  }
  uint32 getChunkSize(void)const
  {
   return chunksize;
  }
  return_type operator ()(argument1 joint, argument2 index)
  {
   // skip root joint
   if(joint.parent == INVALID_JOINT) return true;
   chunksize += 0x02; // for VX
   chunksize += 0x02; // for tag
   return true;
  }
};

class lwJointPolyTagSaver : public JOINTVISITOR {
 private :
  std::ofstream& ofile;
  uint32 polygon_index;
  uint32 joint_index;
 public :
  lwJointPolyTagSaver(std::ofstream& out) : ofile(out), polygon_index(0), joint_index(0)
  {
   // TODO:
   // We need to set a different initial point_id when we have
   // multiple skeletons.
  }
  return_type operator ()(argument1 joint, argument2 joint_id)
  {
   // polygon for each parent-child bone
   if(joint.parent != INVALID_JOINT)
     {
      // polygon data
      BE_write_uint16(ofile, (uint16)(polygon_index));
      BE_write_uint16(ofile, (uint16)(joint_index + 1));

      // update number of polygons seen
      polygon_index++;
     }

   joint_index++;
   return true;
  }
};

class lwJointPartTagChunkSizeCalc : public JOINTVISITOR {
 private :
  std::ofstream& ofile;
  uint32 chunksize;
 public :
  lwJointPartTagChunkSizeCalc(std::ofstream& out) : ofile(out), chunksize(0)
  {
  }
  uint32 getChunkSize(void)const
  {
   return chunksize;
  }
  return_type operator ()(argument1 joint, argument2 index)
  {
   // skip root joint
   if(joint.parent == INVALID_JOINT) return true;
   chunksize += 0x02; // for VX
   chunksize += 0x02; // for tag
   return true;
  }
};

class lwJointPartTagSaver : public JOINTVISITOR {
 private :
  std::ofstream& ofile;
  uint32 polygon_index;
  uint32 joint_index;
 public :
  lwJointPartTagSaver(std::ofstream& out) : ofile(out), polygon_index(0), joint_index(0)
  {
   // TODO:
   // We need to set a different initial point_id when we have
   // multiple skeletons.
  }
  return_type operator ()(argument1 joint, argument2 joint_id)
  {
   // polygon for each parent-child bone
   if(joint.parent != INVALID_JOINT)
     {
      // polygon data
      BE_write_uint16(ofile, (uint16)(polygon_index));
      BE_write_uint16(ofile, (uint16)(joint_index + 1));

      // update number of polygons seen
      polygon_index++;
     }

   joint_index++;
   return true;
  }
};

struct lwWeightMapPair {
 uint32 index;
 real32 value;
 lwWeightMapPair(uint32 p1 = 0, real32 p2 = 0.0f) : index(p1), value(p2) {}
};

struct lwWeightMap {
 std::string name;
 std::deque<lwWeightMapPair> data;
};

class lwConverter {
 private :
  const SIMPLEMODELCONTAINER& data;
  std::ofstream ofile;
 private :
  uint32 n_wmaps;
  std::vector<lwWeightMap> wmapdata;
 private :
  uint32 FORM_chunksize;
  uint32 LAYR_chunksize;
  uint32 TAGS_chunksize;
  uint32 PNTS_chunksize;
  uint32 POLS_chunksize;
  uint32 PTAG_chunksize;
  boost::shared_array<uint32> TXUV_chunksize;
  boost::shared_array<uint32> WGHT_chunksize;
 private :
  uint32 BONELAYR_chunksize;
  uint32 BONETAGS_chunksize;
  uint32 BONEPNTS_chunksize;
  uint32 BONEPOLS_chunksize;
  uint32 BONEPTAG_chunksize;
  uint32 BONEPART_chunksize;
 private :
  bool sanityChecker(void)const;
  bool error(const char* message)const;
  bool constructWeightMaps(void);
 private :
  void computeFORMChunkSize(void);  // FORM
  void computeLAYRChunkSize(void);  // LAYR
  void computeTAGSChunkSize(void);  // TAGS
  void computePNTSChunkSize(void);  // PNTS
  void computePOLSChunkSize(void);  // POLS
  void computePTAGChunkSize(void);  // SURF
  void computeTXUVChunkSize(void);  // VMAP:TXUV
  void computeWGHTChunkSize(void);  // VMAP:WGHT
  bool saveFORM(void);
  bool saveLAYR(void);
  bool saveTAGS(void);
  bool savePNTS(void);
  bool savePOLS(void);
  bool savePTAG(void);
  bool saveTXUV(void);
  bool saveWGHT(void);
 private :
  void computeBONELAYRChunkSize(void);  // LAYR
  void computeBONETAGSChunkSize(void);  // TAGS
  void computeBONEPNTSChunkSize(void);  // PNTS
  void computeBONEPOLSChunkSize(void);  // POLS
  void computeBONEPTAGChunkSize(void);  // PTAG - BONE
  void computeBONEPARTChunkSize(void);  // PTAG - PART
  bool saveBONELAYR(void);
  bool saveBONETAGS(void);
  bool saveBONEPNTS(void);
  bool saveBONEPOLS(void);
  bool saveBONEPTAG(void);
  bool saveBONEPART(void);
 public :
  bool convert(const char* path, const char* name);
 public :
  lwConverter(const SIMPLEMODELCONTAINER& smc);
 ~lwConverter();
};

lwConverter::lwConverter(const SIMPLEMODELCONTAINER& smc) : data(smc), n_wmaps(0)
{
 FORM_chunksize = 0;
 LAYR_chunksize = 0;
 TAGS_chunksize = 0;
 PNTS_chunksize = 0;
 POLS_chunksize = 0;
 PTAG_chunksize = 0;

 BONELAYR_chunksize = 0;
 BONETAGS_chunksize = 0;
 BONEPNTS_chunksize = 0;
 BONEPOLS_chunksize = 0;
 BONEPTAG_chunksize = 0;
 BONEPART_chunksize = 0;
}

lwConverter::~lwConverter()
{
}

bool lwConverter::sanityChecker(void)const
{
 // must have model
 if(!data.model) return error("No model present in container.");

 // if index buffers are present
 uint32 n_ibuffers = data.model->ibuffer.size();
 for(size_t i = 0; i < n_ibuffers; i++)
    {
     // index buffer must be a triangle list
     IDX_BUFFER& ib = data.model->ibuffer[i];
     if(ib.elem == 0) return error("One or more index buffers are empty.");
     if(ib.elem <= 2) return error("One or more index buffers do not contain at least one triangle.");

     // index buffer must have a valid format
     if(data.model->ibuffer[i].format == FACE_FORMAT_UINT_32) ;
     else if(data.model->ibuffer[i].format == FACE_FORMAT_UINT_16) ;
     else if(data.model->ibuffer[i].format == FACE_FORMAT_UINT_08) ;
     else return error("Unknown face format.");

     // index buffer must have a valid triangle type
     if(data.model->ibuffer[i].type == FACE_TYPE_TRIANGLES) ;
     else if(data.model->ibuffer[i].type == FACE_TYPE_TRISTRIP) ;
     else if(data.model->ibuffer[i].type == FACE_TYPE_TRISTRIPCUT) ;
     else return error("Unknown triangle type.");
    }

 // if skeletons are present
 uint32 n_skeletons = data.skeletons.size();
 if(n_skeletons) {
    for(uint32 i = 0; i < n_skeletons; i++) {
        const SKELETON& skel = data.skeletons[i];
        if(skel.joints() < 2) return error("Skeletons must have at least two joints.");
        if(strlen(skel.get_identifier()) < 1) return error("Skeletons must have a valid string identifier.");
       }
   }

 // TODO:
 // if weight maps are present

 return true;
}

bool lwConverter::error(const char* message)const
{
 std::cout << "LWO Export Error: " << message << std::endl;
 return false;
}

bool lwConverter::constructWeightMaps(void)
{
 // nothing to compute
 n_wmaps = 0;
 if(!(data.model->vbuffer.flags & VERTEX_WEIGHTS)) return true;

 // compute number of weight maps
 if(data.model->vbuffer.flags & VERTEX_WMAPIDX)
   {
    // create weight maps
    uint16 max_index = 0;
    for(uint32 i = 0; i < data.model->vbuffer.elem; i++) {
        const VERTEX& v = data.model->vbuffer.data[i];
        if(v.m1 != INVALID_VERTEX_WMAP_INDEX && !(v.m1 < max_index)) max_index = v.m1;
        if(v.m2 != INVALID_VERTEX_WMAP_INDEX && !(v.m2 < max_index)) max_index = v.m2;
        if(v.m3 != INVALID_VERTEX_WMAP_INDEX && !(v.m3 < max_index)) max_index = v.m3;
        if(v.m4 != INVALID_VERTEX_WMAP_INDEX && !(v.m4 < max_index)) max_index = v.m4;
        if(v.m5 != INVALID_VERTEX_WMAP_INDEX && !(v.m5 < max_index)) max_index = v.m5;
        if(v.m6 != INVALID_VERTEX_WMAP_INDEX && !(v.m6 < max_index)) max_index = v.m6;
        if(v.m7 != INVALID_VERTEX_WMAP_INDEX && !(v.m7 < max_index)) max_index = v.m7;
        if(v.m8 != INVALID_VERTEX_WMAP_INDEX && !(v.m8 < max_index)) max_index = v.m8;
       }
    n_wmaps = max_index + 1;
    wmapdata.resize(n_wmaps);

    // assign names
    for(uint32 i = 0; i < wmapdata.size(); i++) {
        std::stringstream ss;
        ss << "wmap_" << std::setfill('0') << std::setw(3) << i;
        wmapdata[i].name = ss.str();
       }

    // assign weights
    for(uint32 i = 0; i < data.model->vbuffer.elem; i++) {
        const VERTEX& v = data.model->vbuffer.data[i];
        if(v.m1 != INVALID_VERTEX_WMAP_INDEX) wmapdata[v.m1].data.push_back(lwWeightMapPair(i, v.w1/65535.0f));
        if(v.m2 != INVALID_VERTEX_WMAP_INDEX) wmapdata[v.m2].data.push_back(lwWeightMapPair(i, v.w2/65535.0f));
        if(v.m3 != INVALID_VERTEX_WMAP_INDEX) wmapdata[v.m3].data.push_back(lwWeightMapPair(i, v.w3/65535.0f));
        if(v.m4 != INVALID_VERTEX_WMAP_INDEX) wmapdata[v.m4].data.push_back(lwWeightMapPair(i, v.w4/65535.0f));
        if(v.m5 != INVALID_VERTEX_WMAP_INDEX) wmapdata[v.m5].data.push_back(lwWeightMapPair(i, v.w5/65535.0f));
        if(v.m6 != INVALID_VERTEX_WMAP_INDEX) wmapdata[v.m6].data.push_back(lwWeightMapPair(i, v.w6/65535.0f));
        if(v.m7 != INVALID_VERTEX_WMAP_INDEX) wmapdata[v.m7].data.push_back(lwWeightMapPair(i, v.w7/65535.0f));
        if(v.m8 != INVALID_VERTEX_WMAP_INDEX) wmapdata[v.m8].data.push_back(lwWeightMapPair(i, v.w8/65535.0f));
       }
   }
 else if(data.model->vbuffer.flags & VERTEX_BONEIDX)
   {
    // create weight maps
    uint16 max_index = 0;
    for(uint32 i = 0; i < data.model->vbuffer.elem; i++) {
        const VERTEX& v = data.model->vbuffer.data[i];
        if(v.b1 != INVALID_VERTEX_BONE_INDEX && !(v.b1 < max_index)) max_index = v.b1;
        if(v.b2 != INVALID_VERTEX_BONE_INDEX && !(v.b2 < max_index)) max_index = v.b2;
        if(v.b3 != INVALID_VERTEX_BONE_INDEX && !(v.b3 < max_index)) max_index = v.b3;
        if(v.b4 != INVALID_VERTEX_BONE_INDEX && !(v.b4 < max_index)) max_index = v.b4;
        if(v.b5 != INVALID_VERTEX_BONE_INDEX && !(v.b5 < max_index)) max_index = v.b5;
        if(v.b6 != INVALID_VERTEX_BONE_INDEX && !(v.b6 < max_index)) max_index = v.b6;
        if(v.b7 != INVALID_VERTEX_BONE_INDEX && !(v.b7 < max_index)) max_index = v.b7;
        if(v.b8 != INVALID_VERTEX_BONE_INDEX && !(v.b8 < max_index)) max_index = v.b8;
       }
    n_wmaps = max_index + 1;
    wmapdata.resize(n_wmaps);
    std::cout << " n_wmaps = " << n_wmaps << std::endl;

    // TODO
   }
 else
   {
    // assume there is only one weight map
    n_wmaps = 1;
    wmapdata.resize(n_wmaps);
    std::cout << " n_wmaps = " << n_wmaps << std::endl;

    // TODO
   }

 return true;
}

bool lwConverter::convert(const char* path, const char* name)
{
 // preprocessing
 if(!sanityChecker()) return false;
 if(!constructWeightMaps()) return false;

 // create file
 std::stringstream ss;
 ss << path << name << ".lwo";
 ofile.open(ss.str().c_str(), std::ios::binary);
 if(!ofile) return error("Error creating LWO file.");

 // compute 1st layer chunksizes
 computeLAYRChunkSize();
 computeTAGSChunkSize();
 computePNTSChunkSize();
 computePOLSChunkSize();
 computePTAGChunkSize();
 computeTXUVChunkSize();
 computeWGHTChunkSize();

 // compute 2nd layer chunksizes
 computeBONELAYRChunkSize();
 computeBONETAGSChunkSize();
 computeBONEPNTSChunkSize();
 computeBONEPOLSChunkSize();
 computeBONEPTAGChunkSize();
 computeBONEPARTChunkSize();

 // compute form chunksize
 computeFORMChunkSize();

 // save 1st layer chunks
 if(!saveFORM()) return false;
 if(!saveLAYR()) return false;
 if(!saveTAGS()) return false;
 if(!savePNTS()) return false;
 if(!savePOLS()) return false;
 if(!savePTAG()) return false;
 if(!saveTXUV()) return false;
 if(!saveWGHT()) return false;

 // save 2nd layer chunks
 if(!saveBONELAYR()) return false;
 if(!saveBONETAGS()) return false;
 if(!saveBONEPNTS()) return false;
 if(!saveBONEPOLS()) return false;
 if(!saveBONEPTAG()) return false;
 if(!saveBONEPART()) return false;

 return true;
}

//
// FORM
//

void lwConverter::computeFORMChunkSize(void)
{
 FORM_chunksize = 0;
 FORM_chunksize += 0x04; // LWO2

 // TAGS contribution
 if(TAGS_chunksize) {
    FORM_chunksize += 0x08;
    FORM_chunksize += TAGS_chunksize;
   }

 // LAYR contribution
 FORM_chunksize += 0x08;
 FORM_chunksize += LAYR_chunksize;

 // PNTS contribution
 if(PNTS_chunksize) {
    FORM_chunksize += 0x08;
    FORM_chunksize += PNTS_chunksize;
   }

 // POLS contribution
 if(POLS_chunksize) {
    FORM_chunksize += 0x08;
    FORM_chunksize += POLS_chunksize;
   }

 // PTAG contribution
 if(PTAG_chunksize) {
    FORM_chunksize += 0x08;
    FORM_chunksize += PTAG_chunksize;
   }

 // TXUV contribution
 if(TXUV_chunksize) {
    uint32 n_surface = (uint32)data.model->ibuffer.size();
    for(uint32 i = 0; i < n_surface; i++) {
        FORM_chunksize += 0x08;
        FORM_chunksize += TXUV_chunksize[i];
       }
   }

 // WGHT contribution
 if(WGHT_chunksize) {
    for(uint32 i = 0; i < n_wmaps; i++) {
        if(WGHT_chunksize[i]) {
           FORM_chunksize += 0x08;
           FORM_chunksize += WGHT_chunksize[i];
          }
       }
   }

 // LAYR (from bones) contribution
 if(BONELAYR_chunksize) {
    FORM_chunksize += 0x08;
    FORM_chunksize += BONELAYR_chunksize;
   }

 // TAGS (from bones) contribution
 if(BONETAGS_chunksize) {
    FORM_chunksize += 0x08;
    FORM_chunksize += BONETAGS_chunksize;
   }

 // PNTS (from bones) contribution
 if(BONEPNTS_chunksize) {
    FORM_chunksize += 0x08;
    FORM_chunksize += BONEPNTS_chunksize;
   }

 // POLS (from bones) contribution
 if(BONEPOLS_chunksize) {
    FORM_chunksize += 0x08;
    FORM_chunksize += BONEPOLS_chunksize;
   }

 // PTAG (from bones) contribution
 if(BONEPTAG_chunksize) {
    FORM_chunksize += 0x08;
    FORM_chunksize += BONEPTAG_chunksize;
   }

 // PTAG (from bones) contribution
 if(BONEPART_chunksize) {
    FORM_chunksize += 0x08;
    FORM_chunksize += BONEPART_chunksize;
   }
}

bool lwConverter::saveFORM(void)
{
 // FORM + FORM_chunksize + LWO2
 BE_write_uint32(ofile, LWO_FORM);
 BE_write_uint32(ofile, FORM_chunksize);
 BE_write_uint32(ofile, LWO_LWO2);
 return true;
}

void lwConverter::computeLAYRChunkSize(void)
{
 LAYR_chunksize = 0x12;
}

bool lwConverter::saveLAYR(void)
{
 // save LAYR + LAYR_chunksize
 BE_write_uint32(ofile, LWO_LAYR);
 BE_write_uint32(ofile, LAYR_chunksize);

 // save LAYR data
 BE_write_uint16(ofile, 0); // number
 BE_write_uint16(ofile, 0); // flags
 BE_write_real32(ofile, 0); // pivot
 BE_write_real32(ofile, 0); // ...
 BE_write_real32(ofile, 0); // ...
 BE_write_uint08(ofile, 0); // name
 BE_write_uint08(ofile, 0); // ...

 return true;
}

void lwConverter::computeTAGSChunkSize(void)
{
 // compute chunksize
 TAGS_chunksize = 0;
 for(size_t i = 0; i < data.model->ibuffer.size(); i++) {
     IDX_BUFFER& ib = data.model->ibuffer[i];
     TAGS_chunksize += align02((ib.name.length() + 1));
    }
}

bool lwConverter::saveTAGS(void)
{
 // don't save chunk if there are no TAGS
 if(!TAGS_chunksize) return true;

 // save TAGS + TAGS_chunksize
 BE_write_uint32(ofile, LWO_TAGS);
 BE_write_uint32(ofile, TAGS_chunksize);

 // save surface names
 for(size_t i = 0; i < data.model->ibuffer.size(); i++) {
     IDX_BUFFER& ib = data.model->ibuffer[i];
     write_aligned_string_02(ofile, ib.name.c_str());
    }

 return true;
}

void lwConverter::computePNTSChunkSize(void)
{
 // compute chunksize
 uint32 n_points = data.model->vbuffer.elem;
 PNTS_chunksize = 0x0C*n_points;
}

bool lwConverter::savePNTS(void)
{
 // don't save chunk if there are no PNTS
 if(!PNTS_chunksize) return true;

 // save PNTS + PNTS_chunksize
 BE_write_uint32(ofile, LWO_PNTS);
 BE_write_uint32(ofile, PNTS_chunksize);

 // save points
 for(uint32 i = 0; i < data.model->vbuffer.elem; i++) {
     float x = data.model->vbuffer.data[i].vx;
     float y = data.model->vbuffer.data[i].vy;
     float z = data.model->vbuffer.data[i].vz;
     BE_write_real32(ofile, x);
     BE_write_real32(ofile, y);
     BE_write_real32(ofile, z);
    }

 return true;
}

//
// POLS
//

template<class T>
uint32 computeHelperPOLS(const IDX_BUFFER& ib)
{
 uint32 datasize = 0;

 if(ib.type == FACE_TYPE_TRIANGLES)
   {
    // for number of vertices and flags
    uint32 n_triangles = ib.elem/3;
    datasize += 2*n_triangles;

    // for indices
    const T limit = (T)0xFF00;
    const T* buffer = (const T*)(ib.data.get());
    for(uint32 j = 0; j < ib.elem; j++) {
        if(buffer[j] < limit) datasize += 2;
        else datasize += 4;
       }
   }
 else if(ib.type == FACE_TYPE_TRISTRIP)
   {
   }
 else if(ib.type == FACE_TYPE_TRISTRIPCUT)
   {
   }

 return datasize;
}

template<class T>
bool saveHelperPOLS(const IDX_BUFFER& ib, std::ofstream& ofile)
{
 if(ib.type == FACE_TYPE_TRIANGLES)
   {
    uint32 index = 0;
    uint32 n_triangles = ib.elem/3;
    const T* buffer = reinterpret_cast<const T*>(ib.data.get());
    for(uint32 j = 0; j < n_triangles; j++) {
        BE_write_uint16(ofile, 0x0003);     // number of vertices + flags
        uint32 a = (uint32)buffer[index++]; // vertex
        uint32 b = (uint32)buffer[index++]; // vertex
        uint32 c = (uint32)buffer[index++]; // vertex
        if(a < 0xFF00) BE_write_uint16(ofile, (uint16)a); else BE_write_uint32(ofile, lwConvertToVX(a));
        if(b < 0xFF00) BE_write_uint16(ofile, (uint16)b); else BE_write_uint32(ofile, lwConvertToVX(b));
        if(c < 0xFF00) BE_write_uint16(ofile, (uint16)c); else BE_write_uint32(ofile, lwConvertToVX(c));
       }
   }
 else if(ib.type == FACE_TYPE_TRISTRIP)
   {
   }
 else if(ib.type == FACE_TYPE_TRISTRIPCUT)
   {
   }

 return true;
}

void lwConverter::computePOLSChunkSize(void)
{
 // compute chunksize
 POLS_chunksize = 0;
 if(data.model->ibuffer.size()) {
    POLS_chunksize += 0x04; // FACE
    for(uint32 i = 0; i < data.model->ibuffer.size(); i++) {
        // for indices
        IDX_BUFFER& ib = data.model->ibuffer[i];
        if(ib.format == FACE_FORMAT_UINT_32) POLS_chunksize += computeHelperPOLS<const uint32>(ib);
        else if(ib.format == FACE_FORMAT_UINT_16) POLS_chunksize += computeHelperPOLS<const uint16>(ib);
        else if(ib.format == FACE_FORMAT_UINT_08) POLS_chunksize += computeHelperPOLS<const uint08>(ib);
       }
   }
}

bool lwConverter::savePOLS(void)
{
 // don't save chunk if there are no POLS
 if(!POLS_chunksize) return true;

 // save PNTS + PNTS_chunksize + FACE
 BE_write_uint32(ofile, LWO_POLS);
 BE_write_uint32(ofile, POLS_chunksize);
 BE_write_uint32(ofile, LWO_FACE);

 // save polygons
 for(uint32 i = 0; i < data.model->ibuffer.size(); i++) {
     IDX_BUFFER& ib = data.model->ibuffer[i];
     if(ib.format == FACE_FORMAT_UINT_32) saveHelperPOLS<const uint32>(ib, ofile);
     if(ib.format == FACE_FORMAT_UINT_16) saveHelperPOLS<const uint16>(ib, ofile);
     if(ib.format == FACE_FORMAT_UINT_08) saveHelperPOLS<const uint08>(ib, ofile);
    }

 return true;
}

//
// PTAG:SURF
//

void lwConverter::computePTAGChunkSize(void)
{
 // compute chunksize
 PTAG_chunksize = 0;
 if(data.model->ibuffer.size())
   {
    PTAG_chunksize += 0x04; // SURF
    for(uint32 i = 0; i < (uint32)data.model->ibuffer.size(); i++)
       {
        // compute size of VX + tag
        IDX_BUFFER& ib = data.model->ibuffer[i];
        if(ib.tris < 0xFF00) {
           PTAG_chunksize += ib.tris*2; // VX
           PTAG_chunksize += ib.tris*2; // tag
          }
        else {
           PTAG_chunksize += 0xFF00*2; // VX
           PTAG_chunksize += (ib.tris - 0xFF00)*4; // VX
           PTAG_chunksize += ib.tris*2; // tag
          }
       }
   }
}

bool lwConverter::savePTAG(void)
{
 // don't save chunk if there are no TAGS
 if(!PTAG_chunksize) return true;

 // save PTAG + PTAG_chunksize + SURF
 BE_write_uint32(ofile, LWO_PTAG);
 BE_write_uint32(ofile, PTAG_chunksize);
 BE_write_uint32(ofile, LWO_SURF);

 // save VX + tags
 uint32 total_triangles = 0;
 for(uint32 i = 0; i < (uint32)data.model->ibuffer.size(); i++)
    {
     // get index buffer
     IDX_BUFFER& ib = data.model->ibuffer[i];
     if(!ib.elem) continue;

     // for each triangle
     for(uint32 j = 0; j < ib.tris; j++)
        {
         // save VX
         uint32 v1 = total_triangles + j;
         if(v1 < 0xFF00) BE_write_uint16(ofile, (uint16)v1);
         else BE_write_uint32(ofile, lwConvertToVX(v1));
    
         // save tag
         uint16 v2 = (uint16)i;
         BE_write_uint16(ofile, v2);
        }

     // update triangles seen
     total_triangles += ib.tris;
    }

 return true;
}

//
// VMAP:TXUV
//

void lwConverter::computeTXUVChunkSize(void)
{
 // must have surfaces
 uint32 n_surface = (uint32)data.model->ibuffer.size();
 if(!n_surface) return;

 // create chunksize array
 TXUV_chunksize.reset(new uint32[n_surface]);
 for(uint32 i = 0; i < n_surface; i++) TXUV_chunksize[i] = 0;

 // for each surface
 for(uint32 i = 0; i < n_surface; i++)
    {
     // get index buffer
     IDX_BUFFER& ib = data.model->ibuffer[i];
     if(!ib.elem) continue;

     // build a set of vertex indices used in surface
     std::set<uint32> iset;
     if(ib.format == FACE_FORMAT_UINT_32) {
        const uint32* ptr = reinterpret_cast<const uint32*>(ib.data.get());
        for(uint32 j = 0; j < ib.elem; j++) iset.insert((uint32)(ptr[j]));
       }
     else if(ib.format == FACE_FORMAT_UINT_16) {
        const uint16* ptr = reinterpret_cast<const uint16*>(ib.data.get());
        for(uint32 j = 0; j < ib.elem; j++) iset.insert((uint32)(ptr[j]));
       }
     else if(ib.format == FACE_FORMAT_UINT_08) {
        const uint08* ptr = reinterpret_cast<const uint08*>(ib.data.get());
        for(uint32 j = 0; j < ib.elem; j++) iset.insert((uint32)(ptr[j]));
       }

     // TXUV + dimension + name
     TXUV_chunksize[i] += 0x04;
     TXUV_chunksize[i] += 0x02;
     TXUV_chunksize[i] += align02(ib.name.length() + 1);

     // VX + data
     for(std::set<uint32>::iterator ptr = iset.begin(); ptr != iset.end(); ptr++) {
         if((*ptr) < 0xFF00) TXUV_chunksize[i] += 0x02;
         else TXUV_chunksize[i] += 0x04;
         TXUV_chunksize[i] += 0x08;
        }
    }
}

bool lwConverter::saveTXUV(void)
{
 // don't save chunk if there are no TXUV
 if(!TXUV_chunksize) return true;

 // must have vertex buffer
 const VTX_BUFFER& vb = data.model->vbuffer;
 if(!vb.elem) return true;

 // save UV maps
 uint32 n_surface = (uint32)data.model->ibuffer.size();
 for(size_t i = 0; i < n_surface; i++)
    {
     // get index buffer
     IDX_BUFFER& ib = data.model->ibuffer[i];
     if(TXUV_chunksize[i] == 0) continue;

     // build a set of vertex indices used in surface
     std::set<uint32> iset;
     if(ib.format == FACE_FORMAT_UINT_32) {
        const uint32* ptr = reinterpret_cast<const uint32*>(ib.data.get());
        for(uint32 j = 0; j < ib.elem; j++) iset.insert((uint32)(ptr[j]));
       }
     else if(ib.format == FACE_FORMAT_UINT_16) {
        const uint16* ptr = reinterpret_cast<const uint16*>(ib.data.get());
        for(uint32 j = 0; j < ib.elem; j++) iset.insert((uint32)(ptr[j]));
       }
     else if(ib.format == FACE_FORMAT_UINT_08) {
        const uint08* ptr = reinterpret_cast<const uint08*>(ib.data.get());
        for(uint32 j = 0; j < ib.elem; j++) iset.insert((uint32)(ptr[j]));
       }

     // save chunkname, chunksize
     BE_write_uint32(ofile, LWO_VMAP);
     BE_write_uint32(ofile, TXUV_chunksize[i]);

     // save type and dimension
     BE_write_uint32(ofile, LWO_TXUV);
     BE_write_uint16(ofile, 2);

     // save name
     write_aligned_string_02(ofile, ib.name.c_str());

     // save UV map
     for(std::set<uint32>::iterator ptr = iset.begin(); ptr != iset.end(); ptr++) {
         uint32 index = *ptr;
         if(index < 0xFF00) BE_write_uint16(ofile, (uint16)index);
         else BE_write_uint32(ofile, lwConvertToVX(index));
         BE_write_real32(ofile, vb.data[index].tu);
         BE_write_real32(ofile, vb.data[index].tv);
        }
    }

 return true;
}

//
// VMAP:WGHT
//

void lwConverter::computeWGHTChunkSize(void)
{
 // nothing to compute
 if(!n_wmaps) return;

 // create chunksize array
 WGHT_chunksize.reset(new uint32[n_wmaps]);
 for(uint32 i = 0; i < n_wmaps; i++) WGHT_chunksize[i] = 0;

 // for each weight map
 for(uint32 i = 0; i < n_wmaps; i++)
    {
     // skip, nothing in weight map
     const lwWeightMap& wm = wmapdata[i];
     if(!wm.data.size()) continue;

     // WGHT + dimension + name
     WGHT_chunksize[i] += 0x04;
     WGHT_chunksize[i] += 0x02;
     WGHT_chunksize[i] += align02(wm.name.length() + 1);

     // VX + data
     for(uint32 j = 0; j < wm.data.size(); j++) {
         if(wm.data[j].index < 0xFF00) WGHT_chunksize[i] += 0x02;
         else WGHT_chunksize[i] += 0x04;
         WGHT_chunksize[i] += 0x04;
        }
    }
}

bool lwConverter::saveWGHT(void)
{
 // don't save chunk if there are no WGHT
 if(!WGHT_chunksize) return true;

 // save WGHT maps
 for(size_t i = 0; i < n_wmaps; i++)
    {
     // skip, nothing in weight map
     const lwWeightMap& wm = wmapdata[i];
     if(!wm.data.size()) continue;

     // save chunkname, chunksize
     BE_write_uint32(ofile, LWO_VMAP);
     BE_write_uint32(ofile, WGHT_chunksize[i]);

     // save type and dimension
     BE_write_uint32(ofile, LWO_WGHT);
     BE_write_uint16(ofile, 1);

     // save name
     write_aligned_string_02(ofile, wm.name.c_str());

     // save VX + data
     for(uint32 j = 0; j < wm.data.size(); j++) {
         uint32 index = wm.data[j].index;
         real32 value = wm.data[j].value;
         if(index < 0xFF00) BE_write_uint16(ofile, (uint16)index);
         else BE_write_uint32(ofile, lwConvertToVX(index));
         BE_write_real32(ofile, value);
        }
    }

 return true;
}

//
// LAYR (BONES)
//

void lwConverter::computeBONELAYRChunkSize(void)
{
 // compute chunksize
 BONELAYR_chunksize = 0;
 if(data.skeletons.size()) BONELAYR_chunksize = 0x12;
}

bool lwConverter::saveBONELAYR(void)
{
 // don't save chunk if there is no skeleton
 if(!BONELAYR_chunksize) return true;

 // save LAYR + LAYR_chunksize
 BE_write_uint32(ofile, LWO_LAYR);
 BE_write_uint32(ofile, BONELAYR_chunksize);

 // save LAYR data
 BE_write_uint16(ofile, 1); // number
 BE_write_uint16(ofile, 0); // flags
 BE_write_real32(ofile, 0); // pivot
 BE_write_real32(ofile, 0); // ...
 BE_write_real32(ofile, 0); // ...
 BE_write_uint08(ofile, 0); // name
 BE_write_uint08(ofile, 0); // ...

 return true;
}

//
// TAGS (BONES)
//

void lwConverter::computeBONETAGSChunkSize(void)
{
 // compute chunksize
 BONETAGS_chunksize = 0;
 for(size_t i = 0; i < data.skeletons.size(); i++) {
     const SKELETON& skel = data.skeletons[i];
     lwJointPolygonTagsChunkSizeCalc lwinfo(ofile);
     skel.traverse(lwinfo);
     BONETAGS_chunksize += align02(strlen(skel.get_identifier()) + 1); // surface name
     BONETAGS_chunksize += lwinfo.getChunkSize(); // bone names
    }
}

bool lwConverter::saveBONETAGS(void)
{
 // don't save chunk if there are no TAGS
 if(!BONETAGS_chunksize) return true;

 // save TAGS + TAGS_chunksize
 BE_write_uint32(ofile, LWO_TAGS);
 BE_write_uint32(ofile, BONETAGS_chunksize);

 // save surface and bone names
 for(size_t i = 0; i < data.skeletons.size(); i++) {
     const SKELETON& skel = data.skeletons[i];
     write_aligned_string_02(ofile, skel.get_identifier()); // surface name
     lwJointPolygonTagsSaver lwinfo(ofile); // bone names
     skel.traverse(lwinfo);
    }

 return true;
}

//
// PNTS (BONES)
//

void lwConverter::computeBONEPNTSChunkSize(void)
{
 // compute chunksize
 BONEPNTS_chunksize = 0;
 uint32 n_skeletons = data.skeletons.size();
 for(uint32 i = 0; i < n_skeletons; i++) BONEPNTS_chunksize += 0x0C*data.skeletons[i].joints();
}

bool lwConverter::saveBONEPNTS(void)
{
 // don't save chunk if there is no skeleton
 if(!BONEPNTS_chunksize) return true;

 // save PNTS + PNTS_chunksize
 BE_write_uint32(ofile, LWO_PNTS);
 BE_write_uint32(ofile, BONEPNTS_chunksize);

 // save joint positions
 uint32 n_skeletons = data.skeletons.size();
 for(uint32 i = 0; i < n_skeletons; i++) {
     const SKELETON& skel = data.skeletons[i];
     lwJointPointSaver lwjs(ofile);
     skel.traverse(lwjs);
    }

 return true;
}

//
// POLS (BONES)
//

void lwConverter::computeBONEPOLSChunkSize(void)
{
 // compute chunksize
 BONEPOLS_chunksize = 0;
 for(uint32 i = 0; i < data.skeletons.size(); i++) {
     BONEPOLS_chunksize += 0x04; // BONE
     const SKELETON& skel = data.skeletons[i];
     lwJointPolygonChunkSizeCalc lwinfo(ofile);
     skel.traverse(lwinfo);
     BONEPOLS_chunksize += lwinfo.getChunkSize();
    }
}

bool lwConverter::saveBONEPOLS(void)
{
 // don't save chunk if there is no skeleton
 if(!BONEPOLS_chunksize) return true;

 // save POLS + POLS_chunksize
 BE_write_uint32(ofile, LWO_POLS);
 BE_write_uint32(ofile, BONEPOLS_chunksize);
 BE_write_uint32(ofile, LWO_BONE);

 // save joint polygons
 uint32 n_skeletons = data.skeletons.size();
 for(uint32 i = 0; i < n_skeletons; i++) {
     const SKELETON& skel = data.skeletons[i];
     lwJointPolygonSaver lwinfo(ofile);
     skel.traverse(lwinfo);
    }

 return true;
}

//
// PTAG:BONE (BONES)
//

void lwConverter::computeBONEPTAGChunkSize(void)
{
 // compute chunksize
 BONEPTAG_chunksize = 0;
 if(!data.skeletons.size()) return;

 // BONE
 uint32 n_skeletons = data.skeletons.size();
 BONEPTAG_chunksize += 0x04;

 // VX + tags
 for(uint32 i = 0; i < n_skeletons; i++) {
     const SKELETON& skel = data.skeletons[i];
     lwJointPolyTagChunkSizeCalc lwinfo(ofile);
     skel.traverse(lwinfo);
     BONEPTAG_chunksize += lwinfo.getChunkSize();
    }
}

bool lwConverter::saveBONEPTAG(void)
{
 // don't save chunk if there is no skeleton
 if(!BONEPTAG_chunksize) return true;

 // save PTAG + PTAG_chunksize
 BE_write_uint32(ofile, LWO_PTAG);
 BE_write_uint32(ofile, BONEPTAG_chunksize);
 BE_write_uint32(ofile, LWO_BONE);

 // save joint polygon tags
 uint32 n_skeletons = data.skeletons.size();
 for(uint32 i = 0; i < n_skeletons; i++) {
     const SKELETON& skel = data.skeletons[i];
     lwJointPolyTagSaver lwinfo(ofile);
     skel.traverse(lwinfo);
    }

 return true;
}

//
// PTAG:PART (BONES)
//

void lwConverter::computeBONEPARTChunkSize(void)
{
 // compute chunksize
 BONEPART_chunksize = 0;
 if(!data.skeletons.size()) return;

 // BONE
 uint32 n_skeletons = data.skeletons.size();
 BONEPART_chunksize += 0x04;

 // VX + tags
 for(uint32 i = 0; i < n_skeletons; i++) {
     const SKELETON& skel = data.skeletons[i];
     lwJointPartTagChunkSizeCalc lwinfo(ofile);
     skel.traverse(lwinfo);
     BONEPART_chunksize += lwinfo.getChunkSize();
    }
}

bool lwConverter::saveBONEPART(void)
{
 // don't save chunk if there is no skeleton
 if(!BONEPART_chunksize) return true;

 // save PTAG + PTAG_chunksize
 BE_write_uint32(ofile, LWO_PTAG);
 BE_write_uint32(ofile, BONEPART_chunksize);
 BE_write_uint32(ofile, LWO_PART);

 // save joint polygon tags
 uint32 n_skeletons = data.skeletons.size();
 for(uint32 i = 0; i < n_skeletons; i++) {
     const SKELETON& skel = data.skeletons[i];
     lwJointPartTagSaver lwinfo(ofile);
     skel.traverse(lwinfo);
    }

 return true;
}







uint32 lwGetCLIPContribution(const boost::shared_array<uint32>& chunksize, uint32 n);
boost::shared_array<uint32> lwGetCLIPChunkSize(const SIMPLEMODELCONTAINER& data);
bool lwSaveCLIP(std::ofstream& ofile, const SIMPLEMODELCONTAINER& data, const boost::shared_array<uint32>& chunksize);

boost::shared_array<uint32> lwGetSURFChunkSize(const SIMPLEMODELCONTAINER& data);
bool lwSaveSURF(std::ofstream& ofile, const SIMPLEMODELCONTAINER& data, const boost::shared_array<uint32>& chunksize);

bool lwSaveVmapTxuv(std::ofstream& ofile, const SIMPLEMODELCONTAINER& data, const boost::shared_array<uint32>& TXUV_chunksize);

bool GeometryToLWO(const char* path, const char* name, const SIMPLEMODELCONTAINER& data)
{
 lwConverter lwc(data);
 return lwc.convert(path, name);
}

//
// LWO_CLIP
//

uint32 lwGetCLIPContribution(const boost::shared_array<uint32>& chunksize, uint32 n)
{
 uint32 retval = 0;
 for(uint32 i = 0; i < n; i++) {
     retval += 0x04;         // CLIP
     retval += 0x04;         // chunksize
     retval += chunksize[i]; // chunkdata
    }
 return retval;
}

boost::shared_array<uint32> lwGetCLIPChunkSize(const SIMPLEMODELCONTAINER& data)
{
 // must have textures
 boost::shared_array<uint32> retval;
 if(data.textures.size() == 0) return retval;

 // compute chunksizes
 retval.reset(new uint32[data.textures.size()]);
 for(uint32 i = 0; i < data.textures.size(); i++)
    {
     // get texture
     const TEXTURE& tex = data.textures[i];
     retval[i] = 0;

     // index
     retval[i] += 0x04;

     // STIL
     retval[i] += 0x04;

     // STIL chunksize
     retval[i] += 0x02;

     // filename
     uint32 namelen = tex.filename.length() + 1;
     retval[i] += align02(namelen);

     // FLAG, chunksize, flags
     retval[i] += 0x0A;
    }

 return retval;
}

bool lwSaveCLIP(std::ofstream& ofile, const SIMPLEMODELCONTAINER& data, const boost::shared_array<uint32>& chunksize)
{
 // for each texture
 for(uint32 i = 0; i < (uint32)data.textures.size(); i++)
    {
     // get texture
     const TEXTURE& tex = data.textures[i];

     // save chunkname, chunksize
     BE_write_uint32(ofile, LWO_CLIP);
     BE_write_uint32(ofile, chunksize[i]);

     // save index
     uint32 clipindex = (i + 1);
     BE_write_uint32(ofile, clipindex);

     // save STIL
     BE_write_uint32(ofile, LWO_STIL);

     // save STIL chunksize
     uint16 namelen = (uint16)(tex.filename.length() + 1);
     namelen = align02(namelen);
     BE_write_uint16(ofile, namelen);

     // save filename
     write_aligned_string_02(ofile, tex.filename.c_str());

     // save FLAG
     BE_write_uint32(ofile, LWO_FLAG);
     BE_write_uint16(ofile, 0x0004);
     BE_write_uint16(ofile, 0x0800);
     BE_write_uint16(ofile, 0x0080);
    }

 return true;
}

//
// LWO_SURF
//

boost::shared_array<uint32> lwGetSURFChunkSize(const SIMPLEMODELCONTAINER& data)
{
 // must have index buffer
 boost::shared_array<uint32> retval;
 if(data.model->ibuffer.size() == 0) return retval;

 uint32 n_surface = (uint32)data.model->ibuffer.size();
 retval.reset(new uint32[n_surface]);

 // for each surface
 for(uint32 i = 0; i < n_surface; i++)
    {
     // get index buffer
     IDX_BUFFER& ib = data.model->ibuffer[i];
     if(!ib.elem) continue;

     // initialize chunksize
     retval[i] = 0;

     // name
     uint32 namelen = ib.name.length() + 1;
     retval[i] += align02(namelen);

     // source
     retval[i] += 2;

     // color, diffuse, specular
     retval[i] += 0x14;
     retval[i] += 0x0C;
     retval[i] += 0x0C;

     // materials
     if((ib.material != 0xFFFFFFFF) && (ib.material < data.materials.size()))
       {
        // base color map
        const MATERIAL& mat = data.materials[ib.material];
        if((mat.basemap != 0xFFFFFFFF) && (mat.basemap < data.textures.size()))
          {
           retval[i] += 0x06; // BLOK, chunksize
           retval[i] += 0x08; // IMAP, chunksize
           retval[i] += 0x0A; // CHAN, chunksize, channel
           retval[i] += 0x0E; // OPAC, chunksize, type, opacity, envelope
           retval[i] += 0x08; // ENAB, chunksize, bool
           retval[i] += 0x08; // NEGA, chunksize, bool
           retval[i] += 0x06; // TMAP, chunksize
           retval[i] += 0x14; // CNTR, chunksize, x, y, z, envelope
           retval[i] += 0x14; // SIZE, chunksize, x, y, z, envelope
           retval[i] += 0x14; // ROTA, chunksize, x, y, z, envelope
           retval[i] += 0x16; // FALL, chunksize, type, x, y, z, envelope
           retval[i] += 0x08; // PROJ, chunksize, mode
           retval[i] += 0x08; // OREF, chunksize, name
           retval[i] += 0x08; // CSYS, chunksize, type
           retval[i] += 0x08; // AXIS, chunksize, axis
           retval[i] += 0x08; // IMAG, chunksize, clip
           retval[i] += 0x0A; // WRAP, chunksize, wrap, wrap
           retval[i] += 0x0C; // WRPW, chunksize, cycles, envelope
           retval[i] += 0x0C; // WRPW, chunksize, cycles, envelope
           retval[i] += 0x06; // VMAP, chunksize, string
           retval[i] += align02(ib.name.length() + 1);
           retval[i] += 0x0C; // AAST, chunksize, flags, strength
           retval[i] += 0x08; // PIXB, chunksize, flags
          }

        // specular map
        if((mat.specmap != 0xFFFFFFFF) && (mat.specmap < data.textures.size()))
          {
           retval[i] += 0x06; // BLOK, chunksize
           retval[i] += 0x08; // IMAP, chunksize
           retval[i] += 0x0A; // CHAN, chunksize, channel
           retval[i] += 0x0E; // OPAC, chunksize, type, opacity, envelope
           retval[i] += 0x08; // ENAB, chunksize, bool
           retval[i] += 0x08; // NEGA, chunksize, bool
           retval[i] += 0x06; // TMAP, chunksize
           retval[i] += 0x14; // CNTR, chunksize, x, y, z, envelope
           retval[i] += 0x14; // SIZE, chunksize, x, y, z, envelope
           retval[i] += 0x14; // ROTA, chunksize, x, y, z, envelope
           retval[i] += 0x16; // FALL, chunksize, type, x, y, z, envelope
           retval[i] += 0x08; // OREF, chunksize, name
           retval[i] += 0x08; // CSYS, chunksize, type
           retval[i] += 0x08; // PROJ, chunksize, mode
           retval[i] += 0x08; // AXIS, chunksize, axis
           retval[i] += 0x08; // IMAG, chunksize, clip
           retval[i] += 0x0A; // WRAP, chunksize, wrap, wrap
           retval[i] += 0x0C; // WRPW, chunksize, cycles, envelope
           retval[i] += 0x0C; // WRPW, chunksize, cycles, envelope
           retval[i] += 0x06; // VMAP, chunksize, string
           retval[i] += align02(ib.name.length() + 1);
           retval[i] += 0x0C; // AAST, chunksize, flags, strength
           retval[i] += 0x08; // PIXB, chunksize, flags
          }

        // TODO
        // normal map
        if((mat.normmap != 0xFFFFFFFF) && (mat.normmap < data.textures.size()))
          {
          }
       }
    }

 return retval;
}

bool lwSaveSURF(std::ofstream& ofile, const SIMPLEMODELCONTAINER& data, const boost::shared_array<uint32>& chunksize)
{
 // must have index buffer
 if(data.model->ibuffer.size() == 0)
    return true;

 // surface names
 for(size_t i = 0; i < data.model->ibuffer.size(); i++)
    {
     // get index buffer
     IDX_BUFFER& ib = data.model->ibuffer[i];
     if(!ib.elem) continue;

     // save chunkname, chunksize
     BE_write_uint32(ofile, LWO_SURF);
     BE_write_uint32(ofile, chunksize[i]);

     // save name
     write_aligned_string_02(ofile, ib.name.c_str());

     // save source
     BE_write_uint08(ofile, 0);
     BE_write_uint08(ofile, 0);

     // save color
     BE_write_uint32(ofile, LWO_COLR);
     BE_write_uint16(ofile, 0x0E);
     BE_write_uint32(ofile, 0x3F48C8C9);
     BE_write_uint32(ofile, 0x3F48C8C9);
     BE_write_uint32(ofile, 0x3F48C8C9);
     BE_write_uint16(ofile, 0x00);

     // save diffuse
     BE_write_uint32(ofile, LWO_DIFF);
     BE_write_uint16(ofile, 0x06);
     BE_write_uint32(ofile, 0x3F800000);
     BE_write_uint16(ofile, 0x00);

     // save specular
     BE_write_uint32(ofile, LWO_SPEC);
     BE_write_uint16(ofile, 0x06);
     BE_write_uint32(ofile, 0x00000000);
     BE_write_uint16(ofile, 0x00);

     // materials
     if((ib.material != 0xFFFFFFFF) && (ib.material < data.materials.size()))
       {
        // base color map
        const MATERIAL& mat = data.materials[ib.material];
        if((mat.basemap != 0xFFFFFFFF) && (mat.basemap < data.textures.size()))
          {
           // BLCK
           uint16 BLOK_chunksize = 0xEA + (uint16)align02(ib.name.length() + 1);
           BE_write_uint32(ofile, LWO_BLOK);
           BE_write_uint16(ofile, BLOK_chunksize);

           // IMAP
           uint16 IMAP_chunksize = 0x2A;
           BE_write_uint32(ofile, LWO_IMAP);
           BE_write_uint16(ofile, IMAP_chunksize);
           BE_write_uint16(ofile, 0x8000); // ordinal

           // CHAN
           uint16 CHAN_chunksize = 0x04;
           BE_write_uint32(ofile, LWO_CHAN);
           BE_write_uint16(ofile, CHAN_chunksize);
           BE_write_uint32(ofile, LWO_COLR);

           // OPAC
           uint16 OPAC_chunksize = 0x08;
           BE_write_uint32(ofile, LWO_OPAC);
           BE_write_uint16(ofile, OPAC_chunksize);
           BE_write_uint16(ofile, 0x00);
           BE_write_uint32(ofile, 0x3F800000);
           BE_write_uint16(ofile, 0x00);

           // ENAB
           uint16 ENAB_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_ENAB);
           BE_write_uint16(ofile, ENAB_chunksize);
           BE_write_uint16(ofile, 0x01);

           // NEGA
           uint16 NEGA_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_NEGA);
           BE_write_uint16(ofile, NEGA_chunksize);
           BE_write_uint16(ofile, 0x00);

           // TMAP
           uint16 TMAP_chunksize = 0x5A;
           BE_write_uint32(ofile, LWO_TMAP);
           BE_write_uint16(ofile, TMAP_chunksize);

           // CNTR
           uint16 CNTR_chunksize = 0x0E;
           BE_write_uint32(ofile, LWO_CNTR);
           BE_write_uint16(ofile, CNTR_chunksize);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint16(ofile, 0x00);

           // SIZE
           uint16 SIZE_chunksize = 0x0E;
           BE_write_uint32(ofile, LWO_SIZE);
           BE_write_uint16(ofile, SIZE_chunksize);
           BE_write_uint32(ofile, 0x3F800000);
           BE_write_uint32(ofile, 0x3F800000);
           BE_write_uint32(ofile, 0x3F800000);
           BE_write_uint16(ofile, 0x00);

           // ROTA
           uint16 ROTA_chunksize = 0x0E;
           BE_write_uint32(ofile, LWO_ROTA);
           BE_write_uint16(ofile, ROTA_chunksize);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint16(ofile, 0x00);

           // FALL
           uint16 FALL_chunksize = 0x10;
           BE_write_uint32(ofile, LWO_FALL);
           BE_write_uint16(ofile, FALL_chunksize);
           BE_write_uint16(ofile, 0x00);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint16(ofile, 0x00);

           // OMAP
           uint16 OMAP_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_OMAP);
           BE_write_uint16(ofile, OMAP_chunksize);
           BE_write_uint16(ofile, 0x0000);

           // CSYS
           uint16 CSYS_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_CSYS);
           BE_write_uint16(ofile, CSYS_chunksize);
           BE_write_uint16(ofile, 0x0000);

           // PROJ
           uint16 PROJ_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_PROJ);
           BE_write_uint16(ofile, PROJ_chunksize);
           BE_write_uint16(ofile, 0x0005);

           // AXIS
           uint16 AXIS_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_AXIS);
           BE_write_uint16(ofile, AXIS_chunksize);
           BE_write_uint16(ofile, 0x0002);

           // IMAG
           uint16 IMAG_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_IMAG);
           BE_write_uint16(ofile, IMAG_chunksize);
           BE_write_uint16(ofile, (mat.basemap + 1));

           // WRAP
           uint16 WRAP_chunksize = 0x04;
           BE_write_uint32(ofile, LWO_WRAP);
           BE_write_uint16(ofile, WRAP_chunksize);
           BE_write_uint16(ofile, 0x0001);
           BE_write_uint16(ofile, 0x0001);

           // WRPW
           uint16 WRPW_chunksize = 0x06;
           BE_write_uint32(ofile, LWO_WRPW);
           BE_write_uint16(ofile, WRPW_chunksize);
           BE_write_uint32(ofile, 0x3F800000);
           BE_write_uint16(ofile, 0x0000);

           // WRPH
           uint16 WRPH_chunksize = 0x06;
           BE_write_uint32(ofile, LWO_WRPH);
           BE_write_uint16(ofile, WRPH_chunksize);
           BE_write_uint32(ofile, 0x3F800000);
           BE_write_uint16(ofile, 0x0000);

           // VMAP
           uint16 VMAP_chunksize = align02(ib.name.length() + 1);
           BE_write_uint32(ofile, LWO_VMAP);
           BE_write_uint16(ofile, VMAP_chunksize);
           write_aligned_string_02(ofile, ib.name.c_str());

           // AAST
           uint16 AAST_chunksize = 0x06;
           BE_write_uint32(ofile, LWO_AAST);
           BE_write_uint16(ofile, AAST_chunksize);
           BE_write_uint16(ofile, 0x0001);
           BE_write_uint32(ofile, 0x3F800000);

           // PIXB
           uint16 PIXB_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_PIXB);
           BE_write_uint16(ofile, PIXB_chunksize);
           BE_write_uint16(ofile, 0x0001);
          }
        // specular map
        if((mat.specmap != 0xFFFFFFFF) && (mat.specmap < data.textures.size()))
          {
           // BLCK
           uint16 BLOK_chunksize = 0xEA + (uint16)align02(ib.name.length() + 1);
           BE_write_uint32(ofile, LWO_BLOK);
           BE_write_uint16(ofile, BLOK_chunksize);

           // IMAP
           uint16 IMAP_chunksize = 0x2A;
           BE_write_uint32(ofile, LWO_IMAP);
           BE_write_uint16(ofile, IMAP_chunksize);
           BE_write_uint16(ofile, 0x8000); // ordinal

           // CHAN
           uint16 CHAN_chunksize = 0x04;
           BE_write_uint32(ofile, LWO_CHAN);
           BE_write_uint16(ofile, CHAN_chunksize);
           BE_write_uint32(ofile, LWO_SPEC);

           // OPAC
           uint16 OPAC_chunksize = 0x08;
           BE_write_uint32(ofile, LWO_OPAC);
           BE_write_uint16(ofile, OPAC_chunksize);
           BE_write_uint16(ofile, 0x00);
           BE_write_uint32(ofile, 0x3F800000);
           BE_write_uint16(ofile, 0x00);

           // ENAB
           uint16 ENAB_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_ENAB);
           BE_write_uint16(ofile, ENAB_chunksize);
           BE_write_uint16(ofile, 0x01);

           // NEGA
           uint16 NEGA_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_NEGA);
           BE_write_uint16(ofile, NEGA_chunksize);
           BE_write_uint16(ofile, 0x00);

           // TMAP
           uint16 TMAP_chunksize = 0x5A;
           BE_write_uint32(ofile, LWO_TMAP);
           BE_write_uint16(ofile, TMAP_chunksize);

           // CNTR
           uint16 CNTR_chunksize = 0x0E;
           BE_write_uint32(ofile, LWO_CNTR);
           BE_write_uint16(ofile, CNTR_chunksize);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint16(ofile, 0x00);

           // SIZE
           uint16 SIZE_chunksize = 0x0E;
           BE_write_uint32(ofile, LWO_SIZE);
           BE_write_uint16(ofile, SIZE_chunksize);
           BE_write_uint32(ofile, 0x3F800000);
           BE_write_uint32(ofile, 0x3F800000);
           BE_write_uint32(ofile, 0x3F800000);
           BE_write_uint16(ofile, 0x00);

           // ROTA
           uint16 ROTA_chunksize = 0x0E;
           BE_write_uint32(ofile, LWO_ROTA);
           BE_write_uint16(ofile, ROTA_chunksize);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint16(ofile, 0x00);

           // FALL
           uint16 FALL_chunksize = 0x10;
           BE_write_uint32(ofile, LWO_FALL);
           BE_write_uint16(ofile, FALL_chunksize);
           BE_write_uint16(ofile, 0x00);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint32(ofile, 0x00000000);
           BE_write_uint16(ofile, 0x00);

           // OMAP
           uint16 OMAP_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_OMAP);
           BE_write_uint16(ofile, OMAP_chunksize);
           BE_write_uint16(ofile, 0x0000);

           // CSYS
           uint16 CSYS_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_CSYS);
           BE_write_uint16(ofile, CSYS_chunksize);
           BE_write_uint16(ofile, 0x0000);

           // PROJ
           uint16 PROJ_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_PROJ);
           BE_write_uint16(ofile, PROJ_chunksize);
           BE_write_uint16(ofile, 0x0005);

           // AXIS
           uint16 AXIS_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_AXIS);
           BE_write_uint16(ofile, AXIS_chunksize);
           BE_write_uint16(ofile, 0x0002);

           // IMAG
           uint16 IMAG_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_IMAG);
           BE_write_uint16(ofile, IMAG_chunksize);
           BE_write_uint16(ofile, (mat.specmap + 1));

           // WRAP
           uint16 WRAP_chunksize = 0x04;
           BE_write_uint32(ofile, LWO_WRAP);
           BE_write_uint16(ofile, WRAP_chunksize);
           BE_write_uint16(ofile, 0x0001);
           BE_write_uint16(ofile, 0x0001);

           // WRPW
           uint16 WRPW_chunksize = 0x06;
           BE_write_uint32(ofile, LWO_WRPW);
           BE_write_uint16(ofile, WRPW_chunksize);
           BE_write_uint32(ofile, 0x3F800000);
           BE_write_uint16(ofile, 0x0000);

           // WRPH
           uint16 WRPH_chunksize = 0x06;
           BE_write_uint32(ofile, LWO_WRPH);
           BE_write_uint16(ofile, WRPH_chunksize);
           BE_write_uint32(ofile, 0x3F800000);
           BE_write_uint16(ofile, 0x0000);

           // VMAP
           uint16 VMAP_chunksize = align02(ib.name.length() + 1);
           BE_write_uint32(ofile, LWO_VMAP);
           BE_write_uint16(ofile, VMAP_chunksize);
           write_aligned_string_02(ofile, ib.name.c_str());

           // AAST
           uint16 AAST_chunksize = 0x06;
           BE_write_uint32(ofile, LWO_AAST);
           BE_write_uint16(ofile, AAST_chunksize);
           BE_write_uint16(ofile, 0x0001);
           BE_write_uint32(ofile, 0x3F800000);

           // PIXB
           uint16 PIXB_chunksize = 0x02;
           BE_write_uint32(ofile, LWO_PIXB);
           BE_write_uint16(ofile, PIXB_chunksize);
           BE_write_uint16(ofile, 0x0001);
          }
        
        // TODO
        // normal map
        if((mat.normmap != 0xFFFFFFFF) && (mat.normmap < data.textures.size()))
          {
          }
       }
    }

 return true;
}
*/
