add IQM format support into lib/picomodel
[xonotic/netradiant.git] / libs / picomodel / pm_iqm.c
1 /* -----------------------------------------------------------------------------
2
3    InterQuake Model - PicoModel Library
4
5    Copyright (c) 2018-2021, FTE Team <fteqw.org>
6    All rights reserved.
7
8    Redistribution and use in source and binary forms, with or without modification,
9    are permitted provided that the following conditions are met:
10
11    Redistributions of source code must retain the above copyright notice, this list
12    of conditions and the following disclaimer.
13
14    Redistributions in binary form must reproduce the above copyright notice, this
15    list of conditions and the following disclaimer in the documentation and/or
16    other materials provided with the distribution.
17
18    Neither the names of the copyright holders nor the names of its contributors may
19    be used to endorse or promote products derived from this software without
20    specific prior written permission.
21
22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
23    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
26    ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
29    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33    ----------------------------------------------------------------------------- */
34
35 /* dependencies */
36 #include "picointernal.h"
37
38 extern const picoModule_t picoModuleIQM;
39
40 #define IQM_MAGIC "INTERQUAKEMODEL"     //15+null
41
42 /*
43    ========================================================================
44
45    .IQM triangle model file format
46
47    ========================================================================
48  */
49
50 enum
51 {
52         IQM_POSITION = 0,
53         IQM_TEXCOORD = 1,
54         IQM_NORMAL = 2,
55         IQM_TANGENT = 3,
56         IQM_BLENDINDEXES = 4,
57         IQM_BLENDWEIGHTS = 5,
58         IQM_COLOR = 6,
59         IQM_CUSTOM = 0x10
60 };
61
62 enum
63 {
64         IQM_BYTE = 0,
65         IQM_UBYTE = 1,
66         IQM_SHORT = 2,
67         IQM_USHORT = 3,
68         IQM_INT = 4,
69         IQM_UINT = 5,
70         IQM_HALF = 6,
71         IQM_FLOAT = 7,
72         IQM_DOUBLE = 8
73 };
74
75 // animflags
76 #define IQM_LOOP 1
77
78 typedef struct iqmHeader_s {
79         byte id[16];
80         unsigned int version;
81         unsigned int filesize;
82         unsigned int flags;
83         unsigned int num_text, ofs_text;
84         unsigned int num_meshes, ofs_meshes;
85         unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays;
86         unsigned int num_triangles, ofs_triangles, ofs_neighbors;
87         unsigned int num_joints, ofs_joints;
88         unsigned int num_poses, ofs_poses;
89         unsigned int num_anims, ofs_anims;
90         unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds;
91         unsigned int num_comment, ofs_comment;
92         unsigned int num_extensions, ofs_extensions;
93 } iqmHeader_t;
94
95 typedef struct iqmmesh_s {
96         unsigned int name;
97         unsigned int material;
98         unsigned int first_vertex;
99         unsigned int num_vertexes;
100         unsigned int first_triangle;
101         unsigned int num_triangles;
102 } iqmmesh_t;
103
104 typedef struct iqmvertexarray_s {
105         unsigned int type;
106         unsigned int flags;
107         unsigned int format;
108         unsigned int size;
109         unsigned int offset;
110 } iqmvertexarray_t;
111
112 //is anyone actually going to run this on a big-endian cpu?
113 static iqmHeader_t SwapHeader(const iqmHeader_t *h)
114 {
115         iqmHeader_t r = *h;
116         r.version = _pico_little_long(h->version);
117         r.filesize = _pico_little_long(h->filesize);
118         r.flags = _pico_little_long(h->flags);
119         r.num_text = _pico_little_long(h->num_text);
120         r.ofs_text = _pico_little_long(h->ofs_text);
121         r.num_meshes = _pico_little_long(h->num_meshes);
122         r.ofs_meshes = _pico_little_long(h->ofs_meshes);
123         r.num_vertexarrays = _pico_little_long(h->num_vertexarrays);
124         r.num_vertexes = _pico_little_long(h->num_vertexes);
125         r.ofs_vertexarrays = _pico_little_long(h->ofs_vertexarrays);
126         r.num_triangles = _pico_little_long(h->num_triangles);
127         r.ofs_triangles = _pico_little_long(h->ofs_triangles);
128         r.ofs_neighbors = _pico_little_long(h->ofs_neighbors);
129         r.num_joints = _pico_little_long(h->num_joints);
130         r.ofs_joints = _pico_little_long(h->ofs_joints);
131         r.num_poses = _pico_little_long(h->num_poses);
132         r.ofs_poses = _pico_little_long(h->ofs_poses);
133         r.num_anims = _pico_little_long(h->num_anims);
134         r.ofs_anims = _pico_little_long(h->ofs_anims);
135         r.num_frames = _pico_little_long(h->num_frames);
136         r.num_framechannels = _pico_little_long(h->num_framechannels);
137         r.ofs_frames = _pico_little_long(h->ofs_frames);
138         r.ofs_bounds = _pico_little_long(h->ofs_bounds);
139         r.num_comment = _pico_little_long(h->num_comment);
140         r.ofs_comment = _pico_little_long(h->ofs_comment);
141         r.num_extensions = _pico_little_long(h->num_extensions);
142         r.ofs_extensions = _pico_little_long(h->ofs_extensions);
143         return r;
144 }
145
146 // _iqm_canload()
147 static int _iqm_canload( PM_PARAMS_CANLOAD ){
148         iqmHeader_t h;
149
150         //make sure there's enough data for the header...
151         if ((size_t)bufSize < sizeof(h))
152                 return PICO_PMV_ERROR_SIZE;
153         h = SwapHeader(buffer);
154
155         //make sure its actually an iqm
156         if (memcmp(h.id, IQM_MAGIC, sizeof(h.id)))
157                 return PICO_PMV_ERROR_IDENT;
158         //v1 is flawed, we don't know about anything higher either.
159         if (h.version != 2)
160                 return PICO_PMV_ERROR_VERSION;
161         //make sure its not truncated
162         if ((size_t)h.filesize != (size_t)bufSize)
163                 return PICO_PMV_ERROR_SIZE;
164
165         //looks like we can probably use it.
166         return PICO_PMV_OK;
167 }
168
169 // _iqm_load() loads an interquake model file.
170 static picoModel_t *_iqm_load( PM_PARAMS_LOAD ){
171         picoModel_t     *picoModel;
172         picoSurface_t   *picoSurface;
173         picoShader_t    *picoShader;
174         const float *inf;
175         const byte *inb;
176         picoVec3_t xyz, normal;
177         picoVec2_t st;
178         picoColor_t color;
179
180         iqmHeader_t h;
181         iqmmesh_t m;
182         iqmvertexarray_t a;
183         size_t s, t, j, i;
184         const char *stringtable;
185         char skinname[512];
186         const unsigned int *tri;
187
188         //just in case
189         if (_iqm_canload(fileName, buffer, bufSize) != PICO_PMV_OK)
190         {
191                 _pico_printf( PICO_ERROR, "%s is not an IQM File!", fileName );
192                 return NULL;
193         }
194         h = SwapHeader(buffer);
195         stringtable = (const char*)buffer + h.ofs_text;
196
197         // do frame check
198         if ( h.num_anims != 0 ) {
199                 _pico_printf( PICO_WARNING, "%s has animations! Using base pose only.", fileName );
200         }
201
202         /* create new pico model */
203         picoModel = PicoNewModel();
204         if ( picoModel == NULL ) {
205                 _pico_printf( PICO_ERROR, "Unable to allocate a new model" );
206                 return NULL;
207         }
208
209         /* do model setup */
210         PicoSetModelFrameNum( picoModel, frameNum );
211         PicoSetModelNumFrames( picoModel, 1 ); /* sea */
212         PicoSetModelName( picoModel, fileName );
213         PicoSetModelFileName( picoModel, fileName );
214
215         for (s = 0; s < h.num_meshes; s++)
216         {
217                 m = ((const iqmmesh_t*)((const char*)buffer + h.ofs_meshes))[s];
218                 m.first_triangle = _pico_little_long(m.first_triangle);
219                 m.first_vertex = _pico_little_long(m.first_vertex);
220                 m.material = _pico_little_long(m.material);
221                 m.name = _pico_little_long(m.name);
222                 m.num_triangles = _pico_little_long(m.num_triangles);
223                 m.num_vertexes = _pico_little_long(m.num_vertexes);
224
225                 // allocate new pico surface
226                 picoSurface = PicoNewSurface( picoModel );
227                 if ( picoSurface == NULL ) {
228                         _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" );
229                         PicoFreeModel( picoModel );
230                         return NULL;
231                 }
232
233                 // detox Skin name
234                 memcpy(skinname, stringtable+m.material, sizeof(skinname));
235                 _pico_setfext( skinname, "" );
236                 _pico_unixify( skinname );
237
238                 PicoSetSurfaceType( picoSurface, PICO_TRIANGLES );
239                 PicoSetSurfaceName( picoSurface, stringtable+m.name );
240                 picoShader = PicoNewShader( picoModel );
241                 if ( picoShader == NULL ) {
242                         _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" );
243                         PicoFreeModel( picoModel );
244                         return NULL;
245                 }
246
247                 PicoSetShaderName( picoShader, skinname );
248
249                 // associate current surface with newly created shader
250                 PicoSetSurfaceShader( picoSurface, picoShader );
251
252
253                 // spew the surface's indexes
254                 tri = (const unsigned int *)((const char *)buffer+h.ofs_triangles) + m.first_triangle*3;
255                 for (t = 0; t < m.num_triangles*3; t++)
256                         PicoSetSurfaceIndex( picoSurface, t, _pico_little_long(*tri++) - m.first_vertex );
257
258                 for ( j = 0; j < h.num_vertexarrays; j++)
259                 {
260                         a = ((const iqmvertexarray_t*)((const char*)buffer + h.ofs_vertexarrays))[j];
261                         a.flags = _pico_little_long(a.flags);
262                         a.format = _pico_little_long(a.format);
263                         a.offset = _pico_little_long(a.offset);
264                         a.size = _pico_little_long(a.size);
265                         a.type = _pico_little_long(a.type);
266
267                         switch(a.type)
268                         {
269                         case IQM_POSITION:
270                                 if (a.format == IQM_FLOAT && a.size >= 3)
271                                 {
272                                         inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
273                                         for ( i = 0; i < m.num_vertexes; i++, inf += a.size )
274                                         {
275                                                 xyz[0] = _pico_little_float(inf[0]);
276                                                 xyz[1] = _pico_little_float(inf[1]);
277                                                 xyz[2] = _pico_little_float(inf[2]);
278                                                 PicoSetSurfaceXYZ( picoSurface, i, xyz );
279                                         }
280                                 }
281                                 break;
282                         case IQM_TEXCOORD:
283                                 if (a.format == IQM_FLOAT && a.size >= 2)
284                                 {
285                                         inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
286                                         for ( i = 0; i < m.num_vertexes; i++, inf += a.size )
287                                         {
288                                                 st[0] = _pico_little_float(inf[0]);
289                                                 st[1] = _pico_little_float(inf[1]);
290                                                 PicoSetSurfaceST( picoSurface, 0, i, st );
291                                         }
292                                 }
293                                 break;
294                         case IQM_NORMAL:
295                                 if (a.format == IQM_FLOAT && a.size >= 3)
296                                 {
297                                         inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
298                                         for ( i = 0; i < m.num_vertexes; i++, inf += a.size )
299                                         {
300                                                 normal[0] = _pico_little_float(inf[0]);
301                                                 normal[1] = _pico_little_float(inf[1]);
302                                                 normal[2] = _pico_little_float(inf[2]);
303                                                 PicoSetSurfaceNormal( picoSurface, i, normal );
304                                         }
305                                 }
306                                 break;
307                         case IQM_COLOR:
308                                 if (a.format == IQM_UBYTE && a.size >= 3)
309                                 {
310                                         inb = (const byte*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
311                                         for ( i = 0; i < m.num_vertexes; i++, inb += a.size )
312                                         {
313                                                 color[0] = inb[0];
314                                                 color[1] = inb[1];
315                                                 color[2] = inb[2];
316                                                 color[3] = (a.size>=4)?inb[3]:255;
317                                                 PicoSetSurfaceColor( picoSurface, 0, i, color );
318                                         }
319                                 }
320                                 else if (a.format == IQM_FLOAT && a.size >= 3)
321                                 {
322                                         inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
323                                         for ( i = 0; i < m.num_vertexes; i++, inf += a.size )
324                                         {
325                                                 color[0] = inf[0]*255;
326                                                 color[1] = inf[1]*255;
327                                                 color[2] = inf[2]*255;
328                                                 color[3] = (a.size>=4)?inf[3]*255:255;
329                                                 PicoSetSurfaceColor( picoSurface, 0, i, color );
330                                         }
331                                 }
332                                 break;
333                         case IQM_TANGENT:
334                         case IQM_BLENDINDEXES:
335                         case IQM_BLENDWEIGHTS:
336                         case IQM_CUSTOM:
337                                 break;  // these attributes are not relevant.
338                         }
339                 }
340         }
341
342         return picoModel;
343 }
344
345 /* pico file format module definition */
346 const picoModule_t picoModuleIQM =
347 {
348         "0.1",                          /* module version string */
349         "InterQuake Model",             /* module display name */
350         "Spoike",                       /* author's name */
351         "2018-2021 FTE Team",           /* module copyright */
352         {
353                 "iqm", NULL, NULL, NULL /* default extensions to use */
354         },
355         _iqm_canload,                   /* validation routine */
356         _iqm_load,                      /* load routine */
357         NULL,                           /* save validation routine */
358         NULL                            /* save routine */
359 };