mirror of
https://github.com/TTimo/GtkRadiant.git
synced 2024-11-10 07:11:54 +00:00
359 lines
11 KiB
C
359 lines
11 KiB
C
/* -----------------------------------------------------------------------------
|
|
|
|
InterQuake Model - PicoModel Library
|
|
|
|
Copyright (c) 2018-2021, FTE Team <fteqw.org>
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
are permitted provided that the following conditions are met:
|
|
|
|
Redistributions of source code must retain the above copyright notice, this list
|
|
of conditions and the following disclaimer.
|
|
|
|
Redistributions in binary form must reproduce the above copyright notice, this
|
|
list of conditions and the following disclaimer in the documentation and/or
|
|
other materials provided with the distribution.
|
|
|
|
Neither the names of the copyright holders nor the names of its contributors may
|
|
be used to endorse or promote products derived from this software without
|
|
specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
----------------------------------------------------------------------------- */
|
|
|
|
/* dependencies */
|
|
#include "picointernal.h"
|
|
|
|
extern const picoModule_t picoModuleIQM;
|
|
|
|
#define IQM_MAGIC "INTERQUAKEMODEL" //15+null
|
|
|
|
/*
|
|
========================================================================
|
|
|
|
.IQM triangle model file format
|
|
|
|
========================================================================
|
|
*/
|
|
|
|
enum
|
|
{
|
|
IQM_POSITION = 0,
|
|
IQM_TEXCOORD = 1,
|
|
IQM_NORMAL = 2,
|
|
IQM_TANGENT = 3,
|
|
IQM_BLENDINDEXES = 4,
|
|
IQM_BLENDWEIGHTS = 5,
|
|
IQM_COLOR = 6,
|
|
IQM_CUSTOM = 0x10
|
|
};
|
|
|
|
enum
|
|
{
|
|
IQM_BYTE = 0,
|
|
IQM_UBYTE = 1,
|
|
IQM_SHORT = 2,
|
|
IQM_USHORT = 3,
|
|
IQM_INT = 4,
|
|
IQM_UINT = 5,
|
|
IQM_HALF = 6,
|
|
IQM_FLOAT = 7,
|
|
IQM_DOUBLE = 8
|
|
};
|
|
|
|
// animflags
|
|
#define IQM_LOOP 1
|
|
|
|
typedef struct iqmHeader_s {
|
|
byte id[16];
|
|
unsigned int version;
|
|
unsigned int filesize;
|
|
unsigned int flags;
|
|
unsigned int num_text, ofs_text;
|
|
unsigned int num_meshes, ofs_meshes;
|
|
unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays;
|
|
unsigned int num_triangles, ofs_triangles, ofs_neighbors;
|
|
unsigned int num_joints, ofs_joints;
|
|
unsigned int num_poses, ofs_poses;
|
|
unsigned int num_anims, ofs_anims;
|
|
unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds;
|
|
unsigned int num_comment, ofs_comment;
|
|
unsigned int num_extensions, ofs_extensions;
|
|
} iqmHeader_t;
|
|
|
|
typedef struct iqmmesh_s {
|
|
unsigned int name;
|
|
unsigned int material;
|
|
unsigned int first_vertex;
|
|
unsigned int num_vertexes;
|
|
unsigned int first_triangle;
|
|
unsigned int num_triangles;
|
|
} iqmmesh_t;
|
|
|
|
typedef struct iqmvertexarray_s {
|
|
unsigned int type;
|
|
unsigned int flags;
|
|
unsigned int format;
|
|
unsigned int size;
|
|
unsigned int offset;
|
|
} iqmvertexarray_t;
|
|
|
|
//is anyone actually going to run this on a big-endian cpu?
|
|
static iqmHeader_t SwapHeader(const iqmHeader_t *h)
|
|
{
|
|
iqmHeader_t r = *h;
|
|
r.version = _pico_little_long(h->version);
|
|
r.filesize = _pico_little_long(h->filesize);
|
|
r.flags = _pico_little_long(h->flags);
|
|
r.num_text = _pico_little_long(h->num_text);
|
|
r.ofs_text = _pico_little_long(h->ofs_text);
|
|
r.num_meshes = _pico_little_long(h->num_meshes);
|
|
r.ofs_meshes = _pico_little_long(h->ofs_meshes);
|
|
r.num_vertexarrays = _pico_little_long(h->num_vertexarrays);
|
|
r.num_vertexes = _pico_little_long(h->num_vertexes);
|
|
r.ofs_vertexarrays = _pico_little_long(h->ofs_vertexarrays);
|
|
r.num_triangles = _pico_little_long(h->num_triangles);
|
|
r.ofs_triangles = _pico_little_long(h->ofs_triangles);
|
|
r.ofs_neighbors = _pico_little_long(h->ofs_neighbors);
|
|
r.num_joints = _pico_little_long(h->num_joints);
|
|
r.ofs_joints = _pico_little_long(h->ofs_joints);
|
|
r.num_poses = _pico_little_long(h->num_poses);
|
|
r.ofs_poses = _pico_little_long(h->ofs_poses);
|
|
r.num_anims = _pico_little_long(h->num_anims);
|
|
r.ofs_anims = _pico_little_long(h->ofs_anims);
|
|
r.num_frames = _pico_little_long(h->num_frames);
|
|
r.num_framechannels = _pico_little_long(h->num_framechannels);
|
|
r.ofs_frames = _pico_little_long(h->ofs_frames);
|
|
r.ofs_bounds = _pico_little_long(h->ofs_bounds);
|
|
r.num_comment = _pico_little_long(h->num_comment);
|
|
r.ofs_comment = _pico_little_long(h->ofs_comment);
|
|
r.num_extensions = _pico_little_long(h->num_extensions);
|
|
r.ofs_extensions = _pico_little_long(h->ofs_extensions);
|
|
return r;
|
|
}
|
|
|
|
// _iqm_canload()
|
|
static int _iqm_canload( PM_PARAMS_CANLOAD ){
|
|
iqmHeader_t h;
|
|
|
|
//make sure there's enough data for the header...
|
|
if ((size_t)bufSize < sizeof(h))
|
|
return PICO_PMV_ERROR_SIZE;
|
|
h = SwapHeader(buffer);
|
|
|
|
//make sure its actually an iqm
|
|
if (memcmp(h.id, IQM_MAGIC, sizeof(h.id)))
|
|
return PICO_PMV_ERROR_IDENT;
|
|
//v1 is flawed, we don't know about anything higher either.
|
|
if (h.version != 2)
|
|
return PICO_PMV_ERROR_VERSION;
|
|
//make sure its not truncated
|
|
if ((size_t)h.filesize != (size_t)bufSize)
|
|
return PICO_PMV_ERROR_SIZE;
|
|
|
|
//looks like we can probably use it.
|
|
return PICO_PMV_OK;
|
|
}
|
|
|
|
// _iqm_load() loads an interquake model file.
|
|
static picoModel_t *_iqm_load( PM_PARAMS_LOAD ){
|
|
picoModel_t *picoModel;
|
|
picoSurface_t *picoSurface;
|
|
picoShader_t *picoShader;
|
|
const float *inf;
|
|
const byte *inb;
|
|
picoVec3_t xyz, normal;
|
|
picoVec2_t st;
|
|
picoColor_t color;
|
|
|
|
iqmHeader_t h;
|
|
iqmmesh_t m;
|
|
iqmvertexarray_t a;
|
|
size_t s, t, j, i;
|
|
const char *stringtable;
|
|
char skinname[512];
|
|
const unsigned int *tri;
|
|
|
|
//just in case
|
|
if (_iqm_canload(fileName, buffer, bufSize) != PICO_PMV_OK)
|
|
{
|
|
_pico_printf( PICO_ERROR, "%s is not an IQM File!", fileName );
|
|
return NULL;
|
|
}
|
|
h = SwapHeader(buffer);
|
|
stringtable = (const char*)buffer + h.ofs_text;
|
|
|
|
// do frame check
|
|
if ( h.num_anims != 0 ) {
|
|
_pico_printf( PICO_WARNING, "%s has animations! Using base pose only.", fileName );
|
|
}
|
|
|
|
/* create new pico model */
|
|
picoModel = PicoNewModel();
|
|
if ( picoModel == NULL ) {
|
|
_pico_printf( PICO_ERROR, "Unable to allocate a new model" );
|
|
return NULL;
|
|
}
|
|
|
|
/* do model setup */
|
|
PicoSetModelFrameNum( picoModel, frameNum );
|
|
PicoSetModelNumFrames( picoModel, 1 ); /* sea */
|
|
PicoSetModelName( picoModel, fileName );
|
|
PicoSetModelFileName( picoModel, fileName );
|
|
|
|
for (s = 0; s < h.num_meshes; s++)
|
|
{
|
|
m = ((const iqmmesh_t*)((const char*)buffer + h.ofs_meshes))[s];
|
|
m.first_triangle = _pico_little_long(m.first_triangle);
|
|
m.first_vertex = _pico_little_long(m.first_vertex);
|
|
m.material = _pico_little_long(m.material);
|
|
m.name = _pico_little_long(m.name);
|
|
m.num_triangles = _pico_little_long(m.num_triangles);
|
|
m.num_vertexes = _pico_little_long(m.num_vertexes);
|
|
|
|
// allocate new pico surface
|
|
picoSurface = PicoNewSurface( picoModel );
|
|
if ( picoSurface == NULL ) {
|
|
_pico_printf( PICO_ERROR, "Unable to allocate a new model surface" );
|
|
PicoFreeModel( picoModel );
|
|
return NULL;
|
|
}
|
|
|
|
// detox Skin name
|
|
memcpy(skinname, stringtable+m.material, sizeof(skinname));
|
|
_pico_setfext( skinname, "" );
|
|
_pico_unixify( skinname );
|
|
|
|
PicoSetSurfaceType( picoSurface, PICO_TRIANGLES );
|
|
PicoSetSurfaceName( picoSurface, stringtable+m.name );
|
|
picoShader = PicoNewShader( picoModel );
|
|
if ( picoShader == NULL ) {
|
|
_pico_printf( PICO_ERROR, "Unable to allocate a new model shader" );
|
|
PicoFreeModel( picoModel );
|
|
return NULL;
|
|
}
|
|
|
|
PicoSetShaderName( picoShader, skinname );
|
|
|
|
// associate current surface with newly created shader
|
|
PicoSetSurfaceShader( picoSurface, picoShader );
|
|
|
|
|
|
// spew the surface's indexes
|
|
tri = (const unsigned int *)((const char *)buffer+h.ofs_triangles) + m.first_triangle*3;
|
|
for (t = 0; t < m.num_triangles*3; t++)
|
|
PicoSetSurfaceIndex( picoSurface, t, _pico_little_long(*tri++) - m.first_vertex );
|
|
|
|
for ( j = 0; j < h.num_vertexarrays; j++)
|
|
{
|
|
a = ((const iqmvertexarray_t*)((const char*)buffer + h.ofs_vertexarrays))[j];
|
|
a.flags = _pico_little_long(a.flags);
|
|
a.format = _pico_little_long(a.format);
|
|
a.offset = _pico_little_long(a.offset);
|
|
a.size = _pico_little_long(a.size);
|
|
a.type = _pico_little_long(a.type);
|
|
|
|
switch(a.type)
|
|
{
|
|
case IQM_POSITION:
|
|
if (a.format == IQM_FLOAT && a.size >= 3)
|
|
{
|
|
inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
|
|
for ( i = 0; i < m.num_vertexes; i++, inf += a.size )
|
|
{
|
|
xyz[0] = _pico_little_float(inf[0]);
|
|
xyz[1] = _pico_little_float(inf[1]);
|
|
xyz[2] = _pico_little_float(inf[2]);
|
|
PicoSetSurfaceXYZ( picoSurface, i, xyz );
|
|
}
|
|
}
|
|
break;
|
|
case IQM_TEXCOORD:
|
|
if (a.format == IQM_FLOAT && a.size >= 2)
|
|
{
|
|
inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
|
|
for ( i = 0; i < m.num_vertexes; i++, inf += a.size )
|
|
{
|
|
st[0] = _pico_little_float(inf[0]);
|
|
st[1] = _pico_little_float(inf[1]);
|
|
PicoSetSurfaceST( picoSurface, 0, i, st );
|
|
}
|
|
}
|
|
break;
|
|
case IQM_NORMAL:
|
|
if (a.format == IQM_FLOAT && a.size >= 3)
|
|
{
|
|
inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
|
|
for ( i = 0; i < m.num_vertexes; i++, inf += a.size )
|
|
{
|
|
normal[0] = _pico_little_float(inf[0]);
|
|
normal[1] = _pico_little_float(inf[1]);
|
|
normal[2] = _pico_little_float(inf[2]);
|
|
PicoSetSurfaceNormal( picoSurface, i, normal );
|
|
}
|
|
}
|
|
break;
|
|
case IQM_COLOR:
|
|
if (a.format == IQM_UBYTE && a.size >= 3)
|
|
{
|
|
inb = (const byte*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
|
|
for ( i = 0; i < m.num_vertexes; i++, inb += a.size )
|
|
{
|
|
color[0] = inb[0];
|
|
color[1] = inb[1];
|
|
color[2] = inb[2];
|
|
color[3] = (a.size>=4)?inb[3]:255;
|
|
PicoSetSurfaceColor( picoSurface, 0, i, color );
|
|
}
|
|
}
|
|
else if (a.format == IQM_FLOAT && a.size >= 3)
|
|
{
|
|
inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
|
|
for ( i = 0; i < m.num_vertexes; i++, inf += a.size )
|
|
{
|
|
color[0] = inf[0]*255;
|
|
color[1] = inf[1]*255;
|
|
color[2] = inf[2]*255;
|
|
color[3] = (a.size>=4)?inf[3]*255:255;
|
|
PicoSetSurfaceColor( picoSurface, 0, i, color );
|
|
}
|
|
}
|
|
break;
|
|
case IQM_TANGENT:
|
|
case IQM_BLENDINDEXES:
|
|
case IQM_BLENDWEIGHTS:
|
|
case IQM_CUSTOM:
|
|
break; // these attributes are not relevant.
|
|
}
|
|
}
|
|
}
|
|
|
|
return picoModel;
|
|
}
|
|
|
|
/* pico file format module definition */
|
|
const picoModule_t picoModuleIQM =
|
|
{
|
|
"0.1", /* module version string */
|
|
"InterQuake Model", /* module display name */
|
|
"Spoike", /* author's name */
|
|
"2018-2021 FTE Team", /* module copyright */
|
|
{
|
|
"iqm", NULL, NULL, NULL /* default extensions to use */
|
|
},
|
|
_iqm_canload, /* validation routine */
|
|
_iqm_load, /* load routine */
|
|
NULL, /* save validation routine */
|
|
NULL /* save routine */
|
|
};
|