From fda66ae00e9a2ab6d3ebaede13b12fa6767cf856 Mon Sep 17 00:00:00 2001 From: Marco Hladik Date: Thu, 29 Jul 2021 17:44:53 +0200 Subject: [PATCH] add IQM format support into lib/picomodel This is a combination of 2 commits. - add IQM format support into lib/picomodel https://github.com/TTimo/GtkRadiant/commit/3408871d79d51b9bf27f0098cd3bc4edd92b105d - Merge illwieckz their portability fix https://github.com/TTimo/GtkRadiant/commit/be993ad4222fc9fc28080a8ba8f962da82c7b1f4 See https://github.com/TTimo/GtkRadiant/pull/668 --- libs/picomodel/CMakeLists.txt | 1 + libs/picomodel/picointernal.h | 4 + libs/picomodel/picomodules.c | 2 + libs/picomodel/pm_fm.h | 4 - libs/picomodel/pm_iqm.c | 359 ++++++++++++++++++++++++++++++++++ 5 files changed, 366 insertions(+), 4 deletions(-) create mode 100644 libs/picomodel/pm_iqm.c diff --git a/libs/picomodel/CMakeLists.txt b/libs/picomodel/CMakeLists.txt index 4800fb46..2e0b9677 100644 --- a/libs/picomodel/CMakeLists.txt +++ b/libs/picomodel/CMakeLists.txt @@ -16,6 +16,7 @@ add_library(picomodel STATIC pm_3ds.c pm_ase.c pm_fm.c pm_fm.h + pm_iqm.c pm_lwo.c pm_md2.c pm_md3.c diff --git a/libs/picomodel/picointernal.h b/libs/picomodel/picointernal.h index 1ec806ad..98b6cf8f 100644 --- a/libs/picomodel/picointernal.h +++ b/libs/picomodel/picointernal.h @@ -80,6 +80,10 @@ extern "C" extern const picoColor_t picoColor_white; /* types */ +#ifndef byte + typedef unsigned char byte; +#endif + typedef struct picoParser_s { const char *buffer; diff --git a/libs/picomodel/picomodules.c b/libs/picomodel/picomodules.c index 77bb4918..406ee16f 100644 --- a/libs/picomodel/picomodules.c +++ b/libs/picomodel/picomodules.c @@ -48,6 +48,7 @@ extern const picoModule_t picoModuleMD2; extern const picoModule_t picoModuleFM; extern const picoModule_t picoModuleLWO; extern const picoModule_t picoModuleTerrain; +extern const picoModule_t picoModuleIQM; @@ -64,6 +65,7 @@ const picoModule_t *picoModules[] = &picoModuleLWO, /* lightwave object */ &picoModuleTerrain, /* picoterrain object */ &picoModuleOBJ, /* wavefront object */ + &picoModuleIQM, /* interquake model */ NULL /* arnold */ }; diff --git a/libs/picomodel/pm_fm.h b/libs/picomodel/pm_fm.h index 6fa317ca..8ffcc405 100644 --- a/libs/picomodel/pm_fm.h +++ b/libs/picomodel/pm_fm.h @@ -76,10 +76,6 @@ #define INFO_HEIGHT 5 #define INFO_Y ( SKINPAGE_HEIGHT - INFO_HEIGHT ) -#ifndef byte - #define byte unsigned char -#endif - // // Generic header on every chunk diff --git a/libs/picomodel/pm_iqm.c b/libs/picomodel/pm_iqm.c new file mode 100644 index 00000000..739cbeff --- /dev/null +++ b/libs/picomodel/pm_iqm.c @@ -0,0 +1,359 @@ +/* ----------------------------------------------------------------------------- + + InterQuake Model - PicoModel Library + + Copyright (c) 2018-2021, FTE Team + 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 */ +}; -- 2.39.2