/****************************************************************************** libx42 - skinned vertex animation library Copyright (C) 2007 HermitWorks Entertainment Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA. ******************************************************************************/ #include "local.h" static void fix_persist_flags( uint *pf, const x42data_t *x42 ); X42_EXPORT size_t X42_CALL x42_GetFileSize( const x42data_t *x42, uint persistFlags ) { uint i; size_t ret = 0; const x42header_t *h; #ifndef LIBX42_NO_PARAM_VALIDATION demand_rz( x42 != NULL, X42_ERR_BADPTR, "Expected valid x42data_t pointer, got NULL instead." ); demand_rz( x42_ValidateHeader( &x42->header ), X42_ERR_BADDATA, "Invalid x42 file header." ); #endif h = &x42->header; switch( h->ident.version ) { case X42_VER_V5: ret += sizeof( x42Header_v5_t ); break; default: return 0; } fix_persist_flags( &persistFlags, x42 ); ret = align( ret, 8 ); ret += sizeof( x42Bone_v5_t ) * h->numBones; ret = align( ret, 8 ); ret += sizeof( x42AnimGroup_v5_t ) * h->numAnimGroups; ret += sizeof( u16[3] ) * h->numPosValues; if( h->modelFlags & X42_MF_UNIFORM_SCALE ) ret += sizeof( u16 ) * h->numScaleValues; else ret += sizeof( u16[3] ) * h->numScaleValues; ret += sizeof( s16[4] ) * h->numRotValues; ret = align( ret, 8 ); ret += sizeof( x42KeyStreamEntry_v5_t ) * h->keyStreamLength; ret = align( ret, 8 ); ret += sizeof( x42Animation_v5_t ) * h->numAnims; ret = align( ret, 8 ); ret += sizeof( x42Tag_v5_t ) * h->numTags; ret = align( ret, 8 ); ret += sizeof( x42Influence_v5_t ) * h->numInfluences; ret = align( ret, 8 ); ret += sizeof( x42LodRange_v5_t ) * h->numLods; ret = align( ret, 8 ); ret += sizeof( x42Group_v5_t ) * h->numGroups; ret = align( ret, 2 ); ret += sizeof( u16[3] ) * h->numVerts; for( i = 0; i < h->numGroups; i++ ) { if( x42->groups[i].maxVertInfluences ) ret += sizeof( u8 ) * ((x42->groups[i].maxVertInfluences - 1) * 2 + 1); } if( persistFlags & X42_PERSIST_NORMALS ) ret += sizeof( s8[3] ) * h->numVerts; if( persistFlags & X42_PERSIST_TANGENT_BASIS ) ret += sizeof( s8[7] ) * h->numVerts; if( persistFlags & X42_PERSIST_TEXTURE_COORDINATES ) { ret = align( ret, 2 ); ret += sizeof( u16[2] ) * h->numVerts; } if( persistFlags & X42_PERSIST_COLORS ) { ret = align( ret, 4 ); ret += sizeof( rgba_t ) * h->numVerts; } ret = align( ret, 4 ); ret += sizeof( x42index_t ) * h->numIndices; return ret; } static void fix_persist_flags( uint *pf, const x42data_t *x42 ) { if( !x42->vertNorm ) *pf &= ~X42_PERSIST_NORMALS; if( !x42->vertTan ) *pf &= ~X42_PERSIST_TANGENT_BASIS; if( !x42->vertTc ) *pf &= ~X42_PERSIST_TEXTURE_COORDINATES; if( !x42->vertCl ) *pf &= ~X42_PERSIST_COLORS; } static void translate_header( x42Header_v5_t *out, const x42data_t *x42, uint persistFlags ) { const x42header_t *in = &x42->header; out->modelFlags = in->modelFlags & ~X42_MF_HAS_ALL_DATA; if( persistFlags & X42_PERSIST_NORMALS ) out->modelFlags |= X42_MF_HAS_NORMALS; if( persistFlags & X42_PERSIST_TANGENT_BASIS ) out->modelFlags |= X42_MF_HAS_TANGENT_BASIS; if( persistFlags & X42_PERSIST_TEXTURE_COORDINATES ) out->modelFlags |= X42_MF_HAS_TEXTURE_COORDINATES; if( persistFlags & X42_PERSIST_COLORS ) out->modelFlags |= X42_MF_HAS_COLORS; out->baseFrame = in->baseFrame; out->numFrames = in->numFrames; out->nameBlobLen = in->nameBlobLen; out->numBones = in->numBones; out->numAnimGroups = in->numAnimGroups; out->numPosValues = in->numPosValues; out->numScaleValues = in->numScaleValues; out->numRotValues = in->numRotValues; out->keyStreamLength = in->keyStreamLength; out->numAnims = in->numAnims; out->numTags = in->numTags; out->numInfluences = in->numInfluences; out->numLods = in->numLods; out->numGroups = in->numGroups; out->numVerts = in->numVerts; out->numIndices = in->numIndices; out->boundingSphere = in->boundingSphere; out->boundingBox = in->boundingBox; } #ifdef min #undef min #endif #ifdef max #undef max #endif #define min( a, b ) ((a) < (b) ? (a) : (b)) #define max( a, b ) ((a) > (b) ? (a) : (b)) #define min3( a, b, c ) (min( a, min( b, c ) )) #define max3( a, b, c ) (max( a, max( b, c ) )) static void get_stream_bounds( vec2_t *out, uint components, const float *data, uint elements, size_t elemStride ) { uint i; if( !elements ) { memset( out, 0, sizeof( vec2_t ) * components ); return; } for( i = 0; i < components; i++ ) out[i][0] = out[i][1] = data[i]; data = (float*)((byte*)data + elemStride); for( i = 1; i < elements; i++ ) { uint c; for( c = 0; c < components; c++ ) { float f = data[c]; if( f < out[c][0] ) out[c][0] = f; if( f > out[c][1] ) out[c][1] = f; } data = (float*)((byte*)data + elemStride); } } static void get_pack_header( x42PackHeader_v5_t *pack, const x42Header_v5_t *h, const x42data_t *x42 ) { vec2_t bounds[3]; memset( pack, 0, sizeof( pack ) ); get_stream_bounds( pack->animPosPack, 3, (float*)x42->posValues, h->numPosValues, sizeof( vec3_t ) ); get_stream_bounds( bounds, 3, (float*)x42->scaleValues, h->numScaleValues, sizeof( vec3_t ) ); pack->animScalePack[0] = min3( bounds[0][0], bounds[1][0], bounds[2][0] ); pack->animScalePack[1] = max3( bounds[0][1], bounds[1][1], bounds[2][1] ); get_stream_bounds( pack->vertPosPack, 3, (float*)&x42->vertPos[0].pos, h->numVerts, sizeof( x42vertAnim_t ) ); if( x42->vertTc ) get_stream_bounds( pack->vertTcPack, 2, (float*)x42->vertTc, h->numVerts, sizeof( vec2_t ) ); } size_t write_n( const void *buffer, size_t cb, x42outStream_t *file_stream ) { byte *buf = (byte*)buffer; size_t cbWritten = 0; while( cb ) { size_t cr = file_stream->write_fn( buf, cb, file_stream->baton ); demand_rv( cr <= cb, X42_ERR_BADSTREAM, "stream wrote more data than provided", cbWritten ); demand_rv( cr != 0, X42_ERR_EOF, "The write stream aborted.", cbWritten ); cbWritten += cr; buf += cr; cb -= cr; } return cbWritten; } static bool align_stream( x42outStream_t *file_stream, size_t *pos, size_t a ) { size_t ofs = ((a - (*pos % a)) % a); #ifdef LIBX42_NO_ALLOCA byte tmp[128]; assert( a < sizeof( tmp ), X42_ERR_INTERNAL, "crazy huge alignment value" ); #else byte *tmp = ofs ? (byte*)_alloca( ofs ) : NULL; #endif memset( tmp, 0, ofs ); //write zeroes if( ofs ) { size_t ofsWritten = write_n( tmp, ofs, file_stream ); demand_rf( ofs == ofsWritten, X42_ERR_INTERNAL, "failed to align stream" ); *pos += ofsWritten; } demand_rf( *pos % a == 0, X42_ERR_INTERNAL, "failed to align stream" ); return true; } static bool write_packed_floats( x42outStream_t *file_stream, size_t *outPos, const vec2_t *bounds, uint components, const float *data, uint elements, size_t elemStride ) { uint i; float scale[4], bias[4]; size_t cbElem; if( !elements ) return true; for( i = 0; i < components; i++ ) { bias[i] = -bounds[i][0]; scale[i] = 65530.0F / (bounds[i][1] - bounds[i][0]); } cbElem = sizeof( u16 ) * components; for( i = 0; i < elements; i++ ) { uint c; u16 buf[4]; for( c = 0; c < components; c++ ) { float f = data[c]; if( f < bounds[c][0] ) f = bounds[c][0]; if( f > bounds[c][1] ) f = bounds[c][1]; buf[c] = (u16)((f + bias[c]) * scale[c]); } data = (float*)((byte*)data + elemStride); if( write_n( buf, cbElem, file_stream ) != cbElem ) return false; } *outPos += cbElem * elements; return true; } static bool write_packed_floats_s16n( x42outStream_t *file_stream, size_t *outPos, uint components, const float *data, uint elements, size_t elemStride ) { uint i; size_t cbElem; if( !elements ) return true; cbElem = sizeof( s16 ) * components; for( i = 0; i < elements; i++ ) { uint c; s16 buf[4]; for( c = 0; c < components; c++ ) { float f = data[c]; if( f < -1 ) f = -1; if( f > 1 ) f = 1; buf[c] = X42_FLOAT_S16N_PACK( f ); } data = (float*)((byte*)data + elemStride); if( write_n( buf, cbElem, file_stream ) != cbElem ) return false; } *outPos += cbElem * elements; return true; } static bool write_packed_floats_s8n( x42outStream_t *file_stream, size_t *outPos, uint components, const float *data, uint elements, size_t elemStride ) { uint i; size_t cbElem; if( !elements ) return true; cbElem = sizeof( s8 ) * components; for( i = 0; i < elements; i++ ) { uint c; s8 buf[8]; for( c = 0; c < components; c++ ) { float f = data[c]; if( f < -1 ) f = -1; if( f > 1 ) f = 1; buf[c] = X42_FLOAT_S8N_PACK( f ); } data = (float*)((byte*)data + elemStride); if( write_n( buf, cbElem, file_stream ) != cbElem ) return false; } *outPos += cbElem * elements; return true; } X42_EXPORT bool X42_CALL x42_WriteToStream( x42outStream_t *file_stream, uint persist_flags, const x42data_t *x42 ) { uint i; size_t outPos; bool stat; const x42header_t *h; x42Header_ident_t ident; x42Header_v5_t wh; x42PackHeader_v5_t pack; #ifndef LIBX42_NO_PARAM_VALIDATION demand_rf( file_stream != NULL, X42_ERR_BADPTR, "file_stream is NULL" ); demand_rf( x42 != NULL, X42_ERR_BADPTR, "Expected valid x42data_t pointer, got NULL instead." ); demand_rf( x42_ValidateHeader( &x42->header ), X42_ERR_BADDATA, "Invalid x42 file data header." ); #endif h = &x42->header; ident = h->ident; ident.version = X42_VER_V5; fix_persist_flags( &persist_flags, x42 ); translate_header( &wh, x42, persist_flags ); get_pack_header( &pack, &wh, x42 ); #define write_check() demand_rf( stat, X42_ERR_INTERNAL, "failed to write to stream" ) #define checked_write( buf, size ) \ if( !const_cond_false ) \ { \ size_t cb = (size); \ stat = write_n( buf, cb, file_stream ) == cb; \ \ write_check(); \ outPos += cb; \ } \ else \ (void)0 #define checked_align( a ) \ if( !const_cond_false ) \ { \ stat = align_stream( file_stream, &outPos, a ); \ demand_rf( stat, X42_ERR_INTERNAL, "failed to align stream" ); \ } \ else \ (void)0 #define checked_write_a( buf, size, a ) \ if( !const_cond_false ) \ { \ checked_align( a ); \ checked_write( buf, size ); \ } \ else \ (void)0 outPos = 0; checked_write( &ident, sizeof( ident ) ); checked_write( &wh, sizeof( wh ) ); checked_write( &pack, sizeof( pack ) ); checked_write_a( x42->bones, sizeof( x42Bone_v5_t ) * h->numBones, 8 ); checked_write_a( x42->animGroups, sizeof( x42AnimGroup_v5_t ) * h->numAnimGroups, 8 ); checked_align( 2 ); stat = write_packed_floats( file_stream, &outPos, pack.animPosPack, 3, (float*)x42->posValues, h->numPosValues, sizeof( vec3_t ) ); write_check(); if( wh.modelFlags & X42_MF_UNIFORM_SCALE ) { stat = write_packed_floats( file_stream, &outPos, &pack.animScalePack, 1, (float*)x42->scaleValues, h->numScaleValues, sizeof( vec3_t ) ); write_check(); } else { vec2_t bdxyz[3]; bdxyz[0][0] = bdxyz[1][0] = bdxyz[2][0] = pack.animScalePack[0]; bdxyz[0][1] = bdxyz[1][1] = bdxyz[2][1] = pack.animScalePack[1]; stat = write_packed_floats( file_stream, &outPos, bdxyz, 3, (float*)x42->scaleValues, h->numScaleValues, sizeof( vec3_t ) ); write_check(); } stat = write_packed_floats_s16n( file_stream, &outPos, 4, (float*)x42->rotValues, h->numRotValues, sizeof( quat_t ) ); write_check(); checked_write_a( x42->keyStream, sizeof( x42KeyStreamEntry_v5_t ) * h->keyStreamLength, 8 ); checked_write_a( x42->animations, sizeof( x42Animation_v5_t ) * h->numAnims, 8 ); checked_write_a( x42->tags, sizeof( x42Tag_v5_t ) * h->numTags, 8 ); checked_write_a( x42->influences, sizeof( x42influence_t ) * h->numInfluences, 8 ); checked_write_a( x42->lods, sizeof( x42LodRange_v5_t ) * h->numLods, 8 ); checked_write_a( x42->groups, sizeof( x42Group_v5_t ) * h->numGroups, 8 ); checked_align( 2 ); stat = write_packed_floats( file_stream, &outPos, pack.vertPosPack, 3, (float*)&x42->vertPos[0].pos, h->numVerts, sizeof( x42vertAnim_t ) ); write_check(); for( i = 0; i < h->numGroups; i++ ) { uint j; const x42group_t *g = x42->groups + i; size_t cbElem; if( !g->maxVertInfluences ) continue; cbElem = sizeof( byte ) * ((g->maxVertInfluences - 1) * 2 + 1); for( j = 0; j < g->numVerts; j++ ) { u8 out[7]; const x42vertAnim_t *v = x42->vertPos + g->firstVert + j; out[0] = v->idx[0]; out[1] = X42_FLOAT_U8N_PACK( v->wt[0] ); out[2] = v->idx[1]; out[3] = X42_FLOAT_U8N_PACK( v->wt[1] ); out[4] = v->idx[2]; out[5] = X42_FLOAT_U8N_PACK( v->wt[2] ); out[6] = v->idx[3]; checked_write( out, cbElem ); } } if( persist_flags & X42_PERSIST_NORMALS ) { stat = write_packed_floats_s8n( file_stream, &outPos, 3, (float*)&x42->vertNorm[0].norm, h->numVerts, sizeof( x42vertNormal_t ) ); write_check(); } if( persist_flags & X42_PERSIST_TANGENT_BASIS ) { stat = write_packed_floats_s8n( file_stream, &outPos, 7, (float*)&x42->vertTan[0].tan, h->numVerts, sizeof( x42vertTangent_t ) ); write_check(); } if( persist_flags & X42_PERSIST_TEXTURE_COORDINATES ) { checked_align( 2 ); stat = write_packed_floats( file_stream, &outPos, pack.vertTcPack, 2, (float*)x42->vertTc, h->numVerts, sizeof( vec2_t ) ); write_check(); } if( persist_flags & X42_PERSIST_COLORS ) { checked_write_a( x42->vertCl, sizeof( rgba_t ) * h->numVerts, 4 ); } checked_write_a( x42->indices, sizeof( x42index_t ) * h->numIndices, 4 ); checked_write( x42->strings, sizeof( u8 ) * h->nameBlobLen ); #undef checked_align #undef checked_write #undef checked_write_a #undef write_check return true; } typedef struct bufStm_t { byte *pBuf; size_t cb; } bufStm_t; static size_t X42_CALL bufStm_write( const void *buffer, size_t cb, void *baton ) { bufStm_t *s = (bufStm_t*)baton; if( !s->cb ) return 0; if( cb > s->cb ) cb = s->cb; memcpy( s->pBuf, buffer, cb ); s->pBuf += cb; s->cb -= cb; return cb; } X42_EXPORT bool X42_CALL x42_WriteToBuffer( void *out_buffer, uint persist_flags, const x42data_t *x42 ) { bufStm_t s; x42outStream_t stm; #ifndef LIBX42_NO_PARAM_VALIDATION demand_rf( out_buffer != NULL, X42_ERR_BADPTR, "out_buffer is NULL" ); demand_rf( x42 != NULL, X42_ERR_BADPTR, "Expected valid x42data_t pointer, got NULL instead." ); demand_rf( x42_ValidateHeader( &x42->header ), X42_ERR_BADDATA, "Invalid x42 file data header." ); #endif s.pBuf = (byte*)out_buffer; s.cb = x42_GetFileSize( x42, persist_flags ); stm.write_fn = bufStm_write; stm.baton = &s; return x42_WriteToStream( &stm, persist_flags, x42 ); }